Flask_wtf - validate_on_submit() error on page load - python

Flask_wtf's validate_on_submit() is never True on visiting the page for the first time, so it always flashes the else part's (code below) Error message which is always an empty dict.
But the form validation and submission are working properly - the success flash message can be seen on a valid post. And the Error flash doesn't disappear after a valid submission.
Reproducible code:
# necessary import stmts & other stuff
class MyForm(FlaskForm):
sub = StringField(validators=[DataRequired("Choose the title")])
body = TextAreaField(validators=[DataRequired(),Length(min=20)])
subm = SubmitField('Submit')
app.config['SECRET_KEY'] = 'my key'
#app.route('/', methods=['GET','POST'])
def index():
fo = MyForm()
flash('Submitted:'+str(fo.is_submitted())) # False on first time visit
#flash('After Validate:'+str(fo.validate()))
if fo.validate_on_submit():
ex = mytable(bodys = fo.body.data, subs = fo.sub.data)
# DB session add & commit stmt here
flash('Submitted','success')
return redirect(url_for('index'))
else:
flash('After val Errors:'+str(fo.errors))
return render_template('index.html',form=fo)
If I un-comment fo.validate()...it flashes csrf_token': ['The CSRF token is missing.'] and the other data required error msgs but as shown below the html template has form.hidden_tag(). Also used {{ form.csrf_token }} instead of hidden_tag()...no success.
<form method="POST" action="">
{{ form.hidden_tag() }}
{{ form.sub }}
{{ form.body }}
{{ form.subm }}
</form>
Please help to get rid of the validation error on page load, Thank you

So on initial get you don't need to validate your form because there's no data yet, only do it when it's actually posted, like so:
if request.method == 'POST':
if fo.validate_on_submit():
# DB session add & commit stmt here
flash('Submitted', 'success')
return redirect(url_for('index'))
else:
flash('After val Errors:' + str(fo.errors))

Related

Remove the database exception error on a page reload

I am on Django Framework and implemented an interface where a user can do Insert and Update operations on a MySQL table.
MySQL student table does not allow duplicate entries, so if a user enters a duplicate key an Exception message is displayed. If, a user re-submits a successful query to the database the error message disappears.
What is the current issue:
If a user tries to reload the page after a bad query was sent to the database the error message is still displayed. On a page reload it returns a POST request, so it's submitting the duplicate entry again triggering the Exception
How can I stop this request from being submitting again on a page reload, since it seems on a page reload it re-submits the duplicate entry?
views.py
def generate_student_info(request):
# Retrieve inputted value
current_student_name = request.POST.get('student_name')
current_student_id = request.POST.get('student_id')
# Read the table from the database
connection = db.open_connection(read_only=False)
sql_str = f"""SELECT * FROM {students_table};"""
df = db.read(sql_statement=sql_str, connection=connection)
# creating a formset
insert_formset = formset_factory(StudentInfoForm, extra=0)
formset = insert_formset(request.POST or None, initial=[{'student_id': df['student_id'][i], 'student_name': df['student_name'][i]} for i in range(len(df))])
context = {'formset': formset, 'db_error_message': '', 'display_error_message': False}
if request.method == 'POST':
# Insert MySQL query
if 'student_name' in request.POST:
try:
insert_query = f"""INSERT INTO {students_table} (student_id, student_name) VALUES ({current_student_id},'{current_student_name}');"""
db.write(sql_statement=insert_query, connection=connection)
return HttpResponseRedirect("/student_info/")
except Exception as e:
context['db_error_message'] = e
context['display_error_message'] = True
print(e)
return render(request, 'student_info_table.html', context)
# Update MySQL query
else:
if formset.is_valid():
for form in formset:
if form.has_changed():
print(form.cleaned_data)
try:
update_query = f"""UPDATE {students_table} SET student_name='{form.cleaned_data['student_name']}' WHERE student_id='{form.cleaned_data['student_id']}';"""
db.write(sql_statement=update_query, connection=connection)
return HttpResponseRedirect("/student_info/")
except Exception as e:
context['db_error_message'] = e
context['display_error_message'] = True
print(e)
return render(request, 'student_info_table.html', context)
else:
print('formset is not valid')
context = {'formset': formset, 'db_error_message': '', 'display_error_message': False}
return render(request, 'student_info_table.html', context)
student_info_table.html
<form action='' method='post'>
{% csrf_token %}
<div>
<input type="button" value="Insert" id="id_insert" class="generate">
</div>
</form>
{% if display_error_message %}
<h4>{{db_error_message}}</h4>
{% endif %}
<form method="POST" enctype="multipart/form-data" id="insertion-formset">
<table>...</table>
</form>
Instead of sending error message through context use django messages framework. It was built to achieve what you want. To use this you send message from views as
messages.error(request, 'Display error message.')
You can also add multiple message for the request. It is also removed when page is reloaded. Check out document here.

