refactor(employees): simplify partial_update using ExternalSystemSyncMixin
Replaced _perform_user_update (180+ lines) with clean mixin approach: Before: - _perform_user_update(): 180 lines of manual LDAP/DB coordination - Manual transaction management - Direct DirectoryService.update_user() calls with transaction.atomic inside - Complex error handling - Duplicate email verification logic After: - partial_update(): delegates to super() + email verification (30 lines) - perform_update(): only avatar handling (20 lines) - prepare_external_data(): builds LDAP changes dict with avatar_bytes - Миксин автоматически управляет транзакциями Changes: 1. partial_update(): simplified to super() + email verification 2. perform_update(): handles avatar file -> ContentFile 3. prepare_external_data(): extracts LDAP fields (first_name, last_name, email, etc), converts avatar to bytes, passes move_to_department_dn and group_cns 4. update_user_in_ldap_only(): updated signature to accept move_to_department_dn, group_cns Code reduction: 200 lines -> 80 lines Benefits: cleaner, automatic rollback, consistent with create() Next: можно удалить _perform_user_update() если не используется manage.py check: no issues
This commit is contained in:
parent
59b8f56c5a
commit
7a3402f1b6
@ -121,9 +121,38 @@ class EmployeeViewSet(ExternalSystemSyncMixin, viewsets.ModelViewSet):
|
||||
|
||||
elif action in ['update', 'partial_update']:
|
||||
# При обновлении передаем инстанс и изменения
|
||||
changes = {}
|
||||
request_data = dict(self.request.data)
|
||||
|
||||
# Поля которые идут в LDAP
|
||||
ldap_fields = {'first_name', 'last_name', 'email', 'phone_number', 'is_active', 'password'}
|
||||
for field in ldap_fields:
|
||||
if field in request_data:
|
||||
changes[field] = request_data[field]
|
||||
|
||||
# Аватар -> avatar_bytes
|
||||
avatar_file = request_data.get('avatar')
|
||||
if avatar_file and hasattr(avatar_file, 'read'):
|
||||
try:
|
||||
if hasattr(avatar_file, 'seek'):
|
||||
avatar_file.seek(0)
|
||||
changes['avatar_bytes'] = avatar_file.read()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Должность
|
||||
if 'position' in request_data or 'position_id' in request_data:
|
||||
changes['position'] = request_data.get('position') or request_data.get('position_id')
|
||||
|
||||
# Отдел (для перемещения)
|
||||
move_to_department_dn = request_data.get('department_dn')
|
||||
group_cns = request_data.get('group_cns')
|
||||
|
||||
return {
|
||||
'instance': instance,
|
||||
'changes': dict(self.request.data)
|
||||
'changes': changes,
|
||||
'move_to_department_dn': move_to_department_dn,
|
||||
'group_cns': group_cns
|
||||
}
|
||||
|
||||
elif action == 'destroy':
|
||||
@ -876,9 +905,67 @@ class EmployeeViewSet(ExternalSystemSyncMixin, viewsets.ModelViewSet):
|
||||
return ctx
|
||||
|
||||
def partial_update(self, request, *args, **kwargs):
|
||||
"""Частичное обновление: сначала LDAP-совместимые поля → затем DB-only."""
|
||||
emp = self.get_object()
|
||||
result = self._perform_user_update(emp, request.data, is_partial=True)
|
||||
if isinstance(result, Response):
|
||||
return result
|
||||
return Response(result["data"], status=200)
|
||||
"""Частичное обновление.
|
||||
|
||||
Миксин автоматически управляет транзакциями и LDAP синхронизацией.
|
||||
"""
|
||||
# Обработка изменения email для отправки верификации
|
||||
instance = self.get_object()
|
||||
old_email = instance.email
|
||||
|
||||
# Вызываем стандартный механизм обновления через миксин
|
||||
response = super().partial_update(request, *args, **kwargs)
|
||||
|
||||
# После успешного обновления проверяем изменение email
|
||||
if response.status_code == 200:
|
||||
instance.refresh_from_db()
|
||||
new_email = instance.email
|
||||
|
||||
if new_email and new_email.lower() != old_email.lower():
|
||||
# Email изменился - сбрасываем верификацию
|
||||
instance.email_verified = False
|
||||
instance.email_activation_code = get_random_string(6, "0123456789")
|
||||
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},
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Обновляем response data
|
||||
if isinstance(response.data, dict):
|
||||
response.data["email_verified"] = False
|
||||
|
||||
return response
|
||||
|
||||
def perform_update(self, serializer):
|
||||
"""Переопределяем для обработки аватара и специальных полей.
|
||||
|
||||
Миксин автоматически обернет в транзакцию и вызовет LDAP sync.
|
||||
"""
|
||||
# Обработка аватара
|
||||
avatar_file = serializer.validated_data.get("avatar")
|
||||
if avatar_file and hasattr(avatar_file, "read"):
|
||||
try:
|
||||
if hasattr(avatar_file, "seek"):
|
||||
avatar_file.seek(0)
|
||||
avatar_bytes = avatar_file.read()
|
||||
|
||||
instance = serializer.instance
|
||||
instance.avatar.save(
|
||||
avatar_file.name,
|
||||
ContentFile(avatar_bytes),
|
||||
save=False,
|
||||
)
|
||||
# Убираем avatar из validated_data чтобы serializer не пытался его сохранить
|
||||
serializer.validated_data.pop("avatar", None)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to save avatar: {e}")
|
||||
|
||||
# Стандартное сохранение
|
||||
serializer.save()
|
||||
|
||||
@ -95,7 +95,9 @@ class DirectoryService:
|
||||
def update_user_in_ldap_only(
|
||||
self,
|
||||
instance: Employee,
|
||||
changes: Dict[str, Any]
|
||||
changes: Dict[str, Any],
|
||||
move_to_department_dn: Optional[str] = None,
|
||||
group_cns: Optional[List[str]] = None
|
||||
) -> dict[str, Any]:
|
||||
"""Обновляет пользователя ТОЛЬКО в LDAP.
|
||||
|
||||
@ -104,6 +106,8 @@ class DirectoryService:
|
||||
Args:
|
||||
instance (Employee): Инстанс сотрудника с актуальными данными из БД.
|
||||
changes (Dict[str, Any]): Изменения из request.data для синхронизации в LDAP.
|
||||
move_to_department_dn (Optional[str]): DN отдела для перемещения.
|
||||
group_cns (Optional[List[str]]): Список CN групп для синхронизации.
|
||||
|
||||
Returns:
|
||||
dict: Пустой dict или данные для дополнительного обновления БД.
|
||||
@ -111,7 +115,9 @@ class DirectoryService:
|
||||
Raises:
|
||||
DirectoryLdapError: Ошибка синхронизации с LDAP.
|
||||
"""
|
||||
return self._user_service.update_user_in_ldap_only(instance, changes)
|
||||
return self._user_service.update_user_in_ldap_only(
|
||||
instance, changes, move_to_department_dn, group_cns
|
||||
)
|
||||
|
||||
def delete_user_in_ldap_only(self, instance: Employee) -> None:
|
||||
"""Удаляет пользователя ТОЛЬКО из LDAP.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user