- Установлен пакет 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
18 KiB
Интеграция django-rules: Object-Level Permissions
Дата: 28 февраля 2026
Статус: ✅ Базовая интеграция завершена
Ветка: feature/django-rules
Обзор
django-rules — это библиотека для декларативного управления правами доступа на уровне объектов в Django. Она позволяет определять сложную логику permissions через композицию простых предикатов.
Преимущества
- ✅ Object-level permissions: проверка прав на конкретные объекты
- ✅ Декларативный синтаксис: правила определяются явно и читаемо
- ✅ Композиция: комбинирование предикатов через
&,|,~ - ✅ Интеграция: работает с Django, DRF, Admin, Templates
- ✅ Производительность: легковесная библиотека без overhead
- ✅ Тестируемость: правила легко покрываются unit-тестами
Ссылки
- GitHub: https://github.com/dfunckt/django-rules
- Документация: https://github.com/dfunckt/django-rules#rules
- PyPI: https://pypi.org/project/rules/
Установка и настройка
1. Установка пакета
../.venv/Scripts/pip install rules==3.5
Добавлено в requirements.txt:
rules==3.5
2. Настройка Django
settings.py:
INSTALLED_APPS = [
# ...
"rules", # django-rules для декларативных permissions
# ...
]
AUTHENTICATION_BACKENDS = [
"eusrr_backend.auth_backends.LDAP3Backend",
"eusrr_backend.auth_backends.EmailOrPhoneBackend",
"eusrr_backend.auth_backends.SuperuserOnlyBackend",
"eusrr_backend.auth_backends.PositionRoleBackend",
"rules.permissions.ObjectPermissionBackend", # ← django-rules backend
"django.contrib.auth.backends.ModelBackend",
]
3. Создание файлов правил
Созданы базовые правила для двух приложений:
- employees/rules.py: правила доступа к профилям сотрудников
- documents/rules.py: правила доступа к документам
Архитектура правил
Базовая структура
import rules
# 1. ПРЕДИКАТЫ - базовые проверки (возвращают True/False)
@rules.predicate
def is_document_owner(user, document):
return document.owner == user
# 2. ПРАВИЛА - композиция предикатов
rules.add_rule(
'documents.view_document',
is_superuser | is_document_owner | has_document_access
)
# 3. PERMISSIONS (опционально) - регистрация в Django permissions
rules.add_perm('documents.view_document', is_superuser | is_document_owner)
Композиция предикатов
# Логическое И (AND)
rule = predicate1 & predicate2
# Логическое ИЛИ (OR)
rule = predicate1 | predicate2
# Логическое НЕ (NOT)
rule = ~predicate1
# Сложные комбинации
rule = (is_owner | is_admin) & ~is_banned & has_subscription
Реализованные правила
Employees (employees/rules.py)
Предикаты:
is_superuser— пользователь является суперюзеромis_staff— пользователь имеет статус персоналаis_hr_staff— HR сотрудник (по должности)is_department_head— руководитель отделаis_own_profile— собственный профильis_same_department— сотрудники в одном отделе
Правила:
employees.view_employee— просмотр профиляemployees.change_employee— изменение профиляemployees.delete_employee— удаление сотрудникаemployees.view_all_employees— просмотр спискаemployees.view_reports— просмотр отчётовemployees.change_position— изменение должностиemployees.change_department— изменение отделаemployees.view_contact_info— просмотр контактов
Documents (documents/rules.py)
Предикаты:
is_document_owner— владелец документаis_document_author— автор документаhas_document_access— доступ через is_public/department/sharedcan_approve_documents— может согласовывать (по должности)is_document_approver— назначен согласующимis_same_department— документ отдела пользователяis_document_category_manager— менеджер категории
Правила:
documents.view_document— просмотр документаdocuments.change_document— изменение документаdocuments.delete_document— удаление документаdocuments.approve_document— согласование документаdocuments.publish_document— публикация документаdocuments.download_document— скачивание документаdocuments.share_document— выдача доступаdocuments.view_document_history— просмотр историиdocuments.view_department_documents— документы отдела
Использование
1. В Views
from django.core.exceptions import PermissionDenied
import rules
def document_detail(request, pk):
document = get_object_or_404(Document, pk=pk)
# Проверка через rules.test_rule
if not rules.test_rule('documents.view_document', request.user, document):
raise PermissionDenied
return render(request, 'documents/detail.html', {'document': document})
def document_approve(request, pk):
document = get_object_or_404(Document, pk=pk)
if not rules.test_rule('documents.approve_document', request.user, document):
raise PermissionDenied
document.status = 'approved'
document.approved_by = request.user
document.save()
return redirect('documents:detail', pk=document.pk)
2. В Templates
{% load rules %}
{% has_rule 'documents.change_document' user document as can_edit %}
{% if can_edit %}
<a href="{% url 'documents:edit' document.pk %}" class="btn btn-primary">
Редактировать
</a>
{% endif %}
{% has_rule 'documents.approve_document' user document as can_approve %}
{% if can_approve and document.status == 'pending' %}
<form method="post" action="{% url 'documents:approve' document.pk %}">
{% csrf_token %}
<button type="submit" class="btn btn-success">Согласовать</button>
</form>
{% endif %}
3. В DRF Permissions
from rest_framework import permissions
import rules
class DocumentPermission(permissions.BasePermission):
"""Object-level permission для Document через django-rules"""
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return rules.test_rule('documents.view_document', request.user, obj)
elif request.method in ['PUT', 'PATCH']:
return rules.test_rule('documents.change_document', request.user, obj)
elif request.method == 'DELETE':
return rules.test_rule('documents.delete_document', request.user, obj)
return False
# В ViewSet:
class DocumentViewSet(viewsets.ModelViewSet):
queryset = Document.objects.all()
serializer_class = DocumentSerializer
permission_classes = [permissions.IsAuthenticated, DocumentPermission]
4. В Django Admin
from django.contrib import admin
import rules
class DocumentAdmin(admin.ModelAdmin):
def has_change_permission(self, request, obj=None):
if obj is None:
# Проверка на уровне модели (list view)
return super().has_change_permission(request, obj)
# Проверка на уровне объекта (change view)
return rules.test_rule('documents.change_document', request.user, obj)
def has_delete_permission(self, request, obj=None):
if obj is None:
return super().has_delete_permission(request, obj)
return rules.test_rule('documents.delete_document', request.user, obj)
5. Фильтрация QuerySet
from django.db.models import Q
def get_accessible_documents(user):
"""Возвращает документы, к которым пользователь имеет доступ"""
return Document.objects.filter(
Q(created_by=user) | # Созданные пользователем
Q(owner=user) | # Принадлежащие пользователю
Q(is_public=True) | # Публичные
Q(department_access=user.department) | # Для отдела
Q(shared_with=user) # Расшаренные
).distinct()
6. Model-level Permissions (опционально)
Если нужно интегрировать с Django permissions (user.has_perm()):
# В rules.py:
rules.add_perm('documents.view_document', is_superuser | has_document_access)
rules.add_perm('documents.change_document', is_superuser | is_document_owner)
# Теперь можно использовать стандартный API:
if request.user.has_perm('documents.view_document', document):
# ...
Тестирование
Unit-тесты для предикатов
import pytest
import rules
from employees.rules import is_own_profile, is_hr_staff
@pytest.mark.django_db
def test_is_own_profile():
user = Employee.objects.create(username='test')
other = Employee.objects.create(username='other')
assert is_own_profile(user, user) is True
assert is_own_profile(user, other) is False
@pytest.mark.django_db
def test_is_hr_staff():
hr_position = Position.objects.create(name='HR Manager')
dev_position = Position.objects.create(name='Developer')
hr_user = Employee.objects.create(username='hr', position=hr_position)
dev_user = Employee.objects.create(username='dev', position=dev_position)
assert is_hr_staff(hr_user) is True
assert is_hr_staff(dev_user) is False
Интеграционные тесты для правил
import pytest
import rules
@pytest.mark.django_db
def test_view_employee_permission():
hr = Employee.objects.create(username='hr', position=hr_position)
employee = Employee.objects.create(username='employee')
# HR может просматривать любого сотрудника
assert rules.test_rule('employees.view_employee', hr, employee)
# Сотрудник может просматривать свой профиль
assert rules.test_rule('employees.view_employee', employee, employee)
Следующие шаги
1. Адаптация под реальные модели
Текущие правила содержат примеры и комментарии. Необходимо:
- ✅ Проверить наличие полей в моделях (
owner,is_public,shared_withи т.д.) - ✅ Адаптировать предикаты под реальную структуру
- ✅ Убрать неиспользуемые предикаты
- ✅ Добавить недостающие правила
2. Миграция существующих permissions
Заменить ручные проверки на правила:
# Старый код:
if not (request.user.is_superuser or document.owner == request.user):
raise PermissionDenied
# Новый код:
if not rules.test_rule('documents.change_document', request.user, document):
raise PermissionDenied
3. Покрытие тестами
Создать тесты для всех критичных правил:
employees/tests/test_rules.pydocuments/tests/test_rules.py
4. Расширение на другие приложения
Создать rules.py для:
calendar_app— доступ к календарям и событиямrequests_app— создание и обработка заявокcommunications— доступ к чатам и сообщениямfinance— финансовые документы и отчёты
5. Performance optimization
Для больших выборок использовать фильтрацию на уровне БД:
# Вместо:
documents = [d for d in Document.objects.all() if rules.test_rule('view', user, d)]
# Использовать:
documents = get_accessible_documents(user)
6. Документация для команды
- Создать примеры использования для каждого приложения
- Обновить CODE_STYLE.md с правилами написания predicates
- Провести код-ревью существующих permissions
Сравнение с django-guardian
| Аспект | django-rules | django-guardian |
|---|---|---|
| Хранение | В коде (декларативно) | В БД (таблицы permissions) |
| Производительность | Быстрее (нет запросов в БД) | Медленнее (JOIN с таблицами) |
| Гибкость | Высокая (любая логика) | Ограничена (user-object pairs) |
| Миграции | Не требуются | Нужна БД миграция |
| Сложность | Проще | Сложнее |
| Масштабирование | Отлично для ~1K объектов | Лучше для миллионов объектов |
Вывод: для EUSRR с умеренным количеством объектов и сложной логикой прав django-rules — оптимальный выбор.
Примеры реальных сценариев
Сценарий 1: Многоуровневое согласование
@rules.predicate
def can_approve_at_current_stage(user, document):
"""Проверка, что пользователь — текущий согласующий"""
current_stage = document.approval_chain.filter(status='pending').first()
if not current_stage:
return False
return current_stage.approver == user
rules.add_rule(
'documents.approve_at_stage',
is_superuser | can_approve_at_current_stage
)
Сценарий 2: Временные ограничения
from django.utils import timezone
from datetime import timedelta
@rules.predicate
def can_edit_recent_document(user, document):
"""Редактирование только в течение 24 часов после создания"""
if not is_document_owner(user, document):
return False
return timezone.now() - document.created_at < timedelta(hours=24)
rules.add_rule(
'documents.quick_edit',
can_edit_recent_document
)
Сценарий 3: Иерархия отделов
@rules.predicate
def is_parent_department_head(user, employee):
"""Руководитель вышестоящего отдела"""
if not hasattr(employee, 'department') or not hasattr(employee.department, 'parent'):
return False
return (
hasattr(user, 'department') and
user.department == employee.department.parent and
is_department_head(user)
)
rules.add_rule(
'employees.view_subordinate',
is_superuser | is_department_head | is_parent_department_head
)
Отладка и мониторинг
Логирование проверок прав
import logging
logger = logging.getLogger(__name__)
@rules.predicate
def is_document_owner(user, document):
result = document.owner == user
logger.debug(
f"Permission check: is_document_owner({user.username}, doc_{document.pk}) = {result}"
)
return result
Отладочная view для проверки прав
def debug_permissions(request, document_id):
"""DEBUG view для проверки прав доступа"""
if not settings.DEBUG:
raise Http404
document = get_object_or_404(Document, pk=document_id)
checks = {
'view': rules.test_rule('documents.view_document', request.user, document),
'change': rules.test_rule('documents.change_document', request.user, document),
'delete': rules.test_rule('documents.delete_document', request.user, document),
'approve': rules.test_rule('documents.approve_document', request.user, document),
}
return JsonResponse(checks)
Заключение
Базовая интеграция django-rules завершена:
- ✅ Пакет установлен и настроен
- ✅ Созданы примеры правил для employees и documents
- ✅ Документация и примеры использования готовы
Следующий шаг — адаптация правил под реальную структуру моделей и миграция существующих проверок прав на использование django-rules.
Автор: GitHub Copilot
Дата: 28 февраля 2026