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

@ -12,7 +12,7 @@ from .views import (
ImageViewSet, TextViewSet, ImageViewSet, TextViewSet,
ImageTextViewSet, VideoViewSet, ImageTextViewSet, VideoViewSet,
GalleryViewSet, GalleryImageViewSet, GalleryViewSet, GalleryImageViewSet,
UserViewSet, LessonViewSet, UserViewSet, LessonViewSet, ImageObjectViewSet,
) )
router = DefaultRouter() 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'lessons', LessonViewSet, base_name='lessons')
router.register(r'likes', LikeViewSet, base_name='likes') 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'images', ImageViewSet, base_name='images')
router.register(r'texts', TextViewSet, base_name='texts') router.register(r'texts', TextViewSet, base_name='texts')
router.register(r'image-texts', ImageTextViewSet, base_name='image-texts') router.register(r'image-texts', ImageTextViewSet, base_name='image-texts')

@ -15,18 +15,25 @@ from .serializers import (
UserSerializer, UserPhotoSerializer, UserSerializer, UserPhotoSerializer,
LessonSerializer, ContentImageSerializer, LessonSerializer, ContentImageSerializer,
GalleryImageSerializer, CoverImageSerializer, GalleryImageSerializer, CoverImageSerializer,
CourseRetrieveSerializer, ImageObjectSerializer,
) )
from .permissions import IsAdmin, IsAdminOrIsSelf, IsAuthorOrAdmin, IsAuthorObjectOrAdmin from .permissions import IsAdmin, IsAdminOrIsSelf, IsAuthorOrAdmin, IsAuthorObjectOrAdmin
from apps.course.models import Category, Course, Material, Lesson, Like from apps.course.models import Category, Course, Material, Lesson, Like
from apps.content.models import ( from apps.content.models import (
Image, Text, ImageText, Video, Image, Text, ImageText, Video,
Gallery, GalleryImage, Gallery, GalleryImage, ImageObject,
) )
User = get_user_model() User = get_user_model()
class ImageObjectViewSet(ExtendedModelViewSet):
queryset = ImageObject.objects.all()
serializer_class = ImageObjectSerializer
# permission_classes = (IsAuthorOrAdmin,)
class MaterialViewSet(ExtendedModelViewSet): class MaterialViewSet(ExtendedModelViewSet):
queryset = Material.objects.all() queryset = Material.objects.all()
serializer_class = MaterialSerializer serializer_class = MaterialSerializer
@ -53,13 +60,14 @@ class CategoryViewSet(ExtendedModelViewSet):
class CourseViewSet(ExtendedModelViewSet): class CourseViewSet(ExtendedModelViewSet):
queryset = Course.objects.select_related( queryset = Course.objects.select_related(
'author', 'category' 'author', 'category', 'cover',
).prefetch_related( ).prefetch_related(
'likes', 'materials' 'likes', 'materials', 'content',
).all() ).all()
serializer_class = CourseSerializer serializer_class = CourseSerializer
serializer_class_map = { serializer_class_map = {
'upload_photo': CoverImageSerializer, 'list': CourseRetrieveSerializer,
'retrieve': CourseRetrieveSerializer,
} }
filter_fields = ('category', 'status', 'is_infinite', 'is_featured',) filter_fields = ('category', 'status', 'is_infinite', 'is_featured',)
search_fields = ('author__email', 'title', 'category__title',) search_fields = ('author__email', 'title', 'category__title',)
@ -70,25 +78,12 @@ class CourseViewSet(ExtendedModelViewSet):
# 'delete': IsAdmin, # '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): 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 = LessonSerializer
serializer_class_map = {
'upload_photo': CoverImageSerializer,
}
filter_fields = ('course',) filter_fields = ('course',)
search_fields = ('title', 'short_description',) search_fields = ('title', 'short_description',)
ordering_fields = ('title', 'created_at', 'update_at',) ordering_fields = ('title', 'created_at', 'update_at',)
@ -98,27 +93,12 @@ class LessonViewSet(ExtendedModelViewSet):
# 'delete': IsAdmin, # '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): class ImageViewSet(ExtendedModelViewSet):
queryset = Image.objects.select_related( queryset = Image.objects.select_related(
'course', 'lesson' 'course', 'lesson', 'img',
).all() ).all()
serializer_class = ImageSerializer serializer_class = ImageSerializer
serializer_class_map = {
'upload_photo': ContentImageSerializer,
}
search_fields = ('title',) search_fields = ('title',)
ordering_fields = ('title', 'created_at', 'update_at', 'position',) ordering_fields = ('title', 'created_at', 'update_at', 'position',)
# permission_classes = (IsAuthorOrAdmin,) # permission_classes = (IsAuthorOrAdmin,)
@ -126,18 +106,6 @@ class ImageViewSet(ExtendedModelViewSet):
# 'delete': IsAdmin, # '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): class TextViewSet(ExtendedModelViewSet):
queryset = Text.objects.select_related( queryset = Text.objects.select_related(
@ -154,12 +122,9 @@ class TextViewSet(ExtendedModelViewSet):
class ImageTextViewSet(ExtendedModelViewSet): class ImageTextViewSet(ExtendedModelViewSet):
queryset = ImageText.objects.select_related( queryset = ImageText.objects.select_related(
'course', 'lesson' 'course', 'lesson', 'img'
).all() ).all()
serializer_class = ImageTextSerializer serializer_class = ImageTextSerializer
serializer_class_map = {
'upload_photo': ContentImageSerializer,
}
search_fields = ('title',) search_fields = ('title',)
ordering_fields = ('title', 'created_at', 'update_at', 'position',) ordering_fields = ('title', 'created_at', 'update_at', 'position',)
# permission_classes = (IsAuthorOrAdmin,) # permission_classes = (IsAuthorOrAdmin,)
@ -167,18 +132,6 @@ class ImageTextViewSet(ExtendedModelViewSet):
# 'delete': IsAdmin, # '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): class VideoViewSet(ExtendedModelViewSet):
queryset = Video.objects.select_related( queryset = Video.objects.select_related(
@ -194,7 +147,7 @@ class VideoViewSet(ExtendedModelViewSet):
class GalleryViewSet(ExtendedModelViewSet): class GalleryViewSet(ExtendedModelViewSet):
queryset = Gallery.objects.select_related('course').all() queryset = Gallery.objects.all()
serializer_class = GallerySerializer serializer_class = GallerySerializer
search_fields = ('title',) search_fields = ('title',)
ordering_fields = ('title', 'created_at', 'update_at',) ordering_fields = ('title', 'created_at', 'update_at',)
@ -205,29 +158,16 @@ class GalleryViewSet(ExtendedModelViewSet):
class GalleryImageViewSet(ExtendedModelViewSet): class GalleryImageViewSet(ExtendedModelViewSet):
queryset = GalleryImage.objects.select_related('gallery').all() queryset = GalleryImage.objects.select_related(
'gallery', 'img',
).all()
serializer_class = GalleryImageSerializer serializer_class = GalleryImageSerializer
serializer_class_map = {
'upload_photo': GalleryImageSerializer,
}
search_fields = ('gallery__title',) search_fields = ('gallery__title',)
# permission_classes = (IsAuthorOrAdmin,) # permission_classes = (IsAuthorOrAdmin,)
# permission_map = { # permission_map = {
# 'delete': IsAdmin, # '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): class UserViewSet(ExtendedModelViewSet):
queryset = User.objects.all() queryset = User.objects.all()

@ -7,10 +7,20 @@ from polymorphic.admin import (
from apps.content.models import ( from apps.content.models import (
Content, Image, Text, ImageText, Video, 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): class ContentChildAdmin(PolymorphicChildModelAdmin):
base_model = Content 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 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): class Content(PolymorphicModel):
course = models.ForeignKey( course = models.ForeignKey(
Course, on_delete=models.CASCADE, 'course.Course', on_delete=models.CASCADE,
null=True, blank=True, null=True, blank=True,
verbose_name='Курс' verbose_name='Курс',
related_name='content',
) )
lesson = models.ForeignKey( lesson = models.ForeignKey(
Lesson, on_delete=models.CASCADE, 'course.Lesson', on_delete=models.CASCADE,
null=True, blank=True, null=True, blank=True,
verbose_name='Урок' verbose_name='Урок',
related_name='content',
) )
title = models.CharField('Заголовок', max_length=100, default='') title = models.CharField('Заголовок', max_length=100, default='')
position = models.PositiveSmallIntegerField( position = models.PositiveSmallIntegerField(
@ -32,7 +44,10 @@ class Content(PolymorphicModel):
class Image(Content): 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): class Text(Content):
@ -40,7 +55,10 @@ class Text(Content):
class ImageText(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='') txt = models.TextField('Текст', default='')
@ -49,11 +67,6 @@ class Video(Content):
class Gallery(models.Model): 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='') title = models.CharField('Заголовок', max_length=100, default='')
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
@ -70,8 +83,10 @@ class GalleryImage(models.Model):
Gallery, on_delete=models.CASCADE, Gallery, on_delete=models.CASCADE,
verbose_name='Галерея' verbose_name='Галерея'
) )
image = models.ImageField( img = models.ForeignKey(
'Изображение', upload_to='content/gallery_images' ImageObject, related_name='gallery_images',
verbose_name='Объект изображения', on_delete=models.CASCADE,
null=True, blank=True,
) )
created_at = models.DateTimeField(auto_now_add=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", "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.", "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": "", "from_author": "",
"cover": "courses/pic-1_sTaZawQ.jpg",
"price": "1500.00", "price": "1500.00",
"is_infinite": false, "is_infinite": false,
"deferred_start_at": null, "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", "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.", "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": "", "from_author": "",
"cover": "courses/pic-1_sTaZawQ.jpg",
"price": "1900.00", "price": "1900.00",
"is_infinite": false, "is_infinite": false,
"deferred_start_at": null, "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", "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.", "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": "", "from_author": "",
"cover": "courses/pic-1_sTaZawQ.jpg",
"price": "100.00", "price": "100.00",
"is_infinite": false, "is_infinite": false,
"deferred_start_at": null, "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", "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.", "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": "", "from_author": "",
"cover": "courses/pic-1_sTaZawQ.jpg",
"price": "400.00", "price": "400.00",
"is_infinite": false, "is_infinite": false,
"deferred_start_at": null, "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", "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.", "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": "", "from_author": "",
"cover": "courses/pic-1_sTaZawQ.jpg",
"price": "1800.00", "price": "1800.00",
"is_infinite": false, "is_infinite": false,
"deferred_start_at": null, "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", "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.", "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": "", "from_author": "",
"cover": "courses/pic-1_sTaZawQ.jpg",
"price": "100.00", "price": "100.00",
"is_infinite": false, "is_infinite": false,
"deferred_start_at": null, "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", "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.", "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": "", "from_author": "",
"cover": "courses/pic-1_sTaZawQ.jpg",
"price": "1600.00", "price": "1600.00",
"is_infinite": false, "is_infinite": false,
"deferred_start_at": null, "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", "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.", "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": "", "from_author": "",
"cover": "courses/pic-1_sTaZawQ.jpg",
"price": "1900.00", "price": "1900.00",
"is_infinite": false, "is_infinite": false,
"deferred_start_at": null, "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", "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.", "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": "", "from_author": "",
"cover": "courses/pic-1_sTaZawQ.jpg",
"price": "200.00", "price": "200.00",
"is_infinite": false, "is_infinite": false,
"deferred_start_at": null, "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", "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.", "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": "", "from_author": "",
"cover": "courses/pic-1_sTaZawQ.jpg",
"price": "800.00", "price": "800.00",
"is_infinite": false, "is_infinite": false,
"deferred_start_at": null, "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", "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.", "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": "", "from_author": "",
"cover": "courses/pic-1_sTaZawQ.jpg",
"price": "100.00", "price": "100.00",
"is_infinite": false, "is_infinite": false,
"deferred_start_at": "2018-02-28T12:00:00Z", "deferred_start_at": "2018-02-28T12:00:00Z",
@ -330,7 +319,6 @@
"title": "1 \u0423\u0420\u041e\u041a", "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.", "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, "course": 11,
"cover": "lessons/kat-watercolor.jpg",
"created_at": "2018-01-31T15:06:14.830Z", "created_at": "2018-01-31T15:06:14.830Z",
"update_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", "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.", "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, "course": 11,
"cover": "lessons/kat-watercolor_SA9juHa.jpg",
"created_at": "2018-01-31T15:06:46.772Z", "created_at": "2018-01-31T15:06:46.772Z",
"update_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", "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.", "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, "course": 11,
"cover": "lessons/kat-watercolor_QYFi9sq.jpg",
"created_at": "2018-01-31T15:07:08.979Z", "created_at": "2018-01-31T15:07:08.979Z",
"update_at": "2018-01-31T15:07:08.979Z" "update_at": "2018-01-31T15:07:08.979Z"
} }
@ -364,7 +350,6 @@
"pk": 1, "pk": 1,
"fields": { "fields": {
"title": "\u0411\u0443\u043c\u0430\u0433\u0430 \u0430\u043a\u0432\u0430\u0440\u0435\u043b\u044c\u043d\u0430\u044f", "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.", "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", "created_at": "2018-01-31T14:55:48.394Z",
"update_at": "2018-01-31T14:55:48.394Z" "update_at": "2018-01-31T14:55:48.394Z"
@ -375,7 +360,6 @@
"pk": 2, "pk": 2,
"fields": { "fields": {
"title": "\u041a\u0438\u0441\u0442\u043e\u0447\u043a\u0438 \u0434\u043b\u044f \u0440\u0438\u0441\u043e\u0432\u0430\u043d\u0438\u044f", "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.", "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", "created_at": "2018-01-31T14:57:37.751Z",
"update_at": "2018-01-31T14:57:37.751Z" "update_at": "2018-01-31T14:57:37.751Z"
@ -386,7 +370,6 @@
"pk": 3, "pk": 3,
"fields": { "fields": {
"title": "\u041a\u0440\u0430\u0441\u043a\u0438 \u0430\u043a\u0432\u0430\u0440\u0435\u043b\u044c\u043d\u044b\u0435", "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", "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", "created_at": "2018-01-31T14:58:46.209Z",
"update_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 .manager import CategoryQuerySet
from apps.content.models import ImageObject, Gallery
User = get_user_model() User = get_user_model()
@ -30,10 +32,20 @@ class Course(models.Model):
title = models.CharField('Название курса', max_length=100, db_index=True) title = models.CharField('Название курса', max_length=100, db_index=True)
short_description = models.TextField('Краткое описание курса', db_index=True) short_description = models.TextField('Краткое описание курса', db_index=True)
from_author = models.TextField('От автора', default='', null=True, blank=True) from_author = models.TextField('От автора', default='', null=True, blank=True)
cover = models.ImageField('Фон курса', upload_to='courses') cover = models.ForeignKey(
price = models.DecimalField('Цена курса', help_text='Если цены нету, то курс бесплатный', max_digits=10, decimal_places=2, null=True, blank=True) 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) 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) category = models.ForeignKey('Category', on_delete=models.PROTECT)
duration = models.IntegerField('Продолжительность курса', default=0) duration = models.IntegerField('Продолжительность курса', default=0)
is_featured = models.BooleanField(default=False) is_featured = models.BooleanField(default=False)
@ -41,6 +53,10 @@ class Course(models.Model):
status = models.PositiveSmallIntegerField('Статус', default=0, choices=STATUS_CHOICES) status = models.PositiveSmallIntegerField('Статус', default=0, choices=STATUS_CHOICES)
likes = models.ManyToManyField(Like, blank=True) likes = models.ManyToManyField(Like, blank=True)
materials = models.ManyToManyField('Material', 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) created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True) update_at = models.DateTimeField(auto_now=True)
@ -93,8 +109,11 @@ class Lesson(models.Model):
title = models.CharField('Название урока', max_length=100) title = models.CharField('Название урока', max_length=100)
short_description = models.TextField('Краткое описание урока') short_description = models.TextField('Краткое описание урока')
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='lessons') 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) created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True) update_at = models.DateTimeField(auto_now=True)
@ -127,7 +146,10 @@ class Material(models.Model):
class Comment(PolymorphicMPTTModel): class Comment(PolymorphicMPTTModel):
content = models.TextField('Текст комментария', default='') content = models.TextField('Текст комментария', default='')
author = models.ForeignKey(User, on_delete=models.CASCADE) 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) created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=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 %} {% 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 }}"> <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 }}"/> <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> <div class="courses__view">Подробнее</div>
{% if course.is_featured %} {% if course.is_featured %}
<div class="courses__label courses__label_fav"></div> <div class="courses__label courses__label_fav"></div>

