refactor(employees): DRY duplicate logic in employees and departments
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
This commit is contained in:
parent
09dfb7598e
commit
19464a38cf
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user