Code

Ticket #6776: 6776_modeladmin_fix.2.diff

File 6776_modeladmin_fix.2.diff, 4.9 KB (added by brosner, 6 years ago)

slightly better patch and tests

Line 
1diff --git a/django/contrib/admin/loading.py b/django/contrib/admin/loading.py
2new file mode 100644
3index 0000000..4481a13
4--- /dev/null
5+++ b/django/contrib/admin/loading.py
6@@ -0,0 +1,21 @@
7+
8+class ModelAdminCache(object):
9+    """
10+    A cache of ModelAdmin classes. This ensures that once a ModelAdmin is
11+    declared that it will remain the sole class. Basically providing a
12+    guranteed ModelAdmin idenity.
13+    """
14+    def __init__(self):
15+        self.modeladmin_store = {}
16+   
17+    def get(self, key):
18+        return self.modeladmin_store[key]
19+   
20+    def register(self, *admin_classes):
21+        for admin_class in admin_classes:
22+            key = ".".join(admin_class.__module__.split(".")[-2:] + [admin_class.__name__])
23+            if key in self.modeladmin_store:
24+                continue
25+            self.modeladmin_store[key] = admin_class
26+
27+admin_class_cache = ModelAdminCache()
28diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
29index 29ce10a..228c529 100644
30--- a/django/contrib/admin/options.py
31+++ b/django/contrib/admin/options.py
32@@ -4,6 +4,7 @@ from django.newforms.formsets import all_valid
33 from django.newforms.models import _modelform_factory, _inlineformset_factory
34 from django.contrib.contenttypes.models import ContentType
35 from django.contrib.admin import widgets
36+from django.contrib.admin.loading import admin_class_cache
37 from django.contrib.admin.util import get_deleted_objects
38 from django.core.exceptions import ImproperlyConfigured, PermissionDenied
39 from django.db import models, transaction
40@@ -124,6 +125,21 @@ class AdminField(object):
41         attrs = classes and {'class': u' '.join(classes)} or {}
42         return self.field.label_tag(contents=contents, attrs=attrs)
43 
44+class ModelAdminMetaclass(type):
45+    def __new__(cls, name, bases, attrs):
46+        new_class = forms.MediaDefiningClass.__new__(cls, name, bases, attrs)
47+        try:
48+            parents = [b for b in bases if issubclass(b, ModelAdmin)]
49+            if not parents:
50+                return new_class
51+        except NameError:
52+            return new_class
53+        # put this in some sort of registry
54+        admin_class_cache.register(new_class)
55+        # due to the way imports happening only use the admin class that is
56+        # already registered.
57+        return admin_class_cache.get(".".join(new_class.__module__.split(".")[-2:] + [new_class.__name__]))
58+
59 class BaseModelAdmin(object):
60     """Functionality common to both ModelAdmin and InlineAdmin."""
61     raw_id_fields = ()
62@@ -200,7 +216,7 @@ class BaseModelAdmin(object):
63 
64 class ModelAdmin(BaseModelAdmin):
65     "Encapsulates all admin options and functionality for a given model."
66-    __metaclass__ = forms.MediaDefiningClass
67+    __metaclass__ = ModelAdminMetaclass
68 
69     list_display = ('__str__',)
70     list_display_links = ()
71diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py
72index 6c6f47e..b496fb7 100644
73--- a/django/contrib/admin/sites.py
74+++ b/django/contrib/admin/sites.py
75@@ -76,8 +76,8 @@ class AdminSite(object):
76         if isinstance(model_or_iterable, ModelBase):
77             model_or_iterable = [model_or_iterable]
78         for model in model_or_iterable:
79-            if model in self._registry:
80-                raise AlreadyRegistered('The model %s is already registered' % model.__name__)
81+            if model in self._registry and self._registry[model].__class__ is not admin_class:
82+                raise AlreadyRegistered('The model %s is already registered to %s' % (model.__name__, self._registry[model].__class__.__name__))
83             self._registry[model] = admin_class(model, self)
84 
85     def unregister(self, model_or_iterable):
86diff --git a/tests/regressiontests/modeladmin/models.py b/tests/regressiontests/modeladmin/models.py
87index 5e67c87..0a04691 100644
88--- a/tests/regressiontests/modeladmin/models.py
89+++ b/tests/regressiontests/modeladmin/models.py
90@@ -17,6 +17,25 @@ we'll just pass in None.
91 
92 >>> request = None
93 
94+Ensure that only the first ModelAdmin ever declared is the only one for the
95+app.
96+
97+>>> class OneBandAdmin(ModelAdmin):
98+...     pass
99+
100+>>> class TwoBandAdmin(ModelAdmin):
101+...     pass
102+
103+>>> one_band_admin_id = id(OneBandAdmin)
104+>>> id(OneBandAdmin) != id(TwoBandAdmin)
105+True
106+
107+>>> class OneBandAdmin(ModelAdmin):
108+...     pass
109+
110+>>> one_band_admin_id == id(OneBandAdmin)
111+True
112+
113 >>> band = Band(name='The Doors', bio='')
114 
115 Under the covers, the admin system will initialize ModelAdmin with a Model
116@@ -96,10 +115,10 @@ properly. This won't, however, break any of the admin widgets or media.
117 ...     class Meta:
118 ...         model = Band
119 
120->>> class BandAdmin(ModelAdmin):
121+>>> class AnotherBandAdmin(ModelAdmin):
122 ...     form = AdminBandForm
123 
124->>> ma = BandAdmin(Band, site)
125+>>> ma = AnotherBandAdmin(Band, site)
126 >>> ma.get_form(request).base_fields.keys()
127 ['name', 'bio', 'sign_date', 'delete']
128 >>> type(ma.get_form(request).base_fields['sign_date'].widget)