Setting the selected value on a Django forms.ChoiceField - django

Here is the field declaration in a form:
max_number = forms.ChoiceField(widget = forms.Select(),
choices = ([('1','1'), ('2','2'),('3','3'), ]), initial='3', required = True,)
I would like to set the initial value to be 3 and this doesn't seem to work. I have played about with the param, quotes/no quotes, etc... but no change.
Could anyone give me a definitive answer if it is possible? And/or the necessary tweak in my code snippet?
I am using Django 1.0

Try setting the initial value when you instantiate the form:
form = MyForm(initial={'max_number': '3'})

This doesn't touch on the immediate question at hand, but this Q/A comes up for searches related to trying to assign the selected value to a ChoiceField.
If you have already called super().__init__ in your Form class, you should update the form.initial dictionary, not the field.initial property. If you study form.initial (e.g. print self.initial after the call to super().__init__), it will contain values for all the fields. Having a value of None in that dict will override the field.initial value.
e.g.
class MyForm(forms.Form):
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
# assign a (computed, I assume) default value to the choice field
self.initial['choices_field_name'] = 'default value'
# you should NOT do this:
self.fields['choices_field_name'].initial = 'default value'

You can also do the following. in your form class def:
max_number = forms.ChoiceField(widget = forms.Select(),
choices = ([('1','1'), ('2','2'),('3','3'), ]), initial='3', required = True,)
then when calling the form in your view you can dynamically set both initial choices and choice list.
yourFormInstance = YourFormClass()
yourFormInstance.fields['max_number'].choices = [(1,1),(2,2),(3,3)]
yourFormInstance.fields['max_number'].initial = [1]
Note: the initial values has to be a list and the choices has to be 2-tuples, in my example above i have a list of 2-tuples. Hope this helps.

I ran into this problem as well, and figured out that the problem is in the browser. When you refresh the browser is re-populating the form with the same values as before, ignoring the checked field. If you view source, you'll see the checked value is correct. Or put your cursor in your browser's URL field and hit enter. That will re-load the form from scratch.

Both Tom and Burton's answers work for me eventually, but I had a little trouble figuring out how to apply them to a ModelChoiceField.
The only trick to it is that the choices are stored as tuples of (<model's ID>, <model's unicode repr>), so if you want to set the initial model selection, you pass the model's ID as the initial value, not the object itself or it's name or anything else. Then it's as simple as:
form = EmployeeForm(initial={'manager': manager_employee_id})
Alternatively the initial argument can be ignored in place of an extra line with:
form.fields['manager'].initial = manager_employee_id

Dave - any luck finding a solution to the browser problem? Is there a way to force a refresh?
As for the original problem, try the following when initializing the form:
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.base_fields['MyChoiceField'].initial = initial_value

To be sure I need to see how you're rendering the form. The initial value is only used in a unbound form, if it's bound and a value for that field is not included nothing will be selected.

Related

Passing a variable defined in previous form to another form

