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

remotes/origin/hotfix/LIL-691
gzbender 7 years ago
commit 63fcae4fa0
  1. 2
      api/v1/serializers/course.py
  2. 3
      apps/course/filters.py
  3. 18
      apps/course/migrations/0044_course_age.py
  4. 18
      apps/course/models.py
  5. 2
      apps/course/templates/course/_items.html
  6. 7
      apps/course/templates/course/course.html
  7. 23
      apps/course/templates/course/course_only_lessons.html
  8. 25
      apps/course/templates/course/courses.html
  9. 6
      apps/course/templates/course/inclusion/category_items.html
  10. 14
      apps/course/templates/course/lesson.html
  11. 13
      apps/course/views.py
  12. 2
      apps/school/templates/school/livelesson_detail.html
  13. 2
      apps/school/templates/school/livelessons_list.html
  14. 118
      project/templates/blocks/gallery.html
  15. 5
      project/templates/blocks/last_courses.html
  16. 5
      project/templates/blocks/lil_store_js.html
  17. 2
      project/templates/blocks/popup_course_lock.html
  18. 2
      project/templates/lilcity/main.html
  19. 2
      project/views.py
  20. 94
      web/src/components/CourseRedactor.vue
  21. 5
      web/src/components/LessonRedactor.vue
  22. 28
      web/src/components/inputs/LilSelect.vue
  23. 4
      web/src/components/redactor/VueRedactor.vue
  24. 1
      web/src/js/modules/api.js
  25. 112
      web/src/js/modules/courses.js
  26. 38
      web/src/sass/_common.sass

@ -137,6 +137,7 @@ class CourseCreateSerializer(DispatchContentMixin,
'from_author', 'from_author',
'cover', 'cover',
'price', 'price',
'age',
'is_infinite', 'is_infinite',
'deferred_start_at', 'deferred_start_at',
'category', 'category',
@ -288,6 +289,7 @@ class CourseSerializer(serializers.ModelSerializer):
'from_author', 'from_author',
'cover', 'cover',
'price', 'price',
'age',
'is_infinite', 'is_infinite',
'deferred_start_at', 'deferred_start_at',
'category', 'category',

@ -5,7 +5,8 @@ from .models import Course
class CourseFilter(django_filters.FilterSet): class CourseFilter(django_filters.FilterSet):
category = django_filters.CharFilter(field_name='category') category = django_filters.CharFilter(field_name='category')
age = django_filters.ChoiceFilter(field_name='age', choices=Course.AGE_CHOICES)
class Meta: class Meta:
model = Course model = Course
fields = ['category'] fields = ['category', 'age']

@ -0,0 +1,18 @@
# Generated by Django 2.0.6 on 2018-09-07 00:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course', '0043_auto_20180824_2132'),
]
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),
),
]

