Merge branch 'master' of https://gitlab.com/lilcity/backend into feature/LIL-559

remotes/origin/hotfix/LIL-661
gzbender 7 years ago
commit 4bfa505a56
  1. 1
      .gitlab-ci.yml
  2. 0
      __init__.py
  3. 9
      api/v1/serializers/content.py
  4. 75
      api/v1/serializers/contest.py
  5. 29
      api/v1/serializers/course.py
  6. 4
      api/v1/serializers/mixins.py
  7. 71
      api/v1/serializers/payment.py
  8. 5
      api/v1/serializers/school.py
  9. 1
      api/v1/serializers/user.py
  10. 5
      api/v1/urls.py
  11. 116
      api/v1/views.py
  12. 17
      apps/auth/backend.py
  13. 5
      apps/auth/forms.py
  14. 5
      apps/auth/views.py
  15. 11
      apps/content/admin.py
  16. 48
      apps/content/migrations/0021_auto_20180813_1306.py
  17. 17
      apps/content/migrations/0022_auto_20180815_2129.py
  18. 79
      apps/content/models.py
  19. 64
      apps/content/templates/content/blocks/gallery.html
  20. 20
      apps/content/templates/content/blocks/image.html
  21. 2
      apps/content/templates/content/blocks/imagetext.html
  22. 18
      apps/content/templates/content/blocks/text.html
  23. 6
      apps/content/templates/content/blocks/video.html
  24. 47
      apps/content/templates/content/contest.html
  25. 18
      apps/content/templates/content/contest_edit.html
  26. 118
      apps/content/templates/content/contest_work.html
  27. 112
      apps/content/views.py
  28. 2
      apps/course/filters.py
  29. 18
      apps/course/migrations/0040_course_age.py
  30. 36
      apps/course/migrations/0041_auto_20180813_1306.py
  31. 18
      apps/course/migrations/0042_like_ip.py
  32. 29
      apps/course/migrations/0043_auto_20180824_2132.py
  33. 44
      apps/course/models.py
  34. 2
      apps/course/templates/course/_items.html
  35. 10
      apps/course/templates/course/course.html
  36. 47
      apps/course/templates/course/course_only_lessons.html
  37. 4
      apps/course/templates/course/courses.html
  38. 3
      apps/course/templates/course/inclusion/category_items.html
  39. 2
      apps/course/templates/course/inclusion/category_menu_items.html
  40. 49
      apps/course/templates/course/lesson.html
  41. 4
      apps/course/templatetags/lilcity_category.py
  42. 33
      apps/course/views.py
  43. 22
      apps/payment/management/commands/payment_fix_amounts.py
  44. 94
      apps/payment/models.py
  45. 2
      apps/payment/templates/payment/payment_success.html
  46. 61
      apps/payment/views.py
  47. 20
      apps/school/migrations/0019_schoolschedule_cover.py
  48. 29
      apps/school/migrations/0020_auto_20180824_2132.py
  49. 18
      apps/school/migrations/0021_schoolschedule_trial_lesson.py
  50. 24
      apps/school/models.py
  51. 78
      apps/school/templates/blocks/_schedule_purchased_item.html
  52. 6
      apps/school/templates/blocks/day_pay_btn.html
  53. 2
      apps/school/templates/blocks/online.html
  54. 2
      apps/school/templates/blocks/promo.html
  55. 71
      apps/school/templates/blocks/schedule.html
  56. 102
      apps/school/templates/blocks/schedule_item.html
  57. 4
      apps/school/templates/blocks/schedule_purchased.html
  58. 25
      apps/school/templates/school/livelesson_detail.html
  59. 4
      apps/school/templates/school/livelessons_list.html
  60. 62
      apps/school/templates/school/schedules_print.html
  61. 2
      apps/school/templates/school/summer_school.html
  62. 78
      apps/school/templates/summer/_schedule_purchased_item.html
  63. 2
      apps/school/templates/summer/day_pay_btn.html
  64. 4
      apps/school/templates/summer/online.html
  65. 2
      apps/school/templates/summer/open_lesson.html
  66. 14
      apps/school/templates/summer/promo.html
  67. 25
      apps/school/templates/summer/schedule_purchased.html
  68. 3
      apps/school/urls.py
  69. 142
      apps/school/views.py
  70. 2
      apps/user/forms.py
  71. 18
      apps/user/migrations/0023_user_trial_lesson.py
  72. 13
      apps/user/models.py
  73. 87
      apps/user/templates/user/author_profile.html
  74. 2
      apps/user/templates/user/notification-settings.html
  75. 2
      apps/user/templates/user/payment-history.html
  76. 15
      apps/user/templates/user/profile-settings.html
  77. 72
      apps/user/templates/user/profile.html
  78. 84
      apps/user/views.py
  79. 21
      project/context_processors.py
  80. 3
      project/settings.py
  81. 22
      project/templates/blocks/about.html
  82. 2
      project/templates/blocks/comment.html
  83. 1
      project/templates/blocks/footer.html
  84. 9
      project/templates/blocks/header.html
  85. 2
      project/templates/blocks/last_courses.html
  86. 13
      project/templates/blocks/lil_store_js.html
  87. 4
      project/templates/blocks/live.html
  88. 2
      project/templates/blocks/messages.html
  89. 21
      project/templates/blocks/partners.html
  90. 14
      project/templates/blocks/popup_auth.html
  91. 38
      project/templates/blocks/popup_buy.html
  92. 4
      project/templates/blocks/popup_course_lock.html
  93. 10
      project/templates/blocks/popup_subscribe.html
  94. 77
      project/templates/blocks/promo.html
  95. 31
      project/templates/blocks/share.html
  96. 17
      project/templates/blocks/teachers.html
  97. 4
      project/templates/blocks/user_menu.html
  98. 1
      project/templates/lilcity/edit_index.html
  99. 30
      project/templates/lilcity/index.html
  100. 14
      project/urls.py
  101. Some files were not shown because too many files have changed in this diff Show More

@ -44,6 +44,7 @@ stop-review:
script:
- export REVIEW_HOST=$CI_COMMIT_REF_SLUG-$REVIEW_DOMAIN
- cd docker
- cp .env.review .env
- docker-compose -f docker-compose-review.yml -p back$CI_COMMIT_REF_NAME down
- rm -rf /work/data/back_${CI_COMMIT_REF_NAME}/
when: manual

@ -3,8 +3,7 @@ from rest_framework import serializers
from apps.content.models import (
Baner, Content, Image, Text, ImageText, Video,
Gallery, GalleryImage, ImageObject,
)
Gallery, GalleryImage, ImageObject,)
from . import Base64ImageField
@ -14,6 +13,7 @@ BASE_CONTENT_FIELDS = (
'uuid',
'course',
'lesson',
'contest',
'live_lesson',
'title',
'position',
@ -85,12 +85,16 @@ class ImageObjectSerializer(serializers.ModelSerializer):
image = Base64ImageField(
required=True, allow_empty_file=False, allow_null=False, read_only=False,
)
image_thumbnail = Base64ImageField(
required=False, allow_empty_file=True, allow_null=True, read_only=True,
)
class Meta:
model = ImageObject
fields = (
'id',
'image',
'image_thumbnail',
'created_at',
'update_at',
)
@ -254,3 +258,4 @@ class ContentSerializer(serializers.ModelSerializer):
elif isinstance(obj, Gallery):
return GallerySerializer(obj, context=self.context).to_representation(obj)
return super(ContentSerializer, self).to_representation(obj)

@ -0,0 +1,75 @@
from django.contrib.auth import get_user_model
from rest_framework import serializers
from api.v1.serializers.content import ContentSerializer, ContentCreateSerializer, ImageObjectSerializer
from api.v1.serializers.mixins import DispatchContentMixin
from apps.content.models import (Contest, ContestWork)
User = get_user_model()
class ContestSerializer(serializers.ModelSerializer):
cover = ImageObjectSerializer()
content = ContentSerializer(many=True)
class Meta:
model = Contest
fields = ['title', 'description', 'slug', 'cover',
'date_start', 'date_end', 'active', 'content', 'finished']
class ContestCreateSerializer(DispatchContentMixin, serializers.ModelSerializer):
content = serializers.ListSerializer(
child=ContentCreateSerializer(),
required=False,
)
class Meta:
model = Contest
fields = '__all__'
def create(self, validated_data):
content = validated_data.pop('content', [])
contest = super().create(validated_data)
self.dispatch_content(contest, content)
return contest
def update(self, instance, validated_data):
content = validated_data.pop('content', [])
contest = super().update(instance, validated_data)
self.dispatch_content(contest, content)
return contest
def to_representation(self, instance):
return ContestSerializer(instance=instance, context=self.context).to_representation(instance)
class ContestWorkSerializer(serializers.ModelSerializer):
image = ImageObjectSerializer()
likes = serializers.SerializerMethodField()
user_liked = serializers.SerializerMethodField()
class Meta:
model = ContestWork
fields = ['id', 'user', 'contest', 'image', 'child_full_name', 'age',
'created_at', 'likes', 'user_liked', 'img_width', 'img_height']
def get_likes(self, instance):
return instance.likes.filter(user__is_active=True).count()
def get_user_liked(self, instance):
# FIXME
user = self.context['request'].query_params.get('current_user')
if user:
user = User.objects.get(pk=user)
return instance.likes.filter(user=user).exists() if user else False
class ContestWorkCreateSerializer(serializers.ModelSerializer):
class Meta:
model = ContestWork
fields = '__all__'
def to_representation(self, instance):
return ContestWorkSerializer(instance=instance, context=self.context).to_representation(instance)

@ -1,6 +1,10 @@
from ipware import get_client_ip
from rest_framework import serializers
from rest_framework.validators import UniqueValidator
from django.contrib.auth import get_user_model
from apps.course.models import (
Category, Course,
Comment, CourseComment, LessonComment,
@ -15,12 +19,15 @@ from .content import (
from apps.content.models import (
Content, Image, Text, ImageText, Video,
Gallery, GalleryImage, ImageObject,
)
ContestWork)
from .user import UserSerializer
from .mixins import DispatchContentMixin, DispatchGalleryMixin, DispatchMaterialMixin
User = get_user_model()
class MaterialCreateSerializer(serializers.ModelSerializer):
class Meta:
@ -63,6 +70,26 @@ class LikeSerializer(serializers.ModelSerializer):
)
class LikeCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Like
fields = ['user']
def create(self, validated_data):
# FIXME
if validated_data.get('user'):
user = validated_data.get('user')
else:
user = self.context['request'].user
client_ip, is_routable = get_client_ip(self.context['request'])
like = Like.objects.create(user=user, ip=client_ip)
return like
def to_representation(self, instance):
return LikeSerializer(instance, context=self.context).to_representation(instance)
class CategorySerializer(serializers.ModelSerializer):
class Meta:

@ -4,7 +4,7 @@ from apps.school.models import LiveLesson
from apps.content.models import (
Content, Image, Text, ImageText, Video,
Gallery, GalleryImage, ImageObject,
)
Contest)
from .content import (
TextCreateSerializer, ImageCreateSerializer,
ImageTextCreateSerializer, VideoCreateSerializer,
@ -25,6 +25,8 @@ class DispatchContentMixin(object):
obj_type = 'lesson'
elif isinstance(obj, LiveLesson):
obj_type = 'live_lesson'
elif isinstance(obj, Contest):
obj_type = 'contest'
cdata[obj_type] = obj.id
if ctype == 'text':
if 'id' in cdata and cdata['id']:

@ -46,8 +46,29 @@ class AuthorBalanceCreateSerializer(serializers.ModelSerializer):
return AuthorBalanceSerializer(instance, context=self.context).to_representation(instance)
class PaymentSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Payment
fields = BASE_PAYMENT_FIELDS
read_only_fields = (
'id',
'user',
'created_at',
'update_at',
)
def to_representation(self, instance):
if isinstance(instance, CoursePayment):
return CoursePaymentSerializer(instance, context=self.context).to_representation(instance)
elif isinstance(instance, SchoolPayment):
return SchoolPaymentSerializer(instance, context=self.context).to_representation(instance)
class AuthorBalanceSerializer(serializers.ModelSerializer):
author = UserSerializer()
payment = serializers.SerializerMethodField()
class Meta:
model = AuthorBalance
@ -70,6 +91,24 @@ class AuthorBalanceSerializer(serializers.ModelSerializer):
'payment',
)
def get_payment(self, instance):
try:
p = instance.payment
except Exception:
return None
data = {
'id': p.id,
'created_at': p.created_at,
'amount': p.amount,
'data': p.data,
}
if isinstance(instance.payment, CoursePayment):
data['course'] = {
'id': p.course.id,
'title': p.course.title,
}
return data
class PaymentSerializer(serializers.ModelSerializer):
user = UserSerializer()
@ -91,6 +130,20 @@ class PaymentSerializer(serializers.ModelSerializer):
return SchoolPaymentSerializer(instance, context=self.context).to_representation(instance)
class CoursePaymentCreateSerializer(serializers.ModelSerializer):
class Meta:
model = CoursePayment
fields = BASE_PAYMENT_FIELDS + ('course',)
read_only_fields = (
'id',
'user',
'course',
'created_at',
'update_at',
)
class CoursePaymentSerializer(serializers.ModelSerializer):
user = UserSerializer()
course = CourseSerializer()
@ -107,6 +160,24 @@ class CoursePaymentSerializer(serializers.ModelSerializer):
)
class SchoolPaymentCreateSerializer(serializers.ModelSerializer):
class Meta:
model = SchoolPayment
fields = BASE_PAYMENT_FIELDS + (
'weekdays',
'date_start',
'date_end',
)
read_only_fields = (
'id',
'user',
'course',
'created_at',
'update_at',
)
class SchoolPaymentSerializer(serializers.ModelSerializer):
user = UserSerializer()

