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 from routes import blueprint as routes_blueprint
app.register_blueprint(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.register_blueprint(forms_blueprint)
@app.errorhandler(404) @app.errorhandler(404)

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

View File

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

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 render_template('pages/user_edit.html', user=user)
return redirect(url_for('main.view_user', username=username)) return redirect(url_for('main.view_user', username=username))
# @blueprint.route('/blogs') # @blueprint.route('/blogs')
# def blogs(): # def blogs():
# return render_template('pages/blogs.html') # return render_template('pages/blogs.html')
@@ -77,12 +76,3 @@ def edit_user(username: str):
# @blueprint.route('/groups') # @blueprint.route('/groups')
# def groups(): # def groups():
# return render_template('pages/groups.html') # 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; border: 1px solid darkblue;
padding: 0.7em; padding: 0.7em;
} }
form .field { form field {
padding: 4px; padding: 4px;
} }
form .field .checkbox { form field .checkbox {
margin-left: auto; margin-left: auto;
} }
form button { form button {

View File

@@ -154,7 +154,7 @@ form {
padding: 0.7em; padding: 0.7em;
} }
.field { field {
padding: 4px; padding: 4px;
.checkbox { .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('main.edit_user', username=current_user.username) }}"><i class="fas fa-cog fa-1x"></i></a>
| <a href="{{ url_for('auth.logout') }}">Logout</a> | <a href="{{ url_for('auth.logout') }}">Logout</a>
{% else %} {% 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 %} {% endif %}
</div> </div>
</div> </div>

View File

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

View File

@@ -1,4 +1,6 @@
{% extends 'layouts/index.html' %} {% extends 'layouts/index.html' %}
{% from 'macros.html' import render_field %}
{% block content %} {% block content %}
{% with messages = get_flashed_messages() %} {% with messages = get_flashed_messages() %}
{% if messages %} {% if messages %}
@@ -7,29 +9,14 @@
</span> </span>
{% endif %} {% endif %}
{% endwith %} {% endwith %}
<form method="POST" action="{{ url_for('auth.login_post') }}" class="login-form"> <form method="POST" action="{{ url_for('auth.login') }}" class="login-form">
<div class="field"> {{ render_field(form.username) }}
<div class="control"> {{ render_field(form.password) }}
Username {{ render_field(form.remember_me) }}
<input class="input is-large" type="text" name="username" placeholder="Username" autofocus="true"> <p><input type=submit value=Login>
</div>
</div>
<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> </form>
<p class="form-subtext"> <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> </p>
{% endblock content %} {% endblock content %}

View File

@@ -1,45 +1,19 @@
{% extends 'layouts/index.html' %} {% extends 'layouts/index.html' %}
{% from "macros.html" import render_field %}
{% block content %} {% block content %}
{% with messages = get_flashed_messages() %} <form method=post class="login-form">
{% if messages %} <dl>
<span class="error-message"> {{ render_field(form.username) }}
{{ messages[0] }}. Go to <a href="{{ url_for('main.login') }}">login page</a>. {{ render_field(form.name) }}
</span> {{ render_field(form.password) }}
{% endif %} {{ render_field(form.confirm) }}
{% endwith %} {{ render_field(form.accept_tos) }}
</dl>
<form method="POST" action="{{ url_for('auth.signup_post') }}" class="login-form"> <p><input type=submit value=Register>
<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> </form>
<p class="form-subtext"> <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> </p>
{% endblock content %} {% endblock content %}