igor_nadein 7e4c693211 feat: добавлены правила для 5 приложений (requests, communications, calendar, procurement, feed)
- requests_app/rules.py: 8 предикатов + 10 правил
  * Предикаты: is_request_author, is_request_assignee, can_manage_requests и др.
  * Правила: view/change/delete/assign/approve requests, комментирование, статистика

- communications/rules.py: 9 предикатов + 15 правил
  * Предикаты: is_chat_member, is_chat_owner, is_chat_admin, is_message_author и др.
  * Правила: view/send/change/delete чаты и сообщения, управление участниками, закрепление
  * Включены примеры для WebSocket consumers

- calendar_app/rules.py: 9 предикатов + 12 правил
  * Предикаты: is_calendar_owner, is_personal_calendar, has_calendar_access и др.
  * Правила: view/change/delete календари и события, подписки, экспорт iCal, приглашения

- procurement/rules.py: 8 предикатов + 13 правил
  * Предикаты: is_procurement_manager, is_purchase_request_author, can_approve_purchases и др.
  * Правила: view/change/delete заявки на закупку, управление поставщиками и договорами

- feed/rules.py: 8 предикатов + 11 правил
  * Предикаты: is_post_author, can_publish_posts, can_moderate_posts и др.
  * Правила: view/create/change/delete посты, публикация, закрепление, комментирование, реакции

Во всех apps.py добавлен импорт rules для автозагрузки правил при старте Django
2026-02-28 00:13:46 +03:00

