fields and base_fields - Django

2019-04-05 04:50发布

问题:

When creating a flatpage, I want the user to select a template from a predefined list. In order to keep the Flatpage model untouched, I prefer ChoiceField over ModelChoiceField (the latter provides the PK of the template, but I need the name for the template_name field):

class NewFlatpageForm(FlatpageForm):

    template_name = forms.ChoiceField(choices = [])
    def __init__(self, *args, **kwargs):
        self.base_fields['template_name'].choices = ProjectTemplate.objects.values_list('path', 'name')
        super(NewFlatpageForm, self).__init__(*args, **kwargs)

I override __init__ or Django populates choices at server start and does not update the list then.

I don't have any admin experience, but I did similar things using the fields attribute when not using admin. However in this case, I got an exception telling fields is not an attribute of the form. __dict__ showed me there's a base_fields attribute and using it works. So, why use base_fields here, and why is fields not present and finally am I doing something hacky?

回答1:

fields doesn't exist until after you've called super. So just swap the order of the lines, so that super comes first.



回答2:

A lesson from my own experience: modifying basefields means that your modifications stick around "forever" (until python exits). In your case, that's probably not a problem, as you are always using the same field name, and you are replacing its values with the assignment from ProjectTemplate...

In my case, I wanted completely different fields based on parameters in the constructor. Since my field names were usually different, each time I instantiated a form, I added new fields but didn't eliminate the ones from the last time.

By calling super early (as indicated here) and then making my dynamic changes to self.fields instead of self.basefields, I was able to eliminate the problem of an ever growing list of fields. It makes perfect sense now, but I wasn't familiar with all of the syntax details and was hacking through instead of trying to understand it first.



回答3:

In addition to Joe Germuska. If you truly need to change the form based on the request, you can use a deepcopy to make sure nothing is changed by reference:

def get_form(self, request, obj=None, **kwargs):
    form = super(ResourceAdmin, self).get_form(request, obj, **kwargs)
    form = copy.deepcopy(form)

    if obj:
        form.base_fields['email'] = EmailField(initial=obj.user.email)
    if not request.user.is_superuser:
        form.base_fields['user'].widget = HiddenInput(attrs={'class': 'hide_form_row'})

    return form