diff --git a/apps/promotions/admin.py b/apps/promotions/admin.py index b645b25..42451b2 100644 --- a/apps/promotions/admin.py +++ b/apps/promotions/admin.py @@ -2,7 +2,7 @@ from functools import update_wrapper from django.contrib import admin -from .models import Promotion, Plan, Event, Recipient, EventSaver +from .models import Promotion, Plan, Event, Recipient, EventSaver, AllowedUser class PromotionAdmin(admin.ModelAdmin): list_display = ['user_uuid', 'plan', 'promotion_amount', 'recipient', 'event', 'created_at'] @@ -10,9 +10,13 @@ class PromotionAdmin(admin.ModelAdmin): class PlanAdmin(admin.ModelAdmin): list_display = ['uuid', 'title', 'balance'] +class AllowedUserAdmin(admin.ModelAdmin): + list_display = ['user', 'recipient', 'created_at'] + admin.site.register(Plan, PlanAdmin) admin.site.register(Promotion, PromotionAdmin) admin.site.register(Event) admin.site.register(EventSaver) admin.site.register(Recipient) +admin.site.register(AllowedUser, AllowedUserAdmin) diff --git a/apps/promotions/migrations/0009_recipient_access_type_alloweduser.py b/apps/promotions/migrations/0009_recipient_access_type_alloweduser.py new file mode 100644 index 0000000..4427a00 --- /dev/null +++ b/apps/promotions/migrations/0009_recipient_access_type_alloweduser.py @@ -0,0 +1,40 @@ +import uuid + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('promotions', '0008_plan_balance_holder_alter_plan_description_details'), + ] + + operations = [ + migrations.AddField( + model_name='recipient', + name='access_type', + field=models.CharField(choices=[('restricted', 'restricted'), ('public', 'public')], + db_index=True, default='public', max_length=64, verbose_name='access type'), + ), + migrations.CreateModel( + name='AllowedUser', + fields=[ + ('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, + serialize=False, unique=True)), + ('created_at', models.DateTimeField(auto_now_add=True, db_index=True)), + ('updated_at', models.DateTimeField(auto_now=True, db_index=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, + related_name='allowed_recipients', to=settings.AUTH_USER_MODEL)), + ('recipient', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, + related_name='allowed_users', to='promotions.recipient')), + ], + options={ + 'constraints': [ + models.UniqueConstraint(fields=('user', 'recipient'), name='unique_allowed_user_recipient'), + ], + }, + ), + ] diff --git a/apps/promotions/models.py b/apps/promotions/models.py index 541ea1f..269eda1 100644 --- a/apps/promotions/models.py +++ b/apps/promotions/models.py @@ -135,9 +135,11 @@ class Plan(BaseModel): def __str__(self): return self.title - def get_configured_promotion_amount(self): + def get_configured_promotion_amount(self, user_uuid=None): amount = 0 for recipient in self.recipients.filter(recipient_uuid_field='->event:user'): #TODO: instead of hard code use better soloution + if not recipient.is_user_allowed(user_uuid): + continue base_amount, promotion_amount = recipient.get_promotion_amount() amount += promotion_amount return amount @@ -180,12 +182,19 @@ class Plan(BaseModel): return updated +class RecipientTypeChoices(models.TextChoices): + RESTRICTED = 'restricted', _('restricted') + PUBLIC = 'public', _('public') + + class Recipient(BaseModel): label = models.CharField(max_length=255, db_index=True) plan = models.ForeignKey(Plan, on_delete=models.PROTECT, related_name='recipients', null=True, blank=True) wallet_uuid = models.UUIDField(null=True, blank=True) promotion_type = models.CharField(max_length=64, verbose_name=_('promotion type'), db_index=True, choices=PromotionTypeChoices.choices, null=True, blank=True) + access_type = models.CharField(max_length=64, verbose_name=_('access type'), db_index=True, + choices=RecipientTypeChoices.choices, default=RecipientTypeChoices.PUBLIC) recipient_uuid_field = models.CharField(max_length=255, db_index=True) base_amount_field = models.CharField(max_length=255, db_index=True) @@ -207,6 +216,13 @@ class Recipient(BaseModel): def get_wallet_category_uuid(self): return self.wallet_uuid or settings.WALLET_PROMOTION_CATEGORY_UUID + def is_user_allowed(self, user_uuid): + if self.access_type != RecipientTypeChoices.RESTRICTED: + return True + if not user_uuid: + return False + return self.allowed_users.filter(user__uuid=user_uuid).exists() + def get_recipient_uuid(self, plan=None, event=None): if ':' in self.recipient_uuid_field and not self.recipient_uuid_field.startswith('QS:'): filters_key_values, recipient_candidate = self.recipient_uuid_field.split('->') @@ -315,6 +331,9 @@ class Recipient(BaseModel): if not recipient: return None + if not self.is_user_allowed(recipient): + return None + base_amount, promotion_amount = self.get_promotion_amount(plan=plan, event=event) promotion, created = Promotion.objects.get_or_create( promotion_amount=promotion_amount, @@ -341,6 +360,19 @@ class Recipient(BaseModel): return None +class AllowedUser(BaseModel): + user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='allowed_recipients') + recipient = models.ForeignKey(Recipient, on_delete=models.CASCADE, related_name='allowed_users') + + class Meta: + constraints = [ + models.UniqueConstraint(fields=['user', 'recipient'], name='unique_allowed_user_recipient') + ] + + def __str__(self): + return f"{self.user} -> {self.recipient}" + + class PaymentStateChoices(models.IntegerChoices): CREATED = 1, _('created') DELAYED = 2, _('delayed') # delayed as user wish @@ -365,7 +397,7 @@ def get_event_status_for_user(user_uuid, event_label): if not processed: plan = Plan.objects.filter(event_list__icontains=event_label).first() if plan: - promotion_amount = plan.get_configured_promotion_amount() + promotion_amount = plan.get_configured_promotion_amount(user_uuid=user_uuid) return { 'event_label': event_label, 'processed': processed,