Login

Django Model Inheritance

Author:
srid
Posted:
January 1, 2008
Language:
Python
Version:
.96
Score:
4 (after 6 ratings)

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

  1. Template tag - list punctuation for a list of items by shapiromatron 11 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 6 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 7 months ago
  5. Help text hyperlinks by sa2812 1 year, 8 months ago

Comments

Please login first before commenting.