1
0
mirror of https://github.com/django/django.git synced 2024-12-22 09:05:43 +00:00

Fixed #9602 -- Added AdminSite.get_model_admin().

This allows retrieving an admin class for the given model class without
using internal attributes.
This commit is contained in:
Mariusz Felisiak 2023-07-07 08:06:01 +02:00 committed by GitHub
parent d569c1dcfe
commit f64fd47a76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 127 additions and 69 deletions

View File

@ -220,6 +220,8 @@ class BaseModelAdminChecks:
ManyToManyField and that the item has a related ModelAdmin with ManyToManyField and that the item has a related ModelAdmin with
search_fields defined. search_fields defined.
""" """
from django.contrib.admin.sites import NotRegistered
try: try:
field = obj.model._meta.get_field(field_name) field = obj.model._meta.get_field(field_name)
except FieldDoesNotExist: except FieldDoesNotExist:
@ -234,8 +236,9 @@ class BaseModelAdminChecks:
obj=obj, obj=obj,
id="admin.E038", id="admin.E038",
) )
related_admin = obj.admin_site._registry.get(field.remote_field.model) try:
if related_admin is None: related_admin = obj.admin_site.get_model_admin(field.remote_field.model)
except NotRegistered:
return [ return [
checks.Error( checks.Error(
'An admin for model "%s" has to be registered ' 'An admin for model "%s" has to be registered '
@ -248,19 +251,20 @@ class BaseModelAdminChecks:
id="admin.E039", id="admin.E039",
) )
] ]
elif not related_admin.search_fields: else:
return [ if not related_admin.search_fields:
checks.Error( return [
'%s must define "search_fields", because it\'s ' checks.Error(
"referenced by %s.autocomplete_fields." '%s must define "search_fields", because it\'s '
% ( "referenced by %s.autocomplete_fields."
related_admin.__class__.__name__, % (
type(obj).__name__, related_admin.__class__.__name__,
), type(obj).__name__,
obj=obj.__class__, ),
id="admin.E040", obj=obj.__class__,
) id="admin.E040",
] )
]
return [] return []
def _check_raw_id_fields(self, obj): def _check_raw_id_fields(self, obj):

View File

@ -257,10 +257,16 @@ class RelatedFieldListFilter(FieldListFilter):
""" """
Return the model admin's ordering for related field, if provided. Return the model admin's ordering for related field, if provided.
""" """
related_admin = model_admin.admin_site._registry.get(field.remote_field.model) from django.contrib.admin.sites import NotRegistered
if related_admin is not None:
try:
related_admin = model_admin.admin_site.get_model_admin(
field.remote_field.model
)
except NotRegistered:
return ()
else:
return related_admin.get_ordering(request) return related_admin.get_ordering(request)
return ()
def field_choices(self, field, request, model_admin): def field_choices(self, field, request, model_admin):
ordering = self.field_admin_ordering(field, request, model_admin) ordering = self.field_admin_ordering(field, request, model_admin)

View File

