Create new parts for datamodel, SOA and go-live
continuous-integration/drone/push Build is passing Details

This commit is contained in:
Fred Pauchet 2021-12-14 21:51:35 +01:00
parent acb10f07e3
commit c75fd76775
34 changed files with 261 additions and 204 deletions

10
source/glossary.adoc Normal file
View File

@ -0,0 +1,10 @@
[glossary]
= Glossaire
http:: _HyperText Transfer Protocol_, ou plus généralement le protocole utilisé (et détourné) pour tout ce qui touche au **World Wide Web**. Il existe beaucoup d'autres protocoles d'échange de données, comme https://fr.wikipedia.org/wiki/Gopher[Gopher], https://fr.wikipedia.org/wiki/File_Transfer_Protocol[FTP] ou https://fr.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol[SMTP].
IaaS:: _Infrastructure as a Service_, où un tiers vous fournit des machines (généralement virtuelles) que vous devrez ensuite gérer en bon père de famille. L'IaaS propose souvent une API, qui vous permet d'intégrer la durée de vie de chaque machine dans vos flux - en créant, augmentant, détruisant une machine lorsque cela s'avère nécessaire.
ORM:: _Object Relational Mapper_, où une instance est directement (ou à proximité) liée à un mode de persistance de données.
PaaS:: _Platform as a Service_, qui consiste à proposer les composants d'une plateforme (Redis, PostgreSQL, ...) en libre service et disponibles à la demande (quoiqu'après avoir communiqué son numéro de carte de crédit...).

View File

@ -52,12 +52,13 @@ Nous aborderons également la supervision et la mise à jour d'une application e
*Dans la troisième partie*, nous aborderons les grands principes de modélisation, en suivant les lignes de conduites du cadre de travail.
Nous aborderons les concepts clés qui permettent à une application de rester maintenable, les formulaires, leurs validations, comment gérer les données en entrée, les migrations de données et l'administration.
*Dans la quatrième partie*, nous mettrons ces concepts en pratique en présentant le développement de deux "vraies" applications: définition des tables, gestion des utilisateurs, ... et mise à disposition!
*Dans la quatrième partie*, nous aborderons les architectures typées _entreprise_, les services et les différentes manières de structurer son application pour faciliter sa gestion et sa maintenance.
footnote:[Avec même un peu d'https://www.xkcd.com[XKCD] et de Calvin & Hobbes dedans]
*Dans la cinquième partie*, nous mettrons ces concepts en pratique en présentant le développement de deux "vraies" applications: définition des tables, gestion des utilisateurs, ... et mise à disposition!
=== Pour qui ?
Ce livre s'adresse autant au néophyte qui souhaite se lancer dans le développement Web qu'à l'artisan qui a besoin d'un aide-mémoire et qui ne sait toujours
=== Pour quoi ?
@ -76,18 +77,11 @@ include::part-2-deployment/_main.adoc[]
include::part-3-django-concepts/_index.adoc[]
include::part-4-go-live/_index.adoc[]
include::part-5-go-live/_index.adoc[]
include::part-9-resources/_index.adoc[]
[glossary]
= Glossaire
IaaS:: _Infrastructure as a Service_, où un tiers vous fournit des machines (généralement virtuelles) que vous devrez ensuite gérer en bon père de famille. L'IaaS propose souvent une API, qui vous permet d'intégrer la durée de vie de chaque machine dans vos flux - en créant, augmentant, détruisant une machine lorsque cela s'avère nécessaire.
ORM:: _Object Relational Mapper_, où une instance est directement (ou à proximité) liée à un mode de persistance de données.
PaaS:: _Platform as a Service_, qui consiste à proposer les composants d'une plateforme (Redis, PostgreSQL, ...) en libre service et disponibles à la demande (quoiqu'après avoir communiqué son numéro de carte de crédit...).
include::glossary.adoc[]
[index]
== Index

View File

@ -110,6 +110,8 @@ Il est possible de démarrer petit, et de suivre l'évolution des besoins en fon
=== Logs
include::logging.adoc[]
=== Sentry ! :-D
[source,python]

View File

