BREAKING CHANGES:
- Department.head автоматически очищается при увольнении руководителя
- LDAP managedBy атрибут очищается при увольнении
Fixes:
- DN не перемещался при восстановлении сотрудника (Dismissed → Users)
- DN не перемещался при увольнении сотрудника без отделов
- Ошибка при увольнении из множественных отделов (двойной modify_dn)
- Неправильные URL patterns для LDAP моделей в админке
- Двойное кодирование DN в редиректе смены пароля
Features:
- Добавлен метод _ensure_ldap_dn_location для гарантированного перемещения DN
- Добавлена проверка already_there в sync_department_member_to_ldap
- Управление паролями LDAP пользователей через админку:
* Кнопка смены пароля в карточке пользователя
* Массовое действие смены пароля
* Автосинхронизация с Django password hash
- Автоочистка Department.head и LDAP managedBy при увольнении руководителя
- Добавлены методы set_password() и change_password() в LdapUser
Changed:
- Переписан сигнал sync_department_member_to_ldap с реальными методами
- Исправлены URL patterns: ldap_* → employees_*
Both LDAP→Django and Django→LDAP sync actions now check for missing
required fields (name, email, employeeNumber) and display warnings
before syncing. Shows up to 10 warnings per action.
Django's delete_selected passes queryset with dn__in filter that
ldapdb doesn't support. Custom delete_selected_ldap reads DNs from
POST and deletes objects one by one. Also hides standard delete_selected
from action choices.
- Override get_deleted_objects() in LdapUserAdmin to avoid dn__in
queryset that ldapdb doesn't support
- Also fix phone_number UNIQUE constraint in LDAP auth backend
Binary thumbnail_photo data caused 500 error when Django admin tried
to render it as a text input. Now displayed as base64 inline image
preview in a readonly field.
- Department move: set ldap_user.base_dn via ModifyDnMixin (replaces DirectoryService)
- Position sync: assign/unassign POS-groups via PositionService on position change
- DB update: position_id saved to Employee in atomic block
- Best-effort: position sync errors logged but don't interrupt update
- Removed 30 lines of commented-out DirectoryService code
- Removed unused Position import
- Removed unused old_pos/new_pos/pos_in_payload variables
- Fixed stale comment about displayName auto-update in save()
- Position sync remains as a 3-line TODO comment
AD forbids modifying RDN (CN/OU) via modify_s - only via rename.
ModifyDnMixin now:
1. Tracks _original_rdn_value (cn/ou) from __init__/from_db
2. On save(), if RDN changed: reverts to old value → save() → modify_dn
3. This fixes NOT_ALLOWED_ON_RDN for both admin and API edits
Simplified user_service: just set ldap_user.cn = new_cn and call save().
The mixin handles the rest automatically.
AD forbids modifying RDN (CN) through modify_s - must use rename.
- Removed LdapUser.save() override that synced cn→displayName
(this caused cn to be in modlist → NOT_ALLOWED_ON_RDN error)
- user_service now sets displayName explicitly instead of cn
- CN is only changed via conn.modify_dn() after save()
- Flow: set displayName + other attrs → save() → modify_dn(cn)
- Added save() override in LdapUser to auto-update displayName from cn
- Fixed user_service to set ldap_user.cn BEFORE save() (not after)
- This ensures displayName is synced when givenName/sn change
- Previously displayName wasn't updated because cn was set after save()
- Now: set cn → save() → displayName auto-synced → modify_dn()
ldapdb's build_rdn() checks field.primary_key AND field.db_column,
but the 'dn' field has primary_key=True and db_column=None,
while 'cn'/'ou' fields have db_column set but primary_key=False.
No field satisfies both conditions → save() always raises
'Could not build Distinguished Name'.
Fix: ModifyDnMixin.build_rdn() extracts RDN from existing DN
for loaded objects, and builds from rdn_attributes for new ones.