werkzeug.routing.BuildError coming for Flask app

I got an error when trying to run my Flask app of: BuildError: Could not build url for endpoint 'result' with values ['resultFound']. Did you mean 'menu' instead?
The problem has to do with the calling of POST. I have attached the code for 4 files that relate to this error, but I left out the import packages and other parts of the files. I would greatly appreciate your help. Thanks a lot. If you would like any other code I could add it.
This is my main python function that is running the flask app.
#app.route("/search", methods=["POST", "GET"])
def search():
if request.method == "POST":
user = request.form["searching"]
return redirect(url_for('result', resultFound = user))
else:
return render_template("search.html")
app.route("/<resultFound>")
def result(resultFound):
return render_template('result.html', nameartist = artistName(resultFound), numfollowers = artistfollower(resultFound))
This is the python file that is getting the information for the results.html with the input value from the search.html.
def artists(searchinput):
searchResults = spotifyObject.search(searchinput,1,0,"artist")
artist = searchResults['artists']['items'][0]
return artist
def artistname(inputvalue):
value = artists(inputvalue)
artistName = value['name']
return artistName
def artistfollower(inputvalue):
value = artists(inputvalue)
artistfollowers = value['followers']['total']
return artistfollowers
This is the search.html that gets the input value.
<form action="#" method="post">
<input type="text" id="myText" name="searching" value="input artist">
<p><input type="submit" value="submit" /></p>
</form>
This is the result.html that is using the input value from search.html and getting data with the help of the python file.
<p>The artist {{ nameartist }} has {{ numfollowers }} followers.</p>
BuildError: Could not build url for endpoint 'result' with values ['resultFound'].
because you are missing # in result route decorator (#app and not app)
#app.route("/<resultFound>")
def result(resultFound):
[..]

Post to Django's definition from Advanced Rest Client

Posting values from Advanced Rest client to Django's definition returns "Forbidden(403)" alert
looks like CSRF token is missing in the header, What can be done to get rid of this issue? Below is my definition to receive the POST values
def saveToDb(request):
c = {}
c.update(csrf(request))
if request.method == 'POST':
form = RegisterForm(request.POST)
if form.is_valid():
form_unique_id = form.cleaned_data['form_id']
form_meta_data = form.cleaned_data['form_content']
meta_data = FormMetaData.objects.create(
form_id=form_unique_id,
form_content=form_meta_data
)
meta_data.save()
result = FormMetaData.objects.all()
return render(request, "form_saved.html", {'result': result})
There is no issue in the definition as it works well with form input
Post to Django From Advanced Rest Client with CSRF Token:
Set CSRF Token for the key "X-CSRFToken" in the Header Section, add the key-value pairs in the body section, Select the Content type as "application/x-www-form-urlencoded" and click the Send Button
Post to Django from Advanced Rest Client without CSRF Token: Add the key-value pairs in the body section, Select the Content type as "application/x-www-form-urlencoded" and click the Send Button.
Note:
Please make sure to set "#csrf_exempt" for the definition to which you post values
as shown below
You have to give {% csrf_token %} in your html;
<html>
<form method="post">
{% csrf_token %}
</form>
</html>

Flask putting form into URL

