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

271 lines
8.8 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: декларативные правила доступа для requests_app
Правила используются для проверки permissions на уровне объектов.
https://github.com/dfunckt/django-rules
"""
import rules
# -----------------------------------------------------------------------------
# ПРЕДИКАТЫ (predicates)
# -----------------------------------------------------------------------------
@rules.predicate
def is_superuser(user):
"""Суперпользователь имеет все права"""
return user.is_superuser
@rules.predicate
def is_request_author(user, request_obj):
"""Пользователь является автором заявки"""
if request_obj is None:
return False
return request_obj.created_by == user or request_obj.author == user
@rules.predicate
def is_request_assignee(user, request_obj):
"""Пользователь является исполнителем заявки"""
if request_obj is None:
return False
# Проверка через assigned_to (один исполнитель)
if hasattr(request_obj, 'assigned_to'):
return request_obj.assigned_to == user
# Проверка через assignees (несколько исполнителей)
if hasattr(request_obj, 'assignees'):
return user in request_obj.assignees.all()
return False
@rules.predicate
def is_request_approver(user, request_obj):
"""Пользователь назначен согласующим для этой заявки"""
if request_obj is None:
return False
# Проверка через approvers
if hasattr(request_obj, 'approvers'):
return user in request_obj.approvers.all()
# Проверка через approval_chain
if hasattr(request_obj, 'approval_chain'):
return request_obj.approval_chain.filter(approver=user).exists()
return False
@rules.predicate
def can_manage_requests(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_department_request(user, request_obj):
"""Заявка относится к отделу пользователя"""
if request_obj is None or not hasattr(user, 'department'):
return False
# Проверка через department
if hasattr(request_obj, 'department'):
return request_obj.department == user.department
# Проверка через автора заявки
if hasattr(request_obj, 'created_by') and hasattr(request_obj.created_by, 'department'):
return request_obj.created_by.department == user.department
return False
@rules.predicate
def is_request_watcher(user, request_obj):
"""Пользователь наблюдает за заявкой (подписан на уведомления)"""
if request_obj is None:
return False
if hasattr(request_obj, 'watchers'):
return user in request_obj.watchers.all()
return False
@rules.predicate
def can_change_request_status(user, request_obj):
"""Пользователь может менять статус заявки"""
if request_obj is None:
return False
# Автор может закрывать/отменять свою заявку
if is_request_author(user, request_obj):
return True
# Исполнитель может переводить в работу/выполнено
if is_request_assignee(user, request_obj):
return True
# Менеджеры могут менять любые статусы
if can_manage_requests(user):
return True
return False
# -----------------------------------------------------------------------------
# ПРАВИЛА (rules)
# -----------------------------------------------------------------------------
# Просмотр заявки
rules.add_rule(
'requests_app.view_request',
is_superuser | is_request_author | is_request_assignee |
is_request_approver | is_request_watcher | can_manage_requests
)
# Изменение заявки
rules.add_rule(
'requests_app.change_request',
is_superuser | is_request_author | can_manage_requests
)
# Удаление заявки (только автор и менеджеры)
rules.add_rule(
'requests_app.delete_request',
is_superuser | (is_request_author & rules.always_allow) | can_manage_requests
)
# Назначение исполнителя
rules.add_rule(
'requests_app.assign_request',
is_superuser | can_manage_requests | is_request_author
)
# Изменение статуса заявки
rules.add_rule(
'requests_app.change_status',
is_superuser | can_change_request_status
)
# Согласование заявки
rules.add_rule(
'requests_app.approve_request',
is_superuser | is_request_approver | can_manage_requests
)
# Комментирование заявки
rules.add_rule(
'requests_app.comment_request',
is_superuser | is_request_author | is_request_assignee |
is_request_watcher | can_manage_requests
)
# Добавление наблюдателей
rules.add_rule(
'requests_app.add_watchers',
is_superuser | is_request_author | can_manage_requests
)
# Просмотр всех заявок отдела
rules.add_rule(
'requests_app.view_department_requests',
is_superuser | can_manage_requests
)
# Просмотр статистики по заявкам
rules.add_rule(
'requests_app.view_statistics',
is_superuser | can_manage_requests
)
# -----------------------------------------------------------------------------
# ИСПОЛЬЗОВАНИЕ В КОДЕ
# -----------------------------------------------------------------------------
"""
# В views:
from django.core.exceptions import PermissionDenied
import rules
def request_detail(request, pk):
request_obj = get_object_or_404(Request, pk=pk)
if not rules.test_rule('requests_app.view_request', request.user, request_obj):
raise PermissionDenied
return render(request, 'requests_app/detail.html', {'request': request_obj})
def request_assign(request, pk):
request_obj = get_object_or_404(Request, pk=pk)
if not rules.test_rule('requests_app.assign_request', request.user, request_obj):
raise PermissionDenied
# Логика назначения
assignee_id = request.POST.get('assignee_id')
request_obj.assigned_to_id = assignee_id
request_obj.save()
return redirect('requests_app:detail', pk=request_obj.pk)
# В templates:
{% load rules %}
{% has_rule 'requests_app.change_status' user request as can_change_status %}
{% if can_change_status %}
<form method="post" action="{% url 'requests_app:change_status' request.pk %}">
{% csrf_token %}
<select name="status" class="form-select">
<option value="new">Новая</option>
<option value="in_progress">В работе</option>
<option value="completed">Выполнено</option>
</select>
<button type="submit" class="btn btn-primary">Изменить статус</button>
</form>
{% endif %}
# В DRF permissions:
from rest_framework import permissions
import rules
class RequestPermission(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return rules.test_rule('requests_app.view_request', request.user, obj)
elif request.method in ['PUT', 'PATCH']:
return rules.test_rule('requests_app.change_request', request.user, obj)
elif request.method == 'DELETE':
return rules.test_rule('requests_app.delete_request', request.user, obj)
return False
# Фильтрация QuerySet (только доступные заявки):
from django.db.models import Q
def get_accessible_requests(user):
return Request.objects.filter(
Q(created_by=user) | # Созданные пользователем
Q(assigned_to=user) | # Назначенные пользователю
Q(watchers=user) | # Наблюдаемые пользователем
Q(approvers=user) | # Требующие согласования
Q(department=user.department) # Заявки отдела
).distinct()
"""