• Aucun résultat trouvé

L’héritage de modèles

III. Techniques avancées 122

13. Techniques avancées dans les modèles 139

13.3. L’héritage de modèles

Les modèles étant des classes, ils possèdent les mêmes propriétés que n’importe quelle classe, y compris l’héritage de classes. Néanmoins, Django propose trois méthodes principales pour gérer l’héritage de modèles, qui interagiront différemment avec la base de données. Nous les aborderons ici une à une.

13.3.1. Les modèles parents abstraits

Les modèles parents abstraits sont utiles lorsque vous souhaitez utiliser plusieurs méthodes et attributs dans différents modèles, sans devoir les réécrire à chaque fois. Tout modèle héritant d’un modèle abstrait récupère automatiquement toutes les caractéristiques de la classe dont elle hérite. La grande particularité d’un modèle abstrait réside dans le fait que Django ne l’utilisera pas comme représentation pour créer une table dans la base de données. En revanche, tous les modèles qui hériteront de ce parent abstrait auront bel et bien une table qui leur sera dédiée.

Afin de rendre un modèle abstrait, il suffit de lui assigner l’attribut abstract=Truedans sa sous-classe Meta. Django se charge entièrement du reste.

Pour illustrer cette méthode, prenons un exemple simple :

1 class Document(models.Model):

2 titre = models.CharField(max_length=255)

3 date_ajout = models.DateTimeField(auto_now_add=True,

4 verbose_name="Date d'ajout du document")

5 auteur = models.CharField(max_length=255, null=True, blank=True)

67 class Meta:

8 abstract = True

109 class Article(Document):

11 contenu = models.TextField() 1213 class Image(Document):

14 image = models.ImageField(upload_to="images") Listing 89 – Une chaine d’héritages.

Ici, deux tables seront créées dans la base de données : Articleet Image. Le modèleDocument ne sera pas utilisé comme table, étant donné que celui-ci est abstrait. En revanche, les tables Article etImage auront bien les champs de Document (donc par exemple la tableArticle aura les champs titre,date_ajout,auteur etcontenu).

Bien entendu, il est impossible de faire des requêtes sur un modèle abstrait, celui-ci n’ayant aucune table dans la base de données pour enregistrer des données. Vous ne pouvez interagir avec les champs du modèle abstrait que depuis les modèles qui en héritent.

13.3.2. Les modèles parents classiques

Contrairement aux modèles abstraits, il est possible d’hériter de modèles tout à fait normaux. Si un modèle hérite d’un autre modèle non abstrait, il n’y aura aucune différence pour ce dernier, il sera manipulable comme n’importe quel modèle. Django créera une table pour le modèle parent et le modèle enfant.

Listing 90 – Héritage sans modèle abstrait.

À partir de ces deux modèles, Django créera bien deux tables, une pour Lieu, l’autre pour Restaurant. Il est important de noter que la table Restaurant ne contient pas les champs de Lieu (à savoirnom etadresse). En revanche, elle contient bien le champ menu et une clé étrangère vers Lieu que le framework ajoutera tout seul. En effet, si Lieu est un modèle tout à fait classique,Restaurant agira un peu différemment.

Lorsque nous sauvegardons une nouvelle instance de Restaurant dans la base de données, une nouvelle entrée sera créée dans la table correspondant au modèle Restaurant, mais également dans celle correspondant à Lieu. Les valeurs des deux attributs nom et adresse seront enregistrées dans une entrée de la table Lieu, et l’attribut menu sera enregistré dans une entrée de la tableRestaurant. Cette dernière entrée contiendra donc également la clé étrangère vers l’entrée dans la tableLieu qui possède les données associées.

Pour résumer, l’héritage classique s’apparente à la liaison de deux classes avec une clé étrangère telle que nous en avons vu dans le chapitre introductif sur les modèles, à la différence que c’est Django qui se charge de réaliser lui-même cette liaison.

Sachez aussi que lorsque vous créez un objet Restaurant, vous créez aussi un objet Lieu tout à fait banal qui peut être obtenu comme n’importe quel objet Lieu créé précédemment. De plus, même si les attributs du modèle parent sont dans une autre table, le modèle fils a bien hérité de toutes ses méthodes et attributs :

1 >>>

Restaurant(nom=u"La crêperie bretonne",adresse="42 Rue de la crêpe 35000 Rennes", menu=u"Des crêpes !").save()

2 >>> Restaurant.objects.all()

3 [<Restaurant: La crêperie bretonne>]

4 >>> Lieu.objects.all()

5 [<Lieu: La crêperie bretonne>]

Pour finir, tous les attributs de Lieu sont directement accessibles depuis un objet Restau rant :

1 >>> resto = Restaurant.objects.all()[0]

2 >>> print resto.nom+", "+resto.menu 3 La crêperie bretonne, Des crêpes !

En revanche, il n’est pas possible d’accéder aux attributs spécifiques de Restaurant depuis une instance de Lieu :

1 >>> lieu = Lieu.objects.all()[0]

2 >>> print(lieu.nom) 3 La crêperie bretonne

4 >>> print(lieu.menu) #Ça ne marche pas 5 Traceback (most recent call last):

6 File "<console>", line 1, in <module>

7 AttributeError: 'Lieu' object has no attribute 'menu'

Pour accéder à l’instance de Restaurant associée à Lieu, Django crée tout seul une relation vers celle-ci qu’il nommera selon le nom de la classe fille :

1 >>> print type(lieu.restaurant) 2 <class 'blog.models.Restaurant'>

3 >>> print(lieu.restaurant.menu) 4 Des crêpes !

13.3.3. Les modèles proxy

Dernière technique d’héritage avec Django, et probablement la plus complexe, il s’agit de modèles proxy (en français, des modèles « passerelles »).

Le principe est trivial : un modèle proxy hérite de tous les attributs et méthodes du modèle parent, mais aucune table ne sera créée dans la base de données pour le modèle fils. En effet, le modèle fils sera en quelque sorte une passerelle vers le modèle parent (tout objet créé avec le modèle parent sera accessible depuis le modèle fils, et vice-versa).

Quel intérêt ? À première vue, il n’y en a pas, mais pour quelque raison de structure du code, d’organisation, etc., nous pouvons ajouter des méthodes dans le modèle proxy, ou modifier des attributs de la sous-classeMeta sans que le modèle d’origine ne soit altéré, et continuer à utiliser

les mêmes données.

Petit exemple de modèle proxy qui hérite du modèleRestaurant que nous avons défini tout à l’heure (notons qu’il est possible d’hériter d’un modèle qui hérite lui-même d’un autre !) :

1 class RestoProxy(Restaurant):

2 class Meta:

3 proxy = True # Nous spécifions qu'il s'agit d'un proxy 4 ordering = ["nom"] # Nous changeons le tri par défaut, tous

les QuerySet seront triés selon le nom de chaque objet 56 def crepes(self):

7 if u"crêpe" in self.menu: #Il y a des crêpes dans le menu

8 return True

9 return False

Depuis ce modèle, il est donc possible d’accéder aux données enregistrées du modèle parent, tout en bénéficiant des méthodes et attributs supplémentaires :

1 >>> from blog.models import RestoProxy 2 >>> print RestoProxy.objects.all() 3 [<RestoProxy: La crêperie bretonne>]

4 >>> resto = RestoProxy.objects.all()[0]

5 >>> print resto.adresse

6 42 Rue de la crêpe 35000 Rennes 7 >>> print resto.crepes()

8 True