Merge feature/api

remotes/origin/hasaccess
Vitaly Baev 8 years ago
commit f743417e01
  1. 134
      api/v1/serializers.py
  2. 3
      api/v1/urls.py
  3. 102
      api/v1/views.py
  4. 12
      apps/content/admin.py
  5. 31
      apps/content/migrations/0005_auto_20180208_0520.py
  6. 29
      apps/content/migrations/0006_auto_20180208_0551.py
  7. 19
      apps/content/migrations/0007_auto_20180208_0626.py
  8. 19
      apps/content/migrations/0008_auto_20180208_0631.py
  9. 23
      apps/content/migrations/0009_auto_20180208_0637.py
  10. 17
      apps/content/migrations/0010_remove_gallery_course.py
  11. 43
      apps/content/models.py
  12. 17
      apps/course/fixtures/course.json
  13. 19
      apps/course/migrations/0022_auto_20180208_0647.py
  14. 19
      apps/course/migrations/0023_auto_20180208_0714.py
  15. 19
      apps/course/migrations/0024_auto_20180208_0824.py
  16. 20
      apps/course/migrations/0025_course_gallery.py
  17. 34
      apps/course/models.py
  18. 4
      apps/course/templates/course/_items.html
  19. 6
      apps/course/views.py
  20. 49
      apps/user/forms.py
  21. 18
      apps/user/migrations/0005_user_birthday.py
  22. 18
      apps/user/migrations/0006_auto_20180206_1352.py
  23. 23
      apps/user/migrations/0007_auto_20180207_0808.py
  24. 16
      apps/user/models.py
  25. 209
      apps/user/templates/user/profile-settings.html
  26. 2
      apps/user/templates/user/profile.html
  27. 76
      apps/user/views.py
  28. 2
      docker-compose.yml
  29. 2
      project/settings.py
  30. 12
      project/templates/lilcity/index.html
  31. 8
      project/urls.py
  32. 0
      web/build/img/favicon.ico
  33. BIN
      web/build/img/no_cover.png
  34. 28
      web/build/js/app.js
  35. 2
      web/gulp/tasks/copy.js
  36. BIN
      web/src/img/favicon.ico
  37. BIN
      web/src/img/no_cover.png
  38. 1
      web/src/js/app.js
  39. 14
      web/src/js/modules/profile.js
  40. 2
      web/src/sass/_common.sass
  41. 4
      web/yarn.lock

@ -6,12 +6,71 @@ from . import Base64ImageField
from apps.course.models import Category, Course, Material, Lesson, Like
from apps.content.models import (
Image, Text, ImageText, Video,
Gallery, GalleryImage,
Gallery, GalleryImage, ImageObject,
)
User = get_user_model()
class ImageObjectSerializer(serializers.ModelSerializer):
image = Base64ImageField(
required=True, allow_empty_file=False, allow_null=False, read_only=False,
)
class Meta:
model = ImageObject
fields = (
'id',
'image',
'created_at',
'update_at',
)
read_only_fields = (
'id',
'created_at',
'update_at',
)
class GallerySerializer(serializers.ModelSerializer):
class Meta:
model = Gallery
fields = (
'id',
'title',
'created_at',
'update_at',
)
read_only_fields = (
'id',
'created_at',
'update_at',
)
class GalleryImageSerializer(serializers.ModelSerializer):
img = ImageObjectSerializer()
class Meta:
model = GalleryImage
fields = (
'id',
'gallery',
'img',
'created_at',
'update_at',
)
read_only_fields = (
'id',
'created_at',
'update_at',
)
class MaterialSerializer(serializers.ModelSerializer):
class Meta:
@ -27,6 +86,7 @@ class MaterialSerializer(serializers.ModelSerializer):
read_only_fields = (
'id',
'cover',
'created_at',
'update_at',
)
@ -59,12 +119,14 @@ class CategorySerializer(serializers.ModelSerializer):
'title',
)
read_only_fields = (
'id',
)
read_only_fields = (
'id',
)
class CourseSerializer(serializers.ModelSerializer):
cover = ImageObjectSerializer()
gallery = GallerySerializer()
class Meta:
model = Course
@ -87,17 +149,27 @@ class CourseSerializer(serializers.ModelSerializer):
'materials',
'created_at',
'update_at',
'content',
'gallery',
)
read_only_fields = (
'id',
'cover',
'content',
'created_at',
'update_at',
)
class CourseRetrieveSerializer(CourseSerializer):
category = CategorySerializer()
materials = MaterialSerializer(many=True)
cover = ImageObjectSerializer()
gallery = GallerySerializer()
class LessonSerializer(serializers.ModelSerializer):
cover = ImageObjectSerializer()
class Meta:
model = Lesson
@ -107,6 +179,7 @@ class LessonSerializer(serializers.ModelSerializer):
'short_description',
'course',
'cover',
'content',
'created_at',
'update_at',
)
@ -114,12 +187,14 @@ class LessonSerializer(serializers.ModelSerializer):
read_only_fields = (
'id',
'cover',
'content',
'created_at',
'update_at',
)
class ImageSerializer(serializers.ModelSerializer):
img = ImageObjectSerializer()
class Meta:
model = Image
@ -129,13 +204,13 @@ class ImageSerializer(serializers.ModelSerializer):
'lesson',
'title',
'position',
'img',
'created_at',
'update_at',
) + ('img',)
)
read_only_fields = (
'id',
'img',
'created_at',
'update_at',
)
@ -163,6 +238,7 @@ class TextSerializer(serializers.ModelSerializer):
class ImageTextSerializer(serializers.ModelSerializer):
img = ImageObjectSerializer()
class Meta:
model = ImageText
@ -172,13 +248,14 @@ class ImageTextSerializer(serializers.ModelSerializer):
'lesson',
'title',
'position',
'img',
'txt',
'created_at',
'update_at',
) + ('img', 'txt',)
)
read_only_fields = (
'id',
'img',
'created_at',
'update_at',
)
@ -205,45 +282,6 @@ class VideoSerializer(serializers.ModelSerializer):
)
class GallerySerializer(serializers.ModelSerializer):
class Meta:
model = Gallery
fields = (
'id',
'course',
'title',
'created_at',
'update_at',
)
read_only_fields = (
'id',
'created_at',
'update_at',
)
class GalleryImageSerializer(serializers.ModelSerializer):
class Meta:
model = GalleryImage
fields = (
'id',
'gallery',
'image',
'created_at',
'update_at',
)
read_only_fields = (
'id',
'image',
'created_at',
'update_at',
)
class UserSerializer(serializers.ModelSerializer):
class Meta:

@ -12,7 +12,7 @@ from .views import (
ImageViewSet, TextViewSet,
ImageTextViewSet, VideoViewSet,
GalleryViewSet, GalleryImageViewSet,
UserViewSet, LessonViewSet,
UserViewSet, LessonViewSet, ImageObjectViewSet,
)
router = DefaultRouter()
@ -22,6 +22,7 @@ router.register(r'materials', MaterialViewSet, base_name='materials')
router.register(r'lessons', LessonViewSet, base_name='lessons')
router.register(r'likes', LikeViewSet, base_name='likes')
router.register(r'image-objects', ImageObjectViewSet, base_name='image-objects')
router.register(r'images', ImageViewSet, base_name='images')
router.register(r'texts', TextViewSet, base_name='texts')
router.register(r'image-texts', ImageTextViewSet, base_name='image-texts')

