Opened 13 years ago
Last modified 13 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 , 13 years ago
| Triage Stage: | Unreviewed → Accepted |
|---|
comment:2 by , 13 years ago
comment:3 by , 13 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.