Let's say I have a parent class (ThingsThatMigrate) and two children (Coconut and Swallow). Now let's say I have a ThingsThatMigrate object. How can I determine if it is in fact a Coconut or a Swallow? Once having done so, how can I get to the Coconut or Swallow object?
Django doesn't offer such model polymorphism out of the box.The easiest way to do what you are trying to achieve is to store the content type of a new object in it. There's a simple generic app called django-polymorphic-models which offers you this functionality - and - additionally a downcast-method that will return the child object!
Concrete or abstract inheritance? If concrete:
>>> things = ThingsThatMigrate.objects.all().select_related('coconut', 'swallow')
>>> for thing in things:
... thing = thing.coconut or thing.swallow or thing
... print thing
This can be automated using django-model-utils InheritanceManager (then you don't need to worry about select_related or manually listing all possible subclasses). Maintained by another Django core developer.
It's not particularly pretty or efficient, but the best way I can think of implementing this without storing the subclass meta data in the DB (like django-polymorphic-models does) would be a child() method in your ThingsThatMigrate model class:
from django.core.exceptions import ObjectDoesNotExist
def child(self):
for subclass in self.__class__.__subclasses__():
try:
return getattr(self, subclass.__name__.lower())
except (AttributeError, ObjectDoesNotExist):
continue
On a Django CMS I work with (Merengue http://www.merengueproject.org/), we store the "classname" attribute that stores what is the real class of the object.
In order to get the real instance we used the following method:
def get_real_instance(self):
""" get object child instance """
def get_subclasses(cls):
subclasses = cls.__subclasses__()
result = []
for subclass in subclasses:
if not subclass._meta.abstract:
result.append(subclass)
else:
result += get_subclasses(subclass)
return result
if hasattr(self, '_real_instance'): # try looking in our cache
return self._real_instance
subclasses = get_subclasses(self.__class__)
if not subclasses: # already real_instance
self._real_instance = getattr(self, self.class_name, self)
return self._real_instance
else:
subclasses_names = [cls.__name__.lower() for cls in subclasses]
for subcls_name in subclasses_names:
if hasattr(self, subcls_name):
return getattr(self, subcls_name, self).get_real_instance()
return self
The important thing of this function is that it keeps in mind if the class is abstract or not, wich change the logic a little bit.
As DrMeer suggested, I highly recommend django-model-utils (hosted on bitbucket now though). I'm not sure it's convincing enough though.
Let a code example prove it:
>>> ThingsThatMigrate.objects.all().select_subclasses()
Coconut, Coconut, Swallow, Coconut, ThingsThatMigrate
It takes one line, objects = InheritanceManager() in your parent model.
From the docs:
If you have a Place that is also a Restaurant, you can get from the Place object to the Restaurant object by using the lower-case version of the model name...
Related
I am trying to get my head around writing Custom managers. I found the online documentation a little sparse. Toying around myself with code, I discovered the following patterns:
Given the following model...
class QuestionQuerySet(models.QuerySet):
def QS_first (self):
return self.first()
class QuestionManager(models.Manager):
def get_queryset(self):
return QuestionQuerySet(self.model, using=self._db)
def MN_first(self):
return self.get_queryset().first()
class Question(models.Model):
front = models.ForeignKey('Sentence', related_name='question_fronts')
....
I then get the following results...
Grammar.objects.filter(stage=1).question_set.MN_first()
<Question: [<Sentence: eve gideceğim>, <Sentence: I will go home>]>
Grammar.objects.filter(stage=1).question_set.QS_first()
AttributeError: 'RelatedManager' object has no attribute 'QS_first'
But
Question.objects.filter(grammar=1).QS_first()
<Question: [<Sentence: eve gideceğim>, <Sentence: I will go home>]>
Question.objects.filter(grammar=1).MN_first()
AttributeError: 'QuestionQuerySet' object has no attribute 'MN_first'
Why is it that the Manager methods are called when accessing the object through a DB relationship, but the Queryset methods are called when accessing the object directly? If I want the one method universally accessible (DRY), what would be the best solution?
Have a look at the QuerySet.as_manager() method. It allows you to create a manager from a queryset, so that you don't need to duplicate code in a custom manager and queryset,
I have a custom model manager and a custom queryset defined specifically for related obj which means I have defined Meta.base_manager_name in the model.
I would like to use a all() manager method which fetches related obj on a OneToOneFeild.
Now I know this does not make sense since OneToOneFeild will always return one obj there is no need for a all() method. I am working on django-oscar project and am extending its "Partner" model. It originally has a field "users" with ManyToManyField and now changed to a OneToOneFeild.
The users field is called in code several times using relation user.partners.all(). I don't want to extend/modify all these places (am I being lazy here?) since I want to keep the code as upgrade friendly as possible and so instead I wanted to have all() model manager defined which will work. Not sure if it is a good idea?
the all() method takes user arg to return queryset of the user instance
class PartnerQuerySet(models.QuerySet):
def all(self, user):
return self.filter(user=user)
class PartnerManager(models.Manager):
def get_queryset(self):
return PartnerQuerySet(self.model, using=self._db)
def all(self, user):
return self.get_queryset().all(users)
class Partner(models.Model):
objects = PartnerManager()
class Meta:
base_manager_name = 'objects'
The problem is when it is used with related obj it asks for user arg which makes sense but since I am using it with a related obj I wanted to use the related obj as arg so,
user.partner.all() - should use user as arg and fetch the results
user.partner.all(user) - and I should not have to do the below
2 related questions:
1) Does this make sense - should I be doing this?
2) how I can achieve user.partner.all() without adding user in arg
PS: I know i can work with middleware to get_current_user but this function is not reliable as per some of the responses on a different question on SO.
I don't think what you are trying to do will work. Your new situation with a OneToOneField gives you the partner instance.
>>>> user.partner
<Partner xxx>
While in the old situation with the ManyToManyField, the PartnerQuerySet would've been returned.
>>>> user.partner
<PartnerQuerySet []>
A solution would be to create a custom OneToOneField, but this would most probably violate the "simple is better than complex" rule and in the end may even be more work than changing all existing .all()'s.
I have something like this:
class Base(Model):
...
def downcast(self):
try:
return self.childa
except:
pass
try:
return self.childb
except:
pass
return self
class ChildA(Base):
....
class ChildB(Base):
....
class Foo(Model):
child = ForeignKey(Base)
Whenever I have a Foo object, the child foreignkey is always an instance of Base - my understanding is that's how Django works. For now I have added a downcast() method to Base (see above). I don't mind hardcoding the possible derived types.
What I would like is to somehow centralize that downcast automatically in Foo. I added this multi-table inheritance to existing code and I keep finding instances where the code really needs it downcast -- so I have to then manually downcast it locally in the code.
I was using the django-polymorphic package, but it is giving me some side effects I don't know how/nor want to deal with (like I can't delete rows - got some error about opts.pk being None deep in queryset code.)
So I've wondered -- would putting something in __init__() (after calling the base class init) be ok? Are there side effects I'm not thinking of? This seems like it could be a problem when creating new instances from scratch.
def __init__(*args, **kwargs):
super(Base, self).__init__(*arg, **kwargs)
self.child = self.child.downcast()
Should I just rename child?
class Foo(Model):
child_poly = ForeignKey(Base) # was child
#property
def child(self):
return self.child_poly.downcast()
This could be a problem when creating Foo() from scratch. I can't say Foo(child=c).
Is there a better approach? Not looking for a generic polymorphic solution/mixin -- not after trying to debug django and finding that removing django-polymorphic fixed the deletion issue.
In the end, I went back to django-polymorphic and haven't had the issue I was having before again.
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.excellentobject):
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)
I have tried multiple aproaches, but as I cannot use self in the class body, self.__class__.__name__ is not available. Would I need to override the save method to do this? Thanks for your help.
Your question is oddly phrased, so I'm going to come at it sideways.
Assume that you have defined a model Foo as follows:
from django.db import models
class Foo( models.Model ):
foo = models.IntegerField()
bar = models.IntegerField()
def klass( self ):
return self.__class__.__name__
Supposing you start a Django shell (python manage.py shell), you can do the following:
>>> from foo.models import Foo
>>> foo = Foo()
>>> print foo.klass()
Foo
This demonstrates that you can certainly use self.__class__.__name__ in the body of any method for model Foo. Thus you must have some other context where you need to dynamically determine the actual class name for your model, but not from an instance of your model.
If you've finished defining the model, then the following is legal:
>>> print Foo._meta.object_name
Foo
This mechanism would allow you to do introspection directly against the model, without having to create an instance of the model.
If this doesn't work for you, you must need this during the actual definition of the model. In that case, I would respectfully suggest that if you know you're defining the Foo model, you should just hardcode Foo wherever you need it. If you really need a dynamic way during the creation of your model to determine the name of the model ... could you describe the actual problem you're trying to solve, so that we can help you solve it?
This is more or less what I want:
class VFXContainer(models.Model):
classname=models.CharField(max_length=60,editable=False,blank=True)
parent=models.ForeignKey("self",blank=True,null=True)
def save(self, *args, **kwargs):
self.classname=self.__class__.__name__
super(VFXContainer, self).save(*args, **kwargs)
class Company(VFXContainer):
class Meta:
verbose_name_plural="companies"
class Project(VFXContainer):
pass
class CustomVFXContainer(VFXContainer):
pass
Now, what I dont know how to do, is I want to "override" the limit_choices_to option in the parent field on the child classes. What I want is CustomVFXContainer to be parented to any type of class, Project only to be parented by Company, and Company not to be parented at all. Im using this structure for the following reason. There is going to be a ton of fields that I want to be in all the subclasses, and I also have a separate Tasks models that link through a foreign key to the base VFXContainer Class (and thus is attachable to any of the child classes). Hope this makes it more clear on what Im trying to achieve, thanks for your help.