From 43ea71941a9a52f040eeacdf37ae1a4f199b4de9 Mon Sep 17 00:00:00 2001 From: Fred Pauchet Date: Tue, 14 Apr 2020 21:49:49 +0200 Subject: [PATCH] structure architecture and software flow --- ideas/django-patterns.md | 11 --- make.bat | 2 +- resources/themes/basic-theme.yml | 14 +++ source/images/diagrams/architecture.png | Bin 0 -> 25586 bytes source/images/diagrams/django-process.png | Bin 0 -> 21895 bytes .../django-support-lts.png} | Bin source/images/it-works-on-my-machine.jpg | Bin 0 -> 29841 bytes .../images/scenarii-de-plantages.png | Bin source/part-1-workspace/00-main.adoc | 6 +- source/part-1-workspace/environment.adoc | 16 +++- source/part-1-workspace/unit_tests.adoc | 22 ++++- source/part-1-workspace/venvs.adoc | 2 +- source/part-2-deployment/00-main.adoc | 26 +++++- source/part-2-deployment/database.adoc | 7 +- source/part-3-django-concepts/00-main.adoc | 10 ++- source/part-3-django-concepts/auth.adoc | 85 +++++++----------- source/part-9-bonus/00-main.adoc | 5 ++ .../code-snippets.adoc | 0 .../part-9-bonus/legacy.adoc | 4 +- source/resources/themes/gwift-theme.yaml | 8 -- 20 files changed, 127 insertions(+), 91 deletions(-) delete mode 100644 ideas/django-patterns.md create mode 100644 resources/themes/basic-theme.yml create mode 100644 source/images/diagrams/architecture.png create mode 100644 source/images/diagrams/django-process.png rename source/{part-1-workspace/django-lts-support.png => images/django-support-lts.png} (100%) create mode 100644 source/images/it-works-on-my-machine.jpg rename source/{part-1-workspace => }/images/scenarii-de-plantages.png (100%) create mode 100644 source/part-9-bonus/00-main.adoc rename source/{bonus => part-9-bonus}/code-snippets.adoc (100%) rename ideas/legacy.md => source/part-9-bonus/legacy.adoc (90%) delete mode 100644 source/resources/themes/gwift-theme.yaml diff --git a/ideas/django-patterns.md b/ideas/django-patterns.md deleted file mode 100644 index acec614..0000000 --- a/ideas/django-patterns.md +++ /dev/null @@ -1,11 +0,0 @@ -# Django - -[Django](https://www.djangoproject.com/) est l'un des frameworks Web proposant une très bonne intégration des composants, et une flexibilité bien pensée: chacun des composants permet de définir son contenu de manière poussée, en respectant des contraintes logiques et faciles à retenir. - -En restant dans les sentiers battus, votre projet suivra le patron de conception `MVC` (Modèle-Vue-Controleur), avec une petite variante sur les termes utilisés: Django les nomme respectivement Modèle-Template-Vue: - - * Le modèle (`models.py`) fait le lien avec la base de données et permet de définir les champs et leur type à associer à une table. *Grosso modo*, une table SQL correspondra à une classe d'un modèle Django. - * La vue (`views.py`), qui joue le rôle de contrôleur: *a priori*, tous les traitements, la récupération des données, etc. doit passer par ce composant et ne doit (pratiquement) pas être généré à la volée, directement à l'affichage d'une page. - * Le template, qui s'occupe de la mise en forme: c'est le composant qui va s'occuper de transformer les données en un affichage compréhensible (avec l'aide du navigateur) pour l'utilisateur. - - diff --git a/make.bat b/make.bat index eb7a18f..34c806f 100644 --- a/make.bat +++ b/make.bat @@ -38,7 +38,7 @@ if "%1" == "html" ( ) if "%1" == "pdf" ( - asciidoctor-pdf -a pdf-themesdir=resources/themes -a pdf-theme=gwift-theme source/main.adoc -t + asciidoctor-pdf -a pdf-themesdir=resources/themes -a pdf-theme=basic source/main.adoc -t goto end ) diff --git a/resources/themes/basic-theme.yml b/resources/themes/basic-theme.yml new file mode 100644 index 0000000..7c737fd --- /dev/null +++ b/resources/themes/basic-theme.yml @@ -0,0 +1,14 @@ +extends: default +footer: + recto: + right: + content: '{section-or-chapter-title} | {page-number}' + verso: + left: + content: '{page-number} | {chapter-title}' +admonition: + icon: + caution: + name: fa-fire + stroke_color: ff0000 + size: 24 \ No newline at end of file diff --git a/source/images/diagrams/architecture.png b/source/images/diagrams/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..cef440cd692883397facd03ef470bf34fe09f4f6 GIT binary patch literal 25586 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 S5BM*SM{3GiO4SNB(f3nDp*ND@gB1d$|3 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 literal 0 HcmV?d00001 diff --git a/source/part-1-workspace/django-lts-support.png b/source/images/django-support-lts.png similarity index 100% rename from source/part-1-workspace/django-lts-support.png rename to source/images/django-support-lts.png diff --git a/source/images/it-works-on-my-machine.jpg b/source/images/it-works-on-my-machine.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c9dd976a80d23b473d850dab3e6b5f3603ce3615 GIT binary patch literal 29841 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 U2Y>wU@9_VB{lEAEcm5vzUz4R8Pyhe` literal 0 HcmV?d00001 diff --git a/source/part-1-workspace/images/scenarii-de-plantages.png b/source/images/scenarii-de-plantages.png similarity index 100% rename from source/part-1-workspace/images/scenarii-de-plantages.png rename to source/images/scenarii-de-plantages.png diff --git a/source/part-1-workspace/00-main.adoc b/source/part-1-workspace/00-main.adoc index a4f141b..2d7c687 100644 --- a/source/part-1-workspace/00-main.adoc +++ b/source/part-1-workspace/00-main.adoc @@ -4,13 +4,13 @@ Avant de démarrer le développement, il est nécessaire de passer un peu de tem Les morceaux de code seront développés pour Python3.6+ et Django 3.0+. Ils nécessiteront peut-être quelques adaptations pour fonctionner sur une version antérieure. -Django fonctionne sur un link:++roulement de trois versions mineures pour une version majeure++[https://docs.djangoproject.com/en/dev/internals/release-process/], clôturé par une version LTS (_Long Term Support_). +Django fonctionne sur un https://docs.djangoproject.com/en/dev/internals/release-process/[roulement de trois versions mineures pour une version majeure], clôturé par une version LTS (_Long Term Support_). -image:http://ser-libre.com.ar/wp-content/uploads/2016/11/django.png[Support des versions Django] +image::images/django-support-lts.png[] Ce sera une bonne indication à prendre en considération pour nos dépendances, puisqu'en visant une version particulière, on ne devra pratiquement pas se soucier (bon, un peu quand même...) des dépendances à installer, pour peu qu'on reste sous un certain seuil. -Dans cette partie, on va parler de *méthode de travail*, avec un objectif visé. On peut éviter que l'application ne tourne que sur notre machine et que chaque déploiement ne soit une plaie à gérer. Chaque mise à jour doit se limiter au minimum: +Dans cette partie, on va parler de *méthode de travail*, avec un objectif visé. On peut éviter que l'application ne tourne que sur notre machine et que chaque déploiement ne soit une plaie à gérer. Chaque mise à jour doit se limiter à: . démarrer un script, . prévoir un rollback si cela plante diff --git a/source/part-1-workspace/environment.adoc b/source/part-1-workspace/environment.adoc index a307b63..ec2759a 100644 --- a/source/part-1-workspace/environment.adoc +++ b/source/part-1-workspace/environment.adoc @@ -1,8 +1,18 @@ -== Environnement de travail +== Boite à outils -Quelques idées: Git, VSCodium[https://vscodium.com/], un terminal (Si vous êtes sous Windows, cmder[https://cmder.net/]). +Un IDE : + +* https://vscodium.com/[VSCodium], avec les plugins https://marketplace.visualstudio.com/items?itemName=ms-python.python[Python], +* https://www.jetbrains.com/pycharm/[PyCharm] +* https://www.vim.org/[Vim] avec les plugins https://github.com/davidhalter/jedi-vim[Jedi-Vim], https://github.com/preservim/nerdtree[nerdtree] + +Un terminal : + +* Si vous êtes sous Windows, https://cmder.net/[cmder]. Un gestionnaire de base de données ? PHPMyAdmin ou PgAdmin. -Un IDE ? PyCharm, VSCodium, VIM, ... +Un gestionnaire de mots de passe : + +* https://keepassxc.org/[KeepassXC] (on en aura besoin ;-)) diff --git a/source/part-1-workspace/unit_tests.adoc b/source/part-1-workspace/unit_tests.adoc index 9c72c0a..da86ad0 100644 --- a/source/part-1-workspace/unit_tests.adoc +++ b/source/part-1-workspace/unit_tests.adoc @@ -2,9 +2,25 @@ Si on schématise l'infrastructure et le chemin parcouru par une éventuelle requête, on devrait arriver à quelque chose de synthéthique: -User -> Firefox -> http GET 1.1 -> Debian GNU/Linux -> Nginx -> gunicorn -> django -> url -> route -> fonction/classe/méthode -> construction -> accès à la base de données -> retour des données -> rendu -> réponse à l'utilisateur. +* Au niveau de l'infrastructure, + . l'utilisateur fait une requête via son navigateur (Firefox ou Chrome) + . le navigateur envoie une requête http, sa version, un verbe (GET, POST, ...), un port et éventuellement du contenu + . le firewall du serveur (Debian GNU/Linux, CentOS, ...) vérifie si la requête peut être prise en compte + . la requête est transmise à l'application qui écoute sur le port (probablement 80 ou 443; et _a priori_ Nginx) + . elle est ensuite transmise par socket et est prise en compte par Gunicorn + . qui la transmet ensuite à l'un de ses _workers_ (= un processus Python) + . après exécution, une réponse est renvoyée à l'utilisateur. -image::images/scenarii-de-plantages.png +image::images/diagrams/architecture.png[] + +* Au niveau logiciel (la partie mise en subrillance ci-dessus), la requête arrive dans les mains du processus Python, qui doit encore + . effectuer le routage des données, + . trouver la bonne fonction à exécuter, + . récupérer les données depuis la base de données, + . effectuer le rendu ou la conversion des données, + . et renvoyer une réponse à l'utilisateur. + +image::images/diagrams/django-process.png[] En gros, ça peut planter aux points suivants : @@ -14,7 +30,7 @@ En gros, ça peut planter aux points suivants : . Le verbe HTTP n'est pas pris en charge par la fonction . Le système n'a plus de place sur son disque . Nginx est mal configuré -. La communication par socket est mal configuré +. La communication par socket est mal configurée . L'URL est inconnue . La route n'existe pas . La base de dnnées est inaccessible diff --git a/source/part-1-workspace/venvs.adoc b/source/part-1-workspace/venvs.adoc index 27c5ed3..fcec70a 100644 --- a/source/part-1-workspace/venvs.adoc +++ b/source/part-1-workspace/venvs.adoc @@ -9,7 +9,7 @@ Cette pratique est cependant fortement déconseillée pour plusieurs raisons: . Il est tout à fait envisagable que deux applications différentes soient déployées sur un même hôte, et nécessitent chacune deux versions différentes d'une même dépendance. . Pour la reproductibilité d'un environnement spécifique. Cela évite notamment les réponses type "Ca juste marche chez moi", puisque la construction d'un nouvel environnement fait partie intégrante du processus de construction et de la documentation du projet; grace à elle, on a la possibilité de construire un environnement sain et d'appliquer des dépendances identiques, quelle que soit la machine hôte. -image:https://res.cloudinary.com/teepublic/image/private/s--hOdhXtVV--/t_Preview/b_rgb:ffffff,c_limit,f_jpg,h_630,q_90,w_630/v1464028809/production/designs/521845_1.jpg +image::images/it-works-on-my-machine.jpg Depuis la version 3.5 de Python, le module `venv` est https://docs.python.org/3/library/venv.html[recommandé] afin de créer un environnement virtuel. diff --git a/source/part-2-deployment/00-main.adoc b/source/part-2-deployment/00-main.adoc index bff661e..9646a3e 100644 --- a/source/part-2-deployment/00-main.adoc +++ b/source/part-2-deployment/00-main.adoc @@ -1,8 +1,6 @@ = Déploiement -On va déjà parler de déploiement. Le serveur que django met à notre disposition est prévu uniquement pour le développement: inutile de passer par du code Python pour charger des fichiers statiques (feuilles de style, fichiers JavaScript, images, ...). De même, la base de donnée doit supporter plus qu'un seul utilisateur: SQLite fonctionne très bien dès lors qu'on se limite à un seul utilisateur... Sur une application Web, il est plus que probable que vous rencontriez rapidement des erreurs de base de données verrouillée pour écriture par un autre processus. Il est donc plus que bénéfique de passer sur quelque chose de plus solide. - -https://docs.djangoproject.com/fr/3.0/howto/deployment/[Déploiement]. +On va déjà parler de déploiement. Le serveur que django met à notre disposition est prévu uniquement pour le développement: inutile de passer par du code Python pour charger des fichiers statiques (feuilles de style, fichiers JavaScript, images, ...). De même, la base de donnée doit supporter plus qu'un seul utilisateur: SQLite fonctionne très bien dès lors qu'on se limite à un seul utilisateur... Sur une application Web, il est plus que probable que vous rencontriez rapidement des erreurs de base de données verrouillée pour écriture par un autre processus. Il est donc plus que bénéfique de passer sur quelque chose de plus solide. https://docs.djangoproject.com/fr/3.0/howto/deployment/[Déploiement]. Si vous avez suivi les étapes jusqu'ici, vous devriez à peine disposer d'un espace de travail proprement configuré, d'un modèle relativement basique et d'une configuration avec une base de données simpliste. En bref, vous avez quelque chose qui fonctionne, mais qui ressemble de très loin à ce que vous souhaitez au final. @@ -17,6 +15,28 @@ Dans cette partie, on abordera les points suivants: * Les différentes méthodes de supervision de l'application: comment analyser les fichiers de logs et comment intercepter correctement une erreur si elle se présente et comment remonter l'information. * Une partie sur la sécurité et la sécurisation de l'hôte. +Si on schématise l'infrastructure et le chemin parcouru par une éventuelle requête, on devrait arriver à quelque chose de synthéthique: + +* Au niveau de l'infrastructure, + . l'utilisateur fait une requête via son navigateur (Firefox ou Chrome) + . le navigateur envoie une requête http, sa version, un verbe (GET, POST, ...), un port et éventuellement du contenu + . le firewall du serveur (Debian GNU/Linux, CentOS, ...) vérifie si la requête peut être prise en compte + . la requête est transmise à l'application qui écoute sur le port (probablement 80 ou 443; et _a priori_ Nginx) + . elle est ensuite transmise par socket et est prise en compte par Gunicorn + . qui la transmet ensuite à l'un de ses _workers_ (= un processus Python) + . après exécution, une réponse est renvoyée à l'utilisateur. + +image::images/diagrams/architecture.png[] + +* Au niveau logiciel (la partie mise en subrillance ci-dessus), la requête arrive dans les mains du processus Python, qui doit encore + . effectuer le routage des données, + . trouver la bonne fonction à exécuter, + . récupérer les données depuis la base de données, + . effectuer le rendu ou la conversion des données, + . et renvoyer une réponse à l'utilisateur. + +image::images/diagrams/django-process.png[] + == Définition de l'infrastructure Comme on l'a vu dans la première partie, Django est un framework complet, intégrant tous les mécanismes nécessaires à la bonne évolution d'une application. On peut ainsi commencer petit, et suivre l'évolution des besoins en fonction de la charge estimée ou ressentie, ajouter un mécanisme de mise en cache, des logiciels de suivi, ... diff --git a/source/part-2-deployment/database.adoc b/source/part-2-deployment/database.adoc index 544d001..143aa1c 100644 --- a/source/part-2-deployment/database.adoc +++ b/source/part-2-deployment/database.adoc @@ -1,13 +1,14 @@ == Bases de données -On l'a déjà vu, Django se base sur un pattern type https://www.martinfowler.com/eaaCatalog/activeRecord.html[ActiveRecords] pour l'ORM et supporte les principaux moteurs de bases de données connus: +On l'a déjà vu, Django se base sur un pattern type https://www.martinfowler.com/eaaCatalog/activeRecord.html[ActiveRecords] pour la gestion de la persistance des données et supporte les principaux moteurs de bases de données connus: +* SQLite (en natif, mais Django 3.0 exige une version du moteur supérieure ou égale à la 3.8) * MariaDB (en natif depuis Django 3.0), * PostgreSQL au travers de psycopg2 (en natif aussi), * Microsoft SQLServer grâce aux drivers [...à compléter] -* ou Oracle via https://oracle.github.io/python-cx_Oracle/[cx_Oracle]. +* Oracle via https://oracle.github.io/python-cx_Oracle/[cx_Oracle]. -WARNING: Chaque pilote doit être utilisé précautionneusement ! Chaque version de Django n'est pas toujours compatible avec chacune des versions des pilotes, et chaque moteur de base de données nécessite parfois une version spécifique du pilote. Par ce fait, vous serez parfois bloqué sur une version de Django, simplement parce que votre serveur de base de données se trouvera dans une version spécifique (eg. Django 2.3 à cause d'un Oracle 12.1). +CAUTION: Chaque pilote doit être utilisé précautionneusement ! Chaque version de Django n'est pas toujours compatible avec chacune des versions des pilotes, et chaque moteur de base de données nécessite parfois une version spécifique du pilote. Par ce fait, vous serez parfois bloqué sur une version de Django, simplement parce que votre serveur de base de données se trouvera dans une version spécifique (eg. Django 2.3 à cause d'un Oracle 12.1). Ci-dessous, quelques procédures d'installation pour mettre un serveur à disposition. Les deux plus simples seront MariaDB et PostgreSQL, qu'on couvrira ci-dessous. Oracle et Microsoft SQLServer se trouveront en annexes. diff --git a/source/part-3-django-concepts/00-main.adoc b/source/part-3-django-concepts/00-main.adoc index 6fd7ad0..b5d0ffb 100644 --- a/source/part-3-django-concepts/00-main.adoc +++ b/source/part-3-django-concepts/00-main.adoc @@ -1,9 +1,15 @@ = Django -Dans ce chapitre, on va parler de plusieurs concepts utiles au développement rapide d'une application. On parlera de modélisation, de migrations, d'administration auto-générée. +Dans ce chapitre, on va parler de plusieurs concepts utiles au développement rapide d'une application. On parlera de modélisation, de migrations, d'administration auto-générée. C'est un framework Web proposant une très bonne intégration des composants, et une flexibilité bien pensée: chacun des composants permet de définir son contenu de manière poussée, en respectant des contraintes logiques et faciles à retenir. + +En restant dans les sentiers battus, votre projet suivra le patron de conception `MVC` (Modèle-Vue-Controleur), avec une petite variante sur les termes utilisés: Django les nomme respectivement Modèle-Template-Vue: + Dans un *pattern* MVC classique, la traduction immédiate du **contrôleur** est une **vue**. Et comme on le verra par la suite, la **vue** est en fait le **template**. -Les vues agrègent donc les informations à partir d'un des composants et les font transiter vers un autre. En d'autres mots, la vue sert de pont entre les données gérées par la base et l'interface utilisateur. + +* Le modèle (`models.py`) fait le lien avec la base de données et permet de définir les champs et leur type à associer à une table. _Grosso modo_*, une table SQL correspondra à une classe d'un modèle Django. +* La vue (`views.py`), qui joue le rôle de contrôleur: _a priori_, tous les traitements, la récupération des données, etc. doit passer par ce composant et ne doit (pratiquement) pas être généré à la volée, directement à l'affichage d'une page. En d'autres mots, la vue sert de pont entre les données gérées par la base et l'interface utilisateur. +* Le template, qui s'occupe de la mise en forme: c'est le composant qui va s'occuper de transformer les données en un affichage compréhensible (avec l'aide du navigateur) pour l'utilisateur. Pour reprendre une partie du schéma précédent, on a une requête qui est émise par un utilisateur. La première étape consiste à trouver une route qui correspond à cette requête, c'est à dire à trouver la correspondance entre l'URL demandée et la fonction qui sera exécutée. Cette fonction correspond au *contrôleur* et s'occupera de construire le *modèle* correspondant. diff --git a/source/part-3-django-concepts/auth.adoc b/source/part-3-django-concepts/auth.adoc index 9bd1ca7..c1c7f80 100644 --- a/source/part-3-django-concepts/auth.adoc +++ b/source/part-3-django-concepts/auth.adoc @@ -1,12 +1,8 @@ -================ -Authentification -================ +== Authentification -Comme on l'a vu dans la partie sur le modèle, nous souhaitons que le créateur d'une liste puisse retrouver facilement les éléments qu'il aura créé. Ce dont nous n'avons pas parlé cependant, c'est la manière dont l'utilisateur va pouvoir créer son compte et s'authentifier. La `document `_ est très complète, nous allons essayer de la simplifier au maximum. Accrochez-vous, le sujet peut être complexe. +Comme on l'a vu dans la partie sur le modèle, nous souhaitons que le créateur d'une liste puisse retrouver facilement les éléments qu'il aura créé. Ce dont nous n'avons pas parlé cependant, c'est la manière dont l'utilisateur va pouvoir créer son compte et s'authentifier. La https://docs.djangoproject.com/en/stable/topics/auth/[documentation] est très complète, nous allons essayer de la simplifier au maximum. Accrochez-vous, le sujet peut être complexe. -**************************** -Mécanisme d'authentification -**************************** +=== Mécanisme d'authentification On peut schématiser le flux d'authentification de la manière suivante : @@ -23,9 +19,9 @@ En résumé (bis): . Une personne souhaite se connecter; . Les backends d'authentification s'enchaîne jusqu'à trouver une bonne correspondance. Si aucune correspondance n'est trouvée, on envoie la personne sur les roses. -. Si OK, on retourne une instance de type curren_user, qui pourra être utilisée de manière uniforme dans l'application. +. Si OK, on retourne une instance de type current_user, qui pourra être utilisée de manière uniforme dans l'application. -Deux exemples : +Ci-dessous, on définit deux backends différents pour mieux comprendre les différentes possibilités: . Une authentification par jeton . Une authentification LDAP @@ -64,9 +60,7 @@ class TokenBackend(backends.ModelBackend): return None ---- - <1> Sous-entend qu'on a bien une classe qui permet d'accéder à ces jetons ;-) - - +<1> Sous-entend qu'on a bien une classe qui permet d'accéder à ces jetons ;-) [source,python] ---- @@ -102,76 +96,63 @@ class LdapBackend(backends.ModelBackend): On peut résumer le mécanisme d'authentification de la manière suivante: - * Si vous voulez modifier les informations liées à un utilisateur, orientez-vous vers la modification du modèle. Comme nous le verrons ci-dessous, il existe trois manières de prendre ces modifications en compte. Voir également `ici `_. + * Si vous voulez modifier les informations liées à un utilisateur, orientez-vous vers la modification du modèle. Comme nous le verrons ci-dessous, il existe trois manières de prendre ces modifications en compte. Voir également https://docs.djangoproject.com/en/stable/topics/auth/customizing/[ici]. * Si vous souhaitez modifier la manière dont l'utilisateur se connecte, alors vous devrez modifier le *backend*. -Modification du modèle -====================== +=== Modification du modèle -Dans un premier temps, Django a besoin de manipuler `des instances de type `_ ``django.contrib.auth.User``. Cette classe implémente les champs suivants: +Dans un premier temps, Django a besoin de manipuler https://docs.djangoproject.com/en/1.9/ref/contrib/auth/#user-model[des instances de type `django.contrib.auth.User`]. Cette classe implémente les champs suivants: - * ``username`` - * ``first_name`` - * ``last_name`` - * ``email`` - * ``password`` - * ``date_joined``. + * `username` + * `first_name` + * `last_name` + * `email` + * `password` + * `date_joined`. D'autres champs, comme les groupes auxquels l'utilisateur est associé, ses permissions, savoir s'il est un super-utilisateur, ... sont moins pertinents pour le moment. Avec les quelques champs déjà définis ci-dessus, nous avons de quoi identifier correctement nos utilisateurs. Inutile d'implémenter nos propres classes, puisqu'elles existent déjà :-) Si vous souhaitez ajouter un champ, il existe trois manières de faire. -Extension du modèle existant ----------------------------- +=== Extension du modèle existant -Le plus simple consiste à créer une nouvelle classe, et à faire un lien de type ``OneToOne`` vers la classe ``django.contrib.auth.User``. De cette manière, on ne modifie rien à la manière dont Django authentife ses utlisateurs: tout ce qu'on fait, c'est un lien vers une table nouvellement créée, comme on l'a déjà vu au point [...voir l'héritage de modèle]. L'avantage de cette méthode, c'est qu'elle est extrêmement flexible, et qu'on garde les mécanismes Django standard. Le désavantage, c'est que pour avoir toutes les informations de notre utilisateur, on sera obligé d'effectuer une jointure sur le base de données, ce qui pourrait avoir des conséquences sur les performances. +Le plus simple consiste à créer une nouvelle classe, et à faire un lien de type `OneToOne` vers la classe `django.contrib.auth.User`. De cette manière, on ne modifie rien à la manière dont Django authentife ses utlisateurs: tout ce qu'on fait, c'est un lien vers une table nouvellement créée, comme on l'a déjà vu au point [...voir l'héritage de modèle]. L'avantage de cette méthode, c'est qu'elle est extrêmement flexible, et qu'on garde les mécanismes Django standard. Le désavantage, c'est que pour avoir toutes les informations de notre utilisateur, on sera obligé d'effectuer une jointure sur le base de données, ce qui pourrait avoir des conséquences sur les performances. -Substitution ------------- +=== Substitution -Avant de commencer, sachez que cette étape doit être effectuée **avant la première migration**. Le plus simple sera de définir une nouvelle classe héritant de ``django.contrib.auth.User`` et de spécifier la classe à utiliser dans votre fichier de paramètres. Si ce paramètre est modifié après que la première migration ait été effectuée, il ne sera pas pris en compte. Tenez-en compte au moment de modéliser votre application. +Avant de commencer, sachez que cette étape doit être effectuée **avant la première migration**. Le plus simple sera de définir une nouvelle classe héritant de `django.contrib.auth.User` et de spécifier la classe à utiliser dans votre fichier de paramètres. Si ce paramètre est modifié après que la première migration ait été effectuée, il ne sera pas pris en compte. Tenez-en compte au moment de modéliser votre application. -.. code-block:: python +[source,python] +---- +AUTH_USER_MODEL = 'myapp.MyUser' +---- - AUTH_USER_MODEL = 'myapp.MyUser' +Notez bien qu'il ne faut pas spécifier le package `.models` dans cette injection de dépendances: le schéma à indiquer est bien `.`. -Notez bien qu'**il ne faut pas** spécifier le package ``.models`` dans cette injection de dépendances: le schéma à indiquer est bien ``.``. - -Backend -======= +==== Backend - - -********* -Templates -********* +==== Templates Ce qui n'existe pas par contre, ce sont les vues. Django propose donc tout le mécanisme de gestion des utilisateurs, excepté le visuel (hors administration). En premier lieu, ces paramètres sont fixés dans le fichier `settings `_. On y trouve par exemple les paramètres suivants: - * ``LOGIN_REDIRECT_URL``: si vous ne spécifiez pas le paramètre ``next``, l'utilisateur sera automatiquement redirigé vers cette page. - * ``LOGIN_URL``: l'URL de connexion à utiliser. Par défaut, l'utilisateur doit se rendre sur la page ``/accounts/login``. + * `LOGIN_REDIRECT_URL`: si vous ne spécifiez pas le paramètre `next`, l'utilisateur sera automatiquement redirigé vers cette page. + * `LOGIN_URL`: l'URL de connexion à utiliser. Par défaut, l'utilisateur doit se rendre sur la page `/accounts/login`. -*********************** -Social-Authentification -*********************** +==== Social-Authentification -Voir ici : `python social auth `_ +Voir ici : https://github.com/omab/python-social-auth[python social auth] -Un petit mot sur OAuth ----------------------- - -OAuth -===== +==== Un petit mot sur OAuth OAuth est un standard libre définissant un ensemble de méthodes à implémenter pour l'accès (l'autorisation) à une API. Son fonctionnement se base sur un système de jetons (Tokens), attribués par le possesseur de la ressource à laquelle un utilisateur souhaite accéder. Le client initie la connexion en demandant un jeton au serveur. Ce jeton est ensuite utilisée tout au long de la connexion, pour accéder aux différentes ressources offertes par ce serveur. `wikipedia `_. -Une introduction à OAuth est `disponible ici `_. Elle introduit le protocole comme étant une `valet key`, une clé que l'on donne à la personne qui va garer votre voiture pendant que vous profitez des mondanités. Cette clé donne un accès à votre voiture, tout en bloquant un ensemble de fonctionnalités. Le principe du protocole est semblable en ce sens: vous vous réservez un accès total à une API, tandis que le système de jetons permet d'identifier une personne, tout en lui donnant un accès restreint à votre application. +Une introduction à OAuth est http://hueniverse.com/oauth/guide/intro/[disponible ici]. Elle introduit le protocole comme étant une `valet key`, une clé que l'on donne à la personne qui va garer votre voiture pendant que vous profitez des mondanités. Cette clé donne un accès à votre voiture, tout en bloquant un ensemble de fonctionnalités. Le principe du protocole est semblable en ce sens: vous vous réservez un accès total à une API, tandis que le système de jetons permet d'identifier une personne, tout en lui donnant un accès restreint à votre application. L'utilisation de jetons permet notamment de définir une durée d'utilisation et une portée d'utilisation. L'utilisateur d'un service A peut par exemple autoriser un service B à accéder à des ressources qu'il possède, sans pour autant révéler son nom d'utilisateur ou son mot de passe. -L'exemple repris au niveau du `workflow `_ est le suivant : un utilisateur(trice), Jane, a uploadé des photos sur le site faji.com (A). Elle souhaite les imprimer au travers du site beppa.com (B). +L'exemple repris au niveau du http://hueniverse.com/oauth/guide/workflow/[workflow] est le suivant : un utilisateur(trice), Jane, a uploadé des photos sur le site faji.com (A). Elle souhaite les imprimer au travers du site beppa.com (B). Au moment de la commande, le site beppa.com envoie une demande au site faji.com pour accéder aux ressources partagées par Jane. Pour cela, une nouvelle page s'ouvre pour l'utilisateur, et lui demande d'introduire sa "pièce d'identité". Le site A, ayant reçu une demande de B, mais certifiée par l'utilisateur, ouvre alors les ressources et lui permet d'y accéder. diff --git a/source/part-9-bonus/00-main.adoc b/source/part-9-bonus/00-main.adoc new file mode 100644 index 0000000..d645a2f --- /dev/null +++ b/source/part-9-bonus/00-main.adoc @@ -0,0 +1,5 @@ += En Bonus + +include::code-snippets.adoc[] + +include::legacy.adoc[] diff --git a/source/bonus/code-snippets.adoc b/source/part-9-bonus/code-snippets.adoc similarity index 100% rename from source/bonus/code-snippets.adoc rename to source/part-9-bonus/code-snippets.adoc diff --git a/ideas/legacy.md b/source/part-9-bonus/legacy.adoc similarity index 90% rename from ideas/legacy.md rename to source/part-9-bonus/legacy.adoc index 69cc5ce..b0e2798 100644 --- a/ideas/legacy.md +++ b/source/part-9-bonus/legacy.adoc @@ -1,3 +1,5 @@ +== Applications _Legacy_ + Quand on intègre une nouvelle application Django dans un environement existant, la première étape est de se câbler sur la base de données existantes; 1. Soit l'application sur laquelle on se greffe restera telle quelle; @@ -6,4 +8,4 @@ Quand on intègre une nouvelle application Django dans un environement existant, Dans le premier cas, il convient de créer une application et de spécifier pour chaque classe l'attribute `managed = False` dans le `class Meta:` de la définition. Dans le second, il va falloir câbler deux-trois éléments avant d'avoir une intégration complète (comprendre: avec une interface d'admin, les migrations, les tests unitaires et tout le brol :)) - `python manage.py inspectdb > models.py` + `python manage.py inspectdb > models.py` \ No newline at end of file diff --git a/source/resources/themes/gwift-theme.yaml b/source/resources/themes/gwift-theme.yaml deleted file mode 100644 index f66a3bb..0000000 --- a/source/resources/themes/gwift-theme.yaml +++ /dev/null @@ -1,8 +0,0 @@ -extends: default -footer: - recto: - right: - content: '{section-or-chapter-title} | {page-number}' - verso: - left: - content: '{page-number} | {chapter-title}' \ No newline at end of file