How to extend CheckboxInput for a list of checkboxes - python

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.

Related

How to fill a Django custom multiplechoicefield with choices

I have a form with a search box that uses jQuery to fill a multipleChoiceField. I need to use a custom multipleChoiceField so I can control the validation and only check if the choice exists, not if it was one of the original choices as a modelMultipleChoiceField with a queryset would. However, the custom multipleChoiceField renders on the page as empty until you enter something in the search box to fill it with choices via jQuery. I would like it to render with a few choices to begin with instead.
class ArticleMultipleChoiceField(forms.MultipleChoiceField):
def __init__(self, *args, **kwargs):
super(ArticleMultipleChoiceField, self).__init__(*args, **kwargs)
include_articles = [article.id for article in Article.objects.order_by('-sub_date')[:5]]
self.choices = Article.objects.filter(id__in=include_articles).order_by('-sub_date')
In this form, I get the error "Article object is not iterable". I have also tried changing that self.choices to self.data, self.queryset, and self.initial, and in all those 3 cases, I keep getting an empty multiple choice field instead.
How can I use a queryset to provide the initial set of choices here?
Here is the form it is used in:
class StorylineAddArticleForm(forms.Form):
articleSearchBox = forms.CharField(label="Search to narrow list below:")
include_articles = [article.id for article in Article.objects.order_by('-sub_date')[:5]]
articles = ArticleMultipleChoiceField()
def __init__(self, *args, **kwargs):
super(StorylineAddArticleForm, self).__init__(*args, **kwargs)
self.fields['articleSearchBox'].required = False
self.helper = FormHelper(self)
self.helper.layout = Layout(
Field('articleSearchBox'),
Field('articles'),
ButtonHolder(
Submit('submit', 'Add', css_class='button white')
)
)
Also, this is being rendered by Crispy Forms.
choices doesn't accept a QuerySet as an argument, it needs a list or tuple of two-tuples with acceptable values. See the documentation on choices here: https://docs.djangoproject.com/en/2.0/ref/models/fields/#field-choices .
In this case you need to turn your Article queryset into a list or tuple of the above format.

How to change empty_label for modelForm choice field?

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

How do I generate dynamic fields in WTForms

I am trying to generate a form in WTForms that has dynamic fields according to this documentation http://wtforms.simplecodes.com/docs/1.0.2/specific_problems.html#dynamic-form-composition
I have this subform class which allows users to pick items to purchase from a list:
class Item(Form):
itmid = SelectField('Item ID')
qty = IntegerField('Quantity')
class F(Form):
pass
There will be more than one category of shopping items, so I would like to generate a dynamic select field based on what categories the user will choose:
fld = FieldList(FormField(Item))
fld.append_entry()
but I get the following error:
AttributeError: 'UnboundField' object has no attribute 'append_entry'
Am I doing something wrong, or is there no way to accomplish this in WTForms?
I ran into this issue tonight and ended up with this. I hope this helps future people.
RecipeForm.py
class RecipeForm(Form):
category = SelectField('Category', choices=[], coerce=int)
...
views.py
#mod.route('/recipes/create', methods=['POST'])
def validateRecipe():
categories = [(c.id, c.name) for c in g.user.categories.order_by(Category.name).all()]
form = RecipeForm(request.form)
form.category.choices = categories
...
#mod.route('/recipes/create', methods=['GET'])
def createRecipe():
categories = [(c.id, c.name) for c in g.user.categories.order_by(Category.name).all()]
form = RecipeForm(request.form)
form.category.choices = categories
return render_template('recipes/createRecipe.html', form=form)
I found this post helpful as well
class BaseForm(Form):
#classmethod
def append_field(cls, name, field):
setattr(cls, name, field)
return cls
from forms import TestForm
form = TestForm.append_field("do_you_want_fries_with_that",BooleanField('fries'))(obj=db_populate_object)
I use the extended class BaseForm for all my forms and have a convenient append_field function on class.
Returns the class with the field appended, since instances (of Form fields) can't append fields.
Posting without writing full code or testing the code, but maybe it will give you some ideas. Also this could maybe only help with the filling the needed data.
You need to fill choices for SelectField to be able to see the data and be able to select it. Where you fill that? Initial fill should be in the form definition, but if you like dynamic one, I would suggest to modify it in the place where you creating this form for showing to the user. Like the view where you do some form = YourForm() and then passing it to the template.
How to fill form's select field with choices? You must have list of tuples and then something like this:
form.category_select.choices = [(key, categories[key]) for key in categories]
form.category_select.choices.insert(0, ("", "Some default value..."))
categories here must be dictionary containing your categories in format like {1:'One', 2:'Two',...}
So if you will assign something to choices when defining the form it will have that data from the beginning, and where you need to have user's categories, just overwrite it in the view.
Hope that will give you some ideas and you can move forward :)
have you tried calling append_entry() on the form instance instead of the FieldList definition?
class F(Form)
fld = FieldList(SelectField(Item))
form = F()
form.fld.append_entry()
This is how i got it to work.
class MyForm(FlaskForm):
mylist = SelectField('Select Field', choices=[])
#app.route("/test", methods=['GET', 'POST']
def testview():
form = MyForm()
form.mylist.choices = [(str(i), i) for i in range(9)]
Strangely this whole thing stops working for me if i use coerce=int. I am myself a flask beginner, so i am not really sure why coerce=int causes issue.
WTForms Documentation : class wtforms.fields.SelectField
Select fields with dynamic choice values:
class UserDetails(Form):
group_id = SelectField(u'Group', coerce=int)
def edit_user(request, id):
user = User.query.get(id)
form = UserDetails(request.POST, obj=user)
form.group_id.choices = [(g.id, g.name) for g in Group.query.order_by('name')]

