Merge remote-tracking branch 'origin/dev' into dev

remotes/origin/hasaccess
Vitaly Baev 8 years ago
commit 0590e7aa42
  1. 1
      .dockerignore
  2. 25
      api/v1/serializers/payment.py
  3. 2
      api/v1/serializers/user.py
  4. 2
      api/v1/urls.py
  5. 14
      api/v1/views.py
  6. 24
      apps/course/templates/course/course.html
  7. 10
      apps/course/views.py
  8. 60
      apps/payment/admin.py
  9. 16
      apps/payment/migrations/0002_delete_purchase.py
  10. 71
      apps/payment/migrations/0003_auto_20180220_1912.py
  11. 32
      apps/payment/migrations/0004_auto_20180221_1022.py
  12. 25
      apps/payment/migrations/0005_auto_20180221_1120.py
  13. 29
      apps/payment/migrations/0006_auto_20180221_1126.py
  14. 24
      apps/payment/migrations/0007_auto_20180221_1258.py
  15. 20
      apps/payment/migrations/0008_auto_20180221_1335.py
  16. 19
      apps/payment/migrations/0009_auto_20180222_0955.py
  17. 23
      apps/payment/migrations/0010_auto_20180227_0933.py
  18. 119
      apps/payment/models.py
  19. 12
      apps/payment/templates/payment/payment_error.html
  20. 12
      apps/payment/templates/payment/payment_success.html
  21. 9
      apps/payment/templates/payment/paymentwall_widget.html
  22. 153
      apps/payment/views.py
  23. 18
      apps/school/migrations/0004_auto_20180221_1120.py
  24. 18
      apps/school/migrations/0005_schoolschedule_day_discount.py
  25. 6
      apps/school/models.py
  26. 10
      apps/user/models.py
  27. 24
      apps/user/templates/user/payment-history.html
  28. 5
      apps/user/views.py
  29. 9
      project/settings.py
  30. 59
      project/templates/lilcity/index.html
  31. 129
      project/templates/lilcity/main.html
  32. 17
      project/urls.py
  33. 3
      requirements.txt

@ -0,0 +1 @@
venv*/

@ -0,0 +1,25 @@
from rest_framework import serializers
from apps.payment.models import AuthorBalance
class AuthorBalanceSerializer(serializers.ModelSerializer):
class Meta:
model = AuthorBalance
fields = (
'id',
'author',
'type',
'amount',
'commission',
'status',
'payment',
)
read_only_fields = (
'id',
'author',
'type',
'payment',
)

@ -35,6 +35,7 @@ class UserSerializer(serializers.ModelSerializer):
'fb_data', 'fb_data',
'is_email_proved', 'is_email_proved',
'photo', 'photo',
'balance',
) )
read_only_fields = ( read_only_fields = (
@ -44,6 +45,7 @@ class UserSerializer(serializers.ModelSerializer):
'is_staff', 'is_staff',
'fb_id', 'fb_id',
'fb_data', 'fb_data',
'balance',
) )

@ -8,6 +8,7 @@ from drf_yasg import openapi
from .auth import ObtainToken from .auth import ObtainToken
from .views import ( from .views import (
AuthorBalanceViewSet,
CategoryViewSet, CourseViewSet, CategoryViewSet, CourseViewSet,
MaterialViewSet, LikeViewSet, MaterialViewSet, LikeViewSet,
ImageViewSet, TextViewSet, ImageViewSet, TextViewSet,
@ -18,6 +19,7 @@ from .views import (
) )
router = DefaultRouter() router = DefaultRouter()
router.register(r'author-balance', AuthorBalanceViewSet, base_name='author-balance')
router.register(r'courses', CourseViewSet, base_name='courses') router.register(r'courses', CourseViewSet, base_name='courses')
router.register(r'categories', CategoryViewSet, base_name='categories') router.register(r'categories', CategoryViewSet, base_name='categories')
router.register(r'materials', MaterialViewSet, base_name='materials') router.register(r'materials', MaterialViewSet, base_name='materials')

@ -24,6 +24,7 @@ from .serializers.content import (
ImageObjectSerializer, ImageObjectSerializer,
) )
from .serializers.school import SchoolScheduleSerializer from .serializers.school import SchoolScheduleSerializer
from .serializers.payment import AuthorBalanceSerializer
from .serializers.user import ( from .serializers.user import (
UserSerializer, UserPhotoSerializer, UserSerializer, UserPhotoSerializer,
) )
@ -35,10 +36,23 @@ from apps.content.models import (
Image, Text, ImageText, Video, Image, Text, ImageText, Video,
Gallery, GalleryImage, ImageObject, Gallery, GalleryImage, ImageObject,
) )
from apps.payment.models import AuthorBalance
from apps.school.models import SchoolSchedule from apps.school.models import SchoolSchedule
User = get_user_model() User = get_user_model()
class AuthorBalanceViewSet(ExtendedModelViewSet):
queryset = AuthorBalance.objects.all()
serializer_class = AuthorBalanceSerializer
permission_classes = (IsAdmin,)
filter_fields = ('status', 'type')
search_fields = (
'author__email',
'author__first_name',
'author__last_name',
)
class ImageObjectViewSet(ExtendedModelViewSet): class ImageObjectViewSet(ExtendedModelViewSet):
queryset = ImageObject.objects.all() queryset = ImageObject.objects.all()
serializer_class = ImageObjectSerializer serializer_class = ImageObjectSerializer

@ -25,14 +25,18 @@
</div> </div>
<div class="go__title">Вернуться</div> <div class="go__title">Вернуться</div>
</a> </a>
<button {% if not paid %}
class="go__btn btn btn_md" <a
class="go__btn btn{% if pending %} btn_gray{% endif %} btn_md"
{% if user.is_authenticated %} {% if user.is_authenticated %}
data-popup=".js-popup-buy" {% if not pending %}
href="{% url 'course-checkout' course.id %}"
{% endif %}
{% else %} {% else %}
data-popup=".js-popup-auth" data-popup=".js-popup-auth"
{% endif %} {% endif %}
>КУПИТЬ КУРС</button> >{% if pending %}ОЖИДАЕТСЯ ПОДТВЕРЖДЕНИЕ ОПЛАТЫ{% else %}КУПИТЬ КУРС{% endif %}</a>
{% endif %}
</div> </div>
<div <div
class="course" class="course"
@ -383,14 +387,18 @@
<div class="meta__title">{{ course.price|floatformat:"-2" }}₽</div> <div class="meta__title">{{ course.price|floatformat:"-2" }}₽</div>
</div> </div>
</div> </div>
<button {% if not paid %}
class="course__buy btn btn_md" <a
class="go__btn btn{% if pending %} btn_gray{% endif %} btn_md"
{% if user.is_authenticated %} {% if user.is_authenticated %}
data-popup=".js-popup-buy" {% if not pending %}
href="{% url 'course-checkout' course.id %}"
{% endif %}
{% else %} {% else %}
data-popup=".js-popup-auth" data-popup=".js-popup-auth"
{% endif %} {% endif %}
>КУПИТЬ КУРС</button> >{% if pending %}ОЖИДАЕТСЯ ПОДТВЕРЖДЕНИЕ ОПЛАТЫ{% else %}КУПИТЬ КУРС{% endif %}</a>
{% endif %}
</div> </div>
</div> </div>
</div> </div>