@ -177,7 +177,11 @@ class CoursesView(ListView):
return super().get(request, args, kwargs) return super().get(request, args, kwargs)
def get_queryset(self): 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) filtered = CourseFilter(self.request.GET, queryset=queryset)
return filtered.qs 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, 'автор'), (AUTHOR_ROLE, 'автор'),
(ADMIN_ROLE, 'администратор'), (ADMIN_ROLE, 'администратор'),
) )
NOT_DEFINED = 'n'
MALE = 'm'
FEMALE = 'f'
GENDER_CHOICES = ( GENDER_CHOICES = (
('n', 'не указан'), (NOT_DEFINED, 'не указан'),
('m', 'Мужчина'), (MALE, 'Мужчина'),
('f', 'Женщина'), (FEMALE, 'Женщина'),
) )
email = models.EmailField(_('email address'), unique=True) email = models.EmailField(_('email address'), unique=True)
role = models.PositiveSmallIntegerField('Роль', default=0, choices=ROLE_CHOICES) role = models.PositiveSmallIntegerField('Роль', default=0, choices=ROLE_CHOICES)
gender = models.CharField('Пол', max_length=1, default='n', choices=GENDER_CHOICES) gender = models.CharField('Пол', max_length=1, default='n', choices=GENDER_CHOICES)
country = models.CharField('Страна', max_length=50, default='') birthday = models.DateField('День рождения', null=True, blank=True)
city = models.CharField('Город', max_length=85, default='') country = models.CharField('Страна', max_length=50, null=True, blank=True)
about = models.CharField('О себе', max_length=1000, default='', 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) instagram = models.URLField(default='', null=True, blank=True)
facebook = models.URLField(default='', null=True, blank=True) facebook = models.URLField(default='', null=True, blank=True)
twitter = 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">
<div class="section__center center"> <div class="section__center center">
<div class="profile"> <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 %} {% if user.photo %}
<div class="profile__ava ava"> <div class="profile__ava ava">
<img class="ava__pic" src="{{ user.photo.url }}"> <img class="ava__pic" src="{{ user.photo.url }}">

