Django
Des modèles
Recherche…
Introduction
Dans le cas de base, un modèle est une classe Python mappée sur une seule table de base de données. Les attributs de la classe mappent aux colonnes de la table et une instance de la classe représente une ligne dans la table de base de données. Les modèles héritent de django.db.models.Model
qui fournit une API riche pour ajouter et filtrer les résultats de la base de données.
Créer votre premier modèle
Les modèles sont généralement définis dans le fichier models.py
sous le sous-répertoire de votre application. La classe Model
du module django.db.models
est une bonne classe de départ pour étendre vos modèles. Par exemple:
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey('Author', on_delete=models.CASCADE, related_name='authored_books')
publish_date = models.DateField(null=True, blank=True)
def __str__(self): # __unicode__ in python 2.*
return self.title
Chaque attribut dans un modèle représente une colonne dans la base de données.
-
title
est un texte d'une longueur maximale de 100 caractères -
author
est uneForeignKey
qui représente une relation avec un autre modèle / tableau, en l'occurrenceAuthor
(utilisé uniquement à titre d'exemple).on_delete
indique à la base de données quoi faire avec l'objet si l'objet associé (unAuthor
) est supprimé. (Il convient de noter que django 1.9on_delete
peut être utilisé comme deuxième argument de position. Dans django 2, il s'agit d'un argument obligatoire et il est conseillé de le traiter immédiatement. Dans les versions antérieures, il seraCASCADE
par défaut surCASCADE
.) -
publish_date
stocke une date. Lesnull
etblank
sont définies surTrue
pour indiquer qu'il ne s'agit pas d'un champ obligatoire (vous pouvez l'ajouter ultérieurement ou le laisser vide).
Avec les attributs que nous définissons, une méthode __str__
renvoie le titre du livre qui sera utilisé comme représentation sous forme de string
, plutôt que la valeur par défaut.
Appliquer les modifications à la base de données (Migrations)
Après avoir créé un nouveau modèle ou modifié des modèles existants, vous devrez générer des migrations pour vos modifications, puis appliquer les migrations à la base de données spécifiée. Cela peut être fait en utilisant le système de migration intégré de Django. A l'aide de l'utilitaire manage.py
, dans le répertoire racine du projet:
python manage.py makemigrations <appname>
La commande ci-dessus créera les scripts de migration nécessaires sous le sous-répertoire migrations
de votre application. Si vous omettez le paramètre <appname>
, toutes les applications définies dans l'argument INSTALLED_APPS
de settings.py
seront traitées. Si vous le trouvez nécessaire, vous pouvez modifier les migrations.
Vous pouvez vérifier quelles migrations sont nécessaires sans créer la migration en utilisant l'option --dry-run, par exemple:
python manage.py makemigrations --dry-run
Pour appliquer les migrations:
python manage.py migrate <appname>
La commande ci-dessus exécutera les scripts de migration générés lors de la première étape et mettra à jour physiquement la base de données.
Si le modèle de la base de données existante est modifié, la commande suivante est nécessaire pour effectuer les modifications nécessaires.
python manage.py migrate --run-syncdb
Django va créer la table avec le nom <appname>_<classname>
par défaut. Parfois, vous ne voulez pas l'utiliser. Si vous souhaitez modifier le nom par défaut, vous pouvez annoncer le nom de la table en définissant la db_table
dans la classe Meta
:
from django.db import models
class YourModel(models.Model):
parms = models.CharField()
class Meta:
db_table = "custom_table_name"
Si vous voulez voir quel code SQL sera exécuté par une certaine migration, lancez simplement cette commande:
python manage.py sqlmigrate <app_label> <migration_number>
Django> 1.10
La nouvelle option makemigrations --check
permet à la commande de sortir avec un statut différent de zéro lorsque des modifications du modèle sans migration sont détectées.
Voir Migrations pour plus de détails sur les migrations.
Créer un modèle avec des relations
Relation plusieurs à un
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=50)
#Book has a foreignkey (many to one) relationship with author
class Book(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
publish_date = models.DateField()
Option la plus générique. Peut être utilisé partout où vous souhaitez représenter une relation
Relation plusieurs à plusieurs
class Topping(models.Model):
name = models.CharField(max_length=50)
# One pizza can have many toppings and same topping can be on many pizzas
class Pizza(models.Model):
name = models.CharField(max_length=50)
toppings = models.ManyToManyField(Topping)
En interne, cela est représenté via une autre table. Et ManyToManyField
devrait être mis sur des modèles qui seront édités sur un formulaire. Par exemple: Appointment
- Appointment
aura un ManyToManyField
appelé Customer
, Pizza
a Toppings
et ainsi de suite.
Relation plusieurs-à-plusieurs utilisant des cours
class Service(models.Model):
name = models.CharField(max_length=35)
class Client(models.Model):
name = models.CharField(max_length=35)
age = models.IntegerField()
services = models.ManyToManyField(Service, through='Subscription')
class Subscription(models.Model):
client = models.ForeignKey(Client)
service = models.ForeignKey(Service)
subscription_type = models.CharField(max_length=1, choices=SUBSCRIPTION_TYPES)
created_at = models.DateTimeField(default=timezone.now)
De cette façon, nous pouvons réellement conserver plus de métadonnées sur une relation entre deux entités. Comme on peut le voir, un client peut être abonné à plusieurs services via plusieurs types d'abonnement. La seule différence dans ce cas est que pour ajouter de nouvelles instances à la relation M2M, on ne peut pas utiliser la méthode de raccourci pizza.toppings.add(topping)
, mais un nouvel objet de la classe through doit être créé, Subscription.objects.create(client=client, service=service, subscription_type='p')
Dans d'autres langues, les
through tables
sont également connues sous le nom deIntersection table
JoinColumn
,Intersection table
oumapping table
Relation individuelle
class Employee(models.Model):
name = models.CharField(max_length=50)
age = models.IntegerField()
spouse = models.OneToOneField(Spouse)
class Spouse(models.Model):
name = models.CharField(max_length=50)
Utilisez ces champs lorsque vous n'aurez qu'une relation de composition entre les deux modèles.
Requêtes de base sur la base de données Django
Django ORM est une abstraction puissante qui vous permet de stocker et d'extraire des données de la base de données sans écrire vous-même des requêtes SQL.
Supposons les modèles suivants:
class Author(models.Model):
name = models.CharField(max_length=50)
class Book(models.Model):
name = models.CharField(max_length=50)
author = models.ForeignKey(Author)
En supposant que vous avez ajouté le code ci-dessus à une application django et exécutez la commande migrate
(afin que votre base de données soit créée). Démarrer le shell Django par
python manage.py shell
Cela démarre le shell python standard mais avec les bibliothèques Django pertinentes importées, afin que vous puissiez vous concentrer directement sur les parties importantes.
Commencez par importer les modèles que nous venons de définir (je suppose que cela se fait dans un fichier models.py
)
from .models import Book, Author
Exécutez votre première requête de sélection:
>>> Author.objects.all()
[]
>>> Book.objects.all()
[]
Permet de créer un objet auteur et livre:
>>> hawking = Author(name="Stephen hawking")
>>> hawking.save()
>>> history_of_time = Book(name="history of time", author=hawking)
>>> history_of_time.save()
ou utilisez la fonction create pour créer des objets de modèle et les enregistrer dans un code de ligne
>>> wings_of_fire = Book.objects.create(name="Wings of Fire", author="APJ Abdul Kalam")
Maintenant permet de lancer la requête
>>> Book.objects.all()
[<Book: Book object>]
>>> book = Book.objects.first() #getting the first book object
>>> book.name
u'history of time'
Ajoutons une clause where à notre requête select
>>> Book.objects.filter(name='nothing')
[]
>>> Author.objects.filter(name__startswith='Ste')
[<Author: Author object>]
Pour obtenir des détails sur l'auteur d'un livre donné
>>> book = Book.objects.first() #getting the first book object
>>> book.author.name # lookup on related model
u'Stephen hawking'
Pour obtenir tous les livres publiés par Stephen Hawking (Lookup book par son auteur)
>>> hawking.book_set.all()
[<Book: Book object>]
_set
est la notation utilisée pour les "recherches inversées", c'est-à-dire que lorsque le champ de recherche est sur le modèle Book, nous pouvons utiliser book_set
sur un objet auteur pour obtenir tous ses livres.
Une table non gérée de base.
À un certain moment de votre utilisation de Django, vous pourriez vouloir interagir avec des tables déjà créées ou avec des vues de base de données. Dans ces cas, vous ne voudriez pas que Django gère les tables lors de ses migrations. Pour configurer cela, vous devez ajouter une seule variable à la classe Meta
votre modèle: managed = False
.
Voici un exemple de la façon dont vous pouvez créer un modèle non géré pour interagir avec une vue de base de données:
class Dummy(models.Model):
something = models.IntegerField()
class Meta:
managed = False
Cela peut être mappé sur une vue définie dans SQL comme suit.
CREATE VIEW myapp_dummy AS
SELECT id, something FROM complicated_table
WHERE some_complicated_condition = True
Une fois ce modèle créé, vous pouvez l'utiliser comme n'importe quel autre modèle:
>>> Dummy.objects.all()
[<Dummy: Dummy object>, <Dummy: Dummy object>, <Dummy: Dummy object>]
>>> Dummy.objects.filter(something=42)
[<Dummy: Dummy object>]
Modèles avancés
Un modèle peut fournir beaucoup plus d'informations que les seules données relatives à un objet. Voyons un exemple et décomposons-le en ce qui est utile pour:
from django.db import models
from django.urls import reverse
from django.utils.encoding import python_2_unicode_compatible
@python_2_unicode_compatible
class Book(models.Model):
slug = models.SlugField()
title = models.CharField(max_length=128)
publish_date = models.DateField()
def get_absolute_url(self):
return reverse('library:book', kwargs={'pk':self.pk})
def __str__(self):
return self.title
class Meta:
ordering = ['publish_date', 'title']
Clé primaire automatique
Vous remarquerez peut-être l'utilisation de self.pk
dans la méthode get_absolute_url
. Le champ pk
est un alias de la clé primaire d'un modèle. De plus, django ajoutera automatiquement une clé primaire si elle manque. C'est une chose de moins à s'inquiéter et vous permet de définir la clé étrangère sur n'importe quel modèle et de l'obtenir facilement.
URL absolue
La première fonction définie est get_absolute_url
. De cette façon, si vous avez un livre, vous pouvez obtenir un lien vers celui-ci sans manipuler la balise URL, la résolution, l'attribut, etc. Appelez simplement book.get_absolute_url
et vous obtenez le bon lien. En bonus, votre objet dans l'administrateur de django gagnera un bouton "voir sur le site".
Représentation de chaîne
Avoir une méthode __str__
vous permet d'utiliser l'objet lorsque vous avez besoin de l'afficher. Par exemple, avec la méthode précédente, ajouter un lien au livre dans un modèle est aussi simple que <a href="{{ book.get_absolute_url }}">{{ book }}</a>
. Droit au but. Cette méthode contrôle également ce qui est affiché dans la liste déroulante admin, par exemple pour une clé étrangère.
Le décorateur de classe vous permet de définir la méthode une fois pour __str__
et __unicode__
sur python 2 tout en ne causant aucun problème sur python 3. Si vous souhaitez que votre application s'exécute sur les deux versions, c'est la voie à suivre.
Terrain de limaces
Le champ slug est similaire à un champ char mais accepte moins de symboles. Par défaut, seules les lettres, les chiffres, les traits de soulignement ou les tirets. C'est utile si vous voulez identifier un objet en utilisant une belle représentation, par exemple dans url.
La classe Meta
La classe Meta
nous permet de définir beaucoup plus d'informations sur l'ensemble de la collection. Ici, seul le classement par défaut est défini. C'est utile avec l'objet ListView par exemple. Il faut idéalement une courte liste de champs à utiliser pour le tri. Ici, le livre sera trié d'abord par date de publication puis par titre si la date est la même.
Les autres attributs fréquents sont verbose_name
et verbose_name_plural
. Par défaut, ils sont générés à partir du nom du modèle et devraient convenir. Mais la forme plurielle est naïve, en ajoutant simplement un «s» au singulier, de sorte que vous pourriez vouloir le définir explicitement dans certains cas.
Valeurs calculées
Une fois qu'un objet de modèle a été extrait, il devient une instance entièrement réalisée de la classe. En tant que tel, toutes les méthodes supplémentaires sont accessibles dans des formulaires et des sérialiseurs (comme Django Rest Framework).
L'utilisation des propriétés python est un moyen élégant de représenter des valeurs supplémentaires qui ne sont pas stockées dans la base de données en raison de circonstances variables.
def expire():
return timezone.now() + timezone.timedelta(days=7)
class Coupon(models.Model):
expiration_date = models.DateField(default=expire)
@property
def is_expired(self):
return timezone.now() > self.expiration_date
Alors que la plupart des cas, vous pouvez compléter les données avec des annotations sur vos ensembles de requêtes, les valeurs calculées en tant que propriétés de modèle sont idéales pour les calculs qui ne peuvent pas être évalués simplement dans le cadre d'une requête.
De plus, les propriétés, puisqu'elles sont déclarées dans la classe python et non dans le schéma, ne sont pas disponibles pour la recherche.
Ajout d'une représentation sous forme de chaîne d'un modèle
Pour créer une présentation lisible par un humain d'un objet de modèle, vous devez implémenter la méthode Model.__str__()
(ou Model.__unicode__()
sur python2). Cette méthode sera appelée à chaque fois que vous appelez str()
sur une instance de votre modèle (y compris, par exemple, lorsque le modèle est utilisé dans un modèle). Voici un exemple:
Créez un modèle de livre.
# your_app/models.py from django.db import models class Book(models.Model): name = models.CharField(max_length=50) author = models.CharField(max_length=50)
Créez une instance du modèle et enregistrez-la dans la base de données:
>>> himu_book = Book(name='Himu Mama', author='Humayun Ahmed') >>> himu_book.save()
Exécutez
print()
sur l'instance:>>> print(himu_book) <Book: Book object>
<Book: Objet livre> , la sortie par défaut, ne nous aide pas. Pour corriger cela, ajoutons une méthode __str__
.
from django.utils.encoding import python_2_unicode_compatible
@python_2_unicode_compatible
class Book(models.Model):
name = models.CharField(max_length=50)
author = models.CharField(max_length=50)
def __str__(self):
return '{} by {}'.format(self.name, self.author)
Notez que le décorateur python_2_unicode_compatible
n'est nécessaire que si vous voulez que votre code soit compatible avec python 2. Ce décorateur copie la méthode __str__
pour créer une méthode __unicode__
. Importez-le depuis django.utils.encoding
.
Maintenant, si nous appelons la fonction d'impression, l'instance du livre à nouveau:
>>> print(himu_book)
Himu Mama by Humayun Ahmed
Beaucoup mieux!
La représentation sous forme de chaîne est également utilisée lorsque le modèle est utilisé dans les ModelForm
for ForeignKeyField
et ManyToManyField
.
Model mixins
Dans les mêmes cas, différents modèles peuvent avoir les mêmes champs et les mêmes procédures dans le cycle de vie du produit. Pour gérer ces similarités sans avoir à répéter le code, on pourrait utiliser l'héritage. Au lieu d'hériter une classe entière, le modèle de conception de mixin nous permet d'hériter ( ou d'inclure certains) des méthodes et des attributs. Regardons un exemple:
class PostableMixin(models.Model):
class Meta:
abstract=True
sender_name = models.CharField(max_length=128)
sender_address = models.CharField(max_length=255)
receiver_name = models.CharField(max_length=128)
receiver_address = models.CharField(max_length=255)
post_datetime = models.DateTimeField(auto_now_add=True)
delivery_datetime = models.DateTimeField(null=True)
notes = models.TextField(max_length=500)
class Envelope(PostableMixin):
ENVELOPE_COMMERCIAL = 1
ENVELOPE_BOOKLET = 2
ENVELOPE_CATALOG = 3
ENVELOPE_TYPES = (
(ENVELOPE_COMMERCIAL, 'Commercial'),
(ENVELOPE_BOOKLET, 'Booklet'),
(ENVELOPE_CATALOG, 'Catalog'),
)
envelope_type = models.PositiveSmallIntegerField(choices=ENVELOPE_TYPES)
class Package(PostableMixin):
weight = models.DecimalField(max_digits=6, decimal_places=2)
width = models.DecimalField(max_digits=5, decimal_places=2)
height = models.DecimalField(max_digits=5, decimal_places=2)
depth = models.DecimalField(max_digits=5, decimal_places=2)
Pour transformer un modèle en une classe abstraite, vous devez mentionner abstract=True
dans sa classe Meta
interne. Django ne crée aucune table pour les modèles abstraits de la base de données. Cependant, pour les modèles Envelope
et Package
, les tables correspondantes seraient créées dans la base de données.
En plus des champs, certaines méthodes de modélisation seront nécessaires sur plusieurs modèles. Ainsi, ces méthodes pourraient être ajoutées aux mixins pour empêcher la répétition du code. Par exemple, si nous créons une méthode pour définir la date de livraison sur PostableMixin
elle sera accessible à partir de ses deux enfants:
class PostableMixin(models.Model):
class Meta:
abstract=True
...
...
def set_delivery_datetime(self, dt=None):
if dt is None:
from django.utils.timezone import now
dt = now()
self.delivery_datetime = dt
self.save()
Cette méthode pourrait être utilisée comme suit sur les enfants:
>> envelope = Envelope.objects.get(pk=1)
>> envelope.set_delivery_datetime()
>> pack = Package.objects.get(pk=1)
>> pack.set_delivery_datetime()
Clé primaire UUID
Un modèle par défaut utilisera une clé primaire auto-incrémentée (entier). Cela vous donnera une séquence de touches 1, 2, 3.
Différents types de clé primaire peuvent être définis sur un modèle avec une petite modification du modèle.
Un UUID est un identifiant unique, il s’agit d’un identifiant aléatoire de 32 caractères qui peut être utilisé comme identifiant. C'est une bonne option à utiliser lorsque vous ne souhaitez pas que des identifiants séquentiels soient affectés à des enregistrements de votre base de données. Utilisée sur PostgreSQL, cette option stocke un type de données uuid, sinon dans un caractère (32).
import uuid
from django.db import models
class ModelUsingUUID(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
La clé générée sera au format 7778c552-73fc-4bc4-8bf9-5a2f6f7b7f47
Héritage
L'héritage entre les modèles peut se faire de deux manières:
- une classe abstraite commune (voir l'exemple "Model mixins")
- un modèle commun avec plusieurs tables
L'héritage multi-tables créera une table pour les champs communs et un pour chaque exemple de modèle enfant:
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
class Restaurant(Place):
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
créera 2 tables, une pour Place
et une pour Restaurant
avec un champ OneToOne
masqué à Place
pour les champs communs.
Notez que cela nécessitera une requête supplémentaire sur les tables de lieux chaque fois que vous récupérerez un objet de restaurant.