first test pass
This commit is contained in:
parent
c6ad3cb82d
commit
450aadd6da
6 changed files with 173 additions and 28 deletions
|
|
@ -0,0 +1,29 @@
|
|||
# Generated by Django 5.1.7 on 2025-11-06 08:42
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('promotions', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='promotion',
|
||||
name='event',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='promotions', to='promotions.event'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='plan',
|
||||
name='processor',
|
||||
field=models.CharField(choices=[('percentage', 'percentage'), ('referral', 'referral'), ('others', 'others')], db_index=True, max_length=64, verbose_name='processor'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='recipient',
|
||||
name='promotion_type',
|
||||
field=models.CharField(choices=[], db_index=True, max_length=64, verbose_name='promotion type'),
|
||||
),
|
||||
]
|
||||
|
|
@ -45,6 +45,7 @@ class EventSaver(BaseModel):
|
|||
if created:
|
||||
return event
|
||||
else:
|
||||
# TODO: good exception
|
||||
raise Exception(_('already saved'))
|
||||
|
||||
else:
|
||||
|
|
@ -104,11 +105,11 @@ class Plan(BaseModel):
|
|||
|
||||
def process_event(self, event=None):
|
||||
# TODO: processor
|
||||
self.promote(event=event)
|
||||
return self.promote_all(event=event)
|
||||
|
||||
def promote(self, event=None):
|
||||
def promote_all(self, event=None):
|
||||
for recipient in self.recipients.all():
|
||||
recipient.promote(plan=self, event=event)
|
||||
yield recipient.promote(plan=self, event=event)
|
||||
|
||||
def reserve_promotion_amount(self, amount):
|
||||
updated = False
|
||||
|
|
@ -139,14 +140,13 @@ class Recipient(BaseModel):
|
|||
|
||||
def get_recipient_uuid(self, plan=None, event=None):
|
||||
try:
|
||||
model_name, key = self.recipient_uuid_field.split('::')
|
||||
model_name, key = self.recipient_uuid_field.split(':')
|
||||
if model_name == 'event':
|
||||
obj = event
|
||||
elif model_name == 'plan':
|
||||
obj = plan
|
||||
else:
|
||||
raise NotImplement
|
||||
edError()
|
||||
raise NotImplementedError()
|
||||
|
||||
return obj.data.get(key)
|
||||
except Exception as e:
|
||||
|
|
@ -160,7 +160,7 @@ class Recipient(BaseModel):
|
|||
# TODO: get base amount from policy_choice
|
||||
base_amount = 0
|
||||
try:
|
||||
model_name, key = self.base_amount_field.split('::')
|
||||
model_name, key = self.base_amount_field.split(':')
|
||||
if model_name == 'event':
|
||||
obj = event
|
||||
elif model_name == 'plan':
|
||||
|
|
@ -188,7 +188,8 @@ class Recipient(BaseModel):
|
|||
if reserved:
|
||||
promotion, created = Promotion.objects.get_or_create(
|
||||
promotion_amount=promotion_amount,
|
||||
user_id=self.get_recipient_uuid(plan, event),
|
||||
user_uuid=self.get_recipient_uuid(plan, event),
|
||||
event=event,
|
||||
plan=plan,
|
||||
recipient=self,
|
||||
base_amount=promotion_amount,
|
||||
|
|
@ -196,9 +197,11 @@ class Recipient(BaseModel):
|
|||
if created:
|
||||
promotion.promote()
|
||||
else:
|
||||
raise
|
||||
raise Exception('promotion object not created')
|
||||
else:
|
||||
raise
|
||||
raise Exception('promotion reward not reserved')
|
||||
|
||||
return promotion
|
||||
|
||||
|
||||
class PromotionQuerySet(models.QuerySet):
|
||||
|
|
@ -217,6 +220,7 @@ class Promotion(BaseModel):
|
|||
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT,
|
||||
related_name='+', null=True, blank=True)
|
||||
plan = models.ForeignKey(Plan, on_delete=models.PROTECT, related_name='promotions', null=True, blank=True)
|
||||
event = models.ForeignKey(Event, on_delete=models.PROTECT, related_name='promotions', null=True, blank=True)
|
||||
recipient = models.ForeignKey(Recipient, on_delete=models.PROTECT, related_name='promotions', null=True, blank=True)
|
||||
state = models.IntegerField(choices=StateChoices.choices, default=StateChoices.CREATED)
|
||||
base_amount = models.IntegerField(null=True, blank=True)
|
||||
|
|
@ -250,7 +254,7 @@ class Promotion(BaseModel):
|
|||
self.change_state(from_states=[Promotion.StateChoices.CREATED], to_state=Promotion.StateChoices.ASSIGNED,
|
||||
same_ok=False, raise_exception=True)
|
||||
|
||||
payee_uuid = self.user_id
|
||||
payee_uuid = self.user_uuid
|
||||
|
||||
data = {
|
||||
"uuid": str(self.uuid),
|
||||
|
|
@ -274,7 +278,7 @@ class Promotion(BaseModel):
|
|||
verify_response = deposit_to_user_wallet_verify(str(self.uuid))
|
||||
logger.debug(f'verify response: {verify_response}')
|
||||
try:
|
||||
if verify_response.state != 5:
|
||||
if verify_response.state == 5:
|
||||
self.change_state(from_states=[Promotion.StateChoices.ASSIGNED],
|
||||
to_state=Promotion.StateChoices.PROMOTED)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -4,19 +4,19 @@ from .models import Promotion, Plan, Event
|
|||
|
||||
|
||||
class PromotionSerializer(serializers.ModelSerializer):
|
||||
base_amount = serializers.IntegerField(required=True)
|
||||
promotion_amount = serializers.IntegerField(read_only=True)
|
||||
# base_amount = serializers.IntegerField(required=True)
|
||||
# promotion_amount = serializers.IntegerField(read_only=True)
|
||||
user = serializers.UUIDField(source='user_uuid')
|
||||
|
||||
class Meta:
|
||||
model = Promotion
|
||||
fields = (
|
||||
"uuid",
|
||||
"base_amount",
|
||||
"promotion_amount",
|
||||
"plan",
|
||||
"user_uuid",
|
||||
"application",
|
||||
"state",
|
||||
"user",
|
||||
)
|
||||
read_only_fields = ('user_uuid', 'application', "promotion_amount",)
|
||||
|
||||
|
||||
class EventSerializer(serializers.ModelSerializer):
|
||||
|
|
@ -58,3 +58,19 @@ class PlanPromotSerializer(serializers.ModelSerializer):
|
|||
"application",
|
||||
)
|
||||
read_only_fields = ('user', 'application')
|
||||
|
||||
|
||||
|
||||
class PromoteSerializer(serializers.ModelSerializer):
|
||||
label = serializers.CharField(write_only=True)
|
||||
data = serializers.JSONField(write_only=True)
|
||||
|
||||
promotions = PromotionSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Promotion
|
||||
fields = (
|
||||
"promotions",
|
||||
"label",
|
||||
"data",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import uuid
|
||||
from datetime import timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
|
@ -15,6 +16,49 @@ Application = get_application_model()
|
|||
|
||||
application_uuid_hex = 'b14e8b86-8f4a-44d9-b29d-badceb470053'
|
||||
|
||||
|
||||
def mock_submit_deposit_success(data):
|
||||
import uuid as sys_uuid
|
||||
class Tmp():
|
||||
uuid = sys_uuid.uuid4()
|
||||
|
||||
|
||||
data = Tmp()
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def mock_verify_deposit_success(uuid):
|
||||
import uuid as sys_uuid
|
||||
class Tmp():
|
||||
uuid = sys_uuid.uuid4()
|
||||
state = 5
|
||||
|
||||
data = Tmp()
|
||||
|
||||
return data
|
||||
|
||||
def mock_submit_withdraw_success(data):
|
||||
import uuid as sys_uuid
|
||||
class Tmp():
|
||||
uuid = sys_uuid.uuid4()
|
||||
|
||||
data = Tmp()
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def mock_verify_withdraw_success(uuid):
|
||||
import uuid as sys_uuid
|
||||
class Tmp():
|
||||
uuid = sys_uuid.uuid4()
|
||||
state = 5
|
||||
|
||||
data = Tmp()
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class ApplicationApiFlowsTests(APITestCase):
|
||||
client_id = '4INGOCMoulE0fNY1SQlTbPtsWqqxGj2DdqjADq6u'
|
||||
application_uuid = uuid.UUID(application_uuid_hex)
|
||||
|
|
@ -33,6 +77,21 @@ class ApplicationApiFlowsTests(APITestCase):
|
|||
]
|
||||
|
||||
def setUp(self):
|
||||
self.submit_deposit_success_patcher = patch('apps.promotions.models.deposit_to_user_wallet_submit',
|
||||
mock_submit_deposit_success)
|
||||
self.verify_deposit_success_patcher = patch('apps.promotions.models.deposit_to_user_wallet_verify',
|
||||
mock_verify_deposit_success)
|
||||
# self.submit_withdraw_success_patcher = patch('apps.promotions.models.withdraw_from_user_wallet_submit',
|
||||
# mock_submit_withdraw_success)
|
||||
# self.verify_withdraw_success_patcher = patch('apps.promotions.models.withdraw_from_user_wallet_verify',
|
||||
# mock_verify_withdraw_success)
|
||||
|
||||
|
||||
self.submit_deposit_success_patcher.start()
|
||||
self.verify_deposit_success_patcher.start()
|
||||
# self.submit_withdraw_success_patcher.start()
|
||||
# self.verify_withdraw_success_patcher.start()
|
||||
|
||||
self.user, _ = User.objects.get_or_create(pk=self.user_uuid)
|
||||
|
||||
|
||||
|
|
@ -86,8 +145,8 @@ class ApplicationApiFlowsTests(APITestCase):
|
|||
label = self.first_ad_view_reward_label,
|
||||
plan = self.first_ad_view_reward_plan,
|
||||
wallet_uuid = self.user_uuid,
|
||||
recipient_uuid_field="",
|
||||
base_amount_field="",
|
||||
recipient_uuid_field="event:user",
|
||||
base_amount_field="event:base_amount",
|
||||
|
||||
)
|
||||
|
||||
|
|
@ -118,14 +177,18 @@ class ApplicationApiFlowsTests(APITestCase):
|
|||
'label': self.first_ad_view_reward_label,
|
||||
"data": {
|
||||
'user': self.user_uuid,
|
||||
'ad': self.user_uuid
|
||||
'base_amount':800
|
||||
}
|
||||
}
|
||||
response = self.client.post(reverse('promotions:user-promote', kwargs={"pk":self.first_ad_view_reward_plan.uuid}), event_create_data, HTTP_AUTHORIZATION=auth, format='json')
|
||||
# response = self.client.post(reverse('promotions:user-promote', kwargs={"pk":self.first_ad_view_reward_plan.uuid}), event_create_data, HTTP_AUTHORIZATION=auth, format='json')
|
||||
response = self.client.post(reverse('promotions:promote', kwargs={"plan":self.first_ad_view_reward_plan.uuid}), event_create_data, HTTP_AUTHORIZATION=auth, format='json')
|
||||
print(response.json())
|
||||
# event = Event.objects.get(user=self.user, label=self.first_ad_view_reward_label)
|
||||
# event.analyze()
|
||||
# print(event)
|
||||
self.first_ad_view_reward_plan.refresh_from_db()
|
||||
self.assertEqual(self.first_ad_view_reward_plan.balance, 10000 - 800)
|
||||
|
||||
response = self.client.post(reverse('promotions:promote', kwargs={"plan": self.first_ad_view_reward_plan.uuid}), event_create_data, HTTP_AUTHORIZATION=auth, format='json')
|
||||
print(response.json())
|
||||
|
||||
return
|
||||
|
||||
def test_referral_reward_success(self):
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ from utils.router import ProfileRouter
|
|||
from . import views
|
||||
from django.urls import NoReverseMatch, path, re_path, include
|
||||
|
||||
from .views import ApplicationPromoteUserApiView
|
||||
|
||||
app_name = 'promotions'
|
||||
|
||||
router = DefaultRouter()
|
||||
|
|
@ -16,4 +18,5 @@ router.register(r'plans', views.UserViewSet, basename='user')
|
|||
|
||||
urlpatterns = [
|
||||
path('api/v1/', include(router.urls), name='events'),
|
||||
path('api/v1/promote/<plan>/', ApplicationPromoteUserApiView.as_view(), name='promote'),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
from drf_spectacular.utils import extend_schema
|
||||
from rest_framework import mixins
|
||||
from rest_framework import mixins, status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework import exceptions
|
||||
from rest_framework.generics import CreateAPIView, get_object_or_404
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
|
||||
from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements
|
||||
from apps.gooyal_oauth2.utils import get_application
|
||||
from .models import Plan, Promotion, EventSaver
|
||||
from .serializers import PlanSerializer, PromotionSerializer, EventSerializer
|
||||
from .serializers import PlanSerializer, PromotionSerializer, EventSerializer, PromoteSerializer
|
||||
from .tasks import analyze_event_task
|
||||
from ..users.models import User
|
||||
|
||||
|
|
@ -145,8 +147,36 @@ class UserViewSet(mixins.RetrieveModelMixin,
|
|||
raise exceptions.ValidationError(str(e))
|
||||
|
||||
plan: Plan = self.get_object()
|
||||
plan.process_event(event)
|
||||
|
||||
return
|
||||
|
||||
|
||||
class ApplicationPromoteUserApiView(CreateAPIView):
|
||||
model = Promotion
|
||||
serializer_class = PromoteSerializer
|
||||
|
||||
def get_plan(self):
|
||||
plan_uuid = self.kwargs.get('plan')
|
||||
plan = get_object_or_404(Plan, uuid=plan_uuid)
|
||||
return plan
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
plan = self.get_plan()
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
data = serializer.validated_data
|
||||
application = get_application(self.request)
|
||||
event_saver: EventSaver = EventSaver.objects.filter(event_label=serializer.validated_data['label']).first()
|
||||
# try:
|
||||
event = event_saver.save_event(user=None, application=application, **serializer.validated_data)
|
||||
# except Exception as e:
|
||||
# raise exceptions.ValidationError(str(e))
|
||||
|
||||
result_list = plan.process_event(event)
|
||||
data["promotions"] = result_list
|
||||
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue