Encapsulez une entité avec un modèle
Dans la Partie 2, nous avons créé notre premier modèle, le modèle Band
, avec un champ name
. Ce champ était d'un type spécifique : CharField
, qui est l'abréviation de character field
: un champ qui contient des données de type caractère ou chaîne de caractères.
Notre modèle Band
pourrait en outre contenir des données sur de nombreuses autres caractéristiques :
genre musical (genre) ;
biographie (biography) ;
année de formation (year_formed) ;
le groupe est-il actuellement actif : oui ou non ? (active) ;
page officielle (official_page).
Quel type de données pourrions-nous utiliser pour chacun de ces champs ? Réfléchissez-y avant de les ajouter au cours de la prochaine section.
Nous pouvons également vouloir appliquer certaines règles à ces champs. Par exemple, quelle est la longueur maximale en caractères que nous acceptons dans une biographie ? Le genre doit-il être un champ « free text », ou l'utilisateur doit-il choisir dans une liste de choix ? Si le groupe n'a pas de page officielle, pouvons-nous laisser ce champ vide ?
Dans ce chapitre, nous allons découvrir les différents types de champs qu'un modèle peut avoir. Nous verrons également comment appliquer des règles à ces champs afin de contraindre leurs valeurs dans des limites qui nous conviennent.
Définissez les champs d'un modèle
Revenons à notre modèle « Band », que nous avons laissé comme ceci :
# listings/models.py
class Band(models.Model):
name = models.fields.CharField(max_length=100)
Ajoutez les champs supplémentaires que nous avons énumérés ci-dessus :
# listings/models.py
class Band(models.Model):
name = models.fields.CharField(max_length=100)
genre = models.fields.CharField()
biography = models.fields.CharField()
year_formed = models.fields.IntegerField()
active = models.fields.BooleanField()
official_homepage = models.fields.URLField()
Parlons du type de champ que nous avons utilisé pour chaque champ :
genre
etbiography
: tout commename
, ces champs contiendront des données de type caractère ou chaîne de caractères, nous utiliserons doncCharField
.year_formed
: une année est essentiellement un nombre entier, donc j'ai opté pour unIntegerField
ici. Django a aussi unDateField
, mais il comprendrait aussi un mois et un jour, qui ne seraient pas utilisés dans ce cas.active
: il s'agit d'une réponse « oui » ou « non », donc unBooleanField
avec des valeursTrue
ouFalse
est parfait ici.official_homepage
: pour la page officielle, nous aurions pu utiliser un autreCharField
ici, mais Django a une meilleure proposition :URLField
, qui n'autorisera que les URL valides dans ce champ ; nous verrons comment cela fonctionne plus tard.
Passez des arguments à chaque champ
Maintenant que nous savons quels types de champs nous utilisons, transmettons des arguments (également appelés « options de champ ») à chaque champ :
# listings/models.py
from django.core.validators import MaxValueValidator, MinValueValidator
...
class Band(models.Model):
name = models.fields.CharField(max_length=100)
genre = models.fields.CharField(max_length=50)
biography = models.fields.CharField(max_length=1000)
year_formed = models.fields.IntegerField(
validators=[MinValueValidator(1900), MaxValueValidator(2021)]
)
active = models.fields.BooleanField(default=True)
official_homepage = models.fields.URLField(null=True, blank=True)
Pour les
CharField
, l'optionmax_length
est obligatoire, nous obtenons une erreur si nous essayons d'exécuter l'application sans elle. Nous avons choisi des valeurs appropriées pour chaque champ.year_formed
doit avoir des valeurs minimales et maximales appropriées. J'ai opté pour un minimum de1900
(si nous avons vraiment besoin d'ajouter un groupe plus ancien que cela, nous pourrons changer cela plus tard !) Le maximum est fixé à2021
: cela suffira pour l'année où j'écris ces lignes, mais que se passera-t-il lorsque l'année suivante arrivera ? Nous aurons peut-être besoin de revenir sur ce point plus tard.
Pour appliquer ces contraintes, nous passons une liste de validateurs à l'optionvalidators
. Dans cet exemple, nous utilisons deux des classes de validateurs intégrées à Django :MinValueValidator
etMaxValueValidator
, que nous importons dedjango.core.validators
.
Pour
active
, nous avons défini une valeurdéfaut
commeTrue
. Ainsi, si nous n'avons pas spécifié le statut actif d'un groupe, il sera ramené àTrue
.Vous vous souvenez que nous avions dit que nous pourrions laisser le
official_hompage
vide si un groupe n'en avait pas ? Nous pouvons utiliser l'optionnull=True
pour autoriser les valeurs NULL dans la base de données. Et lorsque nous créerons un formulaire pour créer ou modifier des objetsBand
, le fait de définirblank=True
ici nous permettra de soumettre ce formulaire avec une zone de texte vide pour ce champ.
Faisons une personnalisation supplémentaire de ce modèle. Nous avons dit que nous aimerions que le champ genre
soit limité à une liste de choix que nous spécifions. L'un des problèmes des champs de texte libre est que différents utilisateurs peuvent saisir plusieurs versions de la même valeur, par exemple « Hip Hop », « hip hop » ou « Hip-Hop ». Nous pouvons éliminer ces variantes et conserver la cohérence des genres en laissant l'utilisateur choisir au sein d’une liste. L'inconvénient est que nous devrons maintenir cette liste et ajouter de nouveaux genres de temps en temps, mais pour les besoins de notre première application web Django, acceptons cette contrainte.
Ajoutez ce code à notre fichier de modèles :
# listings/models.py
class Band(models.Model):
class Genre(models.TextChoices):
HIP_HOP = 'HH'
SYNTH_POP = 'SP'
ALTERNATIVE_ROCK = 'AR'
...
genre = models.fields.CharField(choices=Genre.choices, max_length=5)
...
Nous avons créé la classeGenre
, définissant les choix qui peuvent être utilisés pour le champ genre
. Genre
est une classe imbriquée : c'est une classe définie dans une autre classe. Parfois, nous le faisons en Python si les classes sont très étroitement liées, comme c'est le cas ici.
La classe
Genre
hérite demodels.TextChoices
: c'est une classe dans Django qui est conçue pour définir une liste de choix.Dans
Genre
, nous ajoutons une constante pour chaque choix de genre que nous voulons autoriser, (par exempleHIP_HOP
).Pour chaque constante, nous définissons une clé (
'HH'
,'SP'
,'AR'
) : c'est la valeur qui sera stockée dans la base de données pour ce genre.
Enfin, nous mettons à jour les options pour notre champ genre
: nous définissons choices=Genre.choices
pour limiter les choix à ceux que nous avons définis dans la classe imbriquée. Jusqu'à présent, nos clés ne font que 2 caractères (par exemple 'HH'
), mais donnons-nous un peu de marge au cas où la liste s'allongerait, et définissons max_length=5
.
Essayez le nouveau modèle
Il est donc temps d'essayer notre nouveau modèle ! Ouvrez le shell de Django et essayez d'enregistrer un nouvel objet Band
:
(env) ~/projects/django-web-app/merchex
→ python manage.py shell
>>> from listings.models import Band
>>> band = Band()
>>> band.save()
...
django.db.utils.OperationalError: table listings_band has no column named genre
Oh ! Nous ne pouvons pas enregistrer un nouveau Band
.
Il y a un problème avec la table listings_band
.
Il n'y a pas encore de colonne nommée genre
.
Est-ce que vous vous rappelez de ce qui doit être fait avant ?
Ajoutez de nouveaux champs de modèle au schéma de la base de données avec une migration
Le problème est que, si nous avons ajouté de nouveaux champs à notre modèle, nous n'avons cependant pas mis à jour le schéma de notre base de données (sa structure) en conséquence.
Faisons donc cela avec une migration. Mais avant de commencer, voici une explication en vidéo de ce que nous allons faire !
Prêt à vous lancer ?
La commande makemigrations
va scanner notre fichier models.py et trouver ce qui a changé depuis la dernière fois que nous avons lancé la commande dans la Partie 2. Il remarquera que nous avons ajouté de nouveaux champs à notre modèle Band
et générera une migration qui mettra à jour notre schéma avec les nouvelles colonnes.
Appuyez sur Ctrl - D pour quitter le shell et ensuite :
(env) ~/projects/django-web-app/merchex
→ python manage.py makemigrations
You are trying to add a non-nullable field 'biography' to band without a default; we can't do that (the database needs something to populate existing rows).
Nous allons maintenant être invités à saisir certaines valeurs par défaut. Django les demande parfois lorsqu'il effectue une migration, et certains de ces champs sont « non-nullables » (en d'autres termes, non facultatifs).
Pensez-y de la manière suivante : nous avons déjà 3 lignes dans ce tableau :
id | name |
1 | De La Soul |
2 | Cut Copy |
3 | Foo Fighters |
Si nous ajoutons de nouvelles colonnes et que les valeurs de ces colonnes ne peuvent pas être nulles, alors nous devons mettre quelque chose dans ces colonnes pour les lignes existantes (indiquées avec un « ? ») :
id | name | biography | genre | year_formed | active | official_homepage |
1 | De La Soul | ? | ? | ? | True | NULL |
2 | Cut Copy | ? | ? | ? | True | NULL |
3 | Foo Fighters | ? | ? | ? | True | NULL |
Lorsque Django vous demande de renseigner chacun de ces champs, sélectionnez l'option 1 (« Provide a one-off default now (Fournir une valeur par défaut unique maintenant) »), puis utilisez ces valeurs par défaut :
biographie
: nous pouvons simplement passer une chaîne vide ici :''
.genre
: ce devrait être l'une des clés que nous avons définies plus tôt. Utilisons'HH'
; bien sûr, tous nos groupes ne sont pas du genre Hip Hop, mais nous pourrons corriger cela dans le prochain chapitre.year_formed
: utilisons par défaut l'année2000
; encore une fois, ce n'est pas vrai pour nos groupes, mais nous corrigerons cela plus tard.
Voici la sortie du terminal pour le premier champ biography
:
(env) ~/projects/django-web-app/merchex
→ python manage.py makemigrations
You are trying to add a non-nullable field 'biography' to band without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Quit, and let me add a default in models.py
Select an option: 1
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now
Type 'exit' to exit this prompt
>>> ''
Vous serez ensuite invité à remplir les deux autres champs.
Enfin, nous verrons que notre migration a été générée :
Migrations for 'listings':
listings/migrations/0003_auto_20210329_2350.py
- Add field active to band
- Add field biography to band
- Add field genre to band
- Add field official_homepage to band
- Add field year_formed to band
Exécutons ça :
(env) ~/projects/django-web-app/merchex
→ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, listings, sessions
Running migrations:
Applying listings.0003_auto_20210329_2350... OK
Maintenant, retournons dans le shell pour tester notre modèle une fois de plus :
(env) ~/projects/django-web-app/merchex
→ python manage.py shell
>>> from listings.models import Band
>>> band = Band()
>>> band.save()
...
django.db.utils.IntegrityError: NOT NULL constraint failed: listings_band.year_formed
On a une IntegrityError
sur le champ year_formed
. C'est une bonne chose ! Nous n'avons pas spécifié null=True
sur ce champ, donc il y a une erreur sur les valeurs NULL.
Nous pouvons observer que year_formed
est actuellement défini à None
:
>>> band.year_formed is None
True
Pour les valeurs None
, une tentative d’enregistrement comme NULL sera effectuée dans la base de données. Mettons donc year_formed
à quelque chose :
>>> band.year_formed = 2000
Et maintenant, essayons à nouveau d’enregistrer :
>>>
band.save()
>>> band
<Band: Band object (4)>
Cette fois, cela a fonctionné et l'objet a été enregistré dans la base de données.
Mais qu'en est-il des autres champs tels que name
, genre
et biography
? Nous n'avons pas non plus spécifié null=True
sur ceux-ci, alors comment se fait-il qu'ils aient pu être sauvegardés dans la base de données ?
Examinons les valeurs de ces champs :
>>> band.name
''
>>> band.genre
''
>>> band.biography
''
Django les a définis comme des chaînes vides pour nous. Techniquement parlant, ce ne sont pas des NULL, il a donc été possible d'insérer ce groupe dans la base de données.
Mais est-ce vraiment ce que nous voulions ? Et si nous voulons obliger les utilisateurs à saisir une valeur ?
Dans le chapitre suivant, nous verrons comment la validation du formulaire oblige l'utilisateur à saisir une valeur pour ces champs.
En attendant, supprimons ce groupe sans nom de notre base de données :
>>> band.delete()
(1, {'listings.Band': 1})
Le shell confirme qu'un objet Band
a été supprimé.
Enfin, fermez le shell avec Ctrl-D.
Vous pouvez rafraîchir vos connaissances sur les concepts de ce chapitre en regardant le screencast.
C'est à vous ! Définissez d'autres champs dans votre modèle, effectuez et exécutez une migration
Ajoutez les champs suivants à votre modèle Listing
, en réfléchissant au type de données que vous devez utiliser, et donc au type de champs que vous devez utiliser pour chacun d'entre eux (CharField
, IntegerField
, etc.) :
description
: décrit l'article vendu.sold
: l’article a-t-il déjà été vendu ou non ? Quelle doit être la valeur par défaut d'une nouvelle annonce ?year
: de quelle année date l'article ? Cela sera utile aux acheteurs qui recherchent des articles vintage. Mais on doit pouvoir laisser ce champ vide si l'année est inconnue.type
: il y a quelquestypes
d’annonces que nous nous attendons à voir : disques (Records
), vêtements (Clothing
, affiches (Posters
) et divers (Miscellaneous
).
Ensuite, faites une migration. N'oubliez pas que des valeurs par défaut vous seront demandées pour tous les champs qui ne peuvent pas être nuls. Ne vous inquiétez pas si la valeur par défaut que vous choisissez n'est pas tout à fait correcte pour vos annonces, nous y remédierons dans le prochain chapitre.
Enfin, appliquez votre migration. Nous verrons ces nouveaux champs en action dans le prochain chapitre.
En résumé
Django est livré avec différents types de champs qui correspondent à différents types de données, comme
CharField
ouIntegerField
. Il existe aussi des champs plus spécifiques qui vont contraindre l'entrée, commeURLField
.Nous pouvons définir des contraintes et des règles pour les champs en leur attribuant des options, comme
max_length
,null
etchoices
.Nous pouvons affiner davantage les contraintes sur les champs en spécifiant des validateurs sur les champs en utilisant l'option
validators
.Lorsque nous ajoutons de nouveaux champs à un modèle, nous devons effectuer une migration pour ajouter de nouvelles colonnes à la base de données, avant de pouvoir commencer à les utiliser.
Si nous ajoutons des champs non nuls à un modèle, nous serons invités à leur fournir une valeur par défaut initiale lors de la migration.
Maintenant que nous avons construit nos modèles, découvrons une interface que nous pouvons utiliser pour gérer ces modèles, qui est intégrée à Django : le site d'administration.