@ -15,18 +15,25 @@ from .serializers import (
UserSerializer, UserPhotoSerializer,
LessonSerializer, ContentImageSerializer,
GalleryImageSerializer, CoverImageSerializer,
CourseRetrieveSerializer, ImageObjectSerializer,
)
from .permissions import IsAdmin, IsAdminOrIsSelf, IsAuthorOrAdmin, IsAuthorObjectOrAdmin
from apps.course.models import Category, Course, Material, Lesson, Like
from apps.content.models import (
Image, Text, ImageText, Video,
Gallery, GalleryImage,
Gallery, GalleryImage, ImageObject,
)
User = get_user_model()
class ImageObjectViewSet(ExtendedModelViewSet):
queryset = ImageObject.objects.all()
serializer_class = ImageObjectSerializer
# permission_classes = (IsAuthorOrAdmin,)
class MaterialViewSet(ExtendedModelViewSet):
queryset = Material.objects.all()
serializer_class = MaterialSerializer
@ -53,13 +60,14 @@ class CategoryViewSet(ExtendedModelViewSet):
class CourseViewSet(ExtendedModelViewSet):
queryset = Course.objects.select_related(
'author', 'category'
'author', 'category', 'cover',
).prefetch_related(
'likes', 'materials'
'likes', 'materials', 'content',
).all()
serializer_class = CourseSerializer
serializer_class_map = {
'upload_photo': CoverImageSerializer,
'list': CourseRetrieveSerializer,
'retrieve': CourseRetrieveSerializer,
}
filter_fields = ('category', 'status', 'is_infinite', 'is_featured',)
search_fields = ('author__email', 'title', 'category__title',)
@ -70,25 +78,12 @@ class CourseViewSet(ExtendedModelViewSet):
# 'delete': IsAdmin,
# }
@detail_route(methods=['post'], url_path='upload-photo')
def upload_photo(self, request, pk=None):
course = self.get_object()
serializer = self.get_serializer()
serialized_data = serializer(data=request.data)
if serialized_data.is_valid():
course.cover = serialized_data['cover']
course.save()
return Response({'success': True})
else:
return Response({'success': False}, status=status.HTTP_400_BAD_REQUEST)
class LessonViewSet(ExtendedModelViewSet):
queryset = Lesson.objects.select_related('course').all()
queryset = Lesson.objects.select_related(
'course', 'cover'
).prefetch_related('content').all()
serializer_class = LessonSerializer
serializer_class_map = {
'upload_photo': CoverImageSerializer,
}
filter_fields = ('course',)
search_fields = ('title', 'short_description',)
ordering_fields = ('title', 'created_at', 'update_at',)
@ -98,27 +93,12 @@ class LessonViewSet(ExtendedModelViewSet):
# 'delete': IsAdmin,
# }
@detail_route(methods=['post'], url_path='upload-photo')
def upload_photo(self, request, pk=None):
lesson = self.get_object()
serializer = self.get_serializer()
serialized_data = serializer(data=request.data)
if serialized_data.is_valid():
lesson.cover = serialized_data['cover']
lesson.save()
return Response({'success': True})
else:
return Response({'success': False}, status=status.HTTP_400_BAD_REQUEST)
class ImageViewSet(ExtendedModelViewSet):
queryset = Image.objects.select_related(
'course', 'lesson'
'course', 'lesson', 'img',
).all()
serializer_class = ImageSerializer
serializer_class_map = {
'upload_photo': ContentImageSerializer,
}
search_fields = ('title',)
ordering_fields = ('title', 'created_at', 'update_at', 'position',)
# permission_classes = (IsAuthorOrAdmin,)
@ -126,18 +106,6 @@ class ImageViewSet(ExtendedModelViewSet):
# 'delete': IsAdmin,
# }
@detail_route(methods=['post'], url_path='upload-photo')
def upload_photo(self, request, pk=None):
image = self.get_object()
serializer = self.get_serializer()
serialized_data = serializer(data=request.data)
if serialized_data.is_valid():
image.img = serialized_data['img']
image.save()
return Response({'success': True})
else:
return Response({'success': False}, status=status.HTTP_400_BAD_REQUEST)
class TextViewSet(ExtendedModelViewSet):
queryset = Text.objects.select_related(
@ -154,12 +122,9 @@ class TextViewSet(ExtendedModelViewSet):
class ImageTextViewSet(ExtendedModelViewSet):
queryset = ImageText.objects.select_related(
'course', 'lesson'
'course', 'lesson', 'img'
).all()
serializer_class = ImageTextSerializer
serializer_class_map = {
'upload_photo': ContentImageSerializer,
}
search_fields = ('title',)
ordering_fields = ('title', 'created_at', 'update_at', 'position',)
# permission_classes = (IsAuthorOrAdmin,)
@ -167,18 +132,6 @@ class ImageTextViewSet(ExtendedModelViewSet):
# 'delete': IsAdmin,
# }
@detail_route(methods=['post'], url_path='upload-photo')
def upload_photo(self, request, pk=None):
image_text = self.get_object()
serializer = self.get_serializer()
serialized_data = serializer(data=request.data)
if serialized_data.is_valid():
image_text.img = serialized_data['img']
image_text.save()
return Response({'success': True})
else:
return Response({'success': False}, status=status.HTTP_400_BAD_REQUEST)
class VideoViewSet(ExtendedModelViewSet):
queryset = Video.objects.select_related(
@ -194,7 +147,7 @@ class VideoViewSet(ExtendedModelViewSet):
class GalleryViewSet(ExtendedModelViewSet):
queryset = Gallery.objects.select_related('course').all()
queryset = Gallery.objects.all()
serializer_class = GallerySerializer
search_fields = ('title',)
ordering_fields = ('title', 'created_at', 'update_at',)
@ -205,29 +158,16 @@ class GalleryViewSet(ExtendedModelViewSet):
class GalleryImageViewSet(ExtendedModelViewSet):
queryset = GalleryImage.objects.select_related('gallery').all()
queryset = GalleryImage.objects.select_related(
'gallery', 'img',
).all()
serializer_class = GalleryImageSerializer
serializer_class_map = {
'upload_photo': GalleryImageSerializer,
}
search_fields = ('gallery__title',)
# permission_classes = (IsAuthorOrAdmin,)
# permission_map = {
# 'delete': IsAdmin,
# }
@detail_route(methods=['post'], url_path='upload-photo')
def upload_photo(self, request, pk=None):
gallery_image = self.get_object()
serializer = self.get_serializer()
serialized_data = serializer(data=request.data)
if serialized_data.is_valid():
gallery_image.image = serialized_data['image']
gallery_image.save()
return Response({'success': True})
else:
return Response({'success': False}, status=status.HTTP_400_BAD_REQUEST)
class UserViewSet(ExtendedModelViewSet):
queryset = User.objects.all()

@ -7,10 +7,20 @@ from polymorphic.admin import (
from apps.content.models import (
Content, Image, Text, ImageText, Video,
Gallery, GalleryImage,
Gallery, GalleryImage, ImageObject,
)
@admin.register(ImageObject)
class ImageObjectAdmin(admin.ModelAdmin):
list_display = (
'id',
'image',
'created_at',
'update_at',
)
class ContentChildAdmin(PolymorphicChildModelAdmin):
base_model = Content

@ -0,0 +1,31 @@
# Generated by Django 2.0.2 on 2018-02-08 05:20
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('content', '0004_gallery_galleryimage'),
]
operations = [
migrations.CreateModel(
name='ImageObject',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('image', models.ImageField(upload_to='content/imageobject', verbose_name='Изображение')),
],
),
migrations.AlterField(
model_name='content',
name='course',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='content', to='course.Course', verbose_name='Курс'),
),
migrations.AlterField(
model_name='content',
name='lesson',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='content', to='course.Lesson', verbose_name='Урок'),
),
]

@ -0,0 +1,29 @@
# Generated by Django 2.0.2 on 2018-02-08 05:51
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('content', '0005_auto_20180208_0520'),
]
operations = [
migrations.AlterModelOptions(
name='imageobject',
options={'ordering': ('-created_at',), 'verbose_name': 'Объект изображения', 'verbose_name_plural': 'Объекты изображения'},
),
migrations.AddField(
model_name='imageobject',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='imageobject',
name='update_at',
field=models.DateTimeField(auto_now=True),
),
]

@ -0,0 +1,19 @@
# Generated by Django 2.0.2 on 2018-02-08 06:26
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('content', '0006_auto_20180208_0551'),
]
operations = [
migrations.AlterField(
model_name='image',
name='img',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='content_images', to='content.ImageObject', verbose_name='Объект изображения'),
),
]

@ -0,0 +1,19 @@
# Generated by Django 2.0.2 on 2018-02-08 06:31
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('content', '0007_auto_20180208_0626'),
]
operations = [
migrations.AlterField(
model_name='imagetext',
name='img',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='content_imagetexts', to='content.ImageObject', verbose_name='Объект изображения'),
),
]

