Skip to content

Commit 380d77c

Browse files
authored
Fixed #36921 -- Fixed KeyError in inline form for model not registered with admin.
Regression in b1ffa9a.
1 parent 97228a8 commit 380d77c

2 files changed

Lines changed: 43 additions & 16 deletions

File tree

django/contrib/admin/options.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1419,7 +1419,7 @@ def response_add(self, request, obj, post_url_continue=None):
14191419

14201420
# Find the optgroup for the new item, if available
14211421
source_model_name = request.POST.get(SOURCE_MODEL_VAR)
1422-
1422+
source_admin = None
14231423
if source_model_name:
14241424
app_label, model_name = source_model_name.split(".", 1)
14251425
try:
@@ -1428,21 +1428,23 @@ def response_add(self, request, obj, post_url_continue=None):
14281428
msg = _('The app "%s" could not be found.') % source_model_name
14291429
self.message_user(request, msg, messages.ERROR)
14301430
else:
1431-
source_admin = self.admin_site._registry[source_model]
1432-
form = source_admin.get_form(request)()
1433-
if self.opts.verbose_name_plural in form.fields:
1434-
field = form.fields[self.opts.verbose_name_plural]
1435-
for option_value, option_label in field.choices:
1436-
# Check if this is an optgroup (label is a sequence
1437-
# of choices rather than a single string value).
1438-
if isinstance(option_label, (list, tuple)):
1439-
# It's an optgroup:
1440-
# (group_name, [(value, label), ...])
1441-
optgroup_label = option_value
1442-
for choice_value, choice_display in option_label:
1443-
if choice_display == str(obj):
1444-
popup_response["optgroup"] = str(optgroup_label)
1445-
break
1431+
source_admin = self.admin_site._registry.get(source_model)
1432+
1433+
if source_admin:
1434+
form = source_admin.get_form(request)()
1435+
if self.opts.verbose_name_plural in form.fields:
1436+
field = form.fields[self.opts.verbose_name_plural]
1437+
for option_value, option_label in field.choices:
1438+
# Check if this is an optgroup (label is a sequence
1439+
# of choices rather than a single string value).
1440+
if isinstance(option_label, (list, tuple)):
1441+
# It's an optgroup:
1442+
# (group_name, [(value, label), ...])
1443+
optgroup_label = option_value
1444+
for choice_value, choice_display in option_label:
1445+
if choice_display == str(obj):
1446+
popup_response["optgroup"] = str(optgroup_label)
1447+
break
14461448

14471449
popup_response_data = json.dumps(popup_response)
14481450
return TemplateResponse(

tests/admin_views/tests.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,31 @@ def test_popup_add_POST_with_invalid_source_model(self):
589589
self.assertIn("admin_views.nonexistent", str(messages[0]))
590590
self.assertIn("could not be found", str(messages[0]))
591591

592+
def test_popup_add_POST_with_unregistered_source_model(self):
593+
"""
594+
Popup add where source_model is a valid Django model but is not
595+
registered in the admin site (e.g. a model only used as an inline)
596+
should succeed without raising a KeyError.
597+
"""
598+
post_data = {
599+
IS_POPUP_VAR: "1",
600+
# Chapter exists as a model but is not registered in site (only
601+
# in site6), simulating a model used only as an inline.
602+
SOURCE_MODEL_VAR: "admin_views.chapter",
603+
"title": "Test Article",
604+
"content": "some content",
605+
"date_0": "2010-09-10",
606+
"date_1": "14:55:39",
607+
}
608+
response = self.client.post(reverse("admin:admin_views_article_add"), post_data)
609+
self.assertEqual(response.status_code, 200)
610+
self.assertContains(response, "data-popup-response")
611+
# No error messages - unregistered model is silently skipped.
612+
messages = list(response.wsgi_request._messages)
613+
self.assertEqual(len(messages), 0)
614+
# No optgroup in the response.
615+
self.assertNotContains(response, ""optgroup"")
616+
592617
def test_basic_edit_POST(self):
593618
"""
594619
A smoke test to ensure POST on edit_view works.

0 commit comments

Comments
 (0)