let the subscription card come from the server

کارتِ اشتراک زیرِ هر پلن فهرستِ امکانات و یک برچسب («محبوب‌ترین»، «بهترین
ارزش») نشان می‌دهد، و هر دو در کدِ فرانت هاردکد بودند — یعنی عوض کردنِ یک
بندِ متنی به دیپلویِ تازه‌ی اپ نیاز داشت.

Plan حالا features و badge دارد. `popular` سرِ جایش ماند: بولین است و متنِ
برچسب را نمی‌دهد، پس این جایگزینش نیست، کنارش است. هیچ‌کدام اجباری نیستند و
پلن‌های موجود بدونشان همچنان سرو می‌شوند.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-14 02:03:15 +03:30
parent 99a0cafe24
commit 10ad1e232c
5 changed files with 60 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.2.17 on 2026-09-13 22:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('subscription', '0003_subscription_version'),
]
operations = [
migrations.AddField(
model_name='plan',
name='badge',
field=models.CharField(blank=True, max_length=30),
),
migrations.AddField(
model_name='plan',
name='features',
field=models.JSONField(blank=True, default=list),
),
]

View File

@@ -19,6 +19,14 @@ class Plan(models.Model):
# از رویِ متنِ title (مثلاً کلمه‌ی «سالانه») حدس بزنه.
duration_days = models.PositiveIntegerField(default=30)
# کارتِ اشتراک زیرِ هر پلن فهرستِ امکانات نشان می‌دهد («درس‌های نامحدود»،
# «دستیار هوشمند»…). تا امروز این فهرست در کدِ فرانت هاردکد بود، یعنی
# عوض کردنِ یک بند به دیپلویِ تازه‌ی اپ نیاز داشت.
features = models.JSONField(default=list, blank=True)
# برچسبِ بالای کارت: «محبوب‌ترین»، «بهترین ارزش». `popular` فقط بولین است
# و یک متن نمی‌دهد؛ این جایگزینش نیست، کنارش است.
badge = models.CharField(max_length=30, blank=True)
def __str__(self):
return self.title

View File

@@ -9,6 +9,8 @@ class PlanSerializer(serializers.Serializer):
popular = serializers.BooleanField()
discountPercent = serializers.IntegerField(required=False)
durationDays = serializers.IntegerField(required=False)
features = serializers.ListField(child=serializers.CharField())
badge = serializers.CharField(allow_blank=True)
class PlansListResponseSerializer(serializers.Serializer):

View File

@@ -516,3 +516,28 @@ class PurchaseGuestThrottleBypassTests(TestCase):
self.assertFalse(Subscription.objects.filter(user_id=guest2.id).exists())
self.assertEqual(Order.objects.filter(user_id=guest2.id).count(), 0)
class PlanCardContractTests(TestCase):
"""
کارتِ اشتراک در فرانت فهرستِ امکانات و یک برچسب نشان می‌دهد؛ تا امروز هر
دو در کدِ اپ هاردکد بودند، یعنی عوض کردنِ یک بند به دیپلویِ تازه نیاز داشت.
"""
def setUp(self):
from django.core.cache import cache
cache.clear()
def test_plans_carry_their_feature_list_and_badge(self):
Plan.objects.create(title='طلایی', price=49000, popular=True,
badge='محبوب‌ترین',
features=['درس‌های نامحدود', 'بدون تبلیغ'])
row = self.client.get('/api/subscription/plans').json()['plans'][0]
self.assertEqual(row['badge'], 'محبوب‌ترین')
self.assertEqual(row['features'], ['درس‌های نامحدود', 'بدون تبلیغ'])
def test_a_plan_without_them_still_serializes(self):
Plan.objects.create(title='رایگان', price=0)
row = self.client.get('/api/subscription/plans').json()['plans'][0]
self.assertEqual(row['features'], [])
self.assertEqual(row['badge'], '')

View File

@@ -61,6 +61,8 @@ class PlansListView(APIView):
'popular': p.popular,
'discountPercent': p.discount_percent,
'durationDays': p.duration_days,
'features': p.features or [],
'badge': p.badge,
}
for p in Plan.objects.all()
]