Flask form not validating when csrf token is included - python

I have a flask form that I am using to get input to pass into a class. When I am trying to submit it, it is not showing any error and just reloading the same page. I have made sure that the csrf token is included in the jinja template as well. Here is the code:
Forms.py
class addTripForm(FlaskForm):
location = StringField('Location', validators=[DataRequired()])
startdate = DateField('Start Date', format='%Y=%m-%d', validators=[DataRequired()])
enddate = DateField('End Date', format='%Y=%m-%d', validators=[DataRequired()])
submit = SubmitField('Submit')
app.py
def check_form(form):
print(form.errors)
if form.submit():
print("submitted")
if form.validate():
print("validated")
#app.route('/add', methods=['GET','POST'])
def add():
form = addTripForm(request.form)
check_form(form)
if form.validate_on_submit():
# if form is valid, use fields to create and store a trip
print("adding trip")
user.add_trip(form.location.data,
form.startdate.data,
form.enddate.data)
return redirect('/trips')
return render_template('add.html', form=form)
add.html
{% extends 'base.html' %}
{% block content %}
<body>
<h3> Select a location and date range</h3>
<form action="" method="POST" name="addtrip">
{{ form.csrf_token }}
{{ form.location.label }} {{form.location}}
{{ form.startdate.label }} {{ form.startdate(class='datepicker') }}
{{ form.enddate.label }} {{form.enddate(class='datapicker')}}
<p>
{{ form.submit(class='btn btn-primary') }}
</p>
</form>
</body>
{% endblock %}

You should change your DateFields' format to '%Y-%m-%d' from '%Y=%m-%d' and then your code will work. The date value is formatted as yyyy-mm-dd in the HTTP Request.

Related

Retrieve hx-include value from HTMX request with Flask

I'm trying to send a client side value to the server when a HTMX request is made. I can't seem to get the value using request.args.get('cardCount')or request.form.get('cardCount').
I'm using flask_htmx to catch HTMX requests server side and render partial html. I need to get the cardCount from the dom so I can increment the new card being added correctly. If I don't add 'cards-3-front' and 'cards-3-back' to the html id and name the wtforms FieldList will not save dynamically added textarea content to the db.
Here is the form:
# NOTECARD FORMS
class CardForm(Form):
front = TextAreaField('front', validators=[InputRequired()])
back = TextAreaField('back', validators=[InputRequired()])
class NoteForm(FlaskForm):
title = StringField('Title', validators=[InputRequired(), Length(max=35)])
cards = FieldList(FormField(CardForm), min_entries=3)
Here is the view:
#views.route('/create', methods=['GET', 'POST'])
#login_required
def create():
user_img = get_user_img()
form = NoteForm()
if htmx:
count = request.args.get('cardCount')
return render_template("./partials/notecard.html", count=count)
if request.method == 'POST':
if form.validate_on_submit():
fronts = [bleach.clean(card.front.data) for card in form.cards]
backs = [bleach.clean(card.back.data) for card in form.cards]
set = dict(zip(fronts, backs))
db.session.add(Notes(title=bleach.clean(form.title.data), content=set, user_id=current_user.id))
db.session.commit()
flash('New Notecard Set Created!', 'success')
return redirect(url_for('views.dashboard'))
else:
flash('All notecard fields must be filled in', 'warning')
return render_template("create.html", user_img=user_img, form=form)
Here is the htmx request I'm trying to send from the template:
<div name="cardCount">3</div>
<button hx-get="/create" hx-include="[name='cardCount']" hx-target="#newCards" hx-swap="beforeend" class="btn"></button>
It seems to me that your approach using hx-include fails due to the fact that you want to include the content of a div element and not the value of an input element.
Below is an example where you can add and remove form fields using htmx. The entire form is replaced by an htmx call.
I use an input field of type hidden to determine the number of input fields.
To determine whether fields should be added or removed, an additional header is used using hx-headers.
Depending on the data transmitted via htmx, the form is changed and replaced as a whole, with the entries made being retained.
Flask (app.py)
from flask import (
Flask,
redirect,
render_template,
request,
url_for
)
from flask_htmx import HTMX
from flask_wtf import FlaskForm, Form
from wtforms import (
FieldList,
FormField,
StringField,
TextAreaField
)
from wtforms.validators import (
DataRequired,
Length
)
app = Flask(__name__)
app.secret_key = 'your secret here'
htmx = HTMX(app)
class CardForm(Form):
front = TextAreaField('Front', validators=[DataRequired()])
back = TextAreaField('Back', validators=[DataRequired()])
class NoteForm(FlaskForm):
title = StringField('Title', validators=[DataRequired(), Length(min=3, max=35)])
cards = FieldList(FormField(CardForm), min_entries=1)
#app.route('/', methods=['GET', 'POST'])
def index():
form = NoteForm(request.form)
num_fields = max(0, request.form.get('count', 1, type=int))
if htmx:
form.validate()
hx_func = request.headers.get('Hx-Func', 'DEL')
if hx_func.lower() == 'del':
num_fields = max(1, num_fields - 1)
form.cards.min_entries = num_fields
form.cards.entries = form.cards.entries[:form.cards.min_entries]
else:
num_fields += 1
form.cards.min_entries = num_fields
while len(form.cards.entries) < form.cards.min_entries:
form.cards.append_entry()
return render_template('partials/form.html', **locals())
else:
if form.validate_on_submit():
print(form.title.data, form.cards.data)
# Use the form data here.
return render_template('index.html', **locals())
HTML Template (index.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Index</title>
</head>
<body>
{% include 'partials/form.html' %}
<script src="https://unpkg.com/htmx.org#1.8.5"></script>
</body>
</html>
HTML Template (partials/form.html)
<form name="my-form" method="post">
{{ form.csrf_token }}
<div>
{{ form.title.label() }}
{{ form.title() }}
{% if form.title.errors -%}
<ul>
{% for error in form.title.errors -%}
<li>{{ error }}</li>
{% endfor -%}
</ul>
{% endif -%}
</div>
{% for subform in form.cards -%}
{% for field in subform -%}
<div>
{{ field.label() }}
{{ field() }}
{% if field.errors -%}
<ul>
{% for error in field.errors -%}
<li>{{ error }}</li>
{% endfor -%}
</ul>
{% endif -%}
</div>
{% endfor -%}
{% endfor -%}
<input type="hidden" name="count" value="{{ num_fields }}" />
<button
type="button"
hx-post="/"
hx-target="[name='my-form']"
hx-headers='{"Hx-Func": "DEL"}'
hx-swap="outerHTML"
>Less</button>
<button
type="button"
hx-post="/"
hx-target="[name='my-form']"
hx-headers='{"Hx-Func": "ADD"}'
hx-swap="outerHTML"
>More</button>
<button type="submit">Submit</button>
</form>

Page is not redirecting and db.session.add() and db.session.commit() commands are not working

I've just started learning flask. I'm trying to handle a form for user registration. But inside my routes.py, the form's data isn't getting pushed to the database, and also the page isn't redirecting after the click on the submit button. There is no error or any warning. The action just does not get performed and the register page just gets re-loaded.
I'm attaching the codes of the files below. The demo is the folder where my HTML templates and .py files are saved.
#run.py : located outside the root(demo) folder
from demo import app
if __name__ == '__main__':
app.run(debug=True)
-------------------------------------------------------------------------------------
#__init__.py
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///demo.db'
app.config['SECRET_KEY'] = 'df159ba68c3577847ef3dfef'
db = SQLAlchemy(app)
from demo import routes
-------------------------------------------------------------------------------------
#model.py
from demo import db
class Item(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50), nullable=False, unique=True)
email = db.Column(db.String(100), unique=True)
password = db.Column(db.String(60), nullable=False)
def __repr__(self):
return f'Item {self.username}'
------------------------------------------------------------------------------------
#form.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
class RegisterForm(FlaskForm):
username = StringField(label='Username')
email = StringField(label='Email')
password = PasswordField(label='Password')
submit = SubmitField(label='Create Account')
-------------------------------------------------------------------------------------
#routes.py
from demo import app
from flask import render_template, redirect, url_for
from demo.model import Item
from demo.form import RegisterForm
from demo import db
#app.route('/')
#app.route('/home')
def hello_world():
return render_template('home.html', item_name="number")
#app.route('/info')
def second_page():
items = Item.query.all()
return render_template('info.html', items=items)
#app.route('/register', methods=['GET', 'POST'])
def registration_page():
form = RegisterForm()
if form.validate_on_submit():
user_to_create = Item(username=form.username.data,
email=form.email.data,
password=form.password.data)
db.session.add(user_to_create)
db.session.commit()
return redirect(url_for('second_page'))
return render_template('register.html', form=form)
-----------------------------------------------------------------------------------
#register.html
{% extends 'base.html' %}
{% block title %}
Registration page
{% endblock %}
{% block content %}
<form method="POST" >
{{ form.username.label() }}
{{ form.username(placeholder='Enter Your Username') }}
{{ form.email.label() }}
{{ form.email(placeholder='Enter Your Email') }}
{{ form.password.label() }}
{{ form.password(placeholder='Enter Your Password') }}
{{ form.submit() }}
</form>
{% endblock %}
----------------------------------------------------------------------------------
#info.html :The page from second_page() function in routes.py
{% extends 'base.html' %}
{% block title %}
This is Info page
{% endblock %}
{% block content %}
<h1>you are in the info page</h1>
<h1>Welcome to this page</h1>
<h1>Great day!</h1>
{% for item in items %}
<p>{{ item.username }} - {{ item.email }}</p>
{% endfor %}
{% endblock %}
These are the files from my project. Please help me clear this functionality error. Thanks in advance.
Your form is never validated because are you have not used csrf token.
You can try printing print(form.errors) before and after the if statement
[...]
form = RegisterForm()
print(form.errors)
if form.validate_on_submit():
user_to_create = Item(username=form.username.data,
email=form.email.data,
password=form.password.data)
db.session.add(user_to_create)
db.session.commit()
return redirect(url_for('second_page'))
print(form.errors)
return render_template('register.html', form=form)
first it will give no error but after submiting it will give no crsf token
In html You can add {{form.csrf_token}}
<form method="POST" >
{{ form.csrf_token }} // add this and this should work
{{ form.username.label() }}
{{ form.username(placeholder='Enter Your Username') }}
{{ form.email.label() }}
{{ form.email(placeholder='Enter Your Email') }}

Flask edit form submit truble

Objects
class MyObj:
def __init__(self, data_dict):
self.id = ''
self.name = ''
self.minDescription = ''
self.descriptions = ''
class MyObjForm(Form):
name = StringField('name')
minDescription = StringField('minDescription')
descriptions = TextAreaField('descriptions')
Routings
This good work in "POST" and "GET" mode. Submit button have a good reactions when click.
#app.route("/create", methods=["GET", "POST"])
#login_required
def create():
if request.method == 'POST':
form = MyObjForm(request.form)
if form.validate():
new_obj = MyObj(request.form)
return redirect(url_for("/"))
else:
return render_template('create.html', form=form)
else:
return render_template('create.html', form=MyObjForm())
When i route in "GET" mode my code is good work and view have old object data but click on submit button is not have any reactions.
#app.route("/edit/<id>", methods=["GET", "POST"])
#login_required
def edit(id):
if request.method == 'GET':
old_obj = d.get_by_id(id)
form = MyObjForm(obj=old_obj)
return render_template('create.html', form=form, id=id)
else:
#never entry
pass
HTML 'create.html'
<form method="post" role="form">
{{ form.csrf }}
{{ macros.render_field(form.name) }}
{{ macros.render_field(form.minDescription) }}
{{ macros.render_field(form.descriptions) }}
<input class="btn special" type="submit" value="Save"/>
</form>
I try this:
<form method="post" role="form" action="{{ url_for('edit', id=id) }}">
and this:
class MyObjForm(Form):
id = StringField()
any not have a progress :(
Wath wrong?
I identified the problem:
If not use args ('obj=new_obj') when MyObjForm create the save button call my route in post mode as well
When i published the question too simplified the description of the problem. Realy MyObjForm include BooleanField:
class MyObjForm(Form):
name = StringField('name')
minDescription = StringField('minDescription')
descriptions = TextAreaField('descriptions')
isArchive = BooleanField('Is not public')
I didn't notice the checkbox stopped showing up on the form for reasons unknown to me. The standard rendering method was used for output:
{{ macros.render_field2(form.isArchive) }}
{% macro render_field2(field) %}
{{ field.label }}
{{ field(**kwargs)|safe }}
{% if field.errors %}
{% for error in field.errors %}
{{ error }}
{% endfor %}
{% endif %}
{% endmacro %}
To solve the issue with the visualization applied the following:
<input type="checkbox" id="isArchive" name="isArchive">
<label for="isArchive">{{ form.isArchive.label }}</label>
{% for error in form.isArchive.errors %}
<li><font color="red">{{ error }}</font></li>
{% endfor %}
One last thing. Did not notice how, but the form also stopped working when you add an object, no longer pass validation. And the reason was {{ form.csrf }}} which had to be replaced with {{ form.csrf_token() }}. Still had to remove the validation parameters for BooleanField.
Now the problem is partially solved.I associate these issues with the installation of the flask-security component. Unfortunately, I was not able to make a normal display of the checkbox, but at least I can move on.
If anyone has any idea how these things can be related to each other or what I'm wrong please let me know. I do not want to leave gaps in knowledge.

Flask WTF forms terminate when a SelectField is added to the template

I have a wtf form in flask:
class CustomerForm(FlaskForm):
customer_id = StringField('System ID')
crm_id = IntegerField('ID', validators=[DataRequired()])
customer_name = StringField('Customer Name', validators=[DataRequired()])
alias = StringField('Alias', validators=[DataRequired()])
phone = StringField('Phone')
address = StringField('Address')
default_timezone = SelectField('Default Time Zone', validators=[DataRequired()],
choices=pytz.common_timezones
)
#customers_page.route('/customer/<customer_id>', methods=["GET"])
#customers_page.route('/customer/', defaults={'customer_id': '-1'}, methods=["GET"])
#flask_login.login_required
def customer_page(customer_id):
form = CustomerForm()
return render_template('customer.html',
form=form
)
The template looks like:
{% extends "main.html" %}
{% block content %}
<div class="app-content">
<h1>View/Edit/Delete Customer {{ customer.customer_id }}</h1>
<form action="{{ url_for('customers_page.customer_save') }}" method="post">
{{ form.crm_id(readonly=true) }}
{{ form.customer_name }}
{{ form.alias }}
{{ form.phone }}
{{ form.address }}
{{ form.default_timezone }}
{{ form.csrf_token }}
</form>
</div>
{% endblock %}
When I add the SelectField form.default_timezone to the template, browsing the page terminates the application with no errors, exceptions or debug messages. If I omit the SelectField it works fine! Any idea what's going wrong?
UPDATE
Just solved! It seems that you need to specify choices as an empty list or a list with the form [(a, b)] (list of tuples) to make it work. I changed the code to be like choices=[(x, x) for x in pytz.common_timezones] and it solves the problem.
Just solved! It seems that you need to specify choices as an empty list or a list with the form [(a, b)] (list of tuples) to make it work. I changed the code to be like
default_timezone = SelectField('Default Time Zone', validators=[DataRequired()],
choices=[(x, x) for x in pytz.common_timezones])
and it solves the problem.

Django - How do I delete single database entries on button click?

I'm trying to set up a user profile where you can enter skills. Entering the skills and save them in the databse already works. Now I want to give the user the opportunity to delete every single one of them with a button click. I tried posting the ID of each skill on button click in the URL and read it out in my view to get the item and delete it, but that does not work like I thought and I cant find out why...
MY VIEW
def profile_settings(request, id=None):
# get logged in user object from session
user_id = request.user.id
# get related userprofile
userprofile = UserProfile.objects.get(pk=user_id)
# get all skills of the userprofile
user_skills = Skill.objects.filter(user=userprofile)
if request.method == 'POST':
form = SkillForm(request.POST)
if 'delete-skill' in request.POST:
if id:
print(id)
skill = Skill.objects.get(pk=id).delete()
elif 'add-skill' in request.POST:
if form.is_valid():
# get data from form
name = form.cleaned_data['name']
category = form.cleaned_data['category']
rating = form.cleaned_data['rating']
# create new skill object for a user
new_skill = Skill(name=name, category=category, rating=rating, user=userprofile)
# save it in the database
new_skill.save()
else:
form = SkillForm()
return render(request, 'profile-settings.html', {'skillform': form, 'existing_skills': user_skills})
MY URLS
urlpatterns = [
url(r'^landing', views.landing, name='landing'),
url(r'^neuigkeiten', views.news, name='news'),
url(r'^profileinstellungen/', views.profile_settings, name='profileinstellungen'),
url(r'^profileinstellungen/(?P<id>\d+)/$', views.profile_settings, name='profileinstellungen'),
]
MY TEMPLATE
{% extends 'base.html' %}
{% block content %}
<form method="post" style="margin-top: 300px">
{% csrf_token %}
{{ skillform }}
<input type="submit" value="Hinzufügen" name="add-skill "/>
</form>
<form method="post">
{% csrf_token %}
<ul>
{% for skill in existing_skills %}
<li>{{ skill.name }}</li>
<input href="{% url 'profileinstellungen' id=skill.id%}" type="submit" value="Löschen" name="delete-skill"/>
{% endfor %}
</ul>
</form>
{% endblock %}
It does not post the ID of the skill in the URL. Is there an alternative approach?
Try to change this :
<input href="{% url 'profileinstellungen' id=skill.id%}" type="submit" value="Löschen" name="delete-skill"/>
by this :
<input href="{% url 'profileinstellungen' %}{{skill.id}}" type="submit" value="Löschen" name="delete-skill"/>
because the "url" will know automatically that "skill.id" was related to "id"
You have forgotten to include dollar signs at the end of the regexes in your URL patterns. It should be:
urlpatterns = [
url(r'^landing$', views.landing, name='landing'),
url(r'^neuigkeiten$', views.news, name='news'),
url(r'^profileinstellungen/$', views.profile_settings, name='profileinstellungen'),
url(r'^profileinstellungen/(?P<id>\d+)/$', views.profile_settings, name='profileinstellungen'),
]
In particular, the problem is the regex r'^profileinstellungen/. Without the dollar, it matches URLs like /profileinstellungen/4/, so the id is not passed to the view.
Secondly, it doesn't make sense to give the input an href attribute. Each input should be in a separate form, and you can set the form action to the URL you wish to submit the post request to.
<ul>
{% for skill in existing_skills %}
<form method="post" action="{% url 'profileinstellungen' id=skill.id%}">
{% csrf_token %}
<li>
{{ skill.name }}
<input type="submit" value="Löschen" name="delete-skill"/>
</li>
</form>
{% endfor %}
</ul>

Categories