How to move from model base to derived class in Django?

Assuming a simple set of inherited model classes, for example:

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 is the best way to take each found object and return it back to the derived class? The code I'm using now is as follows:

 for found in found_objects: if hasattr(found, 'awesomeobject'): ProcessAwesome(found.awesomeobject) elif hasattr(found, 'excellentobject'): ProcessExcellent(found.excellentobject): 

But this seems to be an abuse of "hasattr". Is there a better way to do this without creating an explicit type field in the base class?

+7
source share
5 answers

This is the best way that I know of. Unfortunately, inheritance is a bit awkward in this regard. Inheritance of several tables is, in fact, only a one-to-one relationship between the parent model and the additional fields that the child adds, so the hasattr trick hasattr . You can think of each of them as an attribute of OneToOneField in your parent model. When you think of it this way, Django has no way of knowing which child should return or even return the child, so you have to deal with this logic yourself:

I try to create a method for the parent, for example get_child , which simply cycles through the attributes and returns the one that appears:

 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 the very least, you can just call found.get_child() and you may have forgotten about the hacking that takes you there.

+2
source

There is django-polymorphic for this particular problem. It works using the content type structure in Django to store the model identifier that the view points to. When you evaluate a set of queries, it will convert all models to their specific type.

You'll get:

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

The transition from a base class to a derived class is usually a sign of poor design in the program. The method you suggest using hasattr can be a serious problem. I will 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' 

Suppose the classes Derived1 and Derived2 defined in the same library. Now you want to use the MyObject functions, so you get 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 changing the base class. By making the base class aware of all its derived classes, you will seriously reduce the usefulness of such a class. You cannot create a derived class without changing the base class.

Either you want to work with a derived class, or you don’t care what a particular class is, and all you need is the properties / methods of the base class. This is the same in all OOP languages. It is possible to find out what a derived class is, but this is usually a bad idea.

From the point of view of django models, I usually use inheritance like this:

 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 inconvenient. In most cases, they are also not needed. Instead of polluting your base class with hasattr checks, define a helper method that can query the required derived classes if such a thing is required. Just don't define it in the Base class.

+3
source

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) 

To create specific classes, you will need a factory, so you will need to correctly save str (self. Class ) in class_name

+2
source

You can also use the InheritanceQuerySet from django-model-utils if you want to explicitly specify which queries to affect, for example:

 from model_utils.managers import InheritanceQuerySet class UserManager([...]): def get_queryset(self): return InheritanceQuerySet(self.model).select_subclasses() 

(code https://stackoverflow.com/a/312947/ )

0
source

All Articles