dynamic form in Flask - flask

in a movie rating app,I would like to generate a WTF form in flask with dynamic number of fields. i.e, if there are three movies, there will be three fields.
I thought about a few options, but none of them worked:
class RatingForm(Form):
rating = TextField("rating",[validators.Length(min=1, max=1)])
movie_order=TextField("movie",[validators.Length(min=1, max=1)])
submit = SubmitField("submit rating")
pass a parameter to the form object - I don't see how can I pass a parameter to this kind of class
make a loop inside the template, thus generate and return multiple forms, and choose the correct one. this also doesnt work, since the request.form is immutableDict, and I end up having multiple fields with the same key, which I cant access.
{% for movie in movies_to_rate %}
<p>
<form method="POST" enctype="multipart/form-data" action="/rate">
{{ movie}}
{{ forms[movie].rating}}
{{ forms[movie].submit }}
<input type="submit" value="Go">
</p> {% endfor %}
any ideas about what can I do?

I think you can generate a list of TextField's as a class member instead of using one field object. (Though it looks a bit weird, I assume your validators are what you meant.)
class RatingForm(Form):
def __init__(self, count):
self.ratings = [TextField("rating_" + str(i), [validators.Length(min=1, max=1)])
for i in range(count)]
...

Related

WTForms - Pass argument to form then create fields if variable exists

Depending on the arguments I pass to the form I want to return different form fields.
class TestForm(FlaskForm):
"""Test Form."""
if one:
field1 = StringField('Field 1')
if two:
field2 = StringField("Field 2")
if three:
field3 = StringField("Field 3")
submit = SubmitField("Add Service")
def __init__(self, one=None, two=None, three=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.one = one
self.two = two
self.three = three
I am not able to see the arguments when doing the if statements.
I am aware of the option to have logic in html when rendering the form, however due the nature of the project have opted to use quick_form on the html side.
Here is the html code I am using.
{% import 'bootstrap/wtf.html' as wtf %}
<h3 >Add Service</h3>
<div class="row">
<div class="col-md-4">
{{ wtf.quick_form(form) }}
</div>
</div>
Instead of creating this logic in your form class. I would recommend to create all the fields you need and then dynamically choose which to show the user using jinja2 in your html file.
Here's an example.
{% for fields in fields_list %}
{% if field == '1' %}
{{ form.field1.label(class="form-control-label") }}
{{ form.field1(class="form-control form-control-lg") }}
{% endif %}
{% if field == '2' %}
{{ form.field2.label(class="form-control-label") }}
{{ form.field2(class="form-control form-control-lg") }}
{% endif %}
{% if field == '3' %}
{{ form.field3.label(class="form-control-label") }}
{{ form.field3(class="form-control form-control-lg") }}
{% endif %}
{% endfor %}
And then when you render or redirect to your .html from your routes code, don't forget to send
the proper arguments, such as
# create your fields list, which do I want to show?
# Make it a list of integers
fields_list = [1, 2, 3]
return render_template('insert-name.html', fields_list , form=form)
If my answer helped you, please consider accepting my answer.
I am new to this site and trying to build up some reputation :)
Thank you! Happy Coding!
If someone ever comes accross the same question, here is a quick solution I came upon some times ago.
It is in part inspired from the accepted answer here.
In your forms.py or wherever you declare your TestForm class, put the class declaration inside a function that takes your parameter as an argument and returns the class object as output.
The argument will now be accessible within the class itself, allowing for any test you may want to perform.
Here's a working example based on your original question (I just added a default value to get at least one StringField in case the parameter is ommited):
def create_test_form(var='one'):
class TestForm(FlaskForm):
"""Test Form."""
if var == 'one':
field1 = StringField('Field 1')
if var == 'two':
field2 = StringField("Field 2")
if var == 'three':
field3 = StringField("Field 3")
submit = SubmitField("Add Service")
return TestForm()
Then simply create the form in your routes like so:
form = create_test_form('two')
Finally pass it to your HTML to render the form with quick_form like you did.
This example will render a form with a single StringField named "Field 2" and a "Add Service" submit button.

How to set Field attributes in jinja 2 loop

