summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Helvey <sean.helvey@gmail.com>2026-02-11 15:07:40 -0800
committerGitHub <noreply@github.com>2026-02-11 18:07:40 -0500
commit380d77cccefbe185ddb3f9368d8fdeb7b7cf7108 (patch)
treee5bc171974ffd94d492362e1f2d0d67355d7705c
parent97228a86d2b7d8011b97bebdfe0f126a536a3841 (diff)
Fixed #36921 -- Fixed KeyError in inline form for model not registered with admin.
Regression in b1ffa9a9d78b0c2c5ad6ed5a1d84e380d5cfd010.
-rw-r--r--django/contrib/admin/options.py34
-rw-r--r--tests/admin_views/tests.py25
2 files changed, 43 insertions, 16 deletions
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 9c787d2329..b67b023bd3 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -1419,7 +1419,7 @@ class ModelAdmin(BaseModelAdmin):
# Find the optgroup for the new item, if available
source_model_name = request.POST.get(SOURCE_MODEL_VAR)
-
+ source_admin = None
if source_model_name:
app_label, model_name = source_model_name.split(".", 1)
try:
@@ -1428,21 +1428,23 @@ class ModelAdmin(BaseModelAdmin):
msg = _('The app "%s" could not be found.') % source_model_name
self.message_user(request, msg, messages.ERROR)
else:
- source_admin = self.admin_site._registry[source_model]
- form = source_admin.get_form(request)()
- if self.opts.verbose_name_plural in form.fields:
- field = form.fields[self.opts.verbose_name_plural]
- for option_value, option_label in field.choices:
- # Check if this is an optgroup (label is a sequence
- # of choices rather than a single string value).
- if isinstance(option_label, (list, tuple)):
- # It's an optgroup:
- # (group_name, [(value, label), ...])
- optgroup_label = option_value
- for choice_value, choice_display in option_label:
- if choice_display == str(obj):
- popup_response["optgroup"] = str(optgroup_label)
- break
+ source_admin = self.admin_site._registry.get(source_model)
+
+ if source_admin:
+ form = source_admin.get_form(request)()
+ if self.opts.verbose_name_plural in form.fields:
+ field = form.fields[self.opts.verbose_name_plural]
+ for option_value, option_label in field.choices:
+ # Check if this is an optgroup (label is a sequence
+ # of choices rather than a single string value).
+ if isinstance(option_label, (list, tuple)):
+ # It's an optgroup:
+ # (group_name, [(value, label), ...])
+ optgroup_label = option_value
+ for choice_value, choice_display in option_label:
+ if choice_display == str(obj):
+ popup_response["optgroup"] = str(optgroup_label)
+ break
popup_response_data = json.dumps(popup_response)
return TemplateResponse(
diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py
index 7f35d7c2f4..fa9d9a2dc6 100644
--- a/tests/admin_views/tests.py
+++ b/tests/admin_views/tests.py
@@ -589,6 +589,31 @@ class AdminViewBasicTest(AdminViewBasicTestCase):
self.assertIn("admin_views.nonexistent", str(messages[0]))
self.assertIn("could not be found", str(messages[0]))
+ def test_popup_add_POST_with_unregistered_source_model(self):
+ """
+ Popup add where source_model is a valid Django model but is not
+ registered in the admin site (e.g. a model only used as an inline)
+ should succeed without raising a KeyError.
+ """
+ post_data = {
+ IS_POPUP_VAR: "1",
+ # Chapter exists as a model but is not registered in site (only
+ # in site6), simulating a model used only as an inline.
+ SOURCE_MODEL_VAR: "admin_views.chapter",
+ "title": "Test Article",
+ "content": "some content",
+ "date_0": "2010-09-10",
+ "date_1": "14:55:39",
+ }
+ response = self.client.post(reverse("admin:admin_views_article_add"), post_data)
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, "data-popup-response")
+ # No error messages - unregistered model is silently skipped.
+ messages = list(response.wsgi_request._messages)
+ self.assertEqual(len(messages), 0)
+ # No optgroup in the response.
+ self.assertNotContains(response, "&quot;optgroup&quot;")
+
def test_basic_edit_POST(self):
"""
A smoke test to ensure POST on edit_view works.