@ -8,6 +8,8 @@ from django.views.generic import View, CreateView, DetailView, ListView, Templat
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods from django.views.decorators.http import require_http_methods
from apps.payment.models import AuthorBalance
from .models import Course, Like, Lesson, CourseComment, LessonComment from .models import Course, Like, Lesson, CourseComment, LessonComment
from .filters import CourseFilter from .filters import CourseFilter
@ -187,6 +189,14 @@ class CourseView(DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['next'] = self.request.GET.get('next', None) context['next'] = self.request.GET.get('next', None)
context['paid'] = self.object.payments.filter(
user=self.request.user,
authorbalance__status=AuthorBalance.ACCEPTED,
).exists()
context['pending'] = self.object.payments.filter(
user=self.request.user,
authorbalance__status=AuthorBalance.PENDING,
).exists()
return context return context
def get_queryset(self): def get_queryset(self):

@ -1,3 +1,61 @@
from django.contrib import admin from django.contrib import admin
from polymorphic.admin import (
PolymorphicParentModelAdmin,
PolymorphicChildModelAdmin,
PolymorphicChildModelFilter,
)
# Register your models here. from .models import AuthorBalance, CoursePayment, SchoolPayment, Payment
@admin.register(AuthorBalance)
class AuthorBalanceAdmin(admin.ModelAdmin):
list_display = (
'author',
'type',
'amount',
'commission',
'status',
'payment',
)
class PaymentChildAdmin(PolymorphicChildModelAdmin):
base_model = Payment
show_in_index = True
list_display = (
'id',
'user',
'amount',
'status',
)
base_fieldsets = (
(None, {'fields': ('user', 'amount', 'status', 'data',)}),
)
readonly_fields = ('amount', 'status', 'data',)
@admin.register(CoursePayment)
class CoursePaymentAdmin(PaymentChildAdmin):
base_model = CoursePayment
list_display = PaymentChildAdmin.list_display + ('course',)
@admin.register(SchoolPayment)
class SchoolPaymentAdmin(PaymentChildAdmin):
base_model = SchoolPayment
list_display = PaymentChildAdmin.list_display + (
'weekdays',
'date_start',
'date_end',
)
@admin.register(Payment)
class PaymentAdmin(PolymorphicParentModelAdmin):
base_model = Payment
polymorphic_list = True
child_models = (
CoursePayment,
SchoolPayment,
)

@ -0,0 +1,16 @@
# Generated by Django 2.0.2 on 2018-02-20 17:37
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('payment', '0001_initial'),
]
operations = [
migrations.DeleteModel(
name='Purchase',
),
]

@ -0,0 +1,71 @@
# Generated by Django 2.0.2 on 2018-02-20 19:12
from django.conf import settings
import django.contrib.postgres.fields
import django.contrib.postgres.fields.jsonb
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('course', '0034_auto_20180215_1503'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('contenttypes', '0002_remove_content_type_name'),
('payment', '0002_delete_purchase'),
]
operations = [
migrations.CreateModel(
name='Payment',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('amount', models.DecimalField(decimal_places=2, default=0, max_digits=8, verbose_name='Итого')),
('status', models.PositiveSmallIntegerField(choices=[(0, 'regular'), (1, 'goodwill'), (2, 'negative'), (200, 'risk under review'), (201, 'risk reviewed accepted'), (202, 'risk reviewed declined'), (203, 'risk authorization voided'), (12, 'subscription cancelation'), (13, 'subscription expired'), (14, 'subscription payment failed')], verbose_name='Статус платежа')),
('data', django.contrib.postgres.fields.jsonb.JSONField(default={}, verbose_name='Данные платежа от провайдера')),
],
options={
'verbose_name': 'Платеж',
'verbose_name_plural': 'Платежи',
},
),
migrations.CreateModel(
name='CoursePayment',
fields=[
('payment_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='payment.Payment')),
('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='course.Course', verbose_name='Курс')),
],
options={
'verbose_name': 'Платеж за курс',
'verbose_name_plural': 'Платежи за курсы',
},
bases=('payment.payment',),
),
migrations.CreateModel(
name='SchoolPayment',
fields=[
('payment_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='payment.Payment')),
('weekdays', django.contrib.postgres.fields.ArrayField(base_field=models.IntegerField(), size=7, verbose_name='Дни недели')),
('date_start', models.DateField(verbose_name='Дата начала подписки')),
('date_end', models.DateField(verbose_name='Дата окончания подписки')),
],
options={
'verbose_name': 'Платеж за курс',
'verbose_name_plural': 'Платежи за курсы',
},
bases=('payment.payment',),
),
migrations.AddField(
model_name='payment',
name='polymorphic_ctype',
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_payment.payment_set+', to='contenttypes.ContentType'),
),
migrations.AddField(
model_name='payment',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Пользователь'),
),
]

@ -0,0 +1,32 @@
# Generated by Django 2.0.2 on 2018-02-21 10:22
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),
('payment', '0003_auto_20180220_1912'),
]
operations = [
migrations.CreateModel(
name='AuthorBalance',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('type', models.PositiveSmallIntegerField(choices=[(0, 'In'), (1, 'Out')], default=0, verbose_name='Тип')),
('amount', models.DecimalField(decimal_places=2, default=0, max_digits=8, verbose_name='Итого')),
('commission', models.DecimalField(decimal_places=2, default=0, max_digits=8, verbose_name='Комиссия')),
('status', models.PositiveSmallIntegerField(choices=[(0, 'Pending'), (1, 'Accepted'), (2, 'Declined')], default=0, verbose_name='Статус')),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Автор')),
('payment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='payment.Payment', verbose_name='Платёж')),
],
),
migrations.AlterModelOptions(
name='schoolpayment',
options={'verbose_name': 'Платеж за школу', 'verbose_name_plural': 'Платежи за школу'},
),
]

