I have been struggling around the WTF forms for quite a while now. But this error, never seems to go away. I When ever I try to run this code the form never validates
Views :
#bundle.route('/content/add/', methods=['GET', 'POST'])
#bundle.route('/content/add', methods=['GET', 'POST'])
#bundle.route('/content/edit/<posturl>/', methods=['GET', 'POST'])
#bundle.route('/content/edit/<posturl>', methods=['GET', 'POST'])
#fas_login_required
def addcontent(posturl=None):
form = CreateContent()
form_action = url_for('content.addcontent')
if posturl is not None:
content = Content.query.filter_by(slug=posturl).first_or_404()
form = CreateContent(obj=content)
if form.slug.data == posturl and request.method == 'POST' and form.validate():
form.populate_obj(content)
db.session.commit()
return redirect(url_for('content.addcontent',
posturl=posturl, updated="True"))
else:
if request.method == 'POST' and form.validate():
query = Content(form.title.data,
form.slug.data,
form.description.data,
form.media_added_ids.data,
form.active.data,
form.tags.data,
g.fas_user['username'],
form.type_content.data
)
try:
db.session.add(query)
db.session.commit()
# Duplicate entry
except Exception as e:
return str(e)
return redirect(url_for('content.addcontent',
posturl=form.slug.data, updated="True"))
else:
print "Please validate form"
return render_template('content/edit_content.html', form=form,
form_action=form_action, title="Create Content")
Form Class :
# -*- coding: utf-8 -*-
from flask.ext.wtf import Form
from wtforms import TextField, TextAreaField
from wtforms import BooleanField, SelectField, validators
from wtforms.validators import Required
__all__ = ['CreateContent']
class CreateContent(Form):
title = TextField(
'Title', [validators.Length(min=4, max=255)])
slug = TextField(
'Url-Slug', [validators.Length(min=4, max=255)])
description = TextAreaField('Content', [validators.Length(min=4)])
media_added_ids = TextField('media')
type_content = SelectField(u'Content Type',
[Required()],
choices=[('blog', 'Blog Post'),
('media', 'Lecture'),
('doc', 'Documentation')]
)
# Comma seprated media id's
active = BooleanField('Published')
tags = TextField('Tags', [Required()])
# Comma seprated tag id's
And my Template :
{% extends "base.html" %}
{% block title %}
{{ title }}
{% endblock %}
{% block content %}
{% from "_formhelpers.html" import render_field %}
<div id="Create Content">
<center><h3> {{ updated }} </h3></center>
<h2>{{ title }}</h2>
<form method="post" action="">
<fieldset>
<legend></legend>
{{ render_field(form.title) }}
{{ render_field(form.slug ) }}
{{ render_field(form.description ) }}
{{ render_field(form.media_added_ids)}}
{{ render_field(form.type_content) }}
{{ render_field(form.active) }}
{{ render_field(form.tags )}}
</fieldset>
<input type="submit" class="button" value="Save"/>
</form>
</div>
{% endblock %}
Any help will be highly apprieciated
If the CSFR tokens are activated in a flask application setting, a CSFR token is included in each form. If developer has activated the setting and not included it in the form template the flask WTF would automatically reject the request.
The solution to this problem was to the following tag :
{{form.hidden_tag()}}
Once added, a CSFR id is included in the request and sent to the views for validation by the WTForms.
If you haven't included this token no errors will appear in the form.errors dictionary. If you iterate over this dictonary no errors will be show, but the form.validate method will return false.
Flask-WTF adds the CSRF token automatically if it is activated in your Flask settings. If this setting is active and it isn't included in the form submission the submission will be rejected. The solution in this case is to add the hidden_tag field in the template so that it is included in the form submission.
{{form.hidden_tag()}}
Related
I have following scenario.
User fills out a form
If the user clicks the "continue" button and the form is valid the user will be redirected to a summary view
In the summary view the user checks the input again. He can either continue or go back.
If he continues the data will be saved in the database, if he goes back he can edit the form.
Since in step 4 the user is at the view summary I have to redirect him to the home view. I don´t want the user to fill out the entire form again, the previously entered data should be autofilled if he goes back.
Something special: I am using django-tagify2 for one input in the form to get tags rather then text. If the user goes back the tags should be rendered correctly in the tagify specific form.
So here are my files:
home.html
{% extends "messenger/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="message-container">
<form method="POST" autocomplete="off">
{% csrf_token %}
{{ form|crispy }}
<button name="sendmessage" class="btn btn-outline-info" type="submit">Continue</button>
</form>
</div>
{% endblock content %}
summary.html
{% extends "messenger/base.html" %}
{% block content %}
<h4>Zusammenfassung</h4>
<p><b>Empfänger: </b>{{ receiver }}</p>
<br>
<p><b>Betreff: </b>{{ subject }}</p>
<br>
<p><b>Nachricht: </b>{{ message }}</p>
<button>Edit</button>
<button>Continue</button>
{% endblock content %}
home view
#login_required(login_url='login')
def home(request):
if request.method == 'POST' and 'sendmessage' in request.POST:
message_form = MessageForm(request.POST)
if message_form.is_valid():
receiver_list = message_form['receiver'].value().split(';')
subject = message_form['subject'].value()
message = message_form['textmessage'].value()
#create sessions and send data to next view
session_receiver = receiver_list
request.session['session_receiver'] = session_receiver
session_subject = subject
request.session['session_subject'] = session_subject
session_message = message
request.session['session_message'] = session_message
return redirect('summary')
else:
message_form = MessageForm()
return render(request, 'messenger/home.html', {'form': message_form})
summary view
def summary(request):
receiver = request.session.get('session_receiver')
subject = request.session.get('session_subject')
message = request.session.get('session_message')
return render(request, 'messenger/summary.html', {'receiver':receiver,
'subject':subject,
'message':message})
So what is the best way to do this?
Can I use the session variables to set the fields in the form?
I don´t want to change the logic in the app. I want a home/summary/success view/template where I can loop as long is I want between home and summary until the user is happy with his entered form data
How about checking request.session when there is get request to home view? Then you can bind message_form = MessageForm() to session data.
You can check out htmx and django-htmx. You can do what you want easily without session by swapping HTML with context.
I played around with the session values and views and finally got a way to redirect to other views with prefilled form fields based on session values.
#login_required(login_url='login')
def home(request):
if request.method == 'POST' and 'sendmessage' in request.POST:
message_form = MessageForm(request.POST)
if message_form.is_valid():
ad_group = message_form['ad_group'].value().split(';')
ad_user = message_form['ad_user'].value().split(';')
subject = message_form['subject'].value()
message = message_form['textmessage'].value()
#create sessions and send data to next view
session_ad_group = ad_group
request.session['session_ad_group'] = session_ad_group
session_ad_user = ad_user
request.session['session_ad_user'] = session_ad_user
session_subject = subject
request.session['session_subject'] = session_subject
session_message = message
request.session['session_message'] = session_message
return redirect('summary')
else:
if request.session.get('session_subject'):
message_form = MessageForm(initial={'ad_group': request.session.get('session_ad_group'),
'ad_user': request.session.get('session_ad_user'),
'subject': request.session.get('session_subject'),
'textmessage': request.session.get('session_message')})
return render(request, 'messenger/home.html', {'form': message_form})
else:
message_form = MessageForm()
return render(request, 'messenger/home.html', {'form': message_form})
def summary(request):
ad_group = request.session.get('session_ad_group')
ad_user = request.session.get('session_ad_user')
subject = request.session.get('session_subject')
message = request.session.get('session_message')
if request.method == 'POST' and 'edit' in request.POST:
message_form = MessageForm(initial={'ad_group':ad_group, 'ad_user': ad_user,
'subject':subject, 'textmessage':message})
return render(request, 'messenger/home.html', {'form': message_form})
return render(request, 'messenger/summary.html', {'ad_group':ad_group,
'ad_user': ad_user,
'subject':subject,
'message':message})
Template
{% extends "messenger/base.html" %}
{% block content %}
<h2>Zusammenfassung</h2>
<div class="border-top pt-3">
<p><b>AD-Gruppe: </b>{{ ad_group }}</p>
<p><b>AD-User: </b>{{ ad_user }}</p>
<br>
<p><b>Betreff: </b>{{ subject }}</p>
<br>
<p><b>Nachricht: </b>{{ message }}</p>
<div class="buttoncontainer">
<form name="edit" action="" method="post">
{% csrf_token %}
<button class="btn edit_btn" formaction="{% url 'messenger-home' %}">Zurück</button>
</form>
<form name="senden" action="" method="post">
{% csrf_token %}
<button class="btn continue_btn" formaction="{% url 'send_messages' %}">Nachricht senden</button>
</form>
</div>
</div>
{% endblock content %}
When filling out a form I get "This field is required." even though all fields are filled in.
It doesn't have to do with setting required to False or anything like that, because all fields are required.
views.py
def upload(request):
if request.method == 'POST':
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid():
title = form.cleaned_data['title']
username = request.user.get_username()
category = form.cleaned_data['category']
handle_uploaded_file(request.FILES['file'],title,username,category)
return HttpResponseRedirect('')
else:
form = UploadFileForm()
return render(request, 'main/upload.html', {'form': form})
function
def handle_uploaded_file(f,title,username,category):
with open('/uploads/' + category + '/' + title, 'wb+') as destination:
for chunk in f.chunks():
destination.write(chunk)
forms.py
class UploadFileForm(forms.Form):
title = forms.CharField(max_length=50)
category = forms.CharField(max_length=50)
file = forms.FileField()
upload.html
{% extends 'base.html' %}
{% block title %}Upload{% endblock %}
{% block content %}
{% if user.is_authenticated %}
Uploading as: {{ user.username }}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit"/>
</form>
{% else %}
<p>You are not logged in</p>
login
{% endif %}
{% endblock %}
The error I get when filling out a form is: "This field is required"
Screenshot:
When I select a file and it throws the error it unselects whatever file I've selected, similar to how the password field is cleared when hitting sign up without completing every field.
The file isn't being submitted with the request because you didn't sent the correct enctype on the form element. Here are Django's docs concerning that.
<form method="post" enctype="multipart/form-data">
One way to verify this/debug it would be to print the form's data form.data, request.POST and/or request.FILES before the call to is_valid. Or verifying the request in a browser's dev tools.
I'm creating a survey application that displays the survey question and choices and allows the user to pick a choice through the Flask-WTForms package. The form uses a RadioField and seems to fail form.validate() when populating the choices attribute dynamically.
When I manually enter in the choices as such:
class SurveyAnswerForm(FlaskForm):
answers = RadioField('Answers',
coerce=str,
choices=[('18-25', '18-25'), ('26-35', '26-35')])
form.validate() returns True and there are no errors in form.error.
When I decide to populate the choices attribute dynamically (see below), form.validate() returns False and form.error returns:
{'answers': ['Not a valid choice']}.
I've been working at this for hours and am not sure why form.validate() returns False.
forms.py:
from flask_wtf import FlaskForm
from wtforms import RadioField
class SurveyAnswerForm(FlaskForm):
answers = RadioField('Answers',
coerce=str,
choices=[])
app.py:
#app.route('/survey/<int:survey_id>/questions', methods=['GET', 'POST'])
def survey_questions(survey_id):
survey = Survey.query.filter_by(id=survey_id).first()
page = request.args.get('page', 1, type=int)
questions = SurveyQuestion.query.filter_by(survey_id=survey_id)\
.order_by(SurveyQuestion.id)\
.paginate(page, 1, True)
for question in questions.items:
question_id = question.id
choices = QuestionChoices.query\
.join(SurveyQuestion,
and_(QuestionChoices.question_id==question_id,
SurveyQuestion.survey_id==survey_id)).all()
form = SurveyAnswerForm(request.form)
form.answers.choices = [(choice.choice, choice.choice)\
for choice in choices]
if request.method =='POST' and form.validate():
print('Successful POST')
next_url = url_for('survey_questions', survey_id=survey.id,
page=questions.next_num)\
if questions.has_next else None
prev_url = url_for('survey_questions', survey_id=survey.id,
page=questions.prev_num)\
if questions.has_prev else None
return render_template('survey_question.html',
survey=survey,
questions=questions.items,
choices=choices,
form=form,
next_url=next_url, prev_url=prev_url)
survey_question.html:
{% extends "layout.html" %}
{% block body %}
<h2>{{ survey.survey_title }}</h2>
{% for question in questions %}
<h3>{{ question.question }}</h3>
{% endfor %}
<form action="{{ next_url }}" method="POST">
{{ form.csrf_token }}
{{ form.answers(name='answer') }}
{% if prev_url %}
Back
{% endif %}
{% if next_url %}
<input type="submit" value="Continue">
{% else %}
Finish
{% endif %}
</form>
{% endblock %}
The problem was submitting a POST request with pagination. if the current link is /survey/1/question?page=2 the form will submit to /submit/1/question?page=3. To remedy this, I just created a separate route for submission and handled logic there.
I am learning flask and made a small application. Now I am trying to learn form. I used a simple code to validate a name request and it should give error when the field remains empty. But it isn't giving one.
Main file :
from flask import Flask, render_template
from flask.ext.moment import Moment
from flask.ext.wtf import Form
from wtforms import StringField, SubmitField, validators
import requests
import json
from datetime import datetime
app = Flask(__name__)
app.config['SECRET_KEY'] = 'abcd'
moment = Moment(app)
class Nameform(Form):
name = StringField("whats your name?", [validators.Required()])
submit = SubmitField('submit')
#app.route('/')
#app.route('/index')
def index():
api_call = requests.get('https://api.stackexchange.com/2.2/users/moderators?order=desc&sort=reputation&site=stackoverflow') # api call to stack for user with highest scores in des order
var_1 = json.loads(api_call.text)
var_2 = [{'link': value['link'], 'name': value['display_name'], 'user_id': value['user_id']} for value in var_1['items']]
return render_template('index.html', posts=var_2, current_time=datetime.utcnow())
#app.route('/user/<id>/<user_name>')
def user(id, user_name):
print id
api_call = requests.get('https://api.stackexchange.com//2.2/users/'+id+'/reputation?site=stackoverflow') # api call for reputation of click user
var_1 = json.loads(api_call.text)
return render_template('reputation.html', result=var_1, user_name=user_name)
#app.route('/test', methods=['GET', 'POST'])
def user_form():
name = None
form = Nameform()
if form.validate_on_submit():
name = form.name.data
form.name.data = ''
return render_template('test_form.html', form=form, name=name)
if __name__ == '__main__':
app.run(debug=True)
Template for rendering:
<div class="page-header">
<h1>Hello, {% if name!= None %}{{ name }}{% else %}Stranger{% endif %}!</h1>
</div>
<form method=post>
{{ form.name.label }} {{ form.name() }}
{{ form.submit() }}
</form>
Why it is not throwing any error? when the field remains empty
You can render the error messages using form.errors. Note that you're also missing your CSRF token, which is required for validation since you didn't disable WTF_CSRF_ENABLED, so I've added {{ form.csrf_token }}. See CSRF Protection.
<div class="page-header">
<h1>Hello, {% if name!= None %}{{ name }}{% else %}Stranger{% endif %}!</h1>
</div>
{% for field in form.errors %}
{% for error in form.errors[field] %}
<div class="error">{{ error }}</div>
{% endfor %}
{% endfor %}
<form method=post>
{{ form.csrf_token }}
{{ form.name.label }} {{ form.name() }}
{{ form.submit() }}
</form>
I think you have not included the novalidate attribute with form.
The novalidate attribute is used to tell the web browser to not apply validation to the fields in this form, which effectively leaves this task to the Flask application running in the server.
For Sample code
I am trying to create a contact form using flask but keep getting this error when the page is rendered.
'forms.ContactForm object' has no attribute 'hidden_tag'
Here are my files:
contact.html
{% extends "layout.html" %}
{% block content %}
<h2>Contact</h2>
<form action="{{ url_for('contact') }}" method=post>
{{ form.hidden_tag() }}
{{ form.name.label }}
{{ form.name }}
{{ form.email.label }}
{{ form.email }}
{{ form.subject.label }}
{{ form.subject }}
{{ form.message.label }}
{{ form.message }}
{{ form.submit }}
</form>
{% endblock %}
forms.py
from flask.ext.wtf import Form
from wtforms import Form, TextField, TextAreaField, SubmitField, validators
class ContactForm(Form):
name = TextField("Name", [validators.Required()])
email = TextField("Email",[validators.Required(), validators.email()])
subject = TextField("Subject", [validators.Required()])
message = TextAreaField("Message", [validators.Required()])
submit = SubmitField("Send")
routes.py
from flask import Flask, render_template, request
from forms import ContactForm
app = Flask(__name__)
app.secret_key = 'development key'
#app.route('/')
def home():
return render_template('home.html')
#app.route('/about')
def about():
return render_template('about.html')
#app.route('/contact', methods=['GET', 'POST'])
def contact():
form = ContactForm()
if request.method == 'POST':
return 'Form posted.'
elif request.method == 'GET':
return render_template('contact.html', form=form)
if __name__ == '__main__':
app.run(debug=True)
All the other page templates are working perfectly fine. Any advice would be awesome! Thanks for the help!
I just fixed this problem as well.
Your problem is that you imported Form twice, rendering your flask-wtf Form import useless.
from flask_wtf import Form
from wtforms import Form, TextField, TextAreaField, SubmitField, validators
# ^^^ Remove
Only the flask-wtf extension has the special Form class which can handle CSRF automatically / other stuff.
I tried to fix this, too.
After removing brackets "()" appended after hidden_tag, it works.
{{ form.hidden_tag }}
It took me some time to fix this.
First import Form, fields, bootstrap as:
from flask_wtf import Form
from wtforms import StringField #etc
from flask_bootstrap import Bootstrap
Config secret key and bootstrap
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret key'
Bootstrap(app)
create your form as your used to:
class ContactForm(Form):
name = TextField("Name", [validators.Required()])
email = TextField("Email",[validators.Required(), validators.email()])
subject = TextField("Subject", [validators.Required()])
message = TextAreaField("Message", [validators.Required()])
submit = SubmitField("Send")
Nothing special in the routing aswell, just return it normaly.
In the html:
{% extends "bootstrap/base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% if form %}
{{ wtf.quick_form(form, ) }}
{% endif %}
And that's it. Hope you find some (or all) of it useful.
Update for #Yuji'Tomita'Tomita answer :
You should import FlaskForm instead of Form
from flask_wtf import FlaskForm
from wtforms import TextField, TextAreaField, SubmitField, validators
The error that you're seeing is telling you that forms.ContactForm has no method called "hidden_tag". You're referencing that method on the 6th line of contact.html like this:
{{ form.hidden_tag() }}
According to the flask documentation, this is the correct way to implement CSRF protection.
I would start by removing the line that references "form.hidden_tag()", then see if your form works. Then go back and implement CSRF protection per those instructions from the documentation.