I am quite new to programming and I need your help. I have a field list of float fields where each one should display a different default value.
I want to change the default attribute by iterating in my template but it's not working.
<div class="form-group">
{% for entry in form.pde_parameters %}
{% set entry.default=pde_parameter_value_list[loop.index0]%}
{{ pde_parameter_list[loop.index0] }}
{{ entry.hidden_tag() }}
{{ render_field(entry.parameter_value) }}
{% endfor %}
</div>
class NewModelParameterMaskForm(FlaskForm):
parameter_value = FloatField("")
class Solution(FlaskForm):
pde_parameters = FieldList(FormField(NewModelParameterMaskForm), min_entries=1)
sde_parameters = FieldList(FormField(NewModelParameterMaskForm), min_entries=1)
I get this error message:
jinja2.exceptions.TemplateRuntimeError: cannot assign attribute on non-namespace object
You need to populate the form before you pass it to Jinja, not inside your template.
def show_solution():
form = Solution(request.form)
if form.validate_on_submit():
# Use form values
return redirect(url_for('done'))
for s in solutions(): # Fetch your solutions from somewhere
form.pde_paramaters.append_entry({'parameter_value': s.float_value})
return render_template('solution.html', form=form)
I was receiving the same error. The solution I found would look similar to this in the question's case:
{% set update_result = entry.update({'default': pde_parameter_value_list[loop.index0]}) %}
Then ignore the update_result variable and use the entry normally.
It seems Jinja doesn't allow setting directly the value of a field/property inside an object. However, if these are dictionaries or lists, you can use methods like update or append since they change the object internally.
Just as with the dict.update suggestion above, it seems to be possible to trick Jinja into setting attributes by simply calling __setattr__ on the object:
{% set _ = obj.__setattr__('fieldname', 'value') %}

Why won't Django template recognize list from object field?

I have an object field that stores a list. The Django template is treating the list like a string, it iterates over each character, not each list value.
Tried saving the object field in various ways, "['1','2']" and "1,2". Tried the "|make_list" filter.
models.py
class SpecialField(models.Model):
name = models.CharField(max_length=200,blank=True,null=True)
description = models.CharField(max_length=200,blank=True,null=True)
value_options = models.CharField(max_length=200,blank=True,null=True)
# value_options might be "['1','2']" or "red, green, blue"
views.py
object_field_list= SpecialField.objects.all()
context = {
'object_field_list': object_field_list,
}
return render(request, 'app1/template-detail.html', context)
template
{% for object_field in object_field_list%}
{% for list_value in object_field.value_options %}
<option>{{ list_value }}</option>
{% endfor %}
{% endfor %}
I was hoping for:
<option>1</option>
<option>2</option>
But I am getting:
<option>[</option>
<option>'</option>
<option>1</option>
<option>'</option>
<option>,</option>
<option>'</option>
<option>2</option>
<option>'</option>
<option>]</option>
You are wanting to show data from a model, so let's suppose you have a model
class SpecialField(models.Model):
name=models.CharField(max_length=101)
price = models.IntegerField(default=0)
Given the way you've set in in context you can show this in the template with
{% for obj in object_field_list %}
{{ obj.name}} - {{ obj.price }}
{% endfor %}
Obviously, you need to amend for your model
Tweak on your suggestion...it needs one more level of looping. Still can't get it to work.
models.py
class SpecialField(models.Model):
name=models.CharField(max_length=101)
price = models.IntegerField(default=0)
custom_list = models.CharField(max_length=200)
template
{% for obj in object_field_list %}
{{ obj.name}} - {{ obj.price }}
{% for list_value in obj.custom_list %}
{{ list_value }}
# this is where it's breaking for me
{% endfor %}
{% endfor %}
context = {
'object_field_list': SpecialField.objects.values_list('value_options', flat=True),
}
Should get you what you actually want to loop over.
EDIT: Sorry, I missed the fact you are storing a string rather than using an ArrayField or similar. The problem from your updated answer is the data you have isn't consistent. If it were simply all comma-separated values you could do:
object_field_list = [value_list.split(',') for value_list in SpecialField.objects.values_list('value_options', flat=True)]
but you will need some way of normalizing the data you're storing in value_options. How does the data get into the database? If it's via Django, you can apply some kind of cleaning method on the form or API endpoint that accepts the data.
Tried saving the object field in various ways, "['1','2']" and "1,2". Tried the "|make_list" filter.
If you have complete control over the incoming data, you would be better off normalizing the data: rather than storing a single value_options entry on SpecialField, you would remove that field and add a second model, e.g., SpecialFieldOption like
class SpecialFieldOption(models.Model):
name = models.CharField(max_length=200, blank=False)
field = models.ForeignKey(SpecialField, related_name='options')
# now you can do
SpecialField.objects.get(pk=1).options.all()
to loop over the list of all options for a given SpecialField.
This was the handiest solution...define a new list using split. https://stackoverflow.com/a/8318915/9268133. Thanks for everyone's help!

Wagtail not pulling through custom field panels