So I have this flask app I'm making and I need some help with a variable access.
Most of the time, when you define a form in flask, you'll do the following :
class MyForm(Form):
my_field = StringField('I'm a field')
my_submit = SubmitField('Go!')
And when the time comes where you need the form, you'll declare an instance of that class with form = MyForm()
Up to here, it's all good, However :
If you want say, a SelectField (Dropdown) where the choices depend on the answers of a previous form, you need to be able to give the new form those choices. This is what I'm trying to achieve, but I can't get a variable to keep its contents.
Here is my Form code (Above the page code):
class DataMappingForm(Form):
dm_choices = #I need this array !
DMpatient_id = SelectField(u'Select Patient ID Column',
choices=dm_choices, validators=[Required()])
...
Here is my Page code :
#app.route('/upload', methods=['GET','POST'])
def upload():
uform = SomeOtherForm()
if uform.is_submitted() and uform.data['Usubmit']:
#Do stuff from previous form
# and declare array_of_choices
dmform = DataMappingForm() #Needs array_of_choices to work
...
What I've tried so far :
session['dm_choices'] gives me a working outside of request context error
global variables, get reset for some reason
overloading the __init__ of Form by adding the array but i can't access it in the parts above the __init__ function.
I should mention, this all needs to be on the same page.
Is there a way to pass this array_of_choices to my DataMappingForm class ?
EDIT This is what it looked like when I trid the __init__ overload:
class DataMappingForm(Form):
def __init__(self, dm_choices, *args, **kwargs):
self.dm_choices = dm_choices
Form.__init__(self, *args, **kwargs)
DMpatient_id = SelectField(u'Select Patient ID Column',
choices=dm_choices, validators=[Required()])
#I've tried putting it above or below, I get 'dm_choices is not defined'
I've Got it ! Thanks to #synonym for pointing me in the right direction with your last link.
All you need to do is declare a function in which the class is defined. You then pass the variable to the function, and it will be accessible within the class.
Finally, make the function return the form object.
Example :
def makeMyForm(myArray):
def class MyForm(Form):
my_select_field = SelectField(u'I'm a select field', choices=myArray)
my_submit = SubmitField(u'Go!')
return MyForm()
And to make the form, you use :
form = makeMyForm(theArrayYouWant)
And VoilĂ  !
Note : As I've had the problem before, I'll mention that the Array is composed of tuples :
myArray = [('value','What you see'),('value2','What you see again')]
If you want to dynamically change the choices of a SelectField the following should work:
class DataMappingForm(Form):
def __init__(self, choices)
self.DMpatient_id.choices = choices
DMpatient_id = SelectField(u'Select Patient ID Column') #note that choices is absent
If you want fully dynamic fields you can create the class dynamically in a function. From the WTForms Documentation:
def my_view():
class F(MyBaseForm):
pass
F.username = StringField('username')
for name in iterate_some_model_dynamically():
setattr(F, name, StringField(name.title()))
form = F(request.POST, ...)
# do view stuff
In that case you can customize the form as much as you want. Of course in the case you only want to customize the choices the first approach should be enough.

How to dynamically update the 'initial' value in a Form ChoiceField

I have a Form with the following field:
image_choices = []
images = forms.ChoiceField(label=_("Images"), choices=image_choices, initial="")
I need to be able to update the value of the 'initial' attribute, after I learn what that value should be.
Currently, I have this assignment done within the __init__:
def __init__(self, request, image_choices=image_choices,
flavor_choices=flavor_choices, args, *kwargs):
super(UpdateWorkload, self).__init__(request, args, *kwargs)
selected_image = selected_workload['image']
self.fields['images'].initial = selected_image
I do not get any errors, and, when printed, the value is there,
but, in the actual form on the screen,
I still get my default list, and no specific items are selected, as per self.fields['images'].initial = str(selected_image)
How can I fix that?
After all, using this approach:
self.fields['images'].initial = selected_image
self.fields['flavors'].initial = selected_flavor
is working. The only thing I did differently was changing my backend from django restframework to tastypie

Django: How can I set initial values to formset's empty_form?

I just want to know how can I set initial values to empty_form.
I do create the Inlines with initial values for extra forms without problem, but, when user clicks to Add button, the fields I expect it have the initial values show up empty, and I hope it have the same initial values than extra forms.
How could I make the empty_form to be filled with initial data?
Thanks in advance.
Django doesn't really provide a way to set initial values for empty forms. I've found a couple ways to work around this:
Set the field values dynamically in javascript.
Overwrite the empty_form property for your formset.
example:
formset = formset_factory(MyClass, **kwargs)
empty = formset.empty_form
# empty is a form instance, so you can do whatever you want to it
my_empty_form_init(empty_form)
formset.empty_form = empty_form
I had a similar problem and what finally worked for me was using Django Dynamic Formset. What DDF does is instead of using the empty form to create the new formset, it uses one of the extra_forms as a template. The default behavior is to clear all field values from the extra_form before inserting the HTML to the DOM, but you can use the keepFieldValues setting to specify the ones you want to keep.
In my case I wanted to keep all hidden field values:
$(function() {
$('#myForm_table tbody tr').formset({
keepFieldValues: 'input:hidden',
}
});
});
Of course you can bypass Django Dynamic Formsets and implement your own add/delete code with Javascript if you prefer.
Accepted answer didn't work for me, hopefully this will help someone in the future, this is my solution:
Create a new class based on BaseInlineFormSet
Override empty_form
Create a FormSet with inlineformset_factory(formset=YourBaseInlineFormSet)
Create a formset instance and pass parameters to initial on the formset instance
Add the field on the HTML as usual
I used BaseInlineFormSet, but probably will work with other types of FormSet
verification is the name of the field for my example.
forms.py
class YourBaseInlineFormSet(forms.BaseInlineFormSet):
#property
def empty_form(self): # This is almost the same as Django 3.1 code
form = self.form(
auto_id=self.auto_id,
prefix=self.add_prefix("__prefix__"),
empty_permitted=True,
use_required_attribute=False,
initial={"verification": self.initial_extra[0]["verification"]}, # This is the extra parameter
**self.get_form_kwargs(None),
)
self.add_fields(form, None)
return form
YourFormSet = forms.inlineformset_factory(
SomeObject,
SomeRelatedObject,
fields="__all__",
widgets={"verification": forms.HiddenInput},
formset=YourBaseInlineFormSet,
)
views.py
from .forms import YourFormSet
def your_view(request):
formset = YourFormSet(
data=request.POST or None,
instance=object,
queryset=object.related_objects.all()
initial=[{"verification": verification} for a in range(FormSet().total_form_count())],
)
return render(request, template, context={'formset': formset})
template.html
<div id="empty_form" style="display:none">
{{ formset.empty_form }}
</div>
Working on Django 3.1
There is at least one way to do this: Specify the default value on your model Field.
Of course, this may have side effects, depending on your implementation of the model.
As #jkk-jonah mentioned, BaseFormSet does not provide a way to set initial values in the empty_form. However, a small change can provide a simple solution.
The following provides a way to supply the FormSet instance with empty initial values without disrupting its base behavior.
from django.forms.formsets import BaseFormSet
class FormSetWithDefaultEmptyFormInitials(BaseFormSet):
"""This formset enables you to set the initial values in ``empty_form``.
Usage: ``formset_factory(..., formset=FormSetWithDefaultEmptyFormInitials)``
"""
def __init__(self, *args, **kwargs):
if 'empty_initial' in kwargs:
self._empty_initial = kwargs.pop('empty_initial')
super().__init__(*args, **kwargs)
def get_form_kwargs(self, index):
"""Augmented to return the empty initial data
when the index is ``None``,
which is the case when creating ``empty_form``.
"""
if index is None:
kwargs = self.form_kwargs.copy()
if self._empty_initial:
# Assign the initial value passed to the Form class.
kwargs['initial'] = self._empty_initial
else:
kwargs = super().get_form_kwargs(index)
return kwargs
Then to use this you'd do something like:
NonEmptyFormSet = formset_factory(
BringYourOwnForm,
min_num=1,
extra=1,
formset=FormSetWithDefaultEmptyFormInitials,
)
# Let's say your form has name and address fields...
empty_form_initial_values = {'name': 'default name', 'address': 'default address'}
formset = NonEmptyFormSet(empty_initial=empty_form_initial_values)
asset formset.empty_form.initial == empty_form_initial_values
In my implementation empty_form is used to provide a template for frontend javascript to add additional forms to the formset. Thus, this allows me to set the initial values for that all of the forms in that formset.
Note, this does not take the place of initial values to the minimum number of forms within the formset (e.g. formset_factory(min_num=2, ...)). Therefore, it is necessary to assign those through the standard initial keyword argument.
Tested with Django 3.2.
See also the standard implementation of get_form_kwargs.
This partially extends the answer given by #RobertPro. Or at least, I used their answer as the stepping stone to my own solution.

Set default value for dynamic choice field

I have a form that asks the user to enter in their zip code. Once they do it sends them to another form where there is a field called 'pickup_date'. This gets the value of the zip from the previous field and gets all of the available pickup_dates that match that zip code into a ChoiceField. I set all of this within the init of the model form.
def __init__(self,*args,**kwargs):
super(ExternalDonateForm,self).__init__(*args,**kwargs)
if kwargs:
zip = kwargs['initial']['zip']
self.fields['pickup_date'] = forms.ChoiceField(choices = self.get_dates(zip))
elif self.errors:
zip = self.data['zip']
self.fields['pickup_date'] = forms.ChoiceField(choices = self.get_dates(zip))
The problem I have is when there are other errors on the form. I use the elif self.errors to regenerate the possible choices but it doesn't default to the original selected option. It goes back and defaults to the first choice. How can I make it so it's default option on form errors is what was originally posted?
Change self.fields['pickup_date'] to self.fields['pickup_date'].initial and see if that helps.
I got it to work after playing around for a while. Above, I was setting all the dynamic choices with a get_dates() function that returned a tuple. Instead of doing that I returned a field object like this using a customized ModelChoiceField instead of a regular ChoiceField....
class MyModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return obj.date.strftime('%a %b %d, %Y')
Dates function
def get_dates(self,zip):
routes = Route.objects.filter(zip=zip).values_list('route',flat=True)
pickups = self.MyModelChoiceField(queryset = PickupSchedule.objects.filter(
current_count__lt=F('specials'),
route__in=routes,
).order_by('date')
)
if not pickups:
pickups = (('----','No Pickups Available At This Time'),)
return pickups
in the init i set the value for self.fields['pickup_date'] like so..
self.fields['pickup_date'] = self.get_dates(zip)

Help understanding a Django view

I am trying to follow the code listed on https://github.com/alex/django-ajax-validation/blob/master/ajax_validation/views.py
I have been able to understand a small chunk of it. I have added comments stating my understanding of what is happening.
I would really appreciate some assistance on questions I listed in comments next to the lines I couldn't quite follow.
def validate(request, *args, **kwargs):
# I thing it is some sort of initializations but I cannot really understand what's happening
form_class = kwargs.pop('form_class')
defaults = {
'data': request.POST
}
extra_args_func = kwargs.pop('callback', lambda request, *args, **kwargs: {})
kwargs = extra_args_func(request, *args, **kwargs)
defaults.update(kwargs)
form = form_class(**defaults)
if form.is_valid(): #straightforward, if there is no error then the form is valid
data = {
'valid': True,
}
else:
# if we're dealing with a FormSet then walk over .forms to populate errors and formfields
if isinstance(form, BaseFormSet): #I cannot really understand what is BaseFromSet
errors = {}
formfields = {}
for f in form.forms: # I am guessing that this is for when there are multiple form submitted for validation
for field in f.fields.keys(): # I think he is looping over all fields and checking for error. what does add_prefix () return? and what is formfields[]?
formfields[f.add_prefix(field)] = f[field]
for field, error in f.errors.iteritems():
errors[f.add_prefix(field)] = error
if form.non_form_errors():
errors['__all__'] = form.non_form_errors() # what is the '__all__'?
else:
errors = form.errors
formfields = dict([(fieldname, form[fieldname]) for fieldname in form.fields.keys()])
# if fields have been specified then restrict the error list
if request.POST.getlist('fields'): # I am having a hard time understanding what this if statement does.
fields = request.POST.getlist('fields') + ['__all__']
errors = dict([(key, val) for key, val in errors.iteritems() if key in fields])
final_errors = {} # here the author of this code totally lost me.
for key, val in errors.iteritems():
if '__all__' in key:
final_errors[key] = val
elif not isinstance(formfields[key].field, forms.FileField):
html_id = formfields[key].field.widget.attrs.get('id') or formfields[key].auto_id
html_id = formfields[key].field.widget.id_for_label(html_id)
final_errors[html_id] = val
data = {
'valid': False or not final_errors,
'errors': final_errors,
}
json_serializer = LazyEncoder() # Why does the result have to be returned in json?
return HttpResponse(json_serializer.encode(data), mimetype='application/json')
validate = require_POST(validate) # a decorator that requires a post to submit
LazyEncoder
class LazyEncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, Promise):
return force_unicode(obj)
return obj
form_class = kwargs.pop('form_class')
This is simply pulling the keyword argument, form_class, that was passed in via the URL conf.
(r'^SOME/URL/$', 'ajax_validation.views.validate',
{'form_class': ContactForm}, # this keyword argument.
'contact_form_validate')
BaseFormSet is simply the formset class doing the work behind the scenes. When you don't know, search the source! grep -ri "baseformset" . It's an invaluable tool.
Take a look at at django.forms.formsets to see how formset_factory produces new "formset" classes based on the BaseFormSet, hence the factory part!
I am guessing that this is for when there are multiple form submitted for validation
Yes, that's exactly what a formset is for (dealing with multiple forms)
I think he is looping over all fields and checking for error. what does add_prefix () return? and what is formfields[]?
Yes, that would be looping through the field names.
add_prefix() is for prefixing form field names with a specific form. Because a formset repeats form elements multiple times, each field needs a unique prefix, such as 0-field1, 1-field1, etc.
formfields is just an empty dictionary defined a few lines above.
what is the 'all'?
__all__ is defined at the top of django.forms.forms
NON_FIELD_ERRORS = '__all__'
It's just what non field specific errors (such as constraints across 2 fields) are stored under in the errors dictionary as opposed to errors[fieldname].
I am having a hard time understanding what this if statement does.
The author has left a note:
# if fields have been specified then restrict the error list
if request.POST.getlist('fields'):
It's checking if you specified any specific fields to validate in your URLConf, this is not django but ajax_validation.
You can see that he's overwriting his errors dictionary based on only the fields specified, thus passing on the validation only for those fields.
errors = dict([(key, val) for key, val in errors.iteritems() if key in fields])
here the author of this code totally lost me.
The author has mapped a custom errors and fields dictionary to specific field names with prefixes, (as opposed to the usual FormSet with each form having its own errors dictionary, unaware of the formset itself) which he presumably uses in the AJAX response to validate all fields.
Normally, you can iterate over a formset and go through the errors on a form by form basis, but not so if you need to validate all of them through ajax.
The line pulling html_id should be straight forward most of the time, but it's there because form widgets CAN add interesting things to the end of the ID's based on whether or not the widget is a radio select for example.
From source comments :
# RadioSelect is represented by multiple <input type="radio"> fields,
# each of which has a distinct ID. The IDs are made distinct by a "_X"
# suffix, where X is the zero-based index of the radio field. Thus,
# the label for a RadioSelect should reference the first one ('_0').
Why does the result have to be returned in json?
Because it's an ajax request and javascript easily eats json.
2- could you go through these lines of code...
extra_args_func = kwargs.pop('callback', lambda request, *args, **kwargs: {})
Either return a keyword argument named 'callback' (which if passed in, is supposed to be a function that accepts request and return a dictionary), and if it wasn't, return a lambda function that only returns an empty dictionary.
I'm not sure what the specific use is for the extra context. You could use it to run arbitrary snippets of code without modifying or subclassing ajax_validation...
It might help you to run this code, and put a debugger breakpoint in somewhere so you can step through and examine the variables and methods. You can do this by simply putting this line where you want to break:
import pdb; pdb.set_trace()
and you will be dumped into the debugger in the console.