ad/mattermost: сопоставление и OU в админке, доступы к командам и каналам по отделам и группам.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-12 13:58:36 +03:00
parent b4876c4efd
commit 456af4a836
22 changed files with 1209 additions and 331 deletions
+74 -1
View File
@@ -94,6 +94,15 @@ class DirectoryTypeAdmin(ModelAdmin, ImportExportModelAdmin):
class DirectoryForm(TreeNodeForm):
"""Кастомная форма с дополнительными проверками"""
create_ou_in_ad = forms.BooleanField(
label=_('Создать OU в AD автоматически'), required=False,
help_text=_(
'Если включено и поле «OU в Active Directory» ниже не задано — при сохранении '
'будет создана новая OU с названием справочника (вложенная в OU родителя, '
'если он привязан; иначе — в users_ou коннектора).'
),
)
class Meta:
model = Directory
fields = '__all__'
@@ -111,6 +120,30 @@ class DirectoryForm(TreeNodeForm):
if 'parent' in self.fields and self.instance.directory_type_id:
self.fields['parent'].queryset = self._get_allowed_parents()
if 'ad_ou_dn' in self.fields:
try:
from apps.ad_connector.ad_service import ADService
service = ADService()
try:
ous = service.list_ous()
root = service.users_ou
finally:
service.unbind()
choices = [('', '— не задано (users_ou коннектора) —')]
if root:
choices.append((root, f'{root} (корень)'))
choices += [(ou['dn'], ou['dn']) for ou in ous if ou['dn'] != root]
self.fields['ad_ou_dn'].widget = forms.Select(choices=choices)
self.fields['ad_ou_dn'].required = False
except Exception:
pass
manage_url = reverse('admin:ad_connector_ou_manage')
self.fields['ad_ou_dn'].help_text = format_html(
'{} <a href="{}" target="_blank">Управлять деревом OU в AD →</a>',
self.fields['ad_ou_dn'].help_text or '',
manage_url,
)
def _get_allowed_parents(self):
"""Получить разрешенных родителей для текущего типа"""
current_type = self.instance.directory_type
@@ -150,7 +183,7 @@ class DirectoryAdmin(ModelAdmin, ImportExportModelAdmin):
'fields': ('directory_type', 'parent', 'code', 'name', 'description', 'is_active')
}),
(_('Должности и локации'), {
'fields': ('location_types', 'ad_group_dn', 'ad_ou_dn'),
'fields': ('location_types', 'ad_group_dn', 'ad_ou_dn', 'create_ou_in_ad'),
'description': 'location_types — для должностей: в каких локациях доступна. '
'ad_group_dn, ad_ou_dn — для отделов.',
'classes': ('collapse',)
@@ -162,6 +195,46 @@ class DirectoryAdmin(ModelAdmin, ImportExportModelAdmin):
)
filter_horizontal = ['location_types']
def save_model(self, request, obj, form, change):
old = None
if change:
old = Directory.objects.filter(pk=obj.pk).values('parent_id', 'ad_ou_dn').first()
if form.cleaned_data.get('create_ou_in_ad') and not obj.ad_ou_dn:
parent_dn = obj.parent.ad_ou_dn if obj.parent_id and obj.parent.ad_ou_dn else None
try:
from apps.ad_connector.ad_service import ADService
service = ADService()
try:
obj.ad_ou_dn = service.create_ou(obj.name, parent_dn)
finally:
service.unbind()
except Exception as exc:
messages.error(request, _('Не удалось создать OU в AD: %s') % exc)
super().save_model(request, obj, form, change)
if old and old['ad_ou_dn'] and obj.ad_ou_dn == old['ad_ou_dn'] and old['parent_id'] != obj.parent_id:
from apps.ad_connector.ad_service import ADService
from apps.ad_connector.services.sync_service import relocate_ou_references
try:
service = ADService()
try:
desired_parent = (
obj.parent.ad_ou_dn
if obj.parent_id and obj.parent.ad_ou_dn
else service.users_ou
)
current_parent = old['ad_ou_dn'].split(',', 1)[1]
if desired_parent and desired_parent != current_parent:
new_dn = service.move_ou(old['ad_ou_dn'], desired_parent)
relocate_ou_references(old['ad_ou_dn'], new_dn)
messages.success(request, _('OU перенесена в AD: %s') % new_dn)
finally:
service.unbind()
except Exception as exc:
messages.error(request, _('Не удалось перенести OU в AD: %s') % exc)
def get_queryset(self, request):
return super().get_queryset(request).select_related(
'parent', 'directory_type'