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

remotes/origin/hotfix/LIL-661
gzbender 8 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: script:
- export REVIEW_HOST=$CI_COMMIT_REF_SLUG-$REVIEW_DOMAIN - export REVIEW_HOST=$CI_COMMIT_REF_SLUG-$REVIEW_DOMAIN
- cd docker - cd docker
- cp .env.review .env
- docker-compose -f docker-compose-review.yml -p back$CI_COMMIT_REF_NAME down - docker-compose -f docker-compose-review.yml -p back$CI_COMMIT_REF_NAME down
- rm -rf /work/data/back_${CI_COMMIT_REF_NAME}/ - rm -rf /work/data/back_${CI_COMMIT_REF_NAME}/
when: manual when: manual

@ -3,8 +3,7 @@ from rest_framework import serializers
from apps.content.models import ( from apps.content.models import (
Baner, Content, Image, Text, ImageText, Video, Baner, Content, Image, Text, ImageText, Video,
Gallery, GalleryImage, ImageObject, Gallery, GalleryImage, ImageObject,)
)
from . import Base64ImageField from . import Base64ImageField
@ -14,6 +13,7 @@ BASE_CONTENT_FIELDS = (
'uuid', 'uuid',
'course', 'course',
'lesson', 'lesson',
'contest',
'live_lesson', 'live_lesson',
'title', 'title',
'position', 'position',
@ -85,12 +85,16 @@ class ImageObjectSerializer(serializers.ModelSerializer):
image = Base64ImageField( image = Base64ImageField(
required=True, allow_empty_file=False, allow_null=False, read_only=False, 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: class Meta:
model = ImageObject model = ImageObject
fields = ( fields = (
'id', 'id',
'image', 'image',
'image_thumbnail',
'created_at', 'created_at',
'update_at', 'update_at',
) )
@ -254,3 +258,4 @@ class ContentSerializer(serializers.ModelSerializer):
elif isinstance(obj, Gallery): elif isinstance(obj, Gallery):
return GallerySerializer(obj, context=self.context).to_representation(obj) return GallerySerializer(obj, context=self.context).to_representation(obj)
return super(ContentSerializer, self).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 import serializers
from rest_framework.validators import UniqueValidator from rest_framework.validators import UniqueValidator
from django.contrib.auth import get_user_model
from apps.course.models import ( from apps.course.models import (
Category, Course, Category, Course,
Comment, CourseComment, LessonComment, Comment, CourseComment, LessonComment,
@ -15,12 +19,15 @@ from .content import (
from apps.content.models import ( from apps.content.models import (
Content, Image, Text, ImageText, Video, Content, Image, Text, ImageText, Video,
Gallery, GalleryImage, ImageObject, Gallery, GalleryImage, ImageObject,
) ContestWork)
from .user import UserSerializer from .user import UserSerializer
from .mixins import DispatchContentMixin, DispatchGalleryMixin, DispatchMaterialMixin from .mixins import DispatchContentMixin, DispatchGalleryMixin, DispatchMaterialMixin
User = get_user_model()
class MaterialCreateSerializer(serializers.ModelSerializer): class MaterialCreateSerializer(serializers.ModelSerializer):
class Meta: 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 CategorySerializer(serializers.ModelSerializer):
class Meta: class Meta:

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

@ -46,8 +46,29 @@ class AuthorBalanceCreateSerializer(serializers.ModelSerializer):
return AuthorBalanceSerializer(instance, context=self.context).to_representation(instance) 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): class AuthorBalanceSerializer(serializers.ModelSerializer):
author = UserSerializer() author = UserSerializer()
payment = serializers.SerializerMethodField()
class Meta: class Meta:
model = AuthorBalance model = AuthorBalance
@ -70,6 +91,24 @@ class AuthorBalanceSerializer(serializers.ModelSerializer):
'payment', '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): class PaymentSerializer(serializers.ModelSerializer):
user = UserSerializer() user = UserSerializer()
@ -91,6 +130,20 @@ class PaymentSerializer(serializers.ModelSerializer):
return SchoolPaymentSerializer(instance, context=self.context).to_representation(instance) 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): class CoursePaymentSerializer(serializers.ModelSerializer):
user = UserSerializer() user = UserSerializer()
course = CourseSerializer() 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): class SchoolPaymentSerializer(serializers.ModelSerializer):
user = UserSerializer() user = UserSerializer()

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

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

@ -18,7 +18,7 @@ from .views import (
UserViewSet, LessonViewSet, ImageObjectViewSet, UserViewSet, LessonViewSet, ImageObjectViewSet,
SchoolScheduleViewSet, LiveLessonViewSet, SchoolScheduleViewSet, LiveLessonViewSet,
PaymentViewSet, ObjectCommentsViewSet, PaymentViewSet, ObjectCommentsViewSet,
) ContestViewSet, ContestWorkViewSet)
router = DefaultRouter() router = DefaultRouter()
router.register(r'author-requests', AuthorRequestViewSet, base_name='author-requests') 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'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') # router.register(r'configs', ConfigViewSet, base_name='configs')

@ -1,7 +1,7 @@
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from rest_framework import status, views, viewsets, generics 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 rest_framework.response import Response
from . import ExtendedModelViewSet from . import ExtendedModelViewSet
@ -14,7 +14,7 @@ from .serializers.course import (
CommentSerializer, CommentCreateSerializer, CommentSerializer, CommentCreateSerializer,
MaterialSerializer, MaterialCreateSerializer, MaterialSerializer, MaterialCreateSerializer,
LessonSerializer, LessonCreateSerializer, LessonSerializer, LessonCreateSerializer,
CourseCommentSerializer, LessonCommentSerializer, LikeCreateSerializer, CourseCommentSerializer, LessonCommentSerializer,
) )
from .serializers.content import ( from .serializers.content import (
BanerSerializer, BanerSerializer,
@ -35,11 +35,14 @@ from .serializers.payment import (
AuthorBalanceSerializer, AuthorBalanceCreateSerializer, AuthorBalanceSerializer, AuthorBalanceCreateSerializer,
PaymentSerializer, CoursePaymentSerializer, PaymentSerializer, CoursePaymentSerializer,
SchoolPaymentSerializer, SchoolPaymentSerializer,
) CoursePaymentCreateSerializer, SchoolPaymentCreateSerializer)
from .serializers.user import ( from .serializers.user import (
AuthorRequestSerializer, AuthorRequestSerializer,
UserSerializer, UserPhotoSerializer, UserSerializer, UserPhotoSerializer,
) )
from .serializers.contest import (
ContestCreateSerializer, ContestSerializer, ContestWorkSerializer, ContestWorkCreateSerializer
)
from .permissions import ( from .permissions import (
IsAdmin, IsAdminOrIsSelf, IsAdmin, IsAdminOrIsSelf,
@ -57,7 +60,7 @@ from apps.config.models import Config
from apps.content.models import ( from apps.content.models import (
Baner, Image, Text, ImageText, Video, Baner, Image, Text, ImageText, Video,
Gallery, GalleryImage, ImageObject, Gallery, GalleryImage, ImageObject,
) Contest, ContestWork)
from apps.payment.models import ( from apps.payment.models import (
AuthorBalance, Payment, AuthorBalance, Payment,
CoursePayment, SchoolPayment, CoursePayment, SchoolPayment,
@ -71,7 +74,7 @@ User = get_user_model()
class AuthorBalanceViewSet(ExtendedModelViewSet): class AuthorBalanceViewSet(ExtendedModelViewSet):
queryset = AuthorBalance.objects.filter( 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 = AuthorBalanceCreateSerializer
serializer_class_map = { serializer_class_map = {
@ -112,6 +115,8 @@ class BanerViewSet(ExtendedModelViewSet):
class ImageObjectViewSet(ExtendedModelViewSet): class ImageObjectViewSet(ExtendedModelViewSet):
queryset = ImageObject.objects.all() queryset = ImageObject.objects.all()
serializer_class = ImageObjectSerializer serializer_class = ImageObjectSerializer
# FIXME
authentication_classes = []
# permission_classes = (IsAuthorOrAdmin,) # permission_classes = (IsAuthorOrAdmin,)
@ -128,11 +133,41 @@ class MaterialViewSet(ExtendedModelViewSet):
class LikeViewSet(ExtendedModelViewSet): class LikeViewSet(ExtendedModelViewSet):
OBJ_TYPE_CONTEST_WORK = 'contest_work'
queryset = Like.objects.select_related('user').all() 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',) search_fields = ('user__email', 'user__firstname', 'user__lastname',)
ordering_fields = ('created_at', 'update_at',) ordering_fields = ('created_at', 'update_at',)
# permission_classes = (IsAdmin,) # 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): class CategoryViewSet(ExtendedModelViewSet):
@ -486,14 +521,77 @@ class AuthorRequestViewSet(ExtendedModelViewSet):
filter_fields = ('status',) filter_fields = ('status',)
class PaymentViewSet(ExtendedModelViewSet): class PaymentViewSet(viewsets.ModelViewSet):
queryset = Payment.objects.filter(status__isnull=False).order_by('-created_at') queryset = Payment.objects.all()
serializer_class = PaymentSerializer serializer_class = PaymentSerializer
permission_classes = (IsAdmin,) permission_classes = (IsAdmin,)
filter_fields = ('status',) filter_fields = ('status', 'user',)
ordering_fields = ( ordering_fields = (
'id', 'user__email', 'id', 'user__email',
'user__first_name', 'user__last_name', 'user__first_name', 'user__last_name',
'amount', 'created_at', 'amount', 'created_at',
) )
search_fields = ('user__email', 'user__first_name', 'user__last_name',) 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() last_name = forms.CharField()
email = forms.EmailField() email = forms.EmailField()
password = forms.CharField() 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): def form_valid(self, form):
first_name = form.cleaned_data['first_name'] first_name = form.cleaned_data['first_name']
last_name = form.cleaned_data['last_name'] last_name = form.cleaned_data['last_name']
email = form.cleaned_data['email'] email = form.cleaned_data['email'].lower()
password = form.cleaned_data['password'] password = form.cleaned_data['password']
user, created = User.objects.get_or_create( user, created = User.objects.get_or_create(
@ -170,8 +170,9 @@ class FacebookLoginOrRegistration(View):
"errors": {"email": 'is field required'} "errors": {"email": 'is field required'}
}) })
else: else:
email = email.lower()
try: try:
user = User.objects.get(email=email) user = User.objects.get(email__iexact=email)
except User.DoesNotExist: except User.DoesNotExist:
first_name = data.get('first_name', '') first_name = data.get('first_name', '')
last_name = data.get('last_name', '') last_name = data.get('last_name', '')

@ -8,6 +8,7 @@ from polymorphic.admin import (
from apps.content.models import ( from apps.content.models import (
Baner, Content, Image, Text, ImageText, Video, Baner, Content, Image, Text, ImageText, Video,
Gallery, GalleryImage, ImageObject, Gallery, GalleryImage, ImageObject,
Contest,ContestWork,
) )
@ -79,3 +80,13 @@ class ContentAdmin(PolymorphicParentModelAdmin):
@admin.register(GalleryImage) @admin.register(GalleryImage)
class GalleryImageAdmin(admin.ModelAdmin): class GalleryImageAdmin(admin.ModelAdmin):
pass 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 urllib.parse import urlparse
from django.conf import settings
from django.db import models 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 from polymorphic.models import PolymorphicModel
User = get_user_model()
class ImageObject(models.Model): class ImageObject(models.Model):
image = models.ImageField('Изображение', upload_to='content/imageobject') 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) created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True) update_at = models.DateTimeField(auto_now=True)
@ -36,6 +50,12 @@ class Content(PolymorphicModel):
verbose_name='Урок онлайн школы', verbose_name='Урок онлайн школы',
related_name='content', 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='') title = models.CharField('Заголовок', max_length=100, default='')
position = models.PositiveSmallIntegerField( position = models.PositiveSmallIntegerField(
'Положение на странице', 'Положение на странице',
@ -131,3 +151,62 @@ class Baner(models.Model):
if self.use: if self.use:
Baner.objects.filter(use=True).update(use=False) Baner.objects.filter(use=True).update(use=False)
return super().save(*args, **kwargs) 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 %} {% load thumbnail %}
{% if results %} {% if results %}
<div class="title">Галерея итогов обучения</div> <div class="title">Галерея итогов обучения</div>
<div class="examples gallery"> <div class="examples gallery">
{% for image in course.gallery.gallery_images.all %} {% for image in course.gallery.gallery_images.all %}
<div class="examples__item"> <div class="examples__item">
<a href="{{ image.img.image.url }}"> <a href="{{ image.img.image.url }}">
{% thumbnail image.img.image "165x165" crop="center" as im %} {% thumbnail image.img.image "165x165" crop="center" as im %}
<img class="examples__pic" src="{{ im.url }}"> <img class="examples__pic" src="{{ im.url }}">
{% endthumbnail %} {% endthumbnail %}
</a> </a>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
{% else %} {% else %}
<div class="section section_gradient"> <div class="section section_gradient">
<div class="section__center center center_sm"> <div class="section__center center center_sm">
<div class="title">{{ content.title }}</div> <div class="title">{{ content.title }}</div>
<div class="examples gallery"> <div class="examples gallery">
{% for image in content.gallery_images.all %} {% for image in content.gallery_images.all %}
<div class="examples__item"> <div class="examples__item">
<a href="{{ image.img.image.url }}"> <a href="{{ image.img.image.url }}">
{% thumbnail image.img.image "165x165" crop="center" as im %} {% thumbnail image.img.image "165x165" crop="center" as im %}
<img class="examples__pic" src="{{ im.url }}"> <img class="examples__pic" src="{{ im.url }}">
{% endthumbnail %} {% endthumbnail %}
</a> </a>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
</div> </div>
{% endif %} {% endif %}

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

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

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

@ -3,10 +3,10 @@
<div class="content-block title"> <div class="content-block title">
{{ content.title }} {{ content.title }}
</div> </div>
<div class="iframe__container"> <div class="content-block__video iframe__container">
{% if 'youtube.com' in content.url or 'youtu.be' in content.url %} {% 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" <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 %} {% elif 'vimeo.com' in content.url %}
<iframe src="https://player.vimeo.com/video/{{ content.video_index }}" frameborder="0" webkitallowfullscreen <iframe src="https://player.vimeo.com/video/{{ content.video_index }}" frameborder="0" webkitallowfullscreen
mozallowfullscreen allowfullscreen> mozallowfullscreen allowfullscreen>
@ -40,4 +40,4 @@
height: 360px; 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): 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: class Meta:
model = Course 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 import arrow
from random import shuffle
from uuid import uuid4 from uuid import uuid4
from django.db import models from django.db import models
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils import timezone from django.utils import timezone
from django.utils.text import slugify
from django.utils.timezone import now from django.utils.timezone import now
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.conf import settings
from polymorphic_tree.models import PolymorphicMPTTModel, PolymorphicTreeForeignKey from polymorphic_tree.models import PolymorphicMPTTModel, PolymorphicTreeForeignKey
from project.mixins import BaseModel, DeactivatedMixin from project.mixins import BaseModel, DeactivatedMixin
from .manager import CategoryQuerySet from apps.content.models import ImageObject, Gallery, Video, ContestWork
from apps.content.models import ImageObject, Gallery, Video
User = get_user_model() User = get_user_model()
class Like(models.Model): class Like(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE)
ip = models.GenericIPAddressField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True) update_at = models.DateTimeField(auto_now=True)
@ -65,7 +65,7 @@ class Course(BaseModel, DeactivatedMixin):
) )
cover = models.ForeignKey( cover = models.ForeignKey(
ImageObject, related_name='course_covers', ImageObject, related_name='course_covers',
verbose_name='Обложка курса', on_delete=models.CASCADE, verbose_name='Обложка курса', on_delete=models.SET_NULL,
null=True, blank=True, null=True, blank=True,
) )
price = models.DecimalField( price = models.DecimalField(
@ -97,7 +97,7 @@ class Course(BaseModel, DeactivatedMixin):
class Meta: class Meta:
verbose_name = 'Курс' verbose_name = 'Курс'
verbose_name_plural = 'Курсы' verbose_name_plural = 'Курсы'
ordering = ['-created_at'] ordering = ['-is_featured', ]
def __str__(self): def __str__(self):
return str(self.id) + ' ' + self.title return str(self.id) + ' ' + self.title
@ -129,11 +129,11 @@ class Course(BaseModel, DeactivatedMixin):
@property @property
def deferred_start_at_humanize(self): 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 @property
def created_at_humanize(self): 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 @property
def is_deferred_start(self): def is_deferred_start(self):
@ -148,6 +148,24 @@ class Course(BaseModel, DeactivatedMixin):
def count_videos_in_lessons(self): def count_videos_in_lessons(self):
return Video.objects.filter(lesson__in=self.lessons.all()).count() 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): class Category(models.Model):
title = models.CharField('Название категории', max_length=100) title = models.CharField('Название категории', max_length=100)
@ -172,7 +190,7 @@ class Lesson(BaseModel, DeactivatedMixin):
) )
cover = models.ForeignKey( cover = models.ForeignKey(
ImageObject, related_name='lesson_covers', ImageObject, related_name='lesson_covers',
verbose_name='Обложка урока', on_delete=models.CASCADE, verbose_name='Обложка урока', on_delete=models.SET_NULL,
null=True, blank=True, null=True, blank=True,
) )
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
@ -204,7 +222,7 @@ class Material(models.Model):
title = models.CharField('Название материала', max_length=100) title = models.CharField('Название материала', max_length=100)
cover = models.ForeignKey( cover = models.ForeignKey(
ImageObject, related_name='material_covers', ImageObject, related_name='material_covers',
verbose_name='Обложка материала', on_delete=models.CASCADE, verbose_name='Обложка материала', on_delete=models.SET_NULL,
null=True, blank=True, null=True, blank=True,
) )
short_description = models.TextField('Краткое описание материала') short_description = models.TextField('Краткое описание материала')
@ -234,7 +252,7 @@ class Comment(PolymorphicMPTTModel, DeactivatedMixin):
@property @property
def created_at_humanize(self): 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): def __str__(self):
return self.content return self.content
@ -264,3 +282,7 @@ class LessonComment(Comment):
class Meta(Comment.Meta): class Meta(Comment.Meta):
verbose_name = 'Комментарий урока' verbose_name = 'Комментарий урока'
verbose_name_plural = 'Комментарии уроков' verbose_name_plural = 'Комментарии уроков'
class ContestWorkComment(Comment):
contest_work = models.ForeignKey(ContestWork, on_delete=models.CASCADE, related_name='comments')

@ -48,7 +48,7 @@
</a> </a>
<div class="courses__details"> <div class="courses__details">
<a class="courses__theme theme {{ theme_color }}" <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 %} {% if not course.is_free %}
<div class="courses__price">{{ course.price|floatformat:"-2" }}₽</div> <div class="courses__price">{{ course.price|floatformat:"-2" }}₽</div>
{% endif %} {% endif %}

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

@ -25,7 +25,7 @@
</div> </div>
<div class="go__title">Вернуться</div> <div class="go__title">Вернуться</div>
</a> </a>
{% if not paid and course.price %} {% if not paid and course.price and not has_full_access %}
<a <a
class="go__btn btn{% if pending %} btn_gray{% endif %} btn_md" class="go__btn btn{% if pending %} btn_gray{% endif %} btn_md"
{% if user.is_authenticated %} {% if user.is_authenticated %}
@ -154,10 +154,10 @@
</a> </a>
{% else %} {% else %}
<a <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 %}" 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"> <svg class="icon icon-lock">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-lock"></use> <use xlink:href="{% static 'img/sprite.svg' %}#icon-lock"></use>
</svg> </svg>
@ -180,19 +180,18 @@
<a href="#"> <a href="#">
{% endif %} {% endif %}
<div class="lessons__item"> <div class="lessons__item">
<div class="lessons__subtitle subtitle">{{ forloop.counter }}. {{ lesson.title }}</div>
<div class="lessons__row"> <div class="lessons__row">
{% if lesson.cover %}
<div class="lessons__preview"> <div class="lessons__preview">
<div class="lessons__pic-wrapper"> <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>
</div> </div>
{% endif %} <div>
<div class="lessons__content">{{ lesson.short_description | truncatechars_html:800 | safe }}</div> <div class="lessons__subtitle subtitle">{{ forloop.counter }}. {{ lesson.title }}</div>
</div> <div class="lessons__content">{{ lesson.short_description | truncatechars_html:800 | safe }}</div>
<div class="lessons__row"> <a href="{% url 'lesson' pk=lesson.id %}" class="btn btn_stroke">Перейти к уроку</a>
<a href="{% url 'lesson' pk=lesson.id %}" class="btn btn_stroke">Перейти к уроку</a> </div>
</div> </div>
</div> </div>
</a> </a>
@ -234,31 +233,7 @@
</div> </div>
<div class="section"> <div class="section">
<div class="section__center center center_sm"> <div class="section__center center center_sm">
<div class="share"> {% include 'templates/blocks/share.html' with share_object_name='курсом' %}
<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>
</div> </div>
</div> </div>
{% endblock content %} {% endblock content %}

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

@ -1,5 +1,6 @@
{% for cat in category_items %} {% 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 class="select__title">{{ cat.title }}</div>
</div> </div>
{% endfor %} {% endfor %}

@ -2,7 +2,7 @@
<div class="header__title">Все курсы</div> <div class="header__title">Все курсы</div>
</a> </a>
{% for cat in category_items %} {% 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> <div class="header__title">{{ cat.title }}</div>
</a> </a>
{% endfor %} {% endfor %}

@ -2,6 +2,9 @@
{% load static %} {% load static %}
{% block title %}{{ lesson.title }} - {{ block.super }}{% endblock title %} {% 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 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 %} {% block content %}
<div class="section" style="margin-bottom:0;padding-bottom:0"> <div class="section" style="margin-bottom:0;padding-bottom:0">
@ -27,24 +30,43 @@
{% endif %} {% endif %}
</div> </div>
<div class="lesson"> <div class="lesson">
<div class="lesson__subtitle subtitle">{{ lesson.title }}</div> <div class="lesson__row">
<div class="lesson__content">{{ lesson.short_description }}</div> <div class="lesson__preview">
{% comment %} <a class="lesson__video video" href="#"> <div class="lesson__pic-wrapper">
{% if lesson.cover %} <img class="lesson__pic"
<img class="video__pic" src="{{ lesson.cover.image.url }}"/> src="{% if lesson.cover %}{{ lesson.cover.image_thumbnail.url }}{% else %}{% static 'img/no_cover.png' %}{% endif %}">
{% else %} </div>
</div>
{% endif %} <div>
<svg class="icon icon-play"> <div class="lesson__subtitle subtitle">{{ lesson.title }}</div>
<use xlink:href="{% static 'img/sprite.svg' %}#icon-play"></use> <div class="lesson__content">{{ lesson.short_description | linebreaks }}</div>
</svg> <a href="{% url 'user' lesson.author.id %}">
</a> {% endcomment %} <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> </div>
</div> </div>
{% for content in lesson.content.all %} {% 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 %} {% include template %}
{% endwith %} {% endwith %}
@ -102,6 +124,7 @@
{% endif %} {% endif %}
default-author-picture="{% static 'img/user_default.jpg' %}" default-author-picture="{% static 'img/user_default.jpg' %}"
access-token="{{ request.user.auth_token }}"></comments> access-token="{{ request.user.auth_token }}"></comments>
</div> </div>
</div> </div>
</div> </div>

@ -9,7 +9,7 @@ register = template.Library()
def category_items(category=None): def category_items(category=None):
return { return {
'category_items': Category.objects.filter(courses__status=Course.PUBLISHED).exclude(courses=None).distinct(), '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): def category_menu_items(category=None):
return { return {
'category_items': Category.objects.filter(courses__status=Course.PUBLISHED).exclude(courses=None).distinct(), '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): def get(self, request, *args, **kwargs):
response = super().get(request, *args, **kwargs) response = super().get(request, *args, **kwargs)
context = self.get_context_data() 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 return response
# ((self.object.status != Course.PUBLISHED and request.user.role != User.ADMIN_ROLE) or # ((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 context['only_lessons'] = self.only_lessons
if self.only_lessons: if self.only_lessons:
context['lessons'] = self.object.lessons.order_by('position') 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 return context
def get_queryset(self): def get_queryset(self):
@ -234,7 +241,6 @@ class CoursesView(ListView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset() self.object_list = self.get_queryset()
allow_empty = self.get_allow_empty()
if request.is_ajax(): if request.is_ajax():
context = self.get_context_data() context = self.get_context_data()
template_name = self.get_template_names() template_name = self.get_template_names()
@ -276,6 +282,7 @@ class CoursesView(ListView):
context = super().get_context_data() context = super().get_context_data()
filtered = CourseFilter(self.request.GET) filtered = CourseFilter(self.request.GET)
context.update(filtered.data) context.update(filtered.data)
context['course_items'] = Course.shuffle(context.get('course_items'))
return context return context
def get_template_names(self): def get_template_names(self):
@ -292,10 +299,20 @@ class LessonView(DetailView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
response = super().get(request, *args, **kwargs) response = super().get(request, *args, **kwargs)
if (self.object.course.status != Course.PUBLISHED and not paid = request.user.is_authenticated and self.object.course.payments.filter(
(request.user.role == User.ADMIN_ROLE or user=self.request.user,
self.object.course.author == request.user)): status__in=[
raise Http404 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 return response
def get_context_data(self, **kwargs): 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 import arrow
from django.db.models import Func, F
from paymentwall import Pingback from paymentwall import Pingback
from polymorphic.models import PolymorphicModel 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.contrib.postgres.fields import ArrayField, JSONField
from django.core.validators import RegexValidator from django.core.validators import RegexValidator
from django.utils.timezone import now 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.course.models import Course
from apps.config.models import Config from apps.config.models import Config
@ -108,6 +110,61 @@ class Payment(PolymorphicModel):
verbose_name_plural = 'Платежи' verbose_name_plural = 'Платежи'
ordering = ('created_at',) 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): def calc_commission(self):
return self.amount * config.SERVICE_COMMISSION / 100 return self.amount * config.SERVICE_COMMISSION / 100
@ -136,7 +193,9 @@ class CoursePayment(Payment):
verbose_name_plural = 'Платежи за курсы' verbose_name_plural = 'Платежи за курсы'
def save(self, *args, **kwargs): 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) super().save(*args, **kwargs)
author_balance = getattr(self, 'authorbalance', None) author_balance = getattr(self, 'authorbalance', None)
if not author_balance: if not author_balance:
@ -168,34 +227,11 @@ class SchoolPayment(Payment):
return days return days
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
aggregate = SchoolSchedule.objects.filter( if self.status is None:
weekday__in=self.weekdays, amount_data = Payment.calc_amount(school_payment=self)
).aggregate( self.amount = amount_data.get('amount')
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
super().save(*args, **kwargs) super().save(*args, **kwargs)
@property @property
def date_end_humanize(self): 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 %} {% if school %}
<div class="done__title title">Вы успешно приобрели доступ к урокам онлайн-школы!</div> <div class="done__title title">Вы успешно приобрели доступ к урокам онлайн-школы!</div>
<div class="done__foot"> <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> </div>
{% else %} {% else %}
<div class="done__title title">Вы успешно приобрели курс!</div> <div class="done__title title">Вы успешно приобрели курс!</div>

@ -1,3 +1,5 @@
from decimal import Decimal
import arrow import arrow
import json import json
import logging import logging
@ -17,6 +19,7 @@ from django.views.decorators.csrf import csrf_exempt
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.timezone import now from django.utils.timezone import now
from django.conf import settings
from paymentwall import Pingback, Product, Widget from paymentwall import Pingback, Product, Widget
@ -97,7 +100,6 @@ class SchoolBuyView(TemplateView):
host = urlsplit(self.request.META.get('HTTP_REFERER')) host = urlsplit(self.request.META.get('HTTP_REFERER'))
host = str(host[0]) + '://' + str(host[1]) host = str(host[0]) + '://' + str(host[1])
weekdays = set(request.GET.getlist('weekdays', [])) weekdays = set(request.GET.getlist('weekdays', []))
add_days = 'add_days' in request.GET
roistat_visit = request.COOKIES.get('roistat_visit', None) roistat_visit = request.COOKIES.get('roistat_visit', None)
if not weekdays: if not weekdays:
messages.error(request, 'Выберите несколько дней недели.') messages.error(request, 'Выберите несколько дней недели.')
@ -107,24 +109,30 @@ class SchoolBuyView(TemplateView):
except ValueError: except ValueError:
messages.error(request, 'Ошибка выбора дней недели.') messages.error(request, 'Ошибка выбора дней недели.')
return redirect('school:summer-school') 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: 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( school_payment = SchoolPayment.objects.create(
user=request.user, user=request.user,
weekdays=weekdays, weekdays=weekdays,
date_start=now().date(), date_start=now().date(),
date_end=_school_payment.date_end, date_end=prev_school_payment.date_end,
add_days=True, add_days=True,
roistat_visit=roistat_visit, roistat_visit=roistat_visit,
) )
if school_payment.amount <= 0: if school_payment.amount <= 0:
messages.error(request, 'Выбранные дни отсутствуют в оставшемся периоде подписки') messages.error(request, 'Выбранные дни отсутствуют в оставшемся периоде подписки')
return redirect(reverse_lazy('school:summer-school')) return redirect(reverse_lazy('school:school'))
else: else:
school_payment = SchoolPayment.objects.create( school_payment = SchoolPayment.objects.create(
user=request.user, user=request.user,
@ -156,12 +164,13 @@ class SchoolBuyView(TemplateView):
@method_decorator(csrf_exempt, name='dispatch') @method_decorator(csrf_exempt, name='dispatch')
class PaymentwallCallbackView(View): class PaymentwallCallbackView(View):
def add_months(self, sourcedate, months): def add_months(self, sourcedate, months=1):
month = sourcedate.month - 1 + months result = arrow.get(sourcedate, settings.TIME_ZONE).shift(months=months)
year = sourcedate.year + month // 12 if months == 1:
month = month % 12 + 1 if (sourcedate.month == 2 and sourcedate.day >= 28) or (sourcedate.day == 31 and result.day <= 30)\
day = min(sourcedate.day, calendar.monthrange(year, month)[1]) or (sourcedate.month == 1 and sourcedate.day >= 29 and result.day == 28):
return datetime.date(year, month, day) result = result.replace(day=1, month=result.month + 1)
return result.datetime
def get_request_ip(self): def get_request_ip(self):
x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR') x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR')
@ -196,12 +205,18 @@ class PaymentwallCallbackView(View):
payment.status = pingback.get_type() payment.status = pingback.get_type()
payment.data = payment_raw_data payment.data = payment_raw_data
if pingback.is_deliverable(): 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( transaction_to_mixpanel.delay(
payment.user.id, payment.user.id,
payment.amount, payment.amount,
now().strftime('%Y-%m-%dT%H:%M:%S'), now().strftime('%Y-%m-%dT%H:%M:%S'),
product_type_name, product_type_name,
) )
if product_type_name == 'school': if product_type_name == 'school':
school_payment = SchoolPayment.objects.filter( school_payment = SchoolPayment.objects.filter(
user=payment.user, user=payment.user,
@ -216,17 +231,14 @@ class PaymentwallCallbackView(View):
).last() ).last()
if school_payment: if school_payment:
if payment.add_days: 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 date_end = school_payment.date_end
else: else:
date_start = arrow.get(school_payment.date_end).shift(days=1).datetime date_start = arrow.get(school_payment.date_end, settings.TIME_ZONE).shift(days=1).datetime
date_end = arrow.get(date_start).shift(months=1).datetime date_end = self.add_months(date_start)
else: else:
#month = 0 if now().day >= 1 and now().day <= 10 else 1 date_start = now().date()
# Логика июльского лагеря: до конца июля приобретаем только на текущий месяц date_end = self.add_months(date_start)
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
payment.date_start = date_start payment.date_start = date_start
payment.date_end = date_end payment.date_end = date_end
if product_type_name == 'course': if product_type_name == 'course':
@ -251,6 +263,7 @@ class PaymentwallCallbackView(View):
'update_at': payment.update_at, 'update_at': payment.update_at,
} }
payment.save() payment.save()
product_payment_to_mixpanel.delay( product_payment_to_mixpanel.delay(
payment.user.id, payment.user.id,
f'{product_type_name.title()} payment', f'{product_type_name.title()} payment',
@ -268,6 +281,7 @@ class PaymentwallCallbackView(View):
product_type_name, product_type_name,
payment.roistat_visit, payment.roistat_visit,
) )
author_balance = getattr(payment, 'author_balance', None) author_balance = getattr(payment, 'author_balance', None)
if author_balance and author_balance.type == AuthorBalance.IN: if author_balance and author_balance.type == AuthorBalance.IN:
if pingback.is_deliverable(): if pingback.is_deliverable():
@ -276,7 +290,6 @@ class PaymentwallCallbackView(View):
payment.author_balance.status = AuthorBalance.PENDING payment.author_balance.status = AuthorBalance.PENDING
else: else:
payment.author_balance.status = AuthorBalance.DECLINED payment.author_balance.status = AuthorBalance.DECLINED
payment.author_balance.save() payment.author_balance.save()
return HttpResponse('OK') return HttpResponse('OK')
else: 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 import arrow
from datetime import datetime, timedelta from datetime import datetime, timedelta, date
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.functional import cached_property
from django.utils.timezone import now from django.utils.timezone import now
from project.mixins import BaseModel, DeactivatedMixin from project.mixins import BaseModel, DeactivatedMixin
@ -13,6 +14,7 @@ from apps.payment import models as payment_models
class SchoolSchedule(models.Model): class SchoolSchedule(models.Model):
WEEKDAY_SHORT_NAMES = ('пн', 'вт', 'ср', 'чт', 'пт', 'сб', 'вс')
WEEKDAY_CHOICES = ( WEEKDAY_CHOICES = (
(1, 'понедельник'), (1, 'понедельник'),
(2, 'вторник'), (2, 'вторник'),
@ -37,6 +39,15 @@ class SchoolSchedule(models.Model):
'Скидка, в валюте', max_digits=8, decimal_places=2, default=0 'Скидка, в валюте', max_digits=8, decimal_places=2, default=0
) )
start_at = models.TimeField('Начало урока', null=True) 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: class Meta:
ordering = ('weekday',) ordering = ('weekday',)
@ -50,15 +61,18 @@ class SchoolSchedule(models.Model):
end_at = datetime.combine(now().today(), self.start_at) + timedelta(hours=1) 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() return self.start_at <= now().time() and end_at.time() >= now().time() and self.weekday == now().isoweekday()
@cached_property
def current_live_lesson(self): def current_live_lesson(self):
now_time = now() september2018 = date(2018, 9, 1)
date_start = max(september2018, now().date())
live_lesson = LiveLesson.objects.filter( live_lesson = LiveLesson.objects.filter(
date__week_day=self.weekday % 7 + 1, 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, deactivated_at__isnull=True,
).first() ).first()
return live_lesson return live_lesson
@cached_property
def previous_live_lesson(self): def previous_live_lesson(self):
now_time = now() now_time = now()
live_lesson = LiveLesson.objects.filter( live_lesson = LiveLesson.objects.filter(
@ -84,7 +98,7 @@ class SchoolScheduleImage(models.Model):
ImageObject, ImageObject,
related_name='schoolschedule_images', related_name='schoolschedule_images',
verbose_name='Объект изображения', verbose_name='Объект изображения',
on_delete=models.CASCADE, on_delete=models.SET_NULL,
null=True, null=True,
blank=True, blank=True,
) )
@ -107,7 +121,7 @@ class LiveLesson(BaseModel, DeactivatedMixin):
ImageObject, ImageObject,
related_name='livelesson_covers', related_name='livelesson_covers',
verbose_name='Обложка урока школы', verbose_name='Обложка урока школы',
on_delete=models.CASCADE, on_delete=models.SET_NULL,
null=True, null=True,
blank=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 <a
{% if not user.is_authenticated %} data-popup=".js-popup-buy"
data-popup=".js-popup-auth"
{% endif %}
class="timing__btn btn" class="timing__btn btn"
href="{% url 'school-checkout' %}?weekdays={{ school_schedule.weekday }}&add_days=true" data-day="{{ school_schedule.weekday }}"
>купить</a> >купить</a>

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

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

@ -6,78 +6,11 @@
<div class="timing js-timing"> <div class="timing js-timing">
<div class="timing__week"> <div class="timing__week">
{% for school_schedule in school_schedules %} {% for school_schedule in school_schedules %}
{% with current_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 %}
<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 %}
{% endfor %} {% endfor %}
</div> </div>
<div class="timing__foot"> <div class="timing__foot">
<!--{% include './pay_btn.html' %}--> {# include './pay_btn.html' #}
<a target="_blank" class="timing__print" href="{% url 'school:school_schedules-print' %}">Распечатать расписание <a target="_blank" class="timing__print" href="{% url 'school:school_schedules-print' %}">Распечатать расписание
<span class="bold">чтобы не забыть</span> <span class="bold">чтобы не забыть</span>
<svg class="icon icon-print"> <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 %} {% for school_schedule in school_schedules %}
{% if is_previous %} {% if is_previous %}
{% if school_schedule.previous_live_lesson in live_lessons %} {% 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 %} {% endif %}
{% else %} {% 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 %} {% endif %}
{% endfor %} {% endfor %}
{% endif %} {% endif %}

@ -2,33 +2,38 @@
{% load static %} {% load static %}
{% block title %}{{ livelesson.title }} - {{ block.super }}{% endblock title %} {% 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 %} {% block content %}
<div class="section" style="margin-bottom:0;padding-bottom:0"> <div class="section" style="margin-bottom:0;padding-bottom:0">
<div class="section__center center center_sm"> <div class="section__center center center_sm">
<div class="lesson"> <div class="lesson">
<div class="lesson__subtitle subtitle">{{ livelesson.title }}</div> <div class="lesson__subtitle subtitle">{{ livelesson.title }}</div>
<div class="lesson__content">{{ livelesson.short_description }}</div> <div class="lesson__content">{{ livelesson.short_description | linebreaks }}</div>
<a class="lesson__video video" href="#"> <div class="lesson__video video">
{% if livelesson.stream_index %} {% if livelesson.stream_index %}
<iframe class="lesson__video_frame" src="https://player.vimeo.com/video/{{ livelesson.stream_index }}?autoplay=1" frameborder="0" webkitallowfullscreen <iframe class="lesson__video_frame" src="https://player.vimeo.com/video/{{ livelesson.stream_index }}?autoplay=1" frameborder="0" webkitallowfullscreen
mozallowfullscreen allowfullscreen> mozallowfullscreen allowfullscreen>
</iframe> </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> <iframe class="lesson__chat_frame" src="https://vimeo.com/live-chat/{{ livelesson.stream_index }}" frameborder="0"></iframe>
{% else %} {% else %}
{% if livelesson.cover %} {% if livelesson.cover %}
<img class="video__pic" src="{{ livelesson.cover.image.url }}"/> <img class="video__pic" src="{{ livelesson.cover.image.url }}"/>
{% else %} {% endif %}
{% endif %}
{% endif %} {% endif %}
</a> </div>
</div> </div>
</div> </div>
</div> </div>
{% for content in livelesson.content.all %} {% 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 %} {% include template %}
{% endwith %} {% endwith %}

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

@ -11,70 +11,16 @@
<link rel="stylesheet" media="all" href={% static "app.css" %}> <link rel="stylesheet" media="all" href={% static "app.css" %}>
</head> </head>
<body onload="window.print()"> <body onload="setTimeout(window.print, 1000)">
<div class="outer js-outer"> <div class="outer js-outer">
<div class="section" id="schedule"> <div class="section" id="schedule">
<div class="section__center center center_md"> <div class="section__center center center_md">
<div class="title title_center">Расписание</div> <div class="title title_center">Расписание</div>
<div class="timing js-timing"> <div class="timing js-timing">
<div class="timing__week"> <div class="timing__week">
{% for school_schedule in school_schedules %} {% with current_live_lesson=school_schedule.current_live_lesson %} {% for school_schedule in school_schedules %}
<div class="timing__item {% if school_schedule.weekday > 5 %} timing__item_bg{% endif %} js-timing-item open"> {% include 'blocks/schedule_item.html' with school_schedule=school_schedule live_lesson=school_schedule.current_live_lesson print=True %}
<div class="timing__cell"> {% endfor %}
<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 %}
</div> </div>
</div> </div>
</div> </div>

@ -1,6 +1,6 @@
{% extends "templates/lilcity/index.html" %} {% load static %} {% extends "templates/lilcity/index.html" %} {% load static %}
{% block title %}Онлайн-школа LilCity{% endblock title%} {% 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 %} {% block content %}
{% if not is_purchased %} {% if not is_purchased %}
{% include "../summer/promo.html" %} {% 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" data-popup=".js-popup-auth"
{% endif %} {% endif %}
class="timing__btn btn" class="timing__btn btn"
href="{% url 'school-checkout' %}?weekdays={{ school_schedule.weekday }}&add_days=true" href="{% url 'school-checkout' %}?weekdays={{ school_schedule.weekday }}"
>купить</a> >купить</a>

@ -1,9 +1,9 @@
{% load static %} {% load static %}
<a class="online" target="_blank" href="https://www.youtube.com/watch?v=PhZ8qQbIej0" style="background-image: url({% static 'img/video-1.jpg' %});"> <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__center center">
<div class="online__type">ПРЯМОЙ ЭФИР</div> <div class="online__type">ВИДЕОУРОКИ</div>
<div class="online__title">В 17.00 (по Мск) </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"> <div class="online__action">
<svg class="icon icon-play"> <svg class="icon icon-play">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-play"></use> <use xlink:href="{% static 'img/sprite.svg' %}#icon-play"></use>

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

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

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

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

@ -18,6 +18,7 @@ class UserEditForm(forms.ModelForm):
# gender = forms.ChoiceField(choices=User.GENDER_CHOICES, required=False) # gender = forms.ChoiceField(choices=User.GENDER_CHOICES, required=False)
gender = forms.CharField(required=False) gender = forms.CharField(required=False)
# about = forms.CharField() # about = forms.CharField()
trial_lesson = forms.URLField(required=False)
old_password = forms.CharField(required=False) old_password = forms.CharField(required=False)
new_password1 = forms.CharField(required=False) new_password1 = forms.CharField(required=False)
new_password2 = forms.CharField(required=False) new_password2 = forms.CharField(required=False)
@ -41,6 +42,7 @@ class UserEditForm(forms.ModelForm):
'birthday', 'birthday',
'gender', 'gender',
'about', 'about',
'trial_lesson',
'old_password', 'old_password',
'new_password1', 'new_password1',
'new_password2', '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') photo = models.ImageField('Фото', null=True, blank=True, upload_to='users')
show_in_mainpage = models.BooleanField('Показывать на главной странице', default=False) show_in_mainpage = models.BooleanField('Показывать на главной странице', default=False)
trial_lesson = models.URLField(default='', null=True, blank=True)
objects = UserManager() objects = UserManager()
@ -91,7 +92,7 @@ class User(AbstractUser):
@property @property
def balance(self): def balance(self):
aggregate = self.balances.filter( income = self.balances.filter(
type=0, type=0,
payment__isnull=False, payment__isnull=False,
payment__status__isnull=False payment__status__isnull=False
@ -99,9 +100,13 @@ class User(AbstractUser):
models.Sum('amount'), models.Sum('amount'),
models.Sum('commission'), models.Sum('commission'),
) )
amount = aggregate.get('amount__sum') or 0 income_amount = income.get('amount__sum') or 0
commission = aggregate.get('commission__sum') or 0 income_commission = income.get('commission__sum') or 0
return amount - commission
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) @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 section_gray section_menu">
<div class="section__center center center_xs"> <div class="section__center center center_xs">
<div class="menu"> <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 active" href="{% url 'user-edit-notifications' user.id %}">Уведомления</a>
<a class="menu__link" href="{% url 'user-edit-payments' user.id %}">Платежи</a> <a class="menu__link" href="{% url 'user-edit-payments' user.id %}">Платежи</a>
</div> </div>

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

@ -2,7 +2,7 @@
<div class="section section_gray section_menu"> <div class="section section_gray section_menu">
<div class="section__center center center_xs"> <div class="section__center center center_xs">
<div class="menu"> <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-notifications' user.id %}">Уведомления</a>
<a class="menu__link" href="{% url 'user-edit-payments' user.id %}">Платежи</a> <a class="menu__link" href="{% url 'user-edit-payments' user.id %}">Платежи</a>
</div> </div>
@ -150,7 +150,18 @@
{% for error in form.about.errors %} {% for error in form.about.errors %}
<div class="field__error">{{ error }}</div> <div class="field__error">{{ error }}</div>
{% endfor %} {% 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>
<div class="form__group"> <div class="form__group">
<div class="form__title">Пароль</div> <div class="form__title">Пароль</div>

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

@ -1,5 +1,3 @@
import arrow
from io import BytesIO from io import BytesIO
from PIL import Image from PIL import Image
from uuid import uuid4 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.decorators import login_required, permission_required
from django.contrib.auth.hashers import check_password, make_password from django.contrib.auth.hashers import check_password, make_password
from django.http import Http404 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.urls import reverse_lazy
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.timezone import now 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])) 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) send_email('Вы успешно прошли регистрацию', request.user.email, "notification/email/verification_email.html", url=url)
messages.info(request, 'Письмо подтверждения отправлено.') 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 model = User
template_name = 'user/profile.html' 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): def get_context_data(self, object):
context = super().get_context_data() context = super().get_context_data()
if not self.request.user.is_anonymous: context['user'] = self.request.user
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['published'] = Course.objects.filter( context['published'] = Course.objects.filter(
author=self.object, author=self.object,
) )
context['is_author'] = context['published'] or self.request.user.role == User.AUTHOR_ROLE
context['paid'] = Course.objects.filter( context['paid'] = Course.objects.filter(
payments__in=CoursePayment.objects.filter( payments__in=CoursePayment.objects.filter(
user=self.object, user=self.object,
@ -76,36 +73,38 @@ class UserView(DetailView):
school_payment = SchoolPayment.objects.filter( school_payment = SchoolPayment.objects.filter(
user=self.object, user=self.object,
date_start__lte=now(), date_start__lte=now(),
date_end__gt=now(), date_end__gte=now(),
status__in=[ status__in=[
Pingback.PINGBACK_TYPE_REGULAR, Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL, Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED, Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
], ],
) )
school_schedules_purchased = school_payment.annotate( context['is_school_purchased'] = school_payment.exists()
joined_weekdays=Func(F('weekdays'), function='unnest',) if context['is_school_purchased']:
).values_list('joined_weekdays', flat=True).distinct() school_schedules_purchased = school_payment.annotate(
context['school_schedules_purchased'] = school_schedules_purchased joined_weekdays=Func(F('weekdays'), function='unnest',)
context['school_payment'] = school_payment ).values_list('joined_weekdays', flat=True).distinct()
context['is_purchased'] = school_payment.exists() 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 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() return context
context['school_purchased_future'] = school_payment_future.last()
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 return context
@ -189,18 +188,23 @@ class PaymentHistoryView(FormView):
@method_decorator(login_required, name='dispatch') @method_decorator(login_required, name='dispatch')
class UserEditView(UpdateView): class ProfileEditView(UpdateView):
model = User model = User
template_name = 'user/profile-settings.html' template_name = 'user/profile-settings.html'
form_class = UserEditForm form_class = UserEditForm
@method_decorator(login_required) def get_object(self, queryset=None):
return self.request.user
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
if request.user != self.object:
raise Http404()
return super().dispatch(request, *args, **kwargs) 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): def post(self, request, *args, **kwargs):
# it's magic *-*-*-*-* # it's magic *-*-*-*-*
if 'photo' in request.FILES: if 'photo' in request.FILES:
@ -244,7 +248,7 @@ class UserEditView(UpdateView):
return super().post(request, *args, **kwargs) return super().post(request, *args, **kwargs)
def get_success_url(self): def get_success_url(self):
return reverse('user-edit-profile', args=[self.object.id]) return reverse('user-edit-profile')
class AuthorRequestView(FormView): 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.config.models import Config
from apps.content.models import Baner from apps.content.models import Baner
from apps.payment.models import SchoolPayment
def config(request): def config(request):
@ -8,3 +12,20 @@ def config(request):
def baner(request): def baner(request):
return {'baner': Baner.objects.filter(use=True).first()} 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', 'sorl.thumbnail',
'raven.contrib.django.raven_compat', 'raven.contrib.django.raven_compat',
'django_user_agents', 'django_user_agents',
'imagekit',
] + [ ] + [
'apps.auth.apps', 'apps.auth.apps',
'apps.user', 'apps.user',
@ -93,6 +94,7 @@ TEMPLATES = [
'context_processors': [ 'context_processors': [
'project.context_processors.config', 'project.context_processors.config',
'project.context_processors.baner', 'project.context_processors.baner',
'project.context_processors.is_summer_school_purchased',
'django.template.context_processors.debug', 'django.template.context_processors.debug',
'django.template.context_processors.request', 'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth', 'django.contrib.auth.context_processors.auth',
@ -141,6 +143,7 @@ AUTH_PASSWORD_VALIDATORS = [
AUTH_USER_MODEL = 'user.User' AUTH_USER_MODEL = 'user.User'
AUTHENTICATION_BACKENDS = ['apps.auth.backend.CaseInsensitiveModelBackend']
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/ # https://docs.djangoproject.com/en/2.0/topics/i18n/

@ -12,8 +12,8 @@
<use xlink:href="{% static 'img/sprite.svg' %}#icon-online"></use> <use xlink:href="{% static 'img/sprite.svg' %}#icon-online"></use>
</svg> </svg>
</div> </div>
<div class="school__title">Прямой эфир</div> <div class="school__title">Видеоуроки</div>
<div class="school__text">Понедельник, среда, пятница</div> <div class="school__text">Каждый день с 1 сентября</div>
</div> </div>
<div class="school__col"> <div class="school__col">
<div class="school__preview"> <div class="school__preview">
@ -30,7 +30,7 @@
<use xlink:href="{% static 'img/sprite.svg' %}#icon-subjects"></use> <use xlink:href="{% static 'img/sprite.svg' %}#icon-subjects"></use>
</svg> </svg>
</div> </div>
<div class="school__title">12 уроков</div> <div class="school__title">7 дисциплин</div>
<div class="school__text">В разных техниках</div> <div class="school__text">В разных техниках</div>
</div> </div>
<div class="school__col"> <div class="school__col">
@ -43,20 +43,6 @@
<div class="school__text">Хранится 7 дней</div> <div class="school__text">Хранится 7 дней</div>
</div> </div>
</div> </div>
<div class="letsgo"> <div class="letsgo"></div>
{% 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> </div>
</div> </div>

@ -1,5 +1,5 @@
{% load static %} {% 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> <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 %}"> <div id="question__replyto__{{ node.id }}" class="questions__item {% if node.is_child_node %}questions__item_reply{% endif %}">
{% if node.author.photo %} {% if node.author.photo %}

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

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

@ -2,7 +2,7 @@
<div class="section section_courses"> <div class="section section_courses">
<div class="section__center center"> <div class="section__center center">
<div class="title title_center">Видео-курсы без расписания</div> <div class="title title_center">Видео-курсы без расписания</div>
<div class="text text_courses">Если вам не совсем удобно заниматься с нами в прямом эфире каждый день как в нашей онлайн-школе, специально для вас мы <div class="text text_courses">Если вам не совсем удобно заниматься с нами каждый день в нашей онлайн-школе, специально для вас мы
делаем отдельные уроки в записи, которые вы можете проходить когда вам будем удобно.</div> делаем отдельные уроки в записи, которые вы можете проходить когда вам будем удобно.</div>
<div class="head"> <div class="head">
<div class="head__text">Учите и развивайте креативное мышление когда и где угодно</div> <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 %} {% load static %}
<a class="online" target="_blank" href="https://www.youtube.com/watch?v=PhZ8qQbIej0" style="background-image: url({% static 'img/video-1.jpg' %});"> <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__center center">
<div class="online__type">ПРЯМОЙ ЭФИР</div> <div class="online__type">ВИДЕОУРОКИ</div>
<div class="online__title">В 17.00 (по Мск) </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"> <div class="online__action">
<svg class="icon icon-play"> <svg class="icon icon-play">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-play"></use> <use xlink:href="{% static 'img/sprite.svg' %}#icon-play"></use>

@ -1,5 +1,5 @@
{% if messages %} {% 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"> <div class="section__center center center_xs">
{% for message in messages %} {% for message in messages %}
<div class="message message_{{ message.tags }}">{{ message }}</div> <div class="message message_{{ message.tags }}">{{ message }}</div>

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

@ -3,7 +3,7 @@
<div class="popup__wrap js-popup-wrap"> <div class="popup__wrap js-popup-wrap">
<button class="popup__close js-popup-close"> <button class="popup__close js-popup-close">
<svg class="icon icon-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> </svg>
</button> </button>
<div class="popup__body"> <div class="popup__body">
@ -37,10 +37,10 @@
<div class="field__password-show"> <div class="field__password-show">
<button class="password-toggle" type="button"> <button class="password-toggle" type="button">
<svg class="icon icon-password-eye"> <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>
<svg class="icon icon-password-hidden-eye"> <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> </svg>
</button> </button>
</div> </div>
@ -53,7 +53,7 @@
<div class="auth__or">или</div> <div class="auth__or">или</div>
<button type="button" class="auth__btn btn btn_fb"> <button type="button" class="auth__btn btn btn_fb">
<svg class="icon icon-facebook"> <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> </svg>
<span class="btn__title">ЧЕРЕЗ FACEBOOK</span> <span class="btn__title">ЧЕРЕЗ FACEBOOK</span>
</button> </button>
@ -96,10 +96,10 @@
<div class="field__password-show"> <div class="field__password-show">
<button class="password-toggle" type="button"> <button class="password-toggle" type="button">
<svg class="icon icon-password-eye"> <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>
<svg class="icon icon-password-hidden-eye"> <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> </svg>
</button> </button>
</div> </div>
@ -122,7 +122,7 @@
<div class="auth__or">или</div> <div class="auth__or">или</div>
<button type="button" class="auth__btn btn btn_fb"> <button type="button" class="auth__btn btn btn_fb">
<svg class="icon icon-facebook"> <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> </svg>
<span class="btn__title">ЧЕРЕЗ FACEBOOK</span> <span class="btn__title">ЧЕРЕЗ FACEBOOK</span>
</button> </button>

@ -29,7 +29,10 @@
data-day="{{school_schedule.weekday}}" data-day="{{school_schedule.weekday}}"
data-price="{{school_schedule.month_price}}" data-price="{{school_schedule.month_price}}"
autocomplete="off" 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 checked
{% endif %}> {% endif %}>
<span class="switch__content"> <span class="switch__content">
@ -37,7 +40,16 @@
{% comment %} dont delete {% endcomment %} {% comment %} dont delete {% endcomment %}
<span class="switch__cell"></span> <span class="switch__cell"></span>
<span class="switch__cell">{{ school_schedule.title }}</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> </span>
</label> </label>
{% endfor %} {% endfor %}
@ -50,7 +62,10 @@
data-day="{{school_schedule.weekday}}" data-day="{{school_schedule.weekday}}"
data-price="{{school_schedule.month_price}}" data-price="{{school_schedule.month_price}}"
autocomplete="off" 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 checked
{% endif %}> {% endif %}>
<span class="switch__content"> <span class="switch__content">
@ -58,7 +73,16 @@
{% comment %} dont delete {% endcomment %} {% comment %} dont delete {% endcomment %}
<span class="switch__cell"></span> <span class="switch__cell"></span>
<span class="switch__cell">{{ school_schedule.title }}</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> </span>
</label> </label>
{% endfor %} {% endfor %}
@ -78,18 +102,18 @@
</div> </div>
<div class="order__info"> <div class="order__info">
<div class="order__label">ШКОЛА</div> <div class="order__label">ШКОЛА</div>
<div class="order__days">Вторник, Четверг, Воскресенье</div> <div class="order__days"></div>
</div> </div>
<div class="order__foot"> <div class="order__foot">
<div class="order__subtitle">Итого, за месяц:</div> <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>
</div> </div>
</div> </div>
<div class="buy__foot"> <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> </div>
</div> </div>

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

@ -10,20 +10,20 @@
<div class="auth js-auth" style="padding:20px 20px 25px;"> <div class="auth js-auth" style="padding:20px 20px 25px;">
<div class="auth__login js-auth-login"> <div class="auth__login js-auth-login">
<div class="auth__body"> <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 %} {% csrf_token %}
<div class="auth__tab js-auth-tab" style="display: block;"> <div class="auth__tab js-auth-tab" style="display: block;">
<div class="auth__enter js-auth-enter"> <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__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" name="email"
placeholder="name@website.com" tabindex="1"></div> placeholder="name@website.com" tabindex="1"></div>
<div id="learner-auth-field-error__username" <div id="learner-auth-field-error__username"
class="field__error learner-auth-form__field-error"></div> class="field__error learner-auth-form__field-error"></div>
</div> </div>
<div class="auth__foot"> <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> </div>
</div> </div>
@ -33,4 +33,4 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>

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

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

@ -17,10 +17,15 @@
</div> </div>
<div class="teachers__wrap"> <div class="teachers__wrap">
<div class="teachers__title"> <div class="teachers__title">
<a href="{% url 'user' teacher.id %}">{{ teacher.get_full_name }}</a>{% if teacher.instagram_hashtag %}, <div class="teachers__title-name">
<a href='https://www.instagram.com/explore/tags/{{ teacher.instagram_hashtag }}/' target="_blank"> <a href="{% url 'user' teacher.id %}">{{ teacher.get_full_name }}</a>{% if teacher.instagram_hashtag %},
{{ teacher.instagram_hashtag }} <a href='https://www.instagram.com/explore/tags/{{ teacher.instagram_hashtag }}/' target="_blank">
</a> {{ 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 %} {% endif %}
</div> </div>
<div class="teachers__social"> <div class="teachers__social">
@ -30,13 +35,13 @@
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-facebook"></use> <use xlink:href="{% static 'img/sprite.svg' %}#icon-share-facebook"></use>
</svg> </svg>
</a> </a>
{% endif %} {% if teacher.instagram %} {% endif %} {# if teacher.instagram #}
<a class="social__item" href="{{ teacher.instagram }}" target="_blank"> <a class="social__item" href="{{ teacher.instagram }}" target="_blank">
<svg class="icon icon-share-instagram"> <svg class="icon icon-share-instagram">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-instagram"></use> <use xlink:href="{% static 'img/sprite.svg' %}#icon-share-instagram"></use>
</svg> </svg>
</a> </a>
{% endif %} {% if teacher.twitter %} {# endif #} {% if teacher.twitter %}
<a class="social__item" href="{{ teacher.twitter }}" target="_blank"> <a class="social__item" href="{{ teacher.twitter }}" target="_blank">
<svg class="icon icon-share-twitter"> <svg class="icon icon-share-twitter">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-twitter"></use> <use xlink:href="{% static 'img/sprite.svg' %}#icon-share-twitter"></use>

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

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

@ -38,6 +38,24 @@
{% endcompress %} {% endcompress %}
<link rel="shortcut icon" type="image/png" href="{% static 'img/favicon.png' %}"/> <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 src="https://js.pusher.com/4.1/pusher.min.js"></script>
<script> <script>
var viewportmeta = document.querySelector('meta[name="viewport"]'); 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> <noscript><div><img src="https://mc.yandex.ru/watch/49354039" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
<!-- /Yandex.Metrika counter --> <!-- /Yandex.Metrika counter -->
{% include "templates/blocks/mixpanel.html" %} {% include "templates/blocks/mixpanel.html" %}
{% block head %}{% endblock head %}
</head> </head>
<body> <body>
{% include "templates/blocks/social.html" %} {% include "templates/blocks/social.html" %}
@ -131,16 +150,7 @@
{% include "templates/blocks/popup_course_lock.html" %} {% include "templates/blocks/popup_course_lock.html" %}
{% include "templates/blocks/popup_subscribe.html" %} {% include "templates/blocks/popup_subscribe.html" %}
</div> </div>
<script> {% include 'templates/blocks/lil_store_js.html' %}
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>
<script type="text/javascript" src={% static "app.js" %}></script> <script type="text/javascript" src={% static "app.js" %}></script>
<script> <script>
var schoolDiscount = parseFloat({{ config.SERVICE_DISCOUNT }}); var schoolDiscount = parseFloat({{ config.SERVICE_DISCOUNT }});

@ -18,7 +18,7 @@ from django.contrib import admin
from django.views.generic import TemplateView from django.views.generic import TemplateView
from django.urls import path, include from django.urls import path, include
from apps.content.views import ContestEditView, ContestView, ContestWorkView, contest_work_comment
from apps.course.views import ( from apps.course.views import (
CoursesView, likes, coursecomment, CoursesView, likes, coursecomment,
CourseView, LessonView, SearchView, CourseView, LessonView, SearchView,
@ -27,9 +27,9 @@ from apps.course.views import (
) )
from apps.user.views import ( from apps.user.views import (
AuthorRequestView, UserView, AuthorRequestView, UserView,
UserEditView, NotificationEditView, ProfileEditView, NotificationEditView,
PaymentHistoryView, resend_email_verify, PaymentHistoryView, resend_email_verify,
SubscribeView, SubscribeView, ProfileView,
) )
from apps.payment.views import ( from apps.payment.views import (
CourseBuySuccessView, CourseBuyView, CourseBuySuccessView, CourseBuyView,
@ -67,8 +67,9 @@ urlpatterns = [
path('payments/error', TemplateView.as_view(template_name='payment/payment_error.html'), name='payment-error'), path('payments/error', TemplateView.as_view(template_name='payment/payment_error.html'), name='payment-error'),
path('school/checkout', SchoolBuyView.as_view(), name='school-checkout'), path('school/checkout', SchoolBuyView.as_view(), name='school-checkout'),
path('search/', SearchView.as_view(), name='search'), 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>/', 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>/notifications', NotificationEditView.as_view(), name='user-edit-notifications'),
path('user/<int:pk>/payments', PaymentHistoryView.as_view(), name='user-edit-payments'), path('user/<int:pk>/payments', PaymentHistoryView.as_view(), name='user-edit-payments'),
path('user/resend-email-verify', resend_email_verify, name='resend-email-verify'), 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('api/v1/', include(('api.v1.urls', 'api_v1'))),
path('school/', include(('apps.school.urls', 'school'))), path('school/', include(('apps.school.urls', 'school'))),
path('test', TemplateView.as_view(template_name='templates/lilcity/test.html'), name='test'), path('test', TemplateView.as_view(template_name='templates/lilcity/test.html'), name='test'),
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