@ -0,0 +1,23 @@
# Generated by Django 2.0.2 on 2018-02-08 06:37
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('content', '0008_auto_20180208_0631'),
]
operations = [
migrations.RemoveField(
model_name='galleryimage',
name='image',
),
migrations.AddField(
model_name='galleryimage',
name='img',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='gallery_images', to='content.ImageObject', verbose_name='Объект изображения'),
),
]

@ -0,0 +1,17 @@
# Generated by Django 2.0.2 on 2018-02-08 08:47
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('content', '0009_auto_20180208_0637'),
]
operations = [
migrations.RemoveField(
model_name='gallery',
name='course',
),
]

@ -2,19 +2,31 @@ from django.db import models
from polymorphic.models import PolymorphicModel
from apps.course.models import Course, Lesson
class ImageObject(models.Model):
image = models.ImageField('Изображение', upload_to='content/imageobject')
created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = 'Объект изображения'
verbose_name_plural = 'Объекты изображения'
ordering = ('-created_at',)
class Content(PolymorphicModel):
course = models.ForeignKey(
Course, on_delete=models.CASCADE,
'course.Course', on_delete=models.CASCADE,
null=True, blank=True,
verbose_name='Курс'
verbose_name='Курс',
related_name='content',
)
lesson = models.ForeignKey(
Lesson, on_delete=models.CASCADE,
'course.Lesson', on_delete=models.CASCADE,
null=True, blank=True,
verbose_name='Урок'
verbose_name='Урок',
related_name='content',
)
title = models.CharField('Заголовок', max_length=100, default='')
position = models.PositiveSmallIntegerField(
@ -32,7 +44,10 @@ class Content(PolymorphicModel):
class Image(Content):
img = models.ImageField('Изображение', upload_to='content/images')
img = models.ForeignKey(
ImageObject, related_name='content_images',
verbose_name='Объект изображения', on_delete=models.CASCADE,
)
class Text(Content):
@ -40,7 +55,10 @@ class Text(Content):
class ImageText(Content):
img = models.ImageField('Изображение', upload_to='content/images')
img = models.ForeignKey(
ImageObject, related_name='content_imagetexts',
verbose_name='Объект изображения', on_delete=models.CASCADE,
)
txt = models.TextField('Текст', default='')
@ -49,11 +67,6 @@ class Video(Content):
class Gallery(models.Model):
course = models.ForeignKey(
Course, on_delete=models.CASCADE,
null=True, blank=True,
verbose_name='Курс'
)
title = models.CharField('Заголовок', max_length=100, default='')
created_at = models.DateTimeField(auto_now_add=True)
@ -70,8 +83,10 @@ class GalleryImage(models.Model):
Gallery, on_delete=models.CASCADE,
verbose_name='Галерея'
)
image = models.ImageField(
'Изображение', upload_to='content/gallery_images'
img = models.ForeignKey(
ImageObject, related_name='gallery_images',
verbose_name='Объект изображения', on_delete=models.CASCADE,
null=True, blank=True,
)
created_at = models.DateTimeField(auto_now_add=True)

@ -7,7 +7,6 @@
"title": "\u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u0443\u0440\u0441 \u0434\u043b\u044f \u0434\u0435\u0442\u0435\u0439 \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043c \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438",
"short_description": "\u042d\u0442\u043e\u0442 \u043a\u0443\u0440\u0441 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0434\u0435\u0442\u044f\u043c \u0443\u0437\u043d\u0430\u0442\u044c \u043e \u0442\u043e\u043c \u043a\u0430\u043a \u0438\u0437 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0444\u043e\u0440\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0435\u0441\u0435\u043b\u044b\u0439 \u0438 \u0445\u0430\u0440\u0438\u0437\u043c\u0430\u0442\u0438\u0447\u043d\u044b\u0445 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u0439.",
"from_author": "",
"cover": "courses/pic-1_sTaZawQ.jpg",
"price": "1500.00",
"is_infinite": false,
"deferred_start_at": null,
@ -30,7 +29,6 @@
"title": "\u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u0443\u0440\u0441 \u0434\u043b\u044f \u0434\u0435\u0442\u0435\u0439 \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043c \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438",
"short_description": "\u042d\u0442\u043e\u0442 \u043a\u0443\u0440\u0441 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0434\u0435\u0442\u044f\u043c \u0443\u0437\u043d\u0430\u0442\u044c \u043e \u0442\u043e\u043c \u043a\u0430\u043a \u0438\u0437 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0444\u043e\u0440\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0435\u0441\u0435\u043b\u044b\u0439 \u0438 \u0445\u0430\u0440\u0438\u0437\u043c\u0430\u0442\u0438\u0447\u043d\u044b\u0445 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u0439.",
"from_author": "",
"cover": "courses/pic-1_sTaZawQ.jpg",
"price": "1900.00",
"is_infinite": false,
"deferred_start_at": null,
@ -53,7 +51,6 @@
"title": "\u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u0443\u0440\u0441 \u0434\u043b\u044f \u0434\u0435\u0442\u0435\u0439 \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043c \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438",
"short_description": "\u042d\u0442\u043e\u0442 \u043a\u0443\u0440\u0441 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0434\u0435\u0442\u044f\u043c \u0443\u0437\u043d\u0430\u0442\u044c \u043e \u0442\u043e\u043c \u043a\u0430\u043a \u0438\u0437 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0444\u043e\u0440\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0435\u0441\u0435\u043b\u044b\u0439 \u0438 \u0445\u0430\u0440\u0438\u0437\u043c\u0430\u0442\u0438\u0447\u043d\u044b\u0445 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u0439.",
"from_author": "",
"cover": "courses/pic-1_sTaZawQ.jpg",
"price": "100.00",
"is_infinite": false,
"deferred_start_at": null,
@ -76,7 +73,6 @@
"title": "\u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u0443\u0440\u0441 \u0434\u043b\u044f \u0434\u0435\u0442\u0435\u0439 \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043c \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438",
"short_description": "\u042d\u0442\u043e\u0442 \u043a\u0443\u0440\u0441 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0434\u0435\u0442\u044f\u043c \u0443\u0437\u043d\u0430\u0442\u044c \u043e \u0442\u043e\u043c \u043a\u0430\u043a \u0438\u0437 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0444\u043e\u0440\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0435\u0441\u0435\u043b\u044b\u0439 \u0438 \u0445\u0430\u0440\u0438\u0437\u043c\u0430\u0442\u0438\u0447\u043d\u044b\u0445 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u0439.",
"from_author": "",
"cover": "courses/pic-1_sTaZawQ.jpg",
"price": "400.00",
"is_infinite": false,
"deferred_start_at": null,
@ -99,7 +95,6 @@
"title": "\u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u0443\u0440\u0441 \u0434\u043b\u044f \u0434\u0435\u0442\u0435\u0439 \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043c \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438",
"short_description": "\u042d\u0442\u043e\u0442 \u043a\u0443\u0440\u0441 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0434\u0435\u0442\u044f\u043c \u0443\u0437\u043d\u0430\u0442\u044c \u043e \u0442\u043e\u043c \u043a\u0430\u043a \u0438\u0437 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0444\u043e\u0440\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0435\u0441\u0435\u043b\u044b\u0439 \u0438 \u0445\u0430\u0440\u0438\u0437\u043c\u0430\u0442\u0438\u0447\u043d\u044b\u0445 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u0439.",
"from_author": "",
"cover": "courses/pic-1_sTaZawQ.jpg",
"price": "1800.00",
"is_infinite": false,
"deferred_start_at": null,
@ -122,7 +117,6 @@
"title": "\u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u0443\u0440\u0441 \u0434\u043b\u044f \u0434\u0435\u0442\u0435\u0439 \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043c \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438",
"short_description": "\u042d\u0442\u043e\u0442 \u043a\u0443\u0440\u0441 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0434\u0435\u0442\u044f\u043c \u0443\u0437\u043d\u0430\u0442\u044c \u043e \u0442\u043e\u043c \u043a\u0430\u043a \u0438\u0437 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0444\u043e\u0440\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0435\u0441\u0435\u043b\u044b\u0439 \u0438 \u0445\u0430\u0440\u0438\u0437\u043c\u0430\u0442\u0438\u0447\u043d\u044b\u0445 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u0439.",
"from_author": "",
"cover": "courses/pic-1_sTaZawQ.jpg",
"price": "100.00",
"is_infinite": false,
"deferred_start_at": null,
@ -145,7 +139,6 @@
"title": "\u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u0443\u0440\u0441 \u0434\u043b\u044f \u0434\u0435\u0442\u0435\u0439 \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043c \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438",
"short_description": "\u042d\u0442\u043e\u0442 \u043a\u0443\u0440\u0441 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0434\u0435\u0442\u044f\u043c \u0443\u0437\u043d\u0430\u0442\u044c \u043e \u0442\u043e\u043c \u043a\u0430\u043a \u0438\u0437 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0444\u043e\u0440\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0435\u0441\u0435\u043b\u044b\u0439 \u0438 \u0445\u0430\u0440\u0438\u0437\u043c\u0430\u0442\u0438\u0447\u043d\u044b\u0445 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u0439.",
"from_author": "",
"cover": "courses/pic-1_sTaZawQ.jpg",
"price": "1600.00",
"is_infinite": false,
"deferred_start_at": null,
@ -168,7 +161,6 @@
"title": "\u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u0443\u0440\u0441 \u0434\u043b\u044f \u0434\u0435\u0442\u0435\u0439 \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043c \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438",
"short_description": "\u042d\u0442\u043e\u0442 \u043a\u0443\u0440\u0441 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0434\u0435\u0442\u044f\u043c \u0443\u0437\u043d\u0430\u0442\u044c \u043e \u0442\u043e\u043c \u043a\u0430\u043a \u0438\u0437 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0444\u043e\u0440\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0435\u0441\u0435\u043b\u044b\u0439 \u0438 \u0445\u0430\u0440\u0438\u0437\u043c\u0430\u0442\u0438\u0447\u043d\u044b\u0445 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u0439.",
"from_author": "",
"cover": "courses/pic-1_sTaZawQ.jpg",
"price": "1900.00",
"is_infinite": false,
"deferred_start_at": null,
@ -191,7 +183,6 @@
"title": "\u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u0443\u0440\u0441 \u0434\u043b\u044f \u0434\u0435\u0442\u0435\u0439 \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043c \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438",
"short_description": "\u042d\u0442\u043e\u0442 \u043a\u0443\u0440\u0441 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0434\u0435\u0442\u044f\u043c \u0443\u0437\u043d\u0430\u0442\u044c \u043e \u0442\u043e\u043c \u043a\u0430\u043a \u0438\u0437 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0444\u043e\u0440\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0435\u0441\u0435\u043b\u044b\u0439 \u0438 \u0445\u0430\u0440\u0438\u0437\u043c\u0430\u0442\u0438\u0447\u043d\u044b\u0445 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u0439.",
"from_author": "",
"cover": "courses/pic-1_sTaZawQ.jpg",
"price": "200.00",
"is_infinite": false,
"deferred_start_at": null,
@ -214,7 +205,6 @@
"title": "\u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u0443\u0440\u0441 \u0434\u043b\u044f \u0434\u0435\u0442\u0435\u0439 \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043c \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438",
"short_description": "\u042d\u0442\u043e\u0442 \u043a\u0443\u0440\u0441 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0434\u0435\u0442\u044f\u043c \u0443\u0437\u043d\u0430\u0442\u044c \u043e \u0442\u043e\u043c \u043a\u0430\u043a \u0438\u0437 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0444\u043e\u0440\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0435\u0441\u0435\u043b\u044b\u0439 \u0438 \u0445\u0430\u0440\u0438\u0437\u043c\u0430\u0442\u0438\u0447\u043d\u044b\u0445 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u0439.",
"from_author": "",
"cover": "courses/pic-1_sTaZawQ.jpg",
"price": "800.00",
"is_infinite": false,
"deferred_start_at": null,
@ -241,7 +231,6 @@
"title": "\u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u0443\u0440\u0441 \u0434\u043b\u044f \u0434\u0435\u0442\u0435\u0439 \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043c \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438",
"short_description": "\u042d\u0442\u043e\u0442 \u043a\u0443\u0440\u0441 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0434\u0435\u0442\u044f\u043c \u0443\u0437\u043d\u0430\u0442\u044c \u043e \u0442\u043e\u043c \u043a\u0430\u043a \u0438\u0437 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0444\u043e\u0440\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0435\u0441\u0435\u043b\u044b\u0439 \u0438 \u0445\u0430\u0440\u0438\u0437\u043c\u0430\u0442\u0438\u0447\u043d\u044b\u0445 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u0439.",
"from_author": "",
"cover": "courses/pic-1_sTaZawQ.jpg",
"price": "100.00",
"is_infinite": false,
"deferred_start_at": "2018-02-28T12:00:00Z",
@ -330,7 +319,6 @@
"title": "1 \u0423\u0420\u041e\u041a",
"short_description": "\u0412\u044b\u0431\u0438\u0440\u0430\u0435\u043c \u0441\u044e\u0436\u0435\u0442, \u0441 \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043d\u0430 \u043a\u0443\u0440\u0441\u0435 \u0438 \u0433\u043b\u0430\u0432\u043d\u043e\u0433\u043e \u0433\u0435\u0440\u043e\u044f \u0432\u0430\u0448\u0435\u0439 \u0438\u0441\u0442\u043e\u0440\u0438\u0438. \u0421 \u044d\u0442\u0438\u043c \u0433\u0435\u0440\u043e\u0435\u043c \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043d\u0430 \u043f\u0440\u043e\u0442\u044f\u0436\u0435\u043d\u0438\u0438 \u0432\u0441\u0435\u0433\u043e \u043a\u0443\u0440\u0441\u0430.",
"course": 11,
"cover": "lessons/kat-watercolor.jpg",
"created_at": "2018-01-31T15:06:14.830Z",
"update_at": "2018-01-31T15:06:14.830Z"
}
@ -342,7 +330,6 @@
"title": "2 \u0423\u0420\u041e\u041a",
"short_description": "\u0421\u043e\u0431\u0438\u0440\u0430\u0435\u043c \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b \u0438 \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0438\u0440\u0443\u0435\u043c \u0441 \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u0433\u0435\u0440\u043e\u044f, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u043d\u044b\u0445 \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0435\u0439 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430. \u041f\u043e \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u043f\u0440\u0438\u0437\u043d\u0430\u043a\u0443 \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u00ab\u0440\u0435\u0444\u0435\u0440\u0435\u043d\u0441\u044b\u00bb. \u0420\u0438\u0441\u0443\u0435\u043c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u043e\u0431\u0440\u0430\u0437\u0430 \u0432 \u0441\u0432\u043e\u0435\u043c \u0441\u0442\u0438\u043b\u0435.\r\n\r\n\u0421\u043e\u0431\u0438\u0440\u0430\u0435\u043c \u0438\u0437 \u043d\u0438\u0445 \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430. \u0412\u044b\u0431\u0438\u0440\u0430\u0435\u043c \u0441\u0430\u043c\u044b\u0435 \u0443\u0434\u0430\u0447\u043d\u044b\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b, \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u0435\u043c \u0438\u0445 \u0432 \u043e\u0434\u043d\u043e\u043c \u043d\u0430\u0431\u0440\u043e\u0441\u043a\u0435.",
"course": 11,
"cover": "lessons/kat-watercolor_SA9juHa.jpg",
"created_at": "2018-01-31T15:06:46.772Z",
"update_at": "2018-01-31T15:06:46.772Z"
}
@ -354,7 +341,6 @@
"title": "3 \u0423\u0420\u041e\u041a",
"short_description": "\u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0435\u043c \u043e\u0436\u0438\u0432\u0438\u0442\u044c \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430. \u0412\u044b\u0431\u0438\u0440\u0430\u0435\u043c 5 \u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u043d\u044b\u0445 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0434\u043b\u044f \u0432\u0430\u0448\u0435\u0433\u043e \u0433\u0435\u0440\u043e\u044f \u0438 \u0442\u043e\u0433\u043e \u0441\u044e\u0436\u0435\u0442\u0430, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043e\u043d \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0443\u0447\u0430\u0441\u0442\u0438\u0435, \u0440\u0438\u0441\u0443\u0435\u043c \u044d\u0441\u043a\u0438\u0437\u044b \u0432\u0430\u0448\u0435\u0433\u043e \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430 \u0432 \u0440\u0430\u0437\u043d\u044b\u0445 \u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u043d\u044b\u0445 \u0434\u043b\u044f \u043d\u0435\u0433\u043e \u043f\u043e\u0437\u0430\u0445 \u0438 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0438.\r\n\r\n\u0412\u044b\u0434\u0435\u043b\u044f\u0435\u043c 5 \u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u043d\u044b\u0445 \u044d\u043c\u043e\u0446\u0438\u0439 \u0434\u043b\u044f \u0433\u0435\u0440\u043e\u044f, \u043d\u0430\u0434 \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c. \u041f\u043e \u044d\u043c\u043e\u0446\u0438\u044f\u043c \u043f\u043e\u0434\u0431\u0438\u0440\u0430\u0435\u043c \u0440\u0435\u0444\u0435\u0440\u0435\u043d\u0441\u044b \u0438 \u0441\u0442\u0438\u043b\u0438\u0437\u0443\u0435\u043c \u0438\u0445 \u0432 \u0441\u0432\u043e\u0435\u043c \u0441\u0442\u0438\u043b\u0435.",
"course": 11,
"cover": "lessons/kat-watercolor_QYFi9sq.jpg",
"created_at": "2018-01-31T15:07:08.979Z",
"update_at": "2018-01-31T15:07:08.979Z"
}
@ -364,7 +350,6 @@
"pk": 1,
"fields": {
"title": "\u0411\u0443\u043c\u0430\u0433\u0430 \u0430\u043a\u0432\u0430\u0440\u0435\u043b\u044c\u043d\u0430\u044f",
"cover": "materials/kat-watercolor.jpg",
"short_description": "\u0411\u0443\u043c\u0430\u0433\u0430 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0430\u043a\u0432\u0430\u0440\u0435\u043b\u044c\u044e \u0438\u043c\u0435\u0435\u0442 \u0431\u043e\u043b\u044c\u0448\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435. \u042d\u0442\u043e \u043e\u0431\u044a\u044f\u0441\u043d\u044f\u0435\u0442\u0441\u044f \u0442\u0435\u043c, \u0447\u0442\u043e \u0430\u043a\u0432\u0430\u0440\u0435\u043b\u044c \u2014 \u043a\u0440\u0430\u0441\u043a\u0430 \u043f\u0440\u043e\u0437\u0440\u0430\u0447\u043d\u0430\u044f, \u0430 \u0437\u043d\u0430\u0447\u0438\u0442 \u0444\u0430\u043a\u0442\u0443\u0440\u0430 \u0431\u0443\u043c\u0430\u0433\u0438 \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0441\u0438\u043b\u044c\u043d\u043e \u0432\u043b\u0438\u044f\u0442\u044c \u043d\u0430 \u0432\u043d\u0435\u0448\u043d\u0438\u0439 \u0432\u0438\u0434 \u043a\u0440\u0430\u0441\u043e\u0447\u043d\u043e\u0433\u043e \u0441\u043b\u043e\u044f.",
"created_at": "2018-01-31T14:55:48.394Z",
"update_at": "2018-01-31T14:55:48.394Z"
@ -375,7 +360,6 @@
"pk": 2,
"fields": {
"title": "\u041a\u0438\u0441\u0442\u043e\u0447\u043a\u0438 \u0434\u043b\u044f \u0440\u0438\u0441\u043e\u0432\u0430\u043d\u0438\u044f",
"cover": "materials/shutterstock_125323070-700x861.jpg",
"short_description": "\u041a\u0438\u0441\u0442\u044c \u2014 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u0434\u043b\u044f \u043f\u043e\u043a\u0440\u0430\u0441\u043a\u0438 \u0438 \u0436\u0438\u0432\u043e\u043f\u0438\u0441\u0438. \u041a\u0438\u0441\u0442\u0438 \u0434\u0435\u043b\u0430\u044e\u0442\u0441\u044f \u0438\u0437 \u0449\u0435\u0442\u0438\u043d\u044b \u0438 \u0445\u0432\u043e\u0441\u0442\u043e\u0432\u044b\u0445 \u0432\u043e\u043b\u043e\u0441\u043a\u043e\u0432 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u0436\u0438\u0432\u043e\u0442\u043d\u044b\u0445.",
"created_at": "2018-01-31T14:57:37.751Z",
"update_at": "2018-01-31T14:57:37.751Z"
@ -386,7 +370,6 @@
"pk": 3,
"fields": {
"title": "\u041a\u0440\u0430\u0441\u043a\u0438 \u0430\u043a\u0432\u0430\u0440\u0435\u043b\u044c\u043d\u044b\u0435",
"cover": "materials/\u043a\u0440\u0430\u0441\u043a\u0438.jpeg",
"short_description": "\u0417\u0430\u0432\u043e\u0434 \u0445\u0443\u0434\u043e\u0436\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0445 \u043a\u0440\u0430\u0441\u043e\u043a \u00ab\u041d\u0435\u0432\u0441\u043a\u0430\u044f \u043f\u0430\u043b\u0438\u0442\u0440\u0430\u00bb \u0432\u044b\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u0430\u043a\u0432\u0430\u0440\u0435\u043b\u044c 80 \u043b\u0435\u0442, \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044f \u0442\u0440\u0430\u0434\u0438\u0446\u0438\u0438 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0441\u0442\u0432\u0430 \u043f\u0440\u043e\u0434\u0443\u043a\u0446\u0438\u0438 \u0432\u044b\u0441\u043e\u0447\u0430\u0439\u0448\u0435\u0433\u043e \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430. \u041f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043d\u044b\u0435 \u0432\u0440\u0435\u043c\u0435\u043d\u0435\u043c \u0440\u0435\u0446\u0435\u043f\u0442\u0443\u0440\u044b, \u043e\u0442\u043b\u0430\u0436\u0435\u043d\u043d\u0430\u044f \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044f \u0438\u0437\u0433\u043e\u0442\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u0434\u0435\u043b\u0430\u043b\u0438 \u0430\u043a\u0432\u0430\u0440\u0435\u043b\u044c\u043d\u044b\u0435 \u043a\u0440\u0430\u0441\u043a\u0438 \u0432\u0438\u0437\u0438\u0442\u043d\u043e\u0439 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u043e\u0439 \u043f\u0440\u0435\u0434\u043f\u0440\u0438\u044f\u0442\u0438\u044f \u0432 \u0420\u043e\u0441\u0441\u0438\u0438",
"created_at": "2018-01-31T14:58:46.209Z",
"update_at": "2018-01-31T14:58:46.209Z"

@ -0,0 +1,19 @@
# Generated by Django 2.0.2 on 2018-02-08 06:47
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('course', '0021_auto_20180206_0632'),
]
operations = [
migrations.AlterField(
model_name='course',
name='cover',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='course_covers', to='content.ImageObject', verbose_name='Обложка курса'),
),
]

@ -0,0 +1,19 @@
# Generated by Django 2.0.2 on 2018-02-08 07:14
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('course', '0022_auto_20180208_0647'),
]
operations = [
migrations.AlterField(
model_name='course',
name='cover',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='course_covers', to='content.ImageObject', verbose_name='Обложка курса'),
),
]

