Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

promo_check #254

Merged
merged 15 commits into from
Nov 1, 2023
2 changes: 2 additions & 0 deletions api_spot/api/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
LocationGetSerializer, LocationGetShortSerializer, LocationMapSerializer,
)
from .plan_photo import PlanPhotoGetSerializer
from .promocode_check import PromocodeCheckSerializer
from .question import QuestionSerializer
from .rule import RuleSerializer
from .spot import SpotDetailSerializer, SpotQuerySerializer, SpotSerializer
Expand All @@ -19,6 +20,7 @@
LocationGetShortSerializer
LocationMapSerializer
PlanPhotoGetSerializer
PromocodeCheckSerializer
QuestionSerializer
RuleSerializer
SpotDetailSerializer
Expand Down
22 changes: 22 additions & 0 deletions api_spot/api/serializers/promocode_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from rest_framework import serializers

from api.services.promocode_check import promocode_available_check
from api.fields import GetSpot


class PromocodeCheckSerializer(serializers.Serializer):
promocode = serializers.CharField(max_length=64)
order = serializers.HiddenField(
default=GetSpot()
)
user = serializers.HiddenField(
default=serializers.CurrentUserDefault()
)

def validate(self, data, *args, **kwargs):
promocode_name = data.get('promocode')
spot = data.get('order')
user = data.get('user')
promocode = promocode_available_check(promocode_name, spot, user)
data['promocode'] = promocode
return data
25 changes: 25 additions & 0 deletions api_spot/api/services/promocode_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from django.utils import timezone
from rest_framework.exceptions import ValidationError

from promo.models import Promocode


def promocode_available_check(promocode_name, spot, user):
"""
Проверка доступности использования промокода.
"""
promocode = Promocode.objects.filter(name=promocode_name).first()
if promocode:
if promocode.balance < 1:
ZUS666 marked this conversation as resolved.
Show resolved Hide resolved
raise ValidationError('Количество использований исчерпано.')
if promocode.expiry_date < timezone.now().date():
raise ValidationError('Срок годности промокода истек.')
if (promocode.only_category
and promocode.only_category != spot.category):
raise ValidationError('Промокод не применяется для этой категории')
used_by_user = promocode.promocode_user.filter(user=user).exists()
if promocode.one_off and used_by_user:
raise ValidationError('Вы уже использовали данный промокод')
return promocode
else:
raise ValidationError('Промокода не существует')
11 changes: 8 additions & 3 deletions api_spot/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
AddSpotsAPIView, AvatarViewSet, EquipmentViewSet, EventViewSet,
FavoriteViewSet, LocationMapListAPIView, LocationShortListAPIView,
LocationViewSet, OrderGetViewSet, OrderViewSet, PayView, PlanPhotoAPIView,
QuestionViewSet, ReviewCreateViewSet, ReviewGetViewSet, RuleViewSet,
SpotViewSet, SubscireAPIView, UserViewSet,
PromocodeCheckAPIView, QuestionViewSet, ReviewCreateViewSet,
ReviewGetViewSet, RuleViewSet, SpotViewSet, SubscireAPIView, UserViewSet,
)


Expand Down Expand Up @@ -75,9 +75,14 @@
r'/order/(?P<order_id>\d+)/pay/',
PayView.as_view(), name='pay'
),
re_path(
r'locations/(?P<location_id>\d+)/spots/(?P<spot_id>\d+)'
r'/order/(?P<order_id>\d+)/check_promocode/',
PromocodeCheckAPIView.as_view(), name='check_promocode'
),
path('short_locations/', LocationShortListAPIView.as_view()),
path('map_locations/', LocationMapListAPIView.as_view()),
path('subscribe/', SubscireAPIView.as_view(),),
path('subscribe/', SubscireAPIView.as_view()),
re_path(
r'locations/(?P<location_id>\d+)/plan_photo/',
PlanPhotoAPIView.as_view()
Expand Down
2 changes: 2 additions & 0 deletions api_spot/api/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from .order import OrderGetViewSet, OrderViewSet
from .pay import PayView
from .plan_photo import PlanPhotoAPIView
from .promocode_check import PromocodeCheckAPIView
from .question import QuestionViewSet
from .review import ReviewCreateViewSet, ReviewGetViewSet
from .rule import RuleViewSet
Expand All @@ -29,6 +30,7 @@
OrderViewSet
PayView
PlanPhotoAPIView
PromocodeCheckAPIView
QuestionViewSet
ReviewCreateViewSet
ReviewGetViewSet
Expand Down
28 changes: 28 additions & 0 deletions api_spot/api/views/promocode_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from drf_spectacular.utils import extend_schema
from rest_framework import status
from rest_framework.generics import CreateAPIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

from api.serializers.promocode_check import PromocodeCheckSerializer


@extend_schema(
tags=('pay',),
)
class PromocodeCheckAPIView(CreateAPIView):
"""
Представление проверки доступности промокода.
"""
serializer_class = PromocodeCheckSerializer
permission_classes = (IsAuthenticated,)

def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
promocode = serializer.validated_data.get('promocode')
discount = promocode.percent_discount
return Response(
{'message': f'Промокод доступен на скидку {discount} %'},
status=status.HTTP_200_OK,
)
14 changes: 13 additions & 1 deletion api_spot/promo/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.contrib import admin

from promo.models import EmailNews, Promocode
from promo.models import EmailNews, Promocode, PromocodeUser


@admin.register(Promocode)
Expand All @@ -11,9 +11,21 @@ class PromcodeAdmin(admin.ModelAdmin):
'percent_discount',
'expiry_date',
'balance',
'only_category',
'one_off',
)


