Coverage for seedboxsync/front/views/settings/users.py: 85%
85 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-26 17:14 +0000
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-26 17:14 +0000
1#
2# Copyright (C) 2015-2026 Guillaume Kulakowski <guillaume@kulakowski.fr>
3#
4# For the full copyright and license information, please view the LICENSE
5# file that was distributed with this source code.
6#
7"""SeedboxSync Flask view for users management."""
9from flask import abort, redirect, render_template, request, url_for
10from werkzeug.security import generate_password_hash
11from werkzeug.wrappers.response import Response
12from seedboxsync.core import current_app
13from seedboxsync.core.database.models import User
14from seedboxsync.front.babel import gettext as _
15from seedboxsync.front.forms import EmptyCSRFForm, UserCreateForm, UserEditForm
16from seedboxsync.front.login_manager import login_required
17from seedboxsync.front.utils import toast
18from seedboxsync.front.views import bp_settings as bp
20settings_users_url = "settings.users"
21msg_logger_error = "Failed to save user."
22msg_flash_error = _("Failed to save user.")
23msg_flash_success = _("User saved successfully.")
26@bp.route("/users")
27@login_required # type: ignore[untyped-decorator]
28def users() -> str | Response:
29 """
30 Render the users management settings page.
32 Retrieves all registered users with their core metadata to display
33 in the administrative user table.
35 Returns:
36 str | Response: Rendered HTML template containing the users list.
37 """
38 users = User.select(User.id, User.username, User.email, User.origin, User.created, User.last_login)
40 return render_template("settings/users.html", users=users)
43@bp.route("/users/create", methods=["GET", "POST"])
44@login_required # type: ignore[untyped-decorator]
45def users_create() -> str | Response:
46 """
47 Render and process the user creation view.
49 Handles fetching user data, populating the creation form, verifying password
50 confirmations, hashing new passwords, and saving updates to the database.
52 Returns:
53 str | Response: Rendered HTML edit form template.
54 """
55 form = UserCreateForm()
56 if form.validate_on_submit():
57 # Check password 1 et 2
58 password2 = request.form.get("password2", "")
59 if form.password.data != password2:
60 form.password.errors.append(_("Passwords do not match.")) # pyright: ignore [reportAttributeAccessIssue]
61 else:
62 try:
63 user = User()
64 form.populate_obj(user)
65 if form.password.data:
66 user.password = generate_password_hash(form.password.data)
67 user.save()
68 toast(msg_flash_success, _("Users"), "success")
69 return redirect(url_for(settings_users_url))
70 except Exception as e:
71 current_app.logger.exception(msg_logger_error, exc_info=e)
72 toast(msg_flash_error, _("Users"), "danger")
74 return render_template("settings/users_edit.html", form=form, action=_("Create"))
77@bp.route("/users/<int:user_id>/edit", methods=["GET", "POST"])
78@login_required # type: ignore[untyped-decorator]
79def users_edit(user_id: int) -> str | Response:
80 """
81 Render and process the user edition view.
83 Handles fetching user data, populating the edition form, verifying password
84 confirmations, hashing new passwords, and saving updates to the database.
86 Args:
87 user_id (int): Database identifier of the user to edit.
89 Returns:
90 str | Response: Rendered HTML edit form template.
92 Raises:
93 HTTPException: 404 error if no user matches the given ID.
94 """
95 try:
96 user = User.get(User.id == user_id)
97 except User.DoesNotExist: # pyright: ignore [reportAttributeAccessIssue]
98 abort(404, f"User id {user_id} doesn't exist.")
100 form = UserEditForm(obj=user)
101 if request.method == "GET":
102 form.password.data = ""
104 if form.validate_on_submit():
105 # Check password 1 et 2
106 new_password = form.password.data
107 password2 = request.form.get("password2", "")
108 if new_password and new_password != password2:
109 form.password.errors.append(_("Passwords do not match.")) # pyright: ignore [reportAttributeAccessIssue]
110 else:
111 try:
112 form.populate_obj(user)
114 if new_password:
115 user.password = generate_password_hash(new_password)
116 else:
117 db_user = User.get_by_id(user_id)
118 user.password = db_user.password
120 user.save()
121 toast(msg_flash_success, _("Users"), "success")
122 return redirect(url_for(settings_users_url))
123 except Exception as e:
124 current_app.logger.exception(msg_logger_error, exc_info=e)
125 toast(msg_flash_error, _("Users"), "danger")
127 return render_template("settings/users_edit.html", form=form, action=_("Edit"))
130@bp.route("/users/<int:user_id>/delete", methods=["GET", "POST"])
131@login_required # type: ignore[untyped-decorator]
132def users_delete(user_id: int) -> str | Response:
133 """
134 Render and process the user delete view.
136 Handles fetching user data, populating the delete form, verifying password
137 confirmations, hashing new passwords, and saving updates to the database.
139 Args:
140 user_id (int): Database identifier of the user to edit.
142 Returns:
143 str | Response: Rendered HTML edit form template.
145 Raises:
146 HTTPException: 404 error if no user matches the given ID.
147 """
148 form = EmptyCSRFForm()
149 try:
150 user = User.get(User.id == user_id)
151 except User.DoesNotExist: # pyright: ignore [reportAttributeAccessIssue]
152 abort(404, f"User id {user_id} doesn't exist.")
154 if form.validate_on_submit():
155 try:
156 username = user.username
157 user.delete_instance()
158 toast(_("User '%(username)s' deleted successfully.") % {"username": username}, _("Users"), "success")
159 return redirect(url_for(settings_users_url))
160 except Exception as e:
161 current_app.logger.exception(msg_logger_error, exc_info=e)
162 toast(_("Failed to delete user."), _("Users"), "danger")
164 return render_template("settings/users_delete.html", form=form, user=user, action=_("Delete"))