开发者

How to go from a Model base to derived class in Django?

Assuming a simple set of inherited Model classes, like this:

class BaseObject(models.Model): 
    some_field = models.SomeField(...)

class AwesomeObject(BaseObject): 
    awesome_field = models.AwesomeField(...)

class ExcellentObject(BaseObject): 
    excellent_field = models.ExcellentField(...)

and a query that looks like this:

found_objects = BaseObject.objects.filter(some_field='bogus')

What's the best way to take each found object and turn it back into it's derived class? The code I'm using now is like this:

for found in found_objects:
    if hasattr(found, 'awesomeobject'): 
        ProcessAwesome(found.awesomeobject)
    elif hasattr(found, 'excellentobject'): 
        ProcessExcellent(found.excellentobj开发者_高级运维ect): 

But, it feels like this is an abuse of "hasattr". Is there a better way to do this without creating an explicit "type" field on the base class?


For this specific problem, there is django-polymorphic. It works by using the content type framework in Django to store the model ID which the derived table points to. When you evaluate the queryset, it will upcast all models their specific type.

You'll get:

>>> BaseProject.objects.all()
[ <AwesomeObject>, <ExcellentObject>, <BaseObject>, <AwesomeObject> ]


That's the best way that I know of. Unfortunately, inheritance is a little clunky in this regard. Multiple table inheritance is basically just a one-to-one relationship between the parent model and the extra fields the child adds, which is why that hasattr trick works. You can think of each of those as a OneToOneField attribute on your parent model. When you think of it that way, Django has no way of knowing which child to return or even if to return a child, so you have to handle that logic yourself:

I tend to create a method on the parent such as get_child, which simply cycles through the attributes and returns the one that pops:

class BaseObject(models.Model):
    some_field = models.SomeField(...)

    def get_child(self):
        if hasattr(self, 'awesomeobject'): 
            return ProcessAwesome(found.awesomeobject)
        elif hasattr(self, 'excellentobject'): 
            return ProcessExcellent(found.excellentobject):
        else:
            return None

At least then, you can just call found.get_child(), and maybe forget about the hackery that gets you there.


Going from a base class to a derived class is generally a sign of bad design in a program. The method you propose, using hasattr, can be a serious problem. I'll show you:

# defined in some open source library
class MyObject(object):
    def what_is_derived(self):
        if hasattr(self, 'derived1'):
            return 'derived1'
        elif hasattr(self, 'derived2'):
            return 'derived2'
        else:
            return 'base'

Let's pretend that classes Derived1 and Derived2 are defined in that same library. Now, you want to use the features of MyObject, so you derive from it in your own code.

# defined in your own code
class MyBetterObject(MyObject):
    pass

better_object = MyBetterObject()
better_object.what_is_derived() # prints 'base'

The whole point of polymorphism is that you can have many derived classes without the base class having to change. By making the base class aware of all of it's derived classes, you severely reduce the usefulness of such a class. You can't create a derived class without changing the base class.

Either you want to work with a derived class, or you don't care what the specific class is and all you need are the properties/methods of the base class. It is the same in all OOP languages. There are facilities for finding out what the derived class is, but usually it's a bad idea.

From a django models perspective, I usually use inheritance in such a way:

class Address(models.Model):
    # fields...

class Person(Address):
    # fields...

class Business(Address):
    # fields...

Address.objects.all() # find all addresses for whatever reason
Person.objects.all() # im only interested in people
Business.objects.all() # need to work with businesses

# need to show all addresses in a postcode, and what type of address they are?
businesses = Business.objects.filter(postcode='90210')
people = Person.objects.filter(postcode='90210')
# use the address properties on both

Deeply nested inheritance chains with django models are awkward. They are also pretty unnecessary in most cases. Instead of polluting your base class with hasattr checks, define a helper method which is capable of querying the required derived classes if such a thing is called for. Just don't define it on the Base class.


I use introspection ;

class Base(models.Model):
[ we have some unique 'key' attribute ]
class_name = models.CharField(..., editable=False)

def get_base(self):
    if self.__class__ == Base:
        return self
    # if we are not an instance of Base we 'go up'
    return Base.objects.get(key=self.key)

def get_specific(self):
    if self.__class__ != Base:
        return self
    # if we are an instance of Base we find the specific class
    class_type = getattr(sys.modules["project.app.models"],
        self.class_name)
    return class_type.objects.get(key=self.key)

You need some factory to create the specific classes so you are sure to correctly save str(self.class) in class_name


You can also use InheritanceQuerySet from django-model-utils in case you want to explicitly state which queries to affect, like this:

from model_utils.managers import InheritanceQuerySet

class UserManager([...]):

    def get_queryset(self):
        return InheritanceQuerySet(self.model).select_subclasses()

(code from https://stackoverflow.com/a/25108201)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