Migrate app to use WTForms for auth form validation

- Not finished yet, major styling breakage in this commit
- Also encapsulated GET & POST requests of /login and /signup routes into one route.
This commit is contained in:
Xevion
2022-03-29 02:30:19 -05:00
parent 2e8688f59b
commit 3b5307ab47
11 changed files with 148 additions and 159 deletions

2
app.py
View File

@@ -44,7 +44,7 @@ def create_app():
from routes import blueprint as routes_blueprint
app.register_blueprint(routes_blueprint)
from forms import blueprint as forms_blueprint
from route_forms import blueprint as forms_blueprint
app.register_blueprint(forms_blueprint)
@app.errorhandler(404)

33
auth.py
View File

@@ -1,4 +1,4 @@
from flask import Blueprint, flash, redirect, request, url_for
from flask import Blueprint, flash, redirect, request, url_for, render_template
from flask_login import login_required, login_user, logout_user, current_user
from werkzeug.security import check_password_hash, generate_password_hash
@@ -14,7 +14,7 @@ def edit_profile_post(username: str):
form = EditProfileForm(request.form)
user = User.query.filter_by(username=username).first_or_404()
if current_user.is_admin or user.id == current_user.id
if current_user.is_admin or user.id == current_user.id:
if form.validate():
user.about_me = form.about_me.data
db.session.commit()
@@ -22,38 +22,45 @@ def edit_profile_post(username: str):
return redirect(url_for('main.view_user', username=user.username))
@blueprint.route('/login', methods=['POST'])
def login_post():
@blueprint.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm(request.form)
user = User.query.filter_by(username=form.data.username).first()
if request.method == 'POST' and form.validate():
user = User.query.filter_by(username=form.username.data).first()
# check if the user actually exists, and compare password given
if not user or not check_password_hash(user.password, form.password.data):
flash('Please check your login details and try again.')
return redirect(url_for('main.login'))
return redirect(url_for('auth.login'))
login_user(user, remember=form.remember.data)
return redirect(url_for('main.index'))
return redirect(url_for('auth.index'))
return render_template('pages/auth/login.html', form=form)
@blueprint.route('/signup', methods=['POST'])
def signup_post():
@blueprint.route('/signup', methods=['GET', 'POST'])
def signup():
# validate and add user to db
form = RegistrationForm(request.form)
if request.method == 'POST' and form.validate():
user = User.query.filter_by(username=form.username.data).first() # Check if the username is already in use
if user: # redirect back to sign-up page
flash('This username is already in use.')
return redirect(url_for('main.signup'))
return redirect(url_for('auth.signup'))
# Create new user with form data
new_user = User(username=form.username.data, name=form.name.data, password=generate_password_hash(form.password.data, method='sha256'))
new_user = User(username=form.username.data, name=form.name.data,
password=generate_password_hash(form.password.data, method='sha256'))
# Add new user to db
db.session.add(new_user)
db.session.commit()
return redirect(url_for('main.login'))
return redirect(url_for('auth.login'))
return render_template('pages/auth/signup.html', form=form)
@blueprint.route('/logout')

View File

