Single FlaskForm rendered multiple times on one page using FOR loop - python

I'm making an application where I need to render identical forms multiple times on one page (number of forms might be from 1 to 20). In the video below I'm using simple for loop over a range from 0 to 5, just for a test. When I input data into the first form and submit it, all data duplicates to other forms but I need the user to submit data of each form independently from others. In other words, the user can fill in data in some forms (in 1, in 2 or in all forms) and submit those data separately.
Youtube video showing my problem
I have tried to change submit buttons manually with unique ids and names for each form but this didn't help.
I also tried to use Field Enclosures from WTForms but this also didn't help.
Here is my FlaskForm I haven't included all fields to save space
class EnvelopeForm(FlaskForm):
vme_name = StringField('VME')
outside_diameter = DecimalField('OD', validators=[DataRequired()])
submit = SubmitField('build envelope')
Here is my view handler
#app.route('/bokeh', methods=['GET', 'POST'])
def nonuniform_ellipse():
form = EnvelopeForm()
if form.validate_on_submit():
envelope = Envelopes(vme_name=form.vme_name.data,
outside_diameter=form.outside_diameter.data)
db.session.add(envelope)
db.session.commit()
return render_template('vme.html', form=form)
Here is HTML I haven't included all fields to save space.
{% for i in range(0, 5) %}
<form method="POST" action="" name="{{ i }}">
{{ form.hidden_tag() }}
<div>
{{ form.vne_name.label }}
{{ form.vme_name }}
</div>
<div>
{{ form.outside_diameter.label }}
{{ form.outside_diameter }}
</div>
<div>
{{ form.submit) }}
</div>
</form>
{% endfor %}

Here is a solution as per #ser-zhm comment. Now I can render whatever forms I have in DB separately on each other as it is shown on this image.
But when I submit data from one of the forms it is always updates the last record in DB and not the particular row. Lets say I have three empty forms rendered on the page and three records (rows) in my DB with NULL data in each column name referring to each form fields like it is shown here. When I fill in form #2, all the data is saved in row #3 instead of the row #2. So whatever form I fill in it is always the last record is updated.
How can I updated the particular row which is referring to the form ID?
View handler
#app.route('/envelope_forms_test', methods=['GET', 'POST'])
def nonuniform_ellipse():
form_ids = list(map(str, [id[0] for id in
Envelopes.query.with_entities(Envelopes.id).all()]))
forms = []
for form_id in form_ids:
form = EnvelopeForm(prefix=form_id)
forms.append(form)
for form in forms:
if form.submit.data and form.validate_on_submit():
Envelopes.query.filter_by(id=form_id).\
update({'vme_name': form.vme_name.data,
'outside_diameter': form.outside_diameter.data})
db.session.commit()
Here is HTML
{% for form in forms %}
<form method="POST" action="">
{{ form.csrf_token() }}
<div>
{{ form.vne_name.label }}
{{ form.vme_name }}
</div>
<div>
{{ form.outside_diameter.label }}
{{ form.outside_diameter }}
</div>
<div>
{{ form.submit) }}
</div>
</form>
{% endfor %}

Related

Multiple form on a page validation errors WTForms

I have multiple form on a page in modals: 1-st to create a new user address and n-forms with addresses which user created (created with loop). When i enter invalid data in fields with validators (e.g. datarequired), i have error messages in each form.
Here is the field render example which i use in every form:
{{ address_form.street.label(class_="form-label", for="InputStreet") }}
{{ address_form.street(class_="form-control", id="InputStreet") }}
{% for error in address_form.street.errors %}
<span style="color: red;">{{ error }}</span>
{% endfor %}
Part of the code from view.py:
#bp.route('/profile/address', methods=['GET', 'POST'])
#login_required
def address():
address_form = AddressForm()
if address_form.submit_address.data and address_form.validate():
address_to_add = Address(
street=address_form.street.data,
house=address_form.house.data,
building=address_form.building.data,
entrance=address_form.entrance.data,
floor=address_form.floor.data,
apartment=address_form.apartment.data,
additional_info=address_form.additional_info.data,
user=current_user)
db.session.add(address_to_add)
db.session.commit()
return redirect(url_for('profile.address'))
if address_form.edit_address.data and address_form.validate():
address_to_edit = Address.query.get(address_form.address_id.data) # Here is data from hidden field
# Editing data in DB
db.session.commit()
return redirect(url_for('profile.address'))
return render_template('profile/address.html', title='Адрес доставки', address_form=address_form)
Forms work fine with adding, editing and deleting data, but work incorrect with validation errors.
I think i need one more condition in if statenent related with hidden field or change something in my html file.
I've tried add an action attr in form like:
<form action="{{ url_for('profile.address', form_id=address.id) }}" method="post" novalidate>
And smth like this in view func but it doesn't work:
form_id = request.args.get('form_id', type=int)
if address_form.edit_address.data and address_form.validate() and form_id == address_form.address_id.data:
pass
Finally i found a very bad solution:
address_form = AddressForm()
form_id = request.args.get('form_id', 0, type=int)
For main form form_id always is 0.
Form tag for main form:
<form action="{{ url_for('profile.address', form_id=form_id) }}" method="post" novalidate>
And a field render for main form:
{{ address_form.street.label(class_="form-label", for="InputStreet") }}
{{ address_form.street(class_="form-control", id="InputStreet") }}
{% if form_id == 0 %}
{% for error in address_form.street.errors %}
<span style="color: red;">{{ error }}</span>
{% endfor %}
{% endif %}
If someone has a better solution about my problem it'd good. Now it's time to learn some JS and solve this problem with AJAX.