@ -0,0 +1,25 @@
# Generated by Django 2.0.2 on 2018-02-21 11:20
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('payment', '0004_auto_20180221_1022'),
]
operations = [
migrations.AlterField(
model_name='authorbalance',
name='author',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Автор'),
),
migrations.AlterField(
model_name='authorbalance',
name='payment',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='payment.Payment', verbose_name='Платёж'),
),
]

@ -0,0 +1,29 @@
# Generated by Django 2.0.2 on 2018-02-21 11:26
import django.contrib.postgres.fields.jsonb
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('payment', '0005_auto_20180221_1120'),
]
operations = [
migrations.AlterField(
model_name='payment',
name='amount',
field=models.DecimalField(decimal_places=2, default=0, editable=False, max_digits=8, verbose_name='Итого'),
),
migrations.AlterField(
model_name='payment',
name='data',
field=django.contrib.postgres.fields.jsonb.JSONField(default={}, editable=False, verbose_name='Данные платежа от провайдера'),
),
migrations.AlterField(
model_name='payment',
name='status',
field=models.PositiveSmallIntegerField(choices=[(0, 'regular'), (1, 'goodwill'), (2, 'negative'), (200, 'risk under review'), (201, 'risk reviewed accepted'), (202, 'risk reviewed declined'), (203, 'risk authorization voided'), (12, 'subscription cancelation'), (13, 'subscription expired'), (14, 'subscription payment failed')], editable=False, null=True, verbose_name='Статус платежа'),
),
]

@ -0,0 +1,24 @@
# Generated by Django 2.0.2 on 2018-02-21 12:58
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('payment', '0006_auto_20180221_1126'),
]
operations = [
migrations.AlterModelOptions(
name='authorbalance',
options={'verbose_name': 'Баланс', 'verbose_name_plural': 'Балансы'},
),
migrations.AlterField(
model_name='authorbalance',
name='author',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='balances', to=settings.AUTH_USER_MODEL, verbose_name='Автор'),
),
]

@ -0,0 +1,20 @@
# Generated by Django 2.0.2 on 2018-02-21 13:35
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('payment', '0007_auto_20180221_1258'),
]
operations = [
migrations.AlterField(
model_name='payment',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to=settings.AUTH_USER_MODEL, verbose_name='Пользователь'),
),
]

@ -0,0 +1,19 @@
# Generated by Django 2.0.2 on 2018-02-22 09:55
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('payment', '0008_auto_20180221_1335'),
]
operations = [
migrations.AlterField(
model_name='coursepayment',
name='course',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to='course.Course', verbose_name='Курс'),
),
]

@ -0,0 +1,23 @@
# Generated by Django 2.0.2 on 2018-02-27 09:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('payment', '0009_auto_20180222_0955'),
]
operations = [
migrations.AlterField(
model_name='schoolpayment',
name='date_end',
field=models.DateField(blank=True, null=True, verbose_name='Дата окончания подписки'),
),
migrations.AlterField(
model_name='schoolpayment',
name='date_start',
field=models.DateField(blank=True, null=True, verbose_name='Дата начала подписки'),
),
]

@ -1,15 +1,112 @@
from django.db import models from django.db import models
from django.contrib.auth import get_user_model
from django.contrib.postgres.fields import ArrayField, JSONField
from constance import config
from paymentwall import Pingback
from polymorphic.models import PolymorphicModel
class Purchase(models.Model): from apps.course.models import Course
COMPLETE = 'COMPLETE' from apps.school.models import SchoolSchedule
CHARGEBACK = 'CHARGEBACK'
REFUNDED = 'REFUNDED'
ERROR = 'ERROR'
PENDING = 'PENDING'
transaction_id = models.PositiveIntegerField() User = get_user_model()
status = models.CharField(max_length=50)
product_id = models.PositiveIntegerField()
created_at = models.DateTimeField(auto_now_add=True) class AuthorBalance(models.Model):
updated_at = models.DateTimeField(auto_now=True) IN = 0
OUT = 1
TYPE_CHOICES = (
(IN, 'In'),
(OUT, 'Out'),
)
PENDING = 0
ACCEPTED = 1
DECLINED = 2
STATUS_CHOICES = (
(PENDING, 'Pending'),
(ACCEPTED, 'Accepted'),
(DECLINED, 'Declined'),
)
author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='Автор',
null=True, blank=True, related_name='balances')
type = models.PositiveSmallIntegerField('Тип', choices=TYPE_CHOICES, default=0)
amount = models.DecimalField('Итого', max_digits=8, decimal_places=2, default=0)
commission = models.DecimalField('Комиссия', max_digits=8, decimal_places=2, default=0)
status = models.PositiveSmallIntegerField('Статус', choices=STATUS_CHOICES, default=0)
payment = models.OneToOneField('Payment', on_delete=models.CASCADE, null=True, blank=True, verbose_name='Платёж')
class Meta:
verbose_name = 'Баланс'
verbose_name_plural = 'Балансы'
class Payment(PolymorphicModel):
PW_STATUS_CHOICES = (
(Pingback.PINGBACK_TYPE_REGULAR, 'regular',),
(Pingback.PINGBACK_TYPE_GOODWILL, 'goodwill',),
(Pingback.PINGBACK_TYPE_NEGATIVE, 'negative',),
(Pingback.PINGBACK_TYPE_RISK_UNDER_REVIEW, 'risk under review',),
(Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED, 'risk reviewed accepted',),
(Pingback.PINGBACK_TYPE_RISK_REVIEWED_DECLINED, 'risk reviewed declined',),
(Pingback.PINGBACK_TYPE_RISK_AUTHORIZATION_VOIDED, 'risk authorization voided',),
(Pingback.PINGBACK_TYPE_SUBSCRIPTION_CANCELLATION, 'subscription cancelation',),
(Pingback.PINGBACK_TYPE_SUBSCRIPTION_EXPIRED, 'subscription expired',),
(Pingback.PINGBACK_TYPE_SUBSCRIPTION_PAYMENT_FAILED, 'subscription payment failed',),
)
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='Пользователь', related_name='payments')
amount = models.DecimalField('Итого', max_digits=8, decimal_places=2, default=0, editable=False)
status = models.PositiveSmallIntegerField('Статус платежа', choices=PW_STATUS_CHOICES, null=True, editable=False)
data = JSONField('Данные платежа от провайдера', default={}, editable=False)
class Meta:
verbose_name = 'Платеж'
verbose_name_plural = 'Платежи'
def calc_commission(self):
return self.amount * config.SERVICE_COMMISSION / 100
class CoursePayment(Payment):
course = models.ForeignKey(Course, on_delete=models.CASCADE, verbose_name='Курс', related_name='payments')
class Meta:
verbose_name = 'Платеж за курс'
verbose_name_plural = 'Платежи за курсы'
def save(self, *args, **kwargs):
self.amount = self.course.price
super().save(*args, **kwargs)
author_balance = getattr(self, 'authorbalance', None)
if not author_balance:
AuthorBalance.objects.create(
author=self.course.author,
amount=self.amount,
payment=self,
commission=self.calc_commission(),
)
else:
author_balance.amount = self.amount
author_balance.commission = self.calc_commission()
author_balance.save()
class SchoolPayment(Payment):
weekdays = ArrayField(models.IntegerField(), size=7, verbose_name='Дни недели')
date_start = models.DateField('Дата начала подписки', null=True, blank=True)
date_end = models.DateField('Дата окончания подписки', null=True, blank=True)
class Meta:
verbose_name = 'Платеж за школу'
verbose_name_plural = 'Платежи за школу'
def save(self, *args, **kwargs):
aggregate = SchoolSchedule.objects.filter(
weekday__in=self.weekdays,
).aggregate(
models.Sum('month_price'),
models.Sum('day_discount'),
)
month_price_sum = aggregate.get('month_price__sum', 0)
day_discount_sum = aggregate.get('day_discount__sum', 0) if len(self.weekdays) == 7 else 0
self.amount = month_price_sum - day_discount_sum
super().save(*args, **kwargs)

