[Solved]-Django forms: default values for bound forms

11👍

By slightly modifying Gonzalo’s solution, this is the right way:

class Form(forms.Form):
    name = forms.CharField(required=False, initial='Hello world')

    def clean_name(self):
        if not self['name'].html_name in self.data:
            return self.fields['name'].initial
        return self.cleaned_data['name']

If you need this, you may have a look at django-filter app. I have discovered it quite recently.

👤clime

5👍

initial isn’t really meant to be used to set default values for form fields.
Instead, it’s really more a placeholder utility when displaying forms to the user, and won’t work well if the field isn’t required (like in your example).

What you can do is define a clean_<fieldname> method that checks if there’s an empty value for that field and return the default:

class Form(forms.Form):
    name = forms.CharField(required=False, initial='Hello world')

    def clean_name(self):
        name = self.cleaned_data['name']
        if name is None:
            return self.fields['name'].initial
        return name

4👍

I use the following pattern for setting default values as initial values given for the form-

class InitialDefaultForm(forms.Form):
    def clean(self):
        cleaned_data = super(InitialDefaultForm, self).clean()
        # if data is not provided for some fields and those fields have an
        # initial value, then set the values to initial value
        for name in self.fields:
            if not self[name].html_name in self.data and self.fields[name].initial is not None:
                cleaned_data[name] = self.fields[name].initial
        return cleaned_data

This ensures that all fields which have an initial value and do not get values from user get populated by their initial value.

2👍

request.GET is a dictionary like object.

initial only works in case of unbound form.

Forms have an attribute named data. This attribute is provided as first positional argument or as a data keyword argument during form initialization.

Bound forms are those in which you provide some data as first argument to the form and unbound form has data attribute set as None.

Here in your initialization of form form=Form(request.GET), you are providing the first positional argument, so data attribute is being set on the form and it becomes a bound form. This happens even if request.GET is an empty dictionary. And since your form becomes a bound form so initial of name field has no effect on it.

So, In you GET request you should either do:

form = Form()

and your initial of name field would be honoured.

Or, if you want to read name from request.GET and if its there then want to use it instead of field’s initial then have following in your view.

name = request.GET.get(name)
form_level_initial = {}
if name:
    form_level_initial['name'] = name
form = Form(initial=form_level_initial)

2👍

Will this work:

initial_form_data = {'name': 'Hello World'}   #put all the initial for fields in this dict
initial_form_data.update(request.GET)  #any field available in request.GET will override that field in initial_form_data
form = Form(initial_form_data)
if form.is_valid():
    name = form.cleaned_data['name']

2👍

The proposed solutions either didn’t work for me or just seemed not very elegant. The documentation specifies that initial does not work for a bound form, which seems to be the original questioners (and my) use case:

This is why initial values are only displayed for unbound forms. For bound forms, the HTML output will use the bound data.

https://docs.djangoproject.com/en/1.10/ref/forms/fields/#initial

My solution is to see if the form should be bound or not:

initial = {'status': [Listing.ACTIVE], 'min_price': 123}     # Create default options

if request.method == 'GET':
    # create a form instance and populate it with data from the request:
    if len(request.GET):
        form = ListingSearchForm(request.GET)       # bind the form
    else:
        form = ListingSearchForm(initial=initial)   # if GET is empty, use default form

You could also use the other ways of initializing the form (mentioned above).

0👍

None of the answers actually does exactly what clime asked for. So here is my solution for the same problem:

class LeadsFiltersForm(forms.Form):
    TYPE_CHOICES = Lead.TYPES
    SITE_CHOICES = [(site.id, site.name) for site in Site.objects.all()]

    type = forms.MultipleChoiceField(
        choices=TYPE_CHOICES, widget=forms.CheckboxSelectMultiple(),
        required=False
    )
    site = forms.MultipleChoiceField(
        widget=forms.CheckboxSelectMultiple(), required=False,
        choices=SITE_CHOICES
    )
    date_from = forms.DateField(input_formats=['%m-%d-%Y',], required=False,
                                widget=forms.TextInput(attrs={'placeholder': 'Date From'}),
                                initial=timezone.now() - datetime.timedelta(days=30))
    date_to = forms.DateField(input_formats=['%m-%d-%Y',], required=False,
                                widget=forms.TextInput(attrs={'placeholder': 'Date To'}))

    defaults = {
        'type': [val[0] for val in TYPE_CHOICES],
        'site': [val[0] for val in SITE_CHOICES],
        'date_from': (timezone.now() - datetime.timedelta(days=30)).strftime('%m-%d-%Y'),
        'date_to': timezone.now().strftime('%m-%d-%Y')
    }

    def __init__(self, data, *args, **kwargs):
        super(LeadsFiltersForm, self).__init__(data, *args, **kwargs)
        self.data = self.defaults.copy()
        for key, val in data.iteritems():
            if not data.get(key):
                continue
            field = self.fields.get(key)
            if field and getattr(field.widget, 'allow_multiple_selected', False):
                self.data[key] = data.getlist(key)
            else:
                self.data[key] = data.get(key)

Leave a comment