@ -0,0 +1,19 @@
# Generated by Django 2.0.2 on 2018-02-08 08:24
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('course', '0023_auto_20180208_0714'),
]
operations = [
migrations.AlterField(
model_name='lesson',
name='cover',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='lesson_covers', to='content.ImageObject', verbose_name='Обложка урока'),
),
]

@ -0,0 +1,20 @@
# Generated by Django 2.0.2 on 2018-02-08 08:47
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('content', '0010_remove_gallery_course'),
('course', '0024_auto_20180208_0824'),
]
operations = [
migrations.AddField(
model_name='course',
name='gallery',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='content.Gallery', verbose_name='Галерея работ'),
),
]

@ -7,6 +7,8 @@ from polymorphic_tree.models import PolymorphicMPTTModel, PolymorphicTreeForeign
from .manager import CategoryQuerySet
from apps.content.models import ImageObject, Gallery
User = get_user_model()
@ -30,10 +32,20 @@ class Course(models.Model):
title = models.CharField('Название курса', max_length=100, db_index=True)
short_description = models.TextField('Краткое описание курса', db_index=True)
from_author = models.TextField('От автора', default='', null=True, blank=True)
cover = models.ImageField('Фон курса', upload_to='courses')
price = models.DecimalField('Цена курса', help_text='Если цены нету, то курс бесплатный', max_digits=10, decimal_places=2, null=True, blank=True)
cover = models.ForeignKey(
ImageObject, related_name='course_covers',
verbose_name='Обложка курса', on_delete=models.CASCADE,
null=True, blank=True,
)
price = models.DecimalField(
'Цена курса', help_text='Если цены нету, то курс бесплатный',
max_digits=10, decimal_places=2, null=True, blank=True
)
is_infinite = models.BooleanField(default=False)
deferred_start_at = models.DateTimeField('Отложенный запуск курса', help_text='Заполнить если курс отложенный', null=True, blank=True)
deferred_start_at = models.DateTimeField(
'Отложенный запуск курса', help_text='Заполнить если курс отложенный',
null=True, blank=True
)
category = models.ForeignKey('Category', on_delete=models.PROTECT)
duration = models.IntegerField('Продолжительность курса', default=0)
is_featured = models.BooleanField(default=False)
@ -41,6 +53,10 @@ class Course(models.Model):
status = models.PositiveSmallIntegerField('Статус', default=0, choices=STATUS_CHOICES)
likes = models.ManyToManyField(Like, blank=True)
materials = models.ManyToManyField('Material', blank=True)
gallery = models.ForeignKey(
Gallery, verbose_name='Галерея работ',
on_delete=models.CASCADE, null=True, blank=True,
)
created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True)
@ -93,8 +109,11 @@ class Lesson(models.Model):
title = models.CharField('Название урока', max_length=100)
short_description = models.TextField('Краткое описание урока')
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='lessons')
cover = models.ImageField('Фон урока', upload_to='lessons')
cover = models.ForeignKey(
ImageObject, related_name='lesson_covers',
verbose_name='Обложка урока', on_delete=models.CASCADE,
null=True, blank=True,
)
created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True)
@ -127,7 +146,10 @@ class Material(models.Model):
class Comment(PolymorphicMPTTModel):
content = models.TextField('Текст комментария', default='')
author = models.ForeignKey(User, on_delete=models.CASCADE)
parent = PolymorphicTreeForeignKey('self', null=True, blank=True, related_name='children', db_index=True, on_delete=models.PROTECT)
parent = PolymorphicTreeForeignKey(
'self', null=True, blank=True, related_name='children',
db_index=True, on_delete=models.PROTECT
)
created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True)