@@ -1,63 +1,31 @@
from flask import Blueprint, flash, redirect, request, url_for
from flask_login import current_user, login_required
from profanity_filter import ProfanityFilter
from database import db
from models import User, Post, Comment
blueprint = Blueprint('forms', __name__)
pf = ProfanityFilter()
from wtforms import Form, BooleanField, StringField, PasswordField, validators
@blueprint.route('/user/<username>/edit', methods=['POST'])
@login_required
def edit_profile_post(username):
user = db.session.query(User).filter_by(username=username).first_or_404()
# Allow admins to edit profiles, but deny other users
if not current_user.is_admin and current_user.id != user.id:
return redirect(url_for('main.user', username=username))
user.about_me = request.form.get('about-me', user.about_me)
user.name = request.form.get('name', user.name)
db.session.commit()
flash('Successfully updated profile.')
return redirect(url_for('main.edit_user', username=username))
class RegistrationForm(Form):
username = StringField('Username', [validators.Length(min=4, max=25)])
name = StringField('Name', [validators.Length(min=2, max=35)])
password = PasswordField('New Password', [
validators.DataRequired(),
validators.EqualTo('confirm', message='Passwords must match')
])
confirm = PasswordField('Repeat Password')
accept_tos = BooleanField('I accept the TOS', [validators.DataRequired()])
@blueprint.route('/feed/new', methods=['POST'])
@login_required
def new_post():
post_text = request.form.get('text')
post = Post(author=current_user.id, text=post_text)
db.session.add(post)
db.session.commit()
return redirect(url_for('main.view_post', post_id=post.id))
class LoginForm(Form):
username = StringField('Username', [validators.DataRequired()])
password = StringField('Password', [validators.DataRequired()])
remember_me = BooleanField('Remember Me', [validators.Optional()])
@blueprint.route('/feed/<post_id>/comment', methods=['POST'])
@login_required
def add_comment(post_id: int):
post = Post.query.get_or_404(post_id)
class EditProfileForm(Form):
name = RegistrationForm.name
about_me = StringField('About Me', [validators.Optional()])
comment_text: str = request.form.get('comment-text')
if len(comment_text) > 50:
flash('Cannot have more than 50 characters of text.')
return redirect(url_for('main.view_post', post_id=post_id))
elif len(comment_text) < 5:
flash('Your comment must have at least 5 characters of text.')
return redirect(url_for('main.view_post', post_id=post_id))
class NewPostForm(Form):
text = StringField('Text', [validators.Length(min=15, max=1000)])
if not pf.is_clean(comment_text):
flash('Sorry, profanity is not allowed on runnerspace.')
return redirect(url_for('main.view_post', post_id=post_id))
comment = Comment(post=post.id, author=current_user.id, text=comment_text)
db.session.add(comment)
db.session.commit()
return redirect(url_for('main.view_post', post_id=post.id))
class NewCommentForm(Form):
text = StringField('Text', [validators.Length(min=5, max=50)])

61
route_forms.py Normal file
View File

@@ -0,0 +1,61 @@
from flask import Blueprint, flash, redirect, request, url_for
from flask_login import current_user, login_required
from profanity_filter import ProfanityFilter
from forms import RegistrationForm, EditProfileForm, NewPostForm, NewCommentForm
from database import db
from models import User, Post, Comment
blueprint = Blueprint('forms', __name__)
pf = ProfanityFilter()
@blueprint.route('/user/<username>/edit', methods=['POST'])
@login_required
def edit_profile_post(username):
user = db.session.query(User).filter_by(username=username).first_or_404()
# Allow admins to edit profiles, but deny other users
if not current_user.is_admin and current_user.id != user.id:
return redirect(url_for('main.view_user', username=username))
form = RegistrationForm(request.form)
if form.validate():
user.about_me = form.about_me.data
user.name = form.name.data
db.session.commit()
flash('Successfully updated profile.')
return redirect(url_for('main.edit_user', username=username))
@blueprint.route('/feed/new', methods=['POST'])
@login_required
def new_post():
form = NewPostForm(request.form)
if form.validate():
post = Post(author=current_user.id, text=form.text.data)
db.session.add(post)
db.session.commit()
return redirect(url_for('main.view_post', post_id=post.id))
else:
redirect(url_for('main.feed'))
@blueprint.route('/feed/<post_id>/comment', methods=['POST'])
@login_required
def add_comment(post_id: int):
post = Post.query.get_or_404(post_id)
form = NewCommentForm(request.form)
if form.validate():
if not pf.is_clean(form.text.data):
flash('Sorry, profanity is not allowed on runnerspace.')
return redirect(url_for('main.view_post', post_id=post_id))
comment = Comment(post=post.id, author=current_user.id, text=form.text.data)
db.session.add(comment)
db.session.commit()
return redirect(url_for('main.view_post', post_id=post.id))

View File

@@ -68,7 +68,6 @@ def edit_user(username: str):
return render_template('pages/user_edit.html', user=user)
return redirect(url_for('main.view_user', username=username))
# @blueprint.route('/blogs')
# def blogs():
# return render_template('pages/blogs.html')
@@ -77,12 +76,3 @@ def edit_user(username: str):
# @blueprint.route('/groups')
# def groups():
# return render_template('pages/groups.html')
@blueprint.route('/login', methods=['GET'])
def login():
return render_template('pages/auth/login.html')
@blueprint.route('/signup', methods=['GET'])
def signup():
return render_template('pages/auth/signup.html')

View File

