Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

[1.5.x] Fixed #19688 -- Allow model subclassing with a custom metacla…

…ss using six.with_metaclass

Backport of 6b03179 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.
  • Loading branch information...
commit f8b41da43156aa4283c83f2e579e8b18157c9b20 1 parent 1845c53
Simon Charette charettes authored aaugustin committed
13 django/db/models/base.py
View
@@ -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  tests/modeltests/base/__init__.py
View
No changes.
5 tests/modeltests/base/models.py
View
@@ -0,0 +1,5 @@
+from django.db.models.base import ModelBase
+
+
+class CustomBaseModel(ModelBase):
+ pass
36 tests/modeltests/base/tests.py
View
@@ -0,0 +1,36 @@
+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`.")
Please sign in to comment.
Something went wrong with that request. Please try again.