summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Charette <charette.s@gmail.com>2013-01-29 00:28:09 -0500
committerAymeric Augustin <aymeric.augustin@m4x.org>2013-02-24 17:45:48 +0100
commitf8b41da43156aa4283c83f2e579e8b18157c9b20 (patch)
tree5d9cbde0115a3f6a2df4f68ef4147f7c68c231d1
parent1845c53081ecf3800e9fdc261bae7904d859592f (diff)
[1.5.x] Fixed #19688 -- Allow model subclassing with a custom metaclass using six.with_metaclass
Backport of 6b03179e126d4df01623dccc162c1579f349e41e 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.
-rw-r--r--django/db/models/base.py13
-rw-r--r--tests/modeltests/base/__init__.py0
-rw-r--r--tests/modeltests/base/models.py5
-rw-r--r--tests/modeltests/base/tests.py36
4 files changed, 52 insertions, 2 deletions
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 38afc60991..a58761b643 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -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.
diff --git a/tests/modeltests/base/__init__.py b/tests/modeltests/base/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/base/__init__.py
diff --git a/tests/modeltests/base/models.py b/tests/modeltests/base/models.py
new file mode 100644
index 0000000000..3d39302aba
--- /dev/null
+++ b/tests/modeltests/base/models.py
@@ -0,0 +1,5 @@
+from django.db.models.base import ModelBase
+
+
+class CustomBaseModel(ModelBase):
+ pass
diff --git a/tests/modeltests/base/tests.py b/tests/modeltests/base/tests.py
new file mode 100644
index 0000000000..6229b7a305
--- /dev/null
+++ b/tests/modeltests/base/tests.py
@@ -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`.")