@ -7,7 +7,11 @@
{% if course.is_deferred_start %}data-future-course data-future-course-time={{ course.deferred_start_at.timestamp }}{% endif %}
>
<a class="courses__preview" href="{% url 'course' course.id %}?next={{ request.get_full_path }}">
{% if course.cover %}
<img class="courses__pic" src="{{ course.cover.url }}"/>
{% else %}
<img class="courses__pic" src="{% static 'img/no_cover.png' %}"/>
{% endif %}
<div class="courses__view">Подробнее</div>
{% if course.is_featured %}
<div class="courses__label courses__label_fav"></div>

@ -177,7 +177,11 @@ class CoursesView(ListView):
return super().get(request, args, kwargs)
def get_queryset(self):
queryset = super().get_queryset()
queryset = super().get_queryset().select_related(
'author', 'category'
).prefetch_related(
'likes', 'materials', 'content',
)
filtered = CourseFilter(self.request.GET, queryset=queryset)
return filtered.qs

@ -0,0 +1,49 @@
from django import forms
from django.contrib.auth import get_user_model
User = get_user_model()
class UserEditForm(forms.ModelForm):
# first_name = forms.CharField()
# last_name = forms.CharField()
# email = forms.CharField()
# city = forms.CharField()
# country = forms.CharField()
birthday = forms.DateField(input_formats=['%d.%m.%Y'])
# gender = forms.ChoiceField(choices=User.GENDER_CHOICES, required=False)
gender = forms.CharField(required=False)
# about = forms.CharField()
old_password = forms.CharField(required=False)
new_password1 = forms.CharField(required=False)
new_password2 = forms.CharField(required=False)
instagram = forms.URLField(required=False)
facebook = forms.URLField(required=False)
twitter = forms.URLField(required=False)
pinterest = forms.URLField(required=False)
youtube = forms.URLField(required=False)
vkontakte = forms.URLField(required=False)
photo = forms.ImageField(required=False)
class Meta:
model = User
fields = (
'first_name',
'last_name',
'email',
'city',
'country',
'birthday',
'gender',
'about',
'old_password',
'new_password1',
'new_password2',
'instagram',
'facebook',
'twitter',
'pinterest',
'youtube',
'vkontakte',
'photo',
)

