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. 30
      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. 159
      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. 26
      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. 131
      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',
'is_email_proved',
'photo',
'balance',
)
read_only_fields = (
@ -44,6 +45,7 @@ class UserSerializer(serializers.ModelSerializer):
'is_staff',
'fb_id',
'fb_data',
'balance',
)

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

@ -24,6 +24,7 @@ from .serializers.content import (
ImageObjectSerializer,
)
from .serializers.school import SchoolScheduleSerializer
from .serializers.payment import AuthorBalanceSerializer
from .serializers.user import (
UserSerializer, UserPhotoSerializer,
)
@ -35,10 +36,23 @@ from apps.content.models import (
Image, Text, ImageText, Video,
Gallery, GalleryImage, ImageObject,
)
from apps.payment.models import AuthorBalance
from apps.school.models import SchoolSchedule
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):
queryset = ImageObject.objects.all()
serializer_class = ImageObjectSerializer

@ -25,14 +25,18 @@
</div>
<div class="go__title">Вернуться</div>
</a>
<button
class="go__btn btn btn_md"
{% if not paid %}
<a
class="go__btn btn{% if pending %} btn_gray{% endif %} btn_md"
{% if user.is_authenticated %}
data-popup=".js-popup-buy"
{% if not pending %}
href="{% url 'course-checkout' course.id %}"
{% endif %}
{% else %}
data-popup=".js-popup-auth"
{% endif %}
>КУПИТЬ КУРС</button>
>{% if pending %}ОЖИДАЕТСЯ ПОДТВЕРЖДЕНИЕ ОПЛАТЫ{% else %}КУПИТЬ КУРС{% endif %}</a>
{% endif %}
</div>
<div
class="course"
@ -156,7 +160,7 @@
<img class="video__pic" src="{{ course.cover.image.url }}"/>
{% else %}
<img class="video__pic" src="{% static 'img/no_cover.png' %}"/>
{% endif %}
{% endif %}
{% if course.is_deferred_start %}
<div class="video__soon">
<div class="video__title">Курс начнется:</div>
@ -306,7 +310,7 @@
</div>
</div>
{% endif %}
<div
<div
class="course course_promo"
style="background-image: url({% if course.cover %}{{ course.cover.image.url }}{% else %}{% static 'img/no_cover.png' %}{% endif %});"
>
@ -383,14 +387,18 @@
<div class="meta__title">{{ course.price|floatformat:"-2" }}₽</div>
</div>
</div>
<button
class="course__buy btn btn_md"
{% if not paid %}
<a
class="go__btn btn{% if pending %} btn_gray{% endif %} btn_md"
{% if user.is_authenticated %}
data-popup=".js-popup-buy"
{% if not pending %}
href="{% url 'course-checkout' course.id %}"
{% endif %}
{% else %}
data-popup=".js-popup-auth"
{% endif %}
>КУПИТЬ КУРС</button>
>{% if pending %}ОЖИДАЕТСЯ ПОДТВЕРЖДЕНИЕ ОПЛАТЫ{% else %}КУПИТЬ КУРС{% endif %}</a>
{% endif %}
</div>
</div>
</div>
@ -459,4 +467,4 @@
</div>
</div>
</div>
{% endblock content %}
{% endblock content %}

@ -8,6 +8,8 @@ from django.views.generic import View, CreateView, DetailView, ListView, Templat
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from apps.payment.models import AuthorBalance
from .models import Course, Like, Lesson, CourseComment, LessonComment
from .filters import CourseFilter
@ -187,6 +189,14 @@ class CourseView(DetailView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
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
def get_queryset(self):

@ -1,3 +1,61 @@
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.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):
COMPLETE = 'COMPLETE'
CHARGEBACK = 'CHARGEBACK'
REFUNDED = 'REFUNDED'
ERROR = 'ERROR'
PENDING = 'PENDING'
from apps.course.models import Course
from apps.school.models import SchoolSchedule
transaction_id = models.PositiveIntegerField()
status = models.CharField(max_length=50)
product_id = models.PositiveIntegerField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
User = get_user_model()
class AuthorBalance(models.Model):
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.views.generic import View
from django.views.decorators.csrf import csrf_exempt
from django.contrib import messages
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 .models import AuthorBalance, CoursePayment, SchoolPayment
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()})
from paymentwall.pingback import Pingback
from .models import Purchase
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')
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):
x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
@ -31,46 +96,38 @@ class PaymentwallCallbackView(View):
return ip
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():
cart_id = pingback.get_product().get_id()
product_type_name, payment_id = pingback.get_product().get_id().split('_')
# try:
# cart = CartModel.objects.get(pk=cart_id)
# except CartModel.DoesNotExist:
# log.error('Paymentwall pingback: Cant find cart, Paymentwall sent this data: {}'.format(request.GET.copy()))
# return HttpResponse(status=403)
if product_type_name == 'course':
product_payment_class = CoursePayment
elif product_type_name == 'school':
product_payment_class = SchoolPayment
else:
return HttpResponse(status=403)
try:
purchase = Purchase.objects.get(transaction_id=pingback.get_reference_id())
except Purchase.DoesNotExist:
# purchase = cart.create_purchase(transaction_id=pingback.get_reference_id())
pass
if pingback.is_deliverable():
purchase.status = Purchase.COMPLETE
elif pingback.is_cancelable():
reason = pingback.get_parameter('reason')
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
payment = product_payment_class.objects.get(pk=payment_id)
except product_payment_class.DoesNotExist:
return HttpResponse(status=403)
payment.status = pingback.get_type()
payment.data = payment_raw_data
payment.save()
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():
payment.author_balance.status = AuthorBalance.PENDING
else:
purchase.status = Purchase.ERROR
elif pingback.is_under_review():
purchase.status = Purchase.PENDING
payment.author_balance.status = AuthorBalance.DECLINED
else:
# log.error('Paymentwall pingback: Unknown pingback type, Paymentwall sent this data: {}'.format(request.GET.copy()))
pass
# purchase.save()
return HttpResponse('OK', status=200)
payment.author_balance.save()
return HttpResponse('OK', status=403)
else:
# log.error('Paymentwall pingback: Cant validate pingback, error: {} Paymentwall sent this data: {}'.format(pingback.get_error_summary(), request.GET.copy()))
pass
return

@ -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, 'суббота'),
(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)
description = models.TextField('Описание')
materials = models.TextField('Материалы')
age = models.PositiveSmallIntegerField('Возраст', 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:
ordering = ('weekday',)
verbose_name = 'Расписание'
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)
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)
def create_auth_token(sender, instance=None, created=False, **kwargs):

@ -27,11 +27,11 @@
<div class="title title_sm">Вывести деньги со счета</div>
<div class="form">
<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="field__label">СУММА</div>
<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 class="field__error">Размер выводимой суммы не должно быть менее 2000 рублей.</div>
</div>
@ -58,21 +58,23 @@
<div class="title title_sm">История платежей</div>
<div class="transactions">
<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__cell">Ноябрь. Школа Lil City</div>
<div class="transactions__cell">2000.00</div>
<div class="transactions__cell">{{balance.payment.course.title}}</div>
<div class="transactions__cell">{{balance.amount}}</div>
<div class="transactions__cell">Получено</div>
</div>
{% endfor %}
{% else %}
{% for payment in request.user.payments.all %}
<div class="transactions__row">
<div class="transactions__cell">Общий курс по иллюстрации</div>
<div class="transactions__cell">2000.00</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">{{payment.course.title}}</div>
<div class="transactions__cell">{{payment.amount}}</div>
<div class="transactions__cell">Получено</div>
</div>
{% endfor %}
{% endif %}
</div>
<div class="transactions__load load">
<button class="load__btn btn">еще</button>
@ -80,4 +82,4 @@
</div>
</div>
</div>
{% endblock content %}
{% endblock content %}

@ -14,6 +14,7 @@ from django.utils.decorators import method_decorator
from apps.auth.tokens import verification_email_token
from apps.course.models import Course
from apps.payment.models import CoursePayment
from apps.notification.utils import send_email
from .forms import UserEditForm
@ -45,7 +46,9 @@ class UserView(DetailView):
context['drafts'] = Course.objects.filter(
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

@ -224,6 +224,7 @@ CONSTANCE_CONFIG = OrderedDict((
('INSTAGRAM_CLIENT_PASSWORD', ('', '')),
('INSTAGRAM_RESULTS_TAG', ('#lil_акварель', 'Тэг результатов работ.')),
('INSTAGRAM_RESULTS_PATH', ('media/instagram/results/', 'Путь до результатов работ.')),
('SERVICE_COMMISSION', (10, 'Комиссия сервиса в процентах.'))
))
try:
@ -231,6 +232,14 @@ try:
except ImportError:
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

@ -136,9 +136,9 @@
<div class="header__ava ava"><img class="ava__pic" src="{% static 'img/user.jpg' %}"></div>
{% endif %}
<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 %}
<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' %}">
{% comment %} <a class="header__link header__link_gray disabled" href="#"> {% endcomment %}
<div class="header__title">ДОБАВИТЬ КУРС</div>
@ -424,46 +424,32 @@
<div class="buy__col">
<div class="buy__head buy__head_main">
<div class="buy__title">Выбор урока/дня</div>
<div class="buy__content">При записи на 5 уроков скидка 10%.</div>
<!-- <div class="buy__content">При записи на 5 уроков скидка 10%.</div> -->
</div>
</div>
<div class="buy__col">
<div class="buy__head">
<div class="buy__label">Месяц:</div>
<div class="buy__title">Январь</div>
<div class="buy__content">Если вы оплачиваете после 15 числа, доступ к урокам будет с 1-го следующего
<!-- <div class="buy__label">Месяц:</div>
<div class="buy__title">Январь</div> -->
<!-- <div class="buy__content">Если вы оплачиваете после 15 числа, доступ к урокам будет с 1-го следующего
месяца.
</div>
</div> -->
</div>
</div>
<div class="buy__col">
<div class="buy__list"><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" 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" 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 class="buy__list">
{% for school_schedule in school_schedules %}
<label class="switch switch_lesson">
<input class="switch__input" type="checkbox">
<span class="switch__content">
<span class="switch__cell">{{ school_schedule }}</span>
{% comment %} dont delete {% endcomment %}
<span class="switch__cell"></span>
<span class="switch__cell">{{ school_schedule.title }}</span>
<span class="switch__cell">{{school_schedule.month_price}}р</span>
</span>
</label>
{% endfor %}
</div>
</div>
<div class="buy__col">
@ -483,7 +469,10 @@
</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>

@ -5,9 +5,22 @@
<div class="main" style="background-image: url({% static 'img/bg-1.jpg' %});">
<div class="main__center center">
<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>
{% 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__center center">
<div class="text text_lg">
@ -331,125 +344,23 @@
<div class="title title_center">Расписание</div>
</a>
<div class="schedule">
{% for school_schedule in school_schedules %}
<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__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__day">{{ school_schedule }}</div>
<div class="schedule__wrap">
<div class="schedule__title">Персонаж.</div>
<div class="schedule__content">Учимся создавать персонажей из простых форм. Изучаем характеры и эмоции.</div>
<div class="schedule__title">{{ school_schedule.title }}</div>
<div class="schedule__content">{{ school_schedule.description }}</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 class="toggle__body">{{ school_schedule.materials }}</div>
</div>
</div>
</div>
{% endfor %}
</div>
<div class="text text_mb0">
<a href='#'>Распечатать расписание</a> чтобы не забыть</div>
@ -512,4 +423,4 @@
</div>
</div>
</div>
{% endblock content %}
{% endblock content %}

@ -29,6 +29,8 @@ from apps.user.views import (
UserView, UserEditView, NotificationEditView,
PaymentHistoryView, resend_email_verify,
)
from apps.payment.views import CourseBuyView, PaymentwallCallbackView, SchoolBuyView
from apps.school.models import SchoolSchedule
urlpatterns = [
path('admin/', admin.site.urls),
@ -38,12 +40,17 @@ urlpatterns = [
path('course/on-moderation', CourseOnModerationView.as_view(), name='course-on-moderation'),
path('course/<int:pk>/', 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>/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>/comment', coursecomment, name='coursecomment'),
path('lesson/<int:pk>/', LessonView.as_view(), name='lesson'),
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('user/<int:pk>/', UserView.as_view(), name='user'),
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('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('', 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('test', TemplateView.as_view(template_name="templates/lilcity/test.html"), name='test'),
]

@ -1,7 +1,8 @@
# Python-3.6
Django==2.0.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
psycopg2==2.7.3.2
facepy==1.0.9

Loading…
Cancel
Save