This snippet shows an alternative and interesting way to do Model Inheritance in Django. For description of the code, follow the inline comments.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | # Django ORM does not support inheritance. Inheritance is a
# good thing. Often I want some of the database models to
# share some common fields and the same manager. For
# example consider the case of adding the delete proxy.
class RetainDeletesMixin(object):
class NonDeleted(models.Manager):
def get_query_set(self):
return super(NonDeleted, self).get_query_set().filter(
delete_flag=0)
delete_flag = models.IntegerField(null=True, blank=True,
default=0)
objects = NonDeleted()
def delete(self):
self.delete_flag = 1
self.save()
class Item(RetainDeletesMixin, models.Model):
...
# Here we are extending the model Item by adding a mixin
# that overrides the delete method - so that when we do
# item.delete() the model is not actually deleted but
# merely flagged (delete_flag=1) so. This means the
# get_query_set method should also be told not to return
# the flagged models. In Python speak,
# >>> items = Item.objects.all()
# >>> item = items[0]
# >>> len(items)
# 1
# >>> item.delete_flag
# 0
# >>> item.delete()
# >>> item.delete_flag
# 1
# >>> len(Item.objects.all())
# 0
# But this will not work. Django does not consider
# delete_flag, which is inherited from RetainDeletesMixin,
# as a database column at all. So I came up with the
# following metaclass hack which enables you to design
# the django models based on the inheritance pattern like
# the above,
class RetainDeletesMixin(object):
class NonDeleted(models.Manager):
def get_query_set(self):
# Ideally, we should be using `super` here but we don't
because
# the variable `NonDeleted` will not be accessible once we
# 'copy' this class in the metaclass.
return models.Manager.get_query_set(self).filter(
delete_flag=0)
delete_flag = models.IntegerField(null=True, blank=True,
default=0)
objects = NonDeleted()
def delete(self):
self.delete_flag = 1
self.save()
def django_extends(base):
class ProxyMetaClass(models.Model.__metaclass__):
def __new__(cls, name, bases, attrs):
# The following attributes must be moved *prior* to
deferring
# execution to the models.Model's metaclass, because they
# will be manipulated by __new__
# - models.fields.Field instances
# - objects (ModelManager)
for key in dir(base):
if not key.startswith('_'):
obj = getattr(base, key)
if isinstance(obj, models.fields.Field) or \
key == 'objects':
attrs[key] = obj
delattr(base, key)
# Delete objects that have attribute
'contribute_to_class'
# for otherwise that will break in
# base.py:Model.add_to_class
# Eg: inner classes inherited from models.Manager
elif hasattr(obj, 'contribute_to_class'):
delattr(base, key)
return super(ProxyMetaClass,
cls).__new__(cls, name,
tuple([base]+list(bases)),
attrs)
frame = sys._getframe(1)
frame.f_locals['__metaclass__'] = ProxyMetaClass
class Item(models.Model):
django_extends(RetainDeletesMixin)
...
# What this basically does is - copy the database fields
# and objects from the base class (RetainDeletesMixin) to
# the derived class (Item) so that Django will recognize it
# upon processing in ModelBase. It also makes
# RetainDeletesMixin a base class of Item. I have not
# tested this code extensively, but it works for the models
# that I have written in our application.
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 11 months ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months, 1 week ago
- Serializer factory with Django Rest Framework by julio 1 year, 6 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 7 months ago
Comments
Please login first before commenting.