igor_nadein d3b648b438 feat: интеграция django-rules для object-level permissions
- Установлен пакет rules==3.5
- Добавлен ObjectPermissionBackend в AUTHENTICATION_BACKENDS
- Созданы базовые правила для employees:
  * Предикаты: is_hr_staff, is_department_head, is_own_profile и др.
  * Правила: view/change/delete employee, view reports и контакты
- Созданы базовые правила для documents:
  * Предикаты: is_document_owner, has_document_access, can_approve и др.
  * Правила: view/change/delete/approve/publish/share documents
- Правила регистрируются в apps.py (ready() метод)
- Создана подробная документация с примерами использования
  * В views, templates, DRF permissions, Django Admin
  * Сценарии: многоуровневое согласование, временные ограничения
  * Сравнение с django-guardian, отладка, тестирование

Документация: docs/guides/DJANGO_RULES_INTEGRATION.md
2026-02-28 00:07:53 +03:00

187 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
django-rules: декларативные правила доступа для employees
Правила используются для проверки permissions на уровне объектов.
https://github.com/dfunckt/django-rules
"""
import rules
# -----------------------------------------------------------------------------
# ПРЕДИКАТЫ (predicates)
# -----------------------------------------------------------------------------
@rules.predicate
def is_superuser(user):
"""Суперпользователь имеет все права"""
return user.is_superuser
@rules.predicate
def is_staff(user):
"""Сотрудник (is_staff) имеет базовые права"""
return user.is_staff
@rules.predicate
def is_hr_staff(user):
"""
HR сотрудник - определяется по должности или группе.
Адаптируйте под вашу логику: по position, department, группе и т.д.
"""
if not hasattr(user, 'position'):
return False
position_name = getattr(user.position, 'name', '').lower()
return 'hr' in position_name or 'кадр' in position_name
@rules.predicate
def is_department_head(user):
"""
Руководитель отдела - определяется по должности.
Адаптируйте под вашу логику.
"""
if not hasattr(user, 'position'):
return False
position_name = getattr(user.position, 'name', '').lower()
return any(keyword in position_name for keyword in [
'руководитель', 'начальник', 'директор', 'заведующий', 'глава'
])
@rules.predicate
def is_own_profile(user, employee):
"""Пользователь просматривает свой собственный профиль"""
if employee is None:
return False
return user.pk == employee.pk
@rules.predicate
def is_same_department(user, employee):
"""Пользователь и сотрудник в одном отделе"""
if employee is None or not hasattr(user, 'department') or not hasattr(employee, 'department'):
return False
return user.department == employee.department
# -----------------------------------------------------------------------------
# ПРАВИЛА (rules)
# -----------------------------------------------------------------------------
# Просмотр профиля сотрудника
rules.add_rule(
'employees.view_employee',
is_superuser | is_staff | is_hr_staff | is_own_profile
)
# Изменение профиля сотрудника
rules.add_rule(
'employees.change_employee',
is_superuser | is_hr_staff | (is_own_profile & ~rules.always_deny)
)
# Удаление сотрудника (только HR и superuser)
rules.add_rule(
'employees.delete_employee',
is_superuser | is_hr_staff
)
# Просмотр списка всех сотрудников
rules.add_rule(
'employees.view_all_employees',
is_superuser | is_staff | is_hr_staff
)
# Просмотр отчётов по персоналу (только HR и руководители)
rules.add_rule(
'employees.view_reports',
is_superuser | is_hr_staff | is_department_head
)
# Изменение должности сотрудника (только HR)
rules.add_rule(
'employees.change_position',
is_superuser | is_hr_staff
)
# Изменение отдела сотрудника (только HR)
rules.add_rule(
'employees.change_department',
is_superuser | is_hr_staff
)
# Просмотр контактной информации сотрудника
rules.add_rule(
'employees.view_contact_info',
is_superuser | is_staff | is_own_profile | is_same_department
)
# -----------------------------------------------------------------------------
# PERMISSIONS ДЛЯ МОДЕЛЕЙ
# -----------------------------------------------------------------------------
# Если вы хотите переопределить стандартные Django permissions для модели:
# rules.add_perm('employees.view_employee', is_superuser | is_staff)
# rules.add_perm('employees.change_employee', is_superuser | is_hr_staff)
# rules.add_perm('employees.delete_employee', is_superuser | is_hr_staff)
# rules.add_perm('employees.add_employee', is_superuser | is_hr_staff)
# -----------------------------------------------------------------------------
# ИСПОЛЬЗОВАНИЕ В КОДЕ
# -----------------------------------------------------------------------------
"""
# В views:
from django.core.exceptions import PermissionDenied
def employee_detail(request, pk):
employee = get_object_or_404(Employee, pk=pk)
# Проверка через has_perm (если правило зарегистрировано через add_perm)
if not request.user.has_perm('employees.view_employee', employee):
raise PermissionDenied
# Или напрямую через rules.test_rule
if not rules.test_rule('employees.view_employee', request.user, employee):
raise PermissionDenied
return render(request, 'employees/detail.html', {'employee': employee})
# В templates:
{% load rules %}
{% has_perm 'employees.change_employee' user employee as can_change %}
{% if can_change %}
<a href="{% url 'employees:edit' employee.pk %}">Редактировать</a>
{% endif %}
# В DRF permissions:
from rest_framework import permissions
import rules
class EmployeePermission(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return rules.test_rule('employees.view_employee', request.user, obj)
elif request.method in ['PUT', 'PATCH']:
return rules.test_rule('employees.change_employee', request.user, obj)
elif request.method == 'DELETE':
return rules.test_rule('employees.delete_employee', request.user, obj)
return False
# В admin.py для object-level permissions:
from django.contrib import admin
class EmployeeAdmin(admin.ModelAdmin):
def has_change_permission(self, request, obj=None):
if obj is None:
return super().has_change_permission(request, obj)
return rules.test_rule('employees.change_employee', request.user, obj)
"""