Form with multiple identical lines

I am writing a Flask app in which the user will have to enter all of the university courses they are planning to take for a semester. The data required is a code (e.g. 'CMPT') and a number (e.g. '101').
I want the user to be able to input up to 10 courses. I would like to do this by reusing the CourseForm I created, instead of manually creating the fields for each course. I have played around with FieldList and FormField to create a new form that uses the CourseForm, but I can't get it to display properly.
How can I create a form that has 10 slots for courses?
class CourseForm(Form):
code = SelectField('Course Code', choices=CHOICES)
number = IntegerField('Course Number')
#app.route(...)
def index():
form = CourseForm()
return render_template('main.html', title='Main', form=form)
main.html:
...
<form action="" method="post" name="course">
{{ form.hidden_tag() }}
<p>{{ form.code() }} {{ form.number() }}</p>
<p><input type="submit" value="Submit"></p>
</form>
...
You're on the right track with FieldList and FormField.
Start with your CourseForm.
class CourseForm(Form):
code = SelectField('Course Code', choices=CHOICES)
number = IntegerField('Course Number')
Then you'll need to encapsulate it inside another form. For the sake of this example I'll call it RegisteredCoursesForm. You can use max_entries to limit the number of entries to 10. If you always want 10 entries to be available, even if they won't all be filled in, you can also include min_entries.
class RegisteredCoursesForm(Form):
courses = FieldList(FormField(CourseForm), min_entries=10, max_entries=10)
# Any other fields can go here (e.g., user_id).
You'll want to pass this new form to your template instead of CourseForm.
def index():
form = RegisteredCoursesForm()
return render_template('main.html', title='Main', form=form)
Finally you can iterate over the courses field to get all of the fields contained without CourseForm.
<form action="" method="post" name="course">
{{ form.hidden_tag() }}
{% for course_form in form.courses %}
<p>{{ course_form.code }} {{ course_form.number }}</p>
{% endfor %}
<p><input type="submit" value="Submit"></p>
</form>

Using Django FormPreview the right way