@ -54,6 +54,8 @@ class SchoolScheduleSerializer(serializers.ModelSerializer):
'day_discount',
'start_at',
'schoolschedule_images',
'cover',
'trial_lesson',
)
read_only_fields = (
@ -96,6 +98,7 @@ class SchoolScheduleSerializerImg(serializers.ModelSerializer):
child=GalleryImageSerializer(),
required=False,
)
cover = ImageObjectSerializer()
class Meta:
model = SchoolSchedule
@ -111,6 +114,8 @@ class SchoolScheduleSerializerImg(serializers.ModelSerializer):
'day_discount',
'start_at',
'schoolschedule_images',
'cover',
'trial_lesson',
)
read_only_fields = (

@ -42,6 +42,7 @@ class UserSerializer(serializers.ModelSerializer):
'photo',
'balance',
'show_in_mainpage',
'trial_lesson',
)
read_only_fields = (

@ -18,7 +18,7 @@ from .views import (
UserViewSet, LessonViewSet, ImageObjectViewSet,
SchoolScheduleViewSet, LiveLessonViewSet,
PaymentViewSet, ObjectCommentsViewSet,
)
ContestViewSet, ContestWorkViewSet)
router = DefaultRouter()
router.register(r'author-requests', AuthorRequestViewSet, base_name='author-requests')
@ -45,6 +45,9 @@ router.register(r'school-schedules', SchoolScheduleViewSet, base_name='school-sc
router.register(r'users', UserViewSet, base_name='users')
router.register(r'contests', ContestViewSet, base_name='contests')
router.register(r'contest-works', ContestWorkViewSet, base_name='contest_works')
# router.register(r'configs', ConfigViewSet, base_name='configs')

@ -1,7 +1,7 @@
from django.contrib.auth import get_user_model
from rest_framework import status, views, viewsets, generics
from rest_framework.decorators import detail_route, list_route
from rest_framework.decorators import detail_route, list_route, action
from rest_framework.response import Response
from . import ExtendedModelViewSet
@ -14,7 +14,7 @@ from .serializers.course import (
CommentSerializer, CommentCreateSerializer,
MaterialSerializer, MaterialCreateSerializer,
LessonSerializer, LessonCreateSerializer,
CourseCommentSerializer, LessonCommentSerializer,
LikeCreateSerializer, CourseCommentSerializer, LessonCommentSerializer,
)
from .serializers.content import (
BanerSerializer,
@ -35,11 +35,14 @@ from .serializers.payment import (
AuthorBalanceSerializer, AuthorBalanceCreateSerializer,
PaymentSerializer, CoursePaymentSerializer,
SchoolPaymentSerializer,
)
CoursePaymentCreateSerializer, SchoolPaymentCreateSerializer)
from .serializers.user import (
AuthorRequestSerializer,
UserSerializer, UserPhotoSerializer,
)
from .serializers.contest import (
ContestCreateSerializer, ContestSerializer, ContestWorkSerializer, ContestWorkCreateSerializer
)
from .permissions import (
IsAdmin, IsAdminOrIsSelf,
@ -57,7 +60,7 @@ from apps.config.models import Config
from apps.content.models import (
Baner, Image, Text, ImageText, Video,
Gallery, GalleryImage, ImageObject,
)
Contest, ContestWork)
from apps.payment.models import (
AuthorBalance, Payment,
CoursePayment, SchoolPayment,
@ -71,7 +74,7 @@ User = get_user_model()
class AuthorBalanceViewSet(ExtendedModelViewSet):
queryset = AuthorBalance.objects.filter(
author__role__in=[User.AUTHOR_ROLE, User.ADMIN_ROLE],
author__role__in=[User.AUTHOR_ROLE, User.ADMIN_ROLE, User.TEACHER_ROLE],
)
serializer_class = AuthorBalanceCreateSerializer
serializer_class_map = {
@ -112,6 +115,8 @@ class BanerViewSet(ExtendedModelViewSet):
class ImageObjectViewSet(ExtendedModelViewSet):
queryset = ImageObject.objects.all()
serializer_class = ImageObjectSerializer
# FIXME
authentication_classes = []
# permission_classes = (IsAuthorOrAdmin,)
@ -128,11 +133,41 @@ class MaterialViewSet(ExtendedModelViewSet):
class LikeViewSet(ExtendedModelViewSet):
OBJ_TYPE_CONTEST_WORK = 'contest_work'
queryset = Like.objects.select_related('user').all()
serializer_class = LikeSerializer
serializer_class = LikeCreateSerializer
serializer_class_map = {
'list': LikeSerializer,
'retrieve': LikeSerializer,
}
search_fields = ('user__email', 'user__firstname', 'user__lastname',)
ordering_fields = ('created_at', 'update_at',)
# permission_classes = (IsAdmin,)
# FIXME
authentication_classes = []
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
obj_type = request.data.get('obj_type')
obj_id = request.data.get('obj_id')
user = serializer.validated_data.get('user')
if not user.is_active: # FIXME and user.is_authenticated):
return Response(status=status.HTTP_403_FORBIDDEN)
if obj_type == self.OBJ_TYPE_CONTEST_WORK:
contest_work = ContestWork.objects.get(pk=obj_id)
if contest_work.user == user:
return Response({'error': u'Нельзя голосовать за свою работу'}, status=status.HTTP_400_BAD_REQUEST)
if contest_work.likes.filter(user=user).exists():
return Response({'error': u'Вы уже голосовали за эту работу'}, status=status.HTTP_400_BAD_REQUEST)
if contest_work.contest.finished:
return Response({'error': u'Голосование закончено'}, status=status.HTTP_400_BAD_REQUEST)
instance = serializer.save()
if obj_type == self.OBJ_TYPE_CONTEST_WORK:
contest_work.likes.add(instance)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
class CategoryViewSet(ExtendedModelViewSet):
@ -486,14 +521,77 @@ class AuthorRequestViewSet(ExtendedModelViewSet):
filter_fields = ('status',)
class PaymentViewSet(ExtendedModelViewSet):
queryset = Payment.objects.filter(status__isnull=False).order_by('-created_at')
class PaymentViewSet(viewsets.ModelViewSet):
queryset = Payment.objects.all()
serializer_class = PaymentSerializer
permission_classes = (IsAdmin,)
filter_fields = ('status',)
filter_fields = ('status', 'user',)
ordering_fields = (
'id', 'user__email',
'user__first_name', 'user__last_name',
'amount', 'created_at',
)
search_fields = ('user__email', 'user__first_name', 'user__last_name',)
def get_serializer(self, instance, *args, **kwargs):
serializer_class = self.get_serializer_class()
if 'update' in self.action:
if isinstance(instance, CoursePayment):
serializer_class = CoursePaymentCreateSerializer
elif isinstance(instance, SchoolPayment):
serializer_class = SchoolPaymentCreateSerializer
kwargs['context'] = self.get_serializer_context()
return serializer_class(instance, *args, **kwargs)
def get_queryset(self):
queryset = self.queryset
course = self.request.query_params.get('course')
weekdays = self.request.query_params.getlist('weekdays[]')
if course:
queryset = CoursePayment.objects.filter(course=course)
if weekdays:
queryset = SchoolPayment.objects.filter(weekdays__overlap=weekdays)
return queryset.filter(status__isnull=False).order_by('-created_at')
@action(methods=['get'], detail=False, url_path='calc-amount', authentication_classes=[], permission_classes=[])
def calc_amount(self, request, pk=None):
user = request.query_params.get('user')
course = request.query_params.get('course')
weekdays = request.query_params.getlist('weekdays[]')
user = user and User.objects.get(pk=user)
course = course and Course.objects.get(pk=course)
return Response(Payment.calc_amount(user=user, course=course, weekdays=weekdays))
class ContestViewSet(ExtendedModelViewSet):
queryset = Contest.objects.all()
serializer_class = ContestCreateSerializer
serializer_class_map = {
'list': ContestSerializer,
'retrieve': ContestSerializer,
}
filter_fields = ('active',)
search_fields = ('description', 'title', 'slug',)
ordering_fields = ('id', 'title', 'active', 'date_start', 'date_end',)
permission_classes = (IsAdmin,)
class ContestWorkViewSet(ExtendedModelViewSet):
queryset = ContestWork.objects.all()
serializer_class = ContestWorkCreateSerializer
serializer_class_map = {
'list': ContestWorkSerializer,
'retrieve': ContestWorkSerializer,
}
filter_fields = ('contest',)
# FIXME
authentication_classes = []
def create(self, request, *args, **kwargs):
# FIXME
user = User.objects.get(pk=request.data.get('user'))
if ContestWork.objects.filter(user=user).exists():
return Response(status=status.HTTP_400_BAD_REQUEST)
return super().create(request, *args, **kwargs)

@ -0,0 +1,17 @@
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
User = get_user_model()
class CaseInsensitiveModelBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
if username is None:
username = kwargs.get(User.USERNAME_FIELD)
try:
user = User.objects.get(**{f'{User.USERNAME_FIELD}__iexact': username})
if user.check_password(password) and self.user_can_authenticate(user):
return user
except User.DoesNotExist:
return None

@ -6,8 +6,3 @@ class LearnerRegistrationForm(forms.Form):
last_name = forms.CharField()
email = forms.EmailField()
password = forms.CharField()
class LoginForm(forms.Form):
email = forms.CharField()
password = forms.CharField()

@ -33,7 +33,7 @@ class LearnerRegistrationView(FormView):
def form_valid(self, form):
first_name = form.cleaned_data['first_name']
last_name = form.cleaned_data['last_name']
email = form.cleaned_data['email']
email = form.cleaned_data['email'].lower()
password = form.cleaned_data['password']
user, created = User.objects.get_or_create(
@ -170,8 +170,9 @@ class FacebookLoginOrRegistration(View):
"errors": {"email": 'is field required'}
})
else:
email = email.lower()
try:
user = User.objects.get(email=email)
user = User.objects.get(email__iexact=email)
except User.DoesNotExist:
first_name = data.get('first_name', '')
last_name = data.get('last_name', '')

@ -8,6 +8,7 @@ from polymorphic.admin import (
from apps.content.models import (
Baner, Content, Image, Text, ImageText, Video,
Gallery, GalleryImage, ImageObject,
Contest,ContestWork,
)
@ -79,3 +80,13 @@ class ContentAdmin(PolymorphicParentModelAdmin):
@admin.register(GalleryImage)
class GalleryImageAdmin(admin.ModelAdmin):
pass
@admin.register(Contest)
class ContestAdmin(admin.ModelAdmin):
base_model = Contest
@admin.register(ContestWork)
class ContestWorkAdmin(admin.ModelAdmin):
base_model = ContestWork

@ -0,0 +1,48 @@
# Generated by Django 2.0.6 on 2018-08-13 13:06
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('course', '0040_course_age'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('content', '0020_auto_20180424_1607'),
]
operations = [
migrations.CreateModel(
name='Contest',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255)),
('description', models.TextField(blank=True, default='', max_length=1000)),
('slug', models.SlugField(allow_unicode=True, blank=True, max_length=100, null=True, unique=True)),
('date_start', models.DateField(blank=True, null=True, verbose_name='Дата начала')),
('date_end', models.DateField(blank=True, null=True, verbose_name='Дата окончания')),
('active', models.BooleanField(default=True)),
('cover', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='contest_covers', to='content.ImageObject', verbose_name='Фоновая картинка')),
],
),
migrations.CreateModel(
name='ContestWork',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('child_full_name', models.CharField(max_length=255)),
('age', models.SmallIntegerField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('contest', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='content.Contest')),
('image', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contest_work_images', to='content.ImageObject', verbose_name='Работа участника')),
('likes', models.ManyToManyField(blank=True, to='course.Like')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddField(
model_name='content',
name='contest',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='content', to='content.Contest', verbose_name='Конкурс'),
),
]

@ -0,0 +1,17 @@
# Generated by Django 2.0.6 on 2018-08-15 21:29
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('content', '0021_auto_20180813_1306'),
]
operations = [
migrations.AlterModelOptions(
name='contestwork',
options={'ordering': ('-created_at',), 'verbose_name': 'Конкурсная работа', 'verbose_name_plural': 'Конкурсные работы'},
),
]

@ -1,11 +1,25 @@
from datetime import datetime, time
from urllib.parse import urlparse
from django.conf import settings
from django.db import models
from django.contrib.auth import get_user_model
from django.urls import reverse_lazy
from django.utils import timezone
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToCover
from polymorphic.models import PolymorphicModel
User = get_user_model()
class ImageObject(models.Model):
image = models.ImageField('Изображение', upload_to='content/imageobject')
image_thumbnail = ImageSpecField(source='image',
processors=[ResizeToCover(300, 200, False)],
format='JPEG',
options={'quality': 85})
created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True)
@ -36,6 +50,12 @@ class Content(PolymorphicModel):
verbose_name='Урок онлайн школы',
related_name='content',
)
contest = models.ForeignKey(
'Contest', on_delete=models.CASCADE,
null=True, blank=True,
verbose_name='Конкурс',
related_name='content',
)
title = models.CharField('Заголовок', max_length=100, default='')
position = models.PositiveSmallIntegerField(
'Положение на странице',
@ -131,3 +151,62 @@ class Baner(models.Model):
if self.use:
Baner.objects.filter(use=True).update(use=False)
return super().save(*args, **kwargs)
class Contest(models.Model):
title = models.CharField(max_length=255)
description = models.TextField(max_length=1000, blank=True, default='')
slug = models.SlugField(
allow_unicode=True, null=True, blank=True,
max_length=100, unique=True, db_index=True,
)
cover = models.ForeignKey(
ImageObject, related_name='contest_covers',
verbose_name='Фоновая картинка', on_delete=models.CASCADE,
null=True, blank=True,
)
date_start = models.DateField('Дата начала', null=True, blank=True)
date_end = models.DateField('Дата окончания', null=True, blank=True)
active = models.BooleanField(default=True)
# TODO? baner
@property
def finished(self):
# FIXME
return datetime(2018, 8, 29, 21) < timezone.now()
def save(self, *args, **kwargs):
if self.active:
Contest.objects.filter(active=True).update(active=False)
return super().save(*args, **kwargs)
class ContestWork(models.Model):
user = models.ForeignKey(
User, on_delete=models.CASCADE
)
contest = models.ForeignKey(Contest, on_delete=models.CASCADE)
image = models.ForeignKey(
ImageObject, related_name='contest_work_images',
verbose_name='Работа участника', on_delete=models.CASCADE,
)
child_full_name = models.CharField(max_length=255)
age = models.SmallIntegerField()
created_at = models.DateTimeField(auto_now_add=True)
likes = models.ManyToManyField('course.Like', blank=True)
class Meta:
verbose_name = 'Конкурсная работа'
verbose_name_plural = 'Конкурсные работы'
ordering = ('-created_at',)
@property
def img_width(self):
return self.image.image.width if self.image and self.image.image else None
@property
def img_height(self):
return self.image.image.height if self.image and self.image.image else None
def get_absolute_url(self):
return reverse_lazy('contest_work', args=[self.id])

@ -1,32 +1,32 @@
{% load thumbnail %}
{% if results %}
<div class="title">Галерея итогов обучения</div>
<div class="examples gallery">
{% for image in course.gallery.gallery_images.all %}
<div class="examples__item">
<a href="{{ image.img.image.url }}">
{% thumbnail image.img.image "165x165" crop="center" as im %}
<img class="examples__pic" src="{{ im.url }}">
{% endthumbnail %}
</a>
</div>
{% endfor %}
</div>
{% else %}
<div class="section section_gradient">
<div class="section__center center center_sm">
<div class="title">{{ content.title }}</div>
<div class="examples gallery">
{% for image in content.gallery_images.all %}
<div class="examples__item">
<a href="{{ image.img.image.url }}">
{% thumbnail image.img.image "165x165" crop="center" as im %}
<img class="examples__pic" src="{{ im.url }}">
{% endthumbnail %}
</a>
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% load thumbnail %}
{% if results %}
<div class="title">Галерея итогов обучения</div>
<div class="examples gallery">
{% for image in course.gallery.gallery_images.all %}
<div class="examples__item">
<a href="{{ image.img.image.url }}">
{% thumbnail image.img.image "165x165" crop="center" as im %}
<img class="examples__pic" src="{{ im.url }}">
{% endthumbnail %}
</a>
</div>
{% endfor %}
</div>
{% else %}
<div class="section section_gradient">
<div class="section__center center center_sm">
<div class="title">{{ content.title }}</div>
<div class="examples gallery">
{% for image in content.gallery_images.all %}
<div class="examples__item">
<a href="{{ image.img.image.url }}">
{% thumbnail image.img.image "165x165" crop="center" as im %}
<img class="examples__pic" src="{{ im.url }}">
{% endthumbnail %}
</a>
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}

@ -1,10 +1,10 @@
<div class="section section_border">
<div class="section__center center center_sm">
<div class="content-block title">
{{ content.title }}
</div>
<div>
<img class="content-block pic" src="{{ content.img.image.url }}" alt="">
</div>
</div>
</div>
<div class="section section_border">
<div class="section__center center center_sm">
<div class="content-block title">
{{ content.title }}
</div>
<div>
<img class="content-block pic" src="{{ content.img.image.url }}" alt="">
</div>
</div>
</div>

@ -9,7 +9,7 @@
<img class="lessons__pic" src="{{ content.img.image.url }}">
</div>
</div>
<div class="lessons__content">{{ content.txt | safe }}</div>
<div class="lessons__content">{{ content.txt | safe | linebreaks }}</div>
</div>
</div>

@ -1,10 +1,10 @@
<div class="section section_border">
<div class="section__center center center_sm">
<div class="content-block title">
{{ content.title }}
</div>
<div class="content-block text" style="margin-bottom:0">
{{ content.txt | safe }}
</div>
</div>
<div class="section section_border">
<div class="section__center center center_sm">
<div class="content-block title">
{{ content.title }}
</div>
<div class="content-block text" style="margin-bottom:0">
{{ content.txt | safe }}
</div>
</div>
</div>

@ -3,10 +3,10 @@
<div class="content-block title">
{{ content.title }}
</div>
<div class="iframe__container">
<div class="content-block__video iframe__container">
{% if 'youtube.com' in content.url or 'youtu.be' in content.url %}
<iframe src="https://www.youtube.com/embed/{{ content.video_index }}" frameborder="0" allow="autoplay; encrypted-media"
allowfullscreen></iframe>
webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
{% elif 'vimeo.com' in content.url %}
<iframe src="https://player.vimeo.com/video/{{ content.video_index }}" frameborder="0" webkitallowfullscreen
mozallowfullscreen allowfullscreen>
@ -40,4 +40,4 @@
height: 360px;
}
}
</style>
</style>

@ -0,0 +1,47 @@
{% extends "templates/lilcity/index.html" %}
{% block content %}
<upload-contest-work contest-id="{{ contest.id }}"></upload-contest-work>
<div class="main main_default" {% if contest.cover %}style="background-image: url({{ contest.cover.image.url }});"{% endif %}>
<div class="main__center center">
<div class="main__title">
{{ contest.title }}
</div>
<div class="main__subtitle">
{{ contest.description }}
</div>
<div class="main__actions">
{% if not contest_work_uploaded and not contest.finished %}
<a class="main__btn btn" href=""
{% if request.user.is_authenticated %}data-show-upload-contest-work
{% else %}data-popup=".js-popup-auth"{% endif %}>Загрузить свою работу</a>
{% endif %}
</div>
</div>
</div>
<div style="text-align: center;">
{% for content in contest.content.all %}
{% with template="content/blocks/"|add:content.ctype|add:".html" %}
{% include template %}
{% endwith %}
{% endfor %}
<div class="section">
<div class="section__center center">
<a id="gallery" name="gallery">
<div class="title title_center">Галерея</div>
</a>
<div class="text">
{% if not contest_work_uploaded and not contest.finished %}
<a class="btn" href=""
{% if request.user.is_authenticated %}data-show-upload-contest-work
{% else %}data-popup=".js-popup-auth"{% endif %}>Загрузить свою работу</a>
{% endif %}
</div>
<contest-works contest-id="{{ contest.id }}" autoload="true"></contest-works>
</div>
</div>
</div>
{% endblock content %}

@ -0,0 +1,18 @@
{% extends "templates/lilcity/edit_index.html" %}
{% load static %}
{% block title %}
{% if object %}
Редактирование конкурса {{ object.title }}
{% else %}
Создание конкурса
{% endif %}
{% endblock title %}
{% block content %}
<contest-redactor {% if object and object.id %}:contest-id="{{ object.id }}"{% endif %}></contest-redactor>
{% endblock content %}
{% block foot %}
<script type="text/javascript" src="{% static 'contestRedactor.js' %}"></script>
<link rel="stylesheet" href="{% static 'contestRedactor.css' %}" />
{% endblock foot %}

@ -0,0 +1,118 @@
{% extends "templates/lilcity/index.html" %}
{% load rupluralize from plural %}
{% load static %}
{% block title %}Конкурс Lil.School{% endblock title %}
{% block description %}Работа {{ contest_work.child_full_name }}, {{ contest_work.age | rupluralize:"год,года,лет" }}{% endblock description%}
{% block ogdescription %}Работа {{ contest_work.child_full_name }}, {{ contest_work.age | rupluralize:"год,года,лет" }}{% endblock ogdescription %}
{% block ogimage %}http://{{request.META.HTTP_HOST}}{{ contest_work.image.image.url }}{% endblock ogimage %}
{% block head %}
<meta property="og:image:height" content="{{ contest_work.image.image.height }}">
<meta property="og:image:width" content="{{ contest_work.image.image.width }}">
{% endblock head %}
{% block content %}
<div class="section" style="padding-bottom: 25px;">
<div class="section__center center center_sm">
<div class="go">
<a class="go__item" href="{% url 'contest' contest_work.contest.slug %}">
<div class="go__arrow">
<svg class="icon icon-arrow-left">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-arrow-left"></use>
</svg>
</div>
<div class="go__title">Вернуться к&nbsp;галерее</div>
</a>
</div>
</div>
<div class="contest-work section__center center center_sm">
<div class="contest-work__img-wrap">
<img class="contest-work__img" src="{{ contest_work.image.image.url }}">
</div>
<div class="contest-work__info">
<div class="contest-work__bio">
<div>{{ contest_work.child_full_name }}</div>
<div class="contest-work__age">{{ contest_work.age | rupluralize:"год,года,лет" }}</div>
</div>
<div class="contest-work__likes">
<likes obj-type="contest_work" obj-id="{{ contest_work.id }}"
{% if user_liked %}:user-liked="true"{% endif %} likes="{{ likes_count }}"></likes>
</div>
</div>
</div>
</div>
<div class="section" style="padding: 0;">
<div class="section__center center center_sm">
<div class="go">
{% if prev_contest_work %}
<a class="go__item" href="{% url 'contest_work' prev_contest_work.id %}">
<div class="go__arrow">
<svg class="icon icon-arrow-left">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-arrow-left"></use>
</svg>
</div>
<div class="go__title">Предыдущая работа</div>
</a>
{% else %}
<div class="go__item"></div>
{% endif %}
{% if next_contest_work %}
<a class="go__item" href="{% url 'contest_work' next_contest_work.id %}">
<div class="go__title">Следующая работа</div>
<div class="go__arrow">
<svg class="icon icon-arrow-right">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-arrow-right"></use>
</svg>
</div>
</a>
{% else %}
<div class="go__item"></div>
{% endif %}
</div>
</div>
</div>
<div class="section">
<div class="section__center center center_sm">
{% include 'templates/blocks/share.html' with share_object_name='работой' %}
</div>
</div>
<div class="section section_gray">
<div class="section__center center center_sm">
<div class="title">Оставьте комментарий:</div>
<div class="questions">
{% if request.user.is_authenticated %}
<form class="questions__form" method="post" action="{% url 'contest_work_comment' contest_work_id=contest_work.id %}">
<input type="hidden" name="reply_id">
<div class="questions__ava ava">
<img
class="ava__pic"
{% if request.user.photo %}
src="{{ request.user.photo.url }}"
{% else %}
src="{% static 'img/user_default.jpg' %}"
{% endif %}
>
</div>
<div class="questions__wrap">
<div class="questions__reply-info">В ответ на
<a href="" class="questions__reply-anchor">этот комментарий</a>.
<a href="#" class="questions__reply-cancel grey-link">Отменить</a>
</div>
<div class="questions__field">
<textarea class="questions__textarea"></textarea>
</div>
<button class="questions__btn btn btn_light">ОТПРАВИТЬ</button>
</div>
</form>
{% else %}
<div>Только зарегистрированные пользователи могут оставлять комментарии.</div>
{% endif %}
<div class="questions__list">
{% include "templates/blocks/comments.html" with object=contest_work %}
</div>
</div>
</div>
</div>
{% endblock content %}

