diff --git a/api/v1/serializers/content.py b/api/v1/serializers/content.py index cdaa5351..c01a2c65 100644 --- a/api/v1/serializers/content.py +++ b/api/v1/serializers/content.py @@ -13,6 +13,7 @@ class ContentCreateSerializer(serializers.Serializer): 'text', 'image', 'image-text', + 'images', 'video', ) type = serializers.ChoiceField(choices=TYPE_CHOICES) @@ -27,6 +28,8 @@ class ContentCreateSerializer(serializers.Serializer): return ImageTextSerializer(obj, context=self.context).to_representation(obj) elif isinstance(obj, Video): return VideoSerializer(obj, context=self.context).to_representation(obj) + elif isinstance(obj, Gallery): + return GallerySerializer(obj, context=self.context).to_representation(obj) return super(ContentSerializer, self).to_representation(obj) @@ -52,6 +55,7 @@ class ImageObjectSerializer(serializers.ModelSerializer): class ImageCreateSerializer(serializers.ModelSerializer): + type = serializers.SerializerMethodField() class Meta: model = Image @@ -62,22 +66,28 @@ class ImageCreateSerializer(serializers.ModelSerializer): 'title', 'position', 'img', + 'type', 'created_at', 'update_at', ) read_only_fields = ( 'id', + 'type', 'created_at', 'update_at', ) + def get_type(self, object): + return 'image' + class ImageSerializer(ImageCreateSerializer): img = ImageObjectSerializer() class TextCreateSerializer(serializers.ModelSerializer): + type = serializers.SerializerMethodField() class Meta: model = Text @@ -87,22 +97,28 @@ class TextCreateSerializer(serializers.ModelSerializer): 'lesson', 'title', 'position', + 'type', 'created_at', 'update_at', ) + ('txt',) read_only_fields = ( 'id', + 'type', 'created_at', 'update_at', ) + def get_type(self, object): + return 'text' + class TextSerializer(TextCreateSerializer): pass class ImageTextCreateSerializer(serializers.ModelSerializer): + type = serializers.SerializerMethodField() class Meta: model = ImageText @@ -114,22 +130,28 @@ class ImageTextCreateSerializer(serializers.ModelSerializer): 'position', 'img', 'txt', + 'type', 'created_at', 'update_at', ) read_only_fields = ( 'id', + 'type', 'created_at', 'update_at', ) + def get_type(self, object): + return 'image-text' + class ImageTextSerializer(ImageTextCreateSerializer): img = ImageObjectSerializer() class VideoCreateSerializer(serializers.ModelSerializer): + type = serializers.SerializerMethodField() class Meta: model = Video @@ -139,83 +161,101 @@ class VideoCreateSerializer(serializers.ModelSerializer): 'lesson', 'title', 'position', + 'type', 'created_at', 'update_at', ) + ('url',) read_only_fields = ( 'id', + 'type', 'created_at', 'update_at', ) + def get_type(self, object): + return 'video' + class VideoSerializer(VideoCreateSerializer): pass -class ContentSerializer(serializers.ModelSerializer): +class GalleryImageCreateSerializer(serializers.ModelSerializer): class Meta: - model = Content + model = GalleryImage fields = ( 'id', - 'course', - 'lesson', - 'title', - 'position', + 'gallery', + 'img', 'created_at', 'update_at', ) - def to_representation(self, obj): - if isinstance(obj, Image): - return ImageSerializer(obj, context=self.context).to_representation(obj) - elif isinstance(obj, Text): - return TextSerializer(obj, context=self.context).to_representation(obj) - elif isinstance(obj, ImageText): - return ImageTextSerializer(obj, context=self.context).to_representation(obj) - elif isinstance(obj, Video): - return VideoSerializer(obj, context=self.context).to_representation(obj) - return super(ContentSerializer, self).to_representation(obj) + read_only_fields = ( + 'id', + 'created_at', + 'update_at', + ) + + +class GalleryImageSerializer(GalleryImageCreateSerializer): + img = ImageObjectSerializer() class GallerySerializer(serializers.ModelSerializer): + type = serializers.SerializerMethodField() + gallery_images = GalleryImageSerializer(many=True) class Meta: model = Gallery fields = ( 'id', + 'course', + 'lesson', 'title', + 'position', + 'gallery_images', + 'type', 'created_at', 'update_at', ) read_only_fields = ( 'id', + 'type', 'created_at', 'update_at', ) + def get_type(self, object): + return 'images' -class GalleryImageCreateSerializer(serializers.ModelSerializer): + +class ContentSerializer(serializers.ModelSerializer): class Meta: - model = GalleryImage + model = Content fields = ( 'id', - 'gallery', - 'img', - 'created_at', - 'update_at', - ) - - read_only_fields = ( - 'id', + 'course', + 'lesson', + 'title', + 'position', 'created_at', 'update_at', ) - -class GalleryImageSerializer(GalleryImageCreateSerializer): - img = ImageObjectSerializer() + def to_representation(self, obj): + if isinstance(obj, Image): + return ImageSerializer(obj, context=self.context).to_representation(obj) + elif isinstance(obj, Text): + return TextSerializer(obj, context=self.context).to_representation(obj) + elif isinstance(obj, ImageText): + return ImageTextSerializer(obj, context=self.context).to_representation(obj) + elif isinstance(obj, Video): + return VideoSerializer(obj, context=self.context).to_representation(obj) + elif isinstance(obj, Gallery): + return GallerySerializer(obj, context=self.context).to_representation(obj) + return super(ContentSerializer, self).to_representation(obj) diff --git a/api/v1/serializers/course.py b/api/v1/serializers/course.py index ba6d73c7..3f78a71f 100644 --- a/api/v1/serializers/course.py +++ b/api/v1/serializers/course.py @@ -76,6 +76,7 @@ class CourseCreateSerializer(serializers.ModelSerializer): required=False, ) materials = MaterialSerializer(many=True, required=False) + gallery = GallerySerializer() class Meta: model = Course @@ -110,12 +111,7 @@ class CourseCreateSerializer(serializers.ModelSerializer): 'update_at', ) - def create(self, validated_data): - materials = validated_data.pop('materials', []) - content = validated_data.pop('content', []) - - course = super().create(validated_data) - + def dispatch_content(self, course, content, materials): for c in content: if c['type'] == 'text': if 'id' in c['data'] and c['data']['id']: @@ -134,14 +130,14 @@ class CourseCreateSerializer(serializers.ModelSerializer): ) elif c['type'] == 'image': if 'id' in c['data'] and c['data']['id']: - i = Image.objects.get(id=c['data']['id']) - i.position = c['data']['position'] - i.title = c['data']['title'] - i.course = course - i.img = ImageObject.objects.get(id=c['data']['img']) - i.save() + image = Image.objects.get(id=c['data']['id']) + image.position = c['data']['position'] + image.title = c['data']['title'] + image.course = course + image.img = ImageObject.objects.get(id=c['data']['img']) + image.save() else: - i = Image.objects.create( + image = Image.objects.create( position=c['data']['position'], title=c['data']['title'], course=course, @@ -179,56 +175,156 @@ class CourseCreateSerializer(serializers.ModelSerializer): course=course, url=c['data']['url'], ) - for material in materials: - if 'id' in material and material['id']: - m = Material.objects.get(id=material['id']) - m.title = material['title'] - m.cover = ImageObject.objects.get(id=material['cover']) - m.short_description = material['short_description'] - m.save() + elif c['type'] == 'images': + if 'id' in c['data'] and c['data']['id']: + g = Gallery.objects.get(id=c['data']['id']) + g.position = c['data']['position'] + g.title = c['data']['title'] + g.save() + if 'images' in c['data']: + for image in c['data']['images']: + gi = GalleryImage.objects.create( + gallery=g, + img=ImageObject.objects.get(id=image['id']) + ) else: - m = Material.objects.create( - title=material['title'], - cover=ImageObject.objects.get(id=material['cover']), - short_description=material['short_description'], + g = Gallery.objects.create( + position=c['data']['position'], + title=c['data']['title'], ) + if 'images' in c['data']: + for image in c['data']['images']: + gi = GalleryImage.objects.create( + gallery=g, + img=ImageObject.objects.get(id=image['id']), + ) + + for material in materials: + if 'id' in material and material['id']: + m = Material.objects.get(id=material['id']) + m.title = material['title'] + m.cover = ImageObject.objects.get(id=material['cover']) + m.short_description = material['short_description'] + m.save() + else: + m = Material.objects.create( + title=material['title'], + cover=ImageObject.objects.get(id=material['cover']), + short_description=material['short_description'], + ) + course.materials.add(m) + + def dispatch_gallery(self, course, gallery): + if gallery: + if 'id' in gallery and gallery['id']: + g = Gallery.objects.get(id=gallery['id']) + g.title = gallery.get('title', g.title) + g.course = course + g.position = 0 + g.save() + else: + g = Gallery.objects.create( + title=gallery.get('title', ''), + course=course, + position=0, + ) + if 'images' in gallery: + for image in gallery['images']: + if 'id' in image and image['id']: + gi = GalleryImage.objects.get(id=image['id']) + gi.gallery = g + gi.img = image['img'] + gi.save() + else: + gi = GalleryImage.objects.create( + gallery=g, + img=image['img'], + ) + course.gallery = g + course.save() + + def create(self, validated_data): + content = validated_data.pop('content', []) + materials = validated_data.pop('materials', []) + gallery = validated_data.pop('gallery', {}) + course = super().create(validated_data) + self.dispatch_content(course, content, materials) + self.dispatch_gallery(course, gallery) return course def update(self, instance, validated_data): - materials = validated_data.pop('materials', []) content = validated_data.pop('content', []) - + materials = validated_data.pop('materials', []) + gallery = validated_data.pop('gallery', {}) course = super().update(instance, validated_data) + self.dispatch_content(course, content, materials) + self.dispatch_gallery(course, gallery) + return course + +class CourseSerializer(CourseCreateSerializer): + category = CategorySerializer() + materials = MaterialSerializer(many=True) + cover = ImageObjectSerializer() + gallery = GallerySerializer() + content = ContentSerializer(many=True) + + +class LessonCreateSerializer(serializers.ModelSerializer): + content = serializers.ListSerializer( + child=ContentCreateSerializer(), + required=False, + ) + + class Meta: + model = Lesson + fields = ( + 'id', + 'title', + 'short_description', + 'course', + 'cover', + 'content', + 'created_at', + 'update_at', + ) + + read_only_fields = ( + 'id', + 'created_at', + 'update_at', + ) + + def dispatch_content(self, lesson, validated_data, content): for c in content: if c['type'] == 'text': if 'id' in c['data'] and c['data']['id']: t = Text.objects.get(id=c['data']['id']) t.position = c['data']['position'] t.title = c['data']['title'] - t.course = course + t.lesson = lesson t.txt = c['data']['txt'] t.save() else: t = Text.objects.create( position=c['data']['position'], title=c['data']['title'], - course=course, + lesson=lesson, txt=c['data']['txt'], ) elif c['type'] == 'image': if 'id' in c['data'] and c['data']['id']: - i = Image.objects.get(id=c['data']['id']) - i.position = c['data']['position'] - i.title = c['data']['title'] - i.course = course - i.img = ImageObject.objects.get(id=c['data']['img']) - i.save() + image = Image.objects.get(id=c['data']['id']) + image.position = c['data']['position'] + image.title = c['data']['title'] + image.lesson = lesson + image.img = ImageObject.objects.get(id=c['data']['img']) + image.save() else: - i = Image.objects.create( + image = Image.objects.create( position=c['data']['position'], title=c['data']['title'], - course=course, + lesson=lesson, img=ImageObject.objects.get(id=c['data']['img']), ) elif c['type'] == 'image-text': @@ -236,7 +332,7 @@ class CourseCreateSerializer(serializers.ModelSerializer): it = ImageText.objects.get(id=c['data']['id']) it.position = c['data']['position'] it.title = c['data']['title'] - it.course = course + it.lesson = lesson it.img = ImageObject.objects.get(id=c['data']['img']) it.txt = c['data']['txt'] it.save() @@ -244,7 +340,7 @@ class CourseCreateSerializer(serializers.ModelSerializer): it = ImageText.objects.create( position=c['data']['position'], title=c['data']['title'], - course=course, + lesson=lesson, img=ImageObject.objects.get(id=c['data']['img']), txt=c['data']['txt'], ) @@ -253,64 +349,54 @@ class CourseCreateSerializer(serializers.ModelSerializer): v = Video.objects.get(id=c['data']['id']) v.position = c['data']['position'] v.title = c['data']['title'] - v.course = course + v.lesson = lesson v.url = c['data']['url'] v.save() else: v = Video.objects.create( position=c['data']['position'], title=c['data']['title'], - course=course, + lesson=lesson, url=c['data']['url'], ) - for material in materials: - if 'id' in material and material['id']: - m = Material.objects.get(id=material['id']) - m.title = material['title'] - m.cover = ImageObject.objects.get(id=material['cover']) - m.short_description = material['short_description'] - m.save() + elif c['type'] == 'images': + if 'id' in c['data'] and c['data']['id']: + g = Gallery.objects.get(id=c['data']['id']) + g.position = c['data']['position'] + g.title = c['data']['title'] + g.save() + if 'images' in c['data']: + for image in c['data']['images']: + gi = GalleryImage.objects.create( + gallery=g, + img=ImageObject.objects.get(id=image['id']), + ) else: - m = Material.objects.create( - title=material['title'], - cover=ImageObject.objects.get(id=material['cover']), - short_description=material['short_description'], + g = Gallery.objects.create( + position=c['data']['position'], + title=c['data']['title'], ) - course.materials.add(m) - return course - - -class CourseSerializer(CourseCreateSerializer): - category = CategorySerializer() - materials = MaterialSerializer(many=True) - cover = ImageObjectSerializer() - gallery = GallerySerializer() - content = ContentSerializer(many=True) - + if 'images' in c['data']: + for image in c['data']['images']: + gi = GalleryImage.objects.create( + gallery=g, + img=ImageObject.objects.get(id=image['id']), + ) -class LessonCreateSerializer(serializers.ModelSerializer): - - class Meta: - model = Lesson - fields = ( - 'id', - 'title', - 'short_description', - 'course', - 'cover', - 'content', - 'created_at', - 'update_at', - ) + def create(self, validated_data): + content = validated_data.pop('content', []) + lesson = super().create(validated_data) + self.dispatch_content(lesson, validated_data, content) + return lesson - read_only_fields = ( - 'id', - 'content', - 'created_at', - 'update_at', - ) + def update(self, instance, validated_data): + content = validated_data.pop('content', []) + lesson = super().update(instance, validated_data) + self.dispatch_content(lesson, validated_data, content) + return lesson class LessonSerializer(LessonCreateSerializer): course = CourseSerializer() cover = ImageObjectSerializer() + content = ContentSerializer() diff --git a/api/v1/views.py b/api/v1/views.py index ea96b54e..043c0fb8 100644 --- a/api/v1/views.py +++ b/api/v1/views.py @@ -73,7 +73,7 @@ class CategoryViewSet(ExtendedModelViewSet): class CourseViewSet(ExtendedModelViewSet): queryset = Course.objects.select_related( - 'author', 'category', 'cover', + 'author', 'category', 'cover', 'gallery', ).prefetch_related( 'likes', 'materials', 'content', ).all() diff --git a/apps/content/admin.py b/apps/content/admin.py index c30a3a38..34b19204 100644 --- a/apps/content/admin.py +++ b/apps/content/admin.py @@ -48,6 +48,11 @@ class VideoAdmin(ContentChildAdmin): base_model = Video +@admin.register(Gallery) +class GalleryAdmin(ContentChildAdmin): + base_model = Gallery + + @admin.register(Content) class ContentAdmin(PolymorphicParentModelAdmin): base_model = Content @@ -55,15 +60,11 @@ class ContentAdmin(PolymorphicParentModelAdmin): Image, Text, ImageText, - Video + Video, + GalleryAdmin, ) -@admin.register(Gallery) -class GalleryAdmin(admin.ModelAdmin): - pass - - @admin.register(GalleryImage) class GalleryImageAdmin(admin.ModelAdmin): pass diff --git a/apps/content/migrations/0012_auto_20180209_1847.py b/apps/content/migrations/0012_auto_20180209_1847.py new file mode 100644 index 00000000..d99887a1 --- /dev/null +++ b/apps/content/migrations/0012_auto_20180209_1847.py @@ -0,0 +1,19 @@ +# Generated by Django 2.0.2 on 2018-02-09 18:47 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('content', '0011_auto_20180209_1549'), + ] + + operations = [ + migrations.AlterField( + model_name='galleryimage', + name='gallery', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='gallery_images', to='content.Gallery', verbose_name='Галерея'), + ), + ] diff --git a/apps/content/migrations/0013_auto_20180212_0537.py b/apps/content/migrations/0013_auto_20180212_0537.py new file mode 100644 index 00000000..bb595917 --- /dev/null +++ b/apps/content/migrations/0013_auto_20180212_0537.py @@ -0,0 +1,40 @@ +# Generated by Django 2.0.2 on 2018-02-12 05:37 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('content', '0012_auto_20180209_1847'), + ] + + operations = [ + migrations.AlterModelOptions( + name='gallery', + options={'base_manager_name': 'objects'}, + ), + migrations.RemoveField( + model_name='gallery', + name='created_at', + ), + migrations.RemoveField( + model_name='gallery', + name='id', + ), + migrations.RemoveField( + model_name='gallery', + name='title', + ), + migrations.RemoveField( + model_name='gallery', + name='update_at', + ), + migrations.AddField( + model_name='gallery', + name='content_ptr', + field=models.OneToOneField(auto_created=True, default=1, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='content.Content'), + preserve_default=False, + ), + ] diff --git a/apps/content/models.py b/apps/content/models.py index 906ee3f9..6f0932bc 100644 --- a/apps/content/models.py +++ b/apps/content/models.py @@ -66,22 +66,14 @@ class Video(Content): url = models.URLField('Ссылка') -class Gallery(models.Model): - title = models.CharField('Заголовок', max_length=100, default='') - - 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 Gallery(Content): + pass class GalleryImage(models.Model): gallery = models.ForeignKey( Gallery, on_delete=models.CASCADE, - verbose_name='Галерея' + verbose_name='Галерея', related_name='gallery_images' ) img = models.ForeignKey( ImageObject, related_name='gallery_images', diff --git a/apps/course/migrations/0030_auto_20180212_0537.py b/apps/course/migrations/0030_auto_20180212_0537.py new file mode 100644 index 00000000..c96cca33 --- /dev/null +++ b/apps/course/migrations/0030_auto_20180212_0537.py @@ -0,0 +1,19 @@ +# Generated by Django 2.0.2 on 2018-02-12 05:37 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('course', '0029_auto_20180209_0911'), + ] + + operations = [ + migrations.AlterField( + model_name='course', + name='gallery', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='results_gallery', to='content.Gallery', verbose_name='Галерея работ'), + ), + ] diff --git a/apps/course/models.py b/apps/course/models.py index efca4682..0eb620cb 100644 --- a/apps/course/models.py +++ b/apps/course/models.py @@ -42,9 +42,11 @@ class Course(models.Model): User, on_delete=models.SET_NULL, null=True, blank=True) title = models.CharField('Название курса', max_length=100, db_index=True) short_description = models.TextField( - 'Краткое описание курса', db_index=True) + 'Краткое описание курса', db_index=True + ) from_author = models.TextField( - 'От автора', default='', null=True, blank=True) + 'От автора', default='', null=True, blank=True + ) cover = models.ForeignKey( ImageObject, related_name='course_covers', verbose_name='Обложка курса', on_delete=models.CASCADE, @@ -63,12 +65,14 @@ class Course(models.Model): duration = models.IntegerField('Продолжительность курса', default=0) is_featured = models.BooleanField(default=False) status = models.PositiveSmallIntegerField( - 'Статус', default=0, choices=STATUS_CHOICES) + 'Статус', default=0, choices=STATUS_CHOICES + ) likes = models.ManyToManyField(Like, blank=True) materials = models.ManyToManyField('Material', blank=True) gallery = models.ForeignKey( Gallery, verbose_name='Галерея работ', on_delete=models.CASCADE, null=True, blank=True, + related_name='results_gallery', ) created_at = models.DateTimeField(auto_now_add=True) @@ -141,7 +145,8 @@ class Lesson(models.Model): title = models.CharField('Название урока', max_length=100) short_description = models.TextField('Краткое описание урока') course = models.ForeignKey( - Course, on_delete=models.CASCADE, related_name='lessons') + Course, on_delete=models.CASCADE, related_name='lessons' + ) cover = models.ForeignKey( ImageObject, related_name='lesson_covers', verbose_name='Обложка урока', on_delete=models.CASCADE, @@ -207,7 +212,8 @@ class Comment(PolymorphicMPTTModel): class CourseComment(Comment): course = models.ForeignKey( - Course, on_delete=models.CASCADE, related_name='comments') + Course, on_delete=models.CASCADE, related_name='comments' + ) class Meta(Comment.Meta): verbose_name = 'Комментарий курса' @@ -216,7 +222,8 @@ class CourseComment(Comment): class LessonComment(Comment): lesson = models.ForeignKey( - Lesson, on_delete=models.CASCADE, related_name='comments') + Lesson, on_delete=models.CASCADE, related_name='comments' + ) class Meta(Comment.Meta): verbose_name = 'Комментарий урока' diff --git a/project/settings.py b/project/settings.py index a450d5b2..10c5e93b 100644 --- a/project/settings.py +++ b/project/settings.py @@ -186,13 +186,16 @@ ACTIVE_LINK_STRICT = True # DRF settings REST_FRAMEWORK = { - 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.TokenAuthentication', 'rest_framework.authentication.SessionAuthentication', - ], - 'DEFAULT_PERMISSION_CLASSES': [ + ), + 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.AllowAny', - ], + ), + 'DEFAULT_RENDERER_CLASSES': ( + 'rest_framework.renderers.JSONRenderer', + ), 'DEFAULT_FILTER_BACKENDS': ( 'django_filters.rest_framework.DjangoFilterBackend', 'rest_framework.filters.SearchFilter',