Le blog: Web, Python, Django, Javascript ...

Héritage de modèle avec Django

Python est un langage objet. C'est même un des points forts du langage, grâce à la souplesse du Duck-Typing, littéralement le typage canard, qui n'impose pas de contrat prédéfini à un objet. En Django, la structure des objets est défini par le modèle et permet de les stocker en base de données. Ceci a des implications lorsqu'on hérite un modèle d'un autre. Voici donc quelques notes collectées ci et là à propos de l'héritage de modèle en Django.

L'héritage traditionnel

Pour hériter un modèle d'un autre, il suffit

class Musicien(models.Model):
    nom = models.CharField(max_length=100)
    style = models.CharField(max_length=100)

class Guitariste(Musicien):
    jazzman = models.BooleanField()

Lors de la génération du modèle, django va créer 2 tables: une pour les musiciens et une pour les guitaristes, chacune gérant les données définies dans la classe du modèle et réliées entre elles par une foreignkey.

Au niveau Python, tout ceci est transparent:

bireli = Guitariste()
bireli.nom = u'Bireli Lagrene'
bireli.style = u'jazz manouche'
bireli.jazzman = True

L'héritage "abstract"

En programmation objet, on a la notion de classe abstraite, c'est à dire qui ne permet pas d'instancier d'objets. Ce type de classe permet de factoriser des comportements.

En django, cette notion de classe abstraite existe. Elle permet de ne pas créer de table pour les classes de base mais de réutiliser les champs qui y sont définis dans les classes dérivées.

class Musicien(models.Model):
    ...
    class Meta:
        abstract = True

Dans notre exemple, on n'a plus de table créée pour le modèle Musicien. On a simplement une table Guitariste qui comporte 4 colonnes id (générée automatiquement par Django), nom, style et jazzman.

Il n'est toutefois plus possible d'utiliser la classe Musicien pour des requêtes dans la base. Musicien.objects.all() lèvera une exception. L'ORM ne gère pas les classes abstract.

L'héritage "proxy"

Une autre problématique peut-être que l'on souhaite modifier le comportement d'un modèle mais sans toucher aux données et donc sans forcer la génération d'une table pour le modèle dérivé. L'héritage "proxy" permet cela.

class Guitariste(Musicien):
     def jazzman(self): return (self.style.find('jazz')>=0)
     class Meta:
         proxy = True

Dans notre exemple, l'information jazzman n'est plus stockée en base mais déduite de la valeur du champ style de la classe Musicien. Il n'est donc plus utile de créer une table dédiée pour le modèle Guitariste puisque sur le plan des données il est identique au modèle Musicien.

Guitariste.objects.all() nous retournera donc des guitaristes pour lesquels il sera possible de savoir s'il est jazzman tandis que Musicien.objects.all() retournera simplement des musiciens pour lesquels cela ne sera pas possible.

L'héritage polymorphique

Revenons à l'héritage "traditionnel" et ajoutons un nouveau modèle.

class Violoniste(Musicien):
    jazzman = models.BooleanField()
    classique = models.BooleanField()

Créons ensuite plusieurs instances par exemple un Guitariste(nom='django reinhardt', ...) et un Violoniste(nom='stephane grapelli', ...)

On peut accéder à la liste des musiciens avec Musicien.objects.all(). Cela nous retournera des musiciens. [Musicien(), Musicien()]

Il serait préférable d'obtenir le type réel des objets [Guitariste(), Violoniste()].

Ceci est possible grâce à l'application django-ploymorphic qui s'intègre facilement. Il suffit d'ajouter polymorphic dans les INSTALLED_APPS et d'hériter le modèle de base de PolymorphicModel

class Musicien(PolymorphicModel):
   ....

Cette application peut se révéler bien pratique et rend les résultats de l'ORM plus naturels, du moins à mon sens.

Pour finir un peu de musique

Voilà pour ces quelques notes sur l'héritage en Django. Je vous propose de finir par un peu de musique avec Biréli Lagrène, un guitariste reconnu comme un des héritiers de Django Reinhardt.


Commentaires rss
Posté par Juju le jeudi 12 janvier 2012 à 21:54
Comment `Guitariste.objects.all()` pourrait retourner tous les guitaristes vu qu'il n'y a aucun filtre dans ton proxy exemple ?
Je me trompe ?

Posté par Luc le jeudi 12 janvier 2012 à 22:10
Je suis pas sur d'avoir bien compris. Tu fais référence au cas du proxy, c'est ca? Ici le modèle Guitariste ajoute simplement des méthodes au modèle Musicien. D'un point de vue base de données, Musicien et Guitariste sont la même chose.

Posté par charlie le mercredi 20 août 2014 à 23:53
bireli.style = u'jazz manouche' est trop restricitf à mon gout ^^

Nom: Email: URL: Commentaire: Si vous saisissez quelque chose dans ce champ, votre commentaire sera considéré comme étant indésirable: Captcha: captcha

Luc JEAN

09.65.20.15.70

ljean@apidev.fr

Luc JEAN

Suivez les nouveautés

Wikio RSS  RSS Blog Python Django selenium Rss commentaires
Paperblog : Les meilleurs actualités issues des blogs Follow luc_apidev on Twitter