@ -160,6 +160,8 @@ class BaseModelAdmin(metaclass=forms.MediaDefiningClass):
If kwargs are given, they're passed to the form Field's constructor. If kwargs are given, they're passed to the form Field's constructor.
""" """
from django.contrib.admin.sites import NotRegistered
# If the field specifies choices, we don't need to look for special # If the field specifies choices, we don't need to look for special
# admin widgets - we just need to use a select widget of some kind. # admin widgets - we just need to use a select widget of some kind.
if db_field.choices: if db_field.choices:
@ -185,23 +187,27 @@ class BaseModelAdmin(metaclass=forms.MediaDefiningClass):
# rendered output. formfield can be None if it came from a # rendered output. formfield can be None if it came from a
# OneToOneField with parent_link=True or a M2M intermediary. # OneToOneField with parent_link=True or a M2M intermediary.
if formfield and db_field.name not in self.raw_id_fields: if formfield and db_field.name not in self.raw_id_fields:
related_modeladmin = self.admin_site._registry.get( try:
db_field.remote_field.model related_modeladmin = self.admin_site.get_model_admin(
) db_field.remote_field.model
wrapper_kwargs = {}
if related_modeladmin:
wrapper_kwargs.update(
can_add_related=related_modeladmin.has_add_permission(request),
can_change_related=related_modeladmin.has_change_permission(
request
),
can_delete_related=related_modeladmin.has_delete_permission(
request
),
can_view_related=related_modeladmin.has_view_permission(
request
),
) )
except NotRegistered:
wrapper_kwargs = {}
else:
wrapper_kwargs = {
"can_add_related": related_modeladmin.has_add_permission(
request
),
"can_change_related": related_modeladmin.has_change_permission(
request
),
"can_delete_related": related_modeladmin.has_delete_permission(
request
),
"can_view_related": related_modeladmin.has_view_permission(
request
),
}
formfield.widget = widgets.RelatedFieldWidgetWrapper( formfield.widget = widgets.RelatedFieldWidgetWrapper(
formfield.widget, formfield.widget,
db_field.remote_field, db_field.remote_field,
@ -246,8 +252,13 @@ class BaseModelAdmin(metaclass=forms.MediaDefiningClass):
ordering. Otherwise don't specify the queryset, let the field decide ordering. Otherwise don't specify the queryset, let the field decide
(return None in that case). (return None in that case).
""" """
related_admin = self.admin_site._registry.get(db_field.remote_field.model) from django.contrib.admin.sites import NotRegistered
if related_admin is not None:
try:
related_admin = self.admin_site.get_model_admin(db_field.remote_field.model)
except NotRegistered:
return None
else:
ordering = related_admin.get_ordering(request) ordering = related_admin.get_ordering(request)
if ordering is not None and ordering != (): if ordering is not None and ordering != ():
return db_field.remote_field.model._default_manager.using(db).order_by( return db_field.remote_field.model._default_manager.using(db).order_by(

View File

@ -121,7 +121,7 @@ class AdminSite:
) )
if self.is_registered(model): if self.is_registered(model):
registered_admin = str(self._registry[model]) registered_admin = str(self.get_model_admin(model))
msg = "The model %s is already registered " % model.__name__ msg = "The model %s is already registered " % model.__name__
if registered_admin.endswith(".ModelAdmin"): if registered_admin.endswith(".ModelAdmin"):
# Most likely registered without a ModelAdmin subclass. # Most likely registered without a ModelAdmin subclass.
@ -166,6 +166,12 @@ class AdminSite:
""" """
return model in self._registry return model in self._registry
def get_model_admin(self, model):
try:
return self._registry[model]
except KeyError:
raise NotRegistered(f"The model {model.__name__} is not registered.")
def add_action(self, action, name=None): def add_action(self, action, name=None):
""" """
Register an action to be available globally. Register an action to be available globally.

View File

@ -144,7 +144,9 @@ def get_deleted_objects(objs, request, admin_site):
no_edit_link = "%s: %s" % (capfirst(opts.verbose_name), obj) no_edit_link = "%s: %s" % (capfirst(opts.verbose_name), obj)
if admin_site.is_registered(model): if admin_site.is_registered(model):
if not admin_site._registry[model].has_delete_permission(request, obj): if not admin_site.get_model_admin(model).has_delete_permission(
request, obj
):
perms_needed.add(opts.verbose_name) perms_needed.add(opts.verbose_name)
try: try:
admin_url = reverse( admin_url = reverse(

View File

@ -74,6 +74,8 @@ class AutocompleteJsonView(BaseListView):
Raise Http404 if the target model admin is not configured properly with Raise Http404 if the target model admin is not configured properly with
search_fields. search_fields.
""" """
from django.contrib.admin.sites import NotRegistered
term = request.GET.get("term", "") term = request.GET.get("term", "")
try: try:
app_label = request.GET["app_label"] app_label = request.GET["app_label"]
@ -97,8 +99,8 @@ class AutocompleteJsonView(BaseListView):
except AttributeError as e: except AttributeError as e:
raise PermissionDenied from e raise PermissionDenied from e
try: try:
model_admin = self.admin_site._registry[remote_model] model_admin = self.admin_site.get_model_admin(remote_model)
except KeyError as e: except NotRegistered as e:
raise PermissionDenied from e raise PermissionDenied from e
# Validate suitability of objects. # Validate suitability of objects.

View File

@ -3024,6 +3024,13 @@ Templates can override or extend base admin templates as described in
Raises ``django.contrib.admin.sites.NotRegistered`` if a model isn't Raises ``django.contrib.admin.sites.NotRegistered`` if a model isn't
already registered. already registered.
.. method:: AdminSite.get_model_admin(model)
.. versionadded:: 5.0
Returns an admin class for the given model class. Raises
``django.contrib.admin.sites.NotRegistered`` if a model isn't registered.
.. method:: AdminSite.get_log_entries(request) .. method:: AdminSite.get_log_entries(request)
.. versionadded:: 5.0 .. versionadded:: 5.0

View File

@ -145,6 +145,9 @@ Minor features
* ``XRegExp`` is upgraded from version 3.2.0 to 5.1.1. * ``XRegExp`` is upgraded from version 3.2.0 to 5.1.1.
* The new :meth:`.AdminSite.get_model_admin` method returns an admin class for
the given model class.
:mod:`django.contrib.admindocs` :mod:`django.contrib.admindocs`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -878,7 +878,7 @@ class ChangeListTests(TestCase):
user_parents = self._create_superuser("parents") user_parents = self._create_superuser("parents")
# Test with user 'noparents' # Test with user 'noparents'
m = custom_site._registry[Child] m = custom_site.get_model_admin(Child)
request = self._mocked_authenticated_request("/child/", user_noparents) request = self._mocked_authenticated_request("/child/", user_noparents)
response = m.changelist_view(request) response = m.changelist_view(request)
self.assertNotContains(response, "Parent object") self.assertNotContains(response, "Parent object")
@ -903,7 +903,7 @@ class ChangeListTests(TestCase):
# Test default implementation # Test default implementation
custom_site.register(Child, ChildAdmin) custom_site.register(Child, ChildAdmin)
m = custom_site._registry[Child] m = custom_site.get_model_admin(Child)
request = self._mocked_authenticated_request("/child/", user_noparents) request = self._mocked_authenticated_request("/child/", user_noparents)
response = m.changelist_view(request) response = m.changelist_view(request)
self.assertContains(response, "Parent object") self.assertContains(response, "Parent object")

View File

@ -276,8 +276,7 @@ class SystemChecksTestCase(SimpleTestCase):
class MyBookAdmin(admin.ModelAdmin): class MyBookAdmin(admin.ModelAdmin):
def check(self, **kwargs): def check(self, **kwargs):
errors = super().check(**kwargs) errors = super().check(**kwargs)
author_admin = self.admin_site._registry.get(Author) if not self.admin_site.is_registered(Author):
if author_admin is None:
errors.append("AuthorAdmin missing!") errors.append("AuthorAdmin missing!")
return errors return errors

View File

@ -152,10 +152,10 @@ class TestRelatedFieldsAdminOrdering(TestCase):
site.unregister(Band) site.unregister(Band)
def check_ordering_of_field_choices(self, correct_ordering): def check_ordering_of_field_choices(self, correct_ordering):
fk_field = site._registry[Song].formfield_for_foreignkey( fk_field = site.get_model_admin(Song).formfield_for_foreignkey(
Song.band.field, request=None Song.band.field, request=None
) )
m2m_field = site._registry[Song].formfield_for_manytomany( m2m_field = site.get_model_admin(Song).formfield_for_manytomany(
Song.other_interpreters.field, request=None Song.other_interpreters.field, request=None
) )
self.assertEqual(list(fk_field.queryset), correct_ordering) self.assertEqual(list(fk_field.queryset), correct_ordering)

View File

@ -22,13 +22,13 @@ class TestRegistration(SimpleTestCase):
def test_bare_registration(self): def test_bare_registration(self):
self.site.register(Person) self.site.register(Person)
self.assertIsInstance(self.site._registry[Person], admin.ModelAdmin) self.assertIsInstance(self.site.get_model_admin(Person), admin.ModelAdmin)
self.site.unregister(Person) self.site.unregister(Person)
self.assertEqual(self.site._registry, {}) self.assertEqual(self.site._registry, {})
def test_registration_with_model_admin(self): def test_registration_with_model_admin(self):
self.site.register(Person, NameAdmin) self.site.register(Person, NameAdmin)
self.assertIsInstance(self.site._registry[Person], NameAdmin) self.assertIsInstance(self.site.get_model_admin(Person), NameAdmin)
self.site.unregister(Person) self.site.unregister(Person)
self.assertEqual(self.site._registry, {}) self.assertEqual(self.site._registry, {})
@ -57,22 +57,28 @@ class TestRegistration(SimpleTestCase):
def test_registration_with_star_star_options(self): def test_registration_with_star_star_options(self):
self.site.register(Person, search_fields=["name"]) self.site.register(Person, search_fields=["name"])
self.assertEqual(self.site._registry[Person].search_fields, ["name"]) self.assertEqual(self.site.get_model_admin(Person).search_fields, ["name"])
def test_get_model_admin_unregister_model(self):
msg = "The model Person is not registered."
with self.assertRaisesMessage(admin.sites.NotRegistered, msg):
self.site.get_model_admin(Person)
def test_star_star_overrides(self): def test_star_star_overrides(self):
self.site.register( self.site.register(
Person, NameAdmin, search_fields=["name"], list_display=["__str__"] Person, NameAdmin, search_fields=["name"], list_display=["__str__"]
) )
self.assertEqual(self.site._registry[Person].search_fields, ["name"]) person_admin = self.site.get_model_admin(Person)
self.assertEqual(self.site._registry[Person].list_display, ["__str__"]) self.assertEqual(person_admin.search_fields, ["name"])
self.assertTrue(self.site._registry[Person].save_on_top) self.assertEqual(person_admin.list_display, ["__str__"])
self.assertIs(person_admin.save_on_top, True)
def test_iterable_registration(self): def test_iterable_registration(self):
self.site.register([Person, Place], search_fields=["name"]) self.site.register([Person, Place], search_fields=["name"])
self.assertIsInstance(self.site._registry[Person], admin.ModelAdmin) self.assertIsInstance(self.site.get_model_admin(Person), admin.ModelAdmin)
self.assertEqual(self.site._registry[Person].search_fields, ["name"]) self.assertEqual(self.site.get_model_admin(Person).search_fields, ["name"])
self.assertIsInstance(self.site._registry[Place], admin.ModelAdmin) self.assertIsInstance(self.site.get_model_admin(Place), admin.ModelAdmin)
self.assertEqual(self.site._registry[Place].search_fields, ["name"]) self.assertEqual(self.site.get_model_admin(Place).search_fields, ["name"])
self.site.unregister([Person, Place]) self.site.unregister([Person, Place])
self.assertEqual(self.site._registry, {}) self.assertEqual(self.site._registry, {})
@ -116,18 +122,26 @@ class TestRegistrationDecorator(SimpleTestCase):
def test_basic_registration(self): def test_basic_registration(self):
register(Person)(NameAdmin) register(Person)(NameAdmin)
self.assertIsInstance(self.default_site._registry[Person], admin.ModelAdmin) self.assertIsInstance(
self.default_site.get_model_admin(Person), admin.ModelAdmin
)
self.default_site.unregister(Person) self.default_site.unregister(Person)
def test_custom_site_registration(self): def test_custom_site_registration(self):
register(Person, site=self.custom_site)(NameAdmin) register(Person, site=self.custom_site)(NameAdmin)
self.assertIsInstance(self.custom_site._registry[Person], admin.ModelAdmin) self.assertIsInstance(
self.custom_site.get_model_admin(Person), admin.ModelAdmin
)
def test_multiple_registration(self): def test_multiple_registration(self):
register(Traveler, Place)(NameAdmin) register(Traveler, Place)(NameAdmin)
self.assertIsInstance(self.default_site._registry[Traveler], admin.ModelAdmin) self.assertIsInstance(
self.default_site.get_model_admin(Traveler), admin.ModelAdmin
)
self.default_site.unregister(Traveler) self.default_site.unregister(Traveler)
self.assertIsInstance(self.default_site._registry[Place], admin.ModelAdmin) self.assertIsInstance(
self.default_site.get_model_admin(Place), admin.ModelAdmin
)
self.default_site.unregister(Place) self.default_site.unregister(Place)
def test_wrapped_class_not_a_model_admin(self): def test_wrapped_class_not_a_model_admin(self):

View File

@ -3,6 +3,7 @@ import json
from contextlib import contextmanager from contextlib import contextmanager
from django.contrib import admin from django.contrib import admin
from django.contrib.admin.sites import NotRegistered
from django.contrib.admin.tests import AdminSeleniumTestCase from django.contrib.admin.tests import AdminSeleniumTestCase
from django.contrib.admin.views.autocomplete import AutocompleteJsonView from django.contrib.admin.views.autocomplete import AutocompleteJsonView
from django.contrib.auth.models import Permission, User from django.contrib.auth.models import Permission, User
@ -61,8 +62,11 @@ site.register(Toy, autocomplete_fields=["child"])
@contextmanager @contextmanager
def model_admin(model, model_admin, admin_site=site): def model_admin(model, model_admin, admin_site=site):
org_admin = admin_site._registry.get(model) try:
if org_admin: org_admin = admin_site.get_model_admin(model)
except NotRegistered:
org_admin = None
else:
admin_site.unregister(model) admin_site.unregister(model)
admin_site.register(model, model_admin) admin_site.register(model, model_admin)
try: try:

View File

@ -9,7 +9,7 @@ class GeoAdminTest(SimpleTestCase):
admin_site = site # ModelAdmin admin_site = site # ModelAdmin
def test_widget_empty_string(self): def test_widget_empty_string(self):
geoadmin = self.admin_site._registry[City] geoadmin = self.admin_site.get_model_admin(City)
form = geoadmin.get_changelist_form(None)({"point": ""}) form = geoadmin.get_changelist_form(None)({"point": ""})
with self.assertRaisesMessage(AssertionError, "no logs"): with self.assertRaisesMessage(AssertionError, "no logs"):
with self.assertLogs("django.contrib.gis", "ERROR"): with self.assertLogs("django.contrib.gis", "ERROR"):
@ -21,7 +21,7 @@ class GeoAdminTest(SimpleTestCase):
) )
def test_widget_invalid_string(self): def test_widget_invalid_string(self):
geoadmin = self.admin_site._registry[City] geoadmin = self.admin_site.get_model_admin(City)
form = geoadmin.get_changelist_form(None)({"point": "INVALID()"}) form = geoadmin.get_changelist_form(None)({"point": "INVALID()"})
with self.assertLogs("django.contrib.gis", "ERROR") as cm: with self.assertLogs("django.contrib.gis", "ERROR") as cm:
output = str(form["point"]) output = str(form["point"])
@ -38,7 +38,7 @@ class GeoAdminTest(SimpleTestCase):
) )
def test_widget_has_changed(self): def test_widget_has_changed(self):
geoadmin = self.admin_site._registry[City] geoadmin = self.admin_site.get_model_admin(City)
form = geoadmin.get_changelist_form(None)() form = geoadmin.get_changelist_form(None)()
has_changed = form.fields["point"].has_changed has_changed = form.fields["point"].has_changed
@ -59,7 +59,7 @@ class GISAdminTests(GeoAdminTest):
admin_site = site_gis # GISModelAdmin admin_site = site_gis # GISModelAdmin
def test_default_gis_widget_kwargs(self): def test_default_gis_widget_kwargs(self):
geoadmin = self.admin_site._registry[City] geoadmin = self.admin_site.get_model_admin(City)
form = geoadmin.get_changelist_form(None)() form = geoadmin.get_changelist_form(None)()
widget = form["point"].field.widget widget = form["point"].field.widget
self.assertEqual(widget.attrs["default_lat"], 47) self.assertEqual(widget.attrs["default_lat"], 47)
@ -67,7 +67,7 @@ class GISAdminTests(GeoAdminTest):
self.assertEqual(widget.attrs["default_zoom"], 12) self.assertEqual(widget.attrs["default_zoom"], 12)
def test_custom_gis_widget_kwargs(self): def test_custom_gis_widget_kwargs(self):
geoadmin = site_gis_custom._registry[City] geoadmin = site_gis_custom.get_model_admin(City)
form = geoadmin.get_changelist_form(None)() form = geoadmin.get_changelist_form(None)()
widget = form["point"].field.widget widget = form["point"].field.widget
self.assertEqual(widget.attrs["default_lat"], 55) self.assertEqual(widget.attrs["default_lat"], 55)

View File

@ -871,7 +871,7 @@ class ModelAdminTests(TestCase):
username="bob", email="bob@test.com", password="test" username="bob", email="bob@test.com", password="test"
) )
self.site.register(Band, ModelAdmin) self.site.register(Band, ModelAdmin)
ma = self.site._registry[Band] ma = self.site.get_model_admin(Band)
( (
deletable_objects, deletable_objects,
model_count, model_count,
@ -898,7 +898,7 @@ class ModelAdminTests(TestCase):
return False return False
self.site.register(Band, TestModelAdmin) self.site.register(Band, TestModelAdmin)
ma = self.site._registry[Band] ma = self.site.get_model_admin(Band)
( (
deletable_objects, deletable_objects,
model_count, model_count,