116 lines
6.0 KiB
Python
116 lines
6.0 KiB
Python
"""
|
||
Permissions для subscriptions модуля.
|
||
"""
|
||
from rest_framework import permissions
|
||
from rest_framework.exceptions import PermissionDenied
|
||
import logging
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class IsSubscriptionOwner(permissions.BasePermission):
|
||
"""
|
||
Проверка что пользователь - владелец подписки.
|
||
"""
|
||
|
||
message = 'Только владелец подписки может выполнить это действие.'
|
||
|
||
def has_object_permission(self, request, view, obj):
|
||
"""Проверка доступа к объекту."""
|
||
return obj.user == request.user
|
||
|
||
|
||
class RequiresActiveSubscription(permissions.BasePermission):
|
||
"""
|
||
Проверка наличия активной подписки.
|
||
Блокирует POST, PUT, PATCH, DELETE запросы, если подписка не активна.
|
||
"""
|
||
|
||
message = 'Требуется активная подписка'
|
||
|
||
def has_permission(self, request, view):
|
||
"""Проверка разрешения на уровне запроса."""
|
||
# Разрешаем GET запросы (чтение)
|
||
if request.method in permissions.SAFE_METHODS:
|
||
return True
|
||
|
||
# Проверяем только для менторов
|
||
if not hasattr(request.user, 'role') or request.user.role != 'mentor':
|
||
return True
|
||
|
||
# Получаем активную подписку
|
||
from .services import SubscriptionService
|
||
from .models import Subscription
|
||
from django.utils import timezone
|
||
|
||
# Получаем все подписки пользователя
|
||
all_subscriptions = Subscription.objects.filter(
|
||
user=request.user,
|
||
status__in=['trial', 'active']
|
||
).order_by('-end_date')
|
||
|
||
# Также проверяем истекшие подписки для диагностики
|
||
expired_subscriptions = Subscription.objects.filter(
|
||
user=request.user,
|
||
status__in=['expired', 'past_due', 'cancelled']
|
||
).order_by('-end_date')
|
||
|
||
# Проверяем, есть ли действительно активная подписка
|
||
active_subscription = None
|
||
for sub in all_subscriptions:
|
||
if sub.is_active():
|
||
active_subscription = sub
|
||
break
|
||
|
||
# Логируем для отладки
|
||
logger.error(
|
||
f"RequiresActiveSubscription: {request.method} {request.path}, "
|
||
f"user={request.user.id}, has_active={active_subscription is not None}"
|
||
)
|
||
|
||
# Если нет активной подписки, блокируем запрос
|
||
if not active_subscription:
|
||
# Получаем информацию о подписке для сообщения об ошибке
|
||
from apps.users.models import Client
|
||
current_clients_count = Client.objects.filter(mentors=request.user).count()
|
||
|
||
# Если подписка есть, но истекла
|
||
expired_subscription = expired_subscriptions.first() if expired_subscriptions.exists() else None
|
||
if expired_subscription:
|
||
logger.error(
|
||
f"❌ BLOCKING: {request.method} {request.path}, "
|
||
f"user={request.user.id} - subscription expired (id={expired_subscription.id})"
|
||
)
|
||
# Используем стандартный формат DRF для PermissionDenied
|
||
# PermissionDenied принимает detail как строку, дополнительные данные добавляем через атрибуты
|
||
error = PermissionDenied(
|
||
detail='Ваша подписка истекла. Для продолжения работы необходимо продлить подписку.'
|
||
)
|
||
# Добавляем дополнительные данные в error для обработки в exception handler
|
||
error.detail_dict = {
|
||
'error': 'Подписка истекла',
|
||
'detail': 'Ваша подписка истекла. Для продолжения работы необходимо продлить подписку.',
|
||
'subscription_id': expired_subscription.id,
|
||
'end_date': expired_subscription.end_date.strftime('%Y-%m-%d') if expired_subscription.end_date else None,
|
||
'current_students': current_clients_count,
|
||
'paid_students': expired_subscription.student_count if expired_subscription.plan.subscription_type == 'per_student' else None,
|
||
'requires_renewal': True
|
||
}
|
||
raise error
|
||
else:
|
||
logger.error(
|
||
f"❌ BLOCKING: {request.method} {request.path}, "
|
||
f"user={request.user.id} - no active subscription"
|
||
)
|
||
error = PermissionDenied(
|
||
detail='Для использования платформы необходимо оформить подписку. Пожалуйста, перейдите на страницу подписок и выберите подходящий тариф.'
|
||
)
|
||
error.detail_dict = {
|
||
'error': 'Требуется активная подписка',
|
||
'detail': 'Для использования платформы необходимо оформить подписку. Пожалуйста, перейдите на страницу подписок и выберите подходящий тариф.'
|
||
}
|
||
raise error
|
||
|
||
return True
|
||
|