Opened 3 hours ago

Last modified 24 minutes ago

#36921 assigned Bug

KeyError when adding new objects two relations deep via inlines but the intermediate model is not registered with the admin

Reported by: Jacob Walls Owned by: Sean Helvey
Component: contrib.admin Version: dev
Severity: Release blocker Keywords:
Cc: Sean Helvey Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: yes Patch needs improvement: yes
Easy pickings: no UI/UX: no

Description

With these models:

class Analysis(models.Model): ...

class Song(models.Model):
    users = models.ManyToManyField("auth.User")
    analysis = models.ForeignKey("Analysis", on_delete=models.CASCADE, null=True)

And these admins:

class SongInline(admin.TabularInline):
    model = Song


class AnalysisAdmin(admin.ModelAdmin):
    inlines = [SongInline]

admin.site.register([Analysis], AnalysisAdmin)
  1. Admin: Analysis: Add
  2. Click the "+" in the Users column of the Songs inline
  3. Fill out the field
  4. Notice Song is in the URL as the source_model arg. This will be a problem, since Song is not registered with the admin.
  5. Submit
  File "/Users/jacobwalls/django/django/contrib/admin/options.py", line 711, in wrapper
    return self.admin_site.admin_view(view)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jacobwalls/django/django/utils/decorators.py", line 191, in _view_wrapper
    result = _process_exception(request, e)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jacobwalls/django/django/utils/decorators.py", line 189, in _view_wrapper
    response = view_func(request, *args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jacobwalls/django/django/views/decorators/cache.py", line 79, in _view_wrapper
    response = view_func(request, *args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jacobwalls/django/django/contrib/admin/sites.py", line 247, in inner
    return view(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jacobwalls/django/django/utils/decorators.py", line 47, in _wrapper
    return bound_method(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jacobwalls/django/django/views/decorators/debug.py", line 142, in sensitive_post_parameters_wrapper
    return view(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jacobwalls/django/django/utils/decorators.py", line 191, in _view_wrapper
    result = _process_exception(request, e)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jacobwalls/django/django/utils/decorators.py", line 189, in _view_wrapper
    response = view_func(request, *args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jacobwalls/django/django/contrib/auth/admin.py", line 119, in add_view
    return self._add_view(request, form_url, extra_context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jacobwalls/django/django/contrib/auth/admin.py", line 147, in _add_view
    return super().add_view(request, form_url, extra_context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jacobwalls/django/django/contrib/admin/options.py", line 1984, in add_view
    return self.changeform_view(request, None, form_url, extra_context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jacobwalls/django/django/utils/decorators.py", line 47, in _wrapper
    return bound_method(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jacobwalls/django/django/utils/decorators.py", line 191, in _view_wrapper
    result = _process_exception(request, e)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jacobwalls/django/django/utils/decorators.py", line 189, in _view_wrapper
    response = view_func(request, *args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jacobwalls/django/django/contrib/admin/options.py", line 1842, in changeform_view
    return self._changeform_view(request, object_id, form_url, extra_context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jacobwalls/django/django/contrib/admin/options.py", line 1900, in _changeform_view
    return self.response_add(request, new_object)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jacobwalls/django/django/contrib/auth/admin.py", line 251, in response_add
    return super().response_add(request, obj, post_url_continue)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jacobwalls/django/django/contrib/admin/options.py", line 1431, in response_add
    source_admin = self.admin_site._registry[source_model]
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Exception Type: KeyError at /admin/auth/user/add/
Exception Value: <class 'earTrain.models.Song'>

Regression in b1ffa9a9d78b0c2c5ad6ed5a1d84e380d5cfd010.

Change History (5)

comment:1 by Sean Helvey, 104 minutes ago

Eek! I'm not familiar with the triage process but believe this patch would add an if statement to check for that. It worked for me locally:

-                    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
+                    if source_model in self.admin_site._registry:
+                        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

How can I help?

Version 0, edited 104 minutes ago by Sean Helvey (next)

in reply to:  1 comment:2 by Natalia Bidart, 94 minutes ago

Replying to Sean Helvey:

How can I help?

If you have reproduced the reported issue, you can accept the ticket saying so. If you also want to work on the ticket, assign it to yourself after accepting. More details on the triage process here: https://docs.djangoproject.com/en/dev/internals/contributing/triaging-tickets/

Thank you!

comment:3 by Sean Helvey, 81 minutes ago

Needs tests: set
Owner: set to Sean Helvey
Status: newassigned
Triage Stage: UnreviewedAccepted

comment:4 by Sean Helvey, 42 minutes ago

Has patch: set

comment:5 by Jacob Walls, 24 minutes ago

Patch needs improvement: set
Note: See TracTickets for help on using tickets.
Back to Top