3359a8db13
Модель/сервисы HR-заявок с заменами, приглашения на установку пароля (инвайты), обновлённая страница профиля сотрудника и правки 2FA. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
113 lines
4.3 KiB
Python
113 lines
4.3 KiB
Python
from django.db import models
|
||
from django.utils import timezone
|
||
from django.utils.translation import gettext_lazy as _
|
||
from core.models import BaseModel
|
||
|
||
|
||
class PermissionGroup(BaseModel):
|
||
"""
|
||
Расширение стандартной auth.Group — добавляет описание и флаг активности.
|
||
Права назначаются через стандартный механизм auth.Group.permissions.
|
||
"""
|
||
group = models.OneToOneField(
|
||
'auth.Group', on_delete=models.CASCADE,
|
||
related_name='profile', verbose_name=_('Группа')
|
||
)
|
||
description = models.TextField(_('Описание'), blank=True)
|
||
is_active = models.BooleanField(_('Активна'), default=True)
|
||
|
||
class Meta:
|
||
verbose_name = _('Группа разрешений')
|
||
verbose_name_plural = _('Группы разрешений')
|
||
ordering = ['group__name']
|
||
|
||
def __str__(self):
|
||
return self.group.name
|
||
|
||
def permission_codes(self) -> list[str]:
|
||
return list(
|
||
self.group.permissions.values_list('codename', flat=True)
|
||
)
|
||
|
||
|
||
class GroupSectionAccess(BaseModel):
|
||
"""
|
||
Действия Create/Update/Delete по разделу портала для auth.Group.
|
||
|
||
Просмотр раздела — Django permission core.access_<section_key> на группе.
|
||
"""
|
||
group = models.ForeignKey(
|
||
'auth.Group',
|
||
on_delete=models.CASCADE,
|
||
related_name='section_access',
|
||
verbose_name=_('Группа'),
|
||
)
|
||
section_key = models.CharField(_('Раздел'), max_length=64, db_index=True)
|
||
can_create = models.BooleanField(_('Создать'), default=True)
|
||
can_update = models.BooleanField(_('Обновить'), default=True)
|
||
can_delete = models.BooleanField(_('Удалить'), default=True)
|
||
|
||
class Meta:
|
||
verbose_name = _('Доступ группы к разделу')
|
||
verbose_name_plural = _('Доступы групп к разделам')
|
||
ordering = ['group__name', 'section_key']
|
||
constraints = [
|
||
models.UniqueConstraint(
|
||
fields=['group', 'section_key'],
|
||
name='uniq_group_section_access',
|
||
),
|
||
]
|
||
|
||
def __str__(self):
|
||
return f'{self.group.name}: {self.section_key}'
|
||
|
||
|
||
class EmployeePermissionOverride(BaseModel):
|
||
"""
|
||
Индивидуальные override-ы прав для сотрудника.
|
||
grant — выдать право сверх группы.
|
||
revoke — отозвать право даже если оно есть в группе.
|
||
Поддерживает временны́е ограничения через valid_from / valid_until.
|
||
"""
|
||
OVERRIDE_TYPES = [
|
||
('grant', _('Выдать')),
|
||
('revoke', _('Отозвать')),
|
||
]
|
||
|
||
employee = models.ForeignKey(
|
||
'employees.Employee', on_delete=models.CASCADE,
|
||
related_name='permission_overrides', verbose_name=_('Сотрудник')
|
||
)
|
||
permission = models.ForeignKey(
|
||
'auth.Permission', on_delete=models.CASCADE,
|
||
verbose_name=_('Разрешение')
|
||
)
|
||
override_type = models.CharField(
|
||
_('Тип'), max_length=10, choices=OVERRIDE_TYPES
|
||
)
|
||
valid_from = models.DateField(_('Действует с'), null=True, blank=True)
|
||
valid_until = models.DateField(_('Действует до'), null=True, blank=True)
|
||
is_active = models.BooleanField(_('Активен'), default=True)
|
||
comment = models.TextField(_('Причина / комментарий'), blank=True)
|
||
|
||
class Meta:
|
||
verbose_name = _('Override разрешения')
|
||
verbose_name_plural = _('Override разрешений')
|
||
ordering = ['employee', 'permission__codename']
|
||
indexes = [
|
||
models.Index(fields=['employee', 'is_active']),
|
||
]
|
||
|
||
def __str__(self):
|
||
return f"{self.employee} — {self.get_override_type_display()}: {self.permission.codename}"
|
||
|
||
def is_currently_active(self) -> bool:
|
||
if not self.is_active:
|
||
return False
|
||
today = timezone.now().date()
|
||
if self.valid_from and today < self.valid_from:
|
||
return False
|
||
if self.valid_until and today > self.valid_until:
|
||
return False
|
||
return True
|