#24502: Using non model fields in an admin ModelForm does not work if the field 
is
listed in fieldsets (and fields I think)
-------------------------------+--------------------
     Reporter:  bdauvergne     |      Owner:  nobody
         Type:  Uncategorized  |     Status:  new
    Component:  Uncategorized  |    Version:  1.7
     Severity:  Normal         |   Keywords:
 Triage Stage:  Unreviewed     |  Has patch:  0
Easy pickings:  0              |      UI/UX:  0
-------------------------------+--------------------
 I have such change form:

 {{{
  19 class UserAttributeFormMixin(object):
  20     def __init__(self, *args, **kwargs):
  21         super(UserAttributeFormMixin, self).__init__(*args, **kwargs)
  22         self.attributes = self.get_attributes()
  23         initial = {}
  24         if 'instance' in kwargs:
  25             content_type =
 ContentType.objects.get_for_model(self.instance)
  26             for av in models.AttributeValue.objects.filter(
  27                     content_type=content_type,
  28                     object_id=self.instance.pk):
  29                 initial[av.attribute.name] = av.to_python()
  30         for attribute in self.attributes:
  31             iv = initial.get(attribute.name)
  32             attribute.contribute_to_form(self, initial=iv)
  33

 class UserChangeForm(forms.UserAttributeFormMixin,
  11         AuthUserChangeForm):
  12
  13     class Meta(AuthUserChangeForm.Meta):
  14         model = get_user_model()
  15         fields = '__all__'
  16
 }}}

 Use by the following ModelAdmin:

 {{{
 161 class AuthenticUserAdmin(UserAdmin):
 162     fieldsets = (
 163         (None, {'fields': ('username', 'password')}),
 164         (_('Personal info'), {'fields': ('first_name', 'last_name',
 'email')}),
 165         (_('Permissions'), {'fields': ('is_active', 'is_staff',
 'is_superuser',
 166                                        'groups')}),
 167         (_('Important dates'), {'fields': ('last_login',
 'date_joined')}),
 168     )
 169     form = admin_forms.UserChangeForm
 170     add_form = admin_forms.UserCreationForm
 171     add_fieldsets = (
 172             (None, {
 173                 'classes': ('wide',),
 174                 'fields': ('username', 'first_name', 'last_name',
 'email', 'password1', 'password2')}
 175             ),
 176         )
 177     list_filter = UserAdmin.list_filter +
 (UserRealmListFilter,ExternalUserListFilter)
 178
 179     def get_fieldsets(self, request, obj=None):
 180         fieldsets = deepcopy(super(AuthenticUserAdmin,
 self).get_fieldsets(request, obj))
 181         if obj:
 182             if not request.user.is_superuser:
 183                 fieldsets[2][1]['fields'] = filter(lambda x: x !=
 184                         'is_superuser', fieldsets[2][1]['fields'])
 185             qs = models.Attribute.objects.all()
 186             insertion_idx = 2
 187         else:
 188             qs = models.Attribute.objects.filter(required=True)
 189             insertion_idx = 1
 190         if qs.exists():
 191             fieldsets = list(fieldsets)
 192             fieldsets.insert(insertion_idx,·
 193                     (_('Attributes'), {'fields': [at.name for at in
 qs]}))
 194         return fieldsets
 }}}

 I get the following traceback I did not get with Django 1.5 (I cannot say
 if it was already the case with Django 1.6 we skiped it):

 {{{

 /home/bdauvergne/.virtualenvs/authentic/local/lib/python2.7/site-
 packages/django/contrib/admin/options.py in changeform_view

                         'name': force_text(opts.verbose_name), 'key':
 escape(object_id)})

                 if request.method == 'POST' and "_saveasnew" in
 request.POST:

                     return self.add_view(request,
 form_url=reverse('admin:%s_%s_add' % (

                         opts.app_label, opts.model_name),

                         current_app=self.admin_site.name))

             ModelForm = self.get_form(request, obj)

     ...

             if request.method == 'POST':

                 form = ModelForm(request.POST, request.FILES,
 instance=obj)

                 if form.is_valid():

                     form_validated = True

                     new_object = self.save_form(request, form, change=not
 add)

                 else:

 ▶ Local vars
 /home/bdauvergne/.virtualenvs/authentic/local/lib/python2.7/site-
 packages/django/contrib/auth/admin.py in get_form

             """

             Use special form during user creation

             """

             defaults = {}

             if obj is None:

                 defaults['form'] = self.add_form

             defaults.update(kwargs)

             return super(UserAdmin, self).get_form(request, obj,
 **defaults)

     ...

         def get_urls(self):

             from django.conf.urls import patterns

             return patterns('',

                 (r'^(\d+)/password/$',

                  self.admin_site.admin_view(self.user_change_password))

 ▶ Local vars
 /home/bdauvergne/.virtualenvs/authentic/local/lib/python2.7/site-
 packages/django/contrib/admin/options.py in get_form

             if defaults['fields'] is None and not
 modelform_defines_fields(defaults['form']):

                 defaults['fields'] = forms.ALL_FIELDS

             try:

                 return modelform_factory(self.model, **defaults)

             except FieldError as e:

                 raise FieldError('%s. Check fields/fieldsets/exclude
 attributes of class %s.'

                                  % (e, self.__class__.__name__))

     ...

         def get_changelist(self, request, **kwargs):

             """

             Returns the ChangeList class for use on the changelist page.

             """

             from django.contrib.admin.views.main import ChangeList

 ▶ Local vars
 }}}

 What happens is that ModelAdmin is passing all fields defined to
 modelform_factory which use them to set the Meta.fields property of a new
 modelform class and it breaks with a new validation of corrects
 Meta.fields in django/forms/models.py l.294:

 {{{
  280
  281             fields = fields_for_model(opts.model, opts.fields,
 opts.exclude,
  282                                       opts.widgets,
 formfield_callback,
  283                                       opts.localized_fields,
 opts.labels,
  284                                       opts.help_texts,
 opts.error_messages)
  285
  286             # make sure opts.fields doesn't specify an invalid field
  287             none_model_fields = [k for k, v in six.iteritems(fields)
 if not v]
  288             missing_fields = (set(none_model_fields) -
  289                               set(new_class.declared_fields.keys()))
  290             if missing_fields:
  291                 message = 'Unknown field(s) (%s) specified for %s'
  292                 message = message % (', '.join(missing_fields),
  293                                      opts.model.__name__)
  294                 raise FieldError(message)
  295             # Override default model fields with any custom declared
 ones
  296             # (plus, include all the other declared fields).
  297             fields.update(new_class.declared_fields)
  298         else:
 }}}

 Don't know it it's better to allow non-model fields in Meta.fields or to
 fix the admin.

--
Ticket URL: <https://code.djangoproject.com/ticket/24502>
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 [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/053.c63768b722300aaea2c5fc24789ba55c%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to