@ -1,2 +1,112 @@
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from django.template import loader
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 django.views.generic import TemplateView, DetailView
from apps.content.models import Contest, ContestWork
from apps.course.models import ContestWorkComment
@method_decorator(login_required, name='dispatch')
class ContestEditView(TemplateView):
template_name = 'content/contest_edit.html'
query_pk_and_slug = True
def get(self, request, slug=None, lesson=None):
if slug:
self.object = get_object_or_404(Contest, slug=slug)
else:
self.object = Contest()
return super().get(request)
def get_context_data(self):
context = super().get_context_data()
context['object'] = self.object
return context
class ContestView(DetailView):
model = Contest
context_object_name = 'contest'
template_name = 'content/contest.html'
query_pk_and_slug = True
def get_context_data(self, *args, **kwargs):
context = super().get_context_data()
if self.request.user.is_authenticated:
context['contest_work_uploaded'] = ContestWork.objects.filter(user=self.request.user).exists()
return context
class ContestWorkView(DetailView):
model = ContestWork
context_object_name = 'contest_work'
template_name = 'content/contest_work.html'
def get_context_data(self, *args, **kwargs):
context = super().get_context_data()
prev_contest_work = ContestWork.objects.filter(created_at__gt=self.object.created_at)[:1]
if prev_contest_work:
context['prev_contest_work'] = prev_contest_work[0]
next_contest_work = ContestWork.objects.filter(created_at__lt=self.object.created_at)[:1]
if next_contest_work:
context['next_contest_work'] = next_contest_work[0]
context['user_liked'] = self.object.likes.filter(user=self.request.user).exists() \
if self.request.user.is_authenticated else False
context['likes_count'] = self.object.likes.filter(user__is_active=True).count()
return context
@login_required
@csrf_exempt
@require_http_methods(['POST'])
def contest_work_comment(request, contest_work_id):
try:
contest_work = ContestWork.objects.get(id=contest_work_id)
except ContestWork.DoesNotExist:
return JsonResponse({
'success': False,
'errors': ['Contest_work with id f{contest_work_id} not found']
}, status=400)
else:
reply_to = request.POST.get('reply_id', 0)
comment = request.POST.get('comment', '')
if not comment:
return JsonResponse({
'success': False,
'errors': ['Comment can not be empty']
}, status=400)
if not int(reply_to):
contest_work_comment = ContestWorkComment.objects.create(
author=request.user,
content=comment,
contest_work=contest_work,
)
else:
try:
_contest_work_comment = ContestWorkComment.objects.get(id=reply_to)
except ContestWorkComment.DoesNotExist:
return JsonResponse({
'success': False,
'errors': ['LessonComment with id f{reply_to} not found']
}, status=400)
else:
contest_work_comment = ContestWorkComment.objects.create(
author=request.user,
content=comment,
contest_work=contest_work,
parent=_contest_work_comment,
)
ctx = {'node': contest_work_comment, 'user': request.user}
html = loader.render_to_string('templates/blocks/comment.html', ctx)
return JsonResponse({
'success': True,
'comment': html,
})

@ -4,7 +4,7 @@ from .models import Course
class CourseFilter(django_filters.FilterSet):
category = django_filters.CharFilter(field_name='category__title', lookup_expr='iexact')
category = django_filters.CharFilter(field_name='category')
class Meta:
model = Course

@ -0,0 +1,18 @@
# Generated by Django 2.0.6 on 2018-08-08 01:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course', '0039_lesson_position'),
]
operations = [
migrations.AddField(
model_name='course',
name='age',
field=models.SmallIntegerField(choices=[(0, ''), (1, 'до 5'), (2, '5-7'), (3, '7-9'), (4, '9-12'), (5, '12-15'), (6, '15-18'), (7, 'от 18')], default=0),
),
]

@ -0,0 +1,36 @@
# Generated by Django 2.0.6 on 2018-08-13 13:06
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('content', '0021_auto_20180813_1306'),
('course', '0040_course_age'),
]
operations = [
migrations.CreateModel(
name='ContestWorkComment',
fields=[
('comment_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='course.Comment')),
('contest_work', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='content.ContestWork')),
],
options={
'ordering': ('tree_id', 'lft'),
'abstract': False,
'base_manager_name': 'objects',
},
bases=('course.comment',),
),
migrations.AlterModelOptions(
name='course',
options={'ordering': ['-is_featured'], 'verbose_name': 'Курс', 'verbose_name_plural': 'Курсы'},
),
migrations.RemoveField(
model_name='course',
name='age',
),
]

@ -0,0 +1,18 @@
# Generated by Django 2.0.6 on 2018-08-16 16:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course', '0041_auto_20180813_1306'),
]
operations = [
migrations.AddField(
model_name='like',
name='ip',
field=models.GenericIPAddressField(blank=True, null=True),
),
]

@ -0,0 +1,29 @@
# Generated by Django 2.0.6 on 2018-08-24 21:32
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('course', '0042_like_ip'),
]
operations = [
migrations.AlterField(
model_name='course',
name='cover',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='course_covers', to='content.ImageObject', verbose_name='Обложка курса'),
),
migrations.AlterField(
model_name='lesson',
name='cover',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='lesson_covers', to='content.ImageObject', verbose_name='Обложка урока'),
),
migrations.AlterField(
model_name='material',
name='cover',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='material_covers', to='content.ImageObject', verbose_name='Обложка материала'),
),
]

@ -1,25 +1,25 @@
import arrow
from random import shuffle
from uuid import uuid4
from django.db import models
from django.core.exceptions import ValidationError
from django.utils import timezone
from django.utils.text import slugify
from django.utils.timezone import now
from django.contrib.auth import get_user_model
from django.urls import reverse_lazy
from django.conf import settings
from polymorphic_tree.models import PolymorphicMPTTModel, PolymorphicTreeForeignKey
from project.mixins import BaseModel, DeactivatedMixin
from .manager import CategoryQuerySet
from apps.content.models import ImageObject, Gallery, Video
from apps.content.models import ImageObject, Gallery, Video, ContestWork
User = get_user_model()
class Like(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
ip = models.GenericIPAddressField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True)
@ -65,7 +65,7 @@ class Course(BaseModel, DeactivatedMixin):
)
cover = models.ForeignKey(
ImageObject, related_name='course_covers',
verbose_name='Обложка курса', on_delete=models.CASCADE,
verbose_name='Обложка курса', on_delete=models.SET_NULL,
null=True, blank=True,
)
price = models.DecimalField(
@ -97,7 +97,7 @@ class Course(BaseModel, DeactivatedMixin):
class Meta:
verbose_name = 'Курс'
verbose_name_plural = 'Курсы'
ordering = ['-created_at']
ordering = ['-is_featured', ]
def __str__(self):
return str(self.id) + ' ' + self.title
@ -129,11 +129,11 @@ class Course(BaseModel, DeactivatedMixin):
@property
def deferred_start_at_humanize(self):
return arrow.get(self.deferred_start_at).humanize(locale='ru')
return arrow.get(self.deferred_start_at, settings.TIME_ZONE).humanize(locale='ru')
@property
def created_at_humanize(self):
return arrow.get(self.created_at).humanize(locale='ru')
return arrow.get(self.created_at, settings.TIME_ZONE).humanize(locale='ru')
@property
def is_deferred_start(self):
@ -148,6 +148,24 @@ class Course(BaseModel, DeactivatedMixin):
def count_videos_in_lessons(self):
return Video.objects.filter(lesson__in=self.lessons.all()).count()
@classmethod
def shuffle(cls, qs, order_is_featured = True):
if order_is_featured:
featured = []
other = []
for c in list(qs):
if c.is_featured:
featured.append(c)
else:
other.append(c)
shuffle(featured)
shuffle(other)
return featured + other
else:
all = list(qs)
shuffle(all)
return all
class Category(models.Model):
title = models.CharField('Название категории', max_length=100)
@ -172,7 +190,7 @@ class Lesson(BaseModel, DeactivatedMixin):
)
cover = models.ForeignKey(
ImageObject, related_name='lesson_covers',
verbose_name='Обложка урока', on_delete=models.CASCADE,
verbose_name='Обложка урока', on_delete=models.SET_NULL,
null=True, blank=True,
)
created_at = models.DateTimeField(auto_now_add=True)
@ -204,7 +222,7 @@ class Material(models.Model):
title = models.CharField('Название материала', max_length=100)
cover = models.ForeignKey(
ImageObject, related_name='material_covers',
verbose_name='Обложка материала', on_delete=models.CASCADE,
verbose_name='Обложка материала', on_delete=models.SET_NULL,
null=True, blank=True,
)
short_description = models.TextField('Краткое описание материала')
@ -234,7 +252,7 @@ class Comment(PolymorphicMPTTModel, DeactivatedMixin):
@property
def created_at_humanize(self):
return arrow.get(self.created_at).humanize(locale='ru')
return arrow.get(self.created_at, settings.TIME_ZONE).humanize(locale='ru')
def __str__(self):
return self.content
@ -264,3 +282,7 @@ class LessonComment(Comment):
class Meta(Comment.Meta):
verbose_name = 'Комментарий урока'
verbose_name_plural = 'Комментарии уроков'
class ContestWorkComment(Comment):
contest_work = models.ForeignKey(ContestWork, on_delete=models.CASCADE, related_name='comments')

@ -48,7 +48,7 @@
</a>
<div class="courses__details">
<a class="courses__theme theme {{ theme_color }}"
href="{% url 'courses' %}?category={{ course.category.title }}">{{ course.category | upper }}</a>
href="{% url 'courses' %}?category={{ course.category.id }}">{{ course.category | upper }}</a>
{% if not course.is_free %}
<div class="courses__price">{{ course.price|floatformat:"-2" }}₽</div>
{% endif %}

@ -70,7 +70,7 @@
</a>
</div>
<div class="course__title title">{{ course.title }}</div>
<div class="course__content">{{ course.short_description }}</div>
<div class="course__content">{{ course.short_description | linebreaks }}</div>
<a href="{% url 'user' course.author.id %}">
<div class="course__user user">
{% if course.author.photo %}
@ -158,13 +158,13 @@
{% else %}
<a
class="course__action btn btn_lg{% if only_lessons %} btn_stroke{% else %} btn_gray{% endif %}"
{% if paid %}
{% if paid or not course.price %}
href="{% url 'course-only-lessons' course.id %}"
{% else %}
data-popup=".js-popup-course-lock"
{% endif %}
>УРОКИ
{% if not paid %}
{% if not paid and course.price %}
<svg class="icon icon-lock">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-lock"></use>
</svg>
@ -227,7 +227,7 @@
</div>
{% for content in course.content.all %}
{% with template="course/content/"|add:content.ctype|add:".html" %}
{% with template="content/blocks/"|add:content.ctype|add:".html" %}
{% include template %}
{% endwith %}
@ -270,7 +270,7 @@
</a>
</div>
<div class="course__title title">{{ course.title }}</div>
<div class="course__content">{{ course.short_description }}</div>
<div class="course__content">{{ course.short_description | linebreaks }}</div>
<a href="{% url 'user' course.author.id %}">
<div class="course__user user user_white">
{% if course.author.photo %}

@ -25,7 +25,7 @@
</div>
<div class="go__title">Вернуться</div>
</a>
{% if not paid and course.price %}
{% if not paid and course.price and not has_full_access %}
<a
class="go__btn btn{% if pending %} btn_gray{% endif %} btn_md"
{% if user.is_authenticated %}
@ -154,10 +154,10 @@
</a>
{% else %}
<a
href="{% if paid %}{% url 'course-only-lessons' course.id %}{% else %}#{% endif %}"
href="{% if paid or not course.price %}{% url 'course-only-lessons' course.id %}{% else %}#{% endif %}"
class="course__action btn btn_lg{% if only_lessons %} btn_stroke{% else %} btn_gray{% endif %}"
>УРОКИ
{% if not paid %}
{% if not paid and course.price %}
<svg class="icon icon-lock">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-lock"></use>
</svg>
@ -180,19 +180,18 @@
<a href="#">
{% endif %}
<div class="lessons__item">
<div class="lessons__subtitle subtitle">{{ forloop.counter }}. {{ lesson.title }}</div>
<div class="lessons__row">
{% if lesson.cover %}
<div class="lessons__preview">
<div class="lessons__pic-wrapper">
<img class="lessons__pic" src="{{ lesson.cover.image.url }}">
<img class="lessons__pic"
src="{% if lesson.cover %}{{ lesson.cover.image_thumbnail.url }}{% else %}{% static 'img/no_cover.png' %}{% endif %}">
</div>
</div>
{% endif %}
<div class="lessons__content">{{ lesson.short_description | truncatechars_html:800 | safe }}</div>
</div>
<div class="lessons__row">
<a href="{% url 'lesson' pk=lesson.id %}" class="btn btn_stroke">Перейти к уроку</a>
<div>
<div class="lessons__subtitle subtitle">{{ forloop.counter }}. {{ lesson.title }}</div>
<div class="lessons__content">{{ lesson.short_description | truncatechars_html:800 | safe }}</div>
<a href="{% url 'lesson' pk=lesson.id %}" class="btn btn_stroke">Перейти к уроку</a>
</div>
</div>
</div>
</a>
@ -234,31 +233,7 @@
</div>
<div class="section">
<div class="section__center center center_sm">
<div class="share">
<div class="share__title">Поделиться курсом</div>
<div class="share__list likely">
<a class="share__item facebook" href="#">
<svg class="icon icon-share-facebook">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-facebook"></use>
</svg>
</a>
<a class="share__item twitter" href="#">
<svg class="icon icon-share-twitter">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-twitter"></use>
</svg>
</a>
<a class="share__item gplus" href="#">
<svg class="icon icon-share-google">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-google"></use>
</svg>
</a>
<a class="share__item pinterest" href="#">
<svg class="icon icon-share-pinterest">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-pinterest"></use>
</svg>
</a>
</div>
</div>
{% include 'templates/blocks/share.html' with share_object_name='курсом' %}
</div>
</div>
{% endblock content %}

@ -20,8 +20,8 @@
<div class="head__right">
<div class="head__field field">
<div class="field__wrap">
<div class="field__select select js-select{% if category %} selected{% endif %}">
<div class="select__head js-select-head">{% if category %}{{ category.0 }}{% else %}Категории{% endif %}</div>
<div class="field__select select js-select{% if category %} selected{% endif %}" data-category-select>
<div class="select__head js-select-head">Категории</div>
<div class="select__drop js-select-drop">
<div class="select__option js-select-option{% if not category %} active{% endif %}"
data-category-option data-category-url="{% url 'courses' %}">

@ -1,5 +1,6 @@
{% for cat in category_items %}
<div class="select__option js-select-option{% if category and category.0 == cat.title %} active{% endif %}" data-category-option data-category-name="{{ cat.title }}" data-category-url="{% url 'courses' %}?category={{ cat.title }}">
<div class="select__option js-select-option{% if category == cat.id %} active{% endif %}"
data-category-option data-category-name="{{ cat.title }}" data-category-url="{% url 'courses' %}?category={{ cat.id }}">
<div class="select__title">{{ cat.title }}</div>
</div>
{% endfor %}

@ -2,7 +2,7 @@
<div class="header__title">Все курсы</div>
</a>
{% for cat in category_items %}
<a class="header__link{% if category.0 == cat.title %} active{% endif %}" data-category-name="{{ cat.title }}" href="{% url 'courses' %}?category={{ cat.title }}">
<a class="header__link{% if category == cat.id %} active{% endif %}" data-category-name="{{ cat.title }}" href="{% url 'courses' %}?category={{ cat.id }}">
<div class="header__title">{{ cat.title }}</div>
</a>
{% endfor %}

@ -2,6 +2,9 @@
{% load static %}
{% block title %}{{ lesson.title }} - {{ block.super }}{% endblock title %}
{% block twurl %}{{ request.build_absolute_uri }}{% endblock twurl %}
{% block ogtitle %}{{ lesson.title }} - {{ block.super }}{% endblock ogtitle %}
{% block ogurl %}{{ request.build_absolute_uri }}{% endblock ogurl %}
{% block ogimage %}http://{{request.META.HTTP_HOST}}{% if lesson.course.cover %}{{ lesson.course.cover.image.url }}{% else %}{% static 'img/og_courses.jpg' %}{% endif %}{% endblock ogimage %}
{% block content %}
<div class="section" style="margin-bottom:0;padding-bottom:0">
@ -27,24 +30,43 @@
{% endif %}
</div>
<div class="lesson">
<div class="lesson__subtitle subtitle">{{ lesson.title }}</div>
<div class="lesson__content">{{ lesson.short_description }}</div>
{% comment %} <a class="lesson__video video" href="#">
{% if lesson.cover %}
<img class="video__pic" src="{{ lesson.cover.image.url }}"/>
{% else %}
{% endif %}
<svg class="icon icon-play">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-play"></use>
</svg>
</a> {% endcomment %}
<div class="lesson__row">
<div class="lesson__preview">
<div class="lesson__pic-wrapper">
<img class="lesson__pic"
src="{% if lesson.cover %}{{ lesson.cover.image_thumbnail.url }}{% else %}{% static 'img/no_cover.png' %}{% endif %}">
</div>
</div>
<div>
<div class="lesson__subtitle subtitle">{{ lesson.title }}</div>
<div class="lesson__content">{{ lesson.short_description | linebreaks }}</div>
<a href="{% url 'user' lesson.author.id %}">
<div class="lesson__user user">
{% if lesson.author.photo %}
<div class="user__ava ava">
<img class="ava__pic" src="{{ lesson.author.photo.url }}">
</div>
{% else %}
<div class="user__ava ava">
<img class="ava__pic" src="{% static 'img/user_default.jpg' %}">
</div>
{% endif %}
<div class="user__info">
<div class="user__name">{{ lesson.author.get_full_name }}</div>
<div class="user__meta">
<div class="user__date">{{ lesson.created_at_humanize }}</div>
</div>
</div>
</div>
</a>
</div>
</div>
</div>
</div>
</div>
{% for content in lesson.content.all %}
{% with template="course/content/"|add:content.ctype|add:".html" %}
{% with template="content/blocks/"|add:content.ctype|add:".html" %}
{% include template %}
{% endwith %}
@ -102,6 +124,7 @@
{% endif %}
default-author-picture="{% static 'img/user_default.jpg' %}"
access-token="{{ request.user.auth_token }}"></comments>
</div>
</div>
</div>

@ -9,7 +9,7 @@ register = template.Library()
def category_items(category=None):
return {
'category_items': Category.objects.filter(courses__status=Course.PUBLISHED).exclude(courses=None).distinct(),
'category': category,
'category': int(category[0]) if category and category[0] else None,
}
@ -17,5 +17,5 @@ def category_items(category=None):
def category_menu_items(category=None):
return {
'category_items': Category.objects.filter(courses__status=Course.PUBLISHED).exclude(courses=None).distinct(),
'category': category,
'category': int(category[0]) if category and category[0] else None,
}

@ -189,9 +189,13 @@ class CourseView(DetailView):
def get(self, request, *args, **kwargs):
response = super().get(request, *args, **kwargs)
context = self.get_context_data()
if (not request.user.is_authenticated and self.object.status != Course.PUBLISHED) or\
(request.user.is_authenticated and request.user.role < User.AUTHOR_ROLE and self.object.author != request.user and self.only_lessons and not context['paid']):
raise Http404
# если это не админ или автор
if not context.get('has_full_access'):
# если это не опубл курс или это страница уроков, курс платный, а юзер не оплатил курс - 404
if (self.object.status != Course.PUBLISHED) or \
(self.only_lessons and self.object.price and not context.get('paid')):
raise Http404
return response
# ((self.object.status != Course.PUBLISHED and request.user.role != User.ADMIN_ROLE) or
@ -216,6 +220,9 @@ class CourseView(DetailView):
context['only_lessons'] = self.only_lessons
if self.only_lessons:
context['lessons'] = self.object.lessons.order_by('position')
context['is_owner'] = self.object.author == self.request.user
context['is_admin'] = self.request.user.role == User.ADMIN_ROLE
context['has_full_access'] = context['is_owner'] or context['is_admin']
return context
def get_queryset(self):
@ -234,7 +241,6 @@ class CoursesView(ListView):
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
allow_empty = self.get_allow_empty()
if request.is_ajax():
context = self.get_context_data()
template_name = self.get_template_names()
@ -276,6 +282,7 @@ class CoursesView(ListView):
context = super().get_context_data()
filtered = CourseFilter(self.request.GET)
context.update(filtered.data)
context['course_items'] = Course.shuffle(context.get('course_items'))
return context
def get_template_names(self):
@ -292,10 +299,20 @@ class LessonView(DetailView):
def get(self, request, *args, **kwargs):
response = super().get(request, *args, **kwargs)
if (self.object.course.status != Course.PUBLISHED and not
(request.user.role == User.ADMIN_ROLE or
self.object.course.author == request.user)):
raise Http404
paid = request.user.is_authenticated and self.object.course.payments.filter(
user=self.request.user,
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
).exists()
# если это не автор или админ
if not (request.user.is_authenticated and
(request.user.role == User.ADMIN_ROLE or self.object.course.author == request.user)):
# если курс не опубликован или он платный и не оплачен - 404
if self.object.course.status != Course.PUBLISHED or (self.object.course.price and not paid):
raise Http404
return response
def get_context_data(self, **kwargs):

@ -0,0 +1,22 @@
from decimal import Decimal
from django.core.management.base import BaseCommand
from django.db.models import F
from apps.payment.models import Payment, AuthorBalance
class Command(BaseCommand):
help = 'Fix payment and author balance amount based on payment.data.effective_price_amount'
def handle(self, *args, **options):
for payment in Payment.objects.exclude(data__effective_price_amount=''):
if payment.data.get('effective_price_amount'):
payment.amount = Decimal(payment.data.get('effective_price_amount'))
payment.save()
for ab in AuthorBalance.objects.exclude(payment__amount=F('amount')).select_related('payment'):
ab.amount = ab.payment.amount
ab.save()

@ -1,4 +1,5 @@
import arrow
from django.db.models import Func, F
from paymentwall import Pingback
from polymorphic.models import PolymorphicModel
@ -9,8 +10,9 @@ from django.contrib.auth import get_user_model
from django.contrib.postgres.fields import ArrayField, JSONField
from django.core.validators import RegexValidator
from django.utils.timezone import now
from django.conf import settings
from project.utils import weekday_in_date_range
from project.utils import weekdays_in_date_range
from apps.course.models import Course
from apps.config.models import Config
@ -108,6 +110,61 @@ class Payment(PolymorphicModel):
verbose_name_plural = 'Платежи'
ordering = ('created_at',)
@classmethod
def calc_amount(cls, course_payment=None, school_payment=None, user=None, course=None, weekdays=None):
if course_payment:
course = course_payment.course
user = course_payment.user
if school_payment:
user = school_payment.user
weekdays = school_payment.weekdays
discount = 0
price = 0
if course:
price = course.price
else:
if user:
school_payments = SchoolPayment.objects.filter(
user=user,
date_start__lte=now().date(),
date_end__gte=now().date(),
add_days=False,
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
)
school_schedules_purchased = school_payments.annotate(
joined_weekdays=Func(F('weekdays'), function='unnest', )
).values_list('joined_weekdays', flat=True).distinct()
weekdays = set(map(int, weekdays)) - set(school_schedules_purchased)
prev_school_payment = school_payments.last()
add_days = bool(prev_school_payment)
else:
add_days = False
school_schedules = SchoolSchedule.objects.filter(
weekday__in=weekdays,
)
if add_days:
weekdays_count = weekdays_in_date_range(now().date(), prev_school_payment.date_end)
all_weekdays_count = weekdays_in_date_range(prev_school_payment.date_start, prev_school_payment.date_end)
for ss in school_schedules:
price += ss.month_price // all_weekdays_count.get(ss.weekday, 0) * weekdays_count.get(
ss.weekday, 0)
else:
price = school_schedules.aggregate(
models.Sum('month_price'),
).get('month_price__sum', 0)
if not (school_payment and school_payment.id) and price >= config.SERVICE_DISCOUNT_MIN_AMOUNT:
discount = config.SERVICE_DISCOUNT
amount = price - discount
return {
'price': price,
'amount': amount,
'discount': discount,
}
def calc_commission(self):
return self.amount * config.SERVICE_COMMISSION / 100
@ -136,7 +193,9 @@ class CoursePayment(Payment):
verbose_name_plural = 'Платежи за курсы'
def save(self, *args, **kwargs):
self.amount = self.course.price
if self.status is None:
amount_data = Payment.calc_amount(course_payment=self)
self.amount = amount_data.get('amount')
super().save(*args, **kwargs)
author_balance = getattr(self, 'authorbalance', None)
if not author_balance:
@ -168,34 +227,11 @@ class SchoolPayment(Payment):
return days
def save(self, *args, **kwargs):
aggregate = SchoolSchedule.objects.filter(
weekday__in=self.weekdays,
).aggregate(
models.Sum('month_price'),
)
if self.add_days:
_school_payment = SchoolPayment.objects.filter(
add_days=False,
date_start__lte=self.date_start,
date_end__gte=self.date_end,
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
).last()
weekday_count = weekday_in_date_range(self.date_start, self.date_end, self.weekdays[0])
all_weekday_count = weekday_in_date_range(_school_payment.date_start, _school_payment.date_end, self.weekdays[0])
month_price_sum = aggregate.get('month_price__sum', 0) * weekday_count // all_weekday_count
else:
month_price_sum = aggregate.get('month_price__sum', 0)
if month_price_sum >= config.SERVICE_DISCOUNT_MIN_AMOUNT:
discount = config.SERVICE_DISCOUNT
else:
discount = 0
self.amount = month_price_sum - discount
if self.status is None:
amount_data = Payment.calc_amount(school_payment=self)
self.amount = amount_data.get('amount')
super().save(*args, **kwargs)
@property
def date_end_humanize(self):
return arrow.get(self.date_end).humanize(locale='ru')
return arrow.get(self.date_end, settings.TIME_ZONE).humanize(locale='ru')

@ -5,7 +5,7 @@
{% if school %}
<div class="done__title title">Вы успешно приобрели доступ к урокам онлайн-школы!</div>
<div class="done__foot">
<a class="done__btn btn btn_md btn_stroke" href="{% url 'user' request.user.id %}">ПЕРЕЙТИ К ШКОЛЕ</a>
<a class="done__btn btn btn_md btn_stroke" href="{% url 'school:school' %}">ПЕРЕЙТИ К ШКОЛЕ</a>
</div>
{% else %}
<div class="done__title title">Вы успешно приобрели курс!</div>

@ -1,3 +1,5 @@
from decimal import Decimal
import arrow
import json
import logging
@ -17,6 +19,7 @@ from django.views.decorators.csrf import csrf_exempt
from django.urls import reverse_lazy
from django.utils.decorators import method_decorator
from django.utils.timezone import now
from django.conf import settings
from paymentwall import Pingback, Product, Widget
@ -97,7 +100,6 @@ class SchoolBuyView(TemplateView):
host = urlsplit(self.request.META.get('HTTP_REFERER'))
host = str(host[0]) + '://' + str(host[1])
weekdays = set(request.GET.getlist('weekdays', []))
add_days = 'add_days' in request.GET
roistat_visit = request.COOKIES.get('roistat_visit', None)
if not weekdays:
messages.error(request, 'Выберите несколько дней недели.')
@ -107,24 +109,30 @@ class SchoolBuyView(TemplateView):
except ValueError:
messages.error(request, 'Ошибка выбора дней недели.')
return redirect('school:summer-school')
prev_school_payment = SchoolPayment.objects.filter(
user=request.user,
date_start__lte=now().date(),
date_end__gte=now().date(),
add_days=False,
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
).first() # ??? first?
add_days = bool(prev_school_payment)
if add_days:
_school_payment = SchoolPayment.objects.get(
user=request.user,
date_start__lte=now().date(),
date_end__gte=now().date(),
add_days=False,
)
school_payment = SchoolPayment.objects.create(
user=request.user,
weekdays=weekdays,
date_start=now().date(),
date_end=_school_payment.date_end,
date_end=prev_school_payment.date_end,
add_days=True,
roistat_visit=roistat_visit,
)
if school_payment.amount <= 0:
messages.error(request, 'Выбранные дни отсутствуют в оставшемся периоде подписки')
return redirect(reverse_lazy('school:summer-school'))
return redirect(reverse_lazy('school:school'))
else:
school_payment = SchoolPayment.objects.create(
user=request.user,
@ -156,12 +164,13 @@ class SchoolBuyView(TemplateView):
@method_decorator(csrf_exempt, name='dispatch')
class PaymentwallCallbackView(View):
def add_months(self, sourcedate, months):
month = sourcedate.month - 1 + months
year = sourcedate.year + month // 12
month = month % 12 + 1
day = min(sourcedate.day, calendar.monthrange(year, month)[1])
return datetime.date(year, month, day)
def add_months(self, sourcedate, months=1):
result = arrow.get(sourcedate, settings.TIME_ZONE).shift(months=months)
if months == 1:
if (sourcedate.month == 2 and sourcedate.day >= 28) or (sourcedate.day == 31 and result.day <= 30)\
or (sourcedate.month == 1 and sourcedate.day >= 29 and result.day == 28):
result = result.replace(day=1, month=result.month + 1)
return result.datetime
def get_request_ip(self):
x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR')
@ -196,12 +205,18 @@ class PaymentwallCallbackView(View):
payment.status = pingback.get_type()
payment.data = payment_raw_data
if pingback.is_deliverable():
effective_amount = payment_raw_data.get('effective_price_amount')
if effective_amount:
payment.amount = Decimal(effective_amount)
transaction_to_mixpanel.delay(
payment.user.id,
payment.amount,
now().strftime('%Y-%m-%dT%H:%M:%S'),
product_type_name,
)
if product_type_name == 'school':
school_payment = SchoolPayment.objects.filter(
user=payment.user,
@ -216,17 +231,14 @@ class PaymentwallCallbackView(View):
).last()
if school_payment:
if payment.add_days:
date_start = self.add_months(sourcedate=now().replace(hour=0, minute=0, day=1), months=1)
date_start = now().date()
date_end = school_payment.date_end
else:
date_start = arrow.get(school_payment.date_end).shift(days=1).datetime
date_end = arrow.get(date_start).shift(months=1).datetime
date_start = arrow.get(school_payment.date_end, settings.TIME_ZONE).shift(days=1).datetime
date_end = self.add_months(date_start)
else:
#month = 0 if now().day >= 1 and now().day <= 10 else 1
# Логика июльского лагеря: до конца июля приобретаем только на текущий месяц
month = 0
date_start = self.add_months(sourcedate=now().replace(hour=0, minute=0, day=1), months=month)
date_end = arrow.get(date_start).shift(months=1, minutes=-1).datetime
date_start = now().date()
date_end = self.add_months(date_start)
payment.date_start = date_start
payment.date_end = date_end
if product_type_name == 'course':
@ -251,6 +263,7 @@ class PaymentwallCallbackView(View):
'update_at': payment.update_at,
}
payment.save()
product_payment_to_mixpanel.delay(
payment.user.id,
f'{product_type_name.title()} payment',
@ -268,6 +281,7 @@ class PaymentwallCallbackView(View):
product_type_name,
payment.roistat_visit,
)
author_balance = getattr(payment, 'author_balance', None)
if author_balance and author_balance.type == AuthorBalance.IN:
if pingback.is_deliverable():
@ -276,7 +290,6 @@ class PaymentwallCallbackView(View):
payment.author_balance.status = AuthorBalance.PENDING
else:
payment.author_balance.status = AuthorBalance.DECLINED
payment.author_balance.save()
return HttpResponse('OK')
else:

@ -0,0 +1,20 @@
# Generated by Django 2.0.6 on 2018-08-24 19:56
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('content', '0022_auto_20180815_2129'),
('school', '0018_auto_20180629_1501'),
]
operations = [
migrations.AddField(
model_name='schoolschedule',
name='cover',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='schoolschedule_cover', to='content.ImageObject', verbose_name='Обложка дня'),
),
]

@ -0,0 +1,29 @@
# Generated by Django 2.0.6 on 2018-08-24 21:32
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('school', '0019_schoolschedule_cover'),
]
operations = [
migrations.AlterField(
model_name='livelesson',
name='cover',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='livelesson_covers', to='content.ImageObject', verbose_name='Обложка урока школы'),
),
migrations.AlterField(
model_name='schoolschedule',
name='cover',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='schoolschedule_cover', to='content.ImageObject', verbose_name='Обложка дня'),
),
migrations.AlterField(
model_name='schoolscheduleimage',
name='img',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='schoolschedule_images', to='content.ImageObject', verbose_name='Объект изображения'),
),
]

@ -0,0 +1,18 @@
# Generated by Django 2.0.6 on 2018-08-27 21:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('school', '0020_auto_20180824_2132'),
]
operations = [
migrations.AddField(
model_name='schoolschedule',
name='trial_lesson',
field=models.URLField(blank=True, default=''),
),
]

@ -1,9 +1,10 @@
import arrow
from datetime import datetime, timedelta
from datetime import datetime, timedelta, date
from django.conf import settings
from django.db import models
from django.urls import reverse_lazy
from django.utils.functional import cached_property
from django.utils.timezone import now
from project.mixins import BaseModel, DeactivatedMixin
@ -13,6 +14,7 @@ from apps.payment import models as payment_models
class SchoolSchedule(models.Model):
WEEKDAY_SHORT_NAMES = ('пн', 'вт', 'ср', 'чт', 'пт', 'сб', 'вс')
WEEKDAY_CHOICES = (
(1, 'понедельник'),
(2, 'вторник'),
@ -37,6 +39,15 @@ class SchoolSchedule(models.Model):
'Скидка, в валюте', max_digits=8, decimal_places=2, default=0
)
start_at = models.TimeField('Начало урока', null=True)
cover = models.ForeignKey(
ImageObject,
related_name='schoolschedule_cover',
verbose_name='Обложка дня',
on_delete=models.SET_NULL,
null=True,
blank=True,
)
trial_lesson = models.URLField(default='', blank=True)
class Meta:
ordering = ('weekday',)
@ -50,15 +61,18 @@ class SchoolSchedule(models.Model):
end_at = datetime.combine(now().today(), self.start_at) + timedelta(hours=1)
return self.start_at <= now().time() and end_at.time() >= now().time() and self.weekday == now().isoweekday()
@cached_property
def current_live_lesson(self):
now_time = now()
september2018 = date(2018, 9, 1)
date_start = max(september2018, now().date())
live_lesson = LiveLesson.objects.filter(
date__week_day=self.weekday % 7 + 1,
date__range=[now_time.date(), (now_time + timedelta(days=6)).date()],
date__range=[date_start, date_start + timedelta(days=6)],
deactivated_at__isnull=True,
).first()
return live_lesson
@cached_property
def previous_live_lesson(self):
now_time = now()
live_lesson = LiveLesson.objects.filter(
@ -84,7 +98,7 @@ class SchoolScheduleImage(models.Model):
ImageObject,
related_name='schoolschedule_images',
verbose_name='Объект изображения',
on_delete=models.CASCADE,
on_delete=models.SET_NULL,
null=True,
blank=True,
)
@ -107,7 +121,7 @@ class LiveLesson(BaseModel, DeactivatedMixin):
ImageObject,
related_name='livelesson_covers',
verbose_name='Обложка урока школы',
on_delete=models.CASCADE,
on_delete=models.SET_NULL,
null=True,
blank=True,
)

@ -1,78 +0,0 @@
{% load static %} {% load thumbnail %}
<div class="timing__item{% if school_schedule.weekday > 5 %} timing__item_bg{% endif %} js-timing-item js-timing-toggle{% if not school_schedule.weekday in school_schedules_purchased %} disable{% endif %}">
<div class="timing__cell">
<div class="timing__info">
<div class="timing__day{% if school_schedule.is_online %} active{% endif %}">
{{ school_schedule }}
</div>
{% if live_lesson %}
<div class="timing__date">{{ live_lesson.date }}</div>
{% endif %}
</div>
<div class="timing__buy">
<div class="timing__time">{{ school_schedule.start_at }} (МСК)</div>
{% if school_schedule.weekday in school_schedules_purchased %}
{% if live_lesson and live_lesson.title %}
{% include './open_lesson.html' %}
{% endif %}
{% else %}
{% include './day_pay_btn.html' %}
{% endif %}
</div>
{% comment %}
<!-- это нужно чтобы в попапе продления школы всегда знать какие дни выбраны(куплены) -->
{% endcomment %}
{% if school_schedule.weekday in school_schedules_purchased %}
<span style="display: none;" data-purchased="{{ school_schedule.weekday }}"></span>
{% endif %}
</div>
<div class="timing__cell">
<div class="timing__preview">
{% thumbnail live_lesson.cover.image "70x70" crop="center" as im %}
<img class="timing__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" />
{% empty %}
<img class="timing__pic" src="{% static 'img/no_cover.png' %}" width="70px" height="70px" />
{% endthumbnail %}
</div>
</div>
<div class="timing__cell">
<div class="timing__title">{{ school_schedule.title }}{% if live_lesson and live_lesson.title %},
<span class="bold">{{ live_lesson.title }}</span>
{% endif %}
</div>
<div class="timing__content">
{% if live_lesson and live_lesson.short_description %}
{{ live_lesson.short_description }}
{% else %}
{{ school_schedule.description }}
{% endif %}
</div>
<div class="timing__more">
<div class="timing__head">Материалы</div>
<div class="timing__row">
<div class="timing__text">
{{ school_schedule.materials }}
</div>
</div>
{% if school_schedule.schoolschedule_images.exists %}
<div class="timing__head">Результаты прошлых уроков</div>
<div class="timing__works gallery">
{% for image in school_schedule.schoolschedule_images.all %}
{% thumbnail image.img.image "48x48" crop="center" as im %}
<a class="timing__work" href="{{ image.img.image.url }}">
<img class="timing__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}"/>
</a>
{% endthumbnail %}
{% endfor %}
</div>
{% endif %}
</div>
</div>
<div class="timing__cell">
<button class="timing__toggle">
<svg class="icon icon-arrow-down">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-arrow-down"></use>
</svg>
</button>
</div>
</div>

@ -1,7 +1,5 @@
<a
{% if not user.is_authenticated %}
data-popup=".js-popup-auth"
{% endif %}
data-popup=".js-popup-buy"
class="timing__btn btn"
href="{% url 'school-checkout' %}?weekdays={{ school_schedule.weekday }}&add_days=true"
data-day="{{ school_schedule.weekday }}"
>купить</a>

@ -1,7 +1,7 @@
{% load static %}
<a class="online" target="_blank" href="https://www.youtube.com/watch?v=PhZ8qQbIej0" style="background-image: url({% static 'img/video-1.jpg' %});">
<div class="online__center center">
<div class="online__type">ПРЯМОЙ ЭФИР</div>
<div class="online__type">ВИДЕОУРОКИ</div>
<div class="online__title">Каждый день в 17.00 (по Мск) </div>
<div class="online__text text">Кроме выходных. Запись эфира доступна по завершению трансляции</div>
<div class="online__action">

@ -17,7 +17,7 @@
<use xlink:href="{% static 'img/sprite.svg' %}#icon-online"></use>
</svg>
</div>
<div class="school__title">Прямой эфир</div>
<div class="school__title">Видеоуроки</div>
<div class="school__text">С понедельника по&nbsp;пятницу кроме выходных</div>
</div>
<div class="school__col">

@ -6,78 +6,11 @@
<div class="timing js-timing">
<div class="timing__week">
{% for school_schedule in school_schedules %}
{% with current_live_lesson=school_schedule.current_live_lesson %}
<div class="timing__item{% if school_schedule.weekday > 5 %} timing__item_bg{% endif %} js-timing-item js-timing-toggle">
<div class="timing__cell">
<div class="timing__info">
<div class="timing__day{% if school_schedule.is_online %} active{% endif %}">
{{ school_schedule }}
</div>
{% if current_live_lesson %}
<!--<div class="timing__date">{{ current_live_lesson.date }}</div>-->
{% endif %}
</div>
<div class="timing__buy">
<div class="timing__time">{{ school_schedule.start_at }} (МСК)</div>
<!--{% include './pay_btn.html' %}-->
</div>
</div>
<div class="timing__cell">
<div class="timing__preview">
{% thumbnail current_live_lesson.cover.image "70x70" crop="center" as im %}
<img class="timing__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}"/>
{% empty %}
<img class="timing__pic" src="{% static 'img/no_cover.png' %}" width="70px" height="70px"/>
{% endthumbnail %}
</div>
</div>
<div class="timing__cell">
<div class="timing__title">{{ school_schedule.title }}
{% if current_live_lesson and current_live_lesson.title %}
, <span class="bold">{{ current_live_lesson.title }}</span>
{% endif %}
</div>
<div class="timing__content">
{% if live_lesson %}
{{ live_lesson.short_description }}
{% else %}
{{ school_schedule.description }}
{% endif %}
</div>
<div class="timing__more">
<div class="timing__head">Материалы</div>
<div class="timing__row">
<div class="timing__text">
{{ school_schedule.materials }}
</div>
</div>
{% if school_schedule.schoolschedule_images.exists %}
<div class="timing__head">Результаты прошлых уроков</div>
<div class="timing__works gallery">
{% for image in school_schedule.schoolschedule_images.all %}
{% thumbnail image.img.image "48x48" crop="center" as im %}
<a class="timing__work" href="{{ image.img.image.url }}">
<img class="timing__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}"/>
</a>
{% endthumbnail %}
{% endfor %}
</div>
{% endif %}
</div>
</div>
<div class="timing__cell">
<button class="timing__toggle">
<svg class="icon icon-arrow-down">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-arrow-down"></use>
</svg>
</button>
</div>
</div>
{% endwith %}
{% include 'blocks/schedule_item.html' with school_schedule=school_schedule live_lesson=school_schedule.current_live_lesson purchased=True %}
{% endfor %}
</div>
<div class="timing__foot">
<!--{% include './pay_btn.html' %}-->
{# include './pay_btn.html' #}
<a target="_blank" class="timing__print" href="{% url 'school:school_schedules-print' %}">Распечатать расписание
<span class="bold">чтобы не забыть</span>
<svg class="icon icon-print">

@ -0,0 +1,102 @@
{% load static %} {% load thumbnail %}
<div class="timing__item{% if school_schedule.weekday > 5 %} timing__item_bg{% endif %} js-timing-item js-timing-toggle {% if print %}open{% endif %} {% if is_purchased and not school_schedule.weekday in school_schedules_purchased %} disable{% endif %}">
<div class="timing__cell timing__cell--info">
<div class="timing__info">
<div class="timing__day{% if school_schedule.is_online %} active{% endif %}">
{{ school_schedule }}
{% if not is_purchased and request.user_agent.is_mobile and school_schedule.trial_lesson %}
<a class="timing__trial-lesson js-video-modal" href="#" data-video-url="{{ school_schedule.trial_lesson }}">Пробный урок</a>
{% endif %}
</div>
{% if is_purchased and live_lesson %}
<div class="timing__date">{% if request.user_agent.is_mobile %}{{ live_lesson.date|date:"j b" }}{% else %}{{ live_lesson.date|date:"j E" }}{% endif %}</div>
{% endif %}
<div class="timing__time">{{ school_schedule.start_at }} (МСК)</div>
<div class="timing__buy">
{% if is_purchased and school_schedule.weekday in school_schedules_purchased %}
{% if live_lesson and live_lesson.title %}
{% include './open_lesson.html' %}
{% endif %}
{% else %}
{% include './day_pay_btn.html' %}
{% endif %}
{% if not is_purchased and not request.user_agent.is_mobile and school_schedule.trial_lesson %}
<a class="timing__trial-lesson js-video-modal" href="#" data-video-url="{{ school_schedule.trial_lesson }}">Пробный урок</a>
{% endif %}
</div>
</div>
{% comment %}
<!-- это нужно чтобы в попапе продления школы всегда знать какие дни выбраны(куплены) -->
{% endcomment %}
{% if school_schedule.weekday in school_schedules_purchased %}
<span style="display: none;" data-purchased="{{ school_schedule.weekday }}"></span>
{% endif %}
</div>
<div style="display: flex; width: 100%;">
<div class="timing__cell timing__cell--preview">
<div class="timing__preview">
{% comment %}
{% thumbnail live_lesson.cover.image "70x70" crop="center" as im %}
<img class="timing__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" />
{% empty %}
{% endcomment %}
<img class="timing__pic"
src="{% if school_schedule.cover %}{{ school_schedule.cover.image_thumbnail.url }}{% else %}{% static 'img/no_cover.png' %}{% endif %}"
style="width: 70px; height: 70px;" />
{% comment %}{% endthumbnail %}{% endcomment %}
</div>
</div>
<div class="timing__cell timing__cell--content" {% if print %}style="flex: 1 0 0;"{% endif %}>
<div class="timing__title">{{ school_schedule.title }}{% if live_lesson and live_lesson.title %},
<span class="bold">{{ live_lesson.title }}</span>
{% endif %}
</div>
<div class="timing__content">
{% comment %}
{% if live_lesson and live_lesson.short_description %}
{{ live_lesson.short_description }}
{% else %}
{% endcomment %}
{{ school_schedule.description }}
{% comment %}{% endif %}{% endcomment %}
</div>
<div class="timing__more">
<div class="timing__head">Материалы</div>
<div class="timing__row">
{% if print %}
<div class="timing__col" style="flex: 0 0 100%;">
<div class="timing__text">
{{ school_schedule.materials }}
</div>
</div>
{% else %}
<div class="timing__text">
{{ school_schedule.materials }}
</div>
{% endif %}
</div>
{% if not print and school_schedule.schoolschedule_images.exists %}
<div class="timing__head">Результаты прошлых уроков</div>
<div class="timing__works gallery">
{% for image in school_schedule.schoolschedule_images.all %}
{% thumbnail image.img.image "48x48" crop="center" as im %}
<a class="timing__work" href="{{ image.img.image.url }}">
<img class="timing__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}"/>
</a>
{% endthumbnail %}
{% endfor %}
</div>
{% endif %}
</div>
</div>
{% if not print %}
<div class="timing__cell timing__cell--toggle">
<button class="timing__toggle">
<svg class="icon icon-arrow-down">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-arrow-down"></use>
</svg>
</button>
</div>
{% endif %}
</div>
</div>

@ -41,10 +41,10 @@
{% for school_schedule in school_schedules %}
{% if is_previous %}
{% if school_schedule.previous_live_lesson in live_lessons %}
{% include './_schedule_purchased_item.html' with school_schedule=school_schedule live_lesson=school_schedule.previous_live_lesson %}
{% include 'blocks/schedule_item.html' with school_schedule=school_schedule live_lesson=school_schedule.previous_live_lesson purchased=True %}
{% endif %}
{% else %}
{% include './_schedule_purchased_item.html' with school_schedule=school_schedule live_lesson=school_schedule.current_live_lesson %}
{% include 'blocks/schedule_item.html' with school_schedule=school_schedule live_lesson=school_schedule.current_live_lesson purchased=True %}
{% endif %}
{% endfor %}
{% endif %}

@ -2,33 +2,38 @@
{% load static %}
{% block title %}{{ livelesson.title }} - {{ block.super }}{% endblock title %}
{% block twurl %}{{ request.build_absolute_uri }}{% endblock twurl %}
{% block ogtitle %}{{ livelesson.title }} - {{ block.super }}{% endblock ogtitle %}
{% block ogurl %}{{ request.build_absolute_uri }}{% endblock ogurl %}
{% if livelesson.cover and livelesson.cover.image %}
{% block ogimage %}http://{{request.META.HTTP_HOST}}{{ livelesson.cover.image.url }}{% endblock ogimage %}
{% endif %}
{% block content %}
<div class="section" style="margin-bottom:0;padding-bottom:0">
<div class="section__center center center_sm">
<div class="lesson">
<div class="lesson__subtitle subtitle">{{ livelesson.title }}</div>
<div class="lesson__content">{{ livelesson.short_description }}</div>
<a class="lesson__video video" href="#">
<div class="lesson__content">{{ livelesson.short_description | linebreaks }}</div>
<div class="lesson__video video">
{% if livelesson.stream_index %}
<iframe class="lesson__video_frame" src="https://player.vimeo.com/video/{{ livelesson.stream_index }}?autoplay=1" frameborder="0" webkitallowfullscreen
mozallowfullscreen allowfullscreen>
</iframe>
<a href="#" onclick="location.reload();">Если видео не загрузилось обновите страницу</a>
<span>Если видео не загрузилось, - уменьшите качество видео или <a href="#" onclick="location.reload();">обновите страницу</a></span>
<iframe class="lesson__chat_frame" src="https://vimeo.com/live-chat/{{ livelesson.stream_index }}" frameborder="0"></iframe>
{% else %}
{% if livelesson.cover %}
<img class="video__pic" src="{{ livelesson.cover.image.url }}"/>
{% else %}
{% endif %}
{% if livelesson.cover %}
<img class="video__pic" src="{{ livelesson.cover.image.url }}"/>
{% endif %}
{% endif %}
</a>
</div>
</div>
</div>
</div>
{% for content in livelesson.content.all %}
{% with template="course/content/"|add:content.ctype|add:".html" %}
{% with template="content/blocks/"|add:content.ctype|add:".html" %}
{% include template %}
{% endwith %}

@ -9,7 +9,7 @@
<div class="kit__body">
<div class="lessons__list">
{% for livelesson in livelesson_list %}
<div class="lessons__item" v-for="(lesson, index) in lessons">
<div class="lessons__item">
<div class="lessons__actions lessons__actions__no-hover">
<a target="_blank" class="lessons__action" href="{% url 'school:lesson-detail' livelesson.id %}">
<svg class="icon icon-eye">
@ -24,7 +24,7 @@
</div>
<div class="lessons__subtitle subtitle">{{ livelesson.date }} // {{ livelesson.title }}</div>
<div class="lessons__row">
<div class="lessons__content">{{ livelesson.short_description }}</div>
<div class="lessons__content">{{ livelesson.short_description | linebreaks }}</div>
</div>
</div>
{% endfor %}

@ -11,70 +11,16 @@
<link rel="stylesheet" media="all" href={% static "app.css" %}>
</head>
<body onload="window.print()">
<body onload="setTimeout(window.print, 1000)">
<div class="outer js-outer">
<div class="section" id="schedule">
<div class="section__center center center_md">
<div class="title title_center">Расписание</div>
<div class="timing js-timing">
<div class="timing__week">
{% for school_schedule in school_schedules %} {% with current_live_lesson=school_schedule.current_live_lesson %}
<div class="timing__item {% if school_schedule.weekday > 5 %} timing__item_bg{% endif %} js-timing-item open">
<div class="timing__cell">
<div class="timing__info">
<div class="timing__day{% if school_schedule.is_online %} active{% endif %}">
{{ school_schedule }}
</div>
{% if current_live_lesson %}
<!--<div class="timing__date">{{ current_live_lesson.date }}</div>-->
{% endif %}
</div>
<div class="timing__buy">
<div class="timing__time">{{ school_schedule.start_at }} (МСК)</div>
</div>
</div>
<div class="timing__cell">
<div class="timing__preview">
{% thumbnail current_live_lesson.cover.image "70x70" crop="center" as im %}
<img class="timing__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" /> {% empty %}
<img class="timing__pic" src="{% static 'img/no_cover.png' %}" width="70px" height="70px" /> {% endthumbnail %}
</div>
</div>
<div class="timing__cell">
<div class="timing__title">{{ school_schedule.title }}{% if current_live_lesson %},
<span class="bold">{{ current_live_lesson.title }}</span>
{% endif %}
</div>
<div class="timing__content">
{% if live_lesson %}
{{ live_lesson.short_description }}
{% else %}
{{ school_schedule.description }}
{% endif %}
</div>
<div class="timing__more">
<div class="timing__head">Материалы</div>
<div class="timing__row">
<div class="timing__col" style="flex: 0 0 100%;">
<div class="timing__text">
{{ school_schedule.materials }}
</div>
</div>
</div>
<!--{% if school_schedule.schoolschedule_images.exists %}-->
<!--<div class="timing__head">Результаты прошлых уроков</div>-->
<!--<div class="timing__works gallery">-->
<!--{% for image in school_schedule.schoolschedule_images.all %} {% thumbnail image.img.image "48x48" crop="center" as im %}-->
<!--<a class="timing__work" href="{{ image.img.image.url }}">-->
<!--<img class="timing__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" />-->
<!--</a>-->
<!--{% endthumbnail %} {% endfor %}-->
<!--</div>-->
<!--{% endif %}-->
</div>
</div>
</div>
{% endwith %} {% endfor %}
{% for school_schedule in school_schedules %}
{% include 'blocks/schedule_item.html' with school_schedule=school_schedule live_lesson=school_schedule.current_live_lesson print=True %}
{% endfor %}
</div>
</div>
</div>

@ -1,6 +1,6 @@
{% extends "templates/lilcity/index.html" %} {% load static %}
{% block title %}Онлайн-школа LilCity{% endblock title%}
{% block ogimage %}http://{{request.META.HTTP_HOST}}{% static 'img/og_summer_school.jpg' %}{% endblock %}
{% block ogimage %}http://{{request.META.HTTP_HOST}}{% static 'img/og_main.jpg' %}{% endblock %}
{% block content %}
{% if not is_purchased %}
{% include "../summer/promo.html" %}

@ -1,78 +0,0 @@
{% load static %} {% load thumbnail %}
<div class="timing__item{% if school_schedule.weekday > 5 %} timing__item_bg{% endif %} js-timing-item js-timing-toggle{% if not school_schedule.weekday in school_schedules_purchased %} disable{% endif %}">
<div class="timing__cell">
<div class="timing__info">
<div class="timing__day{% if school_schedule.is_online %} active{% endif %}">
{{ school_schedule }}
</div>
{% if live_lesson %}
<div class="timing__date">{{ live_lesson.date }}</div>
{% endif %}
<div class="timing__buy">
<div class="timing__time">{{ school_schedule.start_at }} (МСК)</div>
{% if school_schedule.weekday in school_schedules_purchased %}
{% if live_lesson and live_lesson.title %}
{% include './open_lesson.html' %}
{% endif %}
{% else %}
{% include './day_pay_btn.html' %}
{% endif %}
</div>
</div>
{% comment %}
<!-- это нужно чтобы в попапе продления школы всегда знать какие дни выбраны(куплены) -->
{% endcomment %}
{% if school_schedule.weekday in school_schedules_purchased %}
<span style="display: none;" data-purchased="{{ school_schedule.weekday }}"></span>
{% endif %}
</div>
<div class="timing__cell">
<div class="timing__preview">
{% thumbnail live_lesson.cover.image "70x70" crop="center" as im %}
<img class="timing__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" />
{% empty %}
<img class="timing__pic" src="{% static 'img/no_cover.png' %}" width="70px" height="70px" />
{% endthumbnail %}
</div>
</div>
<div class="timing__cell">
<div class="timing__title">{{ school_schedule.title }}{% if live_lesson and live_lesson.title %},
<span class="bold">{{ live_lesson.title }}</span>
{% endif %}
</div>
<div class="timing__content">
{% if live_lesson and live_lesson.short_description %}
{{ live_lesson.short_description }}
{% else %}
{{ school_schedule.description }}
{% endif %}
</div>
<div class="timing__more">
<div class="timing__head">Материалы</div>
<div class="timing__row">
<div class="timing__text">
{{ school_schedule.materials }}
</div>
</div>
{% if school_schedule.schoolschedule_images.exists %}
<div class="timing__head">Результаты прошлых уроков</div>
<div class="timing__works gallery">
{% for image in school_schedule.schoolschedule_images.all %}
{% thumbnail image.img.image "48x48" crop="center" as im %}
<a class="timing__work" href="{{ image.img.image.url }}">
<img class="timing__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}"/>
</a>
{% endthumbnail %}
{% endfor %}
</div>
{% endif %}
</div>
</div>
<div class="timing__cell">
<button class="timing__toggle">
<svg class="icon icon-arrow-down">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-arrow-down"></use>
</svg>
</button>
</div>
</div>

@ -3,5 +3,5 @@
data-popup=".js-popup-auth"
{% endif %}
class="timing__btn btn"
href="{% url 'school-checkout' %}?weekdays={{ school_schedule.weekday }}&add_days=true"
href="{% url 'school-checkout' %}?weekdays={{ school_schedule.weekday }}"
>купить</a>

@ -1,9 +1,9 @@
{% load static %}
<a class="online" target="_blank" href="https://www.youtube.com/watch?v=PhZ8qQbIej0" style="background-image: url({% static 'img/video-1.jpg' %});">
<div class="online__center center">
<div class="online__type">ПРЯМОЙ ЭФИР</div>
<div class="online__type">ВИДЕОУРОКИ</div>
<div class="online__title">В 17.00 (по Мск) </div>
<div class="online__text text">Понедельник, среда, пятница.</div>
<div class="online__text text">Каждый день с 1 сентября</div>
<div class="online__action">
<svg class="icon icon-play">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-play"></use>

@ -1,4 +1,4 @@
<a
class="timing__btn btn btn_light"
href="{% url 'school:lesson-detail' live_lesson.id %}"
>смотреть урок</a>
>подробнее</a>

@ -5,21 +5,17 @@
<span class="main__bold">Lil School</span> — первая образовательная онлайн-платформа креативного мышления для детей
</div>
<div class="main__subtitle">
Присоединяйтесь в Рисовальный лагерь
Старт школы - 1 сентября
</div>
<div class="main__actions">
<a
{% if not is_purchased_future %}
{% if not user.is_authenticated %}
data-popup=".js-popup-auth"
{% else %}
data-popup=".js-popup-buy"
{% endif %}
{% endif %}
class="main__btn btn"
href="#"
>
{% if not is_purchased and not is_purchased_future %}Получить доступ{% endif %}
{% if not is_purchased and not is_purchased_future %}Купить доступ от {{ min_school_price }} руб./месяц{% endif %}
{% if is_purchased_future and not is_purchased %}ваша подписка начинается {{school_purchased_future.date_start}}{% endif %}
{% if is_purchased %}ваша подписка истекает {{ subscription_ends_humanize }}<br/>перейти к оплате{% endif %}
</a>
@ -32,8 +28,8 @@
<use xlink:href="{% static 'img/sprite.svg' %}#icon-online"></use>
</svg>
</div>
<div class="school__title">Прямой эфир</div>
<div class="school__text">Понедельник, среда, пятница</div>
<div class="school__title">Видеоуроки</div>
<div class="school__text">Каждый день с 1 сентября</div>
</div>
<div class="school__col">
<div class="school__preview">
@ -50,7 +46,7 @@
<use xlink:href="{% static 'img/sprite.svg' %}#icon-subjects"></use>
</svg>
</div>
<div class="school__title">12 уроков</div>
<div class="school__title">7 дисциплин</div>
<div class="school__text">В разных техниках</div>
</div>
<div class="school__col">

@ -25,10 +25,11 @@
{% else %}
<div class="casing__title title">Новые уроки</div>
{% endif %}
<label class="casing__switcher switcher">
<span class="switcher__wrap">
<a href="{% url 'school:summer-school' %}?is_previous=true" class="switcher__item{% if is_previous %} active{% endif %}">запись уроков</a>
<a href="{% url 'school:summer-school' %}" class="switcher__item{% if not is_previous %} active{% endif %}">новые уроки</a>
<a href="{% url 'school:school' %}?is_previous=true" class="switcher__item{% if is_previous %} active{% endif %}">запись уроков</a>
<a href="{% url 'school:school' %}" class="switcher__item{% if not is_previous %} active{% endif %}">новые уроки</a>
</span>
</label>
</div>
@ -38,15 +39,17 @@
{% if is_previous and not live_lessons_exists %}
Записей уроков пока нет
{% else %}
{% for school_schedule in school_schedules %}
{% if is_previous %}
{% if school_schedule.previous_live_lesson in live_lessons %}
{% include './_schedule_purchased_item.html' with school_schedule=school_schedule live_lesson=school_schedule.previous_live_lesson %}
{% endif %}
{% else %}
{% include './_schedule_purchased_item.html' with school_schedule=school_schedule live_lesson=school_schedule.current_live_lesson %}
{% endif %}
{% endfor %}
{% if is_previous %}
{% for live_lesson in live_lessons %}
{% if live_lesson.school_schedule and live_lesson.title %}
{% include 'blocks/schedule_item.html' with school_schedule=live_lesson.school_schedule live_lesson=live_lesson purchased=True %}
{% endif %}
{% endfor %}
{% else %}
{% for school_schedule in school_schedules %}
{% include 'blocks/schedule_item.html' with school_schedule=school_schedule live_lesson=school_schedule.current_live_lesson purchased=True %}
{% endfor %}
{% endif %}
{% endif %}
</div>
<div class="timing__week">

@ -3,12 +3,11 @@ from django.urls import path, include
from .views import (
LiveLessonsView, LiveLessonEditView,
LiveLessonsDetailView, SchoolView,
SchoolSchedulesPrintView, SummerSchoolView,
SchoolSchedulesPrintView,
)
urlpatterns = [
path('', SchoolView.as_view(), name='school'),
path('summer', SummerSchoolView.as_view(), name='summer-school'),
path('schedules/print', SchoolSchedulesPrintView.as_view(), name='school_schedules-print'),
path('lessons/', LiveLessonsView.as_view(), name='lessons'),
path('lessons/create', LiveLessonEditView.as_view(), name='lessons-create'),

@ -1,12 +1,12 @@
from datetime import datetime, timedelta
from datetime import datetime, timedelta, date
from paymentwall import Pingback
from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required, user_passes_test
from django.db.utils import IntegrityError
from django.db.models import Min, F, Func, Q
from django.db.models import Min, F, Func, Q, Value
from django.http import Http404
from django.shortcuts import get_object_or_404
from django.shortcuts import get_object_or_404, redirect
from django.utils.decorators import method_decorator
from django.utils.timezone import now
from django.views.generic import ListView, UpdateView, TemplateView, DetailView
@ -34,19 +34,22 @@ class LiveLessonsView(ListView):
template_name = 'school/livelessons_list.html'
def get_queryset(self):
september2018 = date(2018, 9, 1)
date_start = (now() - timedelta(days=7)).date()
if date_start < september2018:
date_start = september2018
date_range = Q(
date__range=[
(now() - timedelta(days=7)).date(),
(now() + timedelta(days=10)).date(),
date_start,
date_start + timedelta(days=17),
]
)
queryset = LiveLesson.objects.filter(date_range)
if queryset.count() < 17:
start_date = now() - timedelta(days=7)
for i in range(18):
try:
LiveLesson.objects.create(
date=(start_date + timedelta(days=i)).date(),
date=date_start + timedelta(days=i),
)
except IntegrityError:
pass
@ -61,6 +64,7 @@ class LiveLessonsDetailView(DetailView):
def get(self, request, pk=None):
response = super().get(request, pk=pk)
# ??? где проверка?
#try:
# school_payment = SchoolPayment.objects.get(
# user=request.user,
@ -107,12 +111,15 @@ class LiveLessonEditView(TemplateView):
class SchoolView(TemplateView):
template_name = 'school/school.html'
template_name = 'school/summer_school.html'
def get_context_data(self):
context = super().get_context_data()
is_previous = 'is_previous' in self.request.GET
date_now = now().date()
yesterday = date_now - timedelta(days=1)
# month_start = date_now.replace(day=1)
month_start = datetime(2018, 7, 1).date()
now_time = now()
try:
school_schedule = SchoolSchedule.objects.get(weekday=now_time.isoweekday())
@ -123,8 +130,18 @@ class SchoolView(TemplateView):
online = (
school_schedule.start_at <= now_time.time() and
(end_at + timedelta(hours=1)).time() >= now_time.time() and
school_schedule.current_live_lesson()
school_schedule.current_live_lesson
)
school_schedules = SchoolSchedule.objects.all()
try:
school_schedules = sorted(school_schedules, key=lambda ss: ss.current_live_lesson and ss.current_live_lesson.date)
except Exception:
pass
school_schedules_dict = {ss.weekday: ss for ss in school_schedules}
school_schedules_dict[0] = school_schedules_dict.get(7)
live_lessons = None
live_lessons_exists = False
if self.request.user.is_authenticated:
school_payment = SchoolPayment.objects.filter(
user=self.request.user,
@ -137,100 +154,39 @@ class SchoolView(TemplateView):
date_end__gte=date_now
)
school_payment_exists = school_payment.exists()
school_schedules_purchased = school_payment.annotate(
joined_weekdays=Func(F('weekdays'), function='unnest',)
).values_list('joined_weekdays', flat=True).distinct()
else:
school_payment_exists = False
school_schedules_purchased = []
if school_payment_exists and is_previous:
live_lessons = LiveLesson.objects.filter(
date__gte=school_payment.last().date_start,
date__range=[(now_time - timedelta(days=8)).date(), (now_time - timedelta(days=1)).date()],
deactivated_at__isnull=True,
)
live_lessons_exists = live_lessons.exists()
else:
live_lessons = None
live_lessons_exists = False
context.update({
'online': online,
'live_lessons': live_lessons,
'live_lessons_exists': live_lessons_exists,
'is_previous': is_previous,
'course_items': Course.objects.filter(status=Course.PUBLISHED)[:6],
'is_purchased': school_payment_exists,
'min_school_price': SchoolSchedule.objects.aggregate(Min('month_price'))['month_price__min'],
'school_schedules': SchoolSchedule.objects.all(),
'school_schedules_purchased': school_schedules_purchased,
'subscription_ends': school_payment.filter(add_days=False).first().date_end if school_payment_exists else None,
})
return context
class SummerSchoolView(TemplateView):
template_name = 'school/summer_school.html'
def get_context_data(self):
context = super().get_context_data()
is_previous = 'is_previous' in self.request.GET
date_now = now().date()
now_time = now()
try:
school_schedule = SchoolSchedule.objects.get(weekday=now_time.isoweekday())
except SchoolSchedule.DoesNotExist:
online = False
else:
end_at = datetime.combine(now_time.today(), school_schedule.start_at)
online = (
school_schedule.start_at <= now_time.time() and
(end_at + timedelta(hours=1)).time() >= now_time.time() and
school_schedule.current_live_lesson()
)
if self.request.user.is_authenticated:
school_payment = SchoolPayment.objects.filter(
if is_previous:
prev_range = [yesterday - timedelta(days=7), yesterday]
live_lessons = []
# берем все подписки, которые были в периоде
for sp in SchoolPayment.objects.filter(
date_start__lte=prev_range[1],
date_end__gte=prev_range[0],
user=self.request.user,
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
date_start__lte=date_now,
date_end__gte=date_now
)
school_payment_exists = school_payment.exists()
school_payment_future = SchoolPayment.objects.filter(
user=self.request.user,
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
date_start__gte=date_now,
date_end__gte=date_now
)
school_payment_exists_future = school_payment_future.exists()
school_purchased_future = school_payment_future.last()
school_schedules_purchased = school_payment.annotate(
joined_weekdays=Func(F('weekdays'), function='unnest',)
).values_list('joined_weekdays', flat=True).distinct()
else:
school_payment_exists = False
school_payment_exists_future = False
school_purchased_future = False
school_schedules_purchased = []
if school_payment_exists and is_previous:
live_lessons = LiveLesson.objects.filter(
date__gte=school_payment.last().date_start,
date__range=[(now_time - timedelta(days=8)).date(), (now_time - timedelta(days=1)).date()],
deactivated_at__isnull=True,
)
):
# берем все уроки в оплаченном промежутке
date_range = [max(sp.date_start, prev_range[0]), min(sp.date_end, prev_range[1])]
live_lessons += LiveLesson.objects.filter(
date__range=date_range,
deactivated_at__isnull=True,
date__week_day__in=list(map(lambda x: 1 if x == 7 else x+1, sp.weekdays)),
).values_list('id', flat=True)
live_lessons = LiveLesson.objects.filter(id__in=set(live_lessons)).order_by('-date')
for ll in live_lessons:
ll.school_schedule = school_schedules_dict.get(ll.date.isoweekday())
live_lessons_exists = live_lessons.exists()
else:
live_lessons = None
live_lessons_exists = False
live_lessons = live_lessons or None
context.update({
'online': online,
'live_lessons': live_lessons,
@ -238,11 +194,11 @@ class SummerSchoolView(TemplateView):
'is_previous': is_previous,
'course_items': Course.objects.filter(status=Course.PUBLISHED)[:6],
'is_purchased': school_payment_exists,
'is_purchased_future': school_payment_exists_future,
'is_purchased_future': False,
'min_school_price': SchoolSchedule.objects.aggregate(Min('month_price'))['month_price__min'],
'school_schedules': SchoolSchedule.objects.all(),
'school_schedules': school_schedules,
'school_schedules_purchased': school_schedules_purchased,
'school_purchased_future': school_purchased_future,
'school_purchased_future': False,
'subscription_ends': school_payment.filter(add_days=False).first().date_end if school_payment_exists else None,
})
return context

@ -18,6 +18,7 @@ class UserEditForm(forms.ModelForm):
# gender = forms.ChoiceField(choices=User.GENDER_CHOICES, required=False)
gender = forms.CharField(required=False)
# about = forms.CharField()
trial_lesson = forms.URLField(required=False)
old_password = forms.CharField(required=False)
new_password1 = forms.CharField(required=False)
new_password2 = forms.CharField(required=False)
@ -41,6 +42,7 @@ class UserEditForm(forms.ModelForm):
'birthday',
'gender',
'about',
'trial_lesson',
'old_password',
'new_password1',
'new_password2',

@ -0,0 +1,18 @@
# Generated by Django 2.0.6 on 2018-08-22 12:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('user', '0022_user_instagram_hashtag'),
]
operations = [
migrations.AddField(
model_name='user',
name='trial_lesson',
field=models.URLField(blank=True, default='', null=True),
),
]

@ -75,6 +75,7 @@ class User(AbstractUser):
)
photo = models.ImageField('Фото', null=True, blank=True, upload_to='users')
show_in_mainpage = models.BooleanField('Показывать на главной странице', default=False)
trial_lesson = models.URLField(default='', null=True, blank=True)
objects = UserManager()
@ -91,7 +92,7 @@ class User(AbstractUser):
@property
def balance(self):
aggregate = self.balances.filter(
income = self.balances.filter(
type=0,
payment__isnull=False,
payment__status__isnull=False
@ -99,9 +100,13 @@ class User(AbstractUser):
models.Sum('amount'),
models.Sum('commission'),
)
amount = aggregate.get('amount__sum') or 0
commission = aggregate.get('commission__sum') or 0
return amount - commission
income_amount = income.get('amount__sum') or 0
income_commission = income.get('commission__sum') or 0
payout = self.balances.filter(type=1, status=1).aggregate(models.Sum('amount'))
payout_amount = payout.get('amount__sum') or 0
return income_amount - income_commission - payout_amount
@receiver(post_save, sender=User)

@ -0,0 +1,87 @@
{% extends "templates/lilcity/index.html" %} {% load static %} {% load thumbnail %} {% block content %}
<div class="section">
<div class="section__center center">
<div class="profile">
<div class="profile__ava ava">
{% thumbnail user.photo "120x120" crop="center" as im %}
<img class="ava__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" />
{% empty %}
<img class="ava__pic" src="{% static 'img/user_default.jpg' %}" width="120px" height="120px" />
{% endthumbnail %}
</div>
<div class="profile__wrap">
<div class="profile__name">{{ user.get_full_name }}</div>
<div class="profile__share share share_sm">
<div class="share__list">
{% if user.facebook %}
<a class="share__item" href="{{ user.facebook }}">
<svg class="icon icon-share-facebook">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-facebook"></use>
</svg>
</a>
{% endif %} {% if user.instagram %}
<a class="share__item" href="{{ user.instagram }}">
<svg class="icon icon-share-instagram">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-instagram"></use>
</svg>
</a>
{% endif %} {% if user.twitter %}
<a class="share__item" href="{{ user.twitter }}">
<svg class="icon icon-share-twitter">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-twitter"></use>
</svg>
</a>
{% endif %} {% if user.google %}
<a class="share__item" href="{{ user.google }}">
<svg class="icon icon-share-google">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-google"></use>
</svg>
</a>
{% endif %} {% if user.pinterest %}
<a class="share__item" href="{{ user.pinterest }}">
<svg class="icon icon-share-pinterest">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-pinterest"></use>
</svg>
</a>
{% endif %}
</div>
</div>
</div>
<div class="profile__content">
{% if user.about %}
{{ user.about }}
{% endif %}
</div>
</div>
</div>
</div>
<div class="section section_pink-light section_tabs">
<div class="section__center center">
<div class="tabs js-tabs">
<div class="tabs__nav">
<button class="tabs__btn js-tabs-btn {% if not owner %}active{% endif %}">ОПУБЛИКОВАННЫЕ
<span class="mobile-hide">КУРСЫ</span>
</button>
</div>
<div class="tabs__container">
<div class="tabs__item js-tabs-item" style="display: block;">
<div class="courses courses_scroll">
<div class="courses__list">
{% if published.exists %}
{% include "course/course_items.html" with course_items=published %}
{% else %}
<div class="center center_xs">
<div class="done">
<div class="done__title title">Нет опубликованных курсов!</div>
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}

@ -2,7 +2,7 @@
<div class="section section_gray section_menu">
<div class="section__center center center_xs">
<div class="menu">
<a class="menu__link" href="{% url 'user-edit-profile' user.id %}">Профиль</a>
<a class="menu__link" href="{% url 'user-edit-profile' %}">Профиль</a>
<a class="menu__link active" href="{% url 'user-edit-notifications' user.id %}">Уведомления</a>
<a class="menu__link" href="{% url 'user-edit-payments' user.id %}">Платежи</a>
</div>

@ -2,7 +2,7 @@
<div class="section section_gray section_menu">
<div class="section__center center center_xs">
<div class="menu">
<a class="menu__link" href="{% url 'user-edit-profile' user.id %}">Профиль</a>
<a class="menu__link" href="{% url 'user-edit-profile' %}">Профиль</a>
<a class="menu__link" href="{% url 'user-edit-notifications' user.id %}">Уведомления</a>
<a class="menu__link active" href="{% url 'user-edit-payments' user.id %}">Платежи</a>
</div>

@ -2,7 +2,7 @@
<div class="section section_gray section_menu">
<div class="section__center center center_xs">
<div class="menu">
<a class="menu__link active" href="{% url 'user-edit-profile' user.id %}">Профиль</a>
<a class="menu__link active" href="{% url 'user-edit-profile' %}">Профиль</a>
<a class="menu__link" href="{% url 'user-edit-notifications' user.id %}">Уведомления</a>
<a class="menu__link" href="{% url 'user-edit-payments' user.id %}">Платежи</a>
</div>
@ -150,7 +150,18 @@
{% for error in form.about.errors %}
<div class="field__error">{{ error }}</div>
{% endfor %}
</div>
</div>
{% if is_teacher %}
<div class="form__field field{% if form.trial_lesson.errors %} error{% endif %}">
<div class="field__label">Пробный урок</div>
<div class="field__wrap">
<input name='trial_lesson' class="field__input" type="url" value="{{ user.trial_lesson }}">
</div>
{% for error in form.trial_lesson.errors %}
<div class="field__error">{{ error }}</div>
{% endfor %}
</div>
{% endif %}
</div>
<div class="form__group">
<div class="form__title">Пароль</div>

@ -2,9 +2,7 @@
<div class="section">
<div class="section__center center">
<div class="profile">
{% if not guest %}
<a class="profile__btn profile__btn_edit btn" href="{% url 'user-edit-profile' user.id %}">Редактировать</a>
{% endif %}
<a class="profile__btn profile__btn_edit btn" href="{% url 'user-edit-profile' %}">Редактировать</a>
<div class="profile__ava ava">
{% thumbnail user.photo "120x120" crop="center" as im %}
<img class="ava__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" />
@ -56,22 +54,18 @@
{% endif %}
</div>
<div class="profile__foot">
<a class="profile__btn btn" href="{% url 'user-edit-profile' user.id %}">Изменить</a>
<a class="profile__btn btn" href="{% url 'user-edit-profile' %}">Изменить</a>
</div>
</div>
</div>
</div>
{% if not guest %}
<div class="section section_pink-light section_tabs">
<div class="section__center center">
<div class="tabs js-tabs">
<div class="tabs__nav">
<button class="tabs__btn js-tabs-btn active">ЛАГЕРЬ</button>
<button class="tabs__btn js-tabs-btn">ПРИОБРЕТЕННЫЕ
<span class="mobile-hide">КУРСЫ</span>
</button>
{% if not simple_user %}
<button class="tabs__btn js-tabs-btn active">МОИ ПОКУПКИ</button>
{% if is_author %}
<button class="tabs__btn js-tabs-btn">ОПУБЛИКОВАННЫЕ
<span class="mobile-hide">КУРСЫ</span>
</button>
@ -79,45 +73,34 @@
</div>
<div class="tabs__container">
<div class="tabs__item js-tabs-item" style="display: block;">
{% if is_purchased_future %}
<div class="center center_xs">
<div class="done">
<div class="done__title title">Ваша подписка начинается {{school_purchased_future.date_start}}</div>
</div>
</div>
{% else %}
{% if is_purchased %}
{% include "blocks/schedule_purchased.html" %}
{% else %}
<div class="center center_xs">
<div class="done">
<div class="done__title title">Вы не подписаны на лагерь!</div>
<div class="done__foot">
<a
{% if not user.is_authenticated %}
data-popup=".js-popup-auth"
{% else %}
data-popup=".js-popup-buy"
{% endif %}
href="#"
class="done__btn btn btn_md btn_stroke"
>Купить подписку</a>
<div class="courses">
<div class="courses__list">
{% if is_school_purchased %}
<div class="courses__item">
<a class="courses__preview" href="{% url 'school:school' %}">
<img class="courses__pic" src="{% static 'img/og_main.jpg' %}"
style="height: 200px; object-fit: cover;" />
<div class="courses__view">Подробнее</div>
</a>
<div class="courses__details">
<a class="courses__theme theme">{{ school_purchased_weekdays }}</a>
<div class="courses__price">{{ school_purchased_price|floatformat:"-2" }}₽</div>
</div>
<a class="courses__title">Онлайн-школа&nbsp;{{ school_purchased_dates.0|date:"j b" }}&nbsp;-&nbsp;{{ school_purchased_dates.1|date:"j b" }}</a>
<a class="btn" href="{% url 'school:school' %}">Перейти в онлайн-школу</a>
</div>
</div>
{% endif %}
{% endif %}
</div>
<div class="tabs__item js-tabs-item">
<div class="courses courses_scroll">
<div class="courses__list">
{% endif %}
{% if paid.exists %}
{% include "course/course_items.html" with course_items=paid %}
{% else %}
{% endif %}
{% if not is_school_purchased and not paid.exists %}
<div class="center center_xs">
<div class="done">
<div class="done__title title">Нет приобретённых курсов!</div>
<div class="done__title">Вы пока ничего не приобрели...</div>
<div class="done__foot">
<a class="done__btn btn btn_md btn_stroke" href="{% url 'school:school' %}"
style="margin-bottom: 20px;">Записаться в школу</a>
<a class="done__btn btn btn_md btn_stroke" href="{% url 'courses' %}">Купить курсы</a>
</div>
</div>
@ -126,9 +109,9 @@
</div>
</div>
</div>
{% if not simple_user %}
{% if is_author %}
<div class="tabs__item js-tabs-item">
<div class="courses courses_scroll">
<div class="courses">
<div class="courses__list">
{% if published.exists %}
{% include "course/course_items.html" with course_items=published %}
@ -150,5 +133,4 @@
</div>
</div>
</div>
{% endif %}
{% endblock content %}

