portal: отдельные виджеты PNMS/PVMS-прав в ролях и seed-миграции.

Вынесены can_* для кассы и посещаемости, синхронизация в builders/signals, seed групп в core.0023–0024.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-06 17:22:41 +03:00
parent b441b4f781
commit 7a149dd84d
9 changed files with 522 additions and 37 deletions
+61 -5
View File
@@ -29,6 +29,31 @@ def _portal_permissions_qs():
return (access | extra).order_by('content_type__app_label', 'codename')
def _pnms_permissions_qs():
from apps.portal_connector.pos_permissions import (
PNMS_ACCESS_APP_LABEL,
PNMS_ACCESS_MODEL,
POS_PERMISSION_CODENAMES,
)
return Permission.objects.filter(
content_type__app_label=PNMS_ACCESS_APP_LABEL,
content_type__model=PNMS_ACCESS_MODEL,
codename__in=POS_PERMISSION_CODENAMES,
).order_by('codename')
def _pvms_permissions_qs():
from apps.portal_connector.pvms_permissions import (
PVMS_ACCESS_APP_LABEL,
PVMS_ACCESS_MODEL,
VISIT_PERMISSION_CODENAMES,
)
return Permission.objects.filter(
content_type__app_label=PVMS_ACCESS_APP_LABEL,
content_type__model=PVMS_ACCESS_MODEL,
codename__in=VISIT_PERMISSION_CODENAMES,
).order_by('codename')
class HierarchicalDepartmentChoiceField(forms.ModelChoiceField):
"""Поле выбора отдела с отступами по иерархии в выпадающем списке."""
@@ -608,7 +633,7 @@ class EmployeeDefaultsAdmin(ModelAdmin):
class GroupAdminForm(forms.ModelForm):
"""Единая форма роли: имя + портальный доступ + прочие права + профиль."""
"""Единая форма роли: портал + PNMS/касса + PVMS + прочие права + профиль."""
description = forms.CharField(
label=_('Описание'), required=False,
widget=forms.Textarea(attrs={'rows': 2}),
@@ -619,7 +644,21 @@ class GroupAdminForm(forms.ModelForm):
queryset=Permission.objects.none(),
required=False,
widget=admin.widgets.FilteredSelectMultiple(_('разделы'), is_stacked=False),
help_text=_('Доступ к разделам портала. Прочие права (кассовые и т.п.) — в списке «Права».'),
help_text=_('Доступ к разделам портала ACORE.'),
)
pnms_permissions = forms.ModelMultipleChoiceField(
label=_('PNMS / касса (PPOS)'),
queryset=Permission.objects.none(),
required=False,
widget=admin.widgets.FilteredSelectMultiple(_('права кассы'), is_stacked=False),
help_text=_('POS can_* — синхронизируются в PNMS → PPOS как pos_permissions.'),
)
pvms_permissions = forms.ModelMultipleChoiceField(
label=_('PVMS / посещаемость'),
queryset=Permission.objects.none(),
required=False,
widget=admin.widgets.FilteredSelectMultiple(_('права посещаемости'), is_stacked=False),
help_text=_('Visit can_* — синхронизируются в PVMS как visit_permissions.'),
)
class Meta:
@@ -629,11 +668,18 @@ class GroupAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
portal_qs = _portal_permissions_qs()
pnms_qs = _pnms_permissions_qs()
pvms_qs = _pvms_permissions_qs()
self._portal_ids = set(portal_qs.values_list('pk', flat=True))
self._pnms_ids = set(pnms_qs.values_list('pk', flat=True))
self._pvms_ids = set(pvms_qs.values_list('pk', flat=True))
self.fields['portal_permissions'].queryset = portal_qs
# Портальные права не дублируем в общем списке — ими управляет виджет выше.
self.fields['pnms_permissions'].queryset = pnms_qs
self.fields['pvms_permissions'].queryset = pvms_qs
# Портал / PNMS / PVMS не дублируем в общем списке — отдельные виджеты.
managed_ids = self._portal_ids | self._pnms_ids | self._pvms_ids
self.fields['permissions'].queryset = (
self.fields['permissions'].queryset.exclude(pk__in=self._portal_ids)
self.fields['permissions'].queryset.exclude(pk__in=managed_ids)
)
if self.instance and self.instance.pk:
profile = getattr(self.instance, 'profile', None)
@@ -643,11 +689,19 @@ class GroupAdminForm(forms.ModelForm):
self.fields['portal_permissions'].initial = list(
self.instance.permissions.filter(pk__in=self._portal_ids)
)
self.fields['pnms_permissions'].initial = list(
self.instance.permissions.filter(pk__in=self._pnms_ids)
)
self.fields['pvms_permissions'].initial = list(
self.instance.permissions.filter(pk__in=self._pvms_ids)
)
def _save_m2m(self):
# set() для permissions снимет портальные (их нет в queryset) — вернём по виджету.
# set() для permissions снимет managed-права (их нет в queryset) — вернём по виджетам.
super()._save_m2m()
self.instance.permissions.add(*(self.cleaned_data.get('portal_permissions') or []))
self.instance.permissions.add(*(self.cleaned_data.get('pnms_permissions') or []))
self.instance.permissions.add(*(self.cleaned_data.get('pvms_permissions') or []))
PermissionGroup.objects.update_or_create(
group=self.instance,
defaults={
@@ -704,6 +758,8 @@ class GroupAdmin(ModelAdmin):
fieldsets = (
(None, {'fields': ('name', 'description', 'is_active')}),
(_('Доступ к разделам портала'), {'fields': ('portal_permissions',)}),
(_('PNMS / касса (PPOS)'), {'fields': ('pnms_permissions',)}),
(_('PVMS / посещаемость'), {'fields': ('pvms_permissions',)}),
(_('Прочие права'), {'fields': ('permissions',), 'classes': ('collapse',)}),
)