diff --git a/api/v1/serializers/course.py b/api/v1/serializers/course.py index dfa14245..833dd98a 100644 --- a/api/v1/serializers/course.py +++ b/api/v1/serializers/course.py @@ -12,6 +12,8 @@ from apps.content.models import ( Gallery, GalleryImage, ImageObject, ) +from .mixins import DispatchContentMixin, DispatchGalleryMixin, DispatchMaterialMixin + class MaterialCreateSerializer(serializers.ModelSerializer): @@ -69,7 +71,11 @@ class CategorySerializer(serializers.ModelSerializer): ) -class CourseCreateSerializer(serializers.ModelSerializer): +class CourseCreateSerializer(DispatchContentMixin, + DispatchGalleryMixin, + DispatchMaterialMixin, + serializers.ModelSerializer + ): slug = serializers.SlugField(allow_unicode=True, required=False) content = serializers.ListSerializer( child=ContentCreateSerializer(), @@ -100,6 +106,7 @@ class CourseCreateSerializer(serializers.ModelSerializer): 'materials', 'created_at', 'update_at', + 'deactivated_at', 'content', 'gallery', ) @@ -109,146 +116,16 @@ class CourseCreateSerializer(serializers.ModelSerializer): 'url', 'created_at', 'update_at', + 'deactivated_at', ) - def dispatch_content(self, course, content, materials): - 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.txt = c['data']['txt'] - t.save() - else: - t = Text.objects.create( - position=c['data']['position'], - title=c['data']['title'], - course=course, - txt=c['data']['txt'], - ) - elif c['type'] == 'image': - if 'id' in c['data'] and c['data']['id']: - 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: - image = Image.objects.create( - position=c['data']['position'], - title=c['data']['title'], - course=course, - img=ImageObject.objects.get(id=c['data']['img']), - ) - elif c['type'] == 'image-text': - if 'id' in c['data'] and c['data']['id']: - it = ImageText.objects.get(id=c['data']['id']) - it.position = c['data']['position'] - it.title = c['data']['title'] - it.course = course - it.img = ImageObject.objects.get(id=c['data']['img']) - it.txt = c['data']['txt'] - it.save() - else: - it = ImageText.objects.create( - position=c['data']['position'], - title=c['data']['title'], - course=course, - img=ImageObject.objects.get(id=c['data']['img']), - txt=c['data']['txt'], - ) - elif c['type'] == 'video': - if 'id' in c['data'] and c['data']['id']: - v = Video.objects.get(id=c['data']['id']) - v.position = c['data']['position'] - v.title = c['data']['title'] - v.course = course - v.url = c['data']['url'] - v.save() - else: - v = Video.objects.create( - position=c['data']['position'], - title=c['data']['title'], - course=course, - url=c['data']['url'], - ) - elif c['type'] == 'images': - if 'id' in c['data'] and c['data']['id']: - g = Gallery.objects.get(id=c['data']['id']) - g.course = course - 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['img']) - ) - else: - g = Gallery.objects.create( - course=course, - 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['img']), - ) - - 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.position = 0 - g.save() - else: - g = Gallery.objects.create( - title=gallery.get('title', ''), - 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_content(course, content) + self.dispatch_materials(course, materials) self.dispatch_gallery(course, gallery) return course @@ -257,10 +134,14 @@ class CourseCreateSerializer(serializers.ModelSerializer): materials = validated_data.pop('materials', []) gallery = validated_data.pop('gallery', {}) course = super().update(instance, validated_data) + self.dispatch_materials(course, materials) self.dispatch_content(course, content, materials) self.dispatch_gallery(course, gallery) return course + def to_representation(self, instance): + return CourseSerializer(instance, context=self.context).to_representation(instance) + class CourseSerializer(CourseCreateSerializer): category = CategorySerializer() @@ -287,87 +168,93 @@ class LessonCreateSerializer(serializers.ModelSerializer): 'content', 'created_at', 'update_at', + 'deactivated_at', ) read_only_fields = ( 'id', 'created_at', 'update_at', + 'deactivated_at', ) - def dispatch_content(self, lesson, validated_data, content): + def dispatch_content(self, lesson, 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'] + if 'type' not in c or not c['type'] or 'data' not in c or not c['data']: + continue + ctype = c['type'] + cdata = c['data'] + if ctype == 'text': + if 'id' in cdata and cdata['id']: + t = Text.objects.get(id=cdata['id']) + t.position = cdata['position'] + t.title = cdata['title'] t.lesson = lesson - t.txt = c['data']['txt'] + t.txt = cdata['txt'] t.save() else: t = Text.objects.create( - position=c['data']['position'], - title=c['data']['title'], + position=cdata['position'], + title=cdata['title'], lesson=lesson, - txt=c['data']['txt'], + txt=cdata['txt'], ) - elif c['type'] == 'image': - if 'id' in c['data'] and c['data']['id']: - image = Image.objects.get(id=c['data']['id']) - image.position = c['data']['position'] - image.title = c['data']['title'] + elif ctype == 'image': + if 'id' in cdata and cdata['id']: + image = Image.objects.get(id=cdata['id']) + image.position = cdata['position'] + image.title = cdata['title'] image.lesson = lesson - image.img = ImageObject.objects.get(id=c['data']['img']) + image.img = ImageObject.objects.get(id=cdata['img']) image.save() else: image = Image.objects.create( - position=c['data']['position'], - title=c['data']['title'], + position=cdata['position'], + title=cdata['title'], lesson=lesson, - img=ImageObject.objects.get(id=c['data']['img']), + img=ImageObject.objects.get(id=cdata['img']), ) - elif c['type'] == 'image-text': - if 'id' in c['data'] and c['data']['id']: - it = ImageText.objects.get(id=c['data']['id']) - it.position = c['data']['position'] - it.title = c['data']['title'] + elif ctype == 'image-text': + if 'id' in cdata and cdata['id']: + it = ImageText.objects.get(id=cdata['id']) + it.position = cdata['position'] + it.title = cdata['title'] it.lesson = lesson - it.img = ImageObject.objects.get(id=c['data']['img']) - it.txt = c['data']['txt'] + it.img = ImageObject.objects.get(id=cdata['img']) + it.txt = cdata['txt'] it.save() else: it = ImageText.objects.create( - position=c['data']['position'], - title=c['data']['title'], + position=cdata['position'], + title=cdata['title'], lesson=lesson, - img=ImageObject.objects.get(id=c['data']['img']), - txt=c['data']['txt'], + img=ImageObject.objects.get(id=cdata['img']), + txt=cdata['txt'], ) - elif c['type'] == 'video': - if 'id' in c['data'] and c['data']['id']: - v = Video.objects.get(id=c['data']['id']) - v.position = c['data']['position'] - v.title = c['data']['title'] + elif ctype == 'video': + if 'id' in cdata and cdata['id']: + v = Video.objects.get(id=cdata['id']) + v.position = cdata['position'] + v.title = cdata['title'] v.lesson = lesson - v.url = c['data']['url'] + v.url = cdata['url'] v.save() else: v = Video.objects.create( - position=c['data']['position'], - title=c['data']['title'], + position=cdata['position'], + title=cdata['title'], lesson=lesson, - url=c['data']['url'], + url=cdata['url'], ) - 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'] + elif ctype == 'images': + if 'id' in cdata and cdata['id']: + g = Gallery.objects.get(id=cdata['id']) + g.position = cdata['position'] + g.title = cdata['title'] g.lesson = lesson g.save() - if 'images' in c['data']: - for image in c['data']['images']: + if 'images' in cdata: + for image in cdata['images']: gi = GalleryImage.objects.create( gallery=g, img=ImageObject.objects.get(id=image['img']), @@ -375,11 +262,11 @@ class LessonCreateSerializer(serializers.ModelSerializer): else: g = Gallery.objects.create( lesson=lesson, - position=c['data']['position'], - title=c['data']['title'], + position=cdata['position'], + title=cdata['title'], ) - if 'images' in c['data']: - for image in c['data']['images']: + if 'images' in cdata: + for image in cdata['images']: gi = GalleryImage.objects.create( gallery=g, img=ImageObject.objects.get(id=image['img']), @@ -388,17 +275,17 @@ class LessonCreateSerializer(serializers.ModelSerializer): def create(self, validated_data): content = validated_data.pop('content', []) lesson = super().create(validated_data) - self.dispatch_content(lesson, validated_data, content) + self.dispatch_content(lesson, content) return lesson def update(self, instance, validated_data): content = validated_data.pop('content', []) lesson = super().update(instance, validated_data) - self.dispatch_content(lesson, validated_data, content) + self.dispatch_content(lesson, content) return lesson class LessonSerializer(LessonCreateSerializer): course = CourseSerializer() cover = ImageObjectSerializer() - content = ContentSerializer() + content = ContentSerializer(many=True) diff --git a/api/v1/serializers/mixins.py b/api/v1/serializers/mixins.py new file mode 100644 index 00000000..e709002e --- /dev/null +++ b/api/v1/serializers/mixins.py @@ -0,0 +1,152 @@ +from apps.course.models import Category, Course, Material, Lesson, Like + +from apps.content.models import ( + Content, Image, Text, ImageText, Video, + Gallery, GalleryImage, ImageObject, +) + + +class DispatchContentMixin(object): + + def dispatch_content(self, course, content): + for c in content: + if 'type' not in c or not c['type'] or 'data' not in c or not c['data']: + continue + ctype = c['type'] + cdata = c['data'] + if ctype == 'text': + if 'id' in cdata and cdata['id']: + t = Text.objects.get(id=cdata['id']) + t.position = cdata['position'] + t.title = cdata['title'] + t.course = course + t.txt = cdata['txt'] + t.save() + else: + t = Text.objects.create( + position=cdata['position'], + title=cdata['title'], + course=course, + txt=cdata['txt'], + ) + elif ctype == 'image': + if 'id' in cdata and cdata['id']: + image = Image.objects.get(id=cdata['id']) + image.position = cdata['position'] + image.title = cdata['title'] + image.course = course + image.img = ImageObject.objects.get(id=cdata['img']) + image.save() + else: + image = Image.objects.create( + position=cdata['position'], + title=cdata['title'], + course=course, + img=ImageObject.objects.get(id=cdata['img']), + ) + elif ctype == 'image-text': + if 'id' in cdata and cdata['id']: + it = ImageText.objects.get(id=cdata['id']) + it.position = cdata['position'] + it.title = cdata['title'] + it.course = course + it.img = ImageObject.objects.get(id=cdata['img']) + it.txt = cdata['txt'] + it.save() + else: + it = ImageText.objects.create( + position=cdata['position'], + title=cdata['title'], + course=course, + img=ImageObject.objects.get(id=cdata['img']), + txt=cdata['txt'], + ) + elif ctype == 'video': + if 'id' in cdata and cdata['id']: + v = Video.objects.get(id=cdata['id']) + v.position = cdata['position'] + v.title = cdata['title'] + v.course = course + v.url = cdata['url'] + v.save() + else: + v = Video.objects.create( + position=cdata['position'], + title=cdata['title'], + course=course, + url=cdata['url'], + ) + elif ctype == 'images': + if 'id' in cdata and cdata['id']: + g = Gallery.objects.get(id=cdata['id']) + g.course = course + g.position = cdata['position'] + g.title = cdata['title'] + g.save() + if 'images' in cdata: + for image in cdata['images']: + gi = GalleryImage.objects.create( + gallery=g, + img=ImageObject.objects.get(id=image['img']) + ) + else: + g = Gallery.objects.create( + course=course, + position=cdata['position'], + title=cdata['title'], + ) + if 'images' in cdata: + for image in cdata['images']: + gi = GalleryImage.objects.create( + gallery=g, + img=ImageObject.objects.get(id=image['img']), + ) + + +class DispatchMaterialMixin(object): + + def dispatch_materials(self, course, materials): + 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) + + +class DispatchGalleryMixin(object): + + 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.position = 0 + g.save() + else: + g = Gallery.objects.create( + title=gallery.get('title', ''), + 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() diff --git a/apps/content/admin.py b/apps/content/admin.py index 34b19204..555ba173 100644 --- a/apps/content/admin.py +++ b/apps/content/admin.py @@ -23,6 +23,7 @@ class ImageObjectAdmin(admin.ModelAdmin): class ContentChildAdmin(PolymorphicChildModelAdmin): base_model = Content + show_in_index = True base_fieldsets = ( (None, {'fields': ('course', 'lesson', 'title', 'position',)}), ) @@ -56,12 +57,13 @@ class GalleryAdmin(ContentChildAdmin): @admin.register(Content) class ContentAdmin(PolymorphicParentModelAdmin): base_model = Content + polymorphic_list = True child_models = ( Image, Text, ImageText, Video, - GalleryAdmin, + # GalleryAdmin, ) diff --git a/apps/content/models.py b/apps/content/models.py index 6f0932bc..00c15312 100644 --- a/apps/content/models.py +++ b/apps/content/models.py @@ -40,7 +40,10 @@ class Content(PolymorphicModel): class Meta: verbose_name = 'Контент' verbose_name_plural = 'Контент' - ordering = ('-created_at',) + ordering = ('position', '-created_at',) + + def ctype(self): + return self.__class__.__name__.lower() class Image(Content): @@ -65,6 +68,9 @@ class ImageText(Content): class Video(Content): url = models.URLField('Ссылка') + def video_index(self): + return self.url.split('/')[-1] + class Gallery(Content): pass diff --git a/apps/course/migrations/0031_auto_20180213_0906.py b/apps/course/migrations/0031_auto_20180213_0906.py new file mode 100644 index 00000000..e0880533 --- /dev/null +++ b/apps/course/migrations/0031_auto_20180213_0906.py @@ -0,0 +1,23 @@ +# Generated by Django 2.0.2 on 2018-02-13 09:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('course', '0030_auto_20180212_0537'), + ] + + operations = [ + migrations.AddField( + model_name='course', + name='deactivated_at', + field=models.DateTimeField(blank=True, default=None, null=True), + ), + migrations.AddField( + model_name='lesson', + name='deactivated_at', + field=models.DateTimeField(blank=True, default=None, null=True), + ), + ] diff --git a/apps/course/models.py b/apps/course/models.py index 0eb620cb..b0a2045d 100644 --- a/apps/course/models.py +++ b/apps/course/models.py @@ -7,6 +7,8 @@ from django.contrib.auth import get_user_model from django.urls import reverse_lazy from polymorphic_tree.models import PolymorphicMPTTModel, PolymorphicTreeForeignKey +from project.mixins import BaseModel, DeactivatedMixin + from .manager import CategoryQuerySet from apps.content.models import ImageObject, Gallery @@ -25,7 +27,7 @@ def default_slug(): return str(uuid4()) -class Course(models.Model): +class Course(BaseModel, DeactivatedMixin): PENDING = 0 PUBLISHED = 1 ARCHIVED = 2 @@ -141,7 +143,7 @@ class Category(models.Model): ordering = ['title'] -class Lesson(models.Model): +class Lesson(BaseModel, DeactivatedMixin): title = models.CharField('Название урока', max_length=100) short_description = models.TextField('Краткое описание урока') course = models.ForeignKey( diff --git a/apps/course/templates/course/content/gallery.html b/apps/course/templates/course/content/gallery.html new file mode 100644 index 00000000..771df8b6 --- /dev/null +++ b/apps/course/templates/course/content/gallery.html @@ -0,0 +1,19 @@ +{% if results %} +
Галерея итогов обучения
+
+ {% for image in course.gallery.gallery_images.all %} +
+ +
+ {% endfor %} +
+{% else %} +
{{ content.title }}
+
+ {% for image in content.gallery_images.all %} +
+ +
+ {% endfor %} +
+{% endif %} \ No newline at end of file diff --git a/apps/course/templates/course/content/image.html b/apps/course/templates/course/content/image.html new file mode 100644 index 00000000..5daeba4e --- /dev/null +++ b/apps/course/templates/course/content/image.html @@ -0,0 +1,6 @@ +
+ {{ content.title }} +
+
+ +
\ No newline at end of file diff --git a/apps/course/templates/course/content/imagetext.html b/apps/course/templates/course/content/imagetext.html new file mode 100644 index 00000000..f2118327 --- /dev/null +++ b/apps/course/templates/course/content/imagetext.html @@ -0,0 +1,9 @@ +
+ {{ content.title }} +
+
+ {{ content.txt }} +
+
+ +
\ No newline at end of file diff --git a/apps/course/templates/course/content/text.html b/apps/course/templates/course/content/text.html new file mode 100644 index 00000000..40b9ae0e --- /dev/null +++ b/apps/course/templates/course/content/text.html @@ -0,0 +1,6 @@ +
+{{ content.title }} +
+
+{{ content.txt }} +
\ No newline at end of file diff --git a/apps/course/templates/course/content/video.html b/apps/course/templates/course/content/video.html new file mode 100644 index 00000000..df9893f6 --- /dev/null +++ b/apps/course/templates/course/content/video.html @@ -0,0 +1,13 @@ +
+ {{ content.title }} +
+
+ {% if 'youtube.com' in content.url or 'youtu.be' in content.url %} + + {% elif 'vimeo.com' in content.url %} + + {% endif %} +
\ No newline at end of file diff --git a/apps/course/templates/course/course.html b/apps/course/templates/course/course.html index 2176d023..1b927e5d 100644 --- a/apps/course/templates/course/course.html +++ b/apps/course/templates/course/course.html @@ -3,6 +3,7 @@ {% load data_liked from data_liked %} {% load rupluralize from plural %} {% block title %}{{ course.title }} - {{ block.super }}{% endblock title %} +{% block description %}{{ course.title }} - {{ block.super }}{% endblock description%} {% block content %}
@@ -195,6 +196,15 @@
{% endif %}
+{% for content in course.content.all %} +
+
+ {% with template="course/content/"|add:content.ctype|add:".html" %} + {% include template %} + {% endwith %} +
+
+{% endfor %} {% if user.is_authenticated %}
@@ -254,45 +264,7 @@ {% endif %}
-
Галерея итогов обучения
-
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
+ {% include "course/content/gallery.html" with results=True %}
diff --git a/apps/course/templates/course/course_edit.html b/apps/course/templates/course/course_edit.html new file mode 100644 index 00000000..d1b36e89 --- /dev/null +++ b/apps/course/templates/course/course_edit.html @@ -0,0 +1,24 @@ +{% extends "templates/lilcity/index.html" %} +{% load static %} +{% block content %} + +
+
+ {{course}} + {{course.id}} +
+
+{% endblock content %} \ No newline at end of file diff --git a/apps/course/templates/course/lesson.html b/apps/course/templates/course/lesson.html index 5477ab49..151aeec1 100644 --- a/apps/course/templates/course/lesson.html +++ b/apps/course/templates/course/lesson.html @@ -35,7 +35,16 @@
-
+{% for content in lesson.content.all %} +
+
+ {% with template="course/content/"|add:content.ctype|add:".html" %} + {% include template %} + {% endwith %} +
+
+{% endfor %} +{% comment %}
Примеры техники
@@ -77,7 +86,7 @@
-
+ {% endcomment %}
Задавайте вопросы:
diff --git a/apps/course/views.py b/apps/course/views.py index cd5fd64a..1b79be28 100644 --- a/apps/course/views.py +++ b/apps/course/views.py @@ -1,8 +1,9 @@ from django.contrib.auth.decorators import login_required from django.db.models import Q from django.http import JsonResponse +from django.shortcuts import get_object_or_404 from django.template import loader, Context, Template -from django.views.generic import View, CreateView, DetailView, ListView +from django.views.generic import View, CreateView, DetailView, ListView, TemplateView from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_http_methods from .models import Course, Like, Lesson, CourseComment, LessonComment @@ -136,6 +137,19 @@ def lessoncomment(request, lesson_id): }) +class CourseEditView(TemplateView): + template_name = 'course/course_edit.html' + + def get(self, request, pk=None): + self.object = get_object_or_404(Course, pk=pk) + return super().get(request) + + def get_context_data(self): + context = super().get_context_data() + context['course'] = self.object + return context + + class CourseView(DetailView): model = Course context_object_name = 'course' @@ -146,6 +160,14 @@ class CourseView(DetailView): context['next'] = self.request.GET.get('next', None) return context + def get_queryset(self): + queryset = super().get_queryset().select_related( + 'author', 'category', + ).prefetch_related( + 'likes', 'materials', 'content', + ) + return queryset + class CoursesView(ListView): model = Course @@ -158,12 +180,17 @@ class CoursesView(ListView): if request.is_ajax(): context = self.get_context_data() template_name = self.get_template_names() - html = loader.render_to_string(template_name, context, request=request) + html = loader.render_to_string( + template_name, context, request=request) is_paginated = context.get('is_paginated') if is_paginated: page_obj = context.get('page_obj') - prev_url = request.path + '?page=' + str(page_obj.previous_page_number()) if page_obj.has_previous() else None - next_url = request.path + '?page=' + str(page_obj.next_page_number()) if page_obj.has_next() else None + prev_url = request.path + '?page=' + \ + str(page_obj.previous_page_number() + ) if page_obj.has_previous() else None + next_url = request.path + '?page=' + \ + str(page_obj.next_page_number() + ) if page_obj.has_next() else None else: prev_url = None next_url = None @@ -178,7 +205,7 @@ class CoursesView(ListView): def get_queryset(self): queryset = super().get_queryset().select_related( - 'author', 'category' + 'author', 'category', 'cover', ).prefetch_related( 'likes', 'materials', 'content', ) @@ -210,7 +237,8 @@ class SearchView(CoursesView): search_query = self.request.GET.get('q', None) queryset = super().get_queryset() if search_query: - query = Q(title__icontains=search_query) | Q(short_description__icontains=search_query) + query = Q(title__icontains=search_query) | Q( + short_description__icontains=search_query) queryset = queryset.filter(query) else: queryset = queryset.none() @@ -223,7 +251,8 @@ class SearchView(CoursesView): if 'is_paginated' in context and context['is_paginated']: page_obj = context.get('page_obj') context['page'] = page_obj.number - context['next_page'] = str(page_obj.next_page_number()) if page_obj.has_next() else None + context['next_page'] = str( + page_obj.next_page_number()) if page_obj.has_next() else None else: context['page'] = 1 context['next_page'] = None diff --git a/apps/user/fixtures/superuser.json b/apps/user/fixtures/superuser.json index a9118aba..eae528ed 100644 --- a/apps/user/fixtures/superuser.json +++ b/apps/user/fixtures/superuser.json @@ -26,7 +26,7 @@ "vkontakte": null, "fb_id": null, "fb_data": {}, - "is_email_proved": false, + "is_email_proved": true, "photo": "", "groups": [], "user_permissions": [] diff --git a/apps/user/migrations/0008_auto_20180212_0750.py b/apps/user/migrations/0008_auto_20180212_0750.py new file mode 100644 index 00000000..ccd9612e --- /dev/null +++ b/apps/user/migrations/0008_auto_20180212_0750.py @@ -0,0 +1,17 @@ +# Generated by Django 2.0.2 on 2018-02-12 07:50 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('user', '0007_auto_20180207_0808'), + ] + + operations = [ + migrations.AlterModelOptions( + name='user', + options={'ordering': ('-date_joined',), 'verbose_name': 'user', 'verbose_name_plural': 'users'}, + ), + ] diff --git a/apps/user/models.py b/apps/user/models.py index 5986e33c..db34018e 100644 --- a/apps/user/models.py +++ b/apps/user/models.py @@ -1,8 +1,15 @@ from django.db import models +from django.db.models.signals import post_save +from django.dispatch import receiver from django.contrib.auth.models import AbstractUser, UserManager from django.contrib.postgres import fields as pgfields from django.utils.translation import gettext_lazy as _ +from rest_framework.authtoken.models import Token + +from json import dumps +from api.v1 import serializers + class User(AbstractUser): USER_ROLE = 0 @@ -22,8 +29,10 @@ class User(AbstractUser): (FEMALE, 'Женщина'), ) email = models.EmailField(_('email address'), unique=True) - role = models.PositiveSmallIntegerField('Роль', default=0, choices=ROLE_CHOICES) - gender = models.CharField('Пол', max_length=1, default='n', choices=GENDER_CHOICES) + role = models.PositiveSmallIntegerField( + 'Роль', default=0, choices=ROLE_CHOICES) + gender = models.CharField( + 'Пол', max_length=1, default='n', choices=GENDER_CHOICES) birthday = models.DateField('День рождения', null=True, blank=True) country = models.CharField('Страна', max_length=50, null=True, blank=True) city = models.CharField('Город', max_length=85, null=True, blank=True) @@ -36,7 +45,9 @@ class User(AbstractUser): vkontakte = models.URLField('ВКонтакте', default='', null=True, blank=True) fb_id = models.BigIntegerField(null=True, blank=True, unique=True) fb_data = pgfields.JSONField(default={}, null=True, blank=True) - is_email_proved = models.BooleanField('Верифицирован по email', default=False) + is_email_proved = models.BooleanField( + 'Верифицирован по email', default=False + ) photo = models.ImageField('Фото', null=True, blank=True, upload_to='users') USERNAME_FIELD = 'email' @@ -44,3 +55,23 @@ class User(AbstractUser): class Meta(AbstractUser.Meta): ordering = ('-date_joined',) + + def serialized(self): + user_data = serializers.user.UserSerializer(instance=self).data + user_data = dumps(user_data, ensure_ascii=False) + return user_data + + +@receiver(post_save, sender=User) +def create_auth_token(sender, instance=None, created=False, **kwargs): + if ( + (instance.is_active or instance.fb_id) and + instance.role in [User.AUTHOR_ROLE, User.ADMIN_ROLE] and not + hasattr(instance, 'auth_token') + ): + Token.objects.create(user=instance) + elif ( + not (instance.is_active or instance.fb_id) or + instance.role not in [User.AUTHOR_ROLE, User.ADMIN_ROLE] + ) and hasattr(instance, 'auth_token'): + instance.auth_token.delete() diff --git a/project/mixins.py b/project/mixins.py new file mode 100644 index 00000000..6c6681f3 --- /dev/null +++ b/project/mixins.py @@ -0,0 +1,38 @@ +from django.db import models +from django.utils import timezone + + +class BaseModel(models.Model): + + class Meta: + abstract = True + + +class DeactivatedQueryset(models.Manager): + + def allow_delete(self): + super().delete() + + def delete(self): + count = self.count() + self.filter(deactivated_at__isnull=True).update( + deactivated_at=timezone.now() + ) + return (count, None) + + +class DeactivatedMixin(models.Model): + deactivated_at = models.DateTimeField(null=True, blank=True, default=None) + + objects = DeactivatedQueryset() + + class Meta: + abstract = True + + def allow_delete(self, using=None, keep_parents=False): + super().delete(using=using, keep_parents=keep_parents) + + def delete(self, using=None, keep_parents=False): + if not self.deactivated_at: + self.deactivated_at = timezone.now() + self.save() diff --git a/project/templates/lilcity/index.html b/project/templates/lilcity/index.html index 8366692d..3978a2e8 100644 --- a/project/templates/lilcity/index.html +++ b/project/templates/lilcity/index.html @@ -15,7 +15,7 @@ - + @@ -135,7 +135,14 @@ {% endif %}
{% comment %} 234.120.345 руб. {% endcomment %} + {% with roles='author admin' %} + {% if request.user.role in roles.split and request.user.auth_token %} + {{request.user.auth_token}} + {% else %} + + {% endif %} + {% endwith %}
ДОБАВИТЬ КУРС
@@ -158,9 +165,11 @@ {% endif %}
- {% if request.user.is_authenticated and not request.user.fb_id or request.user.is_authenticated and not request.user.is_email_proved %} + {% if request.user.is_authenticated %} + {% if not request.user.is_email_proved and not request.user.fb_id %}
Необходимо подтвердить электронную почту
{% endif %} + {% endif %}
{% block content %}{% endblock content %} diff --git a/project/urls.py b/project/urls.py index 28a51a9a..65451425 100644 --- a/project/urls.py +++ b/project/urls.py @@ -21,7 +21,7 @@ from django.conf import settings from apps.course.views import ( CoursesView, likes, coursecomment, CourseView, LessonView, SearchView, - lessoncomment, + lessoncomment, CourseEditView, ) from apps.course.models import Course from apps.user.views import UserView, UserEditView @@ -33,6 +33,7 @@ urlpatterns = [ path('course//', CourseView.as_view(), name='course'), path('course//', CourseView.as_view(), name='course'), path('course//like', likes, name='likes'), + path('course//edit', CourseEditView.as_view(), name='course-edit'), path('course//comment', coursecomment, name='coursecomment'), path('lesson//', LessonView.as_view(), name='lesson'), path('lesson//comment', lessoncomment, name='lessoncomment'), diff --git a/web/src/sass/_common.sass b/web/src/sass/_common.sass index 6e531c73..6da8323c 100755 --- a/web/src/sass/_common.sass +++ b/web/src/sass/_common.sass @@ -473,6 +473,10 @@ a.btn color: $green-light &:hover color: darken($green-light,10) + &_gray + color: $gray + +m + color: $gray &_border margin-bottom: 5px padding: 2px 20px 10px @@ -3268,9 +3272,19 @@ a.grey-link a &.link--black color: #000 + &.disabled + pointer-events: none + cursor: default + opacity: 0.6 - - - - - +.content-block + &.title + font-size: 24px + font-style: bold + &.text + font-size: 16px + padding-bottom: 24px + text-align: left + &.pic + position: relative + width: 100% \ No newline at end of file