Validating GET Params with WTForms in Flask - python

I have spent a couple of days trying to get WTForms to validate my request.args, but I just can not get form.validate() to return True.
The idea is that I have a simple text field for user input in a WTForm as shown below.
form.py
class SearchForm(FlaskForm):
q = StringField('q',
validators=[])
search = SubmitField('Search')
def validate_q(self, q):
if q.data not in allowed_values: #"allowed_values" is just a list I want to check against
raise ValidationError('')
search.html
<form method="GET" action="{{ url_for('finance.search') }}">
<div class="col-9 col-md-5 p-0 m-0">
{% if form.q.errors %} {{ form.q(class="form-control form-control-md is-invalid") }}
<div class="invalid-feedback">
{% for error in form.q.errors %}
<span>{{ error }}</span> {% endfor %}
</div>
{% else %} {{ form.q(class="form-control form-control-md") }} {% endif %}
</div>
<div class="col-2 col-md-2 p-0">
{{ form.search(class="btn btn-md btn-dark") }}
</div>
</form>
routes.py
#finance.route('/finance/search')
def search():
form = SearchForm(request.args)
print(form.validate()) #always gives false
The HTML code for the form is included on several templates and submitting the form always directs to the search route that is shown below. I tried following WTForms documentation and passed in request.args into the form. When I ran the .validate() on the object, the validate function for the q parameter also executed, but for some reason .validate() always returns False.
Can anyone please elaborate on why that might be? I know I can use post request, or add a custom validation function inside the route, but I want to avoid workarounds if possible.
(stack-overflow seems to use a similar type of architecture for their search http://127.0.0.1:8000/finance/search?q=aapl&search=Search vs https://stackoverflow.com/search?q=aapl and I want to follow that if possible.)
Thanks!

I actually just figured out that the error was happening because I was not including a crsf_token in the form. The token is not needed since the its a get request, but this needs to be explicitly stated with meta = {'csrf': False}.
#finance.route('/finance/search')
def search():
form = SearchForm(request.args, meta={'csrf': False})
print(form.validate()) #Now gives True if validation function does not raise error

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.

How to access variables within html template url_for

I'm building a Netflix like website for my Devops course. I made a Python list of dictionaries (Mockfilms) to define my films, and want to populate a database (Ratings) with reviews in preparation for sending data in the format :filmid: :userid: :rating: to a recommendation engine.
My index page is a list of film images with a link to a review form under each one. I want each review form to appear on a different url (/review/ID where ID is saved in mockfilms as oid). In order to do this I want to access mockfilms.oid, then pass it to the view function to make the url for the form. Once the form is complete I then want to add this ID to the Ratings database. Here is what I have so far:
Index:
{% extends "base.html" %}
{% block content %}
<h1>Hello, {{ current_user.username }}! Welcome to our extensive video library:</h1>
{% for film in mockfilms %}
{% set ID = film.oid %}
<div>
<a href = {{ film.video }}>
<img src = {{ film.image }} alt = "doh" style = "width:200px;height:200px;border:0;">
</a>
</div>
<div>
">Leave a review here!
{% endfor %}
{% endblock %}
Route:
#app.route('/review/<ID>', methods = ['GET', 'POST'])
#login_required
def review(ID):
form = ReviewForm()
if form.validate_on_submit():
review = Ratings(User_id = current_user.id, Score_given = form.score.data, Film_id = ID)
db.session.add(review)
db.session.commit()
flash('Thanks for your review')
return redirect(url_for('index'))
return render_template('review.html', title='Review Page', form=form)
The following error is what I get when I run it:
File "/home/jc/Desktop/Lokal/DevopsAssig/microblog/Kilfinnan/lib/python3.5/site-packages/werkzeug/routing.py", line 1768, in build
raise BuildError(endpoint, values, method, self)
werkzeug.routing.BuildError: Could not build url for endpoint 'review'. Did you forget to specify values ['ID']?
From this I assume that the issue is with the ID variable within this template. My searchings and learnings led me to believe that {% set %} in the index template would let me declare the ID variable and then use it in the dynamic.
Try this:
{% block content %}
<h1>
Hello, {{ current_user.username }}!
Welcome to our extensive video library:
</h1>
{% for film in mockfilms %}
<div>
<a href="{{ film.video }}">
<img src="{{ film.image }}" alt="doh" style="width:200px;height:200px;border:0;" />
</a>
</div>
<div>
<a href="{{ url_for('review', ID=film.oid) }}">
Leave a review here!
</a>
</div>
{% endfor %}
{% endblock %}
Ultimately your solution was quite close, but it is not necessary to use the Jinja set command when you need to pass the variable into url_for() function using the keyword for the parameter. You could still do it using {% set ID = film.oid %} but it would be a bit superfluous.
Try to provide key=value arguments into your url_for function.
Something like this
">Leave a review here!
Also Flask have a great documentation, Flask docs

Flask WTF – Forms always redirect to root

I have created a simple Flask WTF form
class SequenceForm(Form):
sequence = StringField('Please enter a sequence in FASTA format', validators=[Required()])
submit = SubmitField('Submit')
and I have set up a route to make it appear on a page
#main.route('/bioinformatics')
def bioinformatics():
form = SequenceForm()
return render_template('bioinformatics.html', form=form)
It all works great (so far). When I point my browser to foo/bioinformatics, I see a page with a SequenceForm rendered. However, when I hit the Submit button, I am always taken back to the root page defined by #main.route('/').
How can I make the Submit button take me somewhere else? I would like to use validate_on_submit() and do stuff with the data entered in the form.
Thanks!
/Michael Knudsen
UPDATE (Code from bioinformatics.html)
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Bioinformatics{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello, Bioinformatics!</h1>
</div>
{{ wtf.quick_form(form) }}
{% endblock %}
You need to specify an action in the form in your html.
<form action="/url_which_handles_form_data" method="Post">
your code
</form>
make sure to give the correct path if you are using blueprints
Edit:
From https://github.com/mbr/flask-bootstrap/blob/master/flask_bootstrap/templates/bootstrap/wtf.html I found this part.
{% macro quick_form(form,
action="",
method="post",
extra_classes=None,
role="form",
form_type="basic",
horizontal_columns=('lg', 2, 10),
enctype=None,
button_map={},
id="") %}
So you can probably call
{{ wtf.quick_form(form, action="/fancy_url") }}
or
{{ wtf.quick_form(form, action=url_for("blueprint_name.fancy_url")) }}
Depending on where the view is located.
Thanks to Tim Rijavec and Zyber. I used a combination of your suggestions to come up with the following solution.
I added GET and POST to methods for the route
#main.route('/bioinformatics', methods=['GET', 'POST'])
def bioinformatics():
form = SequenceForm()
return render_template('bioinformatics.html', form=form)
and then I wrapped the wtf.quick_form call inside tags.
<form action="{{ url_for('main.bioinformatics') }}" method="POST">
{{ wtf.quick_form(form) }}
</form>
Now everything works beautifully. Thanks!

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.

How to realize a dynamic sidebar with Django?

I plan on creating a sidebar with changing elements (depending on the current url and authentication-status).
For example: The default sidebar shows a login and a tag cloud.
If a user is already logged in, I want to display a user menu.
If the current url is /tagcloud, I want to hide it from the sidebar.
Actually, I need a way which enables me to do something like this in a view:
def some_view(request):
if request.user.is_authenticated():
sidebar.remove('login')
sidebar.add('user_menu')
def tag_cloud(request):
sidebar.remove('tag_cloud')
Afterwards, I want to pass the sidebar (implicitly, without passing it to render_to_response) to the template where I have in mind to do something like this:
<div id="sidebar">
{{ sidebar }}
</div>
Is this possible?
You'd better do this in a context_processors.py file
That also mean you have to use a RequestContext when returning your views
def include_tagcloud(request):
if request.path == '/tagcould/':
tagcloud = Tags.objects.filter(active=True) #whatever
return {'tagcloud': tagcloud}
def include_login(request):
if request.user.is_authenticated():
loginform = MyLoginForm(request.POST)
#passing a Django form + POST data in the case of re-submit
return {'loginform' : loginform}
And then in your template :
{% if loginform %}
<form action="accounts/login/">
{{form.as_p}}
<input type="submit" name="Login">
</form>
{% endif %}
{% if tagcloud %}
{%for tag in tagcloud %}.....{%for}
{% endif %}
In this example the login form points to a fixed view,
if you want to catch the login form post on everyview, I don't know how to do
EDIT : if you don't use the CSRF features of Django, you could simply insert the login form in the base template without using any django form and pointing to a login view :
{% if user.is_authenticated %}
<form action="accounts/login/">
<input type="text" name="username"><input type="password" name="password">
<input type="submit" name="Login">
</form>
{% endif %}
Yeah, but you can use inheritance of templates as well as composition. Then include your sidebar in a parent template that is used/inherited from in all of your templates. Then it is easy to find the template for the sidebar: it's in a separate file.
Answer of #Dominique is correct but When you write something in context_processors that's load at any page of the website. That maybe makes a performance issue.
I think the right way to handle dynamic sidebar is simpletag and use where you need.
def get_sidebar():
tags = Tags.objects.filter(active=True)
latest_posts = Post.objects.all().order_by('-create_at')[:10]
html = render_to_string("sidebar.html", {
"tags": tags,
"latest_posts": latest_posts
})
return html
And now just use in template files:
<div class="col-md-4 sidebar">
{% get_sidebar %}
</div>
Also, you can pass request to simpletag to use user.is_authenticated for authenticated user access.

Categories

Resources