feat(users): add transactional employee deletion and token revocation
All checks were successful
State Corp Backend CI/CD / Quality gate (push) Successful in 2m42s
State Corp Backend CI/CD / Build linux/amd64 images once (push) Successful in 5m29s
State Corp Backend CI/CD / Refresh and release internal main (push) Has been skipped
State Corp Backend CI/CD / Release customer main (push) Has been skipped
State Corp Backend CI/CD / Release dev (push) Successful in 36s
All checks were successful
State Corp Backend CI/CD / Quality gate (push) Successful in 2m42s
State Corp Backend CI/CD / Build linux/amd64 images once (push) Successful in 5m29s
State Corp Backend CI/CD / Refresh and release internal main (push) Has been skipped
State Corp Backend CI/CD / Release customer main (push) Has been skipped
State Corp Backend CI/CD / Release dev (push) Successful in 36s
This commit is contained in:
@@ -1,13 +1,21 @@
|
||||
"""Admin configuration for user app."""
|
||||
|
||||
from contextlib import suppress
|
||||
from functools import wraps
|
||||
|
||||
from apps.core.exceptions import BaseAPIException
|
||||
from apps.core.models import BackgroundJob
|
||||
from apps.user.management import lock_managed_users, validate_access_change
|
||||
from apps.user.models import Profile, User
|
||||
from django.contrib import admin
|
||||
from apps.user.services import UserService
|
||||
from django.contrib import admin, messages
|
||||
from django.contrib.admin.sites import NotRegistered
|
||||
from django.contrib.admin.utils import unquote
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
from django.contrib.auth.models import Group
|
||||
from django.db import transaction
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.urls import reverse
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
@@ -47,6 +55,20 @@ def _unregister(model) -> None:
|
||||
_unregister(User)
|
||||
|
||||
|
||||
def _management_errors(view):
|
||||
"""Present guarded account-change failures through Django admin messages."""
|
||||
|
||||
@wraps(view)
|
||||
def wrapped(self, request, *args, **kwargs):
|
||||
try:
|
||||
return view(self, request, *args, **kwargs)
|
||||
except BaseAPIException as exc:
|
||||
self.message_user(request, exc.message, level=messages.ERROR)
|
||||
return HttpResponseRedirect(reverse("admin:user_user_changelist"))
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
class ProfileInline(admin.StackedInline):
|
||||
"""Inline для профиля пользователя."""
|
||||
|
||||
@@ -121,6 +143,50 @@ class UserAdmin(BaseUserAdmin):
|
||||
readonly_fields = ["created_at", "updated_at", "last_login", "date_joined"]
|
||||
actions = ["verify_users", "unverify_users", "activate_users", "deactivate_users"]
|
||||
|
||||
@_management_errors
|
||||
def changeform_view(self, request, object_id=None, form_url="", extra_context=None):
|
||||
return super().changeform_view(request, object_id, form_url, extra_context)
|
||||
|
||||
@_management_errors
|
||||
def delete_view(self, request, object_id, extra_context=None):
|
||||
return super().delete_view(request, object_id, extra_context)
|
||||
|
||||
@_management_errors
|
||||
def user_change_password(self, request, id, form_url=""):
|
||||
user = self.get_object(request, unquote(id))
|
||||
if request.method == "POST" and user is not None:
|
||||
with lock_managed_users(actor_id=request.user.pk, user_ids=[user.pk]):
|
||||
return super().user_change_password(request, id, form_url)
|
||||
return super().user_change_password(request, id, form_url)
|
||||
|
||||
@_management_errors
|
||||
def changelist_view(self, request, extra_context=None):
|
||||
# delete_selected writes LogEntry rows before delete_queryset. Roll those
|
||||
# back too if the complete selection fails an account-management guard.
|
||||
with transaction.atomic():
|
||||
return super().changelist_view(request, extra_context)
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
with lock_managed_users(
|
||||
actor_id=request.user.pk, user_ids=[obj.pk] if change else []
|
||||
) as users:
|
||||
if change:
|
||||
validate_access_change(
|
||||
users[obj.pk],
|
||||
actor_id=request.user.pk,
|
||||
is_active=obj.is_active,
|
||||
is_staff=obj.is_staff,
|
||||
)
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
def delete_model(self, request, obj):
|
||||
UserService.delete_user(obj.pk, actor_id=request.user.pk)
|
||||
|
||||
def delete_queryset(self, request, queryset):
|
||||
UserService.delete_users(
|
||||
queryset.values_list("pk", flat=True), actor_id=request.user.pk
|
||||
)
|
||||
|
||||
def is_verified_badge(self, obj):
|
||||
if obj.is_verified:
|
||||
return format_html(
|
||||
@@ -157,12 +223,20 @@ class UserAdmin(BaseUserAdmin):
|
||||
|
||||
@admin.action(description="Активировать пользователей")
|
||||
def activate_users(self, request, queryset):
|
||||
updated = queryset.update(is_active=True)
|
||||
updated = UserService.set_users_active(
|
||||
queryset.values_list("pk", flat=True),
|
||||
actor_id=request.user.pk,
|
||||
is_active=True,
|
||||
)
|
||||
self.message_user(request, f"Активировано {updated} пользователей")
|
||||
|
||||
@admin.action(description="Деактивировать пользователей")
|
||||
def deactivate_users(self, request, queryset):
|
||||
updated = queryset.update(is_active=False)
|
||||
updated = UserService.set_users_active(
|
||||
queryset.values_list("pk", flat=True),
|
||||
actor_id=request.user.pk,
|
||||
is_active=False,
|
||||
)
|
||||
self.message_user(request, f"Деактивировано {updated} пользователей")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user