@ -50,6 +50,23 @@ class Course(BaseModel, DeactivatedMixin):
(ARCHIVED, 'Archived'), (ARCHIVED, 'Archived'),
(DENIED, 'Denied') (DENIED, 'Denied')
) )
AGE_LT5 = 1
AGE_57 = 2
AGE_79 = 3
AGE_912 = 4
AGE_1215 = 5
AGE_1518 = 6
AGE_GT18 = 7
AGE_CHOICES = (
(0, 'Любой возраст'),
(AGE_LT5, 'до 5'),
(AGE_57, '5-7'),
(AGE_79, '7-9'),
(AGE_912, '9-12'),
(AGE_1215, '12-15'),
(AGE_1518, '15-18'),
(AGE_GT18, 'от 18'),
)
slug = models.SlugField( slug = models.SlugField(
allow_unicode=True, null=True, blank=True, allow_unicode=True, null=True, blank=True,
max_length=100, unique=True, db_index=True, max_length=100, unique=True, db_index=True,
@ -73,6 +90,7 @@ class Course(BaseModel, DeactivatedMixin):
'Цена курса', help_text='Если цена не выставлена, то курс бесплатный', 'Цена курса', help_text='Если цена не выставлена, то курс бесплатный',
max_digits=10, decimal_places=2, null=True, blank=True max_digits=10, decimal_places=2, null=True, blank=True
) )
age = models.SmallIntegerField(choices=AGE_CHOICES, default=0)
is_infinite = models.BooleanField(default=False) is_infinite = models.BooleanField(default=False)
deferred_start_at = models.DateTimeField( deferred_start_at = models.DateTimeField(
'Отложенный запуск курса', help_text='Заполнить если курс отложенный', 'Отложенный запуск курса', help_text='Заполнить если курс отложенный',

@ -54,7 +54,7 @@
{% endif %} {% endif %}
</div> </div>
<a class="courses__title" href="{% url 'course' course.id %}?next={{ request.get_full_path }}">{{ course.title }}</a> <a class="courses__title" href="{% url 'course' course.id %}?next={{ request.get_full_path }}">{{ course.title }}</a>
<div class="courses__content">{{ course.short_description }} <div class="courses__content">{{ course.short_description | safe | linebreaks | truncatechars_html:300 }}
</div> </div>
<div class="courses__user user"> <div class="courses__user user">
<a href="{% if course.author %}{% url 'user' course.author.id %}{% endif %}"> <a href="{% if course.author %}{% url 'user' course.author.id %}{% endif %}">

@ -4,14 +4,14 @@
{% load rupluralize from plural %} {% load rupluralize from plural %}
{% block title %}{{ course.title }} - {{ block.super }}{% endblock title %} {% block title %}{{ course.title }} - {{ block.super }}{% endblock title %}
{% comment %} seo tags {% endcomment %} {% comment %} seo tags {% endcomment %}
{% block description %}{{ course.short_description }}{% endblock description%} {% block description %}{{ course.short_description | striptags }}{% endblock description%}
{% block twurl %}{{ request.build_absolute_uri }}{% endblock twurl %} {% block twurl %}{{ request.build_absolute_uri }}{% endblock twurl %}
{% block ogtitle %}{{ course.title }} - {{ block.super }}{% endblock ogtitle %} {% block ogtitle %}{{ course.title }} - {{ block.super }}{% endblock ogtitle %}
{% block ogurl %}{{ request.build_absolute_uri }}{% endblock ogurl %} {% block ogurl %}{{ request.build_absolute_uri }}{% endblock ogurl %}
{% if course.cover and course.cover.image %} {% if course.cover and course.cover.image %}
{% block ogimage %}http://{{request.META.HTTP_HOST}}{% if course.cover %}{{ course.cover.image.url }}{% else %}{% static 'img/og_courses.jpg' %}{% endif %}{% endblock ogimage %} {% block ogimage %}http://{{request.META.HTTP_HOST}}{% if course.cover %}{{ course.cover.image.url }}{% else %}{% static 'img/og_courses.jpg' %}{% endif %}{% endblock ogimage %}
{% endif %} {% endif %}
{% block ogdescription %}{{ course.short_description }}{% endblock ogdescription %} {% block ogdescription %}{{ course.short_description | striptags }}{% endblock ogdescription %}
{% block content %} {% block content %}
<div class="section section_border course"> <div class="section section_border course">
@ -25,6 +25,9 @@
</div> </div>
<div class="go__title">Вернуться</div> <div class="go__title">Вернуться</div>
</a> </a>
{% if has_full_access %}
<a class="go__btn btn" href="{% url 'course_edit' course.id %}">Редактировать</a>
{% endif %}
{% if course.author != request.user and not paid and course.price %} {% if course.author != request.user and not paid and course.price %}
<a <a
class="go__btn btn{% if pending %} btn_gray{% endif %} btn_md" class="go__btn btn{% if pending %} btn_gray{% endif %} btn_md"

@ -4,14 +4,14 @@
{% load rupluralize from plural %} {% load rupluralize from plural %}
{% block title %}{{ course.title }} - {{ block.super }}{% endblock title %} {% block title %}{{ course.title }} - {{ block.super }}{% endblock title %}
{% comment %} seo tags {% endcomment %} {% comment %} seo tags {% endcomment %}
{% block description %}{{ course.short_description }}{% endblock description%} {% block description %}{{ course.short_description | striptags }}{% endblock description%}
{% block twurl %}{{ request.build_absolute_uri }}{% endblock twurl %} {% block twurl %}{{ request.build_absolute_uri }}{% endblock twurl %}
{% block ogtitle %}{{ course.title }} - {{ block.super }}{% endblock ogtitle %} {% block ogtitle %}{{ course.title }} - {{ block.super }}{% endblock ogtitle %}
{% block ogurl %}{{ request.build_absolute_uri }}{% endblock ogurl %} {% block ogurl %}{{ request.build_absolute_uri }}{% endblock ogurl %}
{% if course.cover %} {% if course.cover %}
{% block ogimage %}http://{{request.META.HTTP_HOST}}{% if course.cover %}{{ course.cover.image.url }}{% else %}{% static 'img/og_courses.jpg' %}{% endif %}{% endblock ogimage %} {% block ogimage %}http://{{request.META.HTTP_HOST}}{% if course.cover %}{{ course.cover.image.url }}{% else %}{% static 'img/og_courses.jpg' %}{% endif %}{% endblock ogimage %}
{% endif %} {% endif %}
{% block ogdescription %}{{ course.short_description }}{% endblock ogdescription %} {% block ogdescription %}{{ course.short_description | striptags }}{% endblock ogdescription %}
{% block content %} {% block content %}
<div class="section section_border"> <div class="section section_border">
@ -25,6 +25,9 @@
</div> </div>
<div class="go__title">Вернуться</div> <div class="go__title">Вернуться</div>
</a> </a>
{% if has_full_access %}
<a class="go__btn btn" href="{% url 'course_edit' course.id %}">Редактировать</a>
{% endif %}
{% if not paid and course.price and not has_full_access %} {% 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"
@ -174,27 +177,21 @@
<div class="lessons__title title">Содержание курса</div> <div class="lessons__title title">Содержание курса</div>
<div class="lessons__list"> <div class="lessons__list">
{% for lesson in lessons %} {% for lesson in lessons %}
{% if course.author == request.user or request.user.role >= request.user.TEACHER_ROLE or paid %}
<a href="{% url 'lesson' pk=lesson.id %}">
{% else %}
<a href="#">
{% endif %}
<div class="lessons__item"> <div class="lessons__item">
<div class="lessons__row"> <div class="lessons__row">
<div class="lessons__content-wrapper">
<div class="lessons__subtitle subtitle">{{ forloop.counter }}. {{ lesson.title }}</div>
<div class="lessons__content">{{ lesson.short_description | truncatechars_html:800 | safe }}</div>
<a href="{% url 'lesson' pk=lesson.id %}" class="btn btn_stroke">Перейти к уроку</a>
</div>
<div class="lessons__preview"> <div class="lessons__preview">
<div class="lessons__pic-wrapper"> <div class="lessons__pic-wrapper">
<img class="lessons__pic" <img class="lessons__pic"
src="{% if lesson.cover %}{{ lesson.cover.image_thumbnail.url }}{% else %}{% static 'img/no_cover.png' %}{% endif %}"> src="{% if lesson.cover %}{{ lesson.cover.image_thumbnail.url }}{% else %}{% static 'img/no_cover.png' %}{% endif %}">
</div> </div>
</div> </div>
<div>
<div class="lessons__subtitle subtitle">{{ forloop.counter }}. {{ lesson.title }}</div>
<div class="lessons__content">{{ lesson.short_description | truncatechars_html:800 | safe }}</div>
<a href="{% url 'lesson' pk=lesson.id %}" class="btn btn_stroke">Перейти к уроку</a>
</div>
</div> </div>
</div> </div>
</a>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>

@ -23,15 +23,32 @@
<div class="field__select select js-select{% if category %} selected{% endif %}" data-category-select> <div class="field__select select js-select{% if category %} selected{% endif %}" data-category-select>
<div class="select__head js-select-head">Категории</div> <div class="select__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.0 %} active{% endif %}" data-category-option>
data-category-option data-category-url="{% url 'courses' %}"> <div class="select__title">Все категории</div>
<div class="select__title">Все курсы</div>
</div> </div>
{% category_items category %} {% category_items category %}
</div> </div>
<input class="select__input" type="hidden"></div> <input class="select__input" type="hidden"></div>
</div> </div>
</div> </div>
<div class="head__field field">
<div class="field__wrap">
<div class="field__select select js-select{% if age.0 %} selected{% endif %}">
<div class="select__head js-select-head">{% if age_name %}{{ age_name }}{% else %}Возраст{% endif %}</div>
<div class="select__drop js-select-drop">
<div class="select__option js-select-option{% if not age.0 %} active{% endif %}" data-age-option>
<div class="select__title">Любой возраст</div>
</div>
{% for a in ages %}
<div class="select__option js-select-option{% if age and age.0 == a.0 %} active{% endif %}" data-age-option
data-age="{{ a.0 }}" data-age-name="{{ a.1 }}">
<div class="select__title">{{ a.1 }}</div>
</div>
{% endfor %}
</div>
<input class="select__input" type="hidden"></div>
</div>
</div>
</div> </div>
</div> </div>
<div class="courses"> <div class="courses">
@ -40,7 +57,7 @@
</div> </div>
<div class="courses__load load"> <div class="courses__load load">
{% if page_obj.has_next %} {% if page_obj.has_next %}
<button class="load__btn btn" data-next-page-url="{% url 'courses' %}?page={{ page_obj.next_page_number }}">Подгрузить еще</button> <button class="load__btn btn" data-next-page="{{ page_obj.next_page_number }}">Подгрузить еще</button>
{% endif %} {% endif %}
</div> </div>
</div> </div>

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

@ -31,15 +31,9 @@
</div> </div>
<div class="lesson"> <div class="lesson">
<div class="lesson__row"> <div class="lesson__row">
<div class="lesson__preview">
<div class="lesson__pic-wrapper">
<img class="lesson__pic"
src="{% if lesson.cover %}{{ lesson.cover.image_thumbnail.url }}{% else %}{% static 'img/no_cover.png' %}{% endif %}">
</div>
</div>
<div> <div>
<div class="lesson__subtitle subtitle">{{ lesson.title }}</div> <div class="lesson__subtitle subtitle">{{ lesson.title }}</div>
<div class="lesson__content">{{ lesson.short_description | linebreaks }}</div> <div class="lesson__content">{{ lesson.short_description | safe | linebreaks }}</div>
<a href="{% url 'user' lesson.author.id %}"> <a href="{% url 'user' lesson.author.id %}">
<div class="lesson__user user"> <div class="lesson__user user">
{% if lesson.author.photo %} {% if lesson.author.photo %}
@ -60,6 +54,12 @@
</div> </div>
</a> </a>
</div> </div>
<div class="lesson__preview">
<div class="lesson__pic-wrapper">
<img class="lesson__pic"
src="{% if lesson.cover %}{{ lesson.cover.image_thumbnail.url }}{% else %}{% static 'img/no_cover.png' %}{% endif %}">
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

@ -167,9 +167,8 @@ class CourseEditView(TemplateView):
self.object = Course.objects.create( self.object = Course.objects.create(
author=request.user, author=request.user,
) )
#TODO if request.user != self.object.author and request.user.role != User.ADMIN_ROLE:
#if (request.user != self.object.author and request.user.role < User.AUTHOR_ROLE) or request.user.role != User.ADMIN_ROLE: raise Http404
# raise Http404
return super().get(request) return super().get(request)
def get_context_data(self): def get_context_data(self):
@ -283,6 +282,14 @@ class CoursesView(ListView):
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')) context['course_items'] = Course.shuffle(context.get('course_items'))
context['ages'] = Course.AGE_CHOICES[1:]
age = context.get('age')
if age and age[0]:
age = int(age[0])
context['age'] = [age]
context['age_name'] = dict(Course.AGE_CHOICES).get(age, '')
else:
context['age_name'] = ''
return context return context
def get_template_names(self): def get_template_names(self):

@ -14,7 +14,7 @@
<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 | linebreaks }}</div> <div class="lesson__content">{{ livelesson.short_description | safe | linebreaks }}</div>
<div class="lesson__video video"> <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

@ -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 | linebreaks }}</div> <div class="lessons__content">{{ livelesson.short_description | safe | linebreaks | truncatechars_html:800 }}</div>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}

@ -11,80 +11,56 @@
</div> </div>
<div class="gallery"> <div class="gallery">
<div class="gallery__grid"> <div class="gallery__grid">
<div <a href="{% get_media_prefix %}instagram/results/0.jpg"
class="gallery__item gallery__item_lg" class="gallery__item gallery__item_lg">
style="background-image: url({% get_media_prefix %}instagram/results/0.jpg);"
>
<img class="gallery__pic" src="{% get_media_prefix %}instagram/results/0.jpg" onerror="this.style.display='none'"> <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/0.jpg" onerror="this.style.display='none'">
</div> </a>
<div <a href="{% get_media_prefix %}instagram/results/1.jpg"
class="gallery__item" class="gallery__item">
style="background-image: url({% get_media_prefix %}instagram/results/1.jpg);" <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/1.jpg" onerror="this.style.display='none'">
> </a>
{% comment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/1.jpg" onerror="this.style.display='none'"> {% endcomment %} <a href="{% get_media_prefix %}instagram/results/2.jpg"
</div> class="gallery__item">
<div <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/2.jpg" onerror="this.style.display='none'">
class="gallery__item" </a>
style="background-image: url({% get_media_prefix %}instagram/results/2.jpg);" <a href="{% get_media_prefix %}instagram/results/3.jpg"
> class="gallery__item">
{% comment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/2.jpg" onerror="this.style.display='none'"> {% endcomment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/3.jpg" onerror="this.style.display='none'">
</div> </a>
<div <a href="{% get_media_prefix %}instagram/results/4.jpg"
class="gallery__item" class="gallery__item">
style="background-image: url({% get_media_prefix %}instagram/results/3.jpg);" <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/4.jpg" onerror="this.style.display='none'">
> </a>
{% comment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/3.jpg" onerror="this.style.display='none'"> {% endcomment %} <a href="{% get_media_prefix %}instagram/results/5.jpg"
</div> class="gallery__item">
<div <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/5.jpg" onerror="this.style.display='none'">
class="gallery__item" </a>
style="background-image: url({% get_media_prefix %}instagram/results/4.jpg);"
>
{% comment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/4.jpg" onerror="this.style.display='none'"> {% endcomment %}
</div>
<div
class="gallery__item"
style="background-image: url({% get_media_prefix %}instagram/results/5.jpg);"
>
{% comment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/5.jpg" onerror="this.style.display='none'"> {% endcomment %}
</div>
</div> </div>
<div class="gallery__grid"> <div class="gallery__grid">
<div <a href="{% get_media_prefix %}instagram/results/6.jpg"
class="gallery__item" class="gallery__item">
style="background-image: url({% get_media_prefix %}instagram/results/6.jpg);" <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/6.jpg" onerror="this.style.display='none'">
> </a>
{% comment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/6.jpg" onerror="this.style.display='none'"> {% endcomment %} <a href="{% get_media_prefix %}instagram/results/7.jpg"
</div> class="gallery__item">
<div <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/7.jpg" onerror="this.style.display='none'">
class="gallery__item" </a>
style="background-image: url({% get_media_prefix %}instagram/results/7.jpg);" <a href="{% get_media_prefix %}instagram/results/8.jpg"
> class="gallery__item">
{% comment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/7.jpg" onerror="this.style.display='none'"> {% endcomment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/8.jpg" onerror="this.style.display='none'">
</div> </a>
<div <a href="{% get_media_prefix %}instagram/results/9.jpg"
class="gallery__item" class="gallery__item gallery__item_lg">
style="background-image: url({% get_media_prefix %}instagram/results/8.jpg);" <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/9.jpg" onerror="this.style.display='none'">
> </a>
{% comment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/8.jpg" onerror="this.style.display='none'"> {% endcomment %} <a href="{% get_media_prefix %}instagram/results/10.jpg"
</div> class="gallery__item">
<div class="gallery__item gallery__item_lg" <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/10.jpg" onerror="this.style.display='none'">
style="background-image: url({% get_media_prefix %}instagram/results/9.jpg);" </a>
<a href="{% get_media_prefix %}instagram/results/11.jpg"
> class="gallery__item">
{% comment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/9.jpg" onerror="this.style.display='none'"> {% endcomment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/11.jpg" onerror="this.style.display='none'">
</div> </a>
<div
class="gallery__item"
style="background-image: url({% get_media_prefix %}instagram/results/10.jpg);"
>
{% comment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/10.jpg" onerror="this.style.display='none'"> {% endcomment %}
</div>
<div
class="gallery__item"
style="background-image: url({% get_media_prefix %}instagram/results/11.jpg);"
>
{% comment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/11.jpg" onerror="this.style.display='none'"> {% endcomment %}
</div>
</div> </div>
</div> </div>
</div> </div>

@ -3,9 +3,8 @@
<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> делаем отдельные уроки в записи, которые вы можете проходить когда вам будем удобно.<br><br>
<div class="head"> Учите и развивайте креативное мышление когда и где угодно
<div class="head__text">Учите и развивайте креативное мышление когда и где угодно</div>
</div> </div>
<div class="courses"> <div class="courses">
<div class="courses__list"> <div class="courses__list">

@ -18,6 +18,9 @@
role: +'{{ request.user.role }}', role: +'{{ request.user.role }}',
photo: '{% if request.user.photo %}{{ request.user.photo.url }}{% else %}{% static 'img/user_default.jpg' %}{% endif %}', photo: '{% if request.user.photo %}{{ request.user.photo.url }}{% else %}{% static 'img/user_default.jpg' %}{% endif %}',
}, },
components: {} components: {},
urls: {
courses: "{% url 'courses' %}"
},
}; };
</script> </script>

@ -6,7 +6,7 @@
<div class="lock"> <div class="lock">
<div class="lock__label">ЧТОБЫ ПРОДОЛЖИТЬ ПРОСМОТР</div> <div class="lock__label">ЧТОБЫ ПРОДОЛЖИТЬ ПРОСМОТР</div>
<div class="lock__title title">{{ course.title }}</div> <div class="lock__title title">{{ course.title }}</div>
<div class="lock__content">{{ course.short_description }}</div> <div class="lock__content">{{ course.short_description | safe | linebreaks }}</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>

@ -9,7 +9,7 @@
{% include "templates/blocks/about.html" %} {% include "templates/blocks/about.html" %}
{% include "templates/blocks/last_courses.html" %} {% include "templates/blocks/last_courses.html" %}
{% include "templates/blocks/gallery.html" %} {% include "templates/blocks/gallery.html" %}
{% include "templates/blocks/game.html" %}
{% include "templates/blocks/teachers.html" %} {% include "templates/blocks/teachers.html" %}
{% include "templates/blocks/game.html" %}
{% include "templates/blocks/partners.html" %} {% include "templates/blocks/partners.html" %}
{% endblock content %} {% endblock content %}

@ -68,7 +68,7 @@ class IndexView(TemplateView):
'online': online, 'online': online,
'online_coming_soon': online_coming_soon, 'online_coming_soon': online_coming_soon,
'school_schedule': school_schedule, 'school_schedule': school_schedule,
'course_items': Course.shuffle(Course.objects.filter(status=Course.PUBLISHED)[:6]), 'course_items': Course.shuffle(Course.objects.filter(status=Course.PUBLISHED)[:3]),
'is_purchased': school_payment_exists, 'is_purchased': school_payment_exists,
'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': SchoolSchedule.objects.all(),

@ -37,10 +37,8 @@
v-bind:class="{ error: ($v.course.short_description.$dirty || showErrors) && $v.course.short_description.$invalid }"> v-bind:class="{ error: ($v.course.short_description.$dirty || showErrors) && $v.course.short_description.$invalid }">
<div class="field__label">{{titles.shortDescription}}</div> <div class="field__label">{{titles.shortDescription}}</div>
<div class="field__wrap"> <div class="field__wrap">
<textarea class="field__textarea" <vue-redactor :value="course.short_description"
v-autosize="course.short_description" v-on:update:value="(value) => { this.course.short_description = value; }" />
@input="$v.course.short_description.$touch()"
v-model="course.short_description"></textarea>
</div> </div>
</div> </div>
</div> </div>
@ -49,7 +47,7 @@
v-bind:class="{ error: ($v.course.category.$dirty || showErrors) && $v.course.category.$invalid }"> v-bind:class="{ error: ($v.course.category.$dirty || showErrors) && $v.course.category.$invalid }">
<div class="field__label field__label_gray">КАТЕГОРИЯ</div> <div class="field__label field__label_gray">КАТЕГОРИЯ</div>
<div class="field__wrap"> <div class="field__wrap">
<lil-select :value.sync="categorySelect" :options="categoryOptions" <lil-select :value.sync="course.category" :options="categoryOptions"
placeholder="Выберите категорию"/> placeholder="Выберите категорию"/>
</div> </div>
</div> </div>
@ -113,6 +111,13 @@
<button disabled class="field__append">руб.</button> <button disabled class="field__append">руб.</button>
</div> </div>
</div> </div>
<div v-if="!live" class="info__field field">
<div class="field__label field__label_gray">ВОЗРАСТ</div>
<div class="field__wrap">
<lil-select :value.sync="course.age" :options="ages" value-key="value"
placeholder="Выберите возраст"/>
</div>
</div>
<label v-if="me && !live && me.role === ROLE_ADMIN" class="info__switch switch switch_lg"> <label v-if="me && !live && me.role === ROLE_ADMIN" class="info__switch switch switch_lg">
<input type="checkbox" class="switch__input" v-model="course.is_featured"> <input type="checkbox" class="switch__input" v-model="course.is_featured">
<span class="switch__content">Выделить</span> <span class="switch__content">Выделить</span>
@ -198,7 +203,7 @@
</div> </div>
<div class="lessons__subtitle subtitle">{{ lesson.title }}</div> <div class="lessons__subtitle subtitle">{{ lesson.title }}</div>
<div class="lessons__row"> <div class="lessons__row">
<div class="lessons__content">{{ lesson.short_description }}</div> <div class="lessons__content" v-html="lesson.short_description"></div>
</div> </div>
</div> </div>
</vue-draggable> </vue-draggable>
@ -232,6 +237,7 @@
import LinkInput from './inputs/LinkInput' import LinkInput from './inputs/LinkInput'
import DatePicker from 'vuejs-datepicker' import DatePicker from 'vuejs-datepicker'
import BlockContent from './blocks/BlockContent' import BlockContent from './blocks/BlockContent'
import VueRedactor from './redactor/VueRedactor';
import LilSelect from "./inputs/LilSelect"; import LilSelect from "./inputs/LilSelect";
import LessonRedactor from "./LessonRedactor"; import LessonRedactor from "./LessonRedactor";
import {api} from "../js/modules/api"; import {api} from "../js/modules/api";
@ -269,6 +275,7 @@
duration: null, duration: null,
author: null, author: null,
price: null, price: null,
age: 0,
url: '', url: '',
coverImage: '', coverImage: '',
kit__body: null, kit__body: null,
@ -333,6 +340,40 @@
'value': '18:00', 'value': '18:00',
} }
], ],
ages: [
{
'title': 'Любой возраст',
'value': 0,
},
{
'title': 'до 5',
'value': 1,
},
{
'title': '5-7',
'value': 2,
},
{
'title': '7-9',
'value': 3,
},
{
'title': '9-12',
'value': 4,
},
{
'title': '12-15',
'value': 5,
},
{
'title': '15-18',
'value': 6,
},
{
'title': 'от 18',
'value': 7,
},
],
weekdays: [ weekdays: [
'', '',
@ -441,15 +482,6 @@
this.course.url = slugify(this.course.title); this.course.url = slugify(this.course.title);
} }
}, },
updateCategory() {
if (this.categoryOptions && Array.isArray(this.categoryOptions) && this.course.category) {
this.categoryOptions.forEach((category) => {
if (category.id === this.course.category) {
this.course.categorySelect = category;
}
});
}
},
removeLesson(lessonIndex) { removeLesson(lessonIndex) {
if (!confirm('Вы действительно хотите удалить этот урок?')) { if (!confirm('Вы действительно хотите удалить этот урок?')) {
return; return;
@ -885,14 +917,8 @@
promises.push(cats); promises.push(cats);
cats.then((response) => { cats.then((response) => {
if (response.data) { if (response.data) {
this.categoryOptions = response.data.results.map((category) => { this.categoryOptions = response.data.results;
return {
title: category.title,
value: category.id
}
});
} }
this.updateCategory();
}); });
if(this.live) { if(this.live) {
@ -932,7 +958,6 @@
this.scheduleOptions = _.orderBy(options, (item)=>{return moment(item.value)}); this.scheduleOptions = _.orderBy(options, (item)=>{return moment(item.value)});
} }
this.updateCategory();
}); });
} }
@ -1003,23 +1028,6 @@
this.course.price = value || 0; this.course.price = value || 0;
} }
}, },
categorySelect: {
get() {
if (!this.categoryOptions || this.categoryOptions.length === 0 || !this.course || !this.course.category) {
return null;
}
let value;
this.categoryOptions.forEach((category) => {
if (category.value === this.course.category) {
value = category;
}
});
return value;
},
set(value) {
this.course.category = value.value;
}
},
// userSelect: { // userSelect: {
// get() { // get() {
// if (!this.users || this.users.length === 0 || !this.course || !this.course.author) { // if (!this.users || this.users.length === 0 || !this.course || !this.course.author) {
@ -1078,6 +1086,7 @@
'vue-datepicker': DatePicker, 'vue-datepicker': DatePicker,
'lesson-redactor': LessonRedactor, 'lesson-redactor': LessonRedactor,
'vue-draggable': Draggable, 'vue-draggable': Draggable,
'vue-redactor': VueRedactor,
} }
} }
</script> </script>
@ -1178,9 +1187,10 @@
overflow: scroll; overflow: scroll;
} }
.field_short_description { .field_short_description .redactor-box {
overflow-x: visible;
overflow-y: auto;
max-height: 200px; max-height: 200px;
overflow: scroll;
} }
</style> </style>

@ -27,7 +27,8 @@
<div class="kit__field field" <div class="kit__field field"
v-bind:class="{ error: $v.currentLesson.short_description.$invalid }"> v-bind:class="{ error: $v.currentLesson.short_description.$invalid }">
<div class="field__wrap"> <div class="field__wrap">
<textarea class="field__input" v-autosize="lesson.short_description" placeholder="Описание урока" v-model="lesson.short_description"></textarea> <vue-redactor :value="lesson.short_description"
v-on:update:value="(value) => { this.lesson.short_description = value; }" placeholder="Описание урока"/>
</div> </div>
</div> </div>
</div> </div>
@ -46,6 +47,7 @@
<script> <script>
import BlockContent from './blocks/BlockContent' import BlockContent from './blocks/BlockContent'
import LilImage from "./blocks/Image" import LilImage from "./blocks/Image"
import VueRedactor from './redactor/VueRedactor';
import {api} from "../js/modules/api"; import {api} from "../js/modules/api";
import Draggable from 'vuedraggable'; import Draggable from 'vuedraggable';
import _ from 'lodash' import _ from 'lodash'
@ -72,6 +74,7 @@
components: { components: {
BlockContent, BlockContent,
'lil-image': LilImage, 'lil-image': LilImage,
'vue-redactor': VueRedactor,
} }
} }
</script> </script>