@ -0,0 +1,38 @@
= (meta)Data Model
Dans ce chapitre, nous allons parler de plusieurs concepts utiles au développement rapide d'une application.
Nous parlerons de modélisation, de métamodèle, de migrations, d'administration auto-générée, de traductions et de cycle de vie des données.
Django 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.
Pour un néophyte, la courbe d'apprentissage sera relativement ardue; à côté de concepts clés de Django, il conviendra également d'assimiler correctement les structures de données du langage Python, le cycle de vie d'une requête HTTP
En restant dans les sentiers battus, votre projet suivra un dérivé du patron de conception `MVC` (Modèle-Vue-Controleur), où la variante concerne les termes utilisés: Django les nomme respectivement Modèle-Template-Vue et leur contexte d'utilisation.
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**.
* 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, lorsqu'une requête est émise par un utilisateur, la première étape va consister à trouver une _route_ qui correspond à cette requête, c'est à dire à trouver la correspondance entre l'URL qui est demandée par l'utilisateur et la fonction du langage qui sera exécutée pour fournir le résultat attendu.
Cette fonction correspond au *contrôleur* et s'occupera de construire le *modèle* correspondant.
En simplifiant, Django suit bien le modèle MVC, et toutes ces étapes sont liées ensemble grâce aux différentes routes, définies dans les fichiers `urls.py`.
include::models.adoc[]
include::migrations.adoc[]
include::shell.adoc[]
include::admin.adoc[]
include::forms.adoc[]
include::auth.adoc[]
include::settings.adoc[]
include::tests.adoc[]
== Conclusions

View File