@ -0,0 +1,18 @@
# Generated by Django 2.0.2 on 2018-02-06 13:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('user', '0004_auto_20180129_1259'),
]
operations = [
migrations.AddField(
model_name='user',
name='birthday',
field=models.DateField(blank=True, null=True, verbose_name='День рождения'),
),
]

@ -0,0 +1,18 @@
# Generated by Django 2.0.2 on 2018-02-06 13:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('user', '0005_user_birthday'),
]
operations = [
migrations.AlterField(
model_name='user',
name='about',
field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='О себе'),
),
]

@ -0,0 +1,23 @@
# Generated by Django 2.0.2 on 2018-02-07 08:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('user', '0006_auto_20180206_1352'),
]
operations = [
migrations.AlterField(
model_name='user',
name='city',
field=models.CharField(blank=True, max_length=85, null=True, verbose_name='Город'),
),
migrations.AlterField(
model_name='user',
name='country',
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='Страна'),
),
]

@ -13,17 +13,21 @@ class User(AbstractUser):
(AUTHOR_ROLE, 'автор'),
(ADMIN_ROLE, 'администратор'),
)
NOT_DEFINED = 'n'
MALE = 'm'
FEMALE = 'f'
GENDER_CHOICES = (
('n', 'не указан'),
('m', 'Мужчина'),
('f', 'Женщина'),
(NOT_DEFINED, 'не указан'),
(MALE, 'Мужчина'),
(FEMALE, 'Женщина'),
)
email = models.EmailField(_('email address'), unique=True)
role = models.PositiveSmallIntegerField('Роль', default=0, choices=ROLE_CHOICES)
gender = models.CharField('Пол', max_length=1, default='n', choices=GENDER_CHOICES)
country = models.CharField('Страна', max_length=50, default='')
city = models.CharField('Город', max_length=85, default='')
about = models.CharField('О себе', max_length=1000, default='', blank=True)
birthday = models.DateField('День рождения', null=True, blank=True)
country = models.CharField('Страна', max_length=50, null=True, blank=True)
city = models.CharField('Город', max_length=85, null=True, blank=True)
about = models.CharField('О себе', max_length=1000, null=True, blank=True)
instagram = models.URLField(default='', null=True, blank=True)
facebook = models.URLField(default='', null=True, blank=True)
twitter = models.URLField(default='', null=True, blank=True)