@admin.register(PromocodeUser)
class PromocodeUserAdmin(admin.ModelAdmin):
list_display = (
'id',
'user',
'promocode',
)
list_filter = ('promocode',)


@admin.register(EmailNews)
class EmailNewsAdmin(admin.ModelAdmin):
list_display = (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Generated by Django 4.2.5 on 2023-10-31 22:13

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),
('promo', '0004_alter_promocode_expiry_date'),
]

operations = [
migrations.AddField(
model_name='promocode',
name='one_off',
field=models.BooleanField(default=True, verbose_name='Однократное использование пользователем'),
),
migrations.AddField(
model_name='promocode',
name='only_category',
field=models.CharField(blank=True, choices=[('Рабочее место', 'Рабочее место'), ('Переговорная', 'Переговорная')], null=True, verbose_name='Только для категории'),
),
migrations.CreateModel(
name='PromocodeUser',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('promocode', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='promocode_user', to='promo.promocode')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='promocode_user', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Пользователь-промокод',
'verbose_name_plural': 'Пользователи-промокоды',
},
),
migrations.AddField(
model_name='promocode',
name='used_user',
field=models.ManyToManyField(related_name='promocode', through='promo.PromocodeUser', to=settings.AUTH_USER_MODEL, verbose_name='Оборудование'),
),
migrations.AddConstraint(
model_name='promocodeuser',
constraint=models.UniqueConstraint(fields=('user', 'promocode'), name='unique_user_promocode'),
),
]
44 changes: 44 additions & 0 deletions api_spot/promo/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.contrib.auth import get_user_model
from django.db import models, transaction
from django_celery_beat.models import PeriodicTask

Expand All @@ -6,6 +7,10 @@
MaxDiscountValidator, validate_date_less_present,
validate_datetime_less_present,
)
from spots.constants import CATEGORY_CHOICES


User = get_user_model()


class Promocode(models.Model):
Expand All @@ -25,6 +30,22 @@ class Promocode(models.Model):
balance = models.PositiveIntegerField(
'Количество использований',
)
only_category = models.CharField(
'Только для категории',
choices=CATEGORY_CHOICES,
blank=True,
null=True,
)
one_off = models.BooleanField(
'Однократное использование пользователем',
default=True,
)
used_user = models.ManyToManyField(
User,
related_name='promocode',
verbose_name='Оборудование',
through='PromocodeUser',
)

def __str__(self):
return self.name
Expand All @@ -35,6 +56,29 @@ class Meta:
ordering = ('expiry_date',)


class PromocodeUser(models.Model):
user = models.ForeignKey(
User,
related_name='promocode_user',
on_delete=models.CASCADE,
)
promocode = models.ForeignKey(
Promocode,
related_name='promocode_user',
on_delete=models.CASCADE,
)

class Meta:
verbose_name = 'Пользователь-промокод'
verbose_name_plural = 'Пользователи-промокоды'
constraints = (
models.UniqueConstraint(
fields=('user', 'promocode'),
name='unique_user_promocode',
),
)


class EmailNews(models.Model):
subject_message = models.CharField(
'Тема письма',
Expand Down
19 changes: 19 additions & 0 deletions api_spot/spots/migrations/0015_alter_price_discount.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 4.2.5 on 2023-10-31 22:13

import django.core.validators
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('spots', '0014_alter_order_options'),
]

operations = [
migrations.AlterField(
model_name='price',
name='discount',
field=models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(limit_value=70, message='Скидка не может превышать 70%%')], verbose_name='Скидка'),
),
]
17 changes: 17 additions & 0 deletions api_spot/users/migrations/0008_remove_user_have_orders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 4.2.5 on 2023-10-31 23:25

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('users', '0007_user_have_orders_user_is_subscribed'),
]

operations = [
migrations.RemoveField(
model_name='user',
name='have_orders',
),
]
4 changes: 0 additions & 4 deletions api_spot/users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,6 @@ class User(AbstractBaseUser, PermissionsMixin):
'Подписан на рассылку',
default=False,
)
have_orders = models.BooleanField(
'Имеет заказы',
default=False,
)

EMAIL_FIELD = 'email'
USERNAME_FIELD = 'email'
Expand Down
Loading