Comment interroger des objets basés sur des classes abstraites dans Django?
disons que j'ai une classe de base abstraite qui ressemble à ceci:
class StellarObject(BaseModel):
title = models.CharField(max_length=255)
description = models.TextField()
slug = models.SlugField(blank=True, null=True)
class Meta:
abstract = True
maintenant, disons que j'ai deux classes de base de données qui héritent de StellarObject
class Planet(StellarObject):
type = models.CharField(max_length=50)
size = models.IntegerField(max_length=10)
class Star(StellarObject):
mass = models.IntegerField(max_length=10)
jusqu'ici, tout va bien. Si je veux obtenir des Planètes ou des Étoiles, je ne fais ceci:
Thing.objects.all() #or
Thing.objects.filter() #or count(), etc...
mais que faire si je veux avoir tous les StellarObjects? Si je fais:
StellarObject.objects.all()
il retourne bien sûr une erreur, parce qu'une classe abstraite n'est pas un objet de base de données réel, et donc ne peut pas être interrogé. Tout ce que j'ai lu me dit que j'ai besoin de faire deux requêtes, une sur les planètes et les étoiles, et les fusionner ensuite. Cela semble horriblement inefficace. Est-ce la seule?
7 réponses
à la racine, cela fait partie de l'inadéquation entre les objets et les bases de données relationnelles. L'ORM fait du bon travail en faisant abstraction des différences, mais parfois on se heurte à elles de toute façon.
fondamentalement, vous devez choisir entre l'héritage abstrait, dans lequel cas il n'y a pas de relation de base de données entre les deux classes, ou l'héritage multi-tables, qui maintient la relation de base de données à un coût d'efficacité (une jointure de base de données supplémentaire) pour chaque requête.
vous ne pouvez pas interroger les classes de base abstraites. Pour l'héritage multi-tables vous pouvez utiliser django-model-utils
et InheritanceManager
, qui étend la norme QuerySet
select_subclasses()
méthode, qui ne fait que vous avez besoin: à gauche-joint tous les hérité de tables et renvoie un type d'instance pour chaque ligne.
N'utilisez pas de classe de base abstraite si vous avez besoin d'interroger la base. Utilisez plutôt une classe de base en béton.
Ceci est un exemple de polymorphisme dans vos modèles (polymorphe-plusieurs formes d'un).
Option 1-S'il n'y a qu'un seul endroit où vous vous occupez de ceci:
pour le bien d'un petit peu de si-sinon code à un ou deux endroits, il suffit de traiter manuellement - il sera probablement beaucoup plus rapide et plus clair en termes de dev / maintenance (i.e. peut-être en vaut la peine à moins que ces requêtes sont sérieusement marteler votre base de données - c'est votre appel de jugement et dépend de circonstance.)
Option 2-Si vous le faites assez souvent, ou si vous exigez vraiment de l'élégance dans la syntaxe de votre requête:
heureusement il y a une bibliothèque pour traiter le polymorphisme à django, django-polymorphe - ces docs vous montreront comment faire cela précisément. C'est probablement la "bonne réponse" pour interroger directement comme vous l'avez expliqué, surtout si vous voulez faire le modèle de l'héritage dans beaucoup d'endroits.
Option 3 - Si vous voulez une à mi-chemin maison:
ce type de a les inconvénients des deux ci-dessus, mais je l'ai utilisé avec succès dans le passé pour faire automatiquement toutes les fermeture éclair ensemble à partir de plusieurs ensembles de requête, tout en conservant les avantages d'avoir un objet de requête ensemble contenant les deux types de modèles.
découvrez django-querysetsequence qui gère la fusion de plusieurs requête définit ensemble.
ce n'est pas aussi bien supporté ou aussi stable que django-polymorphic
, mais mérite une mention néanmoins.
Dans ce cas, je pense qu'il n'y a pas d'autre moyen.
Pour l'optimisation, vous pouvez éviter l'héritage de l'abstrait StellarObject
et l'utiliser comme table séparée connectée via FK à Star
et Planet
objets.
de cette façon, ils auraient tous les deux ie. star.stellar_info.description
.
une autre façon serait d'ajouter un modèle supplémentaire pour traiter l'information et utiliser StellarObject comme through
dans many2many relation.
j'envisagerais de m'éloigner d'un schéma d'héritage abstrait ou d'un schéma de base concret si vous cherchez à lier des comportements de sous-classe distincts aux objets basés sur leur classe d'enfant respective.
lorsque vous interrogez via la classe parent -- ce que vous avez l'air de vouloir faire -- Django traite les ojects résultants comme des objets de la classe parent, donc accéder aux méthodes au niveau de la classe enfant nécessite de re-mouler les objets dans leur classe enfant 'correcte' à la volée donc ils peuvent voir ces méthodes... à ce moment - là, une série d'énoncés de la fi suspendus à une méthode au niveau de la classe des parents serait sans doute une approche plus propre.
si le comportement de sous-classe décrit ci-dessus n'est pas un problème, vous pouvez envisager un gestionnaire personnalisé attaché à une classe de base abstraite coudre les modèles ensemble via le SQL brut.
si vous êtes principalement intéressé par l'assignation d'un ensemble discret de champs de données identiques à un tas d'objets, je relate le long d'une clé étrangère, comme bx2 suggérer.
cela semble horriblement inefficace. Est-ce la seule?
autant que je sache, c'est le seul moyen avec L'ORM de Django. Tel que mis en œuvre actuellement, les classes abstraites sont un mécanisme commode pour faire abstraction des attributs communs des classes aux super classes. L'ORM ne fournit pas une abstraction similaire pour la interrogation.
Vous feriez mieux d'utiliser un autre mécanisme pour implémenter la hiérarchie dans la base de données. Une façon de le faire serait d'utiliser un une table simple et des lignes "tag" en utilisant le type. Ou vous pouvez implémenter une clé étrangère générique pour un autre modèle qui possède des propriétés (ce dernier ne sonne pas bien même pour moi).