2010-11-10 8 views
4

Disons que je suivre des cours ORM (champs supprimé pour simplifier):héritage Django ORM avec le champ ManyToMany

class Animal(models.Model): 
    say = "?" 

    def say_something(self): 
     return self.say 

class Cat(Animal): 
    self.say = "I'm a cat: miaow" 

class Dog(Animal): 
    self.say = "I'm a dog: wuff" 

class Animals(models.Model): 
    my_zoo = models.ManyToManyField("Animal") 

Quand j'ajouter quelques animaux à mon zoo:

cat = Cat() 
cat.save() 
dog = Dog() 
dog.save() 

animals.my_zoo.add(cat) 
animals.my_zoo.add(dog) 

for animal in animals.my_zoo.all(): 
    print animal.say_something() 

... I s'attendre résultat suivant:

Je suis un chat: miaou, je suis un chien: wuff

mais au contraire, tous les J'ai les instances de l'objet Animal général, incapable de dire autre chose que "?".

Comment obtenir l'héritage vrai objet et le polymorphisme ultérieur lorsque l'objet est récupéré à partir de DB?

+0

Votre code est trop simplifié pour être lu. –

Répondre

4

L'héritage de modèle dans django n'ajoute aucune information de type à la classe de base. Il n'est donc pas vraiment possible de descendre des objets d'Animal() vers leurs formes appropriées. L'héritage n'est utilisé que pour mapper des champs sur des modèles hérités vers des modèles parents. Donc, si Animal a le champ name, le même champ existera sur Cat et lorsque vous sauvegarderez Cat, le animal sera mis à jour.

héritage fonctionne en ajoutant une relation OneToOne:

class Animal(Model): 
    name = CharField() 

class Cat(Model): 
    animal_ptr = OneToOneField(Animal) 

Cat(name='Murky').save() 

print repr(list(Animals.objects.all())) 

: [Animal(name='Murky')] 

Techniquement dans votre situation, il est même possible pour les animaux() être à la fois Chien() et Cat() en même temps:

animal = Animal() 
animal.save() 

Cat(animal_ptr=animal).save() 
Dog(animal_ptr=animal).save() 

La façon de résoudre votre problème serait d'ajouter un champ subtype ou similaire à votre objet Animal() et mettre en œuvre la fonction coulée en descente:

class Animal(Model): 
    subtype = CharField() 

    def downcast(self): 
     if self.subtype == 'cat': 
      return self.cat 
      # The self.cat is a automatic reverse reference created 
      # from OneToOne field with the name of the model 

for animal in Animal.objects.all().select_related('dog', 'cat', ...)]: 
    animal.downcast().say_something() 

Quelques lectures utiles sur débordement de pile avec des sujets similaires: Generic many-to-many relationships hotte. How to do Inheritance Modeling in Relational Databases?

+1

+1 pour une bonne explication; Même si je trouve qu'ajouter un 'nom_espèce' est un hack, il n'y a pas de meilleur moyen (en fait, je l'ai utilisé moi-même). Aussi, si vous me demandez, django devrait faire quelque chose comme ça en interne, et ne pas nous faire frapper la tête contre le clavier. –

+0

Merci pour la réponse. Je comprends comment cela fonctionne, mais j'espérais qu'il y aurait de la magie qui ferait le sale boulot pour moi. – edkirin

0

Vous pouvez accéder aux descendants via un attribute on the parent class. Le nom de l'attribut est la version en minuscule du nom du modèle:

class Animal(models.Model): 
    say = "?" 

    def say_something(self): 
     for animal in ('cat', 'dog'): 
      try: 
       return getattr(self, animal).say 
      except: 
       pass 
      return self.say 
Questions connexes