This commit is contained in:
2026-09-04 13:05:54 +03:30
parent 463b812e3e
commit 17082a5ea9
43 changed files with 5663 additions and 285 deletions
+11 -3
View File
@@ -33,13 +33,21 @@ class FeaturePlanSerializer(serializers.ModelSerializer):
class Meta:
model = FeaturePlan
fields = ["key", "name", "tiers", "is_active", "expires_at"]
fields = ["key", "name", "tiers", "is_active", "is_free", "expires_at"]
def get_is_active(self, obj: FeaturePlan) -> bool:
return FeatureAccessService.is_active(self.context["request"].user, obj.key)
# مهمان (بدون حساب): فقط ابزارهای رایگان باز است — بدونِ این شاخه،
# AnonymousUser به is_active می‌رسید و خطا می‌داد.
user = self.context["request"].user
if not user.is_authenticated:
return obj.is_free
return FeatureAccessService.is_active(user, obj.key)
def get_expires_at(self, obj: FeaturePlan):
return FeatureAccessService.expires_at(self.context["request"].user, obj.key)
user = self.context["request"].user
if not user.is_authenticated:
return None
return FeatureAccessService.expires_at(user, obj.key)
class GemPackSerializer(serializers.ModelSerializer):
+3
View File
@@ -97,6 +97,9 @@ class ReferralService:
class FeatureAccessService:
@staticmethod
def is_active(user: User, feature_key: str) -> bool:
# ابزارهای رایگان همیشه باز — پیش از دوره‌ی آزمایشی و بعد از آن.
if FeaturePlan.objects.filter(key=feature_key, is_free=True).exists():
return True
if user.is_in_trial:
return True
return FeatureActivation.objects.filter(
+13 -3
View File
@@ -1,8 +1,9 @@
from django.db import transaction
from django.db.models import Q
from django.utils import timezone
from rest_framework import generics, status
from rest_framework.exceptions import NotFound
from rest_framework.permissions import IsAuthenticated
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
@@ -38,9 +39,18 @@ class WalletView(APIView):
class FeatureListView(generics.ListAPIView):
"""فهرستِ ابزارها — عمدا AllowAny: کاربرِ مهمان (بدون حساب) هم باید
بتواند بفهمد کدام ابزار رایگان است. این پاسخ هیچ داده‌ی شخصی ندارد؛
فقط کاتالوگِ ابزارها و قیمت‌هاست."""
serializer_class = FeaturePlanSerializer
permission_classes = [IsAuthenticated]
queryset = FeaturePlan.objects.filter(is_purchasable=True).prefetch_related("tiers")
permission_classes = [AllowAny]
# ابزارهای رایگان is_purchasable=False دارند (فروختنی نیستند) ولی باید
# حتما در لیست بیایند — وگرنه اپ آن‌ها را نمی‌شناسد و قفل نشانشان می‌دهد.
queryset = (
FeaturePlan.objects.filter(Q(is_purchasable=True) | Q(is_free=True))
.prefetch_related("tiers")
)
class ActivateFeatureView(APIView):