@ -1,9 +1,20 @@
from django.shortcuts import render from io import BytesIO
from django.views.generic import DetailView 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 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 apps.course.models import Course
from .forms import UserEditForm
User = get_user_model() User = get_user_model()
@ -13,6 +24,65 @@ class UserView(DetailView):
def get_context_data(self, object): def get_context_data(self, object):
context = super().get_context_data() 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() context['paid'] = Course.objects.none()
return context 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 restart: always
volumes: volumes:
- .:/lilcity - .:/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: environment:
- DJANGO_SETTINGS_MODULE=project.settings - DJANGO_SETTINGS_MODULE=project.settings
- DATABASE_SERVICE_HOST=db - DATABASE_SERVICE_HOST=db

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

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

@ -24,7 +24,7 @@ from apps.course.views import (
lessoncomment, lessoncomment,
) )
from apps.course.models import Course from apps.course.models import Course
from apps.user.views import UserView from apps.user.views import UserView, UserEditView
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
@ -37,6 +37,7 @@ urlpatterns = [
path('lesson/<int:lesson_id>/comment', lessoncomment, name='lessoncomment'), path('lesson/<int:lesson_id>/comment', lessoncomment, name='lessoncomment'),
path('search/', SearchView.as_view(), name='search'), path('search/', SearchView.as_view(), name='search'),
path('user/<int:pk>/', UserView.as_view(), name='user'), path('user/<int:pk>/', UserView.as_view(), name='user'),
path('user/<int:pk>/edit/', UserEditView.as_view(), name='user-edit'),
path('privacy', TemplateView.as_view(template_name="templates/lilcity/privacy_policy.html"), name='privacy'), 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('terms', TemplateView.as_view(template_name="templates/lilcity/terms.html"), name='terms'),
path('refund-policy', TemplateView.as_view(template_name="templates/lilcity/refund_policy.html"), name='refund_policy'), path('refund-policy', TemplateView.as_view(template_name="templates/lilcity/refund_policy.html"), name='refund_policy'),
@ -47,6 +48,9 @@ urlpatterns = [
if settings.DEBUG: if settings.DEBUG:
urlpatterns += [path('silk/', include('silk.urls', namespace='silk'))]
from django.conf.urls.static import static 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 += 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__(290);
__webpack_require__(154);
/***/ }), /***/ }),
/* 265 */ /* 265 */
/***/ (function(module, exports, __webpack_require__) { /***/ (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 //# sourceMappingURL=app.js.map

@ -22,7 +22,7 @@ gulp.task('copy:rootfiles', function() {
gulp.task('copy:img', function() { gulp.task('copy:img', function() {
return gulp return gulp
.src([ .src([
config.src.img + '/**/*.{jpg,png,jpeg,svg,gif}', config.src.img + '/**/*.{jpg,png,jpeg,svg,gif,ico}',
'!' + config.src.img + '/svgo/**/*.*' '!' + config.src.img + '/svgo/**/*.*'
]) ])
.pipe(gulp.dest(config.dest.img)); .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/courses";
import "./modules/comments"; import "./modules/comments";
import "./modules/password-show"; 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 z-index: 999
&_error &_error
background: $pink background: $pink
&_info
background: $green
.mobile-hide .mobile-hide

@ -5386,6 +5386,10 @@ slash@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" 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: snapdragon-node@^2.0.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"

Loading…
Cancel
Save