@ -0,0 +1,209 @@
{% extends "templates/lilcity/index.html" %} {% load static %} {% block content %}
<div class="section section_gray section_menu">
<div class="section__center center center_xs">
<div class="menu">
<a class="menu__link active" href="{% url 'user-edit' user.id %}">Профиль</a>
<a class="menu__link" href="#">Уведомления</a>
<a class="menu__link" href="#">Платежи</a>
</div>
</div>
</div>
{% comment %}
<!-- <div class="section section_confirm">
<div class="section__center center center_xs">
<div class="confirm">
<div class="confirm__title title">Подтверждение почты</div>
<div class="confirm__content">На электронный адрес
<strong>sasha@lil.city</strong> отправлено письмо с кодом подтверждения. Введите код, чтобы подтвердить почту.</div>
<div class="confirm__form">
<div class="confirm__field field field_code">
<div class="field__wrap">
<input class="field__input" type="text" placeholder="Введите код подтверждения">
</div>
</div>
<button class="confirm__btn btn btn_dark">ПОДТВЕРДИТЬ</button>
</div>
<div class="confirm__content">Если у вас нет кода или письмо где-то затерялось, вы можете получить новый код подтверждения. Отправить новый код?</div>
</div>
</div>
</div> -->
{% endcomment %}
{% if messages %}
<div class="section section_gray section_menu">
<div class="section__center center center_xs">
{% for message in messages %}
<div class="message message_{{ message.tags }}">{{ message }}</div>
{% endfor %}
</div>
</div>
{% endif %}
{{form.errors}}
<div class="section section_gray">
<div class="section__center center center_xs">
<div class="form">
<form action="" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div class="form__group">
<div class="form__title">Личные данные</div>
<div class="form__ava ava">
{% if user.photo %}
<img id="photo" class="ava__pic" src="{{user.photo.url}}">
{% else %}
<img id="photo" class="ava__pic" src="{% static 'img/user.jpg' %}">
{% endif %}
<input name="photo" class="ava__input" type="file" accept='image/*' onchange='openFile(event)'>
<div class="ava__icon">
<svg class="icon icon-photo">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-photo"></use>
</svg>
</div>
</div>
<div class="form__fieldset">
<div class="form__field field">
<div class="field__label">ИМЯ</div>
<div class="field__wrap">
<input name='first_name' class="field__input" type="text" placeholder="Имя" value="{{ user.first_name }}">
</div>
</div>
<div class="form__field field">
<div class="field__label">ФАМИЛИЯ</div>
<div class="field__wrap">
<input name='last_name' class="field__input" type="text" placeholder="Фамилия" value="{{ user.last_name }}">
</div>
</div>
</div>
<div class="form__field field">
<div class="field__label">Почта</div>
<div class="field__wrap">
<input name='email' class="field__input" type="email" placeholder="Почта" value="{{ user.email }}">
</div>
</div>
<div class="form__fieldset">
<div class="form__field field">
<div class="field__label">ГОРОД</div>
<div class="field__wrap">
<input name='city' class="field__input" type="text" placeholder="Город" value="{% if user.city %}{{ user.city }}{% endif %}">
</div>
</div>
<div class="form__field field">
<div class="field__label">СТРАНА</div>
<div class="field__wrap">
<input name='country' class="field__input" type="text" placeholder="Страна" value="{% if user.country %}{{ user.country }}{% endif %}">
</div>
</div>
</div>
<div class="form__fieldset">
<div class="form__field field">
<div class="field__label">ДАТА РОЖДЕНИЯ</div>
<div class="field__wrap">
<input name='birthday' class="field__input" type="text" placeholder="dd.mm.yyyy" value="{% if user.birthday %}{{ user.birthday | date:'d.m.Y' }}{% endif %}">
</div>
</div>
<div class="form__field field">
<div class="field__label">ПОЛ</div>
<div class="field__wrap">
<div class="field__select select js-select{% if user.gender and user.gender != 'n' %} selected{% endif %}">
<div class="select__head js-select-head">
{% if user.gender == 'f' %}Ж{% elif user.gender == 'm' %}M{% else %}М / Ж{% endif %}
</div>
<div class="select__drop js-select-drop">
<div class="select__option js-select-option" data-gender-option data-gender="m">
<div class="select__title">М</div>
</div>
<div class="select__option js-select-option" data-gender-option data-gender="f">
<div class="select__title">Ж</div>
</div>
</div>
<input id="gender" name='gender' class="select__input" type="hidden">
</div>
</div>
</div>
</div>
<div class="form__field field">
<div class="field__label">О себе</div>
<div class="field__wrap">
<textarea name='about' class="field__textarea" placeholder="Расскажите о себе и своем опыте">{% if user.about %}{{ user.about }}{% endif %}</textarea>
</div>
</div>
</div>
<div class="form__group">
<div class="form__title">Пароль</div>
<div class="form__field field">
<div class="field__label">ТЕКУЩИЙ ПАРОЛЬ</div>
<div class="field__wrap">
<input name='old_password' class="field__input" type="password" placeholder="Введите текущий пароль">
</div>
</div>
<div class="form__field field">
<div class="field__label">НОВЫЙ ПАРОЛЬ</div>
<div class="field__wrap">
<input name='new_password1' class="field__input" type="password" placeholder="Введите новый пароль">
</div>
</div>
<div class="form__field field">
<div class="field__label">ПОДТВЕРДИТЬ НОВЫЙ ПАРОЛЬ</div>
<div class="field__wrap">
<input name='new_password2' class="field__input" type="password" placeholder="Подтвердите новый пароль">
</div>
</div>
</div>
<div class="form__group">
<div class="form__title">Соцсети</div>
<div class="form__field field">
<div class="field__label">INSTAGRAM</div>
<div class="field__wrap">
<input name='instagram' class="field__input" type="text" placeholder="https://instagram.com/school.lil.city" value="{{ user.instagram }}">
</div>
</div>
<div class="form__field field">
<div class="field__label">FACEBOOK</div>
<div class="field__wrap">
<input name='facebook' class="field__input" type="text" placeholder="https://facebook.com/lilcitycompany" value="{{ user.facebook }}">
</div>
</div>
<div class="form__field field">
<div class="field__label">TWITTER</div>
<div class="field__wrap">
<input name='twitter' class="field__input" type="text" placeholder="https://twitter.com/lilcitycompany" value="{{ user.twitter }}">
</div>
</div>
<div class="form__field field">
<div class="field__label">PINTEREST</div>
<div class="field__wrap">
<input name='pinterest' class="field__input" type="text" placeholder="https://pinterest.com/lilcitycompany" value="{{ user.pinterest }}">
</div>
</div>
<div class="form__field field">
<div class="field__label">YOUTUBE</div>
<div class="field__wrap">
<input name='youtube' class="field__input" type="text" placeholder="https://youtube.com/lilcitycompany" value="{{ user.youtube }}">
</div>
</div>
<div class="form__field field">
<div class="field__label">VKONTAKTE</div>
<div class="field__wrap">
<input name='vkontakte' class="field__input" type="text" placeholder="https://vk.com/lilcitycompany" value="{{ user.vkontakte }}">
</div>
</div>
</div>
<div class="form__foot">
<button type="submit" class="form__btn btn btn_md">СОХРАНИТЬ</button>
</div>
</form>
</div>
</div>
</div>
<script>
var openFile = function(file) {
var input = file.target;
var reader = new FileReader();
reader.onload = function(){
var dataURL = reader.result;
var output = document.getElementById('photo');
output.src = dataURL;
};
reader.readAsDataURL(input.files[0]);
};
</script>
{% endblock content %}

@ -2,7 +2,7 @@
<div class="section">
<div class="section__center center">
<div class="profile">
<a class="profile__btn profile__btn_edit btn" href="#">Редактировать</a>
<a class="profile__btn profile__btn_edit btn" href="{% url 'user-edit' user.id %}">Редактировать</a>
{% if user.photo %}
<div class="profile__ava ava">
<img class="ava__pic" src="{{ user.photo.url }}">