@ -1,5 +1,3 @@
import arrow
from io import BytesIO
from PIL import Image
from uuid import uuid4
@ -18,7 +16,7 @@ from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.hashers import check_password, make_password
from django.http import Http404
from django.db.models import F, Func
from django.db.models import F, Func, Sum, Min, Max
from django.urls import reverse_lazy
from django.utils.decorators import method_decorator
from django.utils.timezone import now
@ -41,28 +39,27 @@ def resend_email_verify(request):
url = request.scheme + '://' + request.get_host() + str(reverse_lazy('lilcity:verification-email', args=[token, request.user.id]))
send_email('Вы успешно прошли регистрацию', request.user.email, "notification/email/verification_email.html", url=url)
messages.info(request, 'Письмо подтверждения отправлено.')
return redirect('user-edit-profile', request.user.id)
return redirect('user-edit-profile')
class UserView(DetailView):
@method_decorator(login_required, name='dispatch')
class ProfileView(TemplateView):
model = User
template_name = 'user/profile.html'
def get(self, request, *args, **kwargs):
self.object = self.request.user
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
def get_context_data(self, object):
context = super().get_context_data()
if not self.request.user.is_anonymous:
context['simple_user'] = self.request.user.role == User.USER_ROLE
context['guest'] = self.request.user.id != self.object.id and self.request.user.role <= User.USER_ROLE
else:
context['simple_user'] = True
context['guest'] = True
if context['guest'] and self.object.role <= User.USER_ROLE:
raise Http404()
context['user'] = self.request.user
context['published'] = Course.objects.filter(
author=self.object,
)
context['is_author'] = context['published'] or self.request.user.role == User.AUTHOR_ROLE
context['paid'] = Course.objects.filter(
payments__in=CoursePayment.objects.filter(
user=self.object,
@ -76,36 +73,38 @@ class UserView(DetailView):
school_payment = SchoolPayment.objects.filter(
user=self.object,
date_start__lte=now(),
date_end__gt=now(),
date_end__gte=now(),
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
)
school_schedules_purchased = school_payment.annotate(
joined_weekdays=Func(F('weekdays'), function='unnest',)
).values_list('joined_weekdays', flat=True).distinct()
context['school_schedules_purchased'] = school_schedules_purchased
context['school_payment'] = school_payment
context['is_purchased'] = school_payment.exists()
context['is_school_purchased'] = school_payment.exists()
if context['is_school_purchased']:
school_schedules_purchased = school_payment.annotate(
joined_weekdays=Func(F('weekdays'), function='unnest',)
).values_list('joined_weekdays', flat=True).distinct()
aggregated = school_payment.aggregate(Sum('amount'), Min('date_start'), Max('date_end'),)
context['school_purchased_weekdays'] = '-'.join(map(lambda wd: SchoolSchedule.WEEKDAY_SHORT_NAMES[wd-1],
set(sorted(school_schedules_purchased))))
context['school_purchased_price'] = aggregated.get('amount__sum') or 0
context['school_purchased_dates'] = [aggregated.get('date_start__min'), aggregated.get('date_end__max')]
context['profile'] = True
if school_payment.exists() and school_payment.last().date_end:
context['subscription_ends'] = school_payment.last().date_end
context['school_schedules'] = SchoolSchedule.objects.filter(
weekday__in=school_schedules_purchased if school_payment.exists() else [],
).all()
context['all_school_schedules'] = SchoolSchedule.objects.all()
school_payment_future = SchoolPayment.objects.filter(
user=self.object,
date_start__gte=now(),
date_end__gte=now()
)
context['is_purchased_future'] = school_payment_future.exists()
context['school_purchased_future'] = school_payment_future.last()
return context
class UserView(DetailView):
model = User
template_name = 'user/author_profile.html'
def get_context_data(self, object):
context = super().get_context_data()
context['published'] = Course.objects.filter(
author=self.object, status=Course.PUBLISHED,
)
return context
@ -189,18 +188,23 @@ class PaymentHistoryView(FormView):
@method_decorator(login_required, name='dispatch')
class UserEditView(UpdateView):
class ProfileEditView(UpdateView):
model = User
template_name = 'user/profile-settings.html'
form_class = UserEditForm
@method_decorator(login_required)
def get_object(self, queryset=None):
return self.request.user
def dispatch(self, request, *args, **kwargs):
self.object = self.get_object()
if request.user != self.object:
raise Http404()
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['is_teacher'] = self.object.role == User.TEACHER_ROLE
return context
def post(self, request, *args, **kwargs):
# it's magic *-*-*-*-*
if 'photo' in request.FILES:
@ -244,7 +248,7 @@ class UserEditView(UpdateView):
return super().post(request, *args, **kwargs)
def get_success_url(self):
return reverse('user-edit-profile', args=[self.object.id])
return reverse('user-edit-profile')
class AuthorRequestView(FormView):

@ -1,5 +1,9 @@
from django.utils.timezone import now
from paymentwall.pingback import Pingback
from apps.config.models import Config
from apps.content.models import Baner
from apps.payment.models import SchoolPayment
def config(request):
@ -8,3 +12,20 @@ def config(request):
def baner(request):
return {'baner': Baner.objects.filter(use=True).first()}
def is_summer_school_purchased(request):
if request.user.is_authenticated:
n = now().date()
school_payment = SchoolPayment.objects.filter(
user=request.user,
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
date_start__lte=n,
date_end__gte=n
)
return {'is_summer_school_purchased': school_payment.exists()}
return {'is_summer_school_purchased': False}

@ -56,6 +56,7 @@ INSTALLED_APPS = [
'sorl.thumbnail',
'raven.contrib.django.raven_compat',
'django_user_agents',
'imagekit',
] + [
'apps.auth.apps',
'apps.user',
@ -93,6 +94,7 @@ TEMPLATES = [
'context_processors': [
'project.context_processors.config',
'project.context_processors.baner',
'project.context_processors.is_summer_school_purchased',
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
@ -141,6 +143,7 @@ AUTH_PASSWORD_VALIDATORS = [
AUTH_USER_MODEL = 'user.User'
AUTHENTICATION_BACKENDS = ['apps.auth.backend.CaseInsensitiveModelBackend']
# Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/

@ -12,8 +12,8 @@
<use xlink:href="{% static 'img/sprite.svg' %}#icon-online"></use>
</svg>
</div>
<div class="school__title">Прямой эфир</div>
<div class="school__text">Понедельник, среда, пятница</div>
<div class="school__title">Видеоуроки</div>
<div class="school__text">Каждый день с 1 сентября</div>
</div>
<div class="school__col">
<div class="school__preview">
@ -30,7 +30,7 @@
<use xlink:href="{% static 'img/sprite.svg' %}#icon-subjects"></use>
</svg>
</div>
<div class="school__title">12 уроков</div>
<div class="school__title">7 дисциплин</div>
<div class="school__text">В разных техниках</div>
</div>
<div class="school__col">
@ -43,20 +43,6 @@
<div class="school__text">Хранится 7 дней</div>
</div>
</div>
<div class="letsgo">
{% if not is_purchased and not is_purchased_future %}
<a
{% if not user.is_authenticated %}
data-popup=".js-popup-auth"
{% else %}
data-popup=".js-popup-buy"
{% endif %}
class="main__btn btn"
href="#"
>
купить доступ от {{ min_school_price }} руб./месяц
</a>
{% endif %}
</div>
<div class="letsgo"></div>
</div>
</div>

@ -1,5 +1,5 @@
{% load static %}
{% if not node.deactivated_at %}
{% if not node.deactivated_at and node.author.is_active %}
<a class="questions__anchor" id="question__{{ node.id }}"></a>
<div id="question__replyto__{{ node.id }}" class="questions__item {% if node.is_child_node %}questions__item_reply{% endif %}">
{% if node.author.photo %}

@ -19,7 +19,6 @@
<div class="footer__col">
<div class="footer__title">Программы</div>
<nav class="footer__nav">
<a class="footer__link" href="{% url 'school:summer-school' %}">Лагерь</a>
<a class="footer__link" href="{% url 'school:school' %}">Онлайн-школа</a>
<a class="footer__link" href="{% url 'courses' %}">Онлайн-курсы</a>
<a class="footer__link" href="{% url 'author_request' %}">Стать автором</a>

@ -25,17 +25,10 @@
</form>
</div>
<nav class="header__nav">
<div class="header__group">
<a class="header__section {% active_link 'school:summer-school' %}" href="{% url 'school:summer-school' %}">
ЛАГЕРЬ {% if online or livelesson.is_online %}
<div class="header__dot"></div>
{% endif %}
</a>
</div>
<div class="header__group">
<a class="header__section {% active_link 'school:school' %}" href="{% url 'school:school' %}">
ОНЛАЙН-ШКОЛА {% if online or livelesson.is_online %}
<!--<div class="header__dot"></div>-->
<div class="header__dot"></div>
{% endif %}
</a>
</div>

@ -2,7 +2,7 @@
<div class="section section_courses">
<div class="section__center center">
<div class="title title_center">Видео-курсы без расписания</div>
<div class="text text_courses">Если вам не совсем удобно заниматься с нами в прямом эфире каждый день как в нашей онлайн-школе, специально для вас мы
<div class="text text_courses">Если вам не совсем удобно заниматься с нами каждый день в нашей онлайн-школе, специально для вас мы
делаем отдельные уроки в записи, которые вы можете проходить когда вам будем удобно.</div>
<div class="head">
<div class="head__text">Учите и развивайте креативное мышление когда и где угодно</div>

@ -0,0 +1,13 @@
{% load static %}
<script>
window.LIL_STORE = {
staticUrl: '{% static "" %}',
accessToken: '{{ request.user.auth_token }}',
isMobile: {{ request.user_agent.is_mobile|yesno:"true,false" }},
defaultUserPhoto: "{% static 'img/user_default.jpg' %}",
user: {
id: '{{ request.user.id|default:'' }}',
photo: '{% if request.user.photo %}{{ request.user.photo.url }}{% else %}{% static 'img/user_default.jpg' %}{% endif %}',
}
};
</script>

@ -1,9 +1,9 @@
{% load static %}
<a class="online" target="_blank" href="https://www.youtube.com/watch?v=PhZ8qQbIej0" style="background-image: url({% static 'img/video-1.jpg' %});">
<div class="online__center center">
<div class="online__type">ПРЯМОЙ ЭФИР</div>
<div class="online__type">ВИДЕОУРОКИ</div>
<div class="online__title">В 17.00 (по Мск) </div>
<div class="online__text text">Понедельник, среда, пятница.</div>
<div class="online__text text">Каждый день с 1 сентября</div>
<div class="online__action">
<svg class="icon icon-play">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-play"></use>

@ -1,5 +1,5 @@
{% if messages %}
<div class="section section_gray section_menu">
<div class="section section_gray section_menu" style="margin-bottom: 20px;">
<div class="section__center center center_xs">
{% for message in messages %}
<div class="message message_{{ message.tags }}">{{ message }}</div>

@ -4,13 +4,30 @@
<a name="partners">
<div class="title title_center">Наши партнеры</div>
</a>
<div class="text">
Скидка 15% по промокоду у наших партнеров
</div>
<div class="partners">
<a target="_blank" class="partners__item" href="https://www.mann-ivanov-ferber.ru/tag/sasha-kru/">
<img class="partners__pic" src="{% static 'img/mif.jpg' %}">
</a>
<a target="_blank" class="partners__item" href="https://artkvartal.ru">
<img class="partners__pic" src="{% static 'img/artkvartal.png' %}">
</a>
<a target="_blank" class="partners__item" href="https://www.mann-ivanov-ferber.ru/tag/sasha-kru/">
<img class="partners__pic" src="{% static 'img/mif.jpg' %}">
<a target="_blank" class="partners__item" href="http://www.pinkbus.ru/">
<img class="partners__pic" src="{% static 'img/pinkbus.jpg' %}">
</a>
</div>
<div class="partners partners_promocodes">
<div class="partners__item">
<div class="partners__item-promocode">LIL</div>
</div>
<div class="partners__item">
<div class="partners__item-promocode">LILCITY</div>
</div>
<div class="partners__item">
<div class="partners__item-promocode">LILCITY</div>
</div>
</div>
</div>
</div>

@ -3,7 +3,7 @@
<div class="popup__wrap js-popup-wrap">
<button class="popup__close js-popup-close">
<svg class="icon icon-close">
<use xlink:href={% static "img/sprite.svg" %}#icon-close></use>
<use xlink:href="{% static 'img/sprite.svg' %}#icon-close"></use>
</svg>
</button>
<div class="popup__body">
@ -37,10 +37,10 @@
<div class="field__password-show">
<button class="password-toggle" type="button">
<svg class="icon icon-password-eye">
<use xlink:href={% static "img/sprite.svg" %}#icon-password-eye></use>
<use xlink:href="{% static 'img/sprite.svg' %}#icon-password-eye"></use>
</svg>
<svg class="icon icon-password-hidden-eye">
<use xlink:href={% static "img/sprite.svg" %}#icon-password-hidden-eye></use>
<use xlink:href="{% static 'img/sprite.svg' %}#icon-password-hidden-eye"></use>
</svg>
</button>
</div>
@ -53,7 +53,7 @@
<div class="auth__or">или</div>
<button type="button" class="auth__btn btn btn_fb">
<svg class="icon icon-facebook">
<use xlink:href={% static "img/sprite.svg" %}#icon-facebook></use>
<use xlink:href="{% static 'img/sprite.svg' %}#icon-facebook"></use>
</svg>
<span class="btn__title">ЧЕРЕЗ FACEBOOK</span>
</button>
@ -96,10 +96,10 @@
<div class="field__password-show">
<button class="password-toggle" type="button">
<svg class="icon icon-password-eye">
<use xlink:href={% static "img/sprite.svg" %}#icon-password-eye></use>
<use xlink:href="{% static 'img/sprite.svg' %}#icon-password-eye"></use>
</svg>
<svg class="icon icon-password-hidden-eye">
<use xlink:href={% static "img/sprite.svg" %}#icon-password-hidden-eye></use>
<use xlink:href="{% static 'img/sprite.svg' %}#icon-password-hidden-eye"></use>
</svg>
</button>
</div>
@ -122,7 +122,7 @@
<div class="auth__or">или</div>
<button type="button" class="auth__btn btn btn_fb">
<svg class="icon icon-facebook">
<use xlink:href={% static "img/sprite.svg" %}#icon-facebook></use>
<use xlink:href="{% static 'img/sprite.svg' %}#icon-facebook"></use>
</svg>
<span class="btn__title">ЧЕРЕЗ FACEBOOK</span>
</button>

@ -29,7 +29,10 @@
data-day="{{school_schedule.weekday}}"
data-price="{{school_schedule.month_price}}"
autocomplete="off"
{% if school_schedule.weekday in school_schedules_purchased or not is_purchased %}
{% if school_schedule.weekday in school_schedules_purchased %}
disabled
{% endif %}
{% if not is_purchased %}
checked
{% endif %}>
<span class="switch__content">
@ -37,7 +40,16 @@
{% 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 class="buy__trial-lesson switch__cell">
{% if school_schedule.weekday in school_schedules_purchased %}
Куплено
{% else %}
{% if school_schedule.trial_lesson %}
<a class="js-video-modal" data-video-url="{{ school_schedule.trial_lesson }}" href="">Пробный урок</a>
{% endif %}
{% endif %}
</span>
<span class="switch__cell">{{school_schedule.month_price}}р в мес.</span>
</span>
</label>
{% endfor %}
@ -50,7 +62,10 @@
data-day="{{school_schedule.weekday}}"
data-price="{{school_schedule.month_price}}"
autocomplete="off"
{% if school_schedule.weekday in school_schedules_purchased or not is_purchased %}
{% if school_schedule.weekday in school_schedules_purchased %}
disabled
{% endif %}
{% if not is_purchased %}
checked
{% endif %}>
<span class="switch__content">
@ -58,7 +73,16 @@
{% 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 class="buy__trial-lesson switch__cell">
{% if school_schedule.weekday in school_schedules_purchased %}
Куплено
{% else %}
{% if school_schedule.trial_lesson %}
<a class="js-video-modal" data-video-url="{{ school_schedule.trial_lesson }}" href="">Пробный урок</a>
{% endif %}
{% endif %}
</span>
<span class="switch__cell">{{school_schedule.month_price}}р в мес.</span>
</span>
</label>
{% endfor %}
@ -78,18 +102,18 @@
</div>
<div class="order__info">
<div class="order__label">ШКОЛА</div>
<div class="order__days">Вторник, Четверг, Воскресенье</div>
<div class="order__days"></div>
</div>
<div class="order__foot">
<div class="order__subtitle">Итого, за месяц:</div>
<div class="order__total order_price_text">1800р.</div>
<div class="order__total order_price_text"></div>
</div>
</div>
</div>
</div>
</div>
<div class="buy__foot">
<a class="buy__btn btn btn_md but_btn_popup" data-link="{% url 'school-checkout' %}" href="{% url 'school-checkout' %}?weekdays=1&weekdays=2">ПЕРЕЙТИ К ОПЛАТЕ</a>
<a class="buy__btn btn btn_md but_btn_popup" data-link="{% url 'school-checkout' %}">ПЕРЕЙТИ К ОПЛАТЕ</a>
</div>
</div>
</div>

@ -5,8 +5,8 @@
<div class="popup__body">
<div class="lock">
<div class="lock__label">ЧТОБЫ ПРОДОЛЖИТЬ ПРОСМОТР</div>
<div class="lock__title title">Как просто научиться рисовать простых персонажей.</div>
<div class="lock__content">Этот курс поможет детям узнать о том как из простых форм создавать веселый и харизматичных персонажей.</div>
<div class="lock__title title">{{ course.title }}</div>
<div class="lock__content">{{ course.short_description }}</div>
<a href="{% url 'course-checkout' course.id %}" class="lock__btn btn">КУПИТЬ КУРС</a>
</div>
</div>

@ -10,20 +10,20 @@
<div class="auth js-auth" style="padding:20px 20px 25px;">
<div class="auth__login js-auth-login">
<div class="auth__body">
<form id="learner-auth-form" method="post" action="{% url 'subscribe' %}">
<form {# COMMENTED id="learner-auth-form" #} method="post" action="{% url 'subscribe' %}">
{% csrf_token %}
<div class="auth__tab js-auth-tab" style="display: block;">
<div class="auth__enter js-auth-enter">
<div id="learner-auth-field__username" class="auth__field field learner-auth-form__field">
<div {# COMMENTED id="learner-auth-field__username" #} class="auth__field field learner-auth-form__field">
<div class="field__label">ПОЧТА</div>
<div class="field__wrap"><input id="learner-auth-form__email" class="field__input" type="email"
<div class="field__wrap"><input {# COMMENTED id="learner-auth-form__email" #} class="field__input" type="email"
name="email"
placeholder="name@website.com" tabindex="1"></div>
<div id="learner-auth-field-error__username"
class="field__error learner-auth-form__field-error"></div>
</div>
<div class="auth__foot">
<button id="learner-auth__button" class="auth__btn btn btn_light" tabindex="3">Подписаться</button>
<button {# COMMENTED id="learner-auth__button" #} class="auth__btn btn btn_light" tabindex="3">Подписаться</button>
</div>
</div>
</div>
@ -33,4 +33,4 @@
</div>
</div>
</div>
</div>
</div>

@ -12,59 +12,56 @@
<span class="main__bold">Lil School</span> — первая образовательная онлайн-платформа креативного мышления для детей
</div>
{% if user.is_authenticated and online %}
<div class="main__content">
Сейчас идёт прямой эфир урока «{{ school_schedule.title }}, {{ school_schedule.current_live_lesson.title }}»
</div>
<div class="main__actions">
<a
{% if not is_purchased %}
data-popup=".js-popup-buy"
href='#'
{% else %}
href="{{ school_schedule.current_live_lesson.get_absolute_url }}"
{% endif %}
class="main__btn btn"
>{% if not is_purchased %}Получить доступ{% else %}Смотреть урок{% endif %}</a>
</div>
<div class="main__content">
Сейчас идёт прямой эфир урока «{{ school_schedule.title }}, {{ school_schedule.current_live_lesson.title }}»
</div>
<div class="main__actions">
<a
{% if not school_schedule.weekday in school_schedules_purchased %}
data-popup=".js-popup-buy"
data-day="{{ school_schedule.weekday }}"
href='#'
{% else %}
href="{% url 'school:school' %}"
{% endif %}
class="main__btn btn"
>{% if not school_schedule.weekday in school_schedules_purchased %}Получить доступ{% else %}Смотреть урок{% endif %}</a>
</div>
{% elif user.is_authenticated and online_coming_soon and school_schedule and school_schedule.start_at_humanize %}
<div class="">
Урок «{{ school_schedule.title }}, {{ school_schedule.current_live_lesson.title }}» начнётся
</div>
<div class="main__time">
{{ school_schedule.start_at_humanize }}
</div>
<div class="main__actions">
<a
{% if not is_purchased %}
data-popup=".js-popup-buy"
href='#'
{% else %}
href="{{ school_schedule.current_live_lesson.get_absolute_url }}"
{% endif %}
class="main__btn btn"
>{% if not is_purchased %}Получить доступ{% else %}Смотреть урок{% endif %}</a>
</div>
<div class="">
Урок «{{ school_schedule.title }}, {{ school_schedule.current_live_lesson.title }}» начнётся
</div>
<div class="main__time">
{{ school_schedule.start_at_humanize }}
</div>
<div class="main__actions">
<a
{% if not school_schedule.weekday in school_schedules_purchased %}
data-popup=".js-popup-buy"
data-day="{{ school_schedule.weekday }}"
href='#'
{% else %}
href="{% url 'school:school' %}"
{% endif %}
class="main__btn btn"
>{% if not school_schedule.weekday in school_schedules_purchased %}Получить доступ{% else %}Смотреть урок{% endif %}</a>
</div>
{% else %}
<div class="main__subtitle">
Присоединяйтесь в Рисовальный лагерь
Приглашаем вас присоединиться к онлайн-школе с 1 сентября!
</div>
<div class="main__actions">
{% if not is_purchased and not is_purchased_future %}
<a
{% if not is_purchased_future %}
{% if not user.is_authenticated %}
data-popup=".js-popup-auth"
{% else %}
data-popup=".js-popup-buy"
{% endif %}
{% endif %}
class="main__btn btn"
href="#"
>
Получить доступ
купить доступ от {{ min_school_price }} руб./месяц
</a>
{% else %}
<a class="main__btn btn btn_white" href="{% url 'school:school' %}">Подробнее</a>
{% endif %}
<a class="main__btn btn btn_white" href="{% url 'school:summer-school' %}">О лагере</a>
</div>
{% endif %}
</div>

@ -1,27 +1,14 @@
{% load static %}
<div class="share">
<div class="share__title">Поделиться {% if livelesson or lesson %}уроком{% else %}курсом{% endif %}</div>
<div class="share__list likely">
<a class="share__item facebook" href="#" data-url="http://{{request.META.HTTP_HOST}}{{object.get_absolute_url}}">
<svg class="icon icon-share-facebook">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-facebook"></use>
</svg>
</a>
<a class="share__item twitter" href="#" data-url="http://{{request.META.HTTP_HOST}}{{object.get_absolute_url}}">
<svg class="icon icon-share-twitter">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-twitter"></use>
</svg>
</a>
<a class="share__item gplus" href="#" data-url="http://{{request.META.HTTP_HOST}}{{object.get_absolute_url}}">
<svg class="icon icon-share-google">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-google"></use>
</svg>
</a>
<a class="share__item pinterest" href="#" data-url="http://{{request.META.HTTP_HOST}}{{object.get_absolute_url}}">
<svg class="icon icon-share-pinterest">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-pinterest"></use>
</svg>
</a>
<div class="share__title">Поделиться {% if share_object_name %}{{ share_object_name }}{% else %}{% if livelesson or lesson %}уроком{% else %}курсом{% endif %}{% endif %}</div>
<div class="share__list likely likely-big" data-url="http://{{request.META.HTTP_HOST}}{{object.get_absolute_url}}">
<div class="twitter"></div>
<div class="facebook"></div>
<div class="gplus"></div>
<div class="vkontakte"></div>
<div class="telegram"></div>
<div class="pinterest"></div>
</div>
</div>

@ -17,10 +17,15 @@
</div>
<div class="teachers__wrap">
<div class="teachers__title">
<a href="{% url 'user' teacher.id %}">{{ teacher.get_full_name }}</a>{% if teacher.instagram_hashtag %},
<a href='https://www.instagram.com/explore/tags/{{ teacher.instagram_hashtag }}/' target="_blank">
{{ teacher.instagram_hashtag }}
</a>
<div class="teachers__title-name">
<a href="{% url 'user' teacher.id %}">{{ teacher.get_full_name }}</a>{% if teacher.instagram_hashtag %},
<a href='https://www.instagram.com/explore/tags/{{ teacher.instagram_hashtag }}/' target="_blank">
{{ teacher.instagram_hashtag }}
</a>
{% endif %}
</div>
{% if teacher.trial_lesson %}
<a data-video-url="{{ teacher.trial_lesson }}" href="#" class="btn btn_light js-video-modal">ПРОБНЫЙ УРОК</a>
{% endif %}
</div>
<div class="teachers__social">
@ -30,13 +35,13 @@
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-facebook"></use>
</svg>
</a>
{% endif %} {% if teacher.instagram %}
{% endif %} {# if teacher.instagram #}
<a class="social__item" href="{{ teacher.instagram }}" target="_blank">
<svg class="icon icon-share-instagram">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-instagram"></use>
</svg>
</a>
{% endif %} {% if teacher.twitter %}
{# endif #} {% if teacher.twitter %}
<a class="social__item" href="{{ teacher.twitter }}" target="_blank">
<svg class="icon icon-share-twitter">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-twitter"></use>

@ -1,7 +1,7 @@
{% load static %} {% load thumbnail %}
{% if request.user.is_authenticated %}
<div class="header__login">
<a class="header__ava ava" href="{% if request.user_agent.is_touch_capable %}#{% else %}{% url 'user' request.user.id %}{% endif %}">
<a class="header__ava ava" href="{% if request.user_agent.is_touch_capable %}#{% else %}{% url 'user-profile' %}{% endif %}">
{% thumbnail request.user.photo "48x48" crop="center" as im %}
<img class="ava__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" />
{% empty %}
@ -26,7 +26,7 @@
{% endif %}
{% endif %}
{% endif %}
<a class="header__link" href="{% url 'user' request.user.id %}">
<a class="header__link" href="{% url 'user-profile' %}">
<div class="header__title">ПРОФИЛЬ</div>
</a>
<a class="header__link" href="{% url 'lilcity:logout' %}">

@ -315,6 +315,7 @@
</div>
</div>
</div>
{% include 'templates/blocks/lil_store_js.html' %}
<script type="text/javascript" src={% static "app.js" %}></script>
<script>
var schoolDiscount = parseFloat({{ config.SERVICE_DISCOUNT }});

@ -38,6 +38,24 @@
{% endcompress %}
<link rel="shortcut icon" type="image/png" href="{% static 'img/favicon.png' %}"/>
<!-- Start of LiveChat (www.livechatinc.com) code -->
<script type="text/javascript">
window.__lc = window.__lc || {};
window.__lc.license = 9917240;
{% if request.user.is_authenticated %}
window.__lc.visitor = { name: '{{ request.user.get_full_name }}', email: '{{ request.user.email }}' };
{% endif %}
(function() {
var lc = document.createElement('script'); lc.type = 'text/javascript'; lc.async = true;
lc.src = ('https:' == document.location.protocol ? 'https://' : 'http://') + 'cdn.livechatinc.com/tracking.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(lc, s);
})();
</script>
<noscript>
<a href="https://www.livechatinc.com/chat-with/9917240/">Напишите нам</a>,
powered by <a href="https://www.livechatinc.com/?welcome" rel="noopener" target="_blank">LiveChat</a>
</noscript>
<!-- End of LiveChat code -->
<script src="https://js.pusher.com/4.1/pusher.min.js"></script>
<script>
var viewportmeta = document.querySelector('meta[name="viewport"]');
@ -116,6 +134,7 @@
<noscript><div><img src="https://mc.yandex.ru/watch/49354039" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
<!-- /Yandex.Metrika counter -->
{% include "templates/blocks/mixpanel.html" %}
{% block head %}{% endblock head %}
</head>
<body>
{% include "templates/blocks/social.html" %}
@ -131,16 +150,7 @@
{% include "templates/blocks/popup_course_lock.html" %}
{% include "templates/blocks/popup_subscribe.html" %}
</div>
<script>
window.VUE_STORE = {
accessToken: '{{ request.user.auth_token }}',
defaultUserPhoto: "{% static 'img/user_default.jpg' %}",
user: {
id: '{{ request.user.id }}',
photo: '{% if request.user.photo %}{{ request.user.photo.url }}{% else %}{% static 'img/user_default.jpg' %}{% endif %}',
}
};
</script>
{% include 'templates/blocks/lil_store_js.html' %}
<script type="text/javascript" src={% static "app.js" %}></script>
<script>
var schoolDiscount = parseFloat({{ config.SERVICE_DISCOUNT }});

@ -18,7 +18,7 @@ from django.contrib import admin
from django.views.generic import TemplateView
from django.urls import path, include
from apps.content.views import ContestEditView, ContestView, ContestWorkView, contest_work_comment
from apps.course.views import (
CoursesView, likes, coursecomment,
CourseView, LessonView, SearchView,
@ -27,9 +27,9 @@ from apps.course.views import (
)
from apps.user.views import (
AuthorRequestView, UserView,
UserEditView, NotificationEditView,
ProfileEditView, NotificationEditView,
PaymentHistoryView, resend_email_verify,
SubscribeView,
SubscribeView, ProfileView,
)
from apps.payment.views import (
CourseBuySuccessView, CourseBuyView,
@ -67,8 +67,9 @@ urlpatterns = [
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/profile/', ProfileView.as_view(), name='user-profile'),
path('user/profile/edit', ProfileEditView.as_view(), name='user-edit-profile'),
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>/notifications', NotificationEditView.as_view(), name='user-edit-notifications'),
path('user/<int:pk>/payments', PaymentHistoryView.as_view(), name='user-edit-payments'),
path('user/resend-email-verify', resend_email_verify, name='resend-email-verify'),
@ -82,6 +83,11 @@ urlpatterns = [
path('api/v1/', include(('api.v1.urls', 'api_v1'))),
path('school/', include(('apps.school.urls', 'school'))),
path('test', TemplateView.as_view(template_name='templates/lilcity/test.html'), name='test'),
path('contest/create', ContestEditView.as_view(), name='contest_create'),
path('contest/<str:slug>/edit', ContestEditView.as_view(), name='contest_edit'),
path('contest/<str:slug>/', ContestView.as_view(), name='contest'),
path('contest-work/<int:pk>/', ContestWorkView.as_view(), name='contest_work'),
path('contest-work/<int:contest_work_id>/comment', contest_work_comment, name='contest_work_comment'),
]

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save