• 12 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 7/15/24

Capturez des données avec des modèles et des champs

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 et biography: tout comme name , ces champs contiendront des données de type caractère ou chaîne de caractères, nous utiliserons donc CharField.

  • year_formed: une année est essentiellement un nombre entier, donc j'ai opté pour un IntegerField ici. Django a aussi un DateField , 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 un BooleanField avec des valeurs True ou False est parfait ici.

  • official_homepage : pour la page officielle, nous aurions pu utiliser un autre CharField 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'option max_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 de 1900 (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'option validators. Dans cet exemple, nous utilisons deux des classes de validateurs intégrées à Django : MinValueValidator et MaxValueValidator , que nous importons de django.core.validators.

  • Pour active , nous avons défini une valeur défaut comme True. 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'option null=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 objets Band , le fait de définir blank=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 genreGenre 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 de models.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 exemple HIP_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ée 2000 ; 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 namegenre 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 quelques types 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 ou IntegerField . Il existe aussi des champs plus spécifiques qui vont contraindre l'entrée, comme URLField .

  • Nous pouvons définir des contraintes et des règles pour les champs en leur attribuant des options, comme max_length , null et choices .

  • 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.

Example of certificate of achievement
Example of certificate of achievement