Opened 4 years ago

Closed 4 years ago

#16646 closed Bug (invalid)

Only first meta inner class inherited with multiple abstract base models.

Reported by: varikin Owned by: nobody
Component: Database layer (models, ORM) Version: 1.3
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Code shows this better:

app.models.py

from django.db import models

class FirstMixin(models.Model):
    name = models.CharField(max_length=100)

    class Meta:
        abstract = True

class SecondMixin(models.Model):
    order = models.PositiveIntegerField()

    class Meta:
        abstract = True
        ordering = ['order']

class Concrete(FirstMixin, SecondMixin):
    published = models.BooleanField()

Ordering is set on the Concrete instances:

>>> from red.models import Concrete
>>> c = Concrete()
>>> c._meta.ordering
[]

If instead concrete is defined with

class Concrete(SecondMixin, FirstMixin):
    published = models.BooleanField()

Notice that SecondMixin is the first class that Concrete subclasses instead of FirstMixin. This results in

>>> c = Concrete()
>>> c._meta.ordering
['order']

It is very unintuitive and in my opinion incorrect. The order of which classes to inherit from shouldn't prevent inheriting the Meta inner class as well. This could cause hard to find bugs, such as why 'ordering' is not correct or why two different concrete classes have different meta settings while having the same parent classes.

The other way to address this is like so:

class Concrete(FirstMixin, SecondMixin):
    published = models.BooleanField()
    
    class Meta(SecondMixin.Meta):
        pass

This is more explicit, but would a developer think this has to be done until they ran into this issue? Also, I think this is the only way to possibly get Meta options from multiple parent classes by having class Meta(SecondMixin.Meta, FirstMixin.Meta).

Change History (4)

comment:1 Changed 4 years ago by aaugustin

  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset
  • Resolution set to worksforme
  • Status changed from new to closed

Generally, child classes don't inherit the Meta class of their parents. (There are with two limited exceptions, ordering and get_latest_by.) But you seem to assume that they do. It's probably a source of confusion.

This document explains why this is the correct behavior: https://docs.djangoproject.com/en/dev/topics/db/models/#meta-and-multi-table-inheritance

The multiple inheritance situation is described here: https://docs.djangoproject.com/en/dev/topics/db/models/#multiple-inheritance

Please review these sections of the documentation as they address your questions.

comment:2 follow-up: Changed 4 years ago by varikin

  • Resolution worksforme deleted
  • Status changed from closed to reopened

They do not address the concern. For starters, ordering was not copied over to the child class in the first case. Second, from the docs:

When an abstract base class is created, Django makes any Meta inner class you declared in the base class available as an attribute. If a child class does not declare its own Meta class, it will inherit the parent's Meta.

https://docs.djangoproject.com/en/dev/topics/db/models/#meta-inheritance

My examples use abstract base classes, so the Meta class should be inherited. This is a matter of when using abstract base classes, the meta is not inherited for multiple abstract base classes.

comment:3 in reply to: ↑ 2 Changed 4 years ago by aaugustin

I understand that you would like the parents' Meta classes to be magically merged to build the child Meta class. Unfortunately, as pointed out in the first link I posted, for most options of Meta that doesn't make sense. Even for ordering, there are several way you can merge two values: take the first that isn't None, concatenate, etc.

You could request this feature but it's likely to get rejected on the grounds of backwards-incompatibility.


Replying to varikin:

For starters, ordering was not copied over to the child class in the first case.

You're using multiple inheritance, and the docs say that "if multiple parents contain a Meta class, only the first one is going to be used, and all others will be ignored." This is the second link I posted.

In your first case, the first Meta class contains no ordering. Hence the child contains no ordering.

One way to understand this is as follows:

>>> class A(object):
...     attr = "foo"
... 
>>> class B(object):
...     attr = "bar"
... 
>>> class C(A, B):
...     pass
... 
>>> C.attr
'foo'
>>> class D(B, A):
...     pass
... 
>>> D.attr
'bar'
>>> 

Just put class Meta instead of attr in this example, and hopefully you will Django's behavior will look more natural.

You could also take a look at Python's behavior regarding __metaclass__ and multiple inheritance; it's a very similar problem.

Replying to varikin:

My examples use abstract base classes, so the Meta class should be inherited.

That's true; I had forgotten that; please disregard my first sentence.

This is a matter of when using abstract base classes, the meta is not inherited for multiple abstract base classes.

Only the first one — see above.


So I still believe Django behaves according to the documentation, and there isn't a bug here. Since I'm not really into close/reopen wars, I'll let someone else decide what to do with this ticket.

comment:4 Changed 4 years ago by varikin

  • Resolution set to invalid
  • Status changed from reopened to closed

Ok, I am convinced that it is working as documented though it feels wrong.

Note: See TracTickets for help on using tickets.
Back to Top