diff --git a/apps/payment/admin.py b/apps/payment/admin.py index 0b5a31cb..b26caa41 100644 --- a/apps/payment/admin.py +++ b/apps/payment/admin.py @@ -5,7 +5,8 @@ from polymorphic.admin import ( PolymorphicChildModelFilter, ) -from .models import AuthorBalance, CoursePayment, SchoolPayment, Payment, GiftCertificate, UserBonus +from apps.payment.forms import DiscountAdminForm +from .models import AuthorBalance, CoursePayment, SchoolPayment, Payment, GiftCertificate, UserBonus, Discount @admin.register(AuthorBalance) @@ -70,3 +71,13 @@ class GiftCertificateAdmin(admin.ModelAdmin): @admin.register(UserBonus) class UserBonusAdmin(admin.ModelAdmin): pass + + +@admin.register(Discount) +class DiscountAdmin(admin.ModelAdmin): + form = DiscountAdminForm + + def save_model(self, request, obj, form, change): + if not obj.pk: + obj.author = request.user + obj.save() diff --git a/apps/payment/forms.py b/apps/payment/forms.py new file mode 100644 index 00000000..df5c7e8a --- /dev/null +++ b/apps/payment/forms.py @@ -0,0 +1,26 @@ +from django import forms + +from apps.payment.models import Discount + + +class DiscountAdminForm(forms.ModelForm): + + def clean(self): + clean_data = super().clean() + + if clean_data['product'] == Discount.PRODUCT_ONE_COURSE and clean_data.get('course') is None: + self.add_error('course', 'Не выбран курс') + if clean_data['product'] == Discount.PRODUCT_ONE_PACKAGE and clean_data.get('package') is None: + self.add_error('package', 'Не выбрана подписка') + + if clean_data['usage_type'] == Discount.USAGE_TYPE_LIMIT: + if clean_data.get('usage_count_limit') is None: + self.add_error('usage_count_limit', 'Не указан лимит использования') + elif clean_data['usage_count_limit'] == 0: + self.add_error('usage_count_limit', 'Лимит использования равен 0') + return clean_data + + class Meta: + model = Discount + fields = '__all__' + exclude = ['author'] diff --git a/apps/payment/migrations/0039_auto_20190913_0755.py b/apps/payment/migrations/0039_auto_20190913_0755.py new file mode 100644 index 00000000..5b8147e3 --- /dev/null +++ b/apps/payment/migrations/0039_auto_20190913_0755.py @@ -0,0 +1,53 @@ +# Generated by Django 2.0.7 on 2019-09-13 07:55 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('course', '0050_auto_20190818_1043'), + ('content', '0030_auto_20190809_0133'), + ('payment', '0038_auto_20190814_1506'), + ] + + operations = [ + migrations.CreateModel( + name='Discount', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200, verbose_name='Название')), + ('product', models.PositiveSmallIntegerField(choices=[(0, 'Все курсы'), (1, 'Все подписки'), (2, 'Один курс'), (3, 'Одна подписка')], default=0, verbose_name='На какие продукты распространяется скидка')), + ('promo_id', models.CharField(blank=True, max_length=20, null=True, verbose_name='Промокод')), + ('usage_type', models.PositiveSmallIntegerField(choices=[(0, 'Ограничен по количеству использований'), (1, 'Не ограничен по количеству использований')], default=0, verbose_name='Тип использования')), + ('usage_count_limit', models.PositiveIntegerField(blank=True, null=True, verbose_name='Сколько раз можно использовать')), + ('activate_count', models.PositiveIntegerField(default=0, verbose_name='Сколько раз использован')), + ('value', models.PositiveIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(100, 'Размер скидки не может быть больше 100')], verbose_name='Размер скидки в %')), + ('start', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Дата начала действия скидки')), + ('end', models.DateTimeField(verbose_name='Дата окончания скидки')), + ('is_active', models.BooleanField(default=True, verbose_name='Активность')), + ('author', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='author_discounts', to=settings.AUTH_USER_MODEL, verbose_name='Автор')), + ('client', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='discounts', to=settings.AUTH_USER_MODEL, verbose_name='Пользователь скидки')), + ('course', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='course_discounts', to='course.Course', verbose_name='Курс на который распространяется скидка')), + ('package', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='package_discounts', to='content.Package', verbose_name='Подписка на которую распространяется скидка')), + ], + options={ + 'verbose_name': 'Скидка', + 'verbose_name_plural': 'Скидки', + }, + ), + migrations.AlterField( + model_name='userbonus', + name='payment', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='payment.Payment'), + ), + migrations.AlterField( + model_name='userbonus', + name='referral', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='user.Referral'), + ), + ] diff --git a/apps/payment/models.py b/apps/payment/models.py index 47af6711..7070b5eb 100644 --- a/apps/payment/models.py +++ b/apps/payment/models.py @@ -12,7 +12,7 @@ from django.db.models import Func, F from django.db import models from django.contrib.auth import get_user_model from django.contrib.postgres.fields import ArrayField, JSONField -from django.core.validators import RegexValidator +from django.core.validators import RegexValidator, MaxValueValidator from django.utils.timezone import now from django.conf import settings @@ -29,7 +29,8 @@ from project.utils import dates_overlap config = Config.load() User = get_user_model() -CREDIT_CARD_RE = r'^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\d{11})$' +CREDIT_CARD_RE = r'^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}' \ + r'|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\d{11})$' class AuthorBalance(models.Model): @@ -420,3 +421,55 @@ class UserGiftCertificate(models.Model): @property def code(self): return short_url.encode_url(self.id) if self.id else None + + +class Discount(models.Model): + PRODUCT_ALL_COURSES = 0 + PRODUCT_ALL_PACKAGES = 1 + PRODUCT_ONE_COURSE = 2 + PRODUCT_ONE_PACKAGE = 3 + + USAGE_TYPE_LIMIT = 0 + USAGE_TYPE_UNLIMITED = 1 + + PRODUCT_CHOICES = ( + (PRODUCT_ALL_COURSES, 'Все курсы'), + (PRODUCT_ALL_PACKAGES, 'Все подписки'), + (PRODUCT_ONE_COURSE, 'Один курс'), + (PRODUCT_ONE_PACKAGE, 'Одна подписка') + ) + + USAGE_TYPE_CHOICES = ( + (USAGE_TYPE_LIMIT, 'Ограничен по количеству использований'), + (USAGE_TYPE_UNLIMITED, 'Не ограничен по количеству использований') + ) + + name = models.CharField(verbose_name='Название', max_length=200) + client = models.ForeignKey(User, on_delete=models.CASCADE, related_name='discounts', null=True, blank=True, + verbose_name='Пользователь скидки') + product = models.PositiveSmallIntegerField('На какие продукты распространяется скидка', choices=PRODUCT_CHOICES, + default=PRODUCT_ALL_COURSES) + course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='course_discounts', null=True, blank=True, + verbose_name='Курс на который распространяется скидка') + package = models.ForeignKey(Package, verbose_name='Подписка на которую распространяется скидка', + on_delete=models.CASCADE, related_name='package_discounts', null=True, blank=True) + promo_id = models.CharField(verbose_name='Промокод', max_length=20, null=True, blank=True) + usage_type = models.PositiveSmallIntegerField(verbose_name='Тип использования', + choices=USAGE_TYPE_CHOICES, default=USAGE_TYPE_LIMIT) + usage_count_limit = models.PositiveIntegerField(verbose_name='Сколько раз можно использовать', null=True, + blank=True) + activate_count = models.PositiveIntegerField(verbose_name='Сколько раз использован', default=0) + value = models.PositiveIntegerField(verbose_name='Размер скидки в %', default=0, + validators=[MaxValueValidator(100, 'Размер скидки не может быть больше 100')]) + start = models.DateTimeField(verbose_name='Дата начала действия скидки', default=now) + end = models.DateTimeField(verbose_name='Дата окончания скидки') + is_active = models.BooleanField(verbose_name='Активность', default=True) + author = models.ForeignKey(User, verbose_name='Автор', null=True, blank=True, related_name='author_discounts', + on_delete=models.CASCADE) + + def __str__(self): + return self.name + + class Meta: + verbose_name = 'Скидка' + verbose_name_plural = 'Скидки' diff --git a/apps/user/migrations/0035_auto_20190913_0755.py b/apps/user/migrations/0035_auto_20190913_0755.py new file mode 100644 index 00000000..3796835a --- /dev/null +++ b/apps/user/migrations/0035_auto_20190913_0755.py @@ -0,0 +1,17 @@ +# Generated by Django 2.0.7 on 2019-09-13 07:55 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('user', '0034_auto_20190612_1852'), + ] + + operations = [ + migrations.AlterModelOptions( + name='child', + options={'ordering': ('id',)}, + ), + ]