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
693 lines
25 KiB
Python
693 lines
25 KiB
Python
"""GroupViewSet — CRUD и LDAP-операции с группами."""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Any, List, Optional
|
||
|
||
from django.conf import settings
|
||
from django.contrib.auth.models import Group, Permission
|
||
from django.core.exceptions import FieldError
|
||
from django.db import transaction
|
||
from django.db.models import OuterRef, Q, Subquery
|
||
from employees.ldap.directory_service import DirectoryService
|
||
from employees.models import LdapSyncState
|
||
from rest_framework import filters, status, viewsets
|
||
from rest_framework.decorators import action
|
||
from rest_framework.permissions import IsAuthenticated
|
||
from rest_framework.response import Response
|
||
|
||
from ...permissions import AdminOrActionOrModelPerms
|
||
from ..serializers import GroupSerializer
|
||
from ._helpers import Employee, _is_ldap_enabled
|
||
|
||
|
||
class GroupViewSet(viewsets.ModelViewSet):
|
||
"""CRUD и LDAP-операции с группами.
|
||
|
||
Базовые маршруты:
|
||
GET/POST /api/v1/groups/
|
||
GET/PATCH/DELETE /api/v1/groups/{id}/
|
||
|
||
Экшены: permissions, set-permissions, add-permissions, remove-permissions,
|
||
rename, set-description, members, add-members, remove-members, replace-members
|
||
"""
|
||
|
||
queryset = Group.objects.all()
|
||
serializer_class = GroupSerializer
|
||
pagination_class = None
|
||
|
||
filter_backends = [filters.SearchFilter, filters.OrderingFilter]
|
||
search_fields = ["name"]
|
||
ordering_fields = ["name", "id"]
|
||
ordering = ["name"]
|
||
|
||
permission_classes = [IsAuthenticated, AdminOrActionOrModelPerms]
|
||
required_perms_by_action = {
|
||
"set_permissions": "employees.assign_group_permissions",
|
||
"add_permissions": "employees.assign_group_permissions",
|
||
"remove_permissions": "employees.assign_group_permissions",
|
||
"rename": "employees.assign_group_permissions",
|
||
"set_description": "employees.assign_group_permissions",
|
||
"add_members": "employees.assign_group_permissions",
|
||
"remove_members": "employees.assign_group_permissions",
|
||
"replace_members": "employees.assign_group_permissions",
|
||
}
|
||
|
||
# ---------- queryset ----------
|
||
|
||
def get_queryset(self):
|
||
qs = super().get_queryset()
|
||
|
||
member_raw = self.request.query_params.get(
|
||
"member"
|
||
) or self.request.query_params.get("member_id")
|
||
if member_raw is None:
|
||
return qs
|
||
|
||
try:
|
||
member_id = int(str(member_raw).strip())
|
||
except (TypeError, ValueError):
|
||
return qs.none()
|
||
|
||
try:
|
||
return qs.filter(
|
||
Q(user__id=member_id) | Q(user_set__id=member_id)
|
||
).distinct()
|
||
except FieldError:
|
||
try:
|
||
return qs.filter(user__id=member_id).distinct()
|
||
except FieldError:
|
||
try:
|
||
return qs.filter(user_set__id=member_id).distinct()
|
||
except FieldError:
|
||
return qs.none()
|
||
|
||
# ---------- helpers ----------
|
||
|
||
def _validate_permissions_payload(
|
||
self, request
|
||
) -> tuple[Optional[List[Permission]], Optional[Response]]:
|
||
"""Валидирует payload с ID permissions."""
|
||
ids = request.data.get("permissions")
|
||
if not isinstance(ids, list):
|
||
return None, Response(
|
||
{"detail": "Поле 'permissions' должно быть списком id"},
|
||
status=status.HTTP_400_BAD_REQUEST,
|
||
)
|
||
try:
|
||
pid_list = [int(x) for x in ids]
|
||
except (TypeError, ValueError):
|
||
return None, Response(
|
||
{"detail": "Список 'permissions' должен содержать целые числа"},
|
||
status=status.HTTP_400_BAD_REQUEST,
|
||
)
|
||
qs = Permission.objects.filter(id__in=pid_list)
|
||
if qs.count() != len(set(pid_list)):
|
||
return None, Response(
|
||
{"detail": "Некоторые permissions не найдены"},
|
||
status=status.HTTP_400_BAD_REQUEST,
|
||
)
|
||
return list(qs), None
|
||
|
||
def _resolve_group_dn(self, grp: Group) -> Optional[str]:
|
||
"""Определяет DN группы по её CN."""
|
||
if not _is_ldap_enabled():
|
||
return None
|
||
|
||
base = getattr(settings, "LDAP_GROUPS_BASE", "") or None
|
||
svc = DirectoryService()
|
||
try:
|
||
return svc.group_find_dn(grp.name, bases=[base] if base else None)
|
||
except AttributeError:
|
||
return svc.find_group_dn(grp.name, bases=[base] if base else None)
|
||
|
||
def _members_payload_to_dns(self, payload: dict[str, Any]) -> list[str]:
|
||
"""Извлекает список DN участников из payload (member_dns|member_ids)."""
|
||
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()])
|
||
|
||
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)])
|
||
)
|
||
|
||
uniq, seen = [], set()
|
||
for d in dns:
|
||
if d and d not in seen:
|
||
uniq.append(d)
|
||
seen.add(d)
|
||
|
||
if not _is_ldap_enabled():
|
||
return []
|
||
|
||
if not uniq:
|
||
raise ValueError(
|
||
"Не переданы корректные member_dns или member_ids")
|
||
return uniq
|
||
|
||
def _dns_to_users(self, dns):
|
||
"""Маппит DN участников на локальных пользователей через LdapSyncState."""
|
||
if not dns:
|
||
return []
|
||
|
||
sub = LdapSyncState.objects.filter(
|
||
model="employee", object_pk=OuterRef("pk")
|
||
).values_list("ldap_dn", flat=True)[:1]
|
||
|
||
return list(
|
||
Employee.objects.annotate(ldap_dn=Subquery(sub))
|
||
.filter(ldap_dn__in=dns)
|
||
.only("id")
|
||
)
|
||
|
||
# ---------- override CRUD: LDAP → DB ----------
|
||
|
||
def create(self, request, *args, **kwargs) -> Response:
|
||
"""Создаёт LDAP-группу, затем запись Group в БД."""
|
||
ser = self.get_serializer(data=request.data)
|
||
ser.is_valid(raise_exception=True)
|
||
name: str = ser.validated_data["name"]
|
||
|
||
parent_dn = request.data.get("ldap_parent_dn") or getattr(
|
||
settings, "LDAP_GROUPS_BASE", None
|
||
)
|
||
description = request.data.get("ldap_description")
|
||
scope = request.data.get("ldap_scope", "global")
|
||
security_enabled = bool(request.data.get("ldap_security", True))
|
||
|
||
ldap_enabled = _is_ldap_enabled()
|
||
|
||
if ldap_enabled:
|
||
svc = DirectoryService()
|
||
try:
|
||
svc.group_create(
|
||
cn=name,
|
||
parent_dn=parent_dn,
|
||
description=description,
|
||
scope=scope,
|
||
security_enabled=security_enabled,
|
||
)
|
||
except Exception as e:
|
||
return Response({"detail": str(e)}, status=status.HTTP_502_BAD_GATEWAY)
|
||
|
||
try:
|
||
grp = Group.objects.create(name=name)
|
||
perms = ser.validated_data.get("permissions")
|
||
if perms:
|
||
grp.permissions.set(perms)
|
||
except Exception as e:
|
||
if ldap_enabled:
|
||
try:
|
||
svc = DirectoryService()
|
||
dn = None
|
||
try:
|
||
dn = svc.group_find_dn(
|
||
name, bases=[parent_dn] if parent_dn else None
|
||
)
|
||
except AttributeError:
|
||
dn = svc.find_group_dn(
|
||
name, bases=[parent_dn] if parent_dn else None
|
||
)
|
||
if dn:
|
||
svc.group_delete(dn)
|
||
except Exception:
|
||
pass
|
||
return Response(
|
||
{"detail": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
out = self.get_serializer(grp)
|
||
return Response(
|
||
out.data,
|
||
status=status.HTTP_201_CREATED,
|
||
headers=self.get_success_headers(out.data),
|
||
)
|
||
|
||
def partial_update(self, request, *args, **kwargs) -> Response:
|
||
"""Частичное обновление: сначала LDAP (rename/description), затем БД."""
|
||
grp = self.get_object()
|
||
new_name = request.data.get("name")
|
||
new_desc = request.data.get("ldap_description", "__NO_CHANGE__")
|
||
ldap_enabled = _is_ldap_enabled()
|
||
|
||
if ldap_enabled:
|
||
dn = self._resolve_group_dn(grp)
|
||
if not dn:
|
||
return Response(
|
||
{"detail": "Группа не найдена в LDAP"},
|
||
status=status.HTTP_404_NOT_FOUND,
|
||
)
|
||
|
||
svc = DirectoryService()
|
||
try:
|
||
if new_name and new_name != grp.name:
|
||
dn = svc.group_rename(dn, new_name)
|
||
if new_desc != "__NO_CHANGE__":
|
||
svc.group_set_description(dn, (new_desc or None))
|
||
except Exception as e:
|
||
return Response({"detail": str(e)}, status=status.HTTP_502_BAD_GATEWAY)
|
||
|
||
if new_name and new_name != grp.name:
|
||
grp.name = new_name
|
||
grp.save(update_fields=["name"])
|
||
|
||
return super().partial_update(request, *args, **kwargs)
|
||
|
||
def destroy(self, request, *args, **kwargs) -> Response:
|
||
"""Удаляет LDAP-группу, затем запись Group в БД."""
|
||
grp = self.get_object()
|
||
force_db = str(request.query_params.get("force_db", "")).lower() in {
|
||
"1",
|
||
"true",
|
||
"yes",
|
||
}
|
||
ldap_enabled = _is_ldap_enabled()
|
||
|
||
if ldap_enabled:
|
||
dn = self._resolve_group_dn(grp)
|
||
if dn:
|
||
try:
|
||
DirectoryService().group_delete(dn)
|
||
except Exception as e:
|
||
if not force_db:
|
||
return Response(
|
||
{"detail": str(e)}, status=status.HTTP_502_BAD_GATEWAY
|
||
)
|
||
|
||
return super().destroy(request, *args, **kwargs)
|
||
|
||
def list(self, request, *args, **kwargs) -> Response:
|
||
"""Список групп. Перед выдачей — мягкий LDAP-синк каталога."""
|
||
if _is_ldap_enabled():
|
||
try:
|
||
DirectoryService().sync_groups_catalog(throttle_seconds=60)
|
||
except Exception:
|
||
pass
|
||
return super().list(request, *args, **kwargs)
|
||
|
||
# ---------- Actions: Django permissions ----------
|
||
|
||
@action(detail=True, methods=["get"])
|
||
def permissions(self, request, pk=None) -> Response:
|
||
"""Permissions, привязанные к группе."""
|
||
grp = self.get_object()
|
||
perms = grp.permissions.select_related("content_type").distinct()
|
||
data = [
|
||
{
|
||
"id": p.id,
|
||
"codename": f"{p.content_type.app_label}.{p.codename}",
|
||
"name": p.name,
|
||
"app": p.content_type.app_label,
|
||
"model": p.content_type.model,
|
||
}
|
||
for p in perms
|
||
]
|
||
return Response(
|
||
{"count": len(data), "results": data}, status=status.HTTP_200_OK
|
||
)
|
||
|
||
@action(detail=True, methods=["post"], url_path="set-permissions")
|
||
def set_permissions(self, request, pk=None) -> Response:
|
||
"""Полностью заменяет набор permissions у группы."""
|
||
grp = self.get_object()
|
||
qs, error = self._validate_permissions_payload(request)
|
||
if error:
|
||
return error
|
||
grp.permissions.set(qs)
|
||
return Response(
|
||
{
|
||
"ok": True,
|
||
"permission_ids": list(grp.permissions.values_list("id", flat=True)),
|
||
},
|
||
status=status.HTTP_200_OK,
|
||
)
|
||
|
||
@action(detail=True, methods=["post"], url_path="add-permissions")
|
||
def add_permissions(self, request, pk=None) -> Response:
|
||
"""Добавляет permissions к группе."""
|
||
grp = self.get_object()
|
||
qs, error = self._validate_permissions_payload(request)
|
||
if error:
|
||
return error
|
||
grp.permissions.add(*qs)
|
||
return Response(
|
||
{
|
||
"ok": True,
|
||
"permission_ids": list(grp.permissions.values_list("id", flat=True)),
|
||
},
|
||
status=status.HTTP_200_OK,
|
||
)
|
||
|
||
@action(detail=True, methods=["post"], url_path="remove-permissions")
|
||
def remove_permissions(self, request, pk=None) -> Response:
|
||
"""Удаляет указанные permissions у группы."""
|
||
grp = self.get_object()
|
||
qs, error = self._validate_permissions_payload(request)
|
||
if error:
|
||
return error
|
||
grp.permissions.remove(*qs)
|
||
return Response(
|
||
{
|
||
"ok": True,
|
||
"permission_ids": list(grp.permissions.values_list("id", flat=True)),
|
||
},
|
||
status=status.HTTP_200_OK,
|
||
)
|
||
|
||
# ---------- Actions: LDAP ----------
|
||
|
||
@action(detail=True, methods=["post"])
|
||
def rename(self, request, pk=None) -> Response:
|
||
"""Переименовывает LDAP-группу и синхронизирует имя в БД."""
|
||
grp = self.get_object()
|
||
new_name = (request.data.get("new_name") or "").strip()
|
||
if not new_name:
|
||
return Response(
|
||
{"detail": "new_name обязателен"}, status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
ldap_enabled = _is_ldap_enabled()
|
||
|
||
if ldap_enabled:
|
||
dn = self._resolve_group_dn(grp)
|
||
if not dn:
|
||
return Response(
|
||
{"detail": "Группа не найдена в LDAP"},
|
||
status=status.HTTP_404_NOT_FOUND,
|
||
)
|
||
|
||
svc = DirectoryService()
|
||
try:
|
||
svc.group_rename(dn, new_name)
|
||
except Exception as e:
|
||
return Response({"detail": str(e)}, status=status.HTTP_502_BAD_GATEWAY)
|
||
|
||
grp.name = new_name
|
||
grp.save(update_fields=["name"])
|
||
return Response({"ok": True, "name": grp.name}, status=status.HTTP_200_OK)
|
||
|
||
@action(detail=True, methods=["post"], url_path="set-description")
|
||
def set_description(self, request, pk=None) -> Response:
|
||
"""Устанавливает описание LDAP-группы."""
|
||
grp = self.get_object()
|
||
|
||
if not _is_ldap_enabled():
|
||
return Response({"ok": True}, status=status.HTTP_200_OK)
|
||
|
||
dn = self._resolve_group_dn(grp)
|
||
if not dn:
|
||
return Response(
|
||
{"detail": "Группа не найдена в LDAP"}, status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
svc = DirectoryService()
|
||
try:
|
||
svc.group_set_description(dn, request.data.get("description"))
|
||
return Response({"ok": True}, status=status.HTTP_200_OK)
|
||
except Exception as e:
|
||
return Response({"detail": str(e)}, status=status.HTTP_502_BAD_GATEWAY)
|
||
|
||
@action(detail=True, methods=["get"])
|
||
def members(self, request, pk=None) -> Response:
|
||
"""Состав LDAP-группы."""
|
||
grp = self.get_object()
|
||
|
||
if not _is_ldap_enabled():
|
||
users = grp.user_set.all()
|
||
employees = [
|
||
{
|
||
"id": u.id,
|
||
"email": u.email,
|
||
"first_name": u.first_name,
|
||
"last_name": u.last_name,
|
||
}
|
||
for u in users
|
||
]
|
||
return Response(
|
||
{"dns": [], "employees": employees}, status=status.HTTP_200_OK
|
||
)
|
||
|
||
dn = self._resolve_group_dn(grp)
|
||
if not dn:
|
||
return Response(
|
||
{"detail": "Группа не найдена в LDAP"}, status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
svc = DirectoryService()
|
||
try:
|
||
dns = svc.group_list_members(dn)
|
||
except Exception as e:
|
||
return Response({"detail": str(e)}, status=status.HTTP_502_BAD_GATEWAY)
|
||
|
||
employees = svc.employees_brief_by_dns(dns)
|
||
return Response({"dns": dns, "employees": employees}, status=status.HTTP_200_OK)
|
||
|
||
@action(detail=True, methods=["post"], url_path="add-members")
|
||
def add_members(self, request, pk=None) -> Response:
|
||
"""Добавляет участников в LDAP-группу и связывает в БД."""
|
||
grp = self.get_object()
|
||
ldap_enabled = _is_ldap_enabled()
|
||
|
||
if ldap_enabled:
|
||
dn = self._resolve_group_dn(grp)
|
||
if not dn:
|
||
return Response(
|
||
{"detail": "Группа не найдена в LDAP"},
|
||
status=status.HTTP_404_NOT_FOUND,
|
||
)
|
||
try:
|
||
member_dns = self._members_payload_to_dns(request.data)
|
||
except ValueError as e:
|
||
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
svc = DirectoryService()
|
||
try:
|
||
svc.group_add_members(dn, member_dns)
|
||
except Exception as e:
|
||
return Response({"detail": str(e)}, status=status.HTTP_502_BAD_GATEWAY)
|
||
|
||
users = self._dns_to_users(member_dns)
|
||
ok_user_ids = [u.id for u in users]
|
||
try:
|
||
with transaction.atomic():
|
||
grp.user_set.add(*users)
|
||
except Exception as e:
|
||
try:
|
||
svc.group_remove_members(dn, member_dns)
|
||
except Exception:
|
||
pass
|
||
return Response(
|
||
{"detail": f"DB error: {e}"},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
)
|
||
|
||
return Response(
|
||
{
|
||
"ok": True,
|
||
"ldap_added": len(member_dns),
|
||
"db_added": len(users),
|
||
"ok_dns": member_dns,
|
||
"ok_user_ids": ok_user_ids,
|
||
},
|
||
status=status.HTTP_200_OK,
|
||
)
|
||
else:
|
||
member_ids = request.data.get("member_ids") or []
|
||
if not isinstance(member_ids, list):
|
||
return Response(
|
||
{"detail": "member_ids must be a list"},
|
||
status=status.HTTP_400_BAD_REQUEST,
|
||
)
|
||
|
||
users = Employee.objects.filter(id__in=member_ids)
|
||
try:
|
||
with transaction.atomic():
|
||
grp.user_set.add(*users)
|
||
except Exception as e:
|
||
return Response(
|
||
{"detail": f"DB error: {e}"},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
)
|
||
|
||
ok_user_ids = [u.id for u in users]
|
||
return Response(
|
||
{
|
||
"ok": True,
|
||
"ldap_added": 0,
|
||
"db_added": len(users),
|
||
"ok_dns": [],
|
||
"ok_user_ids": ok_user_ids,
|
||
},
|
||
status=status.HTTP_200_OK,
|
||
)
|
||
|
||
@action(detail=True, methods=["post"], url_path="remove-members")
|
||
def remove_members(self, request, pk=None) -> Response:
|
||
"""Удаляет участников из LDAP-группы и разрывает связи в БД."""
|
||
grp = self.get_object()
|
||
ldap_enabled = _is_ldap_enabled()
|
||
|
||
if ldap_enabled:
|
||
dn = self._resolve_group_dn(grp)
|
||
if not dn:
|
||
return Response(
|
||
{"detail": "Группа не найдена в LDAP"},
|
||
status=status.HTTP_404_NOT_FOUND,
|
||
)
|
||
try:
|
||
member_dns = self._members_payload_to_dns(request.data)
|
||
except ValueError as e:
|
||
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
svc = DirectoryService()
|
||
try:
|
||
svc.group_remove_members(dn, member_dns)
|
||
except Exception as e:
|
||
return Response({"detail": str(e)}, status=status.HTTP_502_BAD_GATEWAY)
|
||
|
||
users = self._dns_to_users(member_dns)
|
||
ok_user_ids = [u.id for u in users]
|
||
try:
|
||
with transaction.atomic():
|
||
grp.user_set.remove(*users)
|
||
except Exception as e:
|
||
try:
|
||
svc.group_add_members(dn, member_dns)
|
||
except Exception:
|
||
pass
|
||
return Response(
|
||
{"detail": f"DB error: {e}"},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
)
|
||
|
||
return Response(
|
||
{
|
||
"ok": True,
|
||
"ldap_removed": len(member_dns),
|
||
"db_removed": len(users),
|
||
"ok_dns": member_dns,
|
||
"ok_user_ids": ok_user_ids,
|
||
},
|
||
status=status.HTTP_200_OK,
|
||
)
|
||
else:
|
||
member_ids = request.data.get("member_ids") or []
|
||
if not isinstance(member_ids, list):
|
||
return Response(
|
||
{"detail": "member_ids must be a list"},
|
||
status=status.HTTP_400_BAD_REQUEST,
|
||
)
|
||
|
||
users = Employee.objects.filter(id__in=member_ids)
|
||
try:
|
||
with transaction.atomic():
|
||
grp.user_set.remove(*users)
|
||
except Exception as e:
|
||
return Response(
|
||
{"detail": f"DB error: {e}"},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
)
|
||
|
||
ok_user_ids = [u.id for u in users]
|
||
return Response(
|
||
{
|
||
"ok": True,
|
||
"ldap_removed": 0,
|
||
"db_removed": len(users),
|
||
"ok_dns": [],
|
||
"ok_user_ids": ok_user_ids,
|
||
},
|
||
status=status.HTTP_200_OK,
|
||
)
|
||
|
||
@action(detail=True, methods=["post"], url_path="replace-members")
|
||
def replace_members(self, request, pk=None) -> Response:
|
||
"""Полностью заменяет состав LDAP-группы и синхронизирует M2M в БД."""
|
||
grp = self.get_object()
|
||
ldap_enabled = _is_ldap_enabled()
|
||
|
||
if ldap_enabled:
|
||
dn = self._resolve_group_dn(grp)
|
||
if not dn:
|
||
return Response(
|
||
{"detail": "Группа не найдена в LDAP"},
|
||
status=status.HTTP_404_NOT_FOUND,
|
||
)
|
||
try:
|
||
desired_dns = self._members_payload_to_dns(request.data)
|
||
except ValueError as e:
|
||
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
svc = DirectoryService()
|
||
try:
|
||
prev_dns = svc.group_list_members(dn)
|
||
except Exception as e:
|
||
return Response({"detail": str(e)}, status=status.HTTP_502_BAD_GATEWAY)
|
||
|
||
try:
|
||
svc.group_replace_members(dn, desired_dns)
|
||
except Exception as e:
|
||
return Response({"detail": str(e)}, status=status.HTTP_502_BAD_GATEWAY)
|
||
|
||
users = self._dns_to_users(desired_dns)
|
||
ok_user_ids = [u.id for u in users]
|
||
try:
|
||
with transaction.atomic():
|
||
grp.user_set.set(users)
|
||
except Exception as e:
|
||
try:
|
||
svc.group_replace_members(dn, prev_dns)
|
||
except Exception:
|
||
pass
|
||
return Response(
|
||
{"detail": f"DB error: {e}"},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
)
|
||
|
||
return Response(
|
||
{
|
||
"ok": True,
|
||
"ldap_total": len(desired_dns),
|
||
"db_total": len(users),
|
||
"ok_dns": desired_dns,
|
||
"ok_user_ids": ok_user_ids,
|
||
},
|
||
status=status.HTTP_200_OK,
|
||
)
|
||
else:
|
||
member_ids = request.data.get("member_ids") or []
|
||
if not isinstance(member_ids, list):
|
||
return Response(
|
||
{"detail": "member_ids must be a list"},
|
||
status=status.HTTP_400_BAD_REQUEST,
|
||
)
|
||
|
||
users = Employee.objects.filter(id__in=member_ids)
|
||
try:
|
||
with transaction.atomic():
|
||
grp.user_set.set(users)
|
||
except Exception as e:
|
||
return Response(
|
||
{"detail": f"DB error: {e}"},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
)
|
||
|
||
ok_user_ids = [u.id for u in users]
|
||
return Response(
|
||
{
|
||
"ok": True,
|
||
"ldap_total": 0,
|
||
"db_total": len(users),
|
||
"ok_dns": [],
|
||
"ok_user_ids": ok_user_ids,
|
||
},
|
||
status=status.HTTP_200_OK,
|
||
)
|