How to initialize django-form data type field from a given choices?

First of all: I am not able to find out the proper Title of this question.
Anyhow the question is:
I have to fill a form at template and the fields of this form are user dependent. For example you passes integer (integer is not a datatype) as a parameter to the method and it should returns like this:
fileds = forms.IntegerField()
If you pass bool then it should like this:
fields = forms.BooleanField()
So that i can use them to create my form. I tried with this code but it returns into the form of string.
Some.py file:
choices = (('bool','BooleanField()'),
('integer','IntegerField()'))
def choose_field():
option = 'bool' # Here it is hardcoded but in my app it comes from database.
for x in choices:
if x[0]==option:
type = x[1]
a = 'forms'
field = [a,type]
field = ".".join(field)
return field
When i print the field it prints 'forms.BooleanField()'. I also use this return value but it didn't work. Amy solution to this problem?
The simpliest way is to create your form class and include fields for all possible choices to it. Then write a constructor in this class and hide the fields you don't want to appear. The constructor must take a parameter indicating which fields do we need. It can be useful to store this parameter in the form and use it in clean method to correct collected data accordingly to this parameter.
class Your_form(forms.ModelForm):
field_integer = forms.IntegerField()
field_boolean = forms.BooleanField()
def __init__(self, *args, **kwargs):
option = kwargs["option"]
if option == "integer":
field_boolean.widget = field_boolean.hidden_widget()
else:
field_integer.widget = field_integer.hidden_widget()
super(Your_form, self).__init__(*args, **kwargs)
In your controller:
option = 'bool'
form = Your_form(option=option)

Modify presentation of element in a TabularInline

For this particular administration page, I'd like to turn the 'current value' (outlined in a red circle) into a link going back to the administration page for this particular object.
But I can't find where to go to make this change. I know that I need to somehow override how this
is displayed but I can't figure it out.
What do I need to override to do what I want?
Admin model definition:
class FirmwareConfigElementsChoiceInline(admin.TabularInline):
model = FirmwareConfigElements
extra = 1
class FirmwareConfigAdmin(admin.ModelAdmin):
save_as = True
list_display = ('name', 'description')
inlines = [FirmwareConfigElementsChoiceInline]
Using Filip's great help I've gotten to this:
class FirmwareConfigElementsForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
klass = FirmwareConfigElementsForm
super(klass, self).__init__(*args, **kwargs)
if self.instance.type == 'incfw':
value = self.instance.value
url = '#' # TODO: get the URL for the value
hyperlink = '%s' % (url, value)
label = self.fields['type'].label.replace(value, hyperlink)
self.fields['type'].label = label
But in the above code, self.fields['type'].label has the contents Type and not Include another FW Config - BASE:IBM-HS22/HS22V as I was expecting.
I've explored it in the debugger but I can't figure out how to get to the particular label that I want to change.
Inline admin models have a template property you can use to supply a custom template. From there, you'll need to modify the code to add the url.
You'll need to provide a custom ModelForm for the FirmwareConfigElements model, which you'll set as the value for the FirmwareConfigElementsChoiceInline.form class attribute.
Here you'll want to override the ModelForm.__init__() instance method to assign a new label for the field you want to override if the form is bound:
class FirmwareConfigElementsForm(models.ModelForm):
def __init__(self, *args, **kwargs):
klass = FirmwareConfigElementsForm
super(klass, self).__init__(*args, **kwargs)
if form.is_bound and 'value' in self.data:
value = self.data['value']
url = '' # TODO: get the URL for the value
hyperlink = '%s' % (url, value)
label = self.fields['type'].label.replace(value, hyperlink)
self.fields['type'].label = label
class FirmwareConfigElementsChoiceInline(admin.TabularInline):
model = FirmwareConfigElements
extra = 1
form = FirmwareConfigElementsForm
Now, if you want the label to change dynamically as the user changes the form data, then it gets a lot uglier and you'll have to resort to referencing JavaScript media and performing the above on the fly.

Categories

Resources