from django.contrib import admin
from django.contrib.admin import SimpleListFilter
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
from django.urls import reverse
from unfold.admin import ModelAdmin
from treenode.forms import TreeNodeForm
from django.forms import TextInput, Textarea
from django.db.models import Count
from core.models import DirectoryType, Directory, ConnectorConfig
from import_export.admin import ImportExportModelAdmin
from unfold.contrib.import_export.forms import ExportForm, ImportForm
# Register your models here.
class DirectoryTypeFilter(SimpleListFilter):
title = _('Тип справочника')
parameter_name = 'directory_type'
def lookups(self, request, model_admin):
return DirectoryType.objects.filter(is_active=True).values_list('id', 'name')
def queryset(self, request, queryset):
if self.value():
return queryset.filter(directory_type_id=self.value())
return queryset
class ActiveFilter(SimpleListFilter):
title = _('Статус')
parameter_name = 'is_active'
def lookups(self, request, model_admin):
return (
('1', _('Активные')),
('0', _('Неактивные')),
)
def queryset(self, request, queryset):
if self.value() == '1':
return queryset.filter(is_active=True)
elif self.value() == '0':
return queryset.filter(is_active=False)
return queryset
class HasChildrenFilter(SimpleListFilter):
title = _('Наличие потомков')
parameter_name = 'has_children'
def lookups(self, request, model_admin):
return (
('yes', _('Есть потомки')),
('no', _('Нет потомков')),
)
def queryset(self, request, queryset):
if self.value() == 'yes':
# Фильтруем по наличию детей
return queryset.filter(tn_children_count__gt=0) if hasattr(queryset.model, 'tn_children_count') else queryset
elif self.value() == 'no':
return queryset.filter(tn_children_count=0) if hasattr(queryset.model, 'tn_children_count') else queryset
return queryset
@admin.register(DirectoryType)
class DirectoryTypeAdmin(ModelAdmin, ImportExportModelAdmin):
import_form_class = ImportForm
export_form_class = ExportForm
list_display = [
'name', 'code', 'hierarchy_type', 'is_active',
'directories_count', 'created_at'
]
def directories_count(self, obj):
"""Количество записей в справочнике"""
count = obj.directory_set.count()
if count > 0:
url = reverse('admin:core_directory_changelist') + f'?directory_type={obj.id}'
return format_html(
'{}',
url, count
)
return count
directories_count.short_description = _('Записей')
directories_count.admin_order_field = 'directory_count'
class DirectoryForm(TreeNodeForm):
"""Кастомная форма с дополнительными проверками"""
class Meta:
model = Directory
fields = '__all__'
widgets = {
'code': TextInput(attrs={'size': '30'}),
'name': TextInput(attrs={'size': '50'}),
'description': Textarea(attrs={'rows': 3, 'cols': 60}),
'extra_data': Textarea(attrs={'rows': 4, 'cols': 60, 'class': 'vLargeTextField'}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Фильтруем допустимых родителей по правилам иерархии
if 'parent' in self.fields and self.instance.directory_type_id:
self.fields['parent'].queryset = self._get_allowed_parents()
def _get_allowed_parents(self):
"""Получить разрешенных родителей для текущего типа"""
current_type = self.instance.directory_type
# Для корневых элементов можем выбирать из всех типов
if not self.instance.parent:
return Directory.objects.filter(directory_type=current_type)
# Иначе фильтруем по правилам иерархии
allowed_parents = Directory.objects.none()
for dir_type in DirectoryType.objects.filter(is_active=True):
if dir_type.can_have_child_type(current_type):
allowed_parents = allowed_parents | Directory.objects.filter(directory_type=dir_type)
return allowed_parents.exclude(pk=self.instance.pk)
@admin.register(Directory)
class DirectoryAdmin(ModelAdmin, ImportExportModelAdmin):
"""Справочники с иерархией (отступы по уровню вложенности)."""
import_form_class = ImportForm
export_form_class = ExportForm
form = DirectoryForm
list_display = ['name_with_hierarchy', 'directory_type', 'code', 'is_active', 'updated_at']
list_display_links = ['name_with_hierarchy']
list_filter = [DirectoryTypeFilter, ActiveFilter]
ordering = ('_path',)
fieldsets = (
(_('Основная информация'), {
'fields': ('directory_type', 'parent', 'code', 'name', 'description', 'is_active')
}),
(_('Должности и локации'), {
'fields': ('location_types', 'ad_group_dn'),
'description': 'location_types — для должностей: в каких локациях доступна. ad_group_dn — для отделов.',
'classes': ('collapse',)
}),
(_('Дополнительные данные'), {
'fields': ('extra_data',),
'classes': ('collapse',)
}),
)
filter_horizontal = ['location_types']
def get_queryset(self, request):
return super().get_queryset(request).select_related(
'parent', 'directory_type'
).annotate(
tn_children_count=Count('children', distinct=True)
).order_by('_path')
def name_with_hierarchy(self, obj):
"""Имя с отступом по уровню вложенности. Глубина из _path (точки)."""
path = getattr(obj, '_path', '') or ''
depth = path.count('.') # root=0, дочерний=1, внук=2...
indent_px = depth * 24
url = reverse('admin:core_directory_change', args=[obj.pk])
has_children = getattr(obj, 'tn_children_count', 0) > 0
icon = '📁' if has_children else '📄'
branch = '└ ' if depth else ''
return format_html(
''
'{}{} {}',
indent_px, branch, url, icon, obj.name
)
name_with_hierarchy.short_description = _('Название')
name_with_hierarchy.admin_order_field = '_path'
def location_types_display(self, obj):
"""Отображение типов локаций для должностей"""
if obj.directory_type.code != 'post':
return '-'
location_types = obj.location_types.all()
if not location_types.exists():
return format_html('Все типы')
types_html = ', '.join([
f'{lt.name}'
for lt in location_types
])
return format_html(types_html)
location_types_display.short_description = _('Типы локаций')
def children_count_display(self, obj):
"""Отображение количества потомков"""
count = obj.get_children_count()
if count > 0:
return format_html(
'{}',
count
)
return count
children_count_display.short_description = 'Потомков'
@admin.register(ConnectorConfig)
class ConnectorConfigAdmin(ModelAdmin):
list_display = ('connector_type_display', 'is_enabled', 'host', 'port', 'use_ssl', 'updated_at')
list_filter = ('is_enabled', 'use_ssl', 'connector_type')
fieldsets = (
(_('Основное'), {
'fields': ('connector_type', 'is_enabled', 'notes'),
}),
(_('Подключение'), {
'fields': ('host', 'port', 'use_ssl'),
}),
(_('Аутентификация'), {
'fields': ('username', 'password', 'token'),
'description': _('Укажите токен или логин/пароль — в зависимости от типа коннектора'),
}),
(_('Дополнительные параметры'), {
'fields': ('extra_config',),
'classes': ('collapse',),
}),
)
def connector_type_display(self, obj):
color = '#16a34a' if obj.is_enabled else '#dc2626'
return format_html(
'{}',
color, obj.get_connector_type_display()
)
connector_type_display.short_description = _('Коннектор')
connector_type_display.admin_order_field = 'connector_type'