@ -0,0 +1,12 @@
{% extends "templates/lilcity/index.html" %} {% load static %} {% block content %}
<div class="section">
<div class="section__center center center_xs">
<div class="done">
<div class="done__title title">Произошла ошибка!</div>
<div class="done__foot">
<a class="done__btn btn btn_md btn_stroke" href="/">ПЕРЕЙТИ К ГЛАВНОЙ</a>
</div>
</div>
</div>
</div>
{% endblock content %}

@ -0,0 +1,12 @@
{% extends "templates/lilcity/index.html" %} {% load static %} {% block content %}
<div class="section">
<div class="section__center center center_xs">
<div class="done">
<div class="done__title title">Вы успешно приобрели курс!</div>
<div class="done__foot">
<a class="done__btn btn btn_md btn_stroke" href="/">ПЕРЕЙТИ К ГЛАВНОЙ</a>
</div>
</div>
</div>
</div>
{% endblock content %}

@ -0,0 +1,9 @@
{% extends "templates/lilcity/index.html" %} {% load static %} {% block content %}
<div class="section">
<div class="section__center center">
{% autoescape off %}
{{ widget }}
{% endautoescape %}
</div>
</div>
{% endblock content %}

@ -1,27 +1,92 @@
from django.utils.decorators import method_decorator from django.contrib import messages
from django.views.generic import View
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import redirect
from django.views.generic import View, TemplateView
from django.views.decorators.csrf import csrf_exempt
from django.urls import reverse_lazy
from django.utils.decorators import method_decorator
from paymentwall import Pingback, Product, Widget
from apps.course.models import Course
from apps.school.models import SchoolSchedule
from paymentwall.pingback import Pingback from .models import AuthorBalance, CoursePayment, SchoolPayment
from .models import Purchase
class CourseBuyView(TemplateView):
template_name = 'payment/paymentwall_widget.html'
def get(self, request, pk=None, *args, **kwargs):
host = request.scheme + '://' + request.get_host()
course = Course.objects.get(id=pk)
course_payment = CoursePayment.objects.create(
user=request.user,
course=course,
)
product = Product(
f'course_{course_payment.id}',
course.price,
'RUB',
f'Курс "{course.title}"',
)
widget = Widget(
str(request.user.id),
'p1',
[product],
extra_params={
'lang': 'ru',
'evaluation': 1,
'success_url': host + str(reverse_lazy('payment-success')),
'failure_url': host + str(reverse_lazy('payment-error')),
}
)
return self.render_to_response(context={'widget': widget.get_html_code()})
class SchoolBuyView(TemplateView):
template_name = 'payment/paymentwall_widget.html'
def get(self, request, *args, **kwargs):
host = request.scheme + '://' + request.get_host()
weekdays = set(request.GET.getlist('weekdays', []))
if not weekdays:
messages.error(request, 'Выберите несколько дней недели.')
return redirect('index')
try:
weekdays = [int(weekday) for weekday in weekdays]
except ValueError:
messages.error(request, 'Ошибка выбора дней недели.')
return redirect('index')
school_payment = SchoolPayment.objects.create(
user=request.user,
weekdays=weekdays,
)
product = Product(
f'school_{school_payment.id}',
school_payment.amount,
'RUB',
'Школа',
)
widget = Widget(
str(request.user.id),
'p1',
[product],
extra_params={
'lang': 'ru',
'evaluation': 1,
'success_url': host + str(reverse_lazy('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') @method_decorator(csrf_exempt, name='dispatch')
class PaymentwallCallbackView(View): class PaymentwallCallbackView(View):
CHARGEBACK = '1'
CREDIT_CARD_FRAUD = '2'
ORDER_FRAUD = '3'
BAD_DATA = '4'
FAKE_PROXY_USER = '5'
REJECTED_BY_ADVERTISER = '6'
DUPLICATED_CONVERSIONS = '7'
GOODWILL_CREDIT_TAKEN_BACK = '8'
CANCELLED_ORDER = '9'
PARTIALLY_REVERSED = '10'
def get_request_ip(self): def get_request_ip(self):
x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR') x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for: if x_forwarded_for:
@ -31,46 +96,38 @@ class PaymentwallCallbackView(View):
return ip return ip
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
pingback = Pingback(request.GET.copy(), self.get_request_ip()) payment_raw_data = request.GET.copy()
pingback = Pingback(payment_raw_data, self.get_request_ip())
if pingback.validate(): if pingback.validate():
cart_id = pingback.get_product().get_id() product_type_name, payment_id = pingback.get_product().get_id().split('_')
# try: if product_type_name == 'course':
# cart = CartModel.objects.get(pk=cart_id) product_payment_class = CoursePayment
# except CartModel.DoesNotExist: elif product_type_name == 'school':
# log.error('Paymentwall pingback: Cant find cart, Paymentwall sent this data: {}'.format(request.GET.copy())) product_payment_class = SchoolPayment
# return HttpResponse(status=403) else:
return HttpResponse(status=403)
try: try:
purchase = Purchase.objects.get(transaction_id=pingback.get_reference_id()) payment = product_payment_class.objects.get(pk=payment_id)
except Purchase.DoesNotExist: except product_payment_class.DoesNotExist:
# purchase = cart.create_purchase(transaction_id=pingback.get_reference_id()) return HttpResponse(status=403)
pass
if pingback.is_deliverable():
purchase.status = Purchase.COMPLETE
elif pingback.is_cancelable(): payment.status = pingback.get_type()
reason = pingback.get_parameter('reason') payment.data = payment_raw_data
payment.save()
if reason == self.CHARGEBACK or reason == self.CREDIT_CARD_FRAUD or reason == self.ORDER_FRAUD or reason == self.PARTIALLY_REVERSED:
purchase.status = Purchase.CHARGEBACK
elif reason == self.CANCELLED_ORDER:
purchase.status = Purchase.REFUNDED
else:
purchase.status = Purchase.ERROR
author_balance = getattr(payment, 'author_balance', None)
if author_balance:
if pingback.is_deliverable():
payment.author_balance.status = AuthorBalance.ACCEPTED
elif pingback.is_under_review(): elif pingback.is_under_review():
purchase.status = Purchase.PENDING payment.author_balance.status = AuthorBalance.PENDING
else: else:
# log.error('Paymentwall pingback: Unknown pingback type, Paymentwall sent this data: {}'.format(request.GET.copy())) payment.author_balance.status = AuthorBalance.DECLINED
pass
# purchase.save() payment.author_balance.save()
return HttpResponse('OK', status=200) return HttpResponse('OK', status=403)
else: else:
# log.error('Paymentwall pingback: Cant validate pingback, error: {} Paymentwall sent this data: {}'.format(pingback.get_error_summary(), request.GET.copy())) return
pass

@ -0,0 +1,18 @@
# Generated by Django 2.0.2 on 2018-02-21 11:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('school', '0003_auto_20180221_0901'),
]
operations = [
migrations.AlterField(
model_name='schoolschedule',
name='weekday',
field=models.PositiveSmallIntegerField(choices=[(1, 'понедельник'), (2, 'вторник'), (3, 'среда'), (4, 'четверг'), (5, 'пятница'), (6, 'суббота'), (7, 'воскресенье')], unique=True, verbose_name='День недели'),
),
]

@ -0,0 +1,18 @@
# Generated by Django 2.0.2 on 2018-02-22 10:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('school', '0004_auto_20180221_1120'),
]
operations = [
migrations.AddField(
model_name='schoolschedule',
name='day_discount',
field=models.DecimalField(decimal_places=2, default=0, max_digits=8, verbose_name='Скидка, в валюте'),
),
]

@ -11,14 +11,18 @@ class SchoolSchedule(models.Model):
(6, 'суббота'), (6, 'суббота'),
(7, 'воскресенье'), (7, 'воскресенье'),
) )
weekday = models.PositiveSmallIntegerField('День недели', choices=WEEKDAY_CHOICES) weekday = models.PositiveSmallIntegerField('День недели', choices=WEEKDAY_CHOICES, unique=True)
title = models.CharField('Заголовок', default='', max_length=100, db_index=True) title = models.CharField('Заголовок', default='', max_length=100, db_index=True)
description = models.TextField('Описание') description = models.TextField('Описание')
materials = models.TextField('Материалы') materials = models.TextField('Материалы')
age = models.PositiveSmallIntegerField('Возраст', default=0) age = models.PositiveSmallIntegerField('Возраст', default=0)
month_price = models.DecimalField('Цена', max_digits=8, decimal_places=2, default=0) month_price = models.DecimalField('Цена', max_digits=8, decimal_places=2, default=0)
day_discount = models.DecimalField('Скидка, в валюте', max_digits=8, decimal_places=2, default=0)
class Meta: class Meta:
ordering = ('weekday',) ordering = ('weekday',)
verbose_name = 'Расписание' verbose_name = 'Расписание'
verbose_name_plural = 'Расписания' verbose_name_plural = 'Расписания'
def __str__(self):
return dict(self.WEEKDAY_CHOICES).get(self.weekday, '')