@ -0,0 +1,115 @@
== Migrations
L'intégration des migrations a été réalisée dans la version 1.7 de Django.
Avant cela, il convenait de passer par une librairie tierce intitulée https://south.readthedocs.io/en/latest[South].
Dans cette section, nous allons voir comment fonctionnent les migrations.
Lors d'une première approche, elles peuvent sembler un peu magiques, puisqu'elles centralisent un ensemble de modifications pouvant être répétées sur un schéma de données, en tenant compte de ce qui a déjà été appliqué et en vérifiant quelles migrations devaient encore l'être pour mettre l'application à niveau.
Une analyse en profondeur montrera qu'elles ne sont pas plus complexes à suivre et à comprendre qu'un ensemble de fonctions de gestion appliquées à notre application.
Prenons l'exemple de notre liste de souhaits; nous nous rendons (bêtement) compte que nous avons oublié d'ajouter un champ de `description` à une liste.
Historiquement, cette action nécessitait l'intervention d'un administrateur système ou d'une personne ayant accès au schéma de la base de données, à partir duquel ce-dit utilisateur pouvait jouer manuellement un script SQL.
Cet enchaînement d'étapes nécessitait une bonne coordination d'équipe, mais également une bonne confiance dans les scripts à exécuter.
Et souvenez-vous (cf. ref-à-insérer), que l'ensemble des actions doit être répétable et automatisable.
Bref, dans les années '80, il convenait de jouer ceci après s'être connecté à la base de données:
[source,sql]
----
ALTER TABLE WishList ADD COLUMN Description nvarchar(MAX);
----
Et là, nous nous rappelons qu'un utilisateur tourne sur Oracle et pas sur MySQL, et qu'il a donc besoin de son propre script d'exécution, parce que le type du nouveau champ n'est pas exactement le même entre les deux moteurs:
[source,sql]
----
----
Bref, vous voyez le(s) problème(s):
1. Aucune autonomie
2. Aucune automatisation possible (à moins d'écrire un programme, qu'il faudra également maintenir et intégrer au niveau des tests)
3. Nécessiter de maintenir autant de scripts différents qu'il y a de moteurs de base de données supportés
4. Aucune possibilité de vérifier si le script a déjà été exécuté ou non (à moins de maintenir un programme supplémentaire, à nouveau)
5. ...
Les migrations résolvent la plupart de ces soucis: le framework embarque ses propres applications, dont les migrations, qui gèrent elles-mêmes l'arbre de dépendances entre les modifications devant être appliquées.
Une migration consiste donc à appliquer un ensemble de modifications (ou **opérations**), qui exercent un ensemble de transformations, pour que le schéma de base de données corresponde au modèle de l'application sous-jacente.
Les migrations (comprendre les "_migrations du schéma de base de données_") sont intimement liées à la représentation d'un contexte fonctionnel.
L'ajout d'une nouvelle information, d'un nouveau champ ou d'une nouvelle fonction peut s'accompagner de tables de données à mettre à jour ou de champs à étendre.
Toujours dans une optique de centralisation, les migrations sont directement embarquées au niveau du code.
Le développeur s'occupe de créer les migrations en fonction des actions à entreprendre; ces migrations peuvent être retravaillées, _squashées_, ... et feront partie intégrante du processus de mise à jour de l'application.
A noter que les migrations n'appliqueront de modifications que si le schéma est impacté.
Ajouter une propriété `related_name` sur une ForeignKey n'engendrera aucune nouvelle action de migration, puisque ce type d'action ne s'applique que sur l'ORM, et pas directement sur la base de données: au niveau des tables, rien ne change.
Seul le code et le modèle sont impactés.
Une migration est donc une classe Python, présentant _a minima_ deux propriétés:
1. `dependencies`, qui décrit les opérations précédentes devant obligatoirement avoir été appliquées
2. `operations`, qui consiste à décrire précisément ce qui doit être exécuté.
Pour reprendre notre exemple d'ajout d'un champ `description` sur le modèle `WishList`, la migration ressemblera à ceci:
[source,python]
----
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('gwift', '0004_name_value'),
]
operations = [
migrations.AddField(
model_name='wishlist',
name='description',
field=models.TextField(default="", null=True)
preserve_default=False,
),
]
----
Nous avions un modèle reprenant quelques classes, elles-mêmes saupoudrées de quelques propriétés.
=== Réinitialisation d'une ou plusieurs migrations
https://simpleisbetterthancomplex.com/tutorial/2016/07/26/how-to-reset-migrations.html[reset migrations].
> En gros, soit on supprime toutes les migrations (en conservant le fichier __init__.py), soit on réinitialise proprement les migrations avec un --fake-initial (sous réserve que toutes les personnes qui utilisent déjà le projet s'y conforment... Ce qui n'est pas gagné.
Pour repartir de notre exemple ci-dessus, nous avions un modèle reprenant quelques classes, saupoudrées de propriétés décrivant nos différents champs. Pour être prise en compte par le moteur de base de données, chaque modification doit être
=== Description d'une migration
1. Décrite, grâce à la commande `makemigrations`
=== Application d'une ou plusieurs migrations
1. Appliquée, avec la commande `migrate`.
=== Analyse
Nous allons ci-dessous analyser exactement les modifications appliquées au schéma de la base de données, en fonction des différents cas, et comment ils sont gérés par les pilotes de Django.
Nous utiliserons https://sqlitebrowser.org/[Sqlite Browser] et la commande `sqldump`, qui nous présentera le schéma tel qu'il sera compris.
==== Création de nouveaux champs
==== Modification d'un champ existant
==== Suppression d'un champ existant

View File

@ -1,17 +1,28 @@
== Modélisation
Nous allons aborder la modélisation des objets en elle-même, qui est en lien direct avec la conception de la base de données et la manière dont celles-ci s'agencent et communiquent entre elles.
Ce chapitre aborde la modélisation des objets et les options qui y sont liées.
Avec Django, la modélisation est en lien direct avec la conception et le stockage de la base de données, la manière dont ces données s'agencent et communiquent entre elles.
Django utilise un paradigme de type https://fr.wikipedia.org/wiki/Mapping_objet-relationnel[ORM] - c'est-à-dire que chaque type d'objet peut s'apparenter à une table SQL, mais en ajoutant une couche propre au modèle orienté objet.
Il est ainsi possible de définir facilement des notions d'héritage (tout en restant dans une forme d'héritage simple), la possibilité d'utiliser des propriétés spécifiques, des classes intermédiaires, ...
Django utilise un paradigme de type https://fr.wikipedia.org/wiki/Mapping_objet-relationnel[ORM] - c'est-à-dire que chaque type d'objet manipulé peut s'apparenter à une table SQL, tout en ajoutant une couche propre à la programmation orientée object.
Plus spécifiquement, l'ORM de Django suit le patron de conception https://en.wikipedia.org/wiki/Active_record_pattern[Active Records], comme ce que font par exemple https://rubyonrails.org/[Rails] pour Ruby ou https://docs.microsoft.com/fr-fr/ef/[EntityFramework] pour .Net.
L'avantage de tout ceci est que tout reste au niveau du code.
Si l'on revient sur la méthodologie des douze facteurs, ce point concerne principalement la minimisation de la divergence entre les environnements d'exécution.
Déployer une nouvelle instance de l'application pourra être réalisé directement à partir d'une seule et même commande, dans la mesure où *tout est embarqué au niveau du code*.
Le modèle de données de Django est sans doute la (seule ?) partie qui soit tellement couplée au framework qu'un changement à ce niveau nécessitera une refonte complète de beaucoup d'autres briques de vos applications; là où un pattern de type https://www.martinfowler.com/eaaCatalog/repository.html[Repository] permettrait justement de découpler le modèle des données de l'accès à ces mêmes données, un pattern Active Record lie de manière extrêmement forte le modèle à sa persistence.
Assez de blabla, on démarre !
Architecturalement, c'est sans doute la plus grosse faiblesse de Django.
Conceptuellement, c'est pourtant la manière de faire qui permettra d'avoir quelque chose à présenter très rapidement: à partir du moment où vous aurez un modèle de données, vous aurez accès :
=== Modélisation de base
1. Aux migrations de données,
2. A un découplage complet entre le moteur de données relationnel et le modèle de données,
3. A une interface d'administration auto-générée
4. A un mécanisme de formulaires HTML qui soit complet, pratique à utiliser, orienté objet et facile à faire évoluer,
5. De définir des notions d'héritage (tout en restant dans une forme d'héritage simple).
Comme tout ceci reste au niveau du code, cela suit également la méthodologie des douze facteurs, concernant la minimisation des divergences entre environnements d'exécution: comme tout se trouve au niveau du code, il n'est plus nécessaire d'avoir un DBA qui doive démarrer un script sur un serveur au moment de la mise à jour, de recevoir une release note de 512 pages en PDF reprenant les modifications ou de nécessiter l'intervention de trois équipes différentes lors d'une modification majeure du code.
Déployer une nouvelle instance de l'application pourra être réalisé directement à partir d'une seule et même commande.
_A contrario_, ces avantages sont balancés au travers d'un couplage extrêmement fort entre la modélisation et le reste du framework - à tel point que *ne pas utiliser cette brique de fonctionnalités* peut remettre en question le choix du framework.
=== Principes de modélisation
Dans le domaine des bases de données relationnelles, un point d'attention est de toujours disposer d'une clé primaire pour nos enregistrements.
Si aucune clé primaire n'est spécifiée, Django s'occupera d'en ajouter une automatiquement et la nommera (par convention) `id`.
@ -62,9 +73,9 @@ class Item(models.Model):
wishlist = models.ForeignKey(Wishlist, related_name='items')
----
NOTE: Si, dans une classe A, plusieurs relations sont liées à une classe B, Django ne saura pas à quoi correspondra la relation inverse.
Pour palier à ce problème, nous fixons une valeur à l'attribut `related_name`.
Par facilité (et pas conventions), prenez l'habitude de toujours ajouter cet attribut.
NOTE: Si, dans une classe A, plusieurs relations sont liées à une classe B, Django ne saura pas à quoi correspondra la relation inverse.
Pour palier à ce problème, nous fixons une valeur à l'attribut `related_name`.
Par facilité (et pas conventions), prenez l'habitude de toujours ajouter cet attribut.
Votre modèle gagnera en cohérence et en lisibilité.
Si cette relation inverse n'est pas nécessaire, il est possible de l'indiquer (par convention) au travers de l'attribut `related_name="+"`.
@ -113,7 +124,7 @@ Les propriétés de la classe Meta les plus utiles sont les suivates:
* `ordering` pour spécifier un ordre de récupération spécifique.
* `verbose_name` pour indiquer le nom à utiliser au singulier pour définir votre classe
* `verbose_name_plural`, pour le pluriel.
* `contraints` (Voir https://girlthatlovestocode.com/django-model[ici]-), par exemple
* `contraints` (Voir https://girlthatlovestocode.com/django-model[ici]-), par exemple
[source,python]
----
@ -148,47 +159,47 @@ class Runner(models.Model):
==== Protocoles de langage (*dunder methods*)
The Python data model specifies a lot of specially named methods that can be overridden in your custom classes to provide
them with additional syntax capabilities.
You can recognize these methods by their specific naming conventions that wrap the method name with double underscores.
Because of this, they are sometimes referred to as dunder methods.
It is simply shorthand for double underscores.The most common and obvious example of such dunder methods is __init__(),
The Python data model specifies a lot of specially named methods that can be overridden in your custom classes to provide
them with additional syntax capabilities.
You can recognize these methods by their specific naming conventions that wrap the method name with double underscores.
Because of this, they are sometimes referred to as dunder methods.
It is simply shorthand for double underscores.The most common and obvious example of such dunder methods is __init__(),
which is used for class instance initialization:
[source,python]
----
class CustomUserClass:
def __init__(self, initiatization_argument):
class CustomUserClass:
def __init__(self, initiatization_argument):
...
----
En fait, l'intérêt concerne surtout la représentation de nos modèles, puisque chaque classe du modèle est représentée par
la définition d'un objet Python.
la définition d'un objet Python.
Nous pouvons donc utiliser ces mêmes *dunder methods* (*double-underscores methods*) pour étoffer les protocoles du langage.
These methods, either alone or when defined in a specific combination, constitute the so-called language protocols.
These methods, either alone or when defined in a specific combination, constitute the so-called language protocols.
If we say that an object implements a specific language protocol, it means that it is compatible with a specific part of the Python language syntax. The following is a table of the most common protocols within the Python language.Protocol nameMethodsDescriptionCallable protocol__call__()Allows objects to be called with parentheses:instance()Descriptor protocols__set__(), __get__(), and __del__()Allows us to manipulate the attribute access pattern of classes (see the Descriptors section)Container protocol__contains__()Allows us to test whether or not an object contains some value using the in keyword:value in instance
Python in Comparison with Other LanguagesIterable protocol__iter__()Allows objects to be iterated using the forkeyword:for value in instance: ...Sequence protocol__getitem__(),__len__()Allows objects to be indexed with square bracket syntax and queried for length using a built-in function:item = instance[index]length = len(instance)Each operator available in Python has its own protocol and operator overloading happens by implementing the dunder methods of that protocol. Python provides over 50 overloadable operators that can be divided into five main groups:• Arithmetic operators • In-place assignment operators• Comparison operators• Identity operators• Bitwise operatorsThat's a lot of protocols so we won't discuss all of them here. We will instead take a look at a practical example that will allow you to better understand how to implement operator overloading on your own
A full list of available dunder methods can be found in the Data model section of the official Python documentation available
A full list of available dunder methods can be found in the Data model section of the official Python documentation available
at https://docs.python.org/3/reference/datamodel.html.
All operators are also exposed as ordinary functions in the operators module.
The documentation of that module gives a good overview of Python operators.
All operators are also exposed as ordinary functions in the operators module.
The documentation of that module gives a good overview of Python operators.
It can be found at https://docs.python.org/3.9/library/operator.html
The `__add__()` method is responsible for overloading the `+` (plus sign) operator and here it allows us to add
two matrices together.
Only matrices of the same dimensions can be added together.
The `__add__()` method is responsible for overloading the `+` (plus sign) operator and here it allows us to add
two matrices together.
Only matrices of the same dimensions can be added together.
This is a fairly simple operation that involves adding all matrix elements one by one to form a new matrix.
The `__sub__()` method is responsible for overloading the `` (minus sign) operator that will be responsible for matrix subtraction.
The `__sub__()` method is responsible for overloading the `` (minus sign) operator that will be responsible for matrix subtraction.
To subtract two matrices, we use a similar technique as in the operator:
[source,python]
----
def __sub__(self, other):
def __sub__(self, other):
if (len(self.rows) != len(other.rows) or len(self.rows[0]) != len(other.rows[0])):
raise ValueError("Matrix dimensions don't match")
raise ValueError("Matrix dimensions don't match")
return Matrix([[a - b for a, b in zip(a_row, b_row)] for a_row, b_row in zip(self.rows, other.rows) ])
----
@ -196,32 +207,32 @@ And the following is the last method we add to our class:
[source,python]
----
def __mul__(self, other):
def __mul__(self, other):
if not isinstance(other, Matrix):
raise TypeError(f"Don't know how to multiply {type(other)} with Matrix")
raise TypeError(f"Don't know how to multiply {type(other)} with Matrix")
if len(self.rows[0]) != len(other.rows):
raise ValueError("Matrix dimensions don't match")
rows = [[0 for _ in other.rows[0]] for _ in self.rows]
raise ValueError("Matrix dimensions don't match")
rows = [[0 for _ in other.rows[0]] for _ in self.rows]
for i in range(len (self.rows)):
for j in range(len (other.rows[0])):
for k in range(len (other.rows)):
rows[i][j] += self.rows[i][k] * other.rows[k][j]
for k in range(len (other.rows)):
rows[i][j] += self.rows[i][k] * other.rows[k][j]
return Matrix(rows)
----
The last overloaded operator is the most complex one.
This is the `*` operator, which is implemented through the `__mul__()` method.
In linear algebra, matrices don't have the same multiplication operation as real numbers.
Two matrices can be multiplied if the first matrix has a number of columns equal to the number of rows of the second matrix.
The result of that operation is a new matrix where each element is a dot product of the corresponding row of the first matrix
The last overloaded operator is the most complex one.
This is the `*` operator, which is implemented through the `__mul__()` method.
In linear algebra, matrices don't have the same multiplication operation as real numbers.
Two matrices can be multiplied if the first matrix has a number of columns equal to the number of rows of the second matrix.
The result of that operation is a new matrix where each element is a dot product of the corresponding row of the first matrix
and the corresponding column of the second matrix.
Here we've built our own implementation of the matrix to present the idea of operators overloading.
Although Python lacks a built-in type for matrices, you don't need to build them from scratch.
The NumPy package is one of the best Python mathematical packages and among others provides native support for matrix algebra.
Here we've built our own implementation of the matrix to present the idea of operators overloading.
Although Python lacks a built-in type for matrices, you don't need to build them from scratch.
The NumPy package is one of the best Python mathematical packages and among others provides native support for matrix algebra.
You can easily obtain the NumPy package from PyPI
(Voir Expert Python Programming, 4th Edition, page 142-144)
@ -254,9 +265,9 @@ class Item(models.Model):
return i
----
Mieux encore: on pourrait passer par un `ModelManager` pour limiter le couplage;
l'accès à une information stockée en base de données ne se ferait dès lors qu'au travers
de cette instance et pas directement au travers du modèle. De cette manière, on limite le
Mieux encore: on pourrait passer par un `ModelManager` pour limiter le couplage;
l'accès à une information stockée en base de données ne se ferait dès lors qu'au travers
de cette instance et pas directement au travers du modèle. De cette manière, on limite le
couplage des classes et on centralise l'accès.
[source,python]
@ -264,3 +275,13 @@ couplage des classes et on centralise l'accès.
class ItemManager(...):
(de mémoire, je ne sais plus exactement :-))
----
=== Conclusion
Le modèle proposé par Django est un composant extrêmement performant, mais fort couplé avec le coeur du framework.
Si tous les composants peuvent être échangés avec quelques manipulations, le cas du modèle sera plus difficile à interchanger.
A côté de cela, il permet énormément de choses, et vous fera gagner un temps précieux, tant en rapidité d'essais/erreurs, que de preuves de concept.
Une possibilité peut également être de cantonner Django à un framework

View File

@ -36,4 +36,4 @@ class HomeTests(TestCase):
def test_home_view_status_code(self):
view = resolve("/")
self.assertEquals(view.func, home)
----
----

View File

@ -1,47 +0,0 @@
= 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. 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**.
* 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.
En simplifiant, Django suit bien le modèle MVC, et toutes ces étapes sont liées ensemble grâce aux différentes routes, définies dans les fichiers `urls.py`.
include::models.adoc[]
include::migrations.adoc[]
include::shell.adoc[]
include::admin.adoc[]
include::forms.adoc[]
include::views.adoc[]
include::templates.adoc[]
include::layout.adoc[]
include::urls.adoc[]
include::auth.adoc[]
include::logging.adoc[]
include::settings.adoc[]
include::tests.adoc[]
NOTE: Ne pas oublier de parler des sessions. Mais je ne sais pas si c'est le bon endroit.
include::multilingual.adoc[]

View File

@ -1,94 +0,0 @@
== Migrations
Dans cette section, nous allons voir comment les migrations fonctionnent.
Dans une première version, elles peuvent sembler un peu magiques, dans la mesure où elles appliquent des modifications au
niveau du schéma de données en se basant uniquement sur des modifications effectuées sur le modèle.
Techniquement, elles ne sont pas plus complexes à comprendre qu'un ensemble de fonctions appliquées à notre application.
[source,python]
----
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('gwift', '0004_name_value'),
]
operations = [
migrations.AddField(
model_name='wish',
name='date',
field=models.DateField(default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='wish',
name='description',
field=models.TextField(default="", null=True)
preserve_default=False,
),
]
----
Pour repartir de notre exemple ci-dessus, nous avions un modèle reprenant quelques classes, et saupoudrées de propriétés.
Pour schématiser, chaque classe correspond à une table dans la base de données, tandis que
chaque propriété correspond à un champ de cette table.
Une migration consiste donc à appliquer un ensemble de modifications (ou **opérations**),
qui exercent un ensemble de transformations, pour que le schéma de base de données corresponde au modèle de l'application.
Les migrations (comprendre les "_migrations du schéma de base de données_") sont intimement liées à la
représentation d'un contexte fonctionnel.
L'ajout d'une nouvelle information, d'un nouveau champ ou d'une nouvelle fonction peut s'accompagner
de tables de données à mettre à jour ou de champs à étendre.
Toujours dans une optique de centralisation, les migrations sont directement embarquées au niveau du code.
Le développeur s'occupe de créer les migrations en fonction des actions à entreprendre;
ces migrations peuvent être retravaillées, _squashées_, ...
et feront partie intégrante du processus de mise à jour de l'application.
A noter que les migrations n'appliqueront de modifications que si le schéma est impacté.
Ajouter une propriété `related_name` sur une ForeignKey n'engendrera aucune nouvelle action de migration,
puisque ce type d'action ne s'applique que sur l'ORM, et pas directement sur la base de données:
au niveau des tables, rien ne change. Seul le code et le modèle sont impactés.
https://simpleisbetterthancomplex.com/tutorial/2016/07/26/how-to-reset-migrations.html[reset migrations].
> En gros, soit on supprime toutes les migrations (en conservant le fichier __init__.py), soit on réinitialise proprement les migrations avec un --fake-initial (sous réserve que toutes les personnes qui utilisent déjà le projet s'y conforment... Ce qui n'est pas gagné.
Pour repartir de notre exemple ci-dessus, nous avions un modèle reprenant quelques classes, saupoudrées de propriétés décrivant nos différents champs. Pour être prise en compte par le moteur de base de données, chaque modification doit être
=== Description d'une migration
1. Décrite, grâce à la commande `makemigrations`
=== Application d'une ou plusieurs migrations
2. Appliquée, avec la commande `migrate`.
=== Analyse
Nous allons ci-dessous analyser exactement les modifications appliquées au schéma de la base de données,
en fonction des différents cas, et comment ils sont gérés par les pilotes de Django.
Nous utiliserons https://sqlitebrowser.org/[Sqlite Browser] et la commande `sqldump`,
qui nous présentera le schéma tel qu'il sera compris.
==== Création de nouveaux champs
==== Modification d'un champ existant
==== Suppression d'un champ existant

View File

@ -0,0 +1,18 @@
= Services Oriented Applications
Nous avons fait exprès de reprendre l'acronyme d'une _Services Oriented Architecture_ pour cette partie.
L'objectif est de vous mettre la puce à l'oreille quant à la finalité du développement: que l'utilisateur soit humain, bot automatique ou client Web, l'objectif est de fournir des applications résilientes, disponibles et accessibles.
Dans cette partie, nous aborderons les vues, la mise en forme, la mise en page, la définition d'une interface REST, la définition d'une interface GraphQL et le routage d'URLs.
include::views.adoc[]
include::templates.adoc[]
include::querysets.adoc[]
include::urls.adoc[]
== Conclusions
De part son pattern `MVT`, Django ne fait pas comme les autres frameworks.