Reputation: 8885
With this form:
class Form(forms.Form):
name = forms.CharField(required=False, initial='Hello world')
If I do something like this in the view:
form = Form(request.GET)
if form.is_valid():
name = form.cleaned_data['name']
Then initial value of name is lost even if request.GET does not contain name
as key. Is there any workaround? I would like initial values work to bound forms as "default values".
Upvotes: 11
Views: 16789
Reputation: 16010
My approach to the problem:
class SomeForm(forms.Form):
# ...
def __init__(self, data, *args, **kwargs):
# get a mutable version of data
super().__init__(data.copy(), *args, **kwargs)
for name, field in self.fields.items():
if field.initial:
self.data.setdefault(name, field.initial)
Upvotes: 0
Reputation: 1426
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).
Upvotes: 2
Reputation: 2793
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)
Upvotes: 0
Reputation: 8885
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.
Upvotes: 12
Reputation: 7246
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.
Upvotes: 4
Reputation: 15211
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']
Upvotes: 2
Reputation: 4269
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
Upvotes: 5
Reputation: 15211
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)
Upvotes: 2