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.
Related
I am trying to use an abstract base class model in Django to avoid writing some duplicate code and encountering some unexpected behavior.
Here is simplified version of my abstract base class:
class AbstractDocument(models.Model):
notes = models.CharField(max_length=255)
document = models.FileField(upload_to=document_file_path)
def document_file_path(instance, filename):
pass
class Meta:
abstract = True
I need to define the method document_file_path or the code generates error. I want to define different behavior in the subclasses for the document_file_path. Below is an example:
class BookDocument(AbstractDocument):
book = models.ForeignKey(Book, on_delete=models.CASCADE)
def document_file_path(instance, filename):
return f'books/{filename}'
It does not appear that the child method is overriding the parent method because I get an error that document_file_path returned NoneType when I run the code above. I tried making the method in AbstractDocument return an actual path, but the child method doesn't override the parent in that scenario either.
Is there some reason why what I'm trying to do is not possible? Is there something I'm missing in the implementation? Is there another or better way to accomplish this?
Field is a class attribute, not instance, but you're trying to link it to an instance's method which is not really possible/correct.
So try marking your method as a #staticmethod or switch to a solution similar to proposed in the second answer here: make a "simple" function calling instance's (which is the first argument in upload_to) method, which can be inherited and overridden.
My goal is to use the same function from multiple classes in Python.
I've seen discussion about mixins and inheritance etc but they all seem to come with caveats and cautions about doing things just right.
So I wondered if I could just call another plain old function that lives outsides the classes. It seems to work, but maybe I'm failing to understand something important?
So my question is - is this a valid approach to sharing a function between Python classes?
def clean_reference_url(self):
if not self.cleaned_data['reference_url']:
return ''
try:
if 'illegaltext' in self.cleaned_data['reference_url']:
raise Exception
except:
raise forms.ValidationError('You cannot put illegaltext in this field.')
return self.cleaned_data['reference_url']
class ContactForm(forms.ModelForm):
def clean_reference_url(self):
return clean_reference_url(self)
class TripForm(forms.ModelForm):
def clean_reference_url(self):
return clean_reference_url(self)
It's valid, but it's unnecessary to have the extra layer of wrapping. The mix-in approach is the simplest, but yes, it has some caveats (largely related to metaclasses), so if you want to avoid that, you can still set a method in multiple classes by just setting during the definition of each class. Keep the function definition the same, and change the classes to:
class ContactForm(forms.ModelForm):
clean_reference_url = clean_reference_url
class TripForm(forms.ModelForm):
clean_reference_url = clean_reference_url
Again, a mixin is even cleaner, e.g.:
class CleanableUrl: # Change name as appropriate
def clean_reference_url(self):
# ...
class ContactForm(CleanableUrl, forms.ModelForm):
# No need to talk about clean_reference_url at all
class TripForm(CleanableUrl, forms.ModelForm):
# No need to talk about clean_reference_url at all
and it's usually the most Pythonic approach, assuming it works for your scenario (no conflicting metaclasses on the base types).
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)
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...
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.