@ -61,6 +61,16 @@ class User(AbstractUser):
user_data = dumps(user_data, ensure_ascii=False) user_data = dumps(user_data, ensure_ascii=False)
return user_data return user_data
@property
def balance(self):
aggregate = self.balances.aggregate(
models.Sum('amount'),
models.Sum('commission'),
)
amount = aggregate.get('amount__sum') or 0
commission = aggregate.get('commission__sum') or 0
return amount - commission
@receiver(post_save, sender=User) @receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs): def create_auth_token(sender, instance=None, created=False, **kwargs):

@ -27,11 +27,11 @@
<div class="title title_sm">Вывести деньги со счета</div> <div class="title title_sm">Вывести деньги со счета</div>
<div class="form"> <div class="form">
<div class="form__group"> <div class="form__group">
<div class="form__content">На вашем счету 20. 123 рублей</div> <div class="form__content">На вашем счету {{ request.user.balance }} руб.</div>
<div class="form__field field"> <div class="form__field field">
<div class="field__label">СУММА</div> <div class="field__label">СУММА</div>
<div class="field__wrap"> <div class="field__wrap">
<input class="field__input" type="text" placeholder="12. 000"> <input class="field__input" type="text" placeholder="{{ request.user.balance }}">
</div> </div>
<div class="field__error">Размер выводимой суммы не должно быть менее 2000 рублей.</div> <div class="field__error">Размер выводимой суммы не должно быть менее 2000 рублей.</div>
</div> </div>
@ -58,21 +58,23 @@
<div class="title title_sm">История платежей</div> <div class="title title_sm">История платежей</div>
<div class="transactions"> <div class="transactions">
<div class="transactions__wrap"> <div class="transactions__wrap">
{% if request.user.role == 1 or request.user.role == 2 %}
{% for balance in request.user.balances.all %}
<div class="transactions__row"> <div class="transactions__row">
<div class="transactions__cell">Ноябрь. Школа Lil City</div> <div class="transactions__cell">{{balance.payment.course.title}}</div>
<div class="transactions__cell">2000.00</div> <div class="transactions__cell">{{balance.amount}}</div>
<div class="transactions__cell">Получено</div> <div class="transactions__cell">Получено</div>
</div> </div>
{% endfor %}
{% else %}
{% for payment in request.user.payments.all %}
<div class="transactions__row"> <div class="transactions__row">
<div class="transactions__cell">Общий курс по иллюстрации</div> <div class="transactions__cell">{{payment.course.title}}</div>
<div class="transactions__cell">2000.00</div> <div class="transactions__cell">{{payment.amount}}</div>
<div class="transactions__cell">Получено</div>
</div>
<div class="transactions__row">
<div class="transactions__cell">Ноябрь. Школа Lil City</div>
<div class="transactions__cell">2000.00</div>
<div class="transactions__cell">Получено</div> <div class="transactions__cell">Получено</div>
</div> </div>
{% endfor %}
{% endif %}
</div> </div>
<div class="transactions__load load"> <div class="transactions__load load">
<button class="load__btn btn">еще</button> <button class="load__btn btn">еще</button>

@ -14,6 +14,7 @@ from django.utils.decorators import method_decorator
from apps.auth.tokens import verification_email_token from apps.auth.tokens import verification_email_token
from apps.course.models import Course from apps.course.models import Course
from apps.payment.models import CoursePayment
from apps.notification.utils import send_email from apps.notification.utils import send_email
from .forms import UserEditForm from .forms import UserEditForm
@ -45,7 +46,9 @@ class UserView(DetailView):
context['drafts'] = Course.objects.filter( context['drafts'] = Course.objects.filter(
author=self.object, status=Course.DRAFT author=self.object, status=Course.DRAFT
) )
context['paid'] = Course.objects.none() context['paid'] = Course.objects.filter(
payments__in=CoursePayment.objects.filter(user=self.object),
).distinct()
return context return context

@ -224,6 +224,7 @@ CONSTANCE_CONFIG = OrderedDict((
('INSTAGRAM_CLIENT_PASSWORD', ('', '')), ('INSTAGRAM_CLIENT_PASSWORD', ('', '')),
('INSTAGRAM_RESULTS_TAG', ('#lil_акварель', 'Тэг результатов работ.')), ('INSTAGRAM_RESULTS_TAG', ('#lil_акварель', 'Тэг результатов работ.')),
('INSTAGRAM_RESULTS_PATH', ('media/instagram/results/', 'Путь до результатов работ.')), ('INSTAGRAM_RESULTS_PATH', ('media/instagram/results/', 'Путь до результатов работ.')),
('SERVICE_COMMISSION', (10, 'Комиссия сервиса в процентах.'))
)) ))
try: try:
@ -231,6 +232,14 @@ try:
except ImportError: except ImportError:
pass pass
try:
from paymentwall import *
except ImportError:
pass
else:
Paymentwall.set_api_type(Paymentwall.API_GOODS)
Paymentwall.set_app_key('d6f02b90cf6b16220932f4037578aff7')
Paymentwall.set_secret_key('4ea515bf94e34cf28646c2e12a7b8707')
# CORS settings # CORS settings

@ -136,9 +136,9 @@
<div class="header__ava ava"><img class="ava__pic" src="{% static 'img/user.jpg' %}"></div> <div class="header__ava ava"><img class="ava__pic" src="{% static 'img/user.jpg' %}"></div>
{% endif %} {% endif %}
<div class="header__drop"> <div class="header__drop">
{% comment %} <a class="header__link header__link_border" href="#">234.120.345 руб.</a> {% endcomment %}
{% if request.user.auth_token %}
{% if request.user.role == 1 or request.user.role == 2 %} {% if request.user.role == 1 or request.user.role == 2 %}
<a class="header__link header__link_border" href="{% url 'user-edit-payments' request.user.id %}">{{ request.user.balance }} руб.</a>
{% if request.user.auth_token %}
<a class="header__link header__link_green" href="{% url 'course_create' %}"> <a class="header__link header__link_green" href="{% url 'course_create' %}">
{% comment %} <a class="header__link header__link_gray disabled" href="#"> {% endcomment %} {% comment %} <a class="header__link header__link_gray disabled" href="#"> {% endcomment %}
<div class="header__title">ДОБАВИТЬ КУРС</div> <div class="header__title">ДОБАВИТЬ КУРС</div>
@ -424,46 +424,32 @@
<div class="buy__col"> <div class="buy__col">
<div class="buy__head buy__head_main"> <div class="buy__head buy__head_main">
<div class="buy__title">Выбор урока/дня</div> <div class="buy__title">Выбор урока/дня</div>
<div class="buy__content">При записи на 5 уроков скидка 10%.</div> <!-- <div class="buy__content">При записи на 5 уроков скидка 10%.</div> -->
</div> </div>
</div> </div>
<div class="buy__col"> <div class="buy__col">
<div class="buy__head"> <div class="buy__head">
<div class="buy__label">Месяц:</div> <!-- <div class="buy__label">Месяц:</div>
<div class="buy__title">Январь</div> <div class="buy__title">Январь</div> -->
<div class="buy__content">Если вы оплачиваете после 15 числа, доступ к урокам будет с 1-го следующего <!-- <div class="buy__content">Если вы оплачиваете после 15 числа, доступ к урокам будет с 1-го следующего
месяца. месяца.
</div> </div> -->
</div> </div>
</div> </div>
<div class="buy__col"> <div class="buy__col">
<div class="buy__list"><label class="switch switch_lesson"><input class="switch__input" <div class="buy__list">
type="checkbox"><span {% for school_schedule in school_schedules %}
class="switch__content"><span class="switch__cell">ПОНЕДЕЛЬНИК</span><span <label class="switch switch_lesson">
class="switch__cell">5+</span><span class="switch__cell">Персонаж</span><span class="switch__cell">600р</span></span></label> <input class="switch__input" type="checkbox">
<label <span class="switch__content">
class="switch switch_lesson"><input class="switch__input" type="checkbox" checked><span <span class="switch__cell">{{ school_schedule }}</span>
class="switch__content"><span class="switch__cell">Вторник</span><span {% comment %} dont delete {% endcomment %}
class="switch__cell">5+</span><span class="switch__cell">Пластилиновая живопись</span><span <span class="switch__cell"></span>
class="switch__cell">600р</span></span> <span class="switch__cell">{{ school_schedule.title }}</span>
</label><label class="switch switch_lesson"><input class="switch__input" type="checkbox"><span <span class="switch__cell">{{school_schedule.month_price}}р</span>
class="switch__content"><span class="switch__cell">Среда</span><span </span>
class="switch__cell">5+</span><span class="switch__cell">Персонаж</span><span class="switch__cell">600р</span></span></label> </label>
<label {% endfor %}
class="switch switch_lesson"><input class="switch__input" type="checkbox" checked><span
class="switch__content"><span class="switch__cell">Четверг</span><span
class="switch__cell">5+</span><span class="switch__cell">Персонаж</span><span class="switch__cell">600р</span></span>
</label><label class="switch switch_lesson"><input class="switch__input" type="checkbox"><span
class="switch__content"><span class="switch__cell">Пятница</span><span
class="switch__cell">5+</span><span class="switch__cell">Развитие креативного мышления</span><span
class="switch__cell">600р</span></span></label>
<label
class="switch switch_lesson"><input class="switch__input" type="checkbox"><span
class="switch__content"><span class="switch__cell">Суббота</span><span
class="switch__cell">7+</span><span class="switch__cell">Персонаж</span><span class="switch__cell">600р</span></span>
</label><label class="switch switch_lesson"><input class="switch__input" type="checkbox" checked><span
class="switch__content"><span class="switch__cell">Воскресенье</span><span
class="switch__cell">7+</span><span class="switch__cell">Персонаж</span><span class="switch__cell">600р</span></span></label>
</div> </div>
</div> </div>
<div class="buy__col"> <div class="buy__col">
@ -483,7 +469,10 @@
</div> </div>
</div> </div>
</div> </div>
<div class="buy__foot"><a class="buy__btn btn btn_md" href="#">ПЕРЕЙТИ К ОПЛАТЕ</a></div> <div class="buy__foot">
{% comment %}В ссылке, в параметре запроса weekdays, нужно указать выбранные дни недели{% endcomment %}
<a class="buy__btn btn btn_md" href="{% url 'school-checkout' %}?weekdays=1&weekdays=2">ПЕРЕЙТИ К ОПЛАТЕ</a>
</div>
</div> </div>
</div> </div>
</div> </div>

