Search code examples
pythondjangodjango-modelsdjango-migrations

Django abstract model with indices, constraints and permissions: attributes are not inherited by sub classes


I am using Django 3.2

I have the following models:

app1

class IsPinnable(models.Model):
    is_pinned = models.BooleanField(default=False)
    pin_after_expiry_day_count = models.DurationField(default=0)    

    class Meta:
        abstract = True
        indexes = [
            models.Index(fields=['is_pinned' ]),
        ]
    


class IsModeratable(models.Model):
    approved = models.BooleanField(default=False)
    target_count_trigger = models.PositiveSmallIntegerField()
    positive_pass_count = models.PositiveSmallIntegerField(default=0)
    negative_pass_count = models.PositiveSmallIntegerField(default=0)
 
    # Other fields and methods ...

    def save(self, *args, **kwargs):
        # TODO: Sanity check on pass_count and trigger sizes
        # ...
        super().save(*args, **kwargs)


    class Meta:
        abstract = True
        permissions = (
                       ("moderate_item", "can moderate item"),
                       ("reset_moderation", "can (re)moderate a moderated item"),
                      )
        indexes = [
            models.Index(fields=['approved' ]),
        ]    

    class MyComponent(IsPinnable, IsModeratable):
        # some fields and methods ...

        class Meta(IsPinnable.Meta, IsModeratable.Meta):
            abstract = True
            
            # other stuff ...

app2

from app1.models import MyComponent

class Foo(MyComponent):
    # some fields and methods ...
    class Meta(MyComponent.Meta):
        abstract = False
   

Now, I know that abstract model classes are not created in the database - so I was initially expecting Django to throw an exception when I attempted to makemigrations - to my surprise, I was able to makemigrations && migrate.

However, when I inspected the database (via psql), I found that although table app2_foo had all the fields described in it's parent class, the indixes were not being carried up from the parent classes as the the documentation would seem to suggest.

What am I missing?, and how do I get the indices, constrains and permissions defined in parent classes to propagate to sub classes?


Solution

  • This is just normal class inheritance behaviour, take this snippet for instance:

    class A:
        attr = [1]
    
    
    class B:
        attr = [2]
    
    
    class C(A, B):
        pass
    
    
    print(C.attr) # Outputs: [1]
    

    This is because A is before B in the Method Resolution Order. If suppose C defined attr = [3] then the output would have been [3].

    You can do something like this in the child class if you need to override those attributes:

    class MyComponent(IsPinnable, IsModeratable):
        # some fields and methods ...
        
        class Meta(IsPinnable.Meta, IsModeratable.Meta):
            abstract = True
            indexes = [...] + IsPinnable.Meta.indexes + IsModeratable.Meta.indexes # explicitly assign the indexes here
    

    Note: Although What you want can be made possible if Django decides to put that logic in the meta class (this is different from Meta here) of the Meta, but I believe that would then require much work on their end to merge such attributes and would be backwards incompatible and honestly would be a weird feature, what if one doesn't want those indexes?