From 52825f70dea112ecc552dab81b8d12d6832351a6 Mon Sep 17 00:00:00 2001 From: mguschin Date: Wed, 13 May 2026 20:58:00 +0300 Subject: [PATCH] feat: admin can create users from /admin/users page Adds a dialog form on the users list. Validates email uniqueness, password length. Creates user as active with confirmed email. System role can assign admin role; admin role can only create users. Co-Authored-By: Claude Sonnet 4.6 --- web/routes/admin.py | 69 ++++++++++++++++++++++++++++++++++ web/templates/admin/users.html | 55 ++++++++++++++++++++++++++- 2 files changed, 123 insertions(+), 1 deletion(-) diff --git a/web/routes/admin.py b/web/routes/admin.py index c999d4e..aba5cb9 100644 --- a/web/routes/admin.py +++ b/web/routes/admin.py @@ -101,6 +101,75 @@ async def admin_user_detail(user_id: int, request: Request, db: Session = Depend return _render(request, "admin/user_detail.html", {"user": admin, "target": target}) +# ── Create user ─────────────────────────────────────────────────────────────── + +@router.post("/users/create") +async def admin_create_user(request: Request, db: Session = Depends(get_db)): + try: + admin = _admin_user(request, db) + except Exception: + return RedirectResponse("/login", 303) + + form = await request.form() + first_name = str(form.get("first_name", "")).strip() + last_name = str(form.get("last_name", "")).strip() + email = str(form.get("email", "")).strip().lower() + phone = str(form.get("phone", "")).strip() or None + password = str(form.get("password", "")) + role_str = str(form.get("role", "user")) + + errors = [] + if not first_name: + errors.append("Имя обязательно") + if not email: + errors.append("Email обязателен") + if not password or len(password) < 8: + errors.append("Пароль должен содержать минимум 8 символов") + if role_str not in ("user", "admin") and admin.role != UserRoleEnum.system: + role_str = "user" + + if not errors: + existing = db.query(User).filter(User.email == email).first() + if existing: + errors.append("Пользователь с таким email уже существует") + + if errors: + # Re-render list page with errors and dialog open + q = db.query(User) + total = q.count() + users = q.order_by(User.created_at.desc()).limit(PAGE_SIZE).all() + return _render(request, "admin/users.html", { + "user": admin, + "users": users, + "search": "", + "status_filter": "", + "role_filter": "", + "page": 1, + "total_pages": max(1, (total + PAGE_SIZE - 1) // PAGE_SIZE), + "total": total, + "create_errors": errors, + }) + + try: + role = UserRoleEnum(role_str) + except ValueError: + role = UserRoleEnum.user + + new_user = User( + first_name=first_name, + last_name=last_name, + email=email, + phone=phone, + password_hash=hash_password(password), + role=role, + status=UserStatusEnum.active, + is_email_confirmed=True, + ) + db.add(new_user) + db.commit() + return RedirectResponse(f"/admin/users/{new_user.id}?success=saved", 303) + + # ── View-as ─────────────────────────────────────────────────────────────────── @router.post("/users/{user_id}/view-as") diff --git a/web/templates/admin/users.html b/web/templates/admin/users.html index 3a664cf..df10cb9 100644 --- a/web/templates/admin/users.html +++ b/web/templates/admin/users.html @@ -4,9 +4,62 @@ {% block content %}

Пользователи

- Всего: {{ total }} +
+ +
+
+ +

Создать пользователя

+
+
+
+
+ +
+
+ +
+
+ + + + {% if user.role == 'system' %} + + {% endif %} +
+ + +
+
+
+
+ +{% if create_errors %} + + +{% endif %} +