@ -5,9 +5,22 @@
<div class="main" style="background-image: url({% static 'img/bg-1.jpg' %});"> <div class="main" style="background-image: url({% static 'img/bg-1.jpg' %});">
<div class="main__center center"> <div class="main__center center">
<div class="main__title">Первая онлайн-школа креативного мышления для детей! 5+</div> <div class="main__title">Первая онлайн-школа креативного мышления для детей! 5+</div>
<a class="main__btn btn" href="#">КУПИТЬ ДОСТУП ОТ 2000р. в мес.</a> <a
data-popup=".js-popup-buy"
class="main__btn btn"
href="#"
>КУПИТЬ ДОСТУП ОТ 2000р. в мес.</a>
</div> </div>
</div> </div>
{% if messages %}
<div class="section section_gray section_menu">
<div class="section__center center center_xs">
{% for message in messages %}
<div class="message message_{{ message.tags }}">{{ message }}</div>
{% endfor %}
</div>
</div>
{% endif %}
<div class="section"> <div class="section">
<div class="section__center center"> <div class="section__center center">
<div class="text text_lg"> <div class="text text_lg">
@ -331,125 +344,23 @@
<div class="title title_center">Расписание</div> <div class="title title_center">Расписание</div>
</a> </a>
<div class="schedule"> <div class="schedule">
{% for school_schedule in school_schedules %}
<div class="schedule__item"> <div class="schedule__item">
<div class="schedule__day">Понедельник</div> <div class="schedule__day">{{ school_schedule }}</div>
<div class="schedule__wrap">
<div class="schedule__title">Персонаж.</div>
<div class="schedule__content">Учимся создавать персонажей из простых форм. Изучаем характеры и эмоции.</div>
<div class="schedule__toggle toggle">
<button class="toggle__head js-toggle-head">Материалы
<svg class="icon icon-arrow-down">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-arrow-down"></use>
</svg>
</button>
<div class="toggle__body">Cамое главное - иметь альбом или блокнот с пустыми страницами (без линий и клеток) плотной гладкой бумагой, формат
А4. Рисовать будем цветными карандашами, а также простым, мягкостью B2. Иногда пригодятся вырезки из журналов
и клей-карандаш.</div>
</div>
</div>
</div>
<div class="schedule__item">
<div class="schedule__day">Вторник</div>
<div class="schedule__wrap">
<div class="schedule__title">Персонаж.</div>
<div class="schedule__content">Учимся создавать персонажей из простых форм. Изучаем характеры и эмоции.</div>
<div class="schedule__toggle toggle">
<button class="toggle__head js-toggle-head">Материалы
<svg class="icon icon-arrow-down">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-arrow-down"></use>
</svg>
</button>
<div class="toggle__body">Cамое главное - иметь альбом или блокнот с пустыми страницами (без линий и клеток) плотной гладкой бумагой, формат
А4. Рисовать будем цветными карандашами, а также простым, мягкостью B2. Иногда пригодятся вырезки из журналов
и клей-карандаш.</div>
</div>
</div>
</div>
<div class="schedule__item">
<div class="schedule__day">Среда</div>
<div class="schedule__wrap">
<div class="schedule__title">Персонаж.</div>
<div class="schedule__content">Учимся создавать персонажей из простых форм. Изучаем характеры и эмоции.</div>
<div class="schedule__toggle toggle">
<button class="toggle__head js-toggle-head">Материалы
<svg class="icon icon-arrow-down">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-arrow-down"></use>
</svg>
</button>
<div class="toggle__body">Cамое главное - иметь альбом или блокнот с пустыми страницами (без линий и клеток) плотной гладкой бумагой, формат
А4. Рисовать будем цветными карандашами, а также простым, мягкостью B2. Иногда пригодятся вырезки из журналов
и клей-карандаш.</div>
</div>
</div>
</div>
<div class="schedule__item">
<div class="schedule__day">Четверг</div>
<div class="schedule__wrap">
<div class="schedule__title">Персонаж.</div>
<div class="schedule__content">Учимся создавать персонажей из простых форм. Изучаем характеры и эмоции.</div>
<div class="schedule__toggle toggle">
<button class="toggle__head js-toggle-head">Материалы
<svg class="icon icon-arrow-down">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-arrow-down"></use>
</svg>
</button>
<div class="toggle__body">Cамое главное - иметь альбом или блокнот с пустыми страницами (без линий и клеток) плотной гладкой бумагой, формат
А4. Рисовать будем цветными карандашами, а также простым, мягкостью B2. Иногда пригодятся вырезки из журналов
и клей-карандаш.</div>
</div>
</div>
</div>
<div class="schedule__item">
<div class="schedule__day">Пятница</div>
<div class="schedule__wrap">
<div class="schedule__title">Персонаж.</div>
<div class="schedule__content">Учимся создавать персонажей из простых форм. Изучаем характеры и эмоции.</div>
<div class="schedule__toggle toggle">
<button class="toggle__head js-toggle-head">Материалы
<svg class="icon icon-arrow-down">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-arrow-down"></use>
</svg>
</button>
<div class="toggle__body">Cамое главное - иметь альбом или блокнот с пустыми страницами (без линий и клеток) плотной гладкой бумагой, формат
А4. Рисовать будем цветными карандашами, а также простым, мягкостью B2. Иногда пригодятся вырезки из журналов
и клей-карандаш.</div>
</div>
</div>
</div>
<div class="schedule__item">
<div class="schedule__day">Суббота</div>
<div class="schedule__wrap">
<div class="schedule__title">Персонаж.</div>
<div class="schedule__content">Учимся создавать персонажей из простых форм. Изучаем характеры и эмоции.</div>
<div class="schedule__toggle toggle">
<button class="toggle__head js-toggle-head">Материалы
<svg class="icon icon-arrow-down">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-arrow-down"></use>
</svg>
</button>
<div class="toggle__body">Cамое главное - иметь альбом или блокнот с пустыми страницами (без линий и клеток) плотной гладкой бумагой, формат
А4. Рисовать будем цветными карандашами, а также простым, мягкостью B2. Иногда пригодятся вырезки из журналов
и клей-карандаш.</div>
</div>
</div>
</div>
<div class="schedule__item">
<div class="schedule__day">Воскресенье</div>
<div class="schedule__wrap"> <div class="schedule__wrap">
<div class="schedule__title">Персонаж.</div> <div class="schedule__title">{{ school_schedule.title }}</div>
<div class="schedule__content">Учимся создавать персонажей из простых форм. Изучаем характеры и эмоции.</div> <div class="schedule__content">{{ school_schedule.description }}</div>
<div class="schedule__toggle toggle"> <div class="schedule__toggle toggle">
<button class="toggle__head js-toggle-head">Материалы <button class="toggle__head js-toggle-head">Материалы
<svg class="icon icon-arrow-down"> <svg class="icon icon-arrow-down">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-arrow-down"></use> <use xlink:href="{% static 'img/sprite.svg' %}#icon-arrow-down"></use>
</svg> </svg>
</button> </button>
<div class="toggle__body">Cамое главное - иметь альбом или блокнот с пустыми страницами (без линий и клеток) плотной гладкой бумагой, формат <div class="toggle__body">{{ school_schedule.materials }}</div>
А4. Рисовать будем цветными карандашами, а также простым, мягкостью B2. Иногда пригодятся вырезки из журналов
и клей-карандаш.</div>
</div> </div>
</div> </div>
</div> </div>
{% endfor %}
</div> </div>
<div class="text text_mb0"> <div class="text text_mb0">
<a href='#'>Распечатать расписание</a> чтобы не забыть</div> <a href='#'>Распечатать расписание</a> чтобы не забыть</div>

