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_*
- Created CODE_QUALITY_CHECKLIST.md with comprehensive guide
- Added audit_code_patterns.sh for automated code scanning
- Created common/request_utils.py with safe_int(), safe_bool(), etc.
- Fixed unsafe request.data access in notifications/api/views.py
- Updated ldap_password_mixin.py docstring with safe example
Audit found and documented:
- 10 unsafe request.data['key'] accesses
- 8+ unsafe int() conversions without try/except
- Multiple .strip() calls without None checks
DRF request.data is dict for JSON but QueryDict for form data.
Use hasattr check to call .dict() only when available, otherwise
use dict() constructor for plain dicts.
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)