Complete models.tex based on easter notes
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
This commit is contained in:
parent
63f2cf23f4
commit
c8f88779ff
|
@ -152,15 +152,17 @@ En résumé, toutes les méthodes qui font qu'une instance sait \textbf{comment}
|
|||
\section{Types de champs, relations et clés étrangères}
|
||||
|
||||
Nous l'avons vu plus tôt, Python est un langage dynamique et fortement typé.
|
||||
Django, de son côté, ajoute une couche de typage statique exigé par le lien sous-jacent avec le moteur de base de données relationnelle.
|
||||
Dans le domaine des bases de données relationnelles, un point d'attention est de toujours disposer d'une clé primaire pour nos enregistrements.
|
||||
Django, de son côté, ajoute une couche de typage statique exigé par le lien sous-jacent avec les moteurs de base de données relationnelles.
|
||||
Dans ce domaine, 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) \texttt{id}.
|
||||
|
||||
Par défaut, et si aucune propriété ne dispose d'un attribut \texttt{primary\_key=True}, Django s'occupera d'ajouter un champ \texttt{id} grâce à son héritage de la classe \texttt{models.Model}.
|
||||
Elle sera ainsi accessible autant par cette propriété que par la propriété \texttt{pk}.
|
||||
|
||||
Chaque champ du modèle est donc typé et lié, soit à une primitive, soit à une autre instance au travers de sa clé d'identification.
|
||||
Chaque champ du modèle est donc typé et lié, soit à un primitif, soit à une autre instance au travers de sa clé d'identification.
|
||||
|
||||
Grâce à toutes ces informations, nous sommes en mesure de représenter facilement des livres liés à des catégories:
|
||||
Grâce à toutes ces informations, nous sommes en mesure de représenter facilement des relations, par exemple des livres liés à des catégories:
|
||||
|
||||
\begin{minted}{python}
|
||||
class Category(models.Model):
|
||||
|
@ -171,13 +173,11 @@ class Book(models.Model):
|
|||
category = models.ForeignKey(Category, on_delete=models.CASCADE)
|
||||
\end{minted}
|
||||
|
||||
|
||||
Par défaut, et si aucune propriété ne dispose d'un attribut \texttt{primary\_key=True}, Django s'occupera d'ajouter un champ \texttt{id} grâce à son héritage de la classe \texttt{models.Model}.
|
||||
Les autres champs nous permettent d'identifier une catégorie (\texttt{Category}) par un nom (\texttt{name}), tandis qu'un livre (\texttt{Book}) le sera par ses propriétés \texttt{title} et une clé de relation vers une catégorie. Un livre est donc lié à une catégorie, tandis qu'une catégorie est associée à plusieurs livres.
|
||||
|
||||
\includegraphics{diagrams/books-foreign-keys-example.drawio.png}
|
||||
|
||||
A présent que notre structure dispose de sa modélisation, il nous faut informer le moteur de base de données de créer la structure correspondance:
|
||||
A présent que notre structure dispose de sa modélisation, il nous faut informer le moteur de base de données de créer la structure correspondance, grâce à la création d'une étape de migration:
|
||||
|
||||
\begin{verbatim}
|
||||
$ python manage.py makemigrations
|
||||
|
@ -224,11 +224,11 @@ Nous nous rendons rapidement compte qu'un livre peut appartenir à plusieurs cat
|
|||
\item
|
||||
Pour \emph{The Great Gatsby}, c'est l'inverse: nous l'avons initialement classé comme film, mais le livre existe depuis 1925.
|
||||
\item
|
||||
Nous pourrions sans doute également étoffer notre bibliothèque avec une catégorie spéciale "Baguettes magiques et trucs phalliques", à laquelle nous pourrons associer la saga \emph{Harry Potter} et ses dérivés.
|
||||
Nous pourrions sans doute également étoffer notre bibliothèque avec une catégorie supplémentaire "Baguettes magiques et trucs phalliques", à laquelle nous pourrons associer la saga \emph{Harry Potter} et ses dérivés.
|
||||
\end{itemize}
|
||||
|
||||
En clair, notre modèle n'est pas adapté, et nous devons le modifier pour qu'une occurrence puisse être liée à plusieurs catégories.
|
||||
Au lieu d'utiliser un champ de type \texttt{ForeignKey}, nous utiliserons un champ de type \texttt{ManyToMany}, c'est-à-dire qu'à présent, un livre pourra être lié à plusieurs catégories, et qu'inversément, une même catégorie pourra être liée à plusieurs livres.
|
||||
En clair, notre modèle n'est pas adapté, et nous devons le modifier pour qu'une occurrence d'un livre puisse être liée à plusieurs catégories.
|
||||
Au lieu d'utiliser un champ de type \texttt{ForeignKey}, nous utiliserons à présent un champ de type \texttt{ManyToMany}, c'est-à-dire qu'un livre pourra être lié à plusieurs catégories, et qu'inversément, une même catégorie pourra être liée à plusieurs livres.
|
||||
|
||||
\begin{minted}{python}
|
||||
class Category(models.Model):
|
||||
|
@ -236,7 +236,7 @@ class Category(models.Model):
|
|||
|
||||
class Book(models.Model):
|
||||
title = models.CharField(max_length=255)
|
||||
category = models.ManyManyField(Category, on_delete=models.CASCADE)
|
||||
category = models.ManyManyField(Category)
|
||||
\end{minted}
|
||||
|
||||
Notre code d'initialisation reste par contre identique: Django s'occupe parfaitement de gérer la transition.
|
||||
|
@ -314,13 +314,57 @@ class Runner(models.Model):
|
|||
|
||||
\section{Validateurs}
|
||||
|
||||
La validation des champs intervient sur toute donnée entrée via le modèle.
|
||||
Cette validation dispose de trois niveaux:
|
||||
|
||||
\begin{enumerate}
|
||||
\item Directement au niveau du modèle, au travers de validateurs sur les champs
|
||||
\item Via une méthode de nettoyage sur un champ, qui permet de prendre d'autres informations contextuelles en considération
|
||||
\item Via une méthode de nettoyage globale, associée à l'instance.
|
||||
\end{enumerate}
|
||||
|
||||
\subsection{Validation d'un champ}
|
||||
|
||||
La première manière de valider le contenu d'un champ est aussi la plus simple.
|
||||
En prenant un modèle type:
|
||||
|
||||
\begin{minted}{python}
|
||||
from django.db import models
|
||||
|
||||
def validate_title(value):
|
||||
if 't' not in value:
|
||||
raise ValidationError('Title does not start with T')
|
||||
|
||||
class Book(models.Model):
|
||||
title = models.CharField(max_length=255, validators=[validate_title])
|
||||
\end{minted}
|
||||
|
||||
\subsection{Clean\_<field\_name>}
|
||||
|
||||
Ca, c'est le deuxième niveau. Le contexte donne accès à déjà plus d'informations et permet de valider les informations en interogeant (par exemple) la base de données.
|
||||
|
||||
\begin{minted}{python}
|
||||
class Bidule(models.Model):
|
||||
def clean_title(self):
|
||||
raise ValidationError('Title does not start with T')
|
||||
\end{minted}
|
||||
|
||||
Il n'est plus nécessaire de définir d'attribut \texttt{validators=[...]}, puisque Django va appliquer un peu d'introspection pour récupérer toutes les méthodes qui commencent par \texttt{clean\_} et pour les faire correspondre au nom du champ à valider (ici, \texttt{title}).
|
||||
|
||||
\subsection{Clean}
|
||||
|
||||
Ici, c'est global: nous pouvons valider des données ou des champs globalement vis-à-vis d'autres champs déjà remplis.
|
||||
|
||||
\begin{minted}{python}
|
||||
class Bidule(models.Model):
|
||||
def clean(self):
|
||||
raise ValidationError('Title does not start with T')
|
||||
\end{minted}
|
||||
|
||||
\section{Constructeurs}
|
||||
|
||||
Si vous décidez de définir un constructeur sur votre modèle, ne surchargez pas la méthode \texttt{init}: créez plutôt une méthode static de type \texttt{create()}, en y associant les paramètres obligatoires ou
|
||||
souhaités.
|
||||
Mieux encore: on pourrait passer par un \texttt{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.
|
||||
Si vous décidez de définir un constructeur sur votre modèle, ne surchargez pas la méthode \texttt{\_\_init\_\_}: créez plutôt une méthode static de type \texttt{create()}, en y associant les paramètres obligatoires ou souhaités.
|
||||
Mieux encore: nous pouvons passer par un \texttt{ModelManager} pour limiter le couplage; l'accès à une information stockée en base de données ne se fait dès lors qu'au travers de cette instance et pas directement au travers du modèle.
|
||||
|
||||
\begin{minted}{python}
|
||||
class ItemManager(...):
|
||||
|
@ -380,26 +424,22 @@ Il existe deux autres options : combiner deux querysets avec l'opérateur `&` ou
|
|||
Soit encore combiner des filtres:
|
||||
|
||||
\begin{minted}{python}
|
||||
from core.models import Wish
|
||||
from core.models import Wish
|
||||
|
||||
Wish.objects
|
||||
Wish.objects.filter(name__icontains="test").filter(name__icontains="too")
|
||||
Wish.objects
|
||||
Wish.objects.filter(name__icontains="test").filter(name__icontains="too")
|
||||
\end{minted}
|
||||
|
||||
|
||||
\begin{itemize}
|
||||
\item
|
||||
Ca, c'est notre manager.
|
||||
\item
|
||||
Et là, on chaîne les requêtes pour composer une recherche sur tous les
|
||||
souhaits dont le nom contient (avec une casse insensible) la chaîne
|
||||
"test" et dont le nom contient la chaîne "too".
|
||||
\end{itemize}
|
||||
\item
|
||||
Ca, c'est notre manager.
|
||||
\item
|
||||
Et là, on chaîne les requêtes pour composer une recherche sur tous les souhaits dont le nom contient (avec une casse insensible) la chaîne "test" et dont le nom contient la chaîne "too".
|
||||
\end{itemize}
|
||||
|
||||
Pour un 'OR', on a deux options :
|
||||
Pour un 'OR', on a deux options :
|
||||
|
||||
\begin{enumerate}
|
||||
\def\labelenumi{\arabic{enumi}.}
|
||||
\item
|
||||
Soit passer par deux querysets, typiuqment
|
||||
\texttt{queryset1\ \textbar{}\ queryset2}
|
||||
|
@ -409,12 +449,12 @@ Wish.objects.filter(name__icontains="test").filter(name__icontains="too")
|
|||
\end{enumerate}
|
||||
|
||||
\begin{minted}{python}
|
||||
from django.db.models import Q
|
||||
from django.db.models import Q
|
||||
|
||||
condition1 = Q(...)
|
||||
condition2 = Q(...)
|
||||
condition1 = Q(...)
|
||||
condition2 = Q(...)
|
||||
|
||||
bidule.objects.filter(condition1 | condition2)
|
||||
bidule.objects.filter(condition1 | condition2)
|
||||
\end{minted}
|
||||
|
||||
L'opérateur inverse (\emph{NOT})
|
||||
|
@ -422,50 +462,38 @@ L'opérateur inverse (\emph{NOT})
|
|||
Idem que ci-dessus : soit on utilise la méthode \texttt{exclude} sur le
|
||||
queryset, soit l'opérateur \texttt{\textasciitilde{}} sur un Q object;
|
||||
|
||||
|
||||
\section{Optimisation}
|
||||
|
||||
|
||||
\subsection{N+1 Queries}
|
||||
|
||||
\begin{itemize}
|
||||
\item
|
||||
\url{http://stackoverflow.com/questions/12681653/when-to-use-or-not-use-iterator-in-the-django-orm}
|
||||
\item
|
||||
\url{https://docs.djangoproject.com/en/1.9/ref/models/querysets/\#django.db.models.query.QuerySet.iterator}
|
||||
\item
|
||||
\url{http://blog.etianen.com/blog/2013/06/08/django-querysets/}
|
||||
\end{itemize}
|
||||
|
||||
Deux solutions:
|
||||
|
||||
\begin{enumerate}
|
||||
\def\labelenumi{\arabic{enumi}.}
|
||||
\item
|
||||
Prefetch
|
||||
\item
|
||||
select\_related
|
||||
\end{enumerate}
|
||||
|
||||
|
||||
\subsection{Unicité}
|
||||
|
||||
\end{enumerate}
|
||||
|
||||
\subsection{Indices}
|
||||
|
||||
\section{Agrégation et annotations}
|
||||
Après analyse seulement.
|
||||
|
||||
\subsection{Agrégation et annotations}
|
||||
|
||||
\url{https://docs.djangoproject.com/en/3.1/topics/db/aggregation/}
|
||||
|
||||
\section{Métamodèle et introspection}
|
||||
|
||||
Comme chaque classe héritant de \texttt{models.Model} possède une propriété \texttt{objects}.
|
||||
Comme on l'a vu dans la section \textbf{Jouons un peu avec la console}, cette propriété permet d'accéder
|
||||
aux objects persistants dans la base de données, au travers d'un \texttt{ModelManager}.
|
||||
|
||||
En plus de cela, il faut bien tenir compte des propriétés \texttt{Meta} de la classe: si elle contient déjà un ordre par défaut, celui-ci sera pris en compte pour l'ensemble des requêtes effectuées sur cette classe.
|
||||
|
||||
\begin{minted}{python}
|
||||
class Wish(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
|
||||
class Meta:
|
||||
ordering = ('name',)
|
||||
\end{minted}
|
||||
|
||||
Nous définissons un ordre par défaut, directement au niveau du modèle.
|
||||
Cela ne signifie pas qu'il ne sera pas possible de modifier cet ordre (la méthode \texttt{order\_by} existe et peut être chaînée à n'importe quel \emph{queryset}). D'où l'intérêt de tester ce type de comportement, dans la mesure où un \texttt{top\ 1} dans votre code pourrait être modifié simplement par cette petite information.
|
||||
|
||||
Pour sélectionner un objet au pif: \texttt{return\ Category.objects.order\_by("?").first()}
|
||||
Cette propriété permet d'accéder aux objects persistants dans la base de données, au travers d'un \texttt{ModelManager}.
|
||||
|
||||
Les propriétés de la classe Meta les plus utiles sont les suivates:
|
||||
|
||||
|
@ -481,6 +509,39 @@ Les propriétés de la classe Meta les plus utiles sont les suivates:
|
|||
\texttt{contraints} (Voir \href{https://girlthatlovestocode.com/django-model}{ici}-), par exemple
|
||||
\end{itemize}
|
||||
|
||||
\subsection{Ordre par défaut}
|
||||
|
||||
\begin{minted}{python}
|
||||
class Wish(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
|
||||
class Meta:
|
||||
ordering = ('name',)
|
||||
\end{minted}
|
||||
|
||||
Nous définissons un ordre par défaut, directement au niveau du modèle.
|
||||
Cela ne signifie pas qu'il ne sera pas possible de modifier cet ordre (la méthode \texttt{order\_by} existe et peut être chaînée à n'importe quel \emph{queryset}).
|
||||
D'où l'intérêt de tester ce type de comportement, dans la mesure où un \texttt{top\ 1} dans votre code pourrait être modifié simplement par cette petite information.
|
||||
|
||||
Pour sélectionner un objet au pif: \texttt{return\ Category.objects.order\_by("?").first()}
|
||||
|
||||
En plus de cela, il faut bien tenir compte des propriétés \texttt{Meta} de la classe: si elle contient déjà un ordre par défaut, celui-ci sera pris en compte pour l'ensemble des requêtes effectuées sur cette classe.
|
||||
Cela signifie que le \texttt{top 1} utilisé dans le code pourrait être impacté en cas de modification de cette propriété \texttt{ordering}, ce qui corrobore la nécessité de \emph{tester le code dans son ensemble}.
|
||||
|
||||
|
||||
\subsection{Représentation textuelle}
|
||||
|
||||
verbose\_name et verbose\_name\_plural + lien avec
|
||||
|
||||
\subsection{Contraintes}
|
||||
|
||||
Les contraintes sont de plusieurs types:
|
||||
|
||||
\begin{enumerate}
|
||||
\item Unicité
|
||||
\item Composée
|
||||
\end{enumerate}
|
||||
|
||||
\begin{minted}{python}
|
||||
constraints = [ # constraints added
|
||||
models.CheckConstraint(check=models.Q(year_born__lte=datetime.date
|
||||
|
@ -490,15 +551,6 @@ constraints = [ # constraints added
|
|||
|
||||
\section{Querysets et managers}
|
||||
|
||||
\begin{itemize}
|
||||
\item
|
||||
\url{http://stackoverflow.com/questions/12681653/when-to-use-or-not-use-iterator-in-the-django-orm}
|
||||
\item
|
||||
\url{https://docs.djangoproject.com/en/1.9/ref/models/querysets/\#django.db.models.query.QuerySet.iterator}
|
||||
\item
|
||||
\url{http://blog.etianen.com/blog/2013/06/08/django-querysets/}
|
||||
\end{itemize}
|
||||
|
||||
L'ORM de Django (et donc, chacune des classes qui composent votre modèle) propose par défaut deux objets hyper importants:
|
||||
|
||||
\begin{itemize}
|
||||
|
@ -751,8 +803,6 @@ Nous pourrions ainsi définir les classes suivantes:
|
|||
w.save()
|
||||
\end{minted}
|
||||
|
||||
|
||||
|
||||
\section{Conclusions}
|
||||
|
||||
Le modèle proposé par Django est un composant extrêmement performant, mais fort couplé avec le coeur du framework.
|
||||
|
@ -761,7 +811,7 @@ 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.
|
||||
|
||||
Dans les examples ci-dessus, nous avons vu les relations multiples (1-N), représentées par des clés étrangères (\textbf{ForeignKey}) d'une classe A vers une classe B.
|
||||
Dans les exemples ci-dessus, nous avons vu les relations multiples (1-N), représentées par des clés étrangères (\textbf{ForeignKey}) d'une classe A vers une classe B.
|
||||
Pour représenter d'autres types de relations, il existe également les champs de type \textbf{ManyToManyField}, afin de représenter une relation N-N.
|
||||
Il existe également un type de champ spécial pour les clés étrangères, qui est le Les champs de type \textbf{OneToOneField}, pour représenter une relation 1-1.
|
||||
|
||||
|
|
Loading…
Reference in New Issue