#29637: KeyError on empty_form.fields in InlineAdminFormSet when user don't have 'add' permission for the child model -------------------------------------+------------------------------------- Reporter: Clément Mangin | Owner: nobody Type: Bug | Status: new Component: contrib.admin | Version: 2.1 Severity: Normal | Resolution: Keywords: admin, permission, | Triage Stage: inline | Unreviewed Has patch: 0 | Needs documentation: 0 Needs tests: 0 | Patch needs improvement: 0 Easy pickings: 0 | UI/UX: 0 -------------------------------------+------------------------------------- Description changed by Clément Mangin:
Old description: > The following use case works with Django 2.0 but breaks with Django 2.1: > * A Parent model and a Child model with a ForeignKey to the Parent model, > * A Parent admin form with an inline formset for adding and editing > Children, > * A user with change permission for the Parent and Child models, but no > add permission for the Child model, trying to edit a Parent object and > its children. > > The following error occurs: > {{{ > KeyError at /admin/demo/parent/1/change/ > 'name' > Request Method: GET > Request URL: /admin/demo/parent/1/change/ > Django Version: 2.1 > Exception Type: KeyError > Exception Value: > 'name' > Exception Location: .../python3.7/site- > packages/django/contrib/admin/helpers.py in fields, line 287 > Python Version: 3.7.0 > > Error during template rendering > In template .../python3.7/site- > packages/django/contrib/admin/templates/admin/edit_inline/tabular.html, > error at line 13 > > name > 3 data-inline-type="tabular" > 4 data-inline-formset="{{ > inline_admin_formset.inline_formset_data }}"> > 5 <div class="tabular inline-related {% if forloop.last %}last- > related{% endif %}"> > 6 {{ inline_admin_formset.formset.management_form }} > 7 <fieldset class="module {{ inline_admin_formset.classes }}"> > 8 <h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst > }}</h2> > 9 {{ inline_admin_formset.formset.non_form_errors }} > 10 <table> > 11 <thead><tr> > 12 <th class="original"></th> > 13 {% for field in inline_admin_formset.fields %} > 14 {% if not field.widget.is_hidden %} > 15 <th{% if field.required %} class="required"{% endif > %}>{{ field.label|capfirst }} > 16 {% if field.help_text %} <img src="{% static > "admin/img/icon-unknown.svg" %}" class="help help-tooltip" width="10" > height="10" alt="({{ field.help_text|striptags }})" title="{{ > field.help_text|striptags }}">{% endif %} > 17 </th> > 18 {% endif %} > 19 {% endfor %} > 20 {% if inline_admin_formset.formset.can_delete %}<th>{% trans > "Delete?" %}</th>{% endif %} > 21 </tr></thead> > 22 > 23 <tbody> > }}} > > Trace: > {{{ > Internal Server Error: /admin/demo/parent/1/change/ > Traceback (most recent call last): > File ".../python3.7/site-packages/django/core/handlers/exception.py", > line 34, in inner > response = get_response(request) > File ".../python3.7/site-packages/django/core/handlers/base.py", line > 156, in _get_response > response = self.process_exception_by_middleware(e, request) > File ".../python3.7/site-packages/django/core/handlers/base.py", line > 154, in _get_response > response = response.render() > File ".../python3.7/site-packages/django/template/response.py", line > 106, in render > self.content = self.rendered_content > File ".../python3.7/site-packages/django/template/response.py", line > 83, in rendered_content > content = template.render(context, self._request) > File ".../python3.7/site-packages/django/template/backends/django.py", > line 61, in render > return self.template.render(context) > File ".../python3.7/site-packages/django/template/base.py", line 171, > in render > return self._render(context) > File ".../python3.7/site-packages/django/template/base.py", line 163, > in _render > return self.nodelist.render(context) > File ".../python3.7/site-packages/django/template/base.py", line 937, > in render > bit = node.render_annotated(context) > File ".../python3.7/site-packages/django/template/base.py", line 904, > in render_annotated > return self.render(context) > File ".../python3.7/site-packages/django/template/loader_tags.py", line > 150, in render > return compiled_parent._render(context) > File ".../python3.7/site-packages/django/template/base.py", line 163, > in _render > return self.nodelist.render(context) > File ".../python3.7/site-packages/django/template/base.py", line 937, > in render > bit = node.render_annotated(context) > File ".../python3.7/site-packages/django/template/base.py", line 904, > in render_annotated > return self.render(context) > File ".../python3.7/site-packages/django/template/loader_tags.py", line > 150, in render > return compiled_parent._render(context) > File ".../python3.7/site-packages/django/template/base.py", line 163, > in _render > return self.nodelist.render(context) > File ".../python3.7/site-packages/django/template/base.py", line 937, > in render > bit = node.render_annotated(context) > File ".../python3.7/site-packages/django/template/base.py", line 904, > in render_annotated > return self.render(context) > File ".../python3.7/site-packages/django/template/loader_tags.py", line > 62, in render > result = block.nodelist.render(context) > File ".../python3.7/site-packages/django/template/base.py", line 937, > in render > bit = node.render_annotated(context) > File ".../python3.7/site-packages/django/template/base.py", line 904, > in render_annotated > return self.render(context) > File ".../python3.7/site-packages/django/template/loader_tags.py", line > 62, in render > result = block.nodelist.render(context) > File ".../python3.7/site-packages/django/template/base.py", line 937, > in render > bit = node.render_annotated(context) > File ".../python3.7/site-packages/django/template/base.py", line 904, > in render_annotated > return self.render(context) > File ".../python3.7/site-packages/django/template/defaulttags.py", line > 209, in render > nodelist.append(node.render_annotated(context)) > File ".../python3.7/site-packages/django/template/base.py", line 904, > in render_annotated > return self.render(context) > File ".../python3.7/site-packages/django/template/loader_tags.py", line > 188, in render > return template.render(context) > File ".../python3.7/site-packages/django/template/base.py", line 173, > in render > return self._render(context) > File ".../python3.7/site-packages/django/template/base.py", line 163, > in _render > return self.nodelist.render(context) > File ".../python3.7/site-packages/django/template/base.py", line 937, > in render > bit = node.render_annotated(context) > File ".../python3.7/site-packages/django/template/base.py", line 904, > in render_annotated > return self.render(context) > File ".../python3.7/site-packages/django/template/defaulttags.py", line > 165, in render > values = list(values) > File ".../python3.7/site-packages/django/contrib/admin/helpers.py", > line 287, in fields > form_field = empty_form.fields[field_name] > KeyError: 'name' > }}} > > GitHub project to see the difference in behaviour between Django 2.0 and > Django 2.1 on this use case, using `tox`: > https://github.com/clementmangin/django-21-error-demo New description: The following use case works with Django 2.0 but breaks with Django 2.1: * A Parent model and a Child model with a ForeignKey to the Parent model, * A Parent admin form with an inline formset for adding and editing Children, * A user with change permission for the Parent and Child models, but no add permission for the Child model, trying to edit a Parent object and its children. The following error occurs: {{{ KeyError at /admin/demo/parent/1/change/ 'name' Request Method: GET Request URL: /admin/demo/parent/1/change/ Django Version: 2.1 Exception Type: KeyError Exception Value: 'name' Exception Location: .../python3.7/site- packages/django/contrib/admin/helpers.py in fields, line 287 Python Version: 3.7.0 Error during template rendering In template .../python3.7/site- packages/django/contrib/admin/templates/admin/edit_inline/tabular.html, error at line 13 name 3 data-inline-type="tabular" 4 data-inline-formset="{{ inline_admin_formset.inline_formset_data }}"> 5 <div class="tabular inline-related {% if forloop.last %}last- related{% endif %}"> 6 {{ inline_admin_formset.formset.management_form }} 7 <fieldset class="module {{ inline_admin_formset.classes }}"> 8 <h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}</h2> 9 {{ inline_admin_formset.formset.non_form_errors }} 10 <table> 11 <thead><tr> 12 <th class="original"></th> 13 {% for field in inline_admin_formset.fields %} 14 {% if not field.widget.is_hidden %} 15 <th{% if field.required %} class="required"{% endif %}>{{ field.label|capfirst }} 16 {% if field.help_text %} <img src="{% static "admin/img/icon-unknown.svg" %}" class="help help-tooltip" width="10" height="10" alt="({{ field.help_text|striptags }})" title="{{ field.help_text|striptags }}">{% endif %} 17 </th> 18 {% endif %} 19 {% endfor %} 20 {% if inline_admin_formset.formset.can_delete %}<th>{% trans "Delete?" %}</th>{% endif %} 21 </tr></thead> 22 23 <tbody> }}} Trace: {{{ Internal Server Error: /admin/demo/parent/1/change/ Traceback (most recent call last): File ".../python3.7/site-packages/django/core/handlers/exception.py", line 34, in inner response = get_response(request) File ".../python3.7/site-packages/django/core/handlers/base.py", line 156, in _get_response response = self.process_exception_by_middleware(e, request) File ".../python3.7/site-packages/django/core/handlers/base.py", line 154, in _get_response response = response.render() File ".../python3.7/site-packages/django/template/response.py", line 106, in render self.content = self.rendered_content File ".../python3.7/site-packages/django/template/response.py", line 83, in rendered_content content = template.render(context, self._request) File ".../python3.7/site-packages/django/template/backends/django.py", line 61, in render return self.template.render(context) File ".../python3.7/site-packages/django/template/base.py", line 171, in render return self._render(context) File ".../python3.7/site-packages/django/template/base.py", line 163, in _render return self.nodelist.render(context) File ".../python3.7/site-packages/django/template/base.py", line 937, in render bit = node.render_annotated(context) File ".../python3.7/site-packages/django/template/base.py", line 904, in render_annotated return self.render(context) File ".../python3.7/site-packages/django/template/loader_tags.py", line 150, in render return compiled_parent._render(context) File ".../python3.7/site-packages/django/template/base.py", line 163, in _render return self.nodelist.render(context) File ".../python3.7/site-packages/django/template/base.py", line 937, in render bit = node.render_annotated(context) File ".../python3.7/site-packages/django/template/base.py", line 904, in render_annotated return self.render(context) File ".../python3.7/site-packages/django/template/loader_tags.py", line 150, in render return compiled_parent._render(context) File ".../python3.7/site-packages/django/template/base.py", line 163, in _render return self.nodelist.render(context) File ".../python3.7/site-packages/django/template/base.py", line 937, in render bit = node.render_annotated(context) File ".../python3.7/site-packages/django/template/base.py", line 904, in render_annotated return self.render(context) File ".../python3.7/site-packages/django/template/loader_tags.py", line 62, in render result = block.nodelist.render(context) File ".../python3.7/site-packages/django/template/base.py", line 937, in render bit = node.render_annotated(context) File ".../python3.7/site-packages/django/template/base.py", line 904, in render_annotated return self.render(context) File ".../python3.7/site-packages/django/template/loader_tags.py", line 62, in render result = block.nodelist.render(context) File ".../python3.7/site-packages/django/template/base.py", line 937, in render bit = node.render_annotated(context) File ".../python3.7/site-packages/django/template/base.py", line 904, in render_annotated return self.render(context) File ".../python3.7/site-packages/django/template/defaulttags.py", line 209, in render nodelist.append(node.render_annotated(context)) File ".../python3.7/site-packages/django/template/base.py", line 904, in render_annotated return self.render(context) File ".../python3.7/site-packages/django/template/loader_tags.py", line 188, in render return template.render(context) File ".../python3.7/site-packages/django/template/base.py", line 173, in render return self._render(context) File ".../python3.7/site-packages/django/template/base.py", line 163, in _render return self.nodelist.render(context) File ".../python3.7/site-packages/django/template/base.py", line 937, in render bit = node.render_annotated(context) File ".../python3.7/site-packages/django/template/base.py", line 904, in render_annotated return self.render(context) File ".../python3.7/site-packages/django/template/defaulttags.py", line 165, in render values = list(values) File ".../python3.7/site-packages/django/contrib/admin/helpers.py", line 287, in fields form_field = empty_form.fields[field_name] KeyError: 'name' }}} GitHub project to see the difference in behaviour between Django 2.0 and Django 2.1 on this use case, using `tox`: https://github.com/clementmangin/django-21-error-demo EDIT: potential origin of issue: https://github.com/django/django/commit/825f0beda804e48e9197fcf3b0d909f9f548aa47 #diff-3c42de3e53aba87b32c494f995a728dfR2023 -- -- Ticket URL: <https://code.djangoproject.com/ticket/29637#comment:1> Django <https://code.djangoproject.com/> The Web framework for perfectionists with deadlines. -- You received this message because you are subscribed to the Google Groups "Django updates" group. To unsubscribe from this group and stop receiving emails from it, send an email to django-updates+unsubscr...@googlegroups.com. To post to this group, send email to django-updates@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/django-updates/071.8fcb6a7cf3f2a88cd9f96bba2825007b%40djangoproject.com. For more options, visit https://groups.google.com/d/optout.