355 lines
12 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: декларативные правила доступа для procurement (закупки)
Правила используются для проверки permissions на уровне объектов.
https://github.com/dfunckt/django-rules
"""
import rules
# -----------------------------------------------------------------------------
# ПРЕДИКАТЫ (predicates)
# -----------------------------------------------------------------------------
@rules.predicate
def is_superuser(user):
"""Суперпользователь имеет все права"""
return user.is_superuser
@rules.predicate
def is_procurement_manager(user):
"""
Пользователь является менеджером по закупкам.
Адаптируйте под вашу логику (по должности, группе и т.д.)
"""
if not hasattr(user, 'position'):
return False
position_name = getattr(user.position, 'name', '').lower()
return any(keyword in position_name for keyword in [
'закупк', 'снабжен', 'procurement', 'supply'
])
@rules.predicate
def is_purchase_request_author(user, purchase_request):
"""Пользователь является автором заявки на закупку"""
if purchase_request is None:
return False
return (
purchase_request.created_by == user or
getattr(purchase_request, 'author', None) == user
)
@rules.predicate
def is_purchase_request_approver(user, purchase_request):
"""Пользователь назначен согласующим для заявки на закупку"""
if purchase_request is None:
return False
# Проверка через approvers
if hasattr(purchase_request, 'approvers'):
return user in purchase_request.approvers.all()
# Проверка через approval_chain
if hasattr(purchase_request, 'approval_chain'):
return purchase_request.approval_chain.filter(approver=user).exists()
return False
@rules.predicate
def is_department_purchase_request(user, purchase_request):
"""Заявка на закупку относится к отделу пользователя"""
if purchase_request is None or not hasattr(user, 'department'):
return False
if hasattr(purchase_request, 'department'):
return purchase_request.department == user.department
# Проверка через автора
if hasattr(purchase_request, 'created_by') and hasattr(purchase_request.created_by, 'department'):
return purchase_request.created_by.department == user.department
return False
@rules.predicate
def can_approve_purchases(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_supplier_contact(user, supplier):
"""Пользователь является контактным лицом поставщика"""
if supplier is None:
return False
if hasattr(supplier, 'contact_person'):
return supplier.contact_person == user
if hasattr(supplier, 'managers'):
return user in supplier.managers.all()
return False
@rules.predicate
def is_contract_manager(user, contract):
"""Пользователь является менеджером договора"""
if contract is None:
return False
if hasattr(contract, 'manager'):
return contract.manager == user
if hasattr(contract, 'responsible_person'):
return contract.responsible_person == user
return False
@rules.predicate
def can_manage_suppliers(user):
"""Пользователь может управлять поставщиками"""
return is_procurement_manager(user) or is_superuser(user)
# -----------------------------------------------------------------------------
# ПРАВИЛА (rules)
# -----------------------------------------------------------------------------
# Просмотр заявки на закупку
rules.add_rule(
'procurement.view_purchase_request',
is_superuser | is_purchase_request_author | is_procurement_manager |
is_purchase_request_approver | can_approve_purchases
)
# Создание заявки на закупку
rules.add_rule(
'procurement.create_purchase_request',
rules.is_authenticated # Любой сотрудник может создать заявку
)
# Изменение заявки на закупку
rules.add_rule(
'procurement.change_purchase_request',
is_superuser | is_purchase_request_author | is_procurement_manager
)
# Удаление заявки на закупку
rules.add_rule(
'procurement.delete_purchase_request',
is_superuser | is_purchase_request_author
)
# Согласование заявки на закупку
rules.add_rule(
'procurement.approve_purchase_request',
is_superuser | is_purchase_request_approver | can_approve_purchases | is_procurement_manager
)
# Изменение статуса заявки
rules.add_rule(
'procurement.change_request_status',
is_superuser | is_procurement_manager | can_approve_purchases
)
# Просмотр поставщика
rules.add_rule(
'procurement.view_supplier',
is_superuser | is_procurement_manager | is_supplier_contact
)
# Создание/изменение поставщика
rules.add_rule(
'procurement.manage_supplier',
is_superuser | can_manage_suppliers
)
# Удаление поставщика
rules.add_rule(
'procurement.delete_supplier',
is_superuser
)
# Просмотр договора
rules.add_rule(
'procurement.view_contract',
is_superuser | is_contract_manager | is_procurement_manager
)
# Создание/изменение договора
rules.add_rule(
'procurement.manage_contract',
is_superuser | is_contract_manager | can_manage_suppliers
)
# Удаление договора
rules.add_rule(
'procurement.delete_contract',
is_superuser
)
# Просмотр всех заявок (для менеджеров)
rules.add_rule(
'procurement.view_all_requests',
is_superuser | is_procurement_manager | can_approve_purchases
)
# Просмотр отчётов по закупкам
rules.add_rule(
'procurement.view_reports',
is_superuser | is_procurement_manager | can_approve_purchases
)
# Экспорт данных по закупкам
rules.add_rule(
'procurement.export_data',
is_superuser | is_procurement_manager
)
# -----------------------------------------------------------------------------
# ИСПОЛЬЗОВАНИЕ В КОДЕ
# -----------------------------------------------------------------------------
"""
# В views:
from django.core.exceptions import PermissionDenied
import rules
def purchase_request_detail(request, pk):
purchase_request = get_object_or_404(PurchaseRequest, pk=pk)
if not rules.test_rule('procurement.view_purchase_request', request.user, purchase_request):
raise PermissionDenied("У вас нет доступа к этой заявке")
return render(request, 'procurement/request_detail.html', {
'request': purchase_request
})
def approve_purchase_request(request, pk):
purchase_request = get_object_or_404(PurchaseRequest, pk=pk)
if not rules.test_rule('procurement.approve_purchase_request', request.user, purchase_request):
return JsonResponse({'error': 'Нет прав на согласование'}, status=403)
purchase_request.status = 'approved'
purchase_request.approved_by = request.user
purchase_request.approved_at = timezone.now()
purchase_request.save()
return JsonResponse({'success': True})
def supplier_list(request):
# Фильтрация: только доступные поставщики
if rules.test_rule('procurement.view_all_requests', request.user, None):
suppliers = Supplier.objects.all()
else:
suppliers = Supplier.objects.filter(
Q(contact_person=request.user) | Q(managers=request.user)
)
return render(request, 'procurement/suppliers.html', {'suppliers': suppliers})
# В templates:
{% load rules %}
{% has_rule 'procurement.approve_purchase_request' user purchase_request as can_approve %}
{% if can_approve and purchase_request.status == 'pending' %}
<form method="post" action="{% url 'procurement:approve' purchase_request.pk %}">
{% csrf_token %}
<button type="submit" class="btn btn-success">Согласовать</button>
</form>
<form method="post" action="{% url 'procurement:reject' purchase_request.pk %}">
{% csrf_token %}
<button type="submit" class="btn btn-danger">Отклонить</button>
</form>
{% endif %}
{% has_rule 'procurement.manage_supplier' user None as can_manage %}
{% if can_manage %}
<a href="{% url 'procurement:supplier_create' %}" class="btn btn-primary">
Добавить поставщика
</a>
{% endif %}
# В DRF permissions:
from rest_framework import permissions
import rules
class PurchaseRequestPermission(permissions.BasePermission):
def has_permission(self, request, view):
if request.method == 'POST':
return rules.test_rule('procurement.create_purchase_request', request.user, None)
return True
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return rules.test_rule('procurement.view_purchase_request', request.user, obj)
elif request.method in ['PUT', 'PATCH']:
return rules.test_rule('procurement.change_purchase_request', request.user, obj)
elif request.method == 'DELETE':
return rules.test_rule('procurement.delete_purchase_request', request.user, obj)
return False
class SupplierPermission(permissions.BasePermission):
def has_permission(self, request, view):
if request.method == 'POST':
return rules.test_rule('procurement.manage_supplier', request.user, None)
return True
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return rules.test_rule('procurement.view_supplier', request.user, obj)
elif request.method in ['PUT', 'PATCH']:
return rules.test_rule('procurement.manage_supplier', request.user, None)
elif request.method == 'DELETE':
return rules.test_rule('procurement.delete_supplier', request.user, None)
return False
# Фильтрация QuerySet:
from django.db.models import Q
def get_accessible_purchase_requests(user):
if rules.test_rule('procurement.view_all_requests', user, None):
return PurchaseRequest.objects.all()
return PurchaseRequest.objects.filter(
Q(created_by=user) | # Созданные пользователем
Q(approvers=user) | # Требующие согласования
Q(department=user.department) # Заявки отдела
).distinct()
def get_accessible_suppliers(user):
if rules.test_rule('procurement.view_all_requests', user, None):
return Supplier.objects.all()
return Supplier.objects.filter(
Q(contact_person=user) | Q(managers=user)
).distinct()
"""