125 lines
5.8 KiB
Python
125 lines
5.8 KiB
Python
# employees/apps.py
|
||
from django.apps import AppConfig
|
||
|
||
|
||
class EmployeesConfig(AppConfig):
|
||
name = 'employees'
|
||
|
||
def ready(self):
|
||
import employees.signals # Все сигналы (common, birthday, ldap)
|
||
import employees.rules # django-rules: регистрация предикатов и правил доступа
|
||
|
||
# Патч для django-ldapdb: исправление бага с Value(1) в .exists()
|
||
self._patch_ldapdb_compiler()
|
||
|
||
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 ldapdb.backends.ldap.compiler import SQLCompiler
|
||
from django.db.models import aggregates
|
||
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):
|
||
"""Обёртка над оригинальным 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
|