I've been working on a form that sends data to a scraper and simultaneously generates a URL from form input. The returned templates works flawlessly, but the URL change ends up giving me the entire form in the URL and I can't figure out why.
The URL ends up looking like this:
http://localhost/options/%3Cinput%20id%3D%22symbol%22%20name%3D%22symbol%22%20type%3D%22text%22%20value%3D%22%22%3E
I'd like it to look like this:
http://localhost/options/ABC
Form class:
class OptionsForm(Form):
symbol = StringField('Enter a ticker symbol:', validators=[Required(), Length(min=1, max=5)])
submit = SubmitField('Get Options Quotes')
Views:
# Where the form data ends up
#app.route('/options/<symbol>', methods=['GET', 'POST'])
def options(symbol):
# Created this try/except so I could test functionality - for example, I can do 'localhost/options/ABC' and it works
try:
symbol = request.form['symbol']
except:
pass
return render_template('options.html', symbol=symbol, company_data=OS.pull_data(symbol, name=True))
# Where the form lives
#app.route('/', methods=['GET', 'POST'])
def index():
form = OptionsForm()
print(form.errors)
if form.validate_on_submit():
return redirect(url_for('options', symbol=form.symbol.data))
return render_template('index.html', options_form=form)
Template:
<div id="options_div">
<form method="POST" name="symbol_form" action="{{ url_for('options', symbol=options_form.symbol) }}">
{{ options_form.hidden_tag() }}
{{ options_form.symbol(size=10) }}
{{ options_form.submit(size=10) }}
</form>
Any help would be appreciated.
Try adding enctype='multipart/form-data' to the form tag. It looks like your form is using application/x-www-form-urlencoded, the default.
Edit OK so check this out. When your template is being rendered there is no value in that data attribute (In the url_for call). When not referencing the data attribute (as your original question shows), you're referencing the actual form element (which is why you see all of that html being passed in the url). Here are your options (that I see):
Use some kind of frontend javascript to bind the form's action attribute to the value in the input box. Something like angular would help for this (but is overkill if you don't use any of its other features).
Just have the form POST to /options (no symbol in url). Then, grab the symbol attribute from the form data.

POSTing forms in Django's admin interface

I'm writing a Django admin action to mass e-mail contacts. The action is defined as follows:
def email_selected(self,request,queryset):
rep_list = []
for each in queryset:
reps = CorporatePerson.objects.filter(company_id = Company.objects.get(name=each.name))
contact_reps = reps.filter(is_contact=True)
for rep in contact_reps:
rep_list.append(rep)
return email_form(request,queryset,rep_list)
email_form exists as a view and fills a template with this code:
def email_form(request,queryset,rep_list):
if request.method == 'POST':
form = EmailForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
send_mail(
cd['subject'],
cd['message'],
cd.get('email','noreply#localboast'),['redacted#email.com'],
)
return HttpResponseRedirect('thanks')
else:
form = EmailForm()
return render_to_response('corpware/admin/email-form.html',{'form':form,})
and the template exists as follows:
<body>
<form action="/process_mail/" method="post">
<table>
{{ form.as_table }}
</table>
<input type = "submit" value = "Submit">
</form>
</body>
/process_mail/ is hardlinked to another view in urls.py - which is a problem. I'd really like it so that I don't have to use <form action="/process_mail/" method="post"> but unfortunately I can't seem to POST the user inputs to the view handler without the admin interface for the model being reloaded in it's place (When I hit the submit button with , the administration interface appears, which I don't want.)
Is there a way that I could make the form POST to itself (<form action="" method="post">) so that I can handle inputs received in email_form? Trying to handle inputs with extraneous URLs and unneeded functions bothers me, as I'm hardcoding URLs to work with the code.
You can use django's inbuilt url tag to avoid hardcoding links. see...
http://docs.djangoproject.com/en/dev/ref/templates/builtins/#url
Chances are you'd be better off setting up a mass mailer to be triggered off by a cron job rather than on the post.
Check out the answer I posted here
Django scheduled jobs
Also if you insist on triggering the email_send function on a view update perhaps look at
http://docs.djangoproject.com/en/dev/topics/signals/

Categories