I'm overriding the wagtail AbstractFormField panel attribute in the following way:
...
before_input = RichTextField(verbose_name=_('before input'), blank=True)
after_input = RichTextField(verbose_name=_('after input'), blank=True)
panels = [
FieldPanel('label'),
FieldPanel('before_input'),
FieldPanel('after_input'),
FieldPanel('required'),
FieldPanel('field_type', classname="formbuilder-type"),
FieldPanel('choices', classname="formbuilder-choices"),
FieldPanel('default_value', classname="formbuilder-default"),
]
where the other panels are what comes out of the box.
This is working perfectly on the admin side and also saving as rich text into my database
I am pulling this through to my form in my template in the following way:
<form action="{% pageurl page %}" method="POST" class="lm-ls1" id="feedback-form">
{% csrf_token %}
{{ form.question1.help_text }} <!-- Simpler non interable way -->
{{ form.question1.before_input }}
<p>---------------</p>
{% for row in form.fields.values %}
{{row.choices}}
<p>---------------</p>
{{row.help_text}}
<p>---------------</p>
{{row.before_input}}
{% endfor %}
</form>
But I am only getting html output for the form panels excluding the before_input and after_input ones
I am getting through roughly the following:
Overall, how did you feel about the service you received today?
---------------
[('Very satisfied', 'Very satisfied'), ('Satisfied', 'Satisfied'),
('Neither satisfied nor dissatisfied', 'Neither satisfied nor dissatisfied'), ('Dissatisfied', 'Dissatisfied'), ('Very dissatisfied', 'Very dissatisfied')]
---------------
Overall, how did you feel about the service you received today?
---------------
---------------
How can I access the before_input field panel data stored in the _formfield wagtail table?
Bit late but hopefully this still helps you or someone else out there.
How Wagtail Forms Work
Wagtail forms provided to the view context for AbstractFormPage models is a fully instanced Django Form. This means that you will only ever find values in the form that can be given to a Django Form.
This includes fields, which are instances of Django's Fields (eg. CharField) and there is no simple way to add additional attributes to these fields.
You can see how the Form object is built in the Wagtail FormBuilder class definition.
1 - Make a Custom Template Tag
A somewhat simple way to get additional attributes on your FormField (Wagtail's FormField) is using a template tag.
Create a new file in in a folder templatetags in your app, and build a simple_tag that will take the form_page, the field (which will be a Django Field instance) and a string of the attribute name you want to get.
# myapp/templatetags/form_tags.py
from django import template
from django.utils.html import mark_safe
register = template.Library()
#register.simple_tag(name='form_field_attribute')
def form_field_attribute(form_page, field, attribute_name, default=None):
"""Return attribute on FormField where field matches 'field' provided."""
# field is a django Field instance
field_name = field.name
results = [
# if html is stored, need to use mark_safe - be careful though.
mark_safe(getattr(form_field, attribute_name, default))
# get_form_fields() is a built in function on AbstractFormPage
for form_field in form_page.get_form_fields()
# clean_name is property on AbstractFormField used for Django Field name
if form_field.clean_name == field_name]
if results:
return results[0]
return default
2 - Revise your form_page.html Template
In your template, cycle through your form (this is the Django Form instance) and use the template helper to get you the extra attributes you need. Example below, passing in page or self will work the same as they are both the instance of your FormPage.
<form action="{% pageurl page %}" method="POST" role="form">
{% csrf_token %}
{% for field in form %}
<div>{% form_field_attribute page field 'before_input' %}</div>
{{ field }}
<div>{% form_field_attribute page field 'after_input' %}</div>
{% endfor %}
<input type="submit">
</form>

Muliple instances of modelform

I have a list in my template. For each item in the list, I have a {{ modelform }} that contains a checkbox. I can check the box and it updates as should. The problem is that when I check the box for one item and submit, it submits for all of the checkboxes because they are the same in each instance. Is there a way to set up a unique checkbox instance for each item in the list?
Current each modelform checkbox renders the same like this:
<input name="is_solution" type="checkbox" class="is_solution" id="is_solution">
I also tried using
test = request.POST.get('checkbox')
and
test = request.POST.get('checkbox')
thinking that using this I might be able to post an update in my view. I think I am going about this all wrong and I am lost. Essentially, I would like to have a checkbox on a list much like here on stackexchange where you can confirm an answer. Any suggestions?
You have to use form's prefix in the view like (just something unique for each form object):
def foo(request, ...):
objs = Model.objects.filter(...)
forms = []
for i, obj in enumerate(objs):
form = ModelForm(instance=obj, prefix=str(i))
forms.append(form)
...
This will make sure each form has unique identifier, hence you will be able to submit a specific form.
And you can render the forms like usual in the template:
<form ...>
{% csrf_token %}
{% for form in forms %}
{{ form }}
{% endfor %}
</form>