diff --git a/api/v1/views.py b/api/v1/views.py index dc798324..03db74f6 100644 --- a/api/v1/views.py +++ b/api/v1/views.py @@ -643,11 +643,13 @@ class PaymentViewSet(viewsets.ModelViewSet): course = request.query_params.get('course') weekdays = request.query_params.getlist('weekdays[]') date_start = request.query_params.get('date_start') + is_camp = bool(request.query_params.get('is_camp')) user = user and User.objects.get(pk=user) course = course and Course.objects.get(pk=course) date_start = date_start and datetime.strptime(date_start, '%Y-%m-%d') - return Response(Payment.calc_amount(user=user, course=course, date_start=date_start, weekdays=weekdays)) + return Response(Payment.calc_amount(user=user, course=course, date_start=date_start, weekdays=weekdays, + is_camp=is_camp)) class ContestViewSet(ExtendedModelViewSet): diff --git a/apps/course/templates/course/course_edit.html b/apps/course/templates/course/course_edit.html index 5fc6666a..29ef86cc 100644 --- a/apps/course/templates/course/course_edit.html +++ b/apps/course/templates/course/course_edit.html @@ -17,7 +17,7 @@ {% endblock header_buttons %} {% block content %} - = 28) or (date_start.day == 31 and date_end.day <= 30) \ + or (date_start.month == 1 and date_start.day >= 29 and date_end.day == 28): + date_end = date_start.replace(day=1, month=date_start.month + 1) - timedelta(1) + if is_camp: + if date_end > camp_end: + date_end = camp_end + elif school_end < date_end < school_start: + date_end = school_end + return [date_start, date_end] + + @classmethod + def calc_amount(cls, payment=None, user=None, course=None, date_start=None, weekdays=None, is_camp=False): price = 0 discount = 0 referral_bonus = 0 @@ -149,6 +177,12 @@ class Payment(PolymorphicModel): user = payment.user weekdays = payment.weekdays date_start = payment.date_start + if isinstance(payment, DrawingCampPayment): + user = payment.user + date_start = payment.date_start + if issubclass(cls, DrawingCampPayment): + is_camp = True + date_start, date_end = Payment.get_date_range(date_start, months=1, is_camp=is_camp) if hasattr(user, 'referral') and not user.referral.payment: referral_bonus = user.referral.bonus referrer_bonus = user.referral.referrer_bonus @@ -159,6 +193,15 @@ class Payment(PolymorphicModel): elif course: paid_before = CoursePayment.objects.filter(user=user, course=course, status__in=Payment.PW_PAID_STATUSES).exists() price = course.price / 2 if paid_before else course.price + elif is_camp: + if date_start.day == 1: + price = DrawingCampPayment.MONTH_PRICE + else: + weekdays_count = weekdays_in_date_range(date_start, date_end) + weekdays_count = sum(weekdays_count[wd] for wd in DrawingCampPayment.WEEKDAYS) + all_weekdays_count = weekdays_in_date_range(date_start.replace(day=1), date_end) + all_weekdays_count = sum(all_weekdays_count[wd] for wd in DrawingCampPayment.WEEKDAYS) + price = round(DrawingCampPayment.MONTH_PRICE / all_weekdays_count * weekdays_count) else: if user: school_payments = SchoolPayment.objects.filter( @@ -242,7 +285,7 @@ class Payment(PolymorphicModel): ] def save(self, *args, **kwargs): - amount_data = Payment.calc_amount(payment=self) + amount_data = type(self).calc_amount(payment=self) if not self.is_paid(): if not self.bonus: self.amount = amount_data.get('amount') @@ -309,6 +352,25 @@ class SchoolPayment(Payment): return arrow.get(self.date_end, settings.TIME_ZONE).humanize(locale='ru') +class DrawingCampPayment(Payment): + MONTH_PRICE = Decimal(1490) + WEEKDAYS = {1, 3, 5} + + date_start = models.DateField('Дата начала подписки', null=True, blank=True) + date_end = models.DateField('Дата окончания подписки', null=True, blank=True) + + class Meta: + verbose_name = 'Платеж за рисовальный лагерь' + verbose_name_plural = 'Платежи за рисовальный лагерь' + + def __str__(self): + return '%s - %s' % (self.date_start.strftime('%d/%m/%Y'), self.date_end.strftime('%d/%m/%Y')) + + @property + def date_end_humanize(self): + return arrow.get(self.date_end, settings.TIME_ZONE).humanize(locale='ru') + + class GiftCertificatePayment(Payment): gift_certificate = models.ForeignKey('GiftCertificate', on_delete=models.CASCADE, verbose_name='Подарочный сертификат', related_name='payments') diff --git a/apps/payment/templates/payment/payment_success.html b/apps/payment/templates/payment/payment_success.html index fe3c6a15..5686ee03 100644 --- a/apps/payment/templates/payment/payment_success.html +++ b/apps/payment/templates/payment/payment_success.html @@ -8,6 +8,12 @@ ПЕРЕЙТИ К ШКОЛЕ {% endif %} + {% if camp %} +
Вы успешно приобрели доступ к урокам рисовального лагеря!
+
+ ПЕРЕЙТИ В ЛАГЕРЬ +
+ {% endif %} {% if course %}
Вы успешно приобрели курс!
diff --git a/apps/payment/views.py b/apps/payment/views.py index 2a135549..acb8b08a 100644 --- a/apps/payment/views.py +++ b/apps/payment/views.py @@ -28,7 +28,7 @@ from apps.school.models import SchoolSchedule from apps.payment.tasks import transaction_to_mixpanel, product_payment_to_mixpanel, transaction_to_roistat from .models import AuthorBalance, CoursePayment, SchoolPayment, Payment, UserBonus, GiftCertificate, \ - GiftCertificatePayment, UserGiftCertificate + GiftCertificatePayment, UserGiftCertificate, DrawingCampPayment logger = logging.getLogger('django') @@ -50,8 +50,8 @@ class CourseBuySuccessView(TemplateView): class SchoolBuySuccessView(TemplateView): template_name = 'payment/payment_success.html' - def get(self, request, pk=None, *args, **kwargs): - return self.render_to_response(context={'school': True}) + def get(self, request, pk=None, is_camp=False, *args, **kwargs): + return self.render_to_response(context={'camp': True} if is_camp else {'school': True}) @method_decorator(login_required, name='dispatch') @@ -119,13 +119,15 @@ class SchoolBuyView(TemplateView): template_name = 'payment/paymentwall_widget.html' def get(self, request, *args, **kwargs): + raise Http404() # FIXME host = urlsplit(self.request.META.get('HTTP_REFERER')) host = str(host[0]) + '://' + str(host[1]) weekdays = set(request.GET.getlist('weekdays', [])) use_bonuses = request.GET.get('use_bonuses') roistat_visit = request.COOKIES.get('roistat_visit', None) date_start = request.GET.get('date_start') - date_start = date_start and datetime.datetime.strptime(date_start, '%Y-%m-%d') or now().date() + date_start = date_start and datetime.datetime.strptime(date_start, '%Y-%m-%d').date() or now().date() + date_start, date_end = Payment.get_date_range(date_start, months=1) if not weekdays: messages.error(request, 'Выберите несколько дней недели.') return redirect('school:school') @@ -165,7 +167,7 @@ class SchoolBuyView(TemplateView): weekdays=weekdays, roistat_visit=roistat_visit, date_start=date_start, - date_end=Payment.add_months(date_start), + date_end=date_end, ) if use_bonuses and request.user.bonus: if request.user.bonus >= school_payment.amount: @@ -203,6 +205,73 @@ class SchoolBuyView(TemplateView): return self.render_to_response(context={'widget': widget.get_html_code()}) +@method_decorator(login_required, name='dispatch') +class DrawingCampBuyView(TemplateView): + template_name = 'payment/paymentwall_widget.html' + + def get(self, request, *args, **kwargs): + host = urlsplit(self.request.META.get('HTTP_REFERER')) + host = str(host[0]) + '://' + str(host[1]) + use_bonuses = request.GET.get('use_bonuses') + roistat_visit = request.COOKIES.get('roistat_visit', None) + date_start = request.GET.get('date_start') + date_start = date_start and datetime.datetime.strptime(date_start, '%Y-%m-%d').date() or now().date() + date_start, date_end = Payment.get_date_range(date_start, months=1, is_camp=True) + prev_payment = DrawingCampPayment.objects.filter( + user=request.user, + date_start__lte=date_start, + date_end__gte=date_start, + status__in=[ + Pingback.PINGBACK_TYPE_REGULAR, + Pingback.PINGBACK_TYPE_GOODWILL, + Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED, + ], + ).exists() + if prev_payment: + return HttpResponse(status=403) + camp_payment = DrawingCampPayment.objects.create( + user=request.user, + roistat_visit=roistat_visit, + date_start=date_start, + date_end=date_end, + ) + if use_bonuses and request.user.bonus: + if request.user.bonus >= camp_payment.amount: + bonus = UserBonus.objects.create(amount=-camp_payment.amount, user=request.user, + payment=camp_payment) + camp_payment.amount = 0 + camp_payment.status = Pingback.PINGBACK_TYPE_REGULAR + else: + bonus = UserBonus.objects.create(amount=-request.user.bonus, user=request.user, + payment=camp_payment) + camp_payment.amount -= request.user.bonus + camp_payment.bonus = bonus + camp_payment.save() + if camp_payment.is_paid(): + return redirect(reverse_lazy('camp-payment-success')) + product = Product( + f'drawing_camp_{camp_payment.id}', + camp_payment.amount, + 'RUB', + 'Школа', + ) + widget = Widget( + str(request.user.id), + 'p1_1', + [product], + extra_params={ + 'email': request.user.email, + 'lang': 'ru', + 'evaluation': 1, + 'demo': 1, + 'test_mode': 1, + 'success_url': host + str(reverse_lazy('camp-payment-success')), + 'failure_url': host + str(reverse_lazy('payment-error')), + } + ) + return self.render_to_response(context={'widget': widget.get_html_code()}) + + @method_decorator(csrf_exempt, name='dispatch') class PaymentwallCallbackView(View): @@ -226,6 +295,8 @@ class PaymentwallCallbackView(View): product_payment_class = CoursePayment elif product_type_name == 'school': product_payment_class = SchoolPayment + elif product_type_name == 'drawing_camp': + product_payment_class = DrawingCampPayment elif product_type_name == 'gift_certificate': product_payment_class = GiftCertificatePayment else: @@ -276,6 +347,16 @@ class PaymentwallCallbackView(View): 'created_at': payment.created_at, 'update_at': payment.update_at, } + elif product_type_name == 'drawing_camp': + properties = { + 'payment_id': payment.id, + 'amount': payment.amount, + 'status': payment.status, + 'date_start': payment.date_start, + 'date_end': payment.date_end, + 'created_at': payment.created_at, + 'update_at': payment.update_at, + } elif product_type_name == 'gift_certificate': properties = { 'payment_id': payment.id, diff --git a/apps/school/admin.py b/apps/school/admin.py index 8f4c6cd9..0f995b9e 100644 --- a/apps/school/admin.py +++ b/apps/school/admin.py @@ -11,6 +11,7 @@ class LiveLessonAdmin(admin.ModelAdmin): 'stream', 'cover', 'date', + 'is_camp', 'created_at', 'update_at', ) diff --git a/apps/school/migrations/0023_livelesson_is_camp.py b/apps/school/migrations/0023_livelesson_is_camp.py new file mode 100644 index 00000000..a3880b8b --- /dev/null +++ b/apps/school/migrations/0023_livelesson_is_camp.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.7 on 2019-05-07 04:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('school', '0022_livelesson_materials'), + ] + + operations = [ + migrations.AddField( + model_name='livelesson', + name='is_camp', + field=models.BooleanField(default=False), + ), + ] diff --git a/apps/school/models.py b/apps/school/models.py index 771f01bb..bccaa7f7 100644 --- a/apps/school/models.py +++ b/apps/school/models.py @@ -69,6 +69,7 @@ class SchoolSchedule(models.Model): date__week_day=self.weekday % 7 + 1, date__range=[date_start, date_start + timedelta(days=6)], deactivated_at__isnull=True, + is_camp=False, ).first() return live_lesson @@ -79,6 +80,29 @@ class SchoolSchedule(models.Model): date__week_day=self.weekday % 7 + 1, date__range=[(now_time - timedelta(days=8)).date(), (now_time - timedelta(days=1)).date()], deactivated_at__isnull=True, + is_camp=False, + ).first() + return live_lesson + + @cached_property + def current_camp_lesson(self): + date_start = now().date() + live_lesson = LiveLesson.objects.filter( + date__week_day=self.weekday % 7 + 1, + date__range=[date_start, date_start + timedelta(days=6)], + deactivated_at__isnull=True, + is_camp=True, + ).first() + return live_lesson + + @cached_property + def previous_camp_lesson(self): + now_time = now() + live_lesson = LiveLesson.objects.filter( + date__week_day=self.weekday % 7 + 1, + date__range=[(now_time - timedelta(days=8)).date(), (now_time - timedelta(days=1)).date()], + deactivated_at__isnull=True, + is_camp=True, ).first() return live_lesson @@ -126,6 +150,7 @@ class LiveLesson(BaseModel, DeactivatedMixin): null=True, blank=True, ) + is_camp = models.BooleanField(default=False) created_at = models.DateTimeField(auto_now_add=True) update_at = models.DateTimeField(auto_now=True) @@ -148,16 +173,17 @@ class LiveLesson(BaseModel, DeactivatedMixin): return self.get_absolute_url() def get_absolute_url(self): - return reverse_lazy('school:lesson-detail', kwargs={'lesson_date': self.date.strftime('%d-%m-%y')}) + return (reverse_lazy('school:camp-lesson-detail', kwargs={'lesson_date': self.date.strftime('%d-%m-%y')}) + if self.is_camp + else reverse_lazy('school:lesson-detail', kwargs={'lesson_date': self.date.strftime('%d-%m-%y')})) def stream_index(self): return self.stream.split('/')[-1] @cached_property def school_schedule(self): - weekday = self.date.isoweekday() if self.date else None try: - return SchoolSchedule.objects.get(weekday=weekday) + return SchoolSchedule.objects.get(weekday=self.date.isoweekday()) except SchoolSchedule.DoesNotExist: return None diff --git a/apps/school/templates/blocks/day_pay_btn.html b/apps/school/templates/blocks/day_pay_btn.html index ec00046f..ea977051 100644 --- a/apps/school/templates/blocks/day_pay_btn.html +++ b/apps/school/templates/blocks/day_pay_btn.html @@ -1,5 +1,9 @@ купить diff --git a/apps/school/templates/blocks/schedule.html b/apps/school/templates/blocks/schedule.html index 1a021d31..daa59d34 100644 --- a/apps/school/templates/blocks/schedule.html +++ b/apps/school/templates/blocks/schedule.html @@ -10,6 +10,7 @@ {% endfor %}
+ {% comment %} {% if not is_purchased and not is_purchased_future %} Получить доступ на месяц {% endif %} @@ -19,6 +20,7 @@ + {% endcomment %}
diff --git a/apps/school/templates/blocks/schedule_item.html b/apps/school/templates/blocks/schedule_item.html index 1c37334e..593d0426 100644 --- a/apps/school/templates/blocks/schedule_item.html +++ b/apps/school/templates/blocks/schedule_item.html @@ -20,9 +20,11 @@ {% include './open_lesson.html' %} {% endif %} {% else %} - {% include './day_pay_btn.html' %} + {% if not is_purchased_future and is_drawing_camp %} + {% include './day_pay_btn.html' %} + {% endif %} {% endif %} - {% if not is_purchased and not request.user_agent.is_mobile and school_schedule.trial_lesson %} + {% if not is_purchased and not request.user_agent.is_mobile and school_schedule.trial_lesson and not is_drawing_camp %} Пробный урок {% endif %} diff --git a/apps/school/templates/blocks/schedule_purchased.html b/apps/school/templates/blocks/schedule_purchased.html index eb7cc8e2..989e8849 100644 --- a/apps/school/templates/blocks/schedule_purchased.html +++ b/apps/school/templates/blocks/schedule_purchased.html @@ -40,15 +40,17 @@ {% if is_previous and not live_lessons_exists %} Записей уроков пока нет {% else %} - {% for school_schedule in school_schedules %} - {% if is_previous %} - {% if school_schedule.previous_live_lesson in live_lessons %} - {% include 'blocks/schedule_item.html' with school_schedule=school_schedule live_lesson=school_schedule.previous_live_lesson purchased=True %} - {% endif %} - {% else %} - {% include 'blocks/schedule_item.html' with school_schedule=school_schedule live_lesson=school_schedule.current_live_lesson purchased=True %} - {% endif %} - {% endfor %} + {% for school_schedule in school_schedules %} + {% if is_previous %} + {% if school_schedule.previous_live_lesson in live_lessons %} + {% include 'blocks/schedule_item.html' with school_schedule=school_schedule + live_lesson=school_schedule.previous_live_lesson purchased=True %} + {% endif %} + {% else %} + {% include 'blocks/schedule_item.html' with school_schedule=school_schedule + live_lesson=school_schedule.current_live_lesson purchased=True %} + {% endif %} + {% endfor %} {% endif %}
diff --git a/apps/school/templates/drawing_camp/about.html b/apps/school/templates/drawing_camp/about.html new file mode 100644 index 00000000..9b0d87ba --- /dev/null +++ b/apps/school/templates/drawing_camp/about.html @@ -0,0 +1,30 @@ +{% load static %} +
+
+
О Рисовальном Лагере
+
+
+ +
+
+
+

