everyone
I want to set initial value for ChoiceField while that value isn't in choices of ChoiceField
following is the ArticleForm definition:
def ArticleForm(forms.Form):
choice = forms.ChoiceField(choices=[['a':'a'],['b':'b']])
then I instantiated the form by passing the initial argument:
form = ArticleForm(initial={'choice':'Portrush'})
notice that the initial value('Portrush') isn't one of choices defined in ChoiceField('a' and 'b')
How could I set initial value? Any suggestion is appreciated.
May be something like this:
class ArticleForm(forms.Form):
LETTER_A = 'a'
LETTER_B = 'b'
# look not a dict
CHOICES = ((LETTER_A,'letter a'),
(LETTER_B,'letter b'))
choice = forms.ChoiceField(choices=CHOICES)
def __init__(self, *args, **kwargs):
initial = kwargs.get('initial', {})
choice = initial.get('choice', None)
# set just the initial value
# in the real form needs something like this {'choice':'a'}
# but in this case you want {'choice':('a', 'letter_a')}
if choice:
kwargs['initial']['choice'] = choice[0]
# create the form
super(ArticleForm, self).__init__(*args, **kwargs)
# self.fields only exist after, so a double validation is needed
if choice and choice[0] not in (c[0] for c in self.CHOICES):
self.fields['choice'].choices.append(choice)
form = ArticleForm(initial={'choice':('c','other')})
form.as_p()
>>> u'<p><label for="id_choice">Choice:</label> <select name="choice" id="id_choice">\n<option value="a">letter a</option>\n<option value="b">letter b</option>\n<option value="c" selected="selected">other</option>\n</select></p>'
Related
I wrote a dynamic form:
class VoteForm(forms.Form):
def __init__(self, *args, **kwargs):
question = kwargs.pop('instance', None)
super().__init__(*args, **kwargs)
if question:
if question.allow_multiple_votes:
choice_field = forms.ModelMultipleChoiceField(queryset=question.choice_set)
else:
choice_field = forms.ModelChoiceField(queryset=question.choice_set)
choice_field.widget = forms.RadioSelect
choice_field.label=False
choice_field.empty_label=None
choice_field.error_messages={'required': _('No choice selected.'),
'invalid': _('Invalid choice selected.')}
self.fields['choice'] = choice_field
Without the RadioSelect widget everything seems to work, but with it, the following error occurs:
TypeError: use_required_attribute() missing 1 required positional argument: 'initial'
When you set the widget after having created the field, it must be a widget instance, not the class itself. But you have to set choices yourself:
choice_field.widget = forms.RadioSelect(choices=...)
Preferably, you can give the widget class when you construct your field:
choice_field = forms.ModelChoiceField(queryset=question.choice_set, widget=forms.RadioSelect)
First things first, you have (assuming a typo) error in your widget declaration. You need an instance of the RadioSelect class as a widget:
choice_field.widget = forms.RadioSelect()
Then you can set the initial value manually.
You can choose one of two options to do that:
Option 1:
Set initial at form instantiation:
if question.allow_multiple_votes:
choice_field = forms.ModelMultipleChoiceField(
queryset=question.choice_set,
initial=0
)
else:
choice_field = forms.ModelChoiceField(
queryset=question.choice_set
initial=0
)
Option 2:
Set the initial attribute: choice_field.initial=0 (as #nik_m points out in the comments)
With initial=0 the first item of the queryset will be selected.
I changed manytomany field to token field with the help of bootstrap tag input javascript plugin. Then I am getting comma (,) separates primary keys as input. After splitting from delimiter comma (,) I got the instance of the exact object. Now how to add those objects to the manytomany field. I tried returning list but it didn't work.
Here is my form class
class EventRegistrationForm(forms.ModelForm):
participants = forms.CharField(widget=forms.TextInput(attrs={'autocomplete':'off', 'data-role':'tagsinput'}),required=True)
class Meta:
model=EventRegistration
fields=['event', 'participants', 'teamName', 'feePaid']
def clean_feePaid(self):
feepaid=self.cleaned_data.get('feePaid')
if not feepaid:
raise ValidationError('Please pay the fee First :)')
return feepaid
def clean_participants(self):
participants_data = self.cleaned_data.get('participants')
event = self.cleaned_data.get('event')
participants =[]
for pd in participants_data.split(','):
p = Candidate.objects.get(pk=pd)
participants.append(p)
if not (event.minParticipant <= len(participants) <= event.maxParticipant):
raise ValidationError('Number of Participants exceeded :D')
return participants
def __init__(self, *args, **kwargs):
super(EventRegistrationForm, self).__init__(*args, **kwargs)
self.fields['event'].empty_label = ''
# following line needed to refresh widget copy of choice list
self.fields['event'].widget.choices =self.fields['event'].choices
one can pass list for m2m field.
I just missed form.save_m2m().
I created a form with a TypedChoiceField:
class EditProjectForm(ModelForm):
def __init__(self, action, *args, **kwargs):
super(EditProjectForm, self).__init__(*args, **kwargs)
now = datetime.datetime.now()
if action == 'edit':
project_year = kwargs['project_year']
self.fields['year'].choices = [(project_year, project_year)]
else:
self.fields['year'].choices = [(now.year, now.year), (now.year + 1, now.year + 1)]
year = forms.TypedChoiceField(coerce=int)
...
This works perfectly fine when using it inside a view. Now I want to write tests for this form:
form_params = {
'project_year': datetime.datetime.now().year,
}
form = EditProjectForm('new', form_params)
self.assertTrue(form.is_valid())
The test fails, because is_valid() returns False. This is because when calling super.__init__() in the EditProjectForm, the field year doesn't have its choices yet. So the validation for this field fails and an error is added to the error list inside the form.
Moving the super call after self.fields['year'].choices doesn't work either, because self.fields is only available after the super.__init__() call.
How can I add the choices dynamically and still be able to test this?
Okay, I found the problem.
The field year is a class variable, and is instantiated even before the tests setUp method and the forms __init__ method was called. Since I haven't passed the required choices parameter for this field, the error was issued way before the form object was created.
I changed the behaviour so I change the type of the fields in the __init__ method rather than using a class variable for that.
class EditProjectForm(ModelForm):
def __init__(self, action, *args, **kwargs):
super(EditProjectForm, self).__init__(*args, **kwargs)
now = datetime.datetime.now()
if action == 'edit':
project_year = kwargs['project_year']
choices = [(project_year, project_year)]
else:
choices = [(now.year, now.year), (now.year + 1, now.year + 1)]
self.fields['year'] = forms.TypedChoiceField(coerce=int, choices=choices)
How I would dynamically create a few form fields with different questions, but the same answers?
from wtforms import Form, RadioField
from wtforms.validators import Required
class VariableForm(Form):
def __init__(formdata=None, obj=None, prefix='', **kwargs):
super(VariableForm, self).__init__(formdata, obj, prefix, **kwargs)
questions = kwargs['questions']
// How to to dynamically create three questions formatted as below?
question = RadioField(
# question ?,
[Required()],
choices = [('yes', 'Yes'), ('no', 'No')],
)
questions = ("Do you like peas?", "Do you like tea?", "Are you nice?")
form = VariableForm(questions = questions)
It was in the docs all along.
def my_view():
class F(MyBaseForm):
pass
F.username = TextField('username')
for name in iterate_some_model_dynamically():
setattr(F, name, TextField(name.title()))
form = F(request.POST, ...)
# do view stuff
What I didn't realize is that the class attributes must be set before any instantiation occurs. The clarity comes from this bitbucket comment:
This is not a bug, it is by design. There are a lot of problems with
adding fields to instantiated forms - For example, data comes in
through the Form constructor.
If you reread the thread you link, you'll notice you need to derive the class, add fields to that, and then instantiate the new class. Typically you'll do this inside your view handler.
You're almost there:
CHOICES = [('yes', 'Yes'), ('no', 'No')]
class VariableForm(Form):
def __new__(cls, questions, **kwargs):
for index, question in enumerate(questions):
field_name = "question_{}".format(index)
field = RadioField(question,
validators=[Required()],
choices=CHOICES)
setattr(cls, field_name, field)
return super(VariableForm, cls).__new__(cls, **kwargs)
In my case,
I used a csv and imported it using pandas.
So, this solution allows you to even use different answers if required.
data=pd.read_csv("./temp.csv")
class UserForm(Form):
for i in data:
if data[i][0] == 'textbox':
formElement='TextField("%s",validators=[validators.required()], default="please add content")' %(i)
elif data[i][0] == 'radio':
choice = list(data[i][1:].dropna().unique().tolist())
choiceStr=''
for k in choice:
choiceStr +="('"+k+"','"+k+"'),"
formElement = 'RadioField("%s",validators=[validators.required()],choices=[%s], default="%s")' %(i,choiceStr, choice[0])
elif data[i][0] == 'dropdown':
choice = list(data[i][1:].dropna().unique().tolist())
# choice.remove('X')
choiceStr=''
for k in choice:
choiceStr +="('"+k+"','"+k+"'),"
formElement = 'SelectField("%s",validators=[validators.required()],choices=[%s])' %(i,choiceStr)
exec("%s=%s" % (i,formElement))
I have a problem with django and forms.
In forms.py I have sth like this:
class MyForm(forms.Form):
def __init__(self, *args, **kwargs):
self.answers = kwargs.pop('answers')
super(AnswersForm, self).__init__(*args, **kwargs)
choices = ["aaaa", "ssssssssss", "ddddddddd"]
self.fields['answers'] = forms.ChoiceField(choices = choices, widget=forms.RadioSelect())
And when i use it in template, I have sth like this:
Answers:
- a
- s
- d
Do you know why I haven't got all words, but only one letter of the word?
choices should be a list (or other iterable) of two-tuples. The first entry in each tuple is the value that gets stored in the database. The second is the human-readable name.
See the documentation on choices for more information.
Thus, your choices list should look more like this:
choices = [("a","aaaa"), ("s", "ssssssssss"), ("d", "ddddddddd")]
As posted, your code is considering each string (aaaa, ssssssss, etc.) to be tuple-like and grabbing the first letter of each for the database representation, and the second letter of each for the human-readable name (used for the label).