From 843ba2adaa40eb2b2510978f21452f6334b229cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B3=D0=BE=D1=80=D1=8C=20=D0=9D=D0=B0=D0=B4=D0=B5?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Tue, 2 Dec 2025 10:24:57 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B0=D1=81=D1=88=D0=B8=D1=80=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=B0=D0=B4=D0=BC=D0=B8=D0=BD=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D0=BF=D0=BE=D0=BB=D0=BD=D0=BE=D0=B3=D0=BE?= =?UTF-8?q?=20=D1=83=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D1=8F=D0=BC=D0=B8=20=D0=B8=20?= =?UTF-8?q?LDAP:=201.=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=BE=20=D0=BF=D0=BE=D0=BB=D0=B5=20ldap=5Fsync=5Finfo=20=D0=B2?= =?UTF-8?q?=20=D0=B0=D0=B4=D0=BC=D0=B8=D0=BD=D0=BA=D1=83=20Employee=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D0=BE=D1=82=D0=BE=D0=B1=D1=80=D0=B0=D0=B6?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=81=D0=BE=D1=81=D1=82=D0=BE=D1=8F?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20LDAP=20=D1=81=D0=B8=D0=BD=D1=85=D1=80?= =?UTF-8?q?=D0=BE=D0=BD=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B8=20(DN,=20GUID,?= =?UTF-8?q?=20=D0=BD=D0=B0=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5,=20timestamps)=202.=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BD=D0=B5=D0=B4=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D1=8E=D1=89=D0=B8=D0=B5=20=D0=BF=D0=BE=D0=BB=D1=8F=20Emp?= =?UTF-8?q?loyee=20=D0=B2=20=D0=B0=D0=B4=D0=BC=D0=B8=D0=BD=D0=BA=D1=83:=20?= =?UTF-8?q?is=5Fldap=5Fmanaged,=20email=5Factivation=5Fcode,=20sms=5Factiv?= =?UTF-8?q?ation=5Fcode=203.=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20is=5Fldap=5Fmanaged=20=D0=B2=20list=5Fdisplay=20=D0=B8?= =?UTF-8?q?=20list=5Ffilter=20=D0=B4=D0=BB=D1=8F=20=D0=B1=D1=8B=D1=81?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=B9=20=D1=84=D0=B8=D0=BB=D1=8C=D1=82=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=B8=204.=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20LDAP=20=D0=BF=D0=BE=D0=BB=D1=8F=20(l?= =?UTF-8?q?dap=5Fgroup=5Fdn)=20=D0=B2=20=D0=B0=D0=B4=D0=BC=D0=B8=D0=BD?= =?UTF-8?q?=D0=BA=D0=B8=20Department,=20DepartmentRole=20=D0=B8=20Position?= =?UTF-8?q?=205.=20=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD=D1=8B=20fiel?= =?UTF-8?q?dsets=20=D0=B4=D0=BB=D1=8F=20=D0=B2=D1=81=D0=B5=D1=85=20=D0=BC?= =?UTF-8?q?=D0=BE=D0=B4=D0=B5=D0=BB=D0=B5=D0=B9=20=D1=81=20=D0=B3=D1=80?= =?UTF-8?q?=D1=83=D0=BF=D0=BF=D0=B8=D1=80=D0=BE=D0=B2=D0=BA=D0=BE=D0=B9=20?= =?UTF-8?q?LDAP=20=D0=BF=D0=BE=D0=BB=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/employees/admin.py | 104 ++++++++++++++++++++++++++++++++++--- 1 file changed, 98 insertions(+), 6 deletions(-) diff --git a/backend/employees/admin.py b/backend/employees/admin.py index 81ca5eaa..1af30f88 100644 --- a/backend/employees/admin.py +++ b/backend/employees/admin.py @@ -157,16 +157,32 @@ class EmployeeAdmin(DjangoUserAdmin): "position", "is_active", "email_verified", + "is_ldap_managed", "is_staff", ) - list_filter = ("is_active", "email_verified", "is_staff", "is_superuser", "position", "groups") + list_filter = ( + "is_active", + "email_verified", + "is_staff", + "is_superuser", + "is_ldap_managed", + "position", + "groups", + ) search_fields = ("email", "first_name", "last_name", "phone_number") ordering = ("last_name", "first_name") list_select_related = ("position",) filter_horizontal = ("groups", "user_permissions", "skills") - readonly_fields = ("last_login", "created_at", "updated_at") + readonly_fields = ( + "last_login", + "created_at", + "updated_at", + "ldap_sync_info", + "email_activation_code", + "sms_activation_code", + ) fieldsets = ( (None, {"fields": ("email", "password")}), @@ -190,14 +206,60 @@ class EmployeeAdmin(DjangoUserAdmin): "fields": ( "is_active", "email_verified", + "email_activation_code", + "sms_activation_code", "is_staff", "is_superuser", "groups", "user_permissions", ) }), + ("LDAP", { + "fields": ("is_ldap_managed", "ldap_sync_info"), + "classes": ("collapse",), + }), ("Служебное", {"fields": ("last_login", "created_at", "updated_at")}), ) + + def ldap_sync_info(self, obj): + """Отображает информацию о состоянии синхронизации LDAP.""" + if not obj.pk: + return "-" + + try: + sync_state = LdapSyncState.objects.filter( + model="employee", + object_pk=str(obj.pk) + ).first() + + if not sync_state: + return "Нет записи синхронизации" + + from django.utils.html import format_html + + info = [] + if sync_state.ldap_dn: + info.append(f"DN: {sync_state.ldap_dn}") + if sync_state.ldap_guid: + info.append(f"GUID: {sync_state.ldap_guid}") + if sync_state.last_sync_dir: + info.append(f"Направление: {sync_state.last_sync_dir}") + if sync_state.last_ldap_modify_ts: + info.append( + f"LDAP изменен: {sync_state.last_ldap_modify_ts}" + ) + if sync_state.last_django_modify_ts: + info.append( + f"Django изменен: {sync_state.last_django_modify_ts}" + ) + if sync_state.updated_at: + info.append(f"Обновлено: {sync_state.updated_at}") + + return format_html("
".join(info)) if info else "-" + except Exception as e: + return f"Ошибка: {e}" + + ldap_sync_info.short_description = "Состояние LDAP синхронизации" add_fieldsets = ( (None, { @@ -227,8 +289,17 @@ class EmployeeAdmin(DjangoUserAdmin): @admin.register(Position) class PositionAdmin(SimpleHistoryAdmin): list_display = ("name", "description") - search_fields = ("name", "description") + search_fields = ("name", "description", "ldap_group_dn") filter_horizontal = ("groups",) + + fieldsets = ( + (None, {"fields": ("name", "description")}), + ("Права", {"fields": ("groups",)}), + ("LDAP", { + "fields": ("ldap_group_dn",), + "classes": ("collapse",), + }), + ) @admin.register(EmployeeAction) @@ -242,21 +313,42 @@ class EmployeeActionAdmin(SimpleHistoryAdmin): @admin.register(Department) class DepartmentAdmin(admin.ModelAdmin): - list_display = ("name", "head", "created_at") + list_display = ("name", "head", "head_appointed_at", "created_at") list_filter = ("head",) - search_fields = ("name",) + search_fields = ("name", "description", "ldap_group_dn") autocomplete_fields = ("head",) readonly_fields = ("created_at", "head_appointed_at") inlines = [DepartmentMembershipInline] + + fieldsets = ( + (None, {"fields": ("name", "description")}), + ("Руководство", { + "fields": ("head", "head_appointed_at"), + }), + ("LDAP", { + "fields": ("ldap_group_dn",), + "classes": ("collapse",), + }), + ("Служебное", {"fields": ("created_at",)}), + ) @admin.register(DepartmentRole) class DepartmentRoleAdmin(admin.ModelAdmin): list_display = ("name", "department") list_filter = ("department",) - search_fields = ("name", "department__name") + search_fields = ("name", "department__name", "ldap_group_dn") autocomplete_fields = ("department",) filter_horizontal = ("scoped_permissions",) + + fieldsets = ( + (None, {"fields": ("department", "name")}), + ("Права", {"fields": ("scoped_permissions",)}), + ("LDAP", { + "fields": ("ldap_group_dn",), + "classes": ("collapse",), + }), + ) @admin.register(EmployeeDepartment) class EmployeeDepartmentAdmin(admin.ModelAdmin):