Что такое «Рисовальный Лагерь»?

+

Это месяц фантазии, красок, карандашей и необычных заданий!

+ +

Любой ребёнок из любой точки мира в удобное время может порисовать с профессиональными преподавателями.

+ +

Мы поговорим о направлениях в живописи. О стилях и жанрах. Рассмотрим великие полотна, + познакомимся с художниками. И, конечно, создадим собственные работы в их стиле.

+ +

На наших занятиях нет понятий «правильно» и «неправильно». Есть только желание и вдохновение.

+ +

Мы зарядим вас и ваших детей творчеством! Присоединяйтесь!

+
+
- Sasha Kru, CEO и основатель + Lil.City +
+ +
+
+
+
diff --git a/apps/school/templates/drawing_camp/gallery.html b/apps/school/templates/drawing_camp/gallery.html new file mode 100644 index 00000000..5d85f072 --- /dev/null +++ b/apps/school/templates/drawing_camp/gallery.html @@ -0,0 +1,65 @@ +{% load static %} +
+
+ +
Работы учеников
+
+
+

Более 5 тысяч детей отучились с нами в Рисовальном Лагере прошлым летом. Присоединяйся и ты!

+
+ +
+
diff --git a/apps/school/templates/drawing_camp/online.html b/apps/school/templates/drawing_camp/online.html new file mode 100644 index 00000000..53957d3f --- /dev/null +++ b/apps/school/templates/drawing_camp/online.html @@ -0,0 +1,9 @@ +{% load static %} + +
+
ВИДЕОУРОКИ
+
В 17.00 (по Мск)
+
Каждый день
+
+
+
diff --git a/apps/school/templates/drawing_camp/promo.html b/apps/school/templates/drawing_camp/promo.html new file mode 100644 index 00000000..18d1ec78 --- /dev/null +++ b/apps/school/templates/drawing_camp/promo.html @@ -0,0 +1,27 @@ +{% load static %} +
+
+
+ Рисовальный лагерь — продолжение нашей онлайн-колы только летом. + Понедельник, среда и пятница ждем вас в 17:00 (мск). Уроки хранятся 7 дней. +
+ +
+
diff --git a/apps/school/templates/drawing_camp/schedule_purchased.html b/apps/school/templates/drawing_camp/schedule_purchased.html new file mode 100644 index 00000000..5027b320 --- /dev/null +++ b/apps/school/templates/drawing_camp/schedule_purchased.html @@ -0,0 +1,62 @@ +{% load static %} {% load thumbnail %} +{% if next_schedule %} +
+
+
+ Следующий урок: {{ next_schedule.title }} +
+
+ Начнется через {{ next_lesson_start }} +
+
+
+{% endif %} +{% include 'templates/blocks/messages.html' %} +
+
+
Расписание
+
+
+ +
+
+
+
+
Новые уроки
+ {% for school_schedule in school_schedules_sorted %} + {% if is_drawing_camp %} + {% include 'blocks/schedule_item.html' with school_schedule=school_schedule live_lesson=school_schedule.current_camp_lesson %} + {% else %} + {% include 'blocks/schedule_item.html' with school_schedule=school_schedule live_lesson=school_schedule.current_live_lesson %} + {% endif %} + {% endfor %} + {% if prev_live_lessons_exists %} +
Прошедшие уроки
+ {% for live_lesson in prev_live_lessons %} + {% if live_lesson.school_schedule and live_lesson.title %} + {% include 'blocks/schedule_item.html' with school_schedule=live_lesson.school_schedule live_lesson=live_lesson is_previous=True is_purchased=True %} + {% endif %} + {% endfor %} + {% endif %} +
+
+ {% comment %} previous week schedules {% endcomment %} +
+
+
+
+
+
diff --git a/apps/school/templates/school/drawing_camp.html b/apps/school/templates/school/drawing_camp.html new file mode 100644 index 00000000..1f7b2b3c --- /dev/null +++ b/apps/school/templates/school/drawing_camp.html @@ -0,0 +1,16 @@ +{% extends "templates/lilcity/index.html" %} {% load static %} +{% block title %}Рисовальный лагерь LilCity{% endblock title%} +{% block ogimage %}http://{{request.META.HTTP_HOST}}{% static 'img/og_main.jpg' %}{% endblock %} +{% block content %} +{% if not is_purchased and not prev_school_payments_exists %} +{% include "../drawing_camp/promo.html" %} +{% include "../blocks/schedule.html" %} +{% include "../drawing_camp/gallery.html" %} +{% include "../drawing_camp/about.html" %} +{% include "templates/blocks/game.html" %} +{% include "templates/blocks/partners.html" %} +{% else %} +{% include "../drawing_camp/schedule_purchased.html" %} +{% include "templates/blocks/partners.html" %} +{% endif %} +{% endblock %} diff --git a/apps/school/templates/school/livelessons_list.html b/apps/school/templates/school/livelessons_list.html index 161a6629..06414796 100644 --- a/apps/school/templates/school/livelessons_list.html +++ b/apps/school/templates/school/livelessons_list.html @@ -1,7 +1,7 @@ {% extends "templates/lilcity/index.html" %}{% load static %} {% block content %}
-
Уроки онлайн-школы LilCity
+
Уроки {% if is_camp %}рисовального лагеря{% else %}онлайн-школы{% endif %} LilCity
@@ -16,7 +16,8 @@ - + diff --git a/apps/school/templates/summer/promo.html b/apps/school/templates/summer/promo.html index 7d12fac4..29948949 100644 --- a/apps/school/templates/summer/promo.html +++ b/apps/school/templates/summer/promo.html @@ -7,6 +7,7 @@
+ {% comment %}
+ {% endcomment %}
diff --git a/apps/school/templates/summer/schedule_purchased.html b/apps/school/templates/summer/schedule_purchased.html index 09d016ad..0994e675 100644 --- a/apps/school/templates/summer/schedule_purchased.html +++ b/apps/school/templates/summer/schedule_purchased.html @@ -24,7 +24,11 @@
Новые уроки
{% for school_schedule in school_schedules_sorted %} - {% include 'blocks/schedule_item.html' with school_schedule=school_schedule live_lesson=school_schedule.current_live_lesson %} + {% if is_drawing_camp %} + {% include 'blocks/schedule_item.html' with school_schedule=school_schedule live_lesson=school_schedule.current_camp_lesson %} + {% else %} + {% include 'blocks/schedule_item.html' with school_schedule=school_schedule live_lesson=school_schedule.current_live_lesson %} + {% endif %} {% endfor %} {% if prev_live_lessons_exists %}
Прошедшие уроки
diff --git a/apps/school/urls.py b/apps/school/urls.py index 9ac1d078..e37768b0 100644 --- a/apps/school/urls.py +++ b/apps/school/urls.py @@ -3,15 +3,19 @@ from django.urls import path, re_path from .views import ( LiveLessonsView, LiveLessonEditView, LiveLessonsDetailView, SchoolView, - SchoolSchedulesPrintView, -) + SchoolSchedulesPrintView, DrawingCampView, + DrawingCampLessonsView) urlpatterns = [ path('', SchoolView.as_view(), name='school'), path('schedules/print', SchoolSchedulesPrintView.as_view(), name='school_schedules-print'), path('lessons/', LiveLessonsView.as_view(), name='lessons'), - path('lessons/create', LiveLessonEditView.as_view(), name='lessons-create'), path('lessons//edit', LiveLessonEditView.as_view(), name='lessons-edit'), path('lessons//', LiveLessonsDetailView.as_view(), name='lesson-detail-id'), - re_path(r'(?P\d+\-\d+\-\d+)', LiveLessonsDetailView.as_view(), name='lesson-detail'), + re_path(r'^(?P\d+\-\d+\-\d+)', LiveLessonsDetailView.as_view(), name='lesson-detail'), + path('camp/', DrawingCampView.as_view(), name='drawing-camp'), + path('camp/lessons/', DrawingCampLessonsView.as_view(), name='camp-lessons'), + path('camp/lessons//edit', LiveLessonEditView.as_view(), name='camp-lessons-edit', kwargs={'is_camp': True}), + re_path(r'^camp/(?P\d+\-\d+\-\d+)', LiveLessonsDetailView.as_view(), name='camp-lesson-detail', + kwargs={'is_camp': True}), ] diff --git a/apps/school/views.py b/apps/school/views.py index 7651b71a..74aa2730 100644 --- a/apps/school/views.py +++ b/apps/school/views.py @@ -1,4 +1,6 @@ from datetime import datetime, timedelta, date + +import arrow from paymentwall import Pingback from django.contrib.auth import get_user_model @@ -10,9 +12,10 @@ from django.shortcuts import get_object_or_404, redirect from django.utils.decorators import method_decorator from django.utils.timezone import now from django.views.generic import ListView, UpdateView, TemplateView, DetailView +from django.conf import settings from apps.course.models import Course -from apps.payment.models import SchoolPayment +from apps.payment.models import SchoolPayment, DrawingCampPayment from .models import LiveLesson, SchoolSchedule User = get_user_model() @@ -34,26 +37,54 @@ class LiveLessonsView(ListView): template_name = 'school/livelessons_list.html' def get_queryset(self): - september2018 = date(2018, 9, 1) date_start = (now() - timedelta(days=7)).date() - if date_start < september2018: - date_start = september2018 + date_start, date_end = SchoolPayment.get_date_range(date_start, days=17, is_camp=False) date_range = Q( - date__range=[ - date_start, - date_start + timedelta(days=17), - ] + date__range=[date_start, date_end] ) + exist_dates = LiveLesson.objects.filter(date_range, is_camp=False).values_list('date', flat=True) + for i in range((date_end - date_start).days): + d = date_start + timedelta(days=i) + if d not in exist_dates: + try: + LiveLesson.objects.create( + date=date_start + timedelta(days=i), + is_camp=False, + ) + except IntegrityError: + pass queryset = LiveLesson.objects.filter(date_range) - if queryset.count() < 17: - for i in range(18): + return queryset + + +@method_decorator([login_required, is_admin_or_teacher], name='dispatch') +class DrawingCampLessonsView(ListView): + model = LiveLesson + template_name = 'school/livelessons_list.html' + + def get_context_data(self, *args, **kwargs): + context = super().get_context_data(*args, **kwargs) + context['is_camp'] = True + return context + + def get_queryset(self): + date_start = (now() - timedelta(days=7)).date() + date_start, date_end = DrawingCampPayment.get_date_range(date_start, days=23, is_camp=True) + date_range = Q( + date__range=[date_start, date_end] + ) + exist_dates = LiveLesson.objects.filter(date_range, is_camp=True).values_list('date', flat=True) + for i in range((date_end - date_start).days): + d = date_start + timedelta(days=i) + if d.isoweekday() in DrawingCampPayment.WEEKDAYS and d not in exist_dates: try: LiveLesson.objects.create( date=date_start + timedelta(days=i), + is_camp=True, ) except IntegrityError: pass - queryset = LiveLesson.objects.filter(date_range) + queryset = LiveLesson.objects.filter(date_range) return queryset @@ -61,16 +92,20 @@ class LiveLessonsDetailView(DetailView): model = LiveLesson template_name = 'school/livelesson_detail.html' - def get(self, request, pk=None, lesson_date=None): + def get(self, request, pk=None, lesson_date=None, is_camp=False): if pk: self.object = self.get_object() if lesson_date: try: - self.object = LiveLesson.objects.get(date=datetime.strptime(lesson_date, '%d-%m-%y')) + self.object = LiveLesson.objects.get(date=datetime.strptime(lesson_date, '%d-%m-%y'), is_camp=is_camp) except Exception: raise Http404() if request.user.is_authenticated: - is_purchased = SchoolPayment.objects.filter( + if self.object.is_camp: + is_purchased = DrawingCampPayment.objects.all() + else: + is_purchased = SchoolPayment.objects.filter(weekdays__contains=[self.object.date.weekday() + 1],) + is_purchased = is_purchased.filter( user=request.user, date_start__lte=now(), date_end__gte=now() - timedelta(days=7), @@ -79,7 +114,6 @@ class LiveLessonsDetailView(DetailView): Pingback.PINGBACK_TYPE_GOODWILL, Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED, ], - weekdays__contains=[self.object.date.weekday() + 1], ).exists() if not is_purchased and request.user.role not in [User.ADMIN_ROLE, User.TEACHER_ROLE]: raise Http404 @@ -94,20 +128,21 @@ class LiveLessonsDetailView(DetailView): class LiveLessonEditView(TemplateView): template_name = 'course/course_edit.html' - def get(self, request, pk=None): + def get(self, request, pk=None, is_camp=False): if pk: - self.object = get_object_or_404(LiveLesson, pk=pk) + self.object = get_object_or_404(LiveLesson, pk=pk, is_camp=is_camp) else: try: self.object = LiveLesson.objects.get(date=now().date()) except LiveLesson.DoesNotExist: - self.object = LiveLesson.objects.create() + self.object = LiveLesson.objects.create(is_camp=is_camp) return super().get(request) def get_context_data(self): context = super().get_context_data() context['object'] = self.object context['live'] = 'true' + context['is_camp'] = 'true' if self.object.is_camp else 'false' return context @@ -204,12 +239,12 @@ class SchoolView(TemplateView): 'prev_live_lessons_exists': prev_live_lessons_exists, 'course_items': Course.objects.filter(status=Course.PUBLISHED)[:6], 'is_purchased': school_payment_exists, - 'is_purchased_future': False, + 'is_purchased_future': school_purchased_future and school_purchased_future.exists(), 'min_school_price': SchoolSchedule.objects.aggregate(Min('month_price'))['month_price__min'], 'school_schedules_sorted': school_schedules_sorted, 'school_schedules': school_schedules, 'school_schedules_purchased': school_schedules_purchased, - 'school_purchased_future': school_purchased_future, + 'school_purchased_future': school_purchased_future and school_purchased_future.last(), 'prev_school_payments_exists': prev_school_payments and prev_school_payments.exists(), 'subscription_ends': subscription_ends, 'prolong_date_start': subscription_ends + timedelta(days=1) if subscription_ends else None, @@ -226,3 +261,117 @@ class SchoolSchedulesPrintView(TemplateView): context = super().get_context_data() context['school_schedules'] = SchoolSchedule.objects.all() return context + + +class DrawingCampView(TemplateView): + template_name = 'school/drawing_camp.html' + + def get_context_data(self): + context = super().get_context_data() + date_now = now().date() + now_time = now() + online = False + next_schedule = None + if now_time.isoweekday() in DrawingCampPayment.WEEKDAYS: + try: + school_schedule = SchoolSchedule.objects.get(weekday=now_time.isoweekday()) + except SchoolSchedule.DoesNotExist: + online = False + else: + end_at = datetime.combine(now_time.today(), school_schedule.start_at) + online = ( + school_schedule.start_at <= now_time.time() and + (end_at + timedelta(hours=1)).time() >= now_time.time() and + school_schedule.current_camp_lesson + ) + if school_schedule.current_camp_lesson and school_schedule.start_at > now_time.time(): + next_schedule = school_schedule + next_lesson_start = next_schedule.start_at_humanize + if not next_schedule: + next_camp_lesson = LiveLesson.objects.filter(date__gt=date_now, is_camp=True).first() + if next_camp_lesson: + next_schedule = next_camp_lesson.school_schedule + next_lesson_start = arrow.get(datetime.combine(next_camp_lesson.date, next_schedule.start_at), + settings.TIME_ZONE).humanize(locale='ru') + + school_schedules = SchoolSchedule.objects.filter(weekday__in=DrawingCampPayment.WEEKDAYS) + try: + school_schedules_sorted = sorted(school_schedules, + key=lambda ss: ss.current_camp_lesson and ss.current_camp_lesson.date) + except Exception: + school_schedules_sorted = school_schedules + prev_live_lessons = [] + prev_live_lessons_exists = False + subscription_ends = None + camp_payment_exists = False + camp_purchased_future = False + prev_camp_payments = None + prev_range = [date_now - timedelta(days=7), date_now - timedelta(days=1)] + if self.request.user.is_authenticated: + camp_payment = DrawingCampPayment.objects.filter( + user=self.request.user, + status__in=DrawingCampPayment.PW_PAID_STATUSES, + date_start__lte=date_now, + date_end__gte=date_now + ) + camp_payment_exists = camp_payment.exists() + + camp_purchased_future = DrawingCampPayment.objects.filter( + user=self.request.user, + status__in=DrawingCampPayment.PW_PAID_STATUSES, + date_start__gt=date_now, + date_end__gt=date_now + ) + + if camp_purchased_future.exists(): + subscription_ends = camp_purchased_future.latest('date_end').date_end + elif camp_payment_exists: + subscription_ends = camp_payment.latest('date_end').date_end + + prev_camp_payments = DrawingCampPayment.objects.filter( + date_start__lte=prev_range[1], + date_end__gte=prev_range[0], + user=self.request.user, + status__in=[ + Pingback.PINGBACK_TYPE_REGULAR, + Pingback.PINGBACK_TYPE_GOODWILL, + Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED, + ], + ) + + # берем все подписки, которые были в периоде + for sp in prev_camp_payments: + # берем все уроки в оплаченном промежутке + date_range = [max(sp.date_start, prev_range[0]), min(sp.date_end, prev_range[1])] + prev_live_lessons += list(LiveLesson.objects.filter( + date__range=date_range, + deactivated_at__isnull=True, + is_camp=True, + ).exclude(title='').values_list('id', flat=True)) + prev_live_lessons = LiveLesson.objects.filter(id__in=set(prev_live_lessons)).order_by('-date') + prev_live_lessons_exists = prev_live_lessons.exists() + if prev_live_lessons_exists: + school_schedules_dict = {ss.weekday: ss for ss in school_schedules} + school_schedules_dict[0] = school_schedules_dict.get(7) + for ll in prev_live_lessons: + ll.school_schedule = school_schedules_dict.get(ll.date.isoweekday()) + context.update({ + 'next_schedule': next_schedule, + 'next_lesson_start': next_lesson_start, + 'is_drawing_camp': True, + 'online': online, + 'prev_live_lessons': prev_live_lessons, + 'prev_live_lessons_exists': prev_live_lessons_exists, + 'course_items': Course.objects.filter(status=Course.PUBLISHED)[:6], + 'is_purchased': camp_payment_exists, + 'is_purchased_future': camp_purchased_future and camp_purchased_future.exists(), + 'drawing_camp_price': DrawingCampPayment.MONTH_PRICE, + 'school_schedules_sorted': school_schedules_sorted, + 'school_schedules': school_schedules, + 'school_schedules_purchased': DrawingCampPayment.WEEKDAYS if camp_payment_exists else [], + 'school_purchased_future': camp_purchased_future and camp_purchased_future.last(), + 'prev_school_payments_exists': prev_camp_payments and prev_camp_payments.exists(), + 'subscription_ends': subscription_ends, + 'prolong_date_start': subscription_ends + timedelta(days=1) if subscription_ends else None, + }) + return context diff --git a/project/templates/blocks/footer.html b/project/templates/blocks/footer.html index b1d37518..21c8df71 100644 --- a/project/templates/blocks/footer.html +++ b/project/templates/blocks/footer.html @@ -11,7 +11,8 @@
diff --git a/project/templates/blocks/promo.html b/project/templates/blocks/promo.html index f438e36f..408cd2cb 100644 --- a/project/templates/blocks/promo.html +++ b/project/templates/blocks/promo.html @@ -11,6 +11,26 @@
Lil School — первая образовательная онлайн-платформа креативного мышления для детей! 5+
+
+ Приглашаем вас присоединиться к рисовальному лагерю +
+
+ {% if not is_purchased and not is_purchased_future %} + + купить доступ от {{ camp_price }} руб./месяц + + Подарить другу + {% else %} + Подробнее + {% endif %} +
+ + {% comment %} {% if user.is_authenticated and online %}
Сейчас идёт прямой эфир урока «{{ school_schedule.title }}, {{ school_schedule.current_live_lesson.title }}» @@ -22,7 +42,7 @@ data-day="{{ school_schedule.weekday }}" href='#' {% else %} - href="{% url 'school:school' %}" + href="{% url 'school:drawing-camp' %}" {% endif %} class="main__btn btn" >{% if not school_schedule.weekday in school_schedules_purchased %}Получить доступ{% else %}Смотреть урок{% endif %} @@ -45,7 +65,7 @@ data-day="{{ school_schedule.weekday }}" href='#' {% else %} - href="{% url 'school:school' %}" + href="{% url 'school:drawing-camp' %}" {% endif %} class="main__btn btn" >{% if not school_schedule.weekday in school_schedules_purchased %}Получить доступ{% else %}Смотреть урок{% endif %} @@ -73,5 +93,6 @@ {% endif %}
{% endif %} + {% endcomment %}
diff --git a/project/templates/blocks/user_menu.html b/project/templates/blocks/user_menu.html index 955d3583..5e0df6d7 100644 --- a/project/templates/blocks/user_menu.html +++ b/project/templates/blocks/user_menu.html @@ -33,6 +33,9 @@
УРОКИ ШКОЛЫ
+ +
УРОКИ ЛАГЕРЯ
+
{% endif %} {% endif %} diff --git a/project/urls.py b/project/urls.py index 6e71969e..9d5266c8 100644 --- a/project/urls.py +++ b/project/urls.py @@ -35,7 +35,7 @@ from apps.payment.views import ( CourseBuySuccessView, CourseBuyView, PaymentwallCallbackView, SchoolBuySuccessView, SchoolBuyView, GiftCertificatesView, GiftCertificateBuyView, - GiftCertificateBuySuccessView, GiftCertificateGetView) + GiftCertificateBuySuccessView, GiftCertificateGetView, DrawingCampBuyView) from .views import AboutView, IndexView, SchoolSchedulesView, LinksView @@ -56,7 +56,8 @@ urlpatterns = [ path('course//checkout', CourseBuyView.as_view(), name='course-checkout'), path('course//edit', CourseEditView.as_view(), name='course_edit'), path('course//edit/lessons', CourseEditView.as_view(), name='course_edit_lessons'), - path('course//lessons', CourseView.as_view(template_name='course/course_only_lessons.html', only_lessons=True), name='course-only-lessons'), + path('course//lessons', CourseView.as_view(template_name='course/course_only_lessons.html', only_lessons=True), + name='course-only-lessons'), path('course//like', likes, name='likes'), path('course//comment', coursecomment, name='coursecomment'), path('lesson//', LessonView.as_view(), name='lesson'), @@ -65,8 +66,11 @@ urlpatterns = [ path('paymentwall/pingback', PaymentwallCallbackView.as_view(), name='payment-ping-second'), path('payments/course//success', CourseBuySuccessView.as_view(), name='course_payment_success'), path('payments/school/success', SchoolBuySuccessView.as_view(), name='payment-success'), + path('payments/school/camp/success', SchoolBuySuccessView.as_view(), name='camp-payment-success', + kwargs={'is_camp': True}), path('payments/error', TemplateView.as_view(template_name='payment/payment_error.html'), name='payment-error'), path('school/checkout', SchoolBuyView.as_view(), name='school-checkout'), + path('school/camp/checkout', DrawingCampBuyView.as_view(), name='camp-checkout'), path('search/', SearchView.as_view(), name='search'), path('user/profile/', ProfileView.as_view(), name='user-profile'), path('user/profile/edit', ProfileEditView.as_view(), name='user-edit-profile'), diff --git a/project/views.py b/project/views.py index 2daf6608..485d7a0c 100644 --- a/project/views.py +++ b/project/views.py @@ -10,7 +10,7 @@ from paymentwall.pingback import Pingback from apps.course.models import Course from apps.school.models import SchoolSchedule -from apps.payment.models import SchoolPayment, UserGiftCertificate, Payment +from apps.payment.models import SchoolPayment, UserGiftCertificate, Payment, DrawingCampPayment User = get_user_model() @@ -65,7 +65,16 @@ class IndexView(TemplateView): joined_weekdays=Func(F('weekdays'), function='unnest',) ).values_list('joined_weekdays', flat=True).distinct() + camp_payment = DrawingCampPayment.objects.filter( + user=self.request.user, + status__in=DrawingCampPayment.PW_PAID_STATUSES, + date_start__lte=date_now, + date_end__gte=date_now + ) + camp_payment_exists = camp_payment.exists() + else: + camp_payment_exists = False school_payment_exists = False school_schedules_purchased = [] @@ -90,6 +99,7 @@ class IndexView(TemplateView): shuffle(review_images) context.update({ + 'is_drawing_camp': True, 'review_images': review_images, 'gift_certificate': user_gift_certificate.gift_certificate if user_gift_certificate else None, 'user_gift_certificate': user_gift_certificate, @@ -100,14 +110,17 @@ class IndexView(TemplateView): 'online_coming_soon': online_coming_soon, 'school_schedule': school_schedule, 'course_items': Course.shuffle(Course.objects.filter(status=Course.PUBLISHED)[:3]), - 'is_purchased': school_payment_exists, + 'is_purchased': camp_payment_exists, # school_payment_exists, + 'camp_price': DrawingCampPayment.MONTH_PRICE, 'min_school_price': SchoolSchedule.objects.aggregate(Min('month_price'))['month_price__min'], - 'school_schedules': SchoolSchedule.objects.all(), - 'school_schedules_purchased': set(school_schedules_purchased), + 'school_schedules': SchoolSchedule.objects.filter(weekday__in=DrawingCampPayment.WEEKDAYS), + 'school_schedules_purchased': DrawingCampPayment.WEEKDAYS if camp_payment_exists else [], # set(school_schedules_purchased), 'teachers': User.objects.filter(role=User.TEACHER_ROLE, show_in_mainpage=True), 'works_count': Payment.objects.filter(status__in=Payment.PW_PAID_STATUSES).count() * 7, - 'subscription_ends': school_payment.filter(add_days=False).first().date_end if school_payment_exists else None, - 'subscription_ends_humanize': school_payment.filter(add_days=False).first().date_end_humanize if school_payment_exists else None, + # 'subscription_ends': school_payment.filter(add_days=False).first().date_end if school_payment_exists else None, + # 'subscription_ends_humanize': school_payment.filter(add_days=False).first().date_end_humanize if school_payment_exists else None, + 'subscription_ends': camp_payment.latest('date_end').date_end if camp_payment_exists else None, + 'subscription_ends_humanize': camp_payment.latest('date_end').date_end_humanize if camp_payment_exists else None, 'school_purchased_future': False, 'is_purchased_future': False, diff --git a/web/src/components/CourseRedactor.vue b/web/src/components/CourseRedactor.vue index 319a59c3..9e5b3fef 100644 --- a/web/src/components/CourseRedactor.vue +++ b/web/src/components/CourseRedactor.vue @@ -259,7 +259,7 @@ export default { name: "course-redactor", - props: ["authorName", "authorPicture", "accessToken", "courseId", "live"], + props: ["authorName", "authorPicture", "accessToken", "courseId", "live", "camp"], data() { return { disabledDates: { @@ -670,7 +670,7 @@ publishButton.attr('disabled', 'disabled'); if(this.live) { - window.location = '/school/lessons'; + window.location = this.camp ? '/school/camp/lessons' : '/school/lessons'; } else { api.publishCourse(this.course, this.accessToken) .then((response) => { @@ -762,7 +762,7 @@ this.courseSaving = true; this.changeSavingStatus(); - const courseObject = this.course; + const courseObject = Object.assign({}, this.course); courseObject.slug = this.getSlug(courseObject.slug); api.saveCourse(courseObject, this.accessToken) .then((response) => { diff --git a/web/src/js/modules/popup.js b/web/src/js/modules/popup.js index 864f5312..1894241e 100644 --- a/web/src/js/modules/popup.js +++ b/web/src/js/modules/popup.js @@ -171,6 +171,7 @@ $(document).ready(function () { if(data === '.js-popup-buy') { popup.data('date-start', $this.data('date-start') || ''); popup.data('day', $this.data('day') || ''); + popup.data('is-camp', $this.data('is-camp') || ''); $('[data-day]').prop('checked', false); if(! window.LIL_STORE.user.id) { @@ -185,7 +186,12 @@ $(document).ready(function () { }); } - if ($this.data('prolong')) { + if ($this.data('is-camp')) { + $('[data-day]').each(function(){ + $(this).prop('checked', true).prop('disabled', true); + }); + } + else if ($this.data('prolong')) { $('[data-day][data-purchased]').each(function(){ $(this).prop('checked', true).prop('disabled', false); }); @@ -410,7 +416,8 @@ $(document).ready(function () { if(weekdays.length){ $order.addClass('order--loading'); $orderPrice.html(''); - api.getPaymentAmount({ user: window.LIL_STORE.user.id, weekdays: weekdays, date_start: dateStart}) + api.getPaymentAmount({ user: window.LIL_STORE.user.id, weekdays: weekdays, date_start: dateStart, + is_camp: popup.data('is-camp')}) .then(response => { $order.removeClass('order--loading'); var text = '';