Determining Django Model Instance Types after a Query on a Base-class

倖福魔咒の 提交于 2019-11-28 02:57:48

问题


Is there a way to determine what the 'real' class of a Django database object is, after it has been returned from a query for on a base class?

For instance, if I have these models...

class Animal(models.Model):
    name= models.CharField(max_length=128)

class Person(Animal):
    pants_size = models.IntegerField(null=True)

class Dog(Animal):
    panting_rate = models.IntegerField(null=True)

And create these instances...

Person(name='Dave').save()
Dog(name='Mr. Rufflesworth').save()

If I do a query like Animal.objects.all(), I end up with two Animal instances, not an instance of Person and an instance of Dog. Is there any way to determine which instance is of which type?


FYI: I already tried doing this...

isinstance(Animal.objects.get(name='Dave'),Person) # <-- Returns false!

But that doesn't seem to work.


回答1:


I had a similar problem in the past and eventually found a satisfactory solution thanks to this answer.

By implementing an abstract class that stores the real class and have it inherited by your parent class, once can cast each parent class instance to the actual type. (The abstract class used in that answer is now available in django-model-utils.)

For example, once you have the abstract class defined (or if you have django-model-utils), you can simply do:

class Animal(InheritanceCastModel):
    name= models.CharField(max_length=128)

class Person(Animal):
    pants_size = models.IntegerField(null=True)

class Dog(Animal):
    panting_rate = models.IntegerField(null=True)

Using it is trivial:

>>> from zoo.models import Animal, Person, Dog
>>> Animal(name='Malcolm').save()
>>> Person(name='Dave').save()
>>> Dog(name='Mr. Rufflesworth').save()
>>> for obj in Animal.objects.all():
...     print obj.name, type(obj.cast())
...
Malcolm <class 'zoo.models.Animal'>
Dave <class 'zoo.models.Person'>
Mr. Rufflesworth <class 'zoo.models.Dog'>



回答2:


We implemented our own cast() function that works quite well (Without ContentType's):

class Base(models.Model):
    """
    If your class needs the basics, like created date, modified date etc, then
    inherit from this Base class.
    """
    created = models.DateTimeField(_('Created'), auto_now_add=True)
    modified = models.DateTimeField(_('Modified'), auto_now=True)

    class Meta:
        abstract = True

    def __str__(self):
        return '%s [%s]' % (self.__class__.__name__, self.id)

    def get_class_name(self):
        return str(self.__class__.__name__).lower()

    def to_json(self, include_related=True):
        return {
            'id': self.id,
            'created': self.created.isoformat(),
            'modified': self.modified.isoformat(),
            'class_name': self.__class__.__name__
        }

    def cast(self):
        """
        This method is quite handy, it converts "self" into its correct child class. For example:

        .. code-block:: python

           class Fruit(models.Model):
               name = models.CharField()

           class Apple(Fruit):
               pass

           fruit = Fruit.objects.get(name='Granny Smith')
           apple = fruit.cast()

        :return self: A casted child class of self
        """
        for name in dir(self):
            try:
                attr = getattr(self, name)
                if isinstance(attr, self.__class__):
                    return attr
            except:
                pass
        return self



回答3:


Yes, this can be done -- you just need to query the automatic reverse OneToOneField relations. E.g.:

a = Animal.objects.select_related('person', 'dog')[0]
a = a.person or a.dog or a # whichever is not None
print a
print isinstance(a, Person)

The use of select_related here allows this to be done in a single query, rather than having to test for DoesNotExist exceptions when you access the subclass attributes/relations.

See also my answer here and the InheritanceManager in django-model-utils for a more elegant/long-term solution.

We're looking at ways of making this easier in Django's core.




回答4:


To solve this, consider using django-polymorphic. It supports automatic downcasting of inherited models, works with ForeignKeys/ManyToMany fields and integrates in the admin too.



来源:https://stackoverflow.com/questions/5225556/determining-django-model-instance-types-after-a-query-on-a-base-class

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!