feat(usermanager): add user edit view

This commit is contained in:
Julian Lobbes 2022-11-16 19:01:37 +01:00
parent 8925b00109
commit 59316042fb
12 changed files with 130 additions and 7 deletions

View File

@ -13,7 +13,9 @@ services:
- ./lumi2/ldap.py:/app/lumi2/ldap.py:ro
- ./lumi2/usermodel.py:/app/lumi2/usermodel.py:ro
- ./lumi2/usermanager.py:/app/lumi2/usermanager.py:ro
- ./lumi2/static/:/app/lumi2/static/:ro
- ./lumi2/static/css:/app/lumi2/static/css:ro
- ./lumi2/static/images/base:/app/lumi2/static/images/base:ro
- ./lumi2/static/images/default:/app/lumi2/static/images/default:ro
- ./lumi2/templates/:/app/lumi2/templates/:ro
ports:
- "8000:80"

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 157 KiB

After

Width:  |  Height:  |  Size: 157 KiB

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -13,16 +13,25 @@
<meta property="og:type" content="website">
<meta property="og:url" content="{{ config.SITE_URL }}">
<meta property="og:description" content="{{ config.SITE_DESCRIPTION }}">
<meta property="og:image" content="{{ url_for('static', filename='images/og.png') }}">
<meta property="og:image" content="{{ url_for('static', filename='images/base/og.png') }}">
<link rel="icon" href="{{ url_for('static', filename='images/favicon.ico') }}">
<link rel="icon" href="{{ url_for('static', filename='images/favicon.svg') }}" type="image/svg+xml">
<link rel="apple-touch-icon" href="{{ url_for('static', filename='images/apple-touch-icon.png') }}">
<link rel="icon" href="{{ url_for('static', filename='images/base/favicon.ico') }}">
<link rel="icon" href="{{ url_for('static', filename='images/base/favicon.svg') }}" type="image/svg+xml">
<link rel="apple-touch-icon" href="{{ url_for('static', filename='images/base/apple-touch-icon.png') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class=flash-message>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% block content %}
{% endblock content %}
</body>

View File

@ -0,0 +1,19 @@
{% extends 'base.html' %}
{% block content %}
<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="********">
<input type="submit" value="Update">
</form>
{% endblock content %}

View File

@ -1,14 +1,73 @@
"""Views for lumi2."""
from flask import (
Blueprint, render_template
Blueprint, render_template, abort, request, flash
)
import lumi2.ldap as ldap
from lumi2.usermodel import User, Group
bp = Blueprint('usermanager', __name__)
@bp.route('/')
def index():
"""Home page view."""
return render_template('usermanager/index.html')
@bp.route("/user/<string:username>", methods=("GET", "POST"))
def user_detail(username: str):
"""Detail view for a specific User."""
try:
conn = ldap.get_connection()
except Exception:
abort(500)
try:
user = ldap.get_user(conn, username)
except ldap.EntryNotFoundException:
conn.unbind()
abort(404)
user._generate_static_images()
if request.method == 'POST':
form_is_valid = True
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
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'])
if form_is_valid:
ldap.update_user(conn, user)
flash("User information was updated!")
conn.unbind()
return render_template('usermanager/user_detail.html', user=user)

View File

@ -4,6 +4,7 @@ from string import ascii_lowercase, ascii_uppercase, digits, whitespace
from base64 import b64encode, b64decode
import hashlib
from binascii import Error as Base64DecodeError
from pathlib import Path
from PIL import Image
from flask import current_app
@ -260,7 +261,7 @@ class User:
The default user profile picture.
"""
image_path = f"{current_app.static_folder}/assets/default_user_icon.jpg"
image_path = f"{current_app.static_folder}/images/default/user.jpg"
return Image.open(image_path)
@ -304,6 +305,39 @@ class User:
self.picture = User._get_default_picture()
def _generate_static_images(self, force=False) -> None:
"""Generates the static images for this User's picture on disc.
The user's full profile picture and a thumbnail are written to
'static/images/user/<username>/full.jpg'
and 'static/images/user/<username>/thumbnail.jpg' respectively.
The thumbnail's fixed size is 512x512 px.
If the parameter force is set to True, existing images are overwritten.
Otherwise, if the images already exist on disk, image generation is skipped.
Parameters
----------
force : bool = False
Whether or not existing images on disk should be regenerated.
"""
path_to_image_folder = Path(current_app.static_folder) / "images" / "users" / self.username
path_to_full_image = path_to_image_folder / "full.jpg"
path_to_thumbnail = path_to_image_folder / "thumbnail.jpg"
if not path_to_image_folder.is_dir():
path_to_image_folder.mkdir(parents=True)
if not path_to_full_image.is_file() or force:
self.picture.save(path_to_full_image)
if not path_to_thumbnail.is_file() or force:
thumb = self.picture.copy()
thumb.thumbnail((256, 256))
thumb.save(path_to_thumbnail)
def get_dn(self) -> str:
"""Returns the LDAP DN for the DIT entry representing this User.