@ -29,6 +29,8 @@ from apps.user.views import (
UserView, UserEditView, NotificationEditView, UserView, UserEditView, NotificationEditView,
PaymentHistoryView, resend_email_verify, PaymentHistoryView, resend_email_verify,
) )
from apps.payment.views import CourseBuyView, PaymentwallCallbackView, SchoolBuyView
from apps.school.models import SchoolSchedule
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
@ -38,12 +40,17 @@ urlpatterns = [
path('course/on-moderation', CourseOnModerationView.as_view(), name='course-on-moderation'), path('course/on-moderation', CourseOnModerationView.as_view(), name='course-on-moderation'),
path('course/<int:pk>/', CourseView.as_view(), name='course'), path('course/<int:pk>/', CourseView.as_view(), name='course'),
path('course/<str:slug>/', CourseView.as_view(), name='course'), path('course/<str:slug>/', CourseView.as_view(), name='course'),
path('course/<int:pk>/checkout', CourseBuyView.as_view(), name='course-checkout'),
path('course/<int:pk>/edit', CourseEditView.as_view(), name='course_edit'), path('course/<int:pk>/edit', CourseEditView.as_view(), name='course_edit'),
path('course/<int:pk>/lessons', CourseView.as_view(template_name='course/course_only_lessons.html'), name='course-only-lessons'), path('course/<int:pk>/lessons', CourseView.as_view(template_name='course/course_only_lessons.html'), name='course-only-lessons'),
path('course/<int:course_id>/like', likes, name='likes'), path('course/<int:course_id>/like', likes, name='likes'),
path('course/<int:course_id>/comment', coursecomment, name='coursecomment'), path('course/<int:course_id>/comment', coursecomment, name='coursecomment'),
path('lesson/<int:pk>/', LessonView.as_view(), name='lesson'), path('lesson/<int:pk>/', LessonView.as_view(), name='lesson'),
path('lesson/<int:lesson_id>/comment', lessoncomment, name='lessoncomment'), path('lesson/<int:lesson_id>/comment', lessoncomment, name='lessoncomment'),
path('payments/ping', PaymentwallCallbackView.as_view(), name='payment-ping'),
path('payments/success', TemplateView.as_view(template_name='payment/payment_success.html'), name='payment-success'),
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('search/', SearchView.as_view(), name='search'), path('search/', SearchView.as_view(), name='search'),
path('user/<int:pk>/', UserView.as_view(), name='user'), path('user/<int:pk>/', UserView.as_view(), name='user'),
path('user/<int:pk>/edit', UserEditView.as_view(), name='user-edit-profile'), path('user/<int:pk>/edit', UserEditView.as_view(), name='user-edit-profile'),
@ -53,7 +60,15 @@ urlpatterns = [
path('privacy', TemplateView.as_view(template_name="templates/lilcity/privacy_policy.html"), name='privacy'), path('privacy', TemplateView.as_view(template_name="templates/lilcity/privacy_policy.html"), name='privacy'),
path('terms', TemplateView.as_view(template_name="templates/lilcity/terms.html"), name='terms'), path('terms', TemplateView.as_view(template_name="templates/lilcity/terms.html"), name='terms'),
path('refund-policy', TemplateView.as_view(template_name="templates/lilcity/refund_policy.html"), name='refund_policy'), path('refund-policy', TemplateView.as_view(template_name="templates/lilcity/refund_policy.html"), name='refund_policy'),
path('', TemplateView.as_view(template_name="templates/lilcity/main.html", extra_context={'course_items': Course.objects.all()[:3]}), name='index'), path('',
TemplateView.as_view(
template_name="templates/lilcity/main.html",
extra_context={
'course_items': Course.objects.filter(status=Course.PUBLISHED)[:3],
'school_schedules': SchoolSchedule.objects.all(),
}),
name='index'
),
path('api/v1/', include(('api.v1.urls', 'api_v1'))), path('api/v1/', include(('api.v1.urls', 'api_v1'))),
path('test', TemplateView.as_view(template_name="templates/lilcity/test.html"), name='test'), path('test', TemplateView.as_view(template_name="templates/lilcity/test.html"), name='test'),
] ]

@ -1,7 +1,8 @@
# Python-3.6 # Python-3.6
Django==2.0.2 Django==2.0.2
django-anymail[mailgun]==1.2 django-anymail[mailgun]==1.2
paymentwall-python==1.0.7 # paymentwall-python==1.0.7
git+https://github.com/ivlevdenis/paymentwall-python.git
twilio==6.10.0 twilio==6.10.0
psycopg2==2.7.3.2 psycopg2==2.7.3.2
facepy==1.0.9 facepy==1.0.9

Loading…
Cancel
Save