#8719 closed (fixed)
KeyError on save of model with inline-edited ForeignKey with non-standard primary key
Reported by: | jonloyens | Owned by: | nobody |
---|---|---|---|
Component: | contrib.admin | Version: | dev |
Severity: | Keywords: | ||
Cc: | Triage Stage: | Unreviewed | |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
When editing a ForeignKey relationship inline in admin, if the model representing the foreign key has a non-standard primary key, a KeyError will be thrown when editing an existing model.
Consider the following models and admin classes:
class ModelA( models.Model ) some_data = models.CharField(max_length=255, blank=True) class ModelB( models.Model ): mypk_id = models.AutoField(primary_key=True) modela = models.ForeignKey(ModelA) more_data = models.CharField(max_length=255, blank=True) class ModelBAdmin( admin.StackedInline ): model = ModelB class ModelAAdmin( admin.ModelAdmin ): inlines = [ModelBAdmin]
When attempting to save an existing ModelA in admin with an existing ModelB. Admin will throw a KeyError due to there not being a 'mypk_id' in the cleaned_data of the FormSet here:
Environment: Request Method: POST Request URL: http://localhost:8000/admin/auth/user/12922/ Django Version: 1.0-beta_2-SVN-8706 Python Version: 2.5.1 Installed Applications: ['django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'mc_billing_site.billing', 'django.contrib.admin'] Installed Middleware: ('django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.middleware.doc.XViewMiddleware') Traceback: File "/Library/Python/2.5/site-packages/django/core/handlers/base.py" in get_response 86. response = callback(request, *callback_args, **callback_kwargs) File "/Library/Python/2.5/site-packages/django/contrib/admin/sites.py" in root 173. return self.model_page(request, *url.split('/', 2)) File "/Library/Python/2.5/site-packages/django/views/decorators/cache.py" in _wrapped_view_func 44. response = view_func(request, *args, **kwargs) File "/Library/Python/2.5/site-packages/django/contrib/admin/sites.py" in model_page 192. return admin_obj(request, rest_of_url) File "/Library/Python/2.5/site-packages/django/contrib/auth/admin.py" in __call__ 42. return super(UserAdmin, self).__call__(request, url) File "/Library/Python/2.5/site-packages/django/contrib/admin/options.py" in __call__ 196. return self.change_view(request, unquote(url)) File "/Library/Python/2.5/site-packages/django/db/transaction.py" in _commit_on_success 238. res = func(*args, **kw) File "/Library/Python/2.5/site-packages/django/contrib/admin/options.py" in change_view 588. self.save_formset(request, form, formset, change=True) File "/Library/Python/2.5/site-packages/django/contrib/admin/options.py" in save_formset 378. formset.save() File "/Library/Python/2.5/site-packages/django/forms/models.py" in save 280. return self.save_existing_objects(commit) + self.save_new_objects(commit) File "/Library/Python/2.5/site-packages/django/forms/models.py" in save_existing_objects 294. obj = existing_objects[form.cleaned_data[self.model._meta.pk.attname]] Exception Type: KeyError at /admin/testapp/modela/12922/ Exception Value: 'mypk_id'
I am absolutely conviced this is related to this other ticket: 8695 but want to make sure this gets tested as well.
Attachments (2)
Change History (10)
comment:1 by , 16 years ago
comment:2 by , 16 years ago
milestone: | → 1.0 |
---|
comment:3 by , 16 years ago
I've uploaded a patch that should fix this whole mess, but attached here to see if it fixes this case too. I need to clean it up and get things in the right place. Let me know if it works.
comment:4 by , 16 years ago
Hi Brian, tried it for this case and it's got a problem. I can't even get to the change page for a model with explicitly specified PK fields. I get an AttributeError 'CluesFormFormSet' object has no attribute '_pk_field'
, traceback is:
Environment: Request Method: GET Request URL: http://lbox:8000/admin/crossword/entries/125408/ Django Version: 1.0-beta_2-SVN-8752 Python Version: 2.5.1 Installed Applications: ['django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.admin', 'django.contrib.admindocs', 'django.contrib.sites', 'django.contrib.humanize', 'xword.crossword'] Installed Middleware: ('django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.middleware.doc.XViewMiddleware') Traceback: File "/home/kmt/tmp/django/trunk/django/core/handlers/base.py" in get_response 86. response = callback(request, *callback_args, **callback_kwargs) File "/home/kmt/tmp/django/trunk/django/contrib/admin/sites.py" in root 173. return self.model_page(request, *url.split('/', 2)) File "/home/kmt/tmp/django/trunk/django/views/decorators/cache.py" in _wrapped_view_func 44. response = view_func(request, *args, **kwargs) File "/home/kmt/tmp/django/trunk/django/contrib/admin/sites.py" in model_page 192. return admin_obj(request, rest_of_url) File "/home/kmt/tmp/django/trunk/django/contrib/admin/options.py" in __call__ 196. return self.change_view(request, unquote(url)) File "/home/kmt/tmp/django/trunk/django/db/transaction.py" in _commit_on_success 238. res = func(*args, **kw) File "/home/kmt/tmp/django/trunk/django/contrib/admin/options.py" in change_view 596. formset = FormSet(instance=obj) File "/home/kmt/tmp/django/trunk/django/forms/models.py" in __init__ 362. super(BaseInlineFormSet, self).__init__(data, files, prefix=prefix or self.rel_name) File "/home/kmt/tmp/django/trunk/django/forms/models.py" in __init__ 256. super(BaseModelFormSet, self).__init__(**defaults) File "/home/kmt/tmp/django/trunk/django/forms/formsets.py" in __init__ 67. self._construct_forms() File "/home/kmt/tmp/django/trunk/django/forms/models.py" in _construct_forms 368. super(BaseInlineFormSet, self)._construct_forms() File "/home/kmt/tmp/django/trunk/django/forms/formsets.py" in _construct_forms 76. self.forms.append(self._construct_form(i)) File "/home/kmt/tmp/django/trunk/django/forms/formsets.py" in _construct_form 96. self.add_fields(form, i) File "/home/kmt/tmp/django/trunk/django/forms/models.py" in add_fields 389. if isinstance(self._pk_field, OneToOneField): Exception Type: AttributeError at /admin/crossword/entries/125408/ Exception Value: 'CluesFormFormSet' object has no attribute '_pk_field'
My Clues model has primary key defined like so:
class Clues(models.Model): ID = models.AutoField(primary_key=True) etc...
comment:5 by , 16 years ago
Ah, ok, thanks Karen. The patch is improved and I added a test case for this ticket. Can you double check it?
comment:6 by , 16 years ago
Change page still won't display, it's back to KeyError, but during rendering of the form vs. attempt to save it:
Environment: Request Method: GET Request URL: http://lbox:8000/admin/crossword/entries/33616/ Django Version: 1.0-beta_2-SVN-8752 Python Version: 2.5.1 Installed Applications: ['django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.admin', 'django.contrib.admindocs', 'django.contrib.sites', 'django.contrib.humanize', 'xword.crossword'] Installed Middleware: ('django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.middleware.doc.XViewMiddleware') Template error: In template /home/kmt/tmp/django/trunk/django/contrib/admin/templates/admin/edit_inline/tabular.html, error at line 18 Caught an exception while rendering: "Key 'ID' not found in Form" 8 : <table> 9 : <thead><tr> 10 : {% for field in inline_admin_formset.fields %} 11 : {% if not field.is_hidden %} 12 : <th {% if forloop.first %}colspan="2"{% endif %}>{{ field.label|capfirst|escape }}</th> 13 : {% endif %} 14 : {% endfor %} 15 : {% if inline_admin_formset.formset.can_delete %}<th>{% trans "Delete?" %}</th>{% endif %} 16 : </tr></thead> 17 : 18 : {% for inline_admin_form in inline_admin_formset %} 19 : 20 : <tr class="{% cycle row1,row2 %} {% if inline_admin_form.original or inline_admin_form.show_url %}has_original{% endif %}"> 21 : 22 : <td class="original"> 23 : {% if inline_admin_form.original or inline_admin_form.show_url %}<p> 24 : {% if inline_admin_form.original %} {{ inline_admin_form.original }}{% endif %} 25 : {% if inline_admin_form.show_url %}<a href="../../../r/{{ inline_admin_form.original.content_type_id }}/{{ inline_admin_form.original.id }}/">{% trans "View on site" %}</a>{% endif %} 26 : </p>{% endif %} 27 : {{ inline_admin_form.pk_field.field }} 28 : {% spaceless %} Traceback: File "/home/kmt/tmp/django/trunk/django/core/handlers/base.py" in get_response 86. response = callback(request, *callback_args, **callback_kwargs) File "/home/kmt/tmp/django/trunk/django/contrib/admin/sites.py" in root 173. return self.model_page(request, *url.split('/', 2)) File "/home/kmt/tmp/django/trunk/django/views/decorators/cache.py" in _wrapped_view_func 44. response = view_func(request, *args, **kwargs) File "/home/kmt/tmp/django/trunk/django/contrib/admin/sites.py" in model_page 192. return admin_obj(request, rest_of_url) File "/home/kmt/tmp/django/trunk/django/contrib/admin/options.py" in __call__ 196. return self.change_view(request, unquote(url)) File "/home/kmt/tmp/django/trunk/django/db/transaction.py" in _commit_on_success 238. res = func(*args, **kw) File "/home/kmt/tmp/django/trunk/django/contrib/admin/options.py" in change_view 622. return self.render_change_form(request, context, change=True, obj=obj) File "/home/kmt/tmp/django/trunk/django/contrib/admin/options.py" in render_change_form 404. ], context, context_instance=template.RequestContext(request)) File "/home/kmt/tmp/django/trunk/django/shortcuts/__init__.py" in render_to_response 18. return HttpResponse(loader.render_to_string(*args, **kwargs), **httpresponse_kwargs) File "/home/kmt/tmp/django/trunk/django/template/loader.py" in render_to_string 107. return t.render(context_instance) File "/home/kmt/tmp/django/trunk/django/template/__init__.py" in render 176. return self.nodelist.render(context) File "/home/kmt/tmp/django/trunk/django/template/__init__.py" in render 756. bits.append(self.render_node(node, context)) File "/home/kmt/tmp/django/trunk/django/template/debug.py" in render_node 71. result = node.render(context) File "/home/kmt/tmp/django/trunk/django/template/loader_tags.py" in render 97. return compiled_parent.render(context) File "/home/kmt/tmp/django/trunk/django/template/__init__.py" in render 176. return self.nodelist.render(context) File "/home/kmt/tmp/django/trunk/django/template/__init__.py" in render 756. bits.append(self.render_node(node, context)) File "/home/kmt/tmp/django/trunk/django/template/debug.py" in render_node 71. result = node.render(context) File "/home/kmt/tmp/django/trunk/django/template/loader_tags.py" in render 97. return compiled_parent.render(context) File "/home/kmt/tmp/django/trunk/django/template/__init__.py" in render 176. return self.nodelist.render(context) File "/home/kmt/tmp/django/trunk/django/template/__init__.py" in render 756. bits.append(self.render_node(node, context)) File "/home/kmt/tmp/django/trunk/django/template/debug.py" in render_node 71. result = node.render(context) File "/home/kmt/tmp/django/trunk/django/template/loader_tags.py" in render 24. result = self.nodelist.render(context) File "/home/kmt/tmp/django/trunk/django/template/__init__.py" in render 756. bits.append(self.render_node(node, context)) File "/home/kmt/tmp/django/trunk/django/template/debug.py" in render_node 71. result = node.render(context) File "/home/kmt/tmp/django/trunk/django/template/defaulttags.py" in render 148. nodelist.append(node.render(context)) File "/home/kmt/tmp/django/trunk/django/template/loader_tags.py" in render 123. return t.render(context) File "/home/kmt/tmp/django/trunk/django/template/__init__.py" in render 176. return self.nodelist.render(context) File "/home/kmt/tmp/django/trunk/django/template/__init__.py" in render 756. bits.append(self.render_node(node, context)) File "/home/kmt/tmp/django/trunk/django/template/debug.py" in render_node 81. raise wrapped Exception Type: TemplateSyntaxError at /admin/crossword/entries/33616/ Exception Value: Caught an exception while rendering: "Key 'ID' not found in Form" Original Traceback (most recent call last): File "/home/kmt/tmp/django/trunk/django/template/debug.py", line 71, in render_node result = node.render(context) File "/home/kmt/tmp/django/trunk/django/template/defaulttags.py", line 148, in render nodelist.append(node.render(context)) File "/home/kmt/tmp/django/trunk/django/template/debug.py", line 87, in render output = force_unicode(self.filter_expression.resolve(context)) File "/home/kmt/tmp/django/trunk/django/template/__init__.py", line 523, in resolve obj = self.var.resolve(context) File "/home/kmt/tmp/django/trunk/django/template/__init__.py", line 664, in resolve value = self._resolve_lookup(context) File "/home/kmt/tmp/django/trunk/django/template/__init__.py", line 699, in _resolve_lookup current = current() File "/home/kmt/tmp/django/trunk/django/contrib/admin/helpers.py", line 129, in pk_field return AdminField(self.form, self.formset._pk_field.name, False) File "/home/kmt/tmp/django/trunk/django/contrib/admin/helpers.py", line 72, in __init__ self.field = form[field] # A django.forms.BoundField instance File "/home/kmt/tmp/django/trunk/django/forms/forms.py", line 105, in __getitem__ raise KeyError('Key %r not found in Form' % name) KeyError: "Key 'ID' not found in Form"
Interesting. This case I know did once work, because all my models have non-standard naming and I was once able to save models with inline-edited objects. I've isolated when this failure was introduced to [8528]. Specifically I'm sure it's the change from
if self.model._meta.has_auto_field:
toif self.model._meta.pk.auto_created:
inadd_fields
. For the old way non-standard-named PK fields were getting added, now they are not.It is quite similar to #8695 in that the problem is the PK value is not added to the form data. However, the #8695 case was not working before [8528] either. In that case the PK field is not an auto field at all.
It seems
add_fields
needs to consider more cases where the PK field needs to be hidden in the form.[Oddly enough the #8694 was also introduced by [8528], however it is a different part of [8528] that causes the problem in #8694.)