I'm new to Django and I'm having a hard time understanding forms when the data to choose from are not taken from the database nor user input that they're generated on the go.
I currently have a template with a single ChoiceField. The data inside this field aren't fixed and they're calculated on the go once the page is requested. To calculate it I need the username of the User who is logged in. Basically, the calculation returns a list of lists in the form of ((title, id),(title,id),(title,id)), etc. that I need to put into the ChoiceField to make the User choose from one of the options.
Now, I'm not understanding how to pass the calculated list of lists to the form. I've tried to add the calculations inside the form as below but it is clearly the wrong way.
The main issue is that, to calculate my list of lists, I need the request value, and I don't know how to access it from the form.
Another idea was to add the generate_selection function inside the init but then I don't know how to pass main_playlist to being able to add it to ChoiceField
Below my not working forms.py
forms.py
class ChoosePlaylistForm(forms.Form):
playlists = forms.ChoiceField(choices=HERE_SHOULD_GO_main_playlist)
def generate_selection(self):
sp_auth, cache_handler = spotify_oauth2(self.request)
spotify = spotipy.Spotify(oauth_manager=sp_auth)
user_playlists = spotify.current_user_playlists(limit=10)
main_playlist = []
for playlists in user_playlists["items"]:
playlists_list = []
playlists_list.append(playlists['name'])
playlists_list.append(playlists['id'])
main_playlist.append(playlists_list)
return main_playlist
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(ChoosePlaylistForm, self).__init__(*args, **kwargs)
class Meta:
model = User
fields = ('playlists',)
The views should be something like below so I'm able to pass the request
views.py
form = ChoosePlaylistForm(request=request)
Maybe overriding the field choices in the form constructor would work:
class ChoosePlaylistForm(forms.Form):
playlists = forms.ChoiceField(choices=())
class Meta:
model = User
fields = ('playlists',)
def __init__(self, *args, request=None, **kwargs):
super(ChoosePlaylistForm, self).__init__(*args, **kwargs)
self.request = request
self.fields['playlists'].choices = self.generate_selection()
def generate_selection(self):
sp_auth, cache_handler = spotify_oauth2(self.request)
spotify = spotipy.Spotify(oauth_manager=sp_auth)
user_playlists = spotify.current_user_playlists(limit=10)
choices = []
for playlist in user_playlists["items"]:
playlist_choice = (playlist["name"], playlist["id"])
choices.append(playlist_choice)
return choices
I want to implement a Django form with dynamically generated ChoiceField options.
In my views.py I have defined the following (relevant) methods:
from .forms import OptionForm
def get_choice():
return # like this [(q.optionA, q.optionA), (q.optionB, q.optionB), (q.optionC, q.optionC), (q.optionD, q.optionD)]
# how can I pass q to this __init__ method
class OptionForm(forms.Form):
options= forms.ChoiceField(choices= [])
def __init__(self, *args, **kwargs):
super(OptionForm, self).__init__(*args, **kwargs)
self.fields['options'].choices = get_choice()
def viewtest(request, test_pk):
# get the test object containing all the questions
# each question contains four options using which I want to generate ChoiceField options corresponding to each question
# each option is available as q.optionA, q.optionB q.optionC & q.optionD
test= get_object_or_404(Test, pk= test_pk)
options= []
for q in test.questions.all():
opform= OptionForm() # how do I pass q.optionA, q.optionB q.optionC & q.optionD here to dynamically provide ChoiceField options?
options.append(opform)
return render(request, 'tests/test.html', {'test': test, 'options': options})
To get choices from model, ModelChoiceField is a better option. Find more details here: https://docs.djangoproject.com/en/3.1/ref/forms/fields/#django.forms.ModelChoiceField
I am currently using a MultiCheckboxField like this:
class MultiCheckboxField(SelectMultipleField):
"""
A multiple-select, except displays a list of checkboxes.
Iterating the field will produce subfields, allowing custom rendering of
the enclosed checkbox fields.
"""
widget = widgets.ListWidget(prefix_label=False)
option_widget = widgets.CheckboxInput()
to generate a list of checkboxes. I would like to extend this list in such a way as to allow some of the list entries to have an associated TextInput field. When the box is checked, the corresponding text input is required.
I am new to Flask and WTForms, and I am having some trouble trying to figure out just how to attack the problem. I would be grateful for any suggestions that might provide some kind of direction.
See the FieldList and FormField with custom widgets
http://wtforms.readthedocs.org/en/latest/fields.html#field-enclosures
You can use a custom validator like this:
class RequiredIfChoice(validators.DataRequired):
# a validator which makes a field required if
# another field is set and has a truthy value
def __init__(self, other_field_name, desired_choice, *args, **kwargs):
self.other_field_name = other_field_name
self.desired_choice = desired_choice
super(RequiredIfChoice, self).__init__(*args, **kwargs)
def __call__(self, form, field):
other_field = form._fields.get(self.other_field_name)
if other_field is None:
raise Exception('no field named "%s" in form' % self.other_field_name)
for value, label, checked in other_field.iter_choices():
if label == self.desired_choice and checked:
super(RequiredIfChoice, self).__call__(form, field)
and in your form:
class MyForm(Form):
"""
Your form.
"""
multi = MultiCheckboxField('Multibox', choices=[(1, 'First'), (2, 'Second')], coerce=int)
multitext = StringField('SubText', [RequiredIfChoice('multi', 'Second')])
For a slightly similar question look at this Q&A.
I have a field in one of my models like the following:
PAYROLL_CHOICES = (
('C1', 'Choice1'),
('C2', 'Choice2')
etc.....
)
payrollProvider = models.CharField(max_length=2, choices=PAYROLL_CHOICES)
When I create a model form for this field, Django correctly generates an HTML select box, but includes a default blank value of "---------".
I would like to know how to change this default value to some other text, such as "please choose value".
I believe I should be able to set this in my model form's init via the following, as documented in this answer and several others:
self.fields['payrollProvider'].empty_label = "please choose value"
However, this isn't working for me. When I include that line in my form's init, "--------" still shows up as the initial choice in the select box. I'm pasting the relevant forms.py below, but it seems that others have also been unable to access / modify empty_label. At this link, the questioner describes a way to delete the default empty_label value (which I was able to do successfully via his method) but what I really want to do is to modify the empty_label that is displayed.
Any ideas?
Here's the code for the form in forms.py, with the empty_label code that isn't successful at changing the default "----------":
class PayrollCredentialForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(PayrollCredentialForm, self).__init__(*args, **kwargs)
self.fields['payrollUsername'].widget.attrs.update({'class' : 'yp-signup'})
self.fields['payrollPassword'].widget.attrs.update({'class' : 'yp-signup'})
self.fields['payrollProvider'].widget.attrs.update({'class' : 'yp-signup'})
self.fields['payrollUsername'].widget.attrs.update({'placeholder' : ' Payroll Username'})
self.fields['payrollPassword'].widget.attrs.update({'placeholder' : ' Payroll Password'})
self.fields['payrollProvider'].empty_label = "please choose value"
class Meta:
model = Company
fields = ('payrollProvider', 'payrollUsername', 'payrollPassword')
widgets = {
'payrollPassword': forms.PasswordInput(),
}
dokkaebi, that won't work properly. You'll receive the following select code:
<select name="payrollProvider" id="id_payrollProvider">
<option value="" selected="selected">---------</option>
<option value="" selected="selected">please choose value</option>
<option value="C1">Choice1</option>
<option value="C2">Choice2</option>
</select>
The only relatively convenient way that came to my mind is to do something like this in the form:
class PayrollCredentialForm(forms.ModelForm):
class Meta:
model = Company
def __init__(self, *args, **kwargs):
super(PayrollCredentialForm, self).__init__(*args, **kwargs)
self.fields["payrollProvider"].choices = [("", "please choose value"),] + list(self.fields["payrollProvider"].choices)[1:]
Actually, now (as of Django 1.8 and higher) override of an empty_label works:
class PayrollCredentialForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(PayrollCredentialForm, self).__init__(*args, **kwargs)
self.fields['payrollProvider'].empty_label = 'Please, choose value'
Also, if you working with Django Admin, there is an option to set empty value for a list view:
class PayrollCredentialAdmin(admin.ModelAdmin):
list_display = ('payrollProvider_value', )
def payrollProvider_value(self, instance):
return instance.payrollProvider
payrollProvider_value.empty_value_display = 'Empty value'
What if field should be readonly?
There is a catch if field modified in such way should be readonly.
If overridden form field will be specified in readonly_fields attribute inside PayrollCredentialAdmin class, it would result in KeyError exception in PayrollCredentialForm (because readonly field won't be included in form's self.fields). To handle that, it's required to override formfield_for_dbfield instead of using readonly_fields:
def formfield_for_dbfield(self, db_field, **kwargs):
field = super(PayrollCredentialAdmin, self).formfield_for_dbfield(
db_field, **kwargs
)
db_fieldname = canonical_fieldname(db_field)
if db_fieldname == 'payrollProvider':
field.widget = forms.Select(attrs={
'readonly': True, 'disabled': 'disabled',
})
return field
Might be useful.
Update for Django 1.11:
Comments below brought assumption that such override is no longer valid for newer version of Django.
The problem is that you are trying to specify something that is not available for the type of Select field.
The empty_label option is for forms.ModelChoiceField, which happens to use a Select widget, but is not the same kind of field as your CharField that you are providing options for.
https://docs.djangoproject.com/en/dev/ref/forms/fields/#modelchoicefield
You can see this also in a previous question here: https://stackoverflow.com/a/740011/1406860
You could try and override the html of the modelform to add the first option as "please choose value". Alternatively, you could use a template filter to do the same thing. Lastly, you could and ("", "please choose value") to PAYROLL_CHOICES, and if you don't want it to be submitted without a payrollProvider just set blank=False for the field in the model.
JD
In your forms.py file,
This would definitely work.. Try this...
class Meta:
model = StaffDetails
fields =['photo','email', 'first_name','school','department', 'middle_name','last_name','gender', 'is_active']
def __init__(self, *args, **kwargs):
super(StaffDetailsForm, self).__init__(*args, **kwargs)
self.fields['Field_name'].empty_label = 'Please Select'
It worked for me.. just replace the field names with yours...
Only ModelChoiceField (generated for ForeignKey fields) supports the empty_label parameter, and in that case it's tricky to get at as those fields are usually generated by django.forms.models.ModelFormMetaclass within a call to django.forms.models.modelform_factory.
ModelFormMetaclass uses the empty_label param to add another choice to the list, with empty_label as the display and '' as its value.
The simplest way to do what you want is just to add an empty choice to your choices list:
PAYROLL_CHOICES = (
('', 'please choose value'),
('C1', 'Choice1'),
('C2', 'Choice2'),
etc.....
)
Another simple way worked for me is:
country = forms.ModelChoiceField(queryset=Country.objects.filter(), empty_label='--Select--')
However, my Django version is 2.2.7
Just add a tuple to your model.field.choices with a value of None:
payrollProvider = models.CharField(max_length=2, choices=PAYROLL_CHOICES)
PAYROLL_CHOICES = (
(None, 'please choose'),
('C1', 'Choice1'),
('C2', 'Choice2')
etc.....
)
From the docs (v4.0):
Unless blank=False is set on the field along with a default then a label containing "---------" will be rendered with the select box. To override this behavior, add a tuple to choices containing None; e.g. (None, 'Your String For Display'). Alternatively, you can use an empty string instead of None where this makes sense - such as on a CharField.
Adapted from Javed answer. Since I have tons of fields in my form I just want to replace all labels in the html by placeholders so for select tags I use their field label.
class PacienteForm(forms.ModelForm):
class Meta:
model=Paciente
fields=('__all__')
def __init__(self, *args, **kwargs):
super(PacienteForm, self).__init__(*args, **kwargs)
for f in self.fields:
if hasattr(self.fields[f], 'choices'):
choices=self.fields[f].choices
if type(choices) == list:
choices[0]=('',self.fields[f].label)
self.fields[f].choices=choices
Add Empty String with "Please Select" to choices as shown below:
class DateForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
class Months(models.TextChoices):
EMPTY_LABEL = '', 'Please Select' # Here
JANUARY = 'JAN', 'January'
FEBRUARY = 'FEB', 'February'
MARCH = 'MAR', 'March'
self.fields['month'].choices = Months.choices
I have a baseform with over 20 fields. Then I have about 15 other forms inheriting from that form, passing in a parameter called fields which the baseform uses to delete all other fields. Best explain via example:
class BaseForm(forms.Form):
reportid = forms.HiddenInput()
fromdate = forms.DateField(label=_("From"), widget=widgets.AdminDateWidget())
todate = forms.DateField(label=_("To"), widget=widgets.AdminDateWidget())
sort_by = forms.ChoiceField(label=_("Sort by"), choices=[])
.......
def __init__(self, *args, **kwargs):
fields = kwargs.pop('fields')
#Pseudo:
***del self.fields[field] for field not in fields***
class SubForm(forms.Form):
def __init__(self, *args, **kwargs):
fields = ['reportid', 'todate']
super(SubForm, self).__init__(fields=fields, *args, **kwargs)
The resulting form would then look like this:
class SubForm(forms.Form):
reportid = forms.HiddenInput()
todate = forms.DateField(label=_("To"), widget=widgets.AdminDateWidget())
My problem is that when the BaseForm is initialized for the first time, the labels are bound to the fields with the active language, and when another user logs in with another language setting (or the current user changes languages) the field labels don't update.
I've come to a solution using a dict like this:
labels = {
'todate': lambda: _("To"),
'fromdate': lambda: _("From"),
.....
}
and then when initializing the baseform looping through all fields and setting
self.fields[field].widget.label = labels[field]()
Do I have any nicer (read: more pythonic) way of achieving this?
Django provides _lazy variants of the translation functions (for example ugettext_lazy) so you can ark strings for translations at the access time (as opposed to when the translation function is called).
It's documented in details at https://docs.djangoproject.com/en/1.7/topics/i18n/translation/#lazy-translation