refactor(usermanager): migrate to flask-wtf

This commit is contained in:
Julian Lobbes 2022-11-17 00:50:14 +01:00
parent 997327338e
commit 7b1196f09d
3 changed files with 75 additions and 103 deletions

View File

@ -4,18 +4,29 @@
<h1>User: {{ user.username }}</h1>
<img src="{{ url_for('static', filename='images/users/' + user.username + '/full.jpg') }}" alt="profile picture for user {{ user.username }}">
<form method="post" enctype="multipart/form-data">
<label for="email">Email</label>
<input name="email" id="email" type="email" placeholder="{{ user.email }}">
<label for="first_name">First name</label>
<input name="first_name" id="first_name" placeholder="{{ user.first_name }}">
<label for="last_name">Last name</label>
<input name="last_name" id="last_name" placeholder="{{ user.last_name }}">
<label for="display_name">Nickname</label>
<input name="display_name" id="display_name" placeholder="{{ user.display_name }}">
<label for="password">Password</label>
<input name="password" id="password" type="password" placeholder="********">
<label for="picture">Picture</label>
<input name="picture" id="picture" type="file" accept="image/jpeg">
{{ form.csrf_token }}
{{ form.email.label }}
{{ form.email }}
{{ form.first_name.label }}
{{ form.first_name }}
{{ form.last_name.label }}
{{ form.last_name }}
{{ form.display_name.label }}
{{ form.display_name }}
{{ form.password.label }}
{{ form.password }}
{{ form.password_confirmation.label }}
{{ form.password_confirmation }}
{{ form.picture.label }}
{{ form.picture }}
<input type="submit" value="Update">
</form>
{% endblock content %}

View File

@ -4,9 +4,13 @@ from pathlib import Path
from tempfile import TemporaryDirectory
from flask import (
Blueprint, render_template, abort, request, flash
Blueprint, render_template, abort, request, flash, redirect
)
from PIL import Image, UnidentifiedImageError
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed
from wtforms import ValidationError, StringField, PasswordField
from wtforms.validators import InputRequired, Email, EqualTo
import lumi2.ldap as ldap
from lumi2.usermodel import User, Group
@ -22,52 +26,39 @@ def index():
return render_template('usermanager/index.html')
class InvalidImageException(Exception):
"""Raised when an image's filename or contents are invalid."""
pass
class UserEditForm(FlaskForm):
@staticmethod
def validate_name(form, field) -> None:
if not User.is_valid_person_name(field.data):
raise ValidationError("Invalid name.")
def _get_image_from_uploaded_file(file) -> Image.Image:
"""Extracts a JPEG image from a file submitted via POST request.
The file's file extension and content is checked for validity as a JPEG image.
Parameters
----------
file
A file object taken from a POST request.
Returns
-------
PIL.Image.Image
A valid JPEG Image object.
Raises
------
InvalidImageException
When the file's file extension or contents are not valid for a JPEG image.
"""
def _file_extension_is_valid(filename: str):
allowed_extensions = ["jpg", "jpeg"]
if '.' not in filename:
return False
if filename.rsplit('.', 1)[1].lower() not in allowed_extensions:
return False
return True
if not _file_extension_is_valid(file.filename):
raise InvalidImageException("Invalid file extension.")
with TemporaryDirectory() as tempdir:
path_to_file = Path(tempdir) / "upload.jpg"
file.save(path_to_file)
try:
return Image.open(path_to_file, formats=['JPEG'])
except UnidentifiedImageError:
raise InvalidImageException(
"Image is either not a JPEG, or its contents are corrupted."
)
email = StringField(
'Email',
[InputRequired(), Email()]
)
first_name = StringField(
'First Name',
[InputRequired(), validate_name]
)
last_name = StringField(
'Last Name',
[InputRequired(), validate_name]
)
display_name = StringField(
'Nick Name',
[InputRequired(), validate_name]
)
password = PasswordField(
'Password',
[EqualTo('password_confirmation', message='Passwords must match')],
)
password_confirmation = PasswordField(
'Password (repeat)',
)
picture = FileField(
'Picture',
[FileAllowed(['jpg', 'jpeg'], 'JPEG images only.')]
)
@bp.route("/user/<string:username>", methods=("GET", "POST"))
@ -87,52 +78,19 @@ def user_detail(username: str):
user._generate_static_images()
if request.method == 'POST':
form_is_valid = True
# data = {
# "email": user.email,
# "first_name": user.first_name,
# "last_name": user.last_name,
# "display_name": user.display_name,
# }
if request.form['email']:
user.email = request.form['email']
if not User.is_valid_email(user.email):
flash("Invalid email address.")
form_is_valid = False
form = UserEditForm(obj=user)
if form.validate_on_submit():
conn.unbind()
if request.form['first_name']:
user.first_name = request.form['first_name']
if not User.is_valid_person_name(user.first_name):
flash("Invalid first name.")
form_is_valid = False
if request.form['last_name']:
user.last_name = request.form['last_name']
if not User.is_valid_person_name(user.last_name):
flash("Invalid last name.")
form_is_valid = False
if request.form['display_name']:
user.display_name = request.form['display_name']
if not User.is_valid_person_name(user.display_name):
flash("Invalid nickname.")
form_is_valid = False
if request.form['password']:
user.password_hash = User.generate_password_hash(request.form['password'])
new_picture = None
if 'picture' in request.files:
file = request.files['picture']
if len(file.filename):
try:
new_picture = _get_image_from_uploaded_file(file)
user.picture = new_picture
except InvalidImageException as e:
flash(f"Invalid picture: {e}")
form_is_valid = False
if form_is_valid:
ldap.update_user(conn, user)
flash("User information was updated!")
if new_picture is not None:
ldap.get_user(conn, user.username)._generate_static_images(force=True)
# TODO update user
return redirect(request.url)
conn.unbind()
return render_template('usermanager/user_detail.html', user=user)
return render_template('usermanager/user_detail.html', form=form, user=user)

View File

@ -3,3 +3,6 @@ ldap3==2.9.1
pytest==7.2.0
coverage==6.5.0
Pillow==9.3.0
WTForms==3.0.1
wtforms[email]==3.0.1
Flask-WTF==1.0.1