Opened 2 years ago

Last modified 2 years ago

#19710 new Bug

ModelAdmin exclude behaviour not consistent with ModelAdmin behaviour

Reported by: rafales Owned by: nobody
Component: contrib.admin Version: 1.4
Severity: Normal Keywords:
Cc: Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

A ModelAdmin like this:

from django.contrib.auth.admin import UserAdmin as UserAdminBase
from django.contrib.auth.models import Group
from django.contrib import admin
from .models import User


class UserAdmin(UserAdminBase):
    exclude = ('user_permissions', 'groups')
    def has_delete_permission(self, request, obj=None):
        # don't allow user removal
        return False


admin.site.register(User, UserAdmin)
admin.site.unregister(Group)

(Note: I'm using custom user model so I don't have to unregister old UserAdmin)

will result in a traceback when trying to visit user admin's change view:

Traceback:
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response
  140.                     response = response.render()
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/response.py" in render
  105.             self.content = self.rendered_content
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/response.py" in rendered_content
  82.         content = template.render(context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/base.py" in render
  140.             return self._render(context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/base.py" in _render
  134.         return self.nodelist.render(context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/base.py" in render
  830.                 bit = self.render_node(node, context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/debug.py" in render_node
  74.             return node.render(context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/loader_tags.py" in render
  124.         return compiled_parent._render(context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/base.py" in _render
  134.         return self.nodelist.render(context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/base.py" in render
  830.                 bit = self.render_node(node, context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/debug.py" in render_node
  74.             return node.render(context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/loader_tags.py" in render
  124.         return compiled_parent._render(context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/base.py" in _render
  134.         return self.nodelist.render(context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/base.py" in render
  830.                 bit = self.render_node(node, context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/debug.py" in render_node
  74.             return node.render(context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/loader_tags.py" in render
  63.             result = block.nodelist.render(context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/base.py" in render
  830.                 bit = self.render_node(node, context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/debug.py" in render_node
  74.             return node.render(context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/loader_tags.py" in render
  63.             result = block.nodelist.render(context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/base.py" in render
  830.                 bit = self.render_node(node, context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/debug.py" in render_node
  74.             return node.render(context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/defaulttags.py" in render
  188.                         nodelist.append(node.render(context))
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/loader_tags.py" in render
  156.         return self.render_template(self.template, context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/loader_tags.py" in render_template
  138.         output = template.render(context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/base.py" in render
  140.             return self._render(context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/base.py" in _render
  134.         return self.nodelist.render(context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/base.py" in render
  830.                 bit = self.render_node(node, context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/debug.py" in render_node
  74.             return node.render(context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/defaulttags.py" in render
  367.         return strip_spaces_between_tags(self.nodelist.render(context).strip())
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/base.py" in render
  830.                 bit = self.render_node(node, context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/debug.py" in render_node
  74.             return node.render(context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/defaulttags.py" in render
  188.                         nodelist.append(node.render(context))
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/defaulttags.py" in render
  284.                 return nodelist.render(context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/base.py" in render
  830.                 bit = self.render_node(node, context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/debug.py" in render_node
  74.             return node.render(context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/defaulttags.py" in render
  277.                     match = condition.eval(context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/defaulttags.py" in eval
  828.         return self.value.resolve(context, ignore_failures=True)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/base.py" in resolve
  578.                 obj = self.var.resolve(context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/base.py" in resolve
  728.             value = self._resolve_lookup(context)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/template/base.py" in _resolve_lookup
  779.                             current = current()
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/contrib/admin/helpers.py" in errors
  115.         return mark_safe('\n'.join([self.form[f].errors.as_ul() for f in self.fields if f not in self.readonly_fields]).strip('\n'))
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/forms/forms.py" in __getitem__
  111.             raise KeyError('Key %r not found in Form' % name)

Exception Type: KeyError at /admin/accounts/user/1/
Exception Value: u"Key 'groups' not found in Form"

It's because ModelForm and ModelAdmin helpers produce different set of fields when there are both fieldsets/fields and exclude attributes specified on ModelAdmin. This affects InlineModelAdmin too.

There are four ways to fix it:

1) Just inform user that it's impossible to use both fields/fieldsets and exclude
2) Remove fields which don't exist in form from formset before passing it to helpers.AdminForm or helpers.InlineAdminFormset
3) Just like 2), but do this in helpers.AdminForm and helpers.InlineAdminFormset init() method
4) Skip non-existent fields in method __iter__() of helpers.Fieldset and helpers.InlineFieldset (note: "field" variable in __iter__() can also be a tuple or a list).

Changing get_fieldsets() may be a bad idea because it's something that people override (UserAdmin does this for example).

Change History (3)

comment:1 Changed 2 years ago by carljm

  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset
  • Triage Stage changed from Unreviewed to Accepted

comment:2 Changed 2 years ago by lukeplant

I haven't looked at the other options in detail, but I'd vote for fix 1) - if the user has specified contradictory things (even implicitly via inheritance), we should refuse the temptation to guess.

comment:3 Changed 2 years ago by sandeepharlalka@…

I would vote for a fix, as this is actually a bug. Fieldsets are from UI viewpoint and exclude is from logic point.

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