Commit f8b41da4 authored by Simon Charette's avatar Simon Charette Committed by Aymeric Augustin
Browse files

[1.5.x] Fixed #19688 -- Allow model subclassing with a custom metaclass using six.with_metaclass

Backport of 6b03179e from master.

Although we're post RC 2, I'm backporting this because it's arguably a
major bug in a new feauture that will prevent several well-known
third-party apps from being ported to Python 3.
parent 1845c530
Loading
Loading
Loading
Loading
+11 −2
Original line number Diff line number Diff line
@@ -58,12 +58,21 @@ class ModelBase(type):
    """
    def __new__(cls, name, bases, attrs):
        super_new = super(ModelBase, cls).__new__

        # six.with_metaclass() inserts an extra class called 'NewBase' in the
        # inheritance tree: Model -> NewBase -> object. Ignore this class.
        # inheritance tree: Model -> NewBase -> object. But the initialization
        # should be executed only once for a given model class.

        # attrs will never be empty for classes declared in the standard way
        # (ie. with the `class` keyword). This is quite robust.
        if name == 'NewBase' and attrs == {}:
            return super_new(cls, name, bases, attrs)

        # Also ensure initialization is only performed for subclasses of Model
        # (excluding Model class itself).
        parents = [b for b in bases if isinstance(b, ModelBase) and
                not (b.__name__ == 'NewBase' and b.__mro__ == (b, object))]
        if not parents:
            # If this isn't a subclass of Model, don't do anything special.
            return super_new(cls, name, bases, attrs)

        # Create the class.
+0 −0

Empty file added.

+5 −0
Original line number Diff line number Diff line
from django.db.models.base import ModelBase


class CustomBaseModel(ModelBase):
    pass
+36 −0
Original line number Diff line number Diff line
from __future__ import unicode_literals

from django.db import models
from django.test.testcases import SimpleTestCase
from django.utils import six
from django.utils.unittest import skipIf

from .models import CustomBaseModel


class CustomBaseTest(SimpleTestCase):

    @skipIf(six.PY3, 'test metaclass definition under Python 2')
    def test_py2_custom_base(self):
        """
        Make sure models.Model can be subclassed with a valid custom base
        using __metaclass__
        """
        try:
            class MyModel(models.Model):
                __metaclass__ = CustomBaseModel
        except Exception:
            self.fail("models.Model couldn't be subclassed with a valid "
                      "custom base using __metaclass__.")

    def test_six_custom_base(self):
        """
        Make sure models.Model can be subclassed with a valid custom base
        using `six.with_metaclass`.
        """
        try:
            class MyModel(six.with_metaclass(CustomBaseModel, models.Model)):
                pass
        except Exception:
            self.fail("models.Model couldn't be subclassed with a valid "
                      "custom base using `six.with_metaclass`.")