@ -4,8 +4,9 @@
{{ selectedTitle }} {{ selectedTitle }}
</div> </div>
<div class="select__drop"> <div class="select__drop">
<div v-for="option in options" :class="{ select__option: true, active: value && option.value == value.value }" @click.stop.prevent="selectOption(option)"> <div v-for="option in options" :class="{ select__option: true, active: option[vk] == selectedValue }"
<div class="select__title">{{ option.title }}</div> @click.stop.prevent="selectOption(option)">
<div class="select__title">{{ option[tk] }}</div>
</div> </div>
</div> </div>
</div> </div>
@ -14,11 +15,14 @@
<script> <script>
export default { export default {
name: "lil-select", name: "lil-select",
props: ["options", "value", "valueKey", "placeholder"], props: ["options", "value", "titleKey", "valueKey", "isObj", "placeholder"],
data() { data() {
return { return {
key: this.valueKey ? this.valueKey : 'title', tk: this.titleKey || 'title',
vk: this.valueKey || 'id',
isOpened: false, isOpened: false,
selected: null,
optionsDict: {},
} }
}, },
methods: { methods: {
@ -30,22 +34,30 @@
}, },
selectOption(option) { selectOption(option) {
this.isOpened = !this.isOpened; this.isOpened = !this.isOpened;
this.$emit('update:value', option); this.$emit('update:value', this.isObj ? option : option[this.vk]);
this.selected = option;
} }
}, },
mounted() { mounted() {
document.addEventListener("click", this.clickListener); document.addEventListener("click", this.clickListener);
this.options.forEach((option) => {
this.optionsDict[option[this.vk]] = option;
});
this.selected = this.optionsDict[this.selectedValue];
}, },
destroyed() { destroyed() {
document.removeEventListener("click", this.clickListener); document.removeEventListener("click", this.clickListener);
}, },
computed: { computed: {
isSelected() { isSelected() {
return this.value && this.value[this.key] ? true : false; return !!this.selected;
},
selectedValue() {
return this.isObj ? this.value && this.value[this.vk] : this.value;
}, },
selectedTitle() { selectedTitle() {
if (this.isSelected) { if (this.isSelected) {
return this.value[this.key]; return this.selected[this.tk];
} }
return this.placeholder ? this.placeholder : ''; return this.placeholder ? this.placeholder : '';
} }
@ -55,4 +67,4 @@
<style scoped> <style scoped>
</style> </style>

@ -7,7 +7,7 @@
import './redactor-full.js'; import './redactor-full.js';
export default { export default {
props: ['value', 'placeholder'], props: ['value', 'placeholder', 'air', 'buttonsHide'],
name: "vue-redactor", name: "vue-redactor",
data() { data() {
return { return {
@ -18,7 +18,7 @@
const me = this; const me = this;
$(me.$refs.input).redactor({ $(me.$refs.input).redactor({
air: true, air: true,
buttonsHide: ['image', 'format'], buttonsHide: this.buttonsHide || ['image', 'format'],
lang: 'ru', lang: 'ru',
placeholder: this.placeholder, placeholder: this.placeholder,
callbacks: { callbacks: {

@ -108,6 +108,7 @@ export const api = {
short_description: courseObject.short_description, short_description: courseObject.short_description,
category: courseObject.category, category: courseObject.category,
price: courseObject.is_paid && courseObject.price || 0, price: courseObject.is_paid && courseObject.price || 0,
age: courseObject.age,
deferred_start_at: deferredStart, deferred_start_at: deferredStart,
duration: courseObject.duration || 0, duration: courseObject.duration || 0,
is_featured: courseObject.is_featured, is_featured: courseObject.is_featured,

@ -12,9 +12,12 @@ moment.locale('ru');
const history = createHistory(); const history = createHistory();
$(document).ready(function () { $(document).ready(function () {
const currentCategory = $('div.js-select-option.active[data-category-option]').attr('data-category-name'); const categoryName = $('div.js-select-option.active[data-category-option]').attr('data-category-name');
if(currentCategory) { let category = $('div.js-select-option.active[data-category-option]').attr('data-category');
$('.js-select[data-category-select] .js-select-head').text(currentCategory); let age = $('div.js-select-option.active[data-age-option]').data('age');
let page = 1;
if(category) {
$('.js-select[data-category-select] .js-select-head').text(categoryName);
} }
// Обработчик отложенных курсов // Обработчик отложенных курсов
setInterval(() => { setInterval(() => {
@ -29,17 +32,30 @@ $(document).ready(function () {
// Обработчик кнопки "Подгрузить еще" // Обработчик кнопки "Подгрузить еще"
$('.courses').on('click', 'button.load__btn', function () { $('.courses').on('click', 'button.load__btn', function () {
load_courses($(this).attr('data-next-page-url'), false); page = $(this).attr('data-next-page');
loadCourses();
}); });
// Обработчик выбора категории // Обработчик выбора категории
$('div.js-select-option[data-category-option]').on('click', function (e) { $('div.js-select-option[data-category-option]').on('click', function (e) {
e.preventDefault(); e.preventDefault();
const currentCategory = $(this).attr('data-category-name'); const categoryName = $(this).attr('data-category-name');
$('[data-category-name]').removeClass('active'); $('[data-category-name]').removeClass('active');
$(`[data-category-name='${currentCategory}']`).addClass('active'); $(`[data-category-name='${categoryName}']`).addClass('active');
history.replace($(this).attr('data-category-url')); category = $(this).attr('data-category');
load_courses($(this).attr('data-category-url'), true); page = 1;
loadCourses(true);
});
// Обработчик выбора возраста
$('div.js-select-option[data-age-option]').on('click', function (e) {
e.preventDefault();
const currentAge = $(this).attr('data-age-name');
$('[data-age-name]').removeClass('active');
$(`[data-age-name='${currentAge}']`).addClass('active');
age = $(this).attr('data-age');
page = 1;
loadCourses(true);
}); });
// Обработчик лайков // Обработчик лайков
@ -95,42 +111,50 @@ $(document).ready(function () {
} }
}); });
}) })
});
function load_courses(coursesUrl, fromStart) { function loadCourses(replaceHistory) {
$('.courses__list').css('opacity', '0.9'); $('.courses__list').css('opacity', '0.9');
const buttonElement = $('.courses').find('button.load__btn'); const buttonElement = $('.courses').find('button.load__btn');
if (!fromStart) { let coursesUrl = window.LIL_STORE.urls.courses + '?' + $.param({
buttonElement.addClass('loading'); category: category,
} age: age,
$.ajax(coursesUrl, { });
method: 'GET' if (page > 1) {
}) buttonElement.addClass('loading');
.done(function (data) { }
if (data.success === true) { else{
if (!fromStart) { history.replace(coursesUrl);
$('.courses__list').append(data.content); }
} else { coursesUrl += `&page=${page}`;
$('.courses__list').html(data.content); $.ajax(coursesUrl, {
method: 'GET'
})
.done(function (data) {
if (data.success === true) {
if (page > 1) {
$('.courses__list').append(data.content);
} else {
$('.courses__list').html(data.content);
}
if (data.next_url) {
buttonElement.attr('data-next-page-url', data.next_url);
buttonElement.show();
} else {
buttonElement.hide()
}
} }
if (data.next_url) { })
buttonElement.attr('data-next-page-url', data.next_url); .fail(function (xhr) {
buttonElement.show(); if (xhr.status === 404) {
} else { // Нет результатов, скрываем кнопку
buttonElement.hide() buttonElement.hide();
} }
} })
}) .always(function () {
.fail(function (xhr) { $('.courses__list').css('opacity', '1');
if (xhr.status === 404) { if (buttonElement) {
// Нет результатов, скрываем кнопку buttonElement.removeClass('loading');
buttonElement.hide(); }
} });
}) }
.always(function () { });
$('.courses__list').css('opacity', '1');
if (buttonElement) {
buttonElement.removeClass('loading');
}
});
}

@ -891,11 +891,11 @@ a[name]
font-size: 20px font-size: 20px
.section .section
padding: 60px 0 padding: 50px 0
+t +t
padding: 40px 0
+m
padding: 30px 0 padding: 30px 0
+m
padding: 20px 0
&_review &_review
background: url(../img/bg-elephants.jpg) 0 0 / 100px 102px background: url(../img/bg-elephants.jpg) 0 0 / 100px 102px
&_gray &_gray
@ -1352,6 +1352,8 @@ a[name]
&__pic &__pic
display: block display: block
width: 100% width: 100%
height: 100%
object-fit: cover
.app .app
position: relative position: relative
@ -2456,11 +2458,11 @@ a.grey-link
.go .go
display: flex display: flex
margin-bottom: 50px margin-bottom: 40px
justify-content: space-between justify-content: space-between
align-items: center align-items: center
+t +t
margin-bottom: 30px margin-bottom: 20px
&__item &__item
display: flex display: flex
max-width: calc(50% - 20px) max-width: calc(50% - 20px)
@ -2517,11 +2519,11 @@ a.grey-link
fill: white fill: white
.course .course
margin-bottom: 60px margin-bottom: 50px
&__head &__head
display: flex display: flex
min-height: 40px min-height: 40px
margin-bottom: 30px margin-bottom: 20px
align-items: center align-items: center
justify-content: space-between justify-content: space-between
+t +t
@ -2531,9 +2533,9 @@ a.grey-link
+t +t
margin-bottom: 10px margin-bottom: 10px
&__content &__content
margin-bottom: 40px margin-bottom: 30px
+t +t
margin-bottom: 25px margin-bottom: 15px
&__about &__about
position: relative position: relative
margin-bottom: 40px margin-bottom: 40px
@ -2563,9 +2565,9 @@ a.grey-link
&:active &:active
color: rgba(white,.4) color: rgba(white,.4)
&__user &__user
margin-bottom: 40px margin-bottom: 30px
+t +t
margin-bottom: 30px margin-bottom: 20px
&__info &__info
display: flex display: flex
align-items: center align-items: center
@ -2580,17 +2582,17 @@ a.grey-link
margin: 20px 0 0 margin: 20px 0 0
&__metas &__metas
display: flex display: flex
margin-bottom: 45px margin-bottom: 35px
justify-content: space-between justify-content: space-between
+m +m
display: block display: block
margin-bottom: 25px margin-bottom: 15px
&__metas &__meta &__metas &__meta
+m +m
margin-bottom: 20px margin-bottom: 20px
&__actions &__actions
display: flex display: flex
margin: 0 -10px 50px margin: 0 -10px 40px
&__action &__action
position: relative position: relative
margin: 0 10px margin: 0 10px
@ -2658,6 +2660,8 @@ a.grey-link
visibility: visible visibility: visible
&__subtitle &__subtitle
margin-bottom: 20px margin-bottom: 20px
text-transform: none
letter-spacing: normal
&__row &__row
display: flex display: flex
+m +m
@ -2676,6 +2680,8 @@ a.grey-link
object-fit: cover object-fit: cover
width: 100% width: 100%
height: 100% height: 100%
&__content-wrapper
flex: 1
&__content &__content
flex: 0 0 calc(100% - 165px) flex: 0 0 calc(100% - 165px)
&__actions &__actions
@ -2715,6 +2721,8 @@ a.grey-link
.lesson .lesson
&__subtitle &__subtitle
letter-spacing: normal
text-transform: none
margin-bottom: 10px margin-bottom: 10px
color: #191919 color: #191919
&__content &__content
@ -4187,6 +4195,8 @@ a
font-size: 16px font-size: 16px
padding-bottom: 24px padding-bottom: 24px
text-align: left text-align: left
margin: 0
max-width: 100%
&.pic &.pic
position: relative position: relative
width: 100% width: 100%

Loading…
Cancel
Save