My Goal
I have a django project with a form, and I want to display a preview page before the user submits.
The problem
I can display a preview page using a Django FormPreview, but not all form data is displayed properly. Specifically, if I have a field with choices, the string values of these choices aren't displayed. I'm also having problems applying template filters to date fields. The end result is that some data on the preview page is visible but other data is blank:
However, if I display the same data for posts that have actually been submitted, then everything displays properly:
My Code
models.py:
class Game(models.Model):
# Game Choices
FOOTBALL = 0
BASKETBALL = 1
TENNIS = 2
OTHER = 3
GAME_CHOICES = (
(FOOTBALL, 'Football'),
(BASKETBALL, 'Basketball'),
(TENNIS, 'Tennis'),
(OTHER, 'Other')
)
game_id = models.AutoField(primary_key=True)
location = models.CharField(max_length=200, verbose_name="Location")
game = models.IntegerField(choices=GAME_CHOICES, default=FOOTBALL)
game_date = models.DateField(verbose_name='Game Date')
forms.py
class GameForm(ModelForm):
class Meta:
model = Game
fields = (
'location',
'game',
'game_date'
)
I'm pretty sure that the problem is in my views.py: I'm not sure that I'm processing the POST request the right way to feed all data to the preview page.
views.py
def form_upload(request):
if request.method == 'GET':
form = GameForm()
else:
# A POST request: Handle Form Upload
form = GameForm(request.POST) # Bind data from request.POST into a GameForm
# If data is valid, proceeds to create a new game and redirect the user
if form.is_valid():
game = form.save()
return render(request, 'games/success.html', {})
return render(request, 'games/form_upload.html', {
'form': form,
})
preview.py
class GameFormPreview(FormPreview):
form_template = 'games/form_upload.html'
preview_template = 'games/preview.html'
def done(self, request, cleaned_data):
# Do something with the cleaned_data, then redirect
# to a "success" page.
return HttpResponseRedirect('/games/success')
form_upload.html
...
<form method="post">
{% csrf_token %}
<ul><li>{{ form.as_p }}</li></ul>
<button type="submit">Preview your post</button>
</form>
...
preview.html
{% load humanize %}
...
<h1>Preview your submission</h1>
<div>
<p>Location: {{ form.data.location }}</p>
<p>Game Date: {{ form.data.game_date|date:"l, F d, Y" }}</p>
<p>Game Type: {{ form.data.get_game_display }}</p>
</div>
<div>
<form action="{% url 'form_upload' %}" method="post">
{% csrf_token %}
{% for field in form %}
{{ field.as_hidden }}
{% endfor %}
<input type="hidden" name="{{ stage_field }}" value="2" />
<input type="hidden" name="{{ hash_field }}" value="{{ hash_value }}" />
<!-- Submit button -->
<button type="submit">Submit your post</button>
<!-- Go back button -->
<button type="submit">
<a href="{% url 'form_upload' %}"
onClick="history.go(-1);return false;" >
Go back and edit your post
</a>
</button>
</div>
</form>
</div>
...
Two issues
Essentially, I'm having these two issues:
String values for choices are not displayed. If I use the get_FOO_display() method in my preview.html template, it returns blank. However, if I use this in a page after the post has been submitted, it displays properly.
The humanize date filter doesn't work. If I apply a humanize filter ({{ form.data.game_date|date:"l, F d, Y" }}) in preview.html, it also displays blank. Again, this works for submitted posts.
My question essentially is: what's the right way to use the FormPreview here?
form.data does not have get_FOO_display attributes. When you access {{ form.data.get_game_display }} in the template, it fails silently and doesn't display anything.
The get_FOO_display are methods of the instance, so try this instead.
{{ form.instance.get_game_display }}
Wherever possible you should access data from form.cleaned_data (which is validated and 'cleaned') instead of form.data, which is the raw data submitted to the form.
The filters don't work with form.data.game_date because it's a raw string. They should work with form.cleaned_data.game_date, which has been converted to a python date object.
Finally, you haven't implemented anything in your done method, you've just copied the comment from the docs. You could create a new game using cleaned_data as follows:
def done(self, request, cleaned_data):
game = Game.objects.create(**cleaned_data)
return HttpResponseRedirect('/games/success')

How to use a WTForms FieldList of FormFields?

