admin: единое управление ролями в «Группы доступа»
Две сущности (auth.Group + PermissionGroup-профиль) слиты в одну страницу админки Group: имя, разделы портала (curated), прочие права, описание, активна, ссылка на матрицу доступа. Отдельный пункт «Группы разрешений» убран из меню; модель PermissionGroup остаётся профилем под капотом. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+64
-48
@@ -1,7 +1,7 @@
|
||||
from django import forms
|
||||
from django.contrib import admin, messages
|
||||
from django.contrib.auth import password_validation
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.contrib.auth.models import Group, Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||
from django.utils.html import format_html
|
||||
@@ -430,76 +430,92 @@ class EmployeeDefaultsAdmin(ModelAdmin):
|
||||
return False
|
||||
|
||||
|
||||
class PermissionGroupAdminForm(forms.ModelForm):
|
||||
class GroupAdminForm(forms.ModelForm):
|
||||
"""Единая форма роли: имя + портальный доступ + прочие права + профиль."""
|
||||
description = forms.CharField(
|
||||
label=_('Описание'), required=False,
|
||||
widget=forms.Textarea(attrs={'rows': 2}),
|
||||
)
|
||||
is_active = forms.BooleanField(label=_('Активна'), required=False, initial=True)
|
||||
portal_permissions = forms.ModelMultipleChoiceField(
|
||||
label=_('Разделы портала'),
|
||||
queryset=Permission.objects.none(),
|
||||
required=False,
|
||||
widget=admin.widgets.FilteredSelectMultiple(_('разделы'), is_stacked=False),
|
||||
help_text=_('Базовый набор доступов к разделам для этой роли. Доп. права сотруднику — через overrides.'),
|
||||
help_text=_('Доступ к разделам портала. Прочие права (кассовые и т.п.) — в списке «Права».'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = PermissionGroup
|
||||
fields = ('group', 'description', 'is_active')
|
||||
model = Group
|
||||
fields = ('name', 'permissions')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['portal_permissions'].queryset = _portal_permissions_qs()
|
||||
if self.instance and self.instance.pk and self.instance.group_id:
|
||||
portal_pks = set(_portal_permissions_qs().values_list('pk', flat=True))
|
||||
self.fields['portal_permissions'].initial = [
|
||||
p for p in self.instance.group.permissions.all() if p.pk in portal_pks
|
||||
]
|
||||
portal_qs = _portal_permissions_qs()
|
||||
self._portal_ids = set(portal_qs.values_list('pk', flat=True))
|
||||
self.fields['portal_permissions'].queryset = portal_qs
|
||||
# Портальные права не дублируем в общем списке — ими управляет виджет выше.
|
||||
self.fields['permissions'].queryset = (
|
||||
self.fields['permissions'].queryset.exclude(pk__in=self._portal_ids)
|
||||
)
|
||||
if self.instance and self.instance.pk:
|
||||
profile = getattr(self.instance, 'profile', None)
|
||||
if profile:
|
||||
self.fields['description'].initial = profile.description
|
||||
self.fields['is_active'].initial = profile.is_active
|
||||
self.fields['portal_permissions'].initial = list(
|
||||
self.instance.permissions.filter(pk__in=self._portal_ids)
|
||||
)
|
||||
|
||||
def save_portal_permissions(self, obj):
|
||||
portal_qs = list(_portal_permissions_qs())
|
||||
selected = self.cleaned_data.get('portal_permissions')
|
||||
if selected is None:
|
||||
return
|
||||
group = obj.group
|
||||
group.permissions.remove(*portal_qs)
|
||||
group.permissions.add(*selected)
|
||||
def _save_m2m(self):
|
||||
# set() для permissions снимет портальные (их нет в queryset) — вернём по виджету.
|
||||
super()._save_m2m()
|
||||
self.instance.permissions.add(*(self.cleaned_data.get('portal_permissions') or []))
|
||||
PermissionGroup.objects.update_or_create(
|
||||
group=self.instance,
|
||||
defaults={
|
||||
'description': self.cleaned_data.get('description', ''),
|
||||
'is_active': self.cleaned_data.get('is_active', True),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@admin.register(PermissionGroup)
|
||||
class PermissionGroupAdmin(ModelAdmin):
|
||||
form = PermissionGroupAdminForm
|
||||
list_display = ('group', 'is_active', 'description', 'portal_matrix_link')
|
||||
list_filter = ('is_active',)
|
||||
search_fields = ('group__name', 'description')
|
||||
if admin.site.is_registered(Group):
|
||||
admin.site.unregister(Group)
|
||||
|
||||
|
||||
@admin.register(Group)
|
||||
class GroupAdmin(ModelAdmin):
|
||||
"""Роль = группа доступа: имя, портальные разделы, прочие права, описание/активна."""
|
||||
form = GroupAdminForm
|
||||
filter_horizontal = ('permissions',)
|
||||
search_fields = ('name',)
|
||||
list_display = ('name', 'active_display', 'description_display', 'portal_matrix_link')
|
||||
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'fields': ('group', 'description', 'is_active', 'portal_permissions'),
|
||||
}),
|
||||
(None, {'fields': ('name', 'description', 'is_active')}),
|
||||
(_('Доступ к разделам портала'), {'fields': ('portal_permissions',)}),
|
||||
(_('Прочие права'), {'fields': ('permissions',), 'classes': ('collapse',)}),
|
||||
)
|
||||
|
||||
@admin.display(description=_('Матрица в портале'))
|
||||
@admin.display(description=_('Активна'), boolean=True)
|
||||
def active_display(self, obj):
|
||||
p = getattr(obj, 'profile', None)
|
||||
return p.is_active if p else True
|
||||
|
||||
@admin.display(description=_('Описание'))
|
||||
def description_display(self, obj):
|
||||
p = getattr(obj, 'profile', None)
|
||||
return (p.description if p else '') or '—'
|
||||
|
||||
@admin.display(description=_('Матрица доступа'))
|
||||
def portal_matrix_link(self, obj):
|
||||
if not obj or not obj.group_id:
|
||||
if not obj or not obj.pk:
|
||||
return '—'
|
||||
from django.urls import reverse
|
||||
from django.utils.html import format_html
|
||||
url = reverse('user_group_access', kwargs={'group_id': obj.group_id})
|
||||
url = reverse('user_group_access', kwargs={'group_id': obj.pk})
|
||||
return format_html('<a href="{}" target="_blank">{}</a>', url, _('Открыть'))
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
if request.user.is_superuser:
|
||||
return ()
|
||||
return ('group', 'description', 'is_active', 'portal_permissions')
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return request.user.is_superuser
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return request.user.is_superuser
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
super().save_model(request, obj, form, change)
|
||||
if request.user.is_superuser:
|
||||
form.save_portal_permissions(obj)
|
||||
|
||||
@admin.register(EmployeePermissionOverride)
|
||||
class EmployeePermissionOverrideAdmin(ModelAdmin):
|
||||
list_display = (
|
||||
|
||||
Reference in New Issue
Block a user