Fix: Refactor LDAP field handling to use get_ldap_str()

- Moved get_ldap_str() helper to ldap_utils.py for reuse
- Updated user_service.py to use shared helper from utils
- Updated admin.py to safely extract string values from LDAP fields
- LDAP fields can be lists, strings, or None - helper handles all cases
- Fixes sync_from_ldap_to_django and compare_ldap_django actions
- Prevents 'list' object errors when comparing or assigning LDAP fields
This commit is contained in:
lizerk 2026-03-20 00:40:51 +03:00
parent be2e899046
commit a3bd9226a5
3 changed files with 41 additions and 35 deletions

View File

@ -13,6 +13,7 @@ from django.db.models import OuterRef, Exists
from employees.models import Employee, LdapSyncState
from .orm_models import LdapUser, LdapGroup, LdapOrganizationalUnit
from .utils.ldap_utils import get_ldap_str
class LdapSyncStateInline(admin.TabularInline):
@ -335,10 +336,11 @@ class LdapUserAdmin(admin.ModelAdmin):
emp = Employee.objects.get(pk=int(ldap_user.employee_number))
# Обновляем Django из LDAP
emp.first_name = ldap_user.given_name or emp.first_name
emp.last_name = ldap_user.sn or emp.last_name
emp.email = ldap_user.mail or emp.email
emp.phone_number = ldap_user.telephone_number or ldap_user.mobile or emp.phone_number
emp.first_name = get_ldap_str(ldap_user.given_name) or emp.first_name
emp.last_name = get_ldap_str(ldap_user.sn) or emp.last_name
emp.email = get_ldap_str(ldap_user.mail) or emp.email
ldap_phone = get_ldap_str(ldap_user.telephone_number or ldap_user.mobile)
emp.phone_number = ldap_phone or emp.phone_number
emp.save()
# Обновляем LdapSyncState
@ -444,16 +446,19 @@ class LdapUserAdmin(admin.ModelAdmin):
user_diffs = []
# Сравниваем поля
if ldap_user.given_name != emp.first_name:
user_diffs.append(f"Имя: LDAP='{ldap_user.given_name}' vs Django='{emp.first_name}'")
ldap_first = get_ldap_str(ldap_user.given_name)
if ldap_first != emp.first_name:
user_diffs.append(f"Имя: LDAP='{ldap_first}' vs Django='{emp.first_name}'")
if ldap_user.sn != emp.last_name:
user_diffs.append(f"Фамилия: LDAP='{ldap_user.sn}' vs Django='{emp.last_name}'")
ldap_last = get_ldap_str(ldap_user.sn)
if ldap_last != emp.last_name:
user_diffs.append(f"Фамилия: LDAP='{ldap_last}' vs Django='{emp.last_name}'")
if ldap_user.mail != emp.email:
user_diffs.append(f"Email: LDAP='{ldap_user.mail}' vs Django='{emp.email}'")
ldap_email = get_ldap_str(ldap_user.mail)
if ldap_email != emp.email:
user_diffs.append(f"Email: LDAP='{ldap_email}' vs Django='{emp.email}'")
ldap_phone = ldap_user.telephone_number or ldap_user.mobile
ldap_phone = get_ldap_str(ldap_user.telephone_number or ldap_user.mobile)
if ldap_phone and ldap_phone != emp.phone_number:
user_diffs.append(f"Телефон: LDAP='{ldap_phone}' vs Django='{emp.phone_number}'")

View File

@ -32,7 +32,7 @@ from ..infrastructure.connections import _ldap
from ..repositories.ldap_repository import (
ensure_container_exists,
)
from ..utils.ldap_utils import cn_candidates
from ..utils.ldap_utils import cn_candidates, get_ldap_str
from ..utils.text_utils import esc_rdn
from .base_service import BaseService
from .constants import (
@ -47,27 +47,6 @@ from .user_mapper_service import UserMapperService
logger = logging.getLogger(__name__)
def _get_ldap_str(value: Any) -> str:
"""Безопасно извлекает строковое значение из LDAP поля.
LDAP поля могут возвращаться как:
- строка (str)
- список строк (list)
- None
Args:
value: Значение LDAP поля
Returns:
str: Первый элемент (если список) или само значение (если строка), или пустая строка
"""
if value is None:
return ""
if isinstance(value, list):
return value[0] if value else ""
return str(value)
class UserService(BaseService):
"""Сервис для управления пользователями в LDAP и Django.
@ -779,8 +758,8 @@ class UserService(BaseService):
if "first_name" in model_changes or "last_name" in model_changes:
# Формируем новый cn из имени и фамилии
parts = []
given_name_str = _get_ldap_str(ldap_user.given_name).strip()
sn_str = _get_ldap_str(ldap_user.sn).strip()
given_name_str = get_ldap_str(ldap_user.given_name).strip()
sn_str = get_ldap_str(ldap_user.sn).strip()
if given_name_str:
parts.append(given_name_str)

View File

@ -25,6 +25,28 @@ def _first(val: Any) -> Any:
return val
def get_ldap_str(value: Any) -> str:
"""Безопасно извлекает строковое значение из LDAP поля.
LDAP поля могут возвращаться как:
- строка (str)
- список строк (list)
- None
Args:
value: Значение LDAP поля
Returns:
str: Первый элемент (если список) или само значение (если строка),
или пустая строка
"""
if value is None:
return ""
if isinstance(value, list):
return value[0] if value else ""
return str(value)
def _uac_is_active(uac_value: Any) -> bool:
"""Определяет активность учётной записи по userAccountControl.