I'm building a website using Flask in which I use WTForms. In a Form I now want to use a FieldList of FormFields as follows:
class LocationForm(Form):
location_id = StringField('location_id')
city = StringField('city')
class CompanyForm(Form):
company_name = StringField('company_name')
locations = FieldList(FormField(LocationForm))
so to give people the ability to enter a company with two locations (dynamic adding of locations comes later) I do this on the front side:
<form action="" method="post" role="form">
{{ companyForm.hidden_tag() }}
{{ companyForm.company_name() }}
{{ locationForm.location_id() }}
{{ locationForm.city() }}
{{ locationForm.location_id() }}
{{ locationForm.city() }}
<input type="submit" value="Submit!" />
</form>
So on submit I print the locations:
print companyForm.locations.data
but I get
[{'location_id': u'', 'city': u''}]
I can print the values of the first location using the locationForm (see below), but I still don't know how to get the data of the second location.
print locationForm.location_id.data
print locationForm.city.data
So the list of locations does have one dict with empty values, but:
Why does the list of locations have only one, and not two dicts?
And why are the values in the location dict empty?
Does anybody know what I'm doing wrong here? All tips are welcome!
For starters, there's an argument for the FieldList called min_entries, that will make space for your data:
class CompanyForm(Form):
company_name = StringField('company_name')
locations = FieldList(FormField(LocationForm), min_entries=2)
This will setup the list the way you need. Next you should render the fields directly from the locations property, so names are generated correctly:
<form action="" method="post" role="form">
{{ companyForm.hidden_tag() }}
{{ companyForm.company_name() }}
{{ companyForm.locations() }}
<input type="submit" value="Submit!" />
</form>
Look at the rendered html, the inputs should have names like locations-0-city, this way WTForms will know which is which.
Alternatively, for custom rendering of elements do
{% for l in companyForms.locations %}
{{ l.form.city }}
{% endfor %}
(in wtforms alone l.city is shorthand for l.form.city. However, that syntax seems to clash with Jinja, and there it is necessary to use the explicit l.form.city in the template.)
Now to ready the submitted data, just create the CompanyForm and iterate over the locations:
for entry in form.locations.entries:
print entry.data['location_id']
print entry.data['city']
This is an old question, but still a good one.
I'd like to add a working Flask based example of a toy database (just a list of strings) with focus on the Python part - how to initialize the form with variable number of subforms and how to process the posted data.
This is the example.py file:
import flask
import wtforms
import flask_wtf
app = flask.Flask(__name__)
app.secret_key = 'fixme!'
# not subclassing from flask_wtf.FlaskForm
# in order to avoid CSRF on subforms
class EntryForm(wtforms.Form):
city = wtforms.fields.StringField('city name:')
delete = wtforms.fields.BooleanField('delete?')
class MainForm(flask_wtf.FlaskForm):
entries = wtforms.fields.FieldList(wtforms.fields.FormField(EntryForm))
submit = wtforms.fields.SubmitField('SUBMIT')
city_db = "Graz Poprad Brno Basel Rosenheim Torino".split() # initial value
#app.route("/", methods=['POST'])
def demo_view_function_post():
global city_db
form = MainForm()
if form.validate_on_submit():
city_db = [
entry['city'] for entry in form.entries.data
if entry['city'] and not entry['delete']]
return flask.redirect(flask.url_for('demo_view_function_get'))
# handle the validation error, i.e. flash a warning
return flask.render_template('demo.html', form=form)
#app.route("/")
def demo_view_function_get():
form = MainForm()
entries_data = [{'city': city, 'delete': False} for city in city_db]
entries_data.append({'city': '', 'delete': False}) # "add new" placeholder
form.process(data={'entries': entries_data})
return flask.render_template('demo.html', form=form)
This is the demo.html file:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Demo</title>
</head>
<body>
<h1>Subform demo</h1>
<p>Edit names / mark for deletion / add new</p>
<form method="post">
{{ form.csrf_token() }}
{% for entry in form.entries %}
{% if loop.last %}
<div>Add new:</div>
{% endif %}
<div>
{{ entry.city.label }} {{ entry.city() }}
{{ entry.delete() }} {{ entry.delete.label }}
</div>
{% endfor %}
{{ form.submit() }}
</form>
</body>
Run with: FLASK_APP=example flask run

How to send hiddenfield in wtf flask form for database insert

How do i do specify the many database field when in a wtf form, so i can insert a row in the database correctly. I need something like this in my template
{{ wtf.form_field(gform.GHF(value="{{ project.name }}")) }}
because I'm iterating over one (Projects) to many (Goals)
Project-(has many goals)
-goal-
and my goal form shows up multiple times.
{% for project in P %}
{% for pgoal in project.goals.all() %}
<li>
Goal: {{ pgoal.goal }}<br>
{% if loop.last %}
<form class="form form-horizontal" method="post" role="gform">
{{ gform.hidden_tag() }}
{{ wtf.form_errors(gform) }}
{{ wtf.form_field(gform.goal) }}
Help here? do i need a hiddenfield to know which project?
{{ wtf.form_field(gform.submit) }}<br>
and so on...
Once I have the correct project, I will use it in my view here
u=models.Projects.query.get(correct project?)
p=models.Goals(goal=gform.goal.data,proj=u)
I wouldn't do it with a hidden field. I'd make each form submit a little differently.
You should have something like
<form class="form form-horizontal" method="post" role="gform"
action="{{ url_for('add_goal_to_project', project_id=project.id) }}">
And the route would be
#app.route('.../<int:project_id>', methods=['POST'])
def add_goal_to_project(project_id):
gform = GForm(....)
if gform.validate_on_submit():
project = models.Projects.query.get(project_id)
goal = models.Goals(gform.goal.data, proj=project)
# Do anything else you need to do, such as adding and committing
# the new object
return redirect(...)
return render_template(...)
I'm skipping the details in the form creation, redirect and render_template calls, but this should get the idea across. Each goal form's action points to a route built from the project id.
You could extend this to allow for the editing of goals, and you'd be able to make it a lot better with some nice ajax posts as well.

Categories

Resources