@ -1,9 +1,20 @@
from django.shortcuts import render
from django.views.generic import DetailView
from io import BytesIO
from PIL import Image
from os.path import splitext
from django.contrib.auth import login
from django.shortcuts import render, reverse
from django.views.generic import DetailView, UpdateView
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.hashers import check_password, make_password
from django.http import Http404
from django.utils.decorators import method_decorator
from apps.course.models import Course
from .forms import UserEditForm
User = get_user_model()
@ -13,6 +24,65 @@ class UserView(DetailView):
def get_context_data(self, object):
context = super().get_context_data()
context['published'] = Course.objects.filter(author=self.object, status=Course.PUBLISHED)
context['published'] = Course.objects.filter(
author=self.object, status=Course.PUBLISHED
)
context['paid'] = Course.objects.none()
return context
class UserEditView(UpdateView):
model = User
template_name = 'user/profile-settings.html'
form_class = UserEditForm
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
self.object = self.get_object()
if request.user != self.object:
raise Http404()
return super().dispatch(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
# it's magic *-*-*-*-*
if 'photo' in request.FILES:
photo_fp = request.FILES.pop('photo')[0]
fname = photo_fp.name
photo = Image.open(photo_fp)
lowest_side = min(photo.size)
horizontal_padding = (lowest_side - photo.size[0]) / 2
vertical_padding = (lowest_side - photo.size[1]) / 2
photo = photo.crop(
(
-horizontal_padding,
-vertical_padding,
photo.size[0] + horizontal_padding,
photo.size[1] + vertical_padding
)
)
if photo.size[0] > 512:
photo = photo.resize((512, 512,))
buffer = BytesIO()
ext = splitext(fname)[1][1:].upper()
if ext == 'JPG':
ext = 'JPEG'
photo.save(buffer, ext)
self.object.photo.save(fname, buffer)
buffer.close()
if not request.POST._mutable:
request.POST._mutable = True
old_password = request.POST.pop('old_password')[0]
new_password1 = request.POST.pop('new_password1')[0]
new_password2 = request.POST.pop('new_password2')[0]
if old_password:
if request.user.check_password(old_password) and new_password1 == new_password2:
request.user.set_password(new_password1)
request.user.save()
login(request, request.user)
else:
messages.error(request, 'Неверный пароль.')
messages.info(request, 'Данные сохранены.')
return super().post(request, *args, **kwargs)
def get_success_url(self):
return reverse('user-edit', args=[self.object.id])

@ -22,7 +22,7 @@ services:
restart: always
volumes:
- .:/lilcity
command: bash -c "python manage.py migrate && python manage.py loaddata /lilcity/apps/*/fixtures/*.json && python manage.py runserver 0.0.0.0:8000 && celery worker -A project -Q web"
command: bash -c "python manage.py migrate && python manage.py loaddata /lilcity/apps/*/fixtures/*.json && python manage.py runserver 0.0.0.0:8000 && celery worker -A project"
environment:
- DJANGO_SETTINGS_MODULE=project.settings
- DATABASE_SERVICE_HOST=db

@ -162,7 +162,7 @@ STATICFILES_DIRS = [
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
LOGIN_URL = '/'
# Email
# https://github.com/anymail/django-anymail

@ -33,7 +33,7 @@
<meta property="fb:admins" content="Facebook numeric ID">
<meta name="csrf-token" content="{{ csrf_token }}">
<link rel="stylesheet" media="all" href={% static "css/app.css" %}>
<link rel="shortcut icon" type="image/png" href="{% static 'favicon.ico' %}"/>
<link rel="shortcut icon" type="image/png" href="{% static 'img/favicon.ico' %}"/>
<script>
var viewportmeta = document.querySelector('meta[name="viewport"]');
if (viewportmeta) {
@ -126,10 +126,10 @@
<div class="header__group"><a class="header__section" target="_blank" href="http://blog.lil.school">БЛОГ</a></div>
</nav>
</div>
{% if user.is_authenticated %}
{% if request.user.is_authenticated %}
<div class="header__login">
{% if user.photo %}
<div class="header__ava ava"><img class="ava__pic" src="{{ user.photo.url }}"></div>
{% if request.user.photo %}
<div class="header__ava ava"><img class="ava__pic" src="{{ request.user.photo.url }}"></div>
{% else %}
<div class="header__ava ava"><img class="ava__pic" src="{% static 'img/user.jpg' %}"></div>
{% endif %}
@ -138,7 +138,7 @@
<a class="header__link header__link_green" href="#">
<div class="header__title">ДОБАВИТЬ КУРС</div>
</a>
<a class="header__link" href="{% url 'user' user.id %}">
<a class="header__link" href="{% url 'user' request.user.id %}">
<div class="header__title">ПРОФИЛЬ</div>
</a>
<a class="header__link" href="#">
@ -158,7 +158,7 @@
{% endif %}
</div>
</div>
{% if user.is_authenticated and not user.fb_id or user.is_authenticated and not user.is_email_proved %}
{% if request.user.is_authenticated and not request.user.fb_id or request.user.is_authenticated and not request.user.is_email_proved %}
<div class="message message_error">Необходимо подтвердить электронную почту</div>
{% endif %}
</header>

@ -24,7 +24,7 @@ from apps.course.views import (
lessoncomment,
)
from apps.course.models import Course
from apps.user.views import UserView
from apps.user.views import UserView, UserEditView
urlpatterns = [
path('admin/', admin.site.urls),
@ -37,6 +37,7 @@ urlpatterns = [
path('lesson/<int:lesson_id>/comment', lessoncomment, name='lessoncomment'),
path('search/', SearchView.as_view(), name='search'),
path('user/<int:pk>/', UserView.as_view(), name='user'),
path('user/<int:pk>/edit/', UserEditView.as_view(), name='user-edit'),
path('privacy', TemplateView.as_view(template_name="templates/lilcity/privacy_policy.html"), name='privacy'),
path('terms', TemplateView.as_view(template_name="templates/lilcity/terms.html"), name='terms'),
path('refund-policy', TemplateView.as_view(template_name="templates/lilcity/refund_policy.html"), name='refund_policy'),
@ -47,6 +48,9 @@ urlpatterns = [
if settings.DEBUG:
urlpatterns += [path('silk/', include('silk.urls', namespace='silk'))]
from django.conf.urls.static import static
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
urlpatterns += [path('silk/', include('silk.urls', namespace='silk'))]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += staticfiles_urlpatterns()

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 793 B

@ -27021,6 +27021,8 @@ __webpack_require__(289);
__webpack_require__(290);
__webpack_require__(154);
/***/ }),
/* 265 */
/***/ (function(module, exports, __webpack_require__) {
@ -29769,6 +29771,32 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
});
});
/***/ }),
/* 154 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var _jquery = __webpack_require__(1);
var _jquery2 = _interopRequireDefault(_jquery);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
(0, _jquery2.default)(document).ready(function () {
// Обработчик выбора пола
var genderInput = (0, _jquery2.default)('#gender');
(0, _jquery2.default)('div.js-select-option[data-gender-option]').on('click', function (e) {
e.preventDefault();
var currentGender = (0, _jquery2.default)(this).attr('data-gender');
(0, _jquery2.default)('[data-gender]').removeClass('active');
(0, _jquery2.default)('[data-gender=' + currentGender + ']').addClass('active');
genderInput.val(currentGender);
});
});
/***/ })
/******/ ]);
//# sourceMappingURL=app.js.map

@ -22,7 +22,7 @@ gulp.task('copy:rootfiles', function() {
gulp.task('copy:img', function() {
return gulp
.src([
config.src.img + '/**/*.{jpg,png,jpeg,svg,gif}',
config.src.img + '/**/*.{jpg,png,jpeg,svg,gif,ico}',
'!' + config.src.img + '/svgo/**/*.*'
])
.pipe(gulp.dest(config.dest.img));

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 793 B

@ -12,3 +12,4 @@ import "./modules/popup";
import "./modules/courses";
import "./modules/comments";
import "./modules/password-show";
import "./modules/profile";

@ -0,0 +1,14 @@
import $ from 'jquery';
$(document).ready(function () {
// Обработчик выбора пола
let genderInput = $('#gender')
$('div.js-select-option[data-gender-option]').on('click', function (e) {
e.preventDefault();
const currentGender = $(this).attr('data-gender');
$('[data-gender]').removeClass('active');
$(`[data-gender=${currentGender}]`).addClass('active');
genderInput.val(currentGender)
});
})

@ -3218,6 +3218,8 @@ a.grey-link
z-index: 999
&_error
background: $pink
&_info
background: $green
.mobile-hide

@ -5386,6 +5386,10 @@ slash@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
smooth-scroll@^12.1.5:
version "12.1.5"
resolved "https://registry.yarnpkg.com/smooth-scroll/-/smooth-scroll-12.1.5.tgz#b6d8deca371a9edd5ea3ad291ef0fb7e741ed41e"
snapdragon-node@^2.0.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"

Loading…
Cancel
Save