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

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.""" 

8 

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 

19 

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.") 

24 

25 

26@bp.route("/users") 

27@login_required # type: ignore[untyped-decorator] 

28def users() -> str | Response: 

29 """ 

30 Render the users management settings page. 

31 

32 Retrieves all registered users with their core metadata to display 

33 in the administrative user table. 

34 

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) 

39 

40 return render_template("settings/users.html", users=users) 

41 

42 

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. 

48 

49 Handles fetching user data, populating the creation form, verifying password 

50 confirmations, hashing new passwords, and saving updates to the database. 

51 

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") 

73 

74 return render_template("settings/users_edit.html", form=form, action=_("Create")) 

75 

76 

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. 

82 

83 Handles fetching user data, populating the edition form, verifying password 

84 confirmations, hashing new passwords, and saving updates to the database. 

85 

86 Args: 

87 user_id (int): Database identifier of the user to edit. 

88 

89 Returns: 

90 str | Response: Rendered HTML edit form template. 

91 

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.") 

99 

100 form = UserEditForm(obj=user) 

101 if request.method == "GET": 

102 form.password.data = "" 

103 

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) 

113 

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 

119 

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") 

126 

127 return render_template("settings/users_edit.html", form=form, action=_("Edit")) 

128 

129 

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. 

135 

136 Handles fetching user data, populating the delete form, verifying password 

137 confirmations, hashing new passwords, and saving updates to the database. 

138 

139 Args: 

140 user_id (int): Database identifier of the user to edit. 

141 

142 Returns: 

143 str | Response: Rendered HTML edit form template. 

144 

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.") 

153 

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") 

163 

164 return render_template("settings/users_delete.html", form=form, user=user, action=_("Delete"))