Opened 3 years ago

Closed 3 years ago

Last modified 3 years ago

#27967 closed Bug (fixed)

The autogenerated OneToOneField on multi table inheritance breaks the InlineAdminForm given UUID pk

Reported by: Robin Anupol Owned by: Paulo
Component: contrib.admin Version: 1.10
Severity: Normal Keywords: admin
Cc: Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

I'm receiving MultiValueDictKeyError given the following setup:

# models.py
class A(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(max_length=32)

class B(A):
    s = models.ForeignKey('S', models.PROTECT)

class S(models.Model):
    name = models.CharField(max_length=32)

# admin.py
class BInline(admin.StackedInline):
    model = B

@admin.register(S)
class SAdmin(admin.ModelAdmin):
    inlines = [BInline]

To get the error:

  1. On admin page, create an S instance.
  2. On S instance, create 1 B inline instance then save.
  3. Still on S instance, create another B inline instance then save.

Here's the trace:

Traceback (most recent call last):
  File "/home/rnddev/.virtualenvs/vulcan/local/lib/python2.7/site-packages/django/core/handlers/exception.py", line 42, in inner
    response = get_response(request)
  File "/home/rnddev/.virtualenvs/vulcan/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 187, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/home/rnddev/.virtualenvs/vulcan/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 185, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/rnddev/.virtualenvs/vulcan/local/lib/python2.7/site-packages/django/contrib/admin/options.py", line 544, in wrapper
    return self.admin_site.admin_view(view)(*args, **kwargs)
  File "/home/rnddev/.virtualenvs/vulcan/local/lib/python2.7/site-packages/django/utils/decorators.py", line 149, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "/home/rnddev/.virtualenvs/vulcan/local/lib/python2.7/site-packages/django/views/decorators/cache.py", line 57, in _wrapped_view_func
    response = view_func(request, *args, **kwargs)
  File "/home/rnddev/.virtualenvs/vulcan/local/lib/python2.7/site-packages/django/contrib/admin/sites.py", line 211, in inner
    return view(request, *args, **kwargs)
  File "/home/rnddev/.virtualenvs/vulcan/local/lib/python2.7/site-packages/django/contrib/admin/options.py", line 1512, in change_view
    return self.changeform_view(request, object_id, form_url, extra_context)
  File "/home/rnddev/.virtualenvs/vulcan/local/lib/python2.7/site-packages/django/utils/decorators.py", line 67, in _wrapper
    return bound_func(*args, **kwargs)
  File "/home/rnddev/.virtualenvs/vulcan/local/lib/python2.7/site-packages/django/utils/decorators.py", line 149, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "/home/rnddev/.virtualenvs/vulcan/local/lib/python2.7/site-packages/django/utils/decorators.py", line 63, in bound_func
    return func.__get__(self, type(self))(*args2, **kwargs2)
  File "/home/rnddev/.virtualenvs/vulcan/local/lib/python2.7/site-packages/django/utils/decorators.py", line 185, in inner
    return func(*args, **kwargs)
  File "/home/rnddev/.virtualenvs/vulcan/local/lib/python2.7/site-packages/django/contrib/admin/options.py", line 1448, in changeform_view
    if all_valid(formsets) and form_validated:
  File "/home/rnddev/.virtualenvs/vulcan/local/lib/python2.7/site-packages/django/forms/formsets.py", line 456, in all_valid
    if not formset.is_valid():
  File "/home/rnddev/.virtualenvs/vulcan/local/lib/python2.7/site-packages/django/forms/formsets.py", line 321, in is_valid
    self.errors
  File "/home/rnddev/.virtualenvs/vulcan/local/lib/python2.7/site-packages/django/forms/formsets.py", line 295, in errors
    self.full_clean()
  File "/home/rnddev/.virtualenvs/vulcan/local/lib/python2.7/site-packages/django/forms/formsets.py", line 343, in full_clean
    form = self.forms[i]
  File "/home/rnddev/.virtualenvs/vulcan/local/lib/python2.7/site-packages/django/utils/functional.py", line 35, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/home/rnddev/.virtualenvs/vulcan/local/lib/python2.7/site-packages/django/forms/formsets.py", line 144, in forms
    for i in range(self.total_form_count())]
  File "/home/rnddev/.virtualenvs/vulcan/local/lib/python2.7/site-packages/django/forms/models.py", line 896, in _construct_form
    form = super(BaseInlineFormSet, self)._construct_form(i, **kwargs)
  File "/home/rnddev/.virtualenvs/vulcan/local/lib/python2.7/site-packages/django/forms/models.py", line 593, in _construct_form
    pk = self.data[pk_key]
  File "/home/rnddev/.virtualenvs/vulcan/local/lib/python2.7/site-packages/django/utils/datastructures.py", line 85, in __getitem__
    raise MultiValueDictKeyError(repr(key))
MultiValueDictKeyError: "u'b_set-0-a_ptr'"

More info:
The error presents itself under both SQLite and PostgreSQL.
If the id of the parent A is the default AutoField, there's no error.
I specified InlineAdminForm because there's a relevant check there (method: needs_explicit_pk_field) that might be the issue.

Change History (11)

comment:1 Changed 3 years ago by Robin Anupol

Type: UncategorizedBug

comment:2 Changed 3 years ago by Vedran Karačić

Cc: vedran@… added
Owner: changed from nobody to Vedran Karačić
Status: newassigned

comment:3 Changed 3 years ago by Tim Graham

Easy pickings: unset
Triage Stage: UnreviewedAccepted

comment:4 Changed 3 years ago by Vedran Karačić

Cc: vedran@… removed
Owner: Vedran Karačić deleted
Status: assignednew

comment:5 Changed 3 years ago by Robin Anupol

As a workaround, on the app's admin.py, I have this prepended:

# admin.py
from django.contrib.admin.helpers import InlineAdminForm

 def needs_explicit_pk_field(self):
    # original definition goes here
    # place this conditional right before the end of the function
    if self.form._meta.model._meta.pk.auto_created:
        return True
    return False

InlineAdminForm.needs_explicit_pk_field = needs_explicit_pk_field

from django.contrib import admin
# the rest of admin.py goes here. 

This works for me but I'm not sure of any edge cases associated with the auto_created attribute.

comment:6 Changed 3 years ago by Marco Silva

added a test that reproduces the error, I think....

wip here
https://github.com/marco-silva0000/django/tree/27967-inline-uuid-breaks

comment:7 Changed 3 years ago by oyooyo

I wonder if this bug is somehow related to #27595
The description reads different, it is said to occur in both PostgreSQL and SQLite - but just like #27595, the issue seems to somehow be related to multi-table inheritance with UUIDFields as primary keys.

comment:8 Changed 3 years ago by Paulo

Owner: set to Paulo
Status: newassigned

comment:10 Changed 3 years ago by Tim Graham <timograham@…>

Resolution: fixed
Status: assignedclosed

In 9dc83c35:

Fixed #27967 -- Fixed KeyError in admin's inline form with inherited non-editable pk.

Thanks Robin Anupol for the initial report and workaround.

comment:11 Changed 3 years ago by Tim Graham <timograham@…>

In 927d9b51:

[1.11.x] Fixed #27967 -- Fixed KeyError in admin's inline form with inherited non-editable pk.

Thanks Robin Anupol for the initial report and workaround.

Backport of 9dc83c356d363c090f3351c908cad6f823aeb7bf from master

Note: See TracTickets for help on using tickets.
Back to Top