Opened 12 years ago
Last modified 12 years ago
#19710 new Bug
ModelAdmin exclude behaviour not consistent with ModelAdmin behaviour
Reported by: | Rafal Stozek | 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 by , 12 years ago
Triage Stage: | Unreviewed → Accepted |
---|
comment:2 by , 12 years ago
comment:3 by , 12 years ago
I would vote for a fix, as this is actually a bug. Fieldsets are from UI viewpoint and exclude is from logic point.
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.