diff --git a/django/contrib/admin/loading.py b/django/contrib/admin/loading.py
new file mode 100644
index 0000000..4481a13
-
|
+
|
|
| 1 | |
| 2 | class ModelAdminCache(object): |
| 3 | """ |
| 4 | A cache of ModelAdmin classes. This ensures that once a ModelAdmin is |
| 5 | declared that it will remain the sole class. Basically providing a |
| 6 | guranteed ModelAdmin idenity. |
| 7 | """ |
| 8 | def __init__(self): |
| 9 | self.modeladmin_store = {} |
| 10 | |
| 11 | def get(self, key): |
| 12 | return self.modeladmin_store[key] |
| 13 | |
| 14 | def register(self, *admin_classes): |
| 15 | for admin_class in admin_classes: |
| 16 | key = ".".join(admin_class.__module__.split(".")[-2:] + [admin_class.__name__]) |
| 17 | if key in self.modeladmin_store: |
| 18 | continue |
| 19 | self.modeladmin_store[key] = admin_class |
| 20 | |
| 21 | admin_class_cache = ModelAdminCache() |
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 29ce10a..228c529 100644
a
|
b
|
from django.newforms.formsets import all_valid
|
4 | 4 | from django.newforms.models import _modelform_factory, _inlineformset_factory |
5 | 5 | from django.contrib.contenttypes.models import ContentType |
6 | 6 | from django.contrib.admin import widgets |
| 7 | from django.contrib.admin.loading import admin_class_cache |
7 | 8 | from django.contrib.admin.util import get_deleted_objects |
8 | 9 | from django.core.exceptions import ImproperlyConfigured, PermissionDenied |
9 | 10 | from django.db import models, transaction |
… |
… |
class AdminField(object):
|
124 | 125 | attrs = classes and {'class': u' '.join(classes)} or {} |
125 | 126 | return self.field.label_tag(contents=contents, attrs=attrs) |
126 | 127 | |
| 128 | class ModelAdminMetaclass(type): |
| 129 | def __new__(cls, name, bases, attrs): |
| 130 | new_class = forms.MediaDefiningClass.__new__(cls, name, bases, attrs) |
| 131 | try: |
| 132 | parents = [b for b in bases if issubclass(b, ModelAdmin)] |
| 133 | if not parents: |
| 134 | return new_class |
| 135 | except NameError: |
| 136 | return new_class |
| 137 | # put this in some sort of registry |
| 138 | admin_class_cache.register(new_class) |
| 139 | # due to the way imports happening only use the admin class that is |
| 140 | # already registered. |
| 141 | return admin_class_cache.get(".".join(new_class.__module__.split(".")[-2:] + [new_class.__name__])) |
| 142 | |
127 | 143 | class BaseModelAdmin(object): |
128 | 144 | """Functionality common to both ModelAdmin and InlineAdmin.""" |
129 | 145 | raw_id_fields = () |
… |
… |
class BaseModelAdmin(object):
|
200 | 216 | |
201 | 217 | class ModelAdmin(BaseModelAdmin): |
202 | 218 | "Encapsulates all admin options and functionality for a given model." |
203 | | __metaclass__ = forms.MediaDefiningClass |
| 219 | __metaclass__ = ModelAdminMetaclass |
204 | 220 | |
205 | 221 | list_display = ('__str__',) |
206 | 222 | list_display_links = () |
diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py
index 6c6f47e..b496fb7 100644
a
|
b
|
class AdminSite(object):
|
76 | 76 | if isinstance(model_or_iterable, ModelBase): |
77 | 77 | model_or_iterable = [model_or_iterable] |
78 | 78 | for model in model_or_iterable: |
79 | | if model in self._registry: |
80 | | raise AlreadyRegistered('The model %s is already registered' % model.__name__) |
| 79 | if model in self._registry and self._registry[model].__class__ is not admin_class: |
| 80 | raise AlreadyRegistered('The model %s is already registered to %s' % (model.__name__, self._registry[model].__class__.__name__)) |
81 | 81 | self._registry[model] = admin_class(model, self) |
82 | 82 | |
83 | 83 | def unregister(self, model_or_iterable): |
diff --git a/tests/regressiontests/modeladmin/models.py b/tests/regressiontests/modeladmin/models.py
index 5e67c87..0a04691 100644
a
|
b
|
we'll just pass in None.
|
17 | 17 | |
18 | 18 | >>> request = None |
19 | 19 | |
| 20 | Ensure that only the first ModelAdmin ever declared is the only one for the |
| 21 | app. |
| 22 | |
| 23 | >>> class OneBandAdmin(ModelAdmin): |
| 24 | ... pass |
| 25 | |
| 26 | >>> class TwoBandAdmin(ModelAdmin): |
| 27 | ... pass |
| 28 | |
| 29 | >>> one_band_admin_id = id(OneBandAdmin) |
| 30 | >>> id(OneBandAdmin) != id(TwoBandAdmin) |
| 31 | True |
| 32 | |
| 33 | >>> class OneBandAdmin(ModelAdmin): |
| 34 | ... pass |
| 35 | |
| 36 | >>> one_band_admin_id == id(OneBandAdmin) |
| 37 | True |
| 38 | |
20 | 39 | >>> band = Band(name='The Doors', bio='') |
21 | 40 | |
22 | 41 | Under the covers, the admin system will initialize ModelAdmin with a Model |
… |
… |
properly. This won't, however, break any of the admin widgets or media.
|
96 | 115 | ... class Meta: |
97 | 116 | ... model = Band |
98 | 117 | |
99 | | >>> class BandAdmin(ModelAdmin): |
| 118 | >>> class AnotherBandAdmin(ModelAdmin): |
100 | 119 | ... form = AdminBandForm |
101 | 120 | |
102 | | >>> ma = BandAdmin(Band, site) |
| 121 | >>> ma = AnotherBandAdmin(Band, site) |
103 | 122 | >>> ma.get_form(request).base_fields.keys() |
104 | 123 | ['name', 'bio', 'sign_date', 'delete'] |
105 | 124 | >>> type(ma.get_form(request).base_fields['sign_date'].widget) |