2026-09-07 19:43:48 +03:00

185 lines
7.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# employees/apps.py
import datetime
from django.apps import AppConfig
from django.conf import settings
def _ensure_ldapdb_timezone_compat():
"""Патч совместимости django-ldapdb 1.5.1 с Django 5.x.
django-ldapdb использует ``timezone.utc.localize(...)`` (pytz API),
но Django 5.x удалил ``django.utils.timezone.utc``.
Пробрасываем обёртку вокруг ``datetime.timezone.utc``
с методом ``localize``.
"""
from django.utils import timezone
if hasattr(timezone, "utc"):
return
class _Utc(datetime.tzinfo):
"""Минимальная обёртка, совместимая с pytz-интерфейсом (localize)."""
ZERO = datetime.timedelta(0)
def utcoffset(self, dt):
return self.ZERO
def tzname(self, dt):
return "UTC"
def dst(self, dt):
return self.ZERO
def localize(self, dt, is_dst=False):
return dt.replace(tzinfo=datetime.timezone.utc)
timezone.utc = _Utc()
class EmployeesConfig(AppConfig):
name = "employees"
def ready(self):
# Миграции импортируют поля django-ldapdb даже когда запись в LDAP
# отключена, поэтому совместимость с Django 5 нужна в любом режиме.
_ensure_ldapdb_timezone_compat()
if getattr(settings, "LDAP_WRITE_ENABLED", False):
self._patch_ldapdb_compiler()
from . import signals # noqa: F401
def _patch_ldapdb_compiler(self):
"""Патчит django-ldapdb для поддержки Value выражений.
Проблема: ldapdb компилятор в results_iter вызывает e[0].field.attname,
но у полей из Value() выражений (например, от .exists()) нет attname.
Решение: Патчим results_iter, заменяя прямые обращения к .attname
на безопасный getattr(..., 'attname', None).
"""
try:
from django.db.models import aggregates
from ldapdb.backends.ldap.compiler import SQLCompiler
from ldapdb.models.fields import ListField
original_results_iter = SQLCompiler.results_iter
def safe_results_iter(
self,
results=None,
tuple_expected=False,
chunked_fetch=False,
chunk_size=None,
):
"""Обёртка над original results_iter.
Использует безопасную проверку attname.
"""
import ldap as ldap_lib
try:
yield from original_results_iter(
self,
results=results,
tuple_expected=tuple_expected,
chunked_fetch=chunked_fetch,
**(
{"chunk_size": chunk_size} if chunk_size is not None else {}
),
)
except ldap_lib.NO_SUCH_OBJECT:
# DN не существует — возвращаем пустой результат
# (django-ldapdb бросает ldap.NO_SUCH_OBJECT
# вместо DoesNotExist)
return
except AttributeError as exc:
if "attname" not in str(exc):
raise
# Fallback: повторяем логику оригинала
# с безопасным getattr
yield from self._safe_results_iter_fallback()
def _safe_results_iter_fallback(self):
"""Fallback-итератор с безопасными проверками attname."""
import ldap as ldap_lib
lookup = None
try:
from ldapdb.backends.ldap.compiler import query_as_ldap
lookup = query_as_ldap(
self.query, compiler=self, connection=self.connection
)
except Exception:
return
if lookup is None:
return
if len(self.query.select):
fields = [x.field for x in self.query.select]
else:
fields = self.query.model._meta.fields
attrlist = [x.db_column for x in fields if x.db_column]
try:
vals = self.connection.search_s(
base=lookup.base,
scope=lookup.scope,
filterstr=lookup.filterstr,
attrlist=attrlist,
)
except ldap_lib.NO_SUCH_OBJECT:
return
# Слайсинг
pos = 0
for dn, attrs in vals:
if (self.query.low_mark and pos < self.query.low_mark) or (
self.query.high_mark is not None and pos >= self.query.high_mark
):
pos += 1
continue
row = []
self.setup_query()
for e in self.select:
if isinstance(e[0], aggregates.Count):
value = 0
input_field = e[0].get_source_expressions()[0].field
if getattr(input_field, "attname", None) == "dn":
value = 1
elif hasattr(input_field, "from_ldap"):
result = input_field.from_ldap(
attrs.get(input_field.db_column, []),
connection=self.connection,
)
if result:
value = 1
if isinstance(input_field, ListField):
value = len(result)
row.append(value)
else:
if getattr(e[0].field, "attname", None) == "dn":
row.append(dn)
elif hasattr(e[0].field, "from_ldap"):
row.append(
e[0].field.from_ldap(
attrs.get(e[0].field.db_column, []),
connection=self.connection,
)
)
else:
row.append(None)
yield row
pos += 1
SQLCompiler.results_iter = safe_results_iter
SQLCompiler._safe_results_iter_fallback = _safe_results_iter_fallback
except ImportError:
# ldapdb не установлен - это нормально для тестов без LDAP
pass