@@ -131,10 +131,10 @@ form.login-form {
border: 1px solid darkblue;
padding: 0.7em;
}
form .field {
form field {
padding: 4px;
}
form .field .checkbox {
form field .checkbox {
margin-left: auto;
}
form button {

View File

@@ -154,7 +154,7 @@ form {
padding: 0.7em;
}
.field {
field {
padding: 4px;
.checkbox {

View File

@@ -11,7 +11,7 @@
<a href="{{ url_for('main.edit_user', username=current_user.username) }}"><i class="fas fa-cog fa-1x"></i></a>
| <a href="{{ url_for('auth.logout') }}">Logout</a>
{% else %}
<a href="{{ url_for('main.login') }}">Login</a> or <a href="{{ url_for('main.signup') }}">Sign-up</a>!
<a href="{{ url_for('auth.login') }}">Login</a> or <a href="{{ url_for('auth.signup') }}">Sign-up</a>!
{% endif %}
</div>
</div>

View File

@@ -1,6 +1,7 @@
{% macro render_field(field) %}
<dt>{{ field.label }}
<dd>{{ field(**kwargs)|safe }}
<field>
<label>{{ field.label }}</label>
{{ field(**kwargs)|safe }}
{% if field.errors %}
<ul class=errors>
{% for error in field.errors %}
@@ -8,5 +9,6 @@
{% endfor %}
</ul>
{% endif %}
</dd>
<br>
</field>
{% endmacro %}

View File

@@ -1,4 +1,6 @@
{% extends 'layouts/index.html' %}
{% from 'macros.html' import render_field %}
{% block content %}
{% with messages = get_flashed_messages() %}
{% if messages %}
@@ -7,29 +9,14 @@
</span>
{% endif %}
{% endwith %}
<form method="POST" action="{{ url_for('auth.login_post') }}" class="login-form">
<div class="field">
<div class="control">
Username
<input class="input is-large" type="text" name="username" placeholder="Username" autofocus="true">
</div>
</div>
<form method="POST" action="{{ url_for('auth.login') }}" class="login-form">
{{ render_field(form.username) }}
{{ render_field(form.password) }}
{{ render_field(form.remember_me) }}
<p><input type=submit value=Login>
<div class="field">
<div class="control">
Password
<input class="input is-large" type="password" name="password" placeholder="Password">
</div>
</div>
<div class="field">
<label class="checkbox">
<input type="checkbox">
Remember me
</label>
</div>
<button class="button">Login</button>
</form>
<p class="form-subtext">
Don't have a login? <a href="{{ url_for('main.signup') }}">Sign-up</a> instead!
Don't have a login? <a href="{{ url_for('auth.signup') }}">Sign-up</a> instead!
</p>
{% endblock content %}

View File

@@ -1,45 +1,19 @@
{% extends 'layouts/index.html' %}
{% from "macros.html" import render_field %}
{% block content %}
{% with messages = get_flashed_messages() %}
{% if messages %}
<span class="error-message">
{{ messages[0] }}. Go to <a href="{{ url_for('main.login') }}">login page</a>.
</span>
{% endif %}
{% endwith %}
<form method="POST" action="{{ url_for('auth.signup_post') }}" class="login-form">
<div class="field">
<div class="control">
Username
<input class="input" type="text" name="username" placeholder="Username" autofocus>
</div>
</div>
<div class="field">
<div class="control">
Name
<input class="input" type="text" name="name" placeholder="Name" autofocus>
</div>
</div>
<div class="field">
<div class="control">
Password
<input class="input" type="password" name="password" placeholder="Password">
</div>
</div>
<div class="field">
<div class="control">
Confirm Password
<input class="input" type="password" name="confirm" placeholder="Confirm Password">
</div>
</div>
<button>Sign Up</button>
<form method=post class="login-form">
<dl>
{{ render_field(form.username) }}
{{ render_field(form.name) }}
{{ render_field(form.password) }}
{{ render_field(form.confirm) }}
{{ render_field(form.accept_tos) }}
</dl>
<p><input type=submit value=Register>
</form>
<p class="form-subtext">
Already have a login? <a href="{{ url_for('main.login') }}">Login</a> instead!
Already have a login? <a href="{{ url_for('auth.login') }}">Login</a> instead!
</p>
{% endblock content %}