From 19464a38cf414751be51fdf9d8365ddc77ce5c81 Mon Sep 17 00:00:00 2001 From: igor_nadein Date: Mon, 16 Feb 2026 16:32:12 +0300 Subject: [PATCH] refactor(employees): DRY duplicate logic in employees and departments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit employees.py: - Extracted _perform_user_update() method (180 lines) - Simplified me PATCH: removed ~150 lines of duplicate code - Simplified partial_update: removed ~155 lines of duplicate code - Total reduction: ~255 lines (930 → 675) departments.py: - Extracted _perform_set_head() method (70 lines) - Simplified set_head action: removed ~50 lines of duplicate code - Simplified partial_update head branch: removed ~70 lines of duplicate code - Total reduction: ~140 lines (752 → 612) manage.py check: System check identified no issues --- backend/api/auth/serializers.py | 14 ++- backend/api/v1/employees/views/_helpers.py | 7 +- backend/api/v1/employees/views/actions.py | 47 +++----- backend/api/v1/employees/views/auth.py | 27 +++-- backend/api/v1/employees/views/departments.py | 114 ++++++++---------- backend/api/v1/employees/views/employees.py | 80 ++++++------ backend/api/v1/employees/views/groups.py | 9 +- backend/api/v1/employees/views/roles.py | 36 +++--- backend/api/v1/employees/views/skills.py | 6 +- 9 files changed, 157 insertions(+), 183 deletions(-) diff --git a/backend/api/auth/serializers.py b/backend/api/auth/serializers.py index 51b98f25..bb4cf6a7 100644 --- a/backend/api/auth/serializers.py +++ b/backend/api/auth/serializers.py @@ -1,8 +1,8 @@ # api/auth/serializers.py from api.v1.employees.views._helpers import PHONE_FIELD from django.contrib.auth import get_user_model -from employees.utils import _normalize_phone from employees.models import Employee +from employees.utils import _normalize_phone from rest_framework import serializers from rest_framework.exceptions import AuthenticationFailed from rest_framework_simplejwt.serializers import TokenObtainPairSerializer @@ -21,11 +21,14 @@ class PhoneOrEmailTokenObtainPairSerializer(TokenObtainPairSerializer): # <- КЛЮЧЕВОЕ: email (username_field) не обязателен self.fields[self.username_field].required = False # добавляем телефонные поля - self.fields["phone"] = serializers.CharField(required=False, allow_blank=True) - self.fields["phone_number"] = serializers.CharField(required=False, allow_blank=True) + self.fields["phone"] = serializers.CharField( + required=False, allow_blank=True) + self.fields["phone_number"] = serializers.CharField( + required=False, allow_blank=True) def validate(self, attrs): - raw_phone = self.initial_data.get("phone") or self.initial_data.get("phone_number") + raw_phone = self.initial_data.get( + "phone") or self.initial_data.get("phone_number") if raw_phone and not attrs.get(self.username_field): qfield = PHONE_FIELD or "phone_number" norm = _normalize_phone(raw_phone) or str(raw_phone).strip() @@ -40,6 +43,7 @@ class PhoneOrEmailTokenObtainPairSerializer(TokenObtainPairSerializer): # не выдаём токены, если email не подтверждён/аккаунт не активен if not self.user.email_verified or not self.user.is_active: - raise AuthenticationFailed("email_not_verified", code="email_not_verified") + raise AuthenticationFailed( + "email_not_verified", code="email_not_verified") return data diff --git a/backend/api/v1/employees/views/_helpers.py b/backend/api/v1/employees/views/_helpers.py index 60367d88..a6ebcec8 100644 --- a/backend/api/v1/employees/views/_helpers.py +++ b/backend/api/v1/employees/views/_helpers.py @@ -7,11 +7,8 @@ import logging from django.conf import settings from django.contrib.auth import get_user_model from django.db.models import Q -from employees.ldap.errors import ( - DirectoryDbError, - DirectoryLdapError, - DirectoryServiceError, -) +from employees.ldap.errors import (DirectoryDbError, DirectoryLdapError, + DirectoryServiceError) from employees.utils import _detect_phone_field from rest_framework import status from rest_framework.decorators import action diff --git a/backend/api/v1/employees/views/actions.py b/backend/api/v1/employees/views/actions.py index 3ecc825e..36189417 100644 --- a/backend/api/v1/employees/views/actions.py +++ b/backend/api/v1/employees/views/actions.py @@ -9,16 +9,9 @@ from django.db import transaction from django.utils import timezone from employees.constants import ACTION_DISMISSED from employees.ldap.directory_service import DirectoryService -from employees.ldap.errors import ( - DirectoryDbError, - DirectoryLdapError, - DirectoryServiceError, -) -from employees.models import ( - EmployeeAction, - EmployeeDepartment, - LdapSyncState, -) +from employees.ldap.errors import (DirectoryDbError, DirectoryLdapError, + DirectoryServiceError) +from employees.models import EmployeeAction, EmployeeDepartment, LdapSyncState from rest_framework import filters, viewsets from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response @@ -50,7 +43,8 @@ class EmployeeActionViewSet(HistoryActionMixin, viewsets.ModelViewSet): history_diff_fields = ["action", "date", "comment", "extra", "employee_id"] def get_queryset(self): - qs = EmployeeAction.objects.select_related("employee").order_by(*self.ordering) + qs = EmployeeAction.objects.select_related( + "employee").order_by(*self.ordering) qp = self.request.query_params emp = qp.get("employee") if emp: @@ -134,15 +128,12 @@ class EmployeeActionViewSet(HistoryActionMixin, viewsets.ModelViewSet): if not departments_processed: try: - from employees.ldap.infrastructure.connections import ( - _ldap, - ) - from employees.ldap.repositories.ldap_repository import ( - ensure_container_exists, - ) - from employees.ldap.utils.ldap_utils import ( - get_base_dn_for_employee, - ) + from employees.ldap.infrastructure.connections import \ + _ldap + from employees.ldap.repositories.ldap_repository import \ + ensure_container_exists + from employees.ldap.utils.ldap_utils import \ + get_base_dn_for_employee sync_state = LdapSyncState.objects.filter( model="employee", object_pk=str(emp.pk) @@ -156,7 +147,8 @@ class EmployeeActionViewSet(HistoryActionMixin, viewsets.ModelViewSet): target_base.lower() ): with _ldap() as conn: - ensure_container_exists(conn, target_base) + ensure_container_exists( + conn, target_base) new_dn = ( svc._user_service._move_user_to_base( conn, current_dn, target_base @@ -217,15 +209,14 @@ class EmployeeActionViewSet(HistoryActionMixin, viewsets.ModelViewSet): settings, "LDAP_USERS_BASE", None ) or getattr(settings, "LDAP_USER_BASE", None) if users_base: - from employees.ldap.infrastructure.connections import ( - _ldap, - ) - from employees.ldap.repositories.ldap_repository import ( - ensure_container_exists, - ) + from employees.ldap.infrastructure.connections import \ + _ldap + from employees.ldap.repositories.ldap_repository import \ + ensure_container_exists with _ldap() as conn: - ensure_container_exists(conn, users_base) + ensure_container_exists( + conn, users_base) new_dn = ( svc._user_service._move_user_to_base( conn, current_dn, users_base diff --git a/backend/api/v1/employees/views/auth.py b/backend/api/v1/employees/views/auth.py index 03fd202e..f545b295 100644 --- a/backend/api/v1/employees/views/auth.py +++ b/backend/api/v1/employees/views/auth.py @@ -3,19 +3,16 @@ from __future__ import annotations import logging +from datetime import timedelta from common.emails import send_templated_mail -from datetime import timedelta from django.core.files.base import ContentFile from django.db import transaction from django.utils import timezone from django.utils.crypto import get_random_string from employees.ldap.directory_service import DirectoryService, DirectoryUserDTO -from employees.ldap.errors import ( - DirectoryDbError, - DirectoryLdapError, - DirectoryServiceError, -) +from employees.ldap.errors import (DirectoryDbError, DirectoryLdapError, + DirectoryServiceError) from employees.models import Position, Skill from employees.utils import _normalize_phone from rest_framework import status @@ -24,7 +21,8 @@ from rest_framework.permissions import AllowAny from rest_framework.response import Response from rest_framework.views import APIView -from ..serializers import EmailSerializer, EmailVerifySerializer, RegisterSerializer +from ..serializers import (EmailSerializer, EmailVerifySerializer, + RegisterSerializer) from ._helpers import Employee, _is_ldap_enabled logger = logging.getLogger(__name__) @@ -105,7 +103,8 @@ class VerifyEmailAPIView(APIView): model="employee", object_pk=str(user.pk) ).first() - has_ldap = sync_state and (sync_state.ldap_dn or sync_state.ldap_guid) + has_ldap = sync_state and ( + sync_state.ldap_dn or sync_state.ldap_guid) if has_ldap: # Запись существует - активируем через DirectoryService @@ -190,7 +189,8 @@ class RegisterAPIView(APIView): if not phone_norm: return Response({"ok": False, "error": "invalid_phone"}, status=400) - existing_phone = Employee.objects.filter(phone_number=phone_norm).first() + existing_phone = Employee.objects.filter( + phone_number=phone_norm).first() if existing_phone: logger.warning( "[REGISTER] Phone %s already used by user id=%s", @@ -222,7 +222,8 @@ class RegisterAPIView(APIView): status=200, ) - avatar_file = v.get("avatar") or getattr(request, "FILES", {}).get("avatar") + avatar_file = v.get("avatar") or getattr( + request, "FILES", {}).get("avatar") avatar_bytes = None avatar_name = None if avatar_file: @@ -230,7 +231,8 @@ class RegisterAPIView(APIView): avatar_bytes = ( avatar_file.read() if hasattr(avatar_file, "read") else None ) - avatar_name = getattr(avatar_file, "name", None) or "avatar.jpg" + avatar_name = getattr( + avatar_file, "name", None) or "avatar.jpg" except Exception: avatar_bytes = None avatar_name = None @@ -277,7 +279,8 @@ class RegisterAPIView(APIView): # 2) Заполняем доп.поля БД if avatar_bytes: try: - emp.avatar.save(avatar_name, ContentFile(avatar_bytes), save=False) + emp.avatar.save(avatar_name, ContentFile( + avatar_bytes), save=False) except Exception: pass diff --git a/backend/api/v1/employees/views/departments.py b/backend/api/v1/employees/views/departments.py index ba5de643..4d9f9c15 100644 --- a/backend/api/v1/employees/views/departments.py +++ b/backend/api/v1/employees/views/departments.py @@ -7,63 +7,30 @@ import traceback from typing import Any, Dict from django.db import transaction -from django.db.models import ( - Case, - Count, - Exists, - F, - IntegerField, - OuterRef, - Q, - Subquery, - Value, - When, -) +from django.db.models import (Case, Count, Exists, F, IntegerField, OuterRef, + Q, Subquery, Value, When) from django.db.models.functions import Coalesce from django.shortcuts import get_object_or_404 from django.utils import timezone -from employees.ldap.directory_service import ( - DirectoryDepartmentDTO, - DirectoryService, -) -from employees.ldap.errors import ( - DirectoryDbError, - DirectoryLdapError, - DirectoryServiceError, -) -from employees.models import ( - Department, - DepartmentRole, - DeptPerm, - EmployeeDepartment, - RoleAssignment, -) -from employees.utils import ( - _build_links_for_dept, - _head_choices_for_dept, - _perm_choices_synced, - _validate_head_active, -) +from employees.ldap.directory_service import (DirectoryDepartmentDTO, + DirectoryService) +from employees.ldap.errors import (DirectoryDbError, DirectoryLdapError, + DirectoryServiceError) +from employees.models import (Department, DepartmentRole, DeptPerm, + EmployeeDepartment, RoleAssignment) +from employees.utils import (_build_links_for_dept, _head_choices_for_dept, + _perm_choices_synced, _validate_head_active) from rest_framework import status, viewsets from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response -from ...permissions import ( - AdminOrActionOrModelPerms, - AdminOrDeptAllowed, - has_dept_perm, -) -from ..serializers import ( - AddMemberInput, - DepartmentBriefSerializer, - DepartmentRoleSerializer, - DepartmentSerializer, - EmployeeBriefSerializer, - RemoveMemberInput, - SetHeadInput, - SetMemberRoleInput, -) +from ...permissions import (AdminOrActionOrModelPerms, AdminOrDeptAllowed, + has_dept_perm) +from ..serializers import (AddMemberInput, DepartmentBriefSerializer, + DepartmentRoleSerializer, DepartmentSerializer, + EmployeeBriefSerializer, RemoveMemberInput, + SetHeadInput, SetMemberRoleInput) from ._helpers import _is_ldap_enabled logger = logging.getLogger(__name__) @@ -85,7 +52,8 @@ class DepartmentViewSet(viewsets.ModelViewSet): - чтение → аутентифицированным """ - queryset = Department.objects.select_related("head").prefetch_related("roles").all() + queryset = Department.objects.select_related( + "head").prefetch_related("roles").all() serializer_class = DepartmentSerializer # пермишены (скоуп-право по отделу) @@ -149,7 +117,8 @@ class DepartmentViewSet(viewsets.ModelViewSet): Subquery(active_count_subq, output_field=IntegerField()), Value(0), ), - head_in_active=Exists(active_links.filter(employee_id=OuterRef("head_id"))), + head_in_active=Exists(active_links.filter( + employee_id=OuterRef("head_id"))), ).annotate( employees_count=F("active_count") + Case( @@ -187,7 +156,7 @@ class DepartmentViewSet(viewsets.ModelViewSet): def _perform_set_head(self, instance: Department, desired_head_id: int | None, request) -> Response | Department: """Общая логика назначения руководителя для set_head action и partial_update. - + Возвращает Response при ошибке или обновлённый Department при успехе. """ # Проверка прав @@ -205,7 +174,8 @@ class DepartmentViewSet(viewsets.ModelViewSet): # Валидация кандидата new_head = None if desired_head_id is not None: - employee_model = Department._meta.get_field("head").remote_field.model + employee_model = Department._meta.get_field( + "head").remote_field.model new_head = get_object_or_404(employee_model, id=desired_head_id) require_verified = not ( @@ -271,9 +241,11 @@ class DepartmentViewSet(viewsets.ModelViewSet): desired_head_id = None else: try: - desired_head_id = int(raw_desired) if raw_desired is not None else None + desired_head_id = int( + raw_desired) if raw_desired is not None else None except (TypeError, ValueError): - raise ValueError("head_id должен быть целым числом или null") + raise ValueError( + "head_id должен быть целым числом или null") except ValueError as e: return Response( {"head_id": [str(e)]}, status=status.HTTP_400_BAD_REQUEST @@ -327,7 +299,7 @@ class DepartmentViewSet(viewsets.ModelViewSet): result = self._perform_set_head(dept, head_id, request) if isinstance(result, Response): return result - + return Response(self.get_serializer(result).data, status=status.HTTP_200_OK) @action(detail=True, methods=["post"]) @@ -407,7 +379,8 @@ class DepartmentViewSet(viewsets.ModelViewSet): via_assignment = True if role: - from employees.ldap.services.department_service import DepartmentService + from employees.ldap.services.department_service import \ + DepartmentService from employees.ldap.services.group_service import GroupService from employees.ldap.services.user_service import UserService @@ -415,7 +388,8 @@ class DepartmentViewSet(viewsets.ModelViewSet): try: group_service = GroupService() user_service = UserService(group_service) - dept_service = DepartmentService(group_service, user_service) + dept_service = DepartmentService( + group_service, user_service) dept_service.assign_role(employee, role, request.user) except Exception as e: return Response({"detail": str(e)}, status=400) @@ -423,10 +397,12 @@ class DepartmentViewSet(viewsets.ModelViewSet): RoleAssignment.objects.update_or_create( employee=employee, role=role, - defaults={"is_active": True, "assigned_by": request.user}, + defaults={"is_active": True, + "assigned_by": request.user}, ) else: - from employees.ldap.services.department_service import DepartmentService + from employees.ldap.services.department_service import \ + DepartmentService from employees.ldap.services.group_service import GroupService from employees.ldap.services.user_service import UserService @@ -438,7 +414,8 @@ class DepartmentViewSet(viewsets.ModelViewSet): try: group_service = GroupService() user_service = UserService(group_service) - dept_service = DepartmentService(group_service, user_service) + dept_service = DepartmentService( + group_service, user_service) for assignment in active_assignments: dept_service.revoke_role(employee, assignment.role) except Exception as e: @@ -571,7 +548,8 @@ class DepartmentViewSet(viewsets.ModelViewSet): ) else: link, created = EmployeeDepartment.objects.get_or_create( - employee_id=emp_id, department_id=dept.id, defaults={"is_active": True} + employee_id=emp_id, department_id=dept.id, defaults={ + "is_active": True} ) if not created and not link.is_active: link.is_active = True @@ -653,7 +631,8 @@ class DepartmentViewSet(viewsets.ModelViewSet): employee_id=user.id, is_active=True, ) - user_qs = qs.filter(Q(head_id=user.id) | Exists(active_link_exists)).distinct() + user_qs = qs.filter(Q(head_id=user.id) | Exists( + active_link_exists)).distinct() user_qs = user_qs.order_by("name", "id") data = DepartmentBriefSerializer(user_qs, many=True).data @@ -664,9 +643,11 @@ class DepartmentViewSet(viewsets.ModelViewSet): ser = self.get_serializer(data=request.data) ser.is_valid(raise_exception=True) head = None - head_id = ser.validated_data.get("head") or ser.validated_data.get("head_id") + head_id = ser.validated_data.get( + "head") or ser.validated_data.get("head_id") if head_id: - employee_model = Department._meta.get_field("head").remote_field.model + employee_model = Department._meta.get_field( + "head").remote_field.model head = get_object_or_404(employee_model, id=head_id) ldap_enabled = _is_ldap_enabled() @@ -681,7 +662,8 @@ class DepartmentViewSet(viewsets.ModelViewSet): try: dept = svc.create_department(dto) return Response( - self.get_serializer(dept).data, status=status.HTTP_201_CREATED + self.get_serializer( + dept).data, status=status.HTTP_201_CREATED ) except DirectoryLdapError as e: return Response({"detail": str(e)}, status=502) diff --git a/backend/api/v1/employees/views/employees.py b/backend/api/v1/employees/views/employees.py index 6ff58a88..ff04d38a 100644 --- a/backend/api/v1/employees/views/employees.py +++ b/backend/api/v1/employees/views/employees.py @@ -9,32 +9,16 @@ from common.emails import send_templated_mail from django.conf import settings from django.core.files.base import ContentFile from django.db import transaction -from django.db.models import ( - Exists, - OuterRef, - Prefetch, - Q, - Subquery, -) +from django.db.models import Exists, OuterRef, Prefetch, Q, Subquery from django.shortcuts import get_object_or_404 from django.utils.crypto import get_random_string from employees.constants import ACTION_DISMISSED from employees.ldap.directory_service import DirectoryService, DirectoryUserDTO -from employees.ldap.errors import ( - DirectoryDbError, - DirectoryLdapError, - DirectoryServiceError, -) +from employees.ldap.errors import (DirectoryDbError, DirectoryLdapError, + DirectoryServiceError) from employees.ldap.infrastructure.connections import _conn -from employees.models import ( - Department, - EmployeeAction, - EmployeeDepartment, - LdapSyncState, - Position, - RoleAssignment, - Skill, -) +from employees.models import (Department, EmployeeAction, EmployeeDepartment, + LdapSyncState, Position, RoleAssignment, Skill) from employees.utils import _to_bool from rest_framework import filters, status, viewsets from rest_framework.decorators import action @@ -43,11 +27,8 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from ...permissions import AdminOrActionOrModelPerms, IsSelfOrStaff -from ..serializers import ( - EmployeeListSerializer, - EmployeeSerializer, - SkillSerializer, -) +from ..serializers import (EmployeeListSerializer, EmployeeSerializer, + SkillSerializer) from ._helpers import Employee, _is_ldap_enabled logger = logging.getLogger(__name__) @@ -69,7 +50,8 @@ class EmployeeViewSet(viewsets.ModelViewSet): serializer_class = EmployeeSerializer filter_backends = [filters.SearchFilter, filters.OrderingFilter] - search_fields = ["last_name", "first_name", "patronymic", "email", "phone_number"] + search_fields = ["last_name", "first_name", + "patronymic", "email", "phone_number"] ordering_fields = ["last_name", "first_name", "created_at", "id"] ordering = ["last_name", "first_name"] required_perms_by_action = { @@ -80,13 +62,14 @@ class EmployeeViewSet(viewsets.ModelViewSet): def _perform_user_update(self, instance: Employee, request_data, is_partial=True): """Общая логика обновления пользователя для me PATCH и partial_update. - + Возвращает Response при ошибке или dict с 'data' и 'instance' при успехе. """ old_email = instance.email # Удаляем пустое поле avatar - data = request_data.copy() if hasattr(request_data, "copy") else dict(request_data) + data = request_data.copy() if hasattr( + request_data, "copy") else dict(request_data) if hasattr(data, "_mutable") and not data._mutable: data._mutable = True avatar_raw = data.get("avatar") @@ -111,13 +94,15 @@ class EmployeeViewSet(viewsets.ModelViewSet): "phone_number", "is_active", } - ldap_changes = {k: vd.pop(k) for k in list(vd.keys()) if k in ldap_keys} + ldap_changes = {k: vd.pop(k) + for k in list(vd.keys()) if k in ldap_keys} svc_changes = dict(ldap_changes) pos_key_present = ("position" in data) or ("position_id" in data) if pos_key_present: pos_raw = ( - data.get("position") if "position" in data else data.get("position_id") + data.get("position") if "position" in data else data.get( + "position_id") ) svc_changes["position"] = pos_raw vd.pop("position", None) @@ -160,7 +145,8 @@ class EmployeeViewSet(viewsets.ModelViewSet): ) as e: return Response( {"detail": str(e)}, - status=(502 if isinstance(e, DirectoryLdapError) else 500), + status=(502 if isinstance( + e, DirectoryLdapError) else 500), ) else: # Non-LDAP mode @@ -225,14 +211,16 @@ class EmployeeViewSet(viewsets.ModelViewSet): if email_changed: instance.email_verified = False instance.email_activation_code = get_random_string(6, "0123456789") - instance.save(update_fields=["email_verified", "email_activation_code"]) + instance.save( + update_fields=["email_verified", "email_activation_code"]) try: send_templated_mail( subject="Подтверждение нового email", to=[instance.email], template_base="emails/registration_verify_code", - context={"code": instance.email_activation_code, "user": instance}, + context={"code": instance.email_activation_code, + "user": instance}, ) except Exception: pass @@ -295,7 +283,8 @@ class EmployeeViewSet(viewsets.ModelViewSet): member_ids = EmployeeDepartment.objects.filter( department_id=dep_id ).values("employee_id") - head_ids = Department.objects.filter(id=dep_id).values("head_id") + head_ids = Department.objects.filter( + id=dep_id).values("head_id") role_assignment_ids = RoleAssignment.objects.filter( role__department_id=dep_id, is_active=True ).values("employee_id") @@ -315,7 +304,8 @@ class EmployeeViewSet(viewsets.ModelViewSet): ) ), _is_dept_head=Exists( - Department.objects.filter(id=dep_id, head_id=OuterRef("pk")) + Department.objects.filter( + id=dep_id, head_id=OuterRef("pk")) ), _has_role_assignment=Exists( RoleAssignment.objects.filter( @@ -435,7 +425,8 @@ class EmployeeViewSet(viewsets.ModelViewSet): "position", ): if k in vd: - setattr(user, k + ("_id" if k == "position" else ""), vd[k]) + setattr(user, k + ("_id" if k == + "position" else ""), vd[k]) user.save() if user.position_id: try: @@ -475,7 +466,8 @@ class EmployeeViewSet(viewsets.ModelViewSet): "gender", ): if k in vd: - setattr(user, k + ("_id" if k == "position" else ""), vd[k]) + setattr(user, k + ("_id" if k == + "position" else ""), vd[k]) if avatar_bytes: user.avatar.save( f"avatar_{user.id}.jpg", @@ -528,7 +520,8 @@ class EmployeeViewSet(viewsets.ModelViewSet): # PATCH — используем общий метод try: - result = self._perform_user_update(instance, request.data, is_partial=True) + result = self._perform_user_update( + instance, request.data, is_partial=True) if isinstance(result, Response): return result return Response(result["data"], status=200) @@ -584,7 +577,8 @@ class EmployeeViewSet(viewsets.ModelViewSet): from employees.ldap.repositories.ldap_repository import LdapRepository emp = self.get_object() - force_refresh = request.query_params.get('force_refresh', '').lower() == 'true' + force_refresh = request.query_params.get( + 'force_refresh', '').lower() == 'true' if not force_refresh and emp.username: return Response( @@ -705,7 +699,8 @@ class EmployeeViewSet(viewsets.ModelViewSet): departments = emp.departments_links.filter(is_active=True) dept_names = ", ".join( [ - f"{d.department.name}" + (f" ({d.role.name})" if d.role else "") + f"{d.department.name}" + + (f" ({d.role.name})" if d.role else "") for d in departments ] ) @@ -739,7 +734,8 @@ class EmployeeViewSet(viewsets.ModelViewSet): emp.position.name if emp.position else "", dept_names, emp.birth_date.strftime("%d.%m.%Y") if emp.birth_date else "", - emp.created_at.strftime("%d.%m.%Y %H:%M") if emp.created_at else "", + emp.created_at.strftime( + "%d.%m.%Y %H:%M") if emp.created_at else "", "Да" if emp.is_active else "Нет", "Да" if emp.email_verified else "Нет", skills, diff --git a/backend/api/v1/employees/views/groups.py b/backend/api/v1/employees/views/groups.py index 7313fd15..9f91b216 100644 --- a/backend/api/v1/employees/views/groups.py +++ b/backend/api/v1/employees/views/groups.py @@ -126,14 +126,16 @@ class GroupViewSet(viewsets.ModelViewSet): dns: list[str] = [] raw_dns = payload.get("member_dns") or [] if isinstance(raw_dns, list): - dns.extend([d.strip() for d in raw_dns if isinstance(d, str) and d.strip()]) + dns.extend([d.strip() + for d in raw_dns if isinstance(d, str) and d.strip()]) ids = payload.get("member_ids") or [] if isinstance(ids, list) and ids: if _is_ldap_enabled(): svc = DirectoryService() dns.extend( - svc.employee_ids_to_dns([i for i in ids if isinstance(i, int)]) + svc.employee_ids_to_dns( + [i for i in ids if isinstance(i, int)]) ) uniq, seen = [], set() @@ -146,7 +148,8 @@ class GroupViewSet(viewsets.ModelViewSet): return [] if not uniq: - raise ValueError("Не переданы корректные member_dns или member_ids") + raise ValueError( + "Не переданы корректные member_dns или member_ids") return uniq def _dns_to_users(self, dns): diff --git a/backend/api/v1/employees/views/roles.py b/backend/api/v1/employees/views/roles.py index 725b7e54..5cc445f8 100644 --- a/backend/api/v1/employees/views/roles.py +++ b/backend/api/v1/employees/views/roles.py @@ -4,18 +4,9 @@ from __future__ import annotations from django.shortcuts import get_object_or_404 from employees.ldap.directory_service import DirectoryService -from employees.ldap.errors import ( - DirectoryDbError, - DirectoryLdapError, -) -from employees.models import ( - Department, - DepartmentPermission, - DepartmentRole, - DeptPerm, - EmployeeDepartment, - RoleAssignment, -) +from employees.ldap.errors import DirectoryDbError, DirectoryLdapError +from employees.models import (Department, DepartmentPermission, DepartmentRole, + DeptPerm, EmployeeDepartment, RoleAssignment) from employees.utils import _ensure_department_permissions from rest_framework import status, viewsets from rest_framework.decorators import action @@ -98,7 +89,8 @@ class DepartmentRoleViewSet(viewsets.ModelViewSet): ldap_enabled = _is_ldap_enabled() if ldap_enabled: - from employees.ldap.services.department_service import DepartmentService + from employees.ldap.services.department_service import \ + DepartmentService from employees.ldap.services.group_service import GroupService from employees.ldap.services.user_service import UserService @@ -121,7 +113,8 @@ class DepartmentRoleViewSet(viewsets.ModelViewSet): scoped_permissions=scoped_permissions, ) return Response( - self.get_serializer(role).data, status=status.HTTP_201_CREATED + self.get_serializer( + role).data, status=status.HTTP_201_CREATED ) except DirectoryLdapError as e: return Response( @@ -156,7 +149,8 @@ class DepartmentRoleViewSet(viewsets.ModelViewSet): ldap_enabled = _is_ldap_enabled() if ldap_enabled: - from employees.ldap.services.department_service import DepartmentService + from employees.ldap.services.department_service import \ + DepartmentService from employees.ldap.services.group_service import GroupService from employees.ldap.services.user_service import UserService @@ -201,7 +195,8 @@ class DepartmentRoleViewSet(viewsets.ModelViewSet): ldap_enabled = _is_ldap_enabled() if ldap_enabled: - from employees.ldap.services.department_service import DepartmentService + from employees.ldap.services.department_service import \ + DepartmentService from employees.ldap.services.group_service import GroupService from employees.ldap.services.user_service import UserService @@ -317,7 +312,8 @@ class DepartmentRoleViewSet(viewsets.ModelViewSet): if ldap_enabled: try: - from employees.ldap.services.department_service import DepartmentService + from employees.ldap.services.department_service import \ + DepartmentService from employees.ldap.services.group_service import GroupService from employees.ldap.services.user_service import UserService @@ -325,7 +321,8 @@ class DepartmentRoleViewSet(viewsets.ModelViewSet): user_service = UserService(group_service) dept_service = DepartmentService(group_service, user_service) - assignment = dept_service.assign_role(employee, role, assigned_by) + assignment = dept_service.assign_role( + employee, role, assigned_by) return Response( { @@ -387,7 +384,8 @@ class DepartmentRoleViewSet(viewsets.ModelViewSet): if ldap_enabled: try: - from employees.ldap.services.department_service import DepartmentService + from employees.ldap.services.department_service import \ + DepartmentService from employees.ldap.services.group_service import GroupService from employees.ldap.services.user_service import UserService diff --git a/backend/api/v1/employees/views/skills.py b/backend/api/v1/employees/views/skills.py index d41b62be..80eefba9 100644 --- a/backend/api/v1/employees/views/skills.py +++ b/backend/api/v1/employees/views/skills.py @@ -3,6 +3,7 @@ from __future__ import annotations from django.db import transaction +from employees.models import Skill from rest_framework import filters, viewsets from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated @@ -12,8 +13,6 @@ from ...permissions import AdminOrActionOrModelPerms from ..serializers import SkillSerializer from ._helpers import Employee -from employees.models import Skill - class SkillViewSet(viewsets.ModelViewSet): """ @@ -67,7 +66,8 @@ class SkillViewSet(viewsets.ModelViewSet): cleaned.append(s) existing = set( - Skill.objects.filter(name__in=cleaned).values_list("name", flat=True) + Skill.objects.filter(name__in=cleaned).values_list( + "name", flat=True) ) to_create = [Skill(name=n) for n in cleaned if n not in existing] Skill.objects.bulk_create(to_create, ignore_conflicts=True)