diff --git a/__init__.py b/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/api/v1/serializers/content.py b/api/v1/serializers/content.py index 729368e2..aff24dd5 100644 --- a/api/v1/serializers/content.py +++ b/api/v1/serializers/content.py @@ -3,8 +3,7 @@ from rest_framework import serializers from apps.content.models import ( Baner, Content, Image, Text, ImageText, Video, - Gallery, GalleryImage, ImageObject, -) + Gallery, GalleryImage, ImageObject,) from . import Base64ImageField @@ -14,6 +13,7 @@ BASE_CONTENT_FIELDS = ( 'uuid', 'course', 'lesson', + 'contest', 'live_lesson', 'title', 'position', @@ -254,3 +254,4 @@ class ContentSerializer(serializers.ModelSerializer): 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/contest.py b/api/v1/serializers/contest.py new file mode 100644 index 00000000..dc5b88fd --- /dev/null +++ b/api/v1/serializers/contest.py @@ -0,0 +1,68 @@ +from rest_framework import serializers + +from api.v1.serializers.content import ContentSerializer, ContentCreateSerializer, ImageObjectSerializer +from api.v1.serializers.mixins import DispatchContentMixin + +from apps.content.models import (Contest, ContestWork) + + +class ContestSerializer(serializers.ModelSerializer): + cover = ImageObjectSerializer() + content = ContentSerializer(many=True) + + class Meta: + model = Contest + fields = '__all__' + + +class ContestCreateSerializer(DispatchContentMixin, serializers.ModelSerializer): + content = serializers.ListSerializer( + child=ContentCreateSerializer(), + required=False, + ) + + class Meta: + model = Contest + fields = '__all__' + + def create(self, validated_data): + content = validated_data.pop('content', []) + contest = super().create(validated_data) + self.dispatch_content(contest, content) + return contest + + def update(self, instance, validated_data): + content = validated_data.pop('content', []) + contest = super().update(instance, validated_data) + self.dispatch_content(contest, content) + return contest + + def to_representation(self, instance): + return ContestSerializer(instance=instance, context=self.context).to_representation(instance) + + +class ContestWorkSerializer(serializers.ModelSerializer): + image = ImageObjectSerializer() + likes = serializers.SerializerMethodField() + user_liked = serializers.SerializerMethodField() + + class Meta: + model = ContestWork + fields = ['id', 'user', 'contest', 'image', 'child_full_name', 'age', + 'created_at', 'likes', 'user_liked', 'img_width', 'img_height'] + + def get_likes(self, instance): + return instance.likes.count() + + def get_user_liked(self, instance): + user = self.context['request'].user + return instance.likes.filter(user=user).exists() if user.is_authenticated else False + + +class ContestWorkCreateSerializer(serializers.ModelSerializer): + class Meta: + model = ContestWork + fields = '__all__' + + def to_representation(self, instance): + return ContestWorkSerializer(instance=instance, context=self.context).to_representation(instance) diff --git a/api/v1/serializers/course.py b/api/v1/serializers/course.py index bdb1a289..c3921890 100644 --- a/api/v1/serializers/course.py +++ b/api/v1/serializers/course.py @@ -1,6 +1,8 @@ from rest_framework import serializers from rest_framework.validators import UniqueValidator +from django.contrib.auth import get_user_model + from apps.course.models import ( Category, Course, Comment, CourseComment, LessonComment, @@ -15,12 +17,15 @@ from .content import ( from apps.content.models import ( Content, Image, Text, ImageText, Video, Gallery, GalleryImage, ImageObject, -) + ContestWork) from .user import UserSerializer from .mixins import DispatchContentMixin, DispatchGalleryMixin, DispatchMaterialMixin +User = get_user_model() + + class MaterialCreateSerializer(serializers.ModelSerializer): class Meta: @@ -63,6 +68,40 @@ class LikeSerializer(serializers.ModelSerializer): ) +class LikeCreateSerializer(serializers.ModelSerializer): + OBJ_TYPE_CONTEST_WORK = 'contest_work' + + obj_type = serializers.CharField(required=True) + obj_id = serializers.IntegerField(required=True) + + class Meta: + model = Like + fields = ['user', 'obj_type', 'obj_id'] + + def create(self, validated_data): + # FIXME + if validated_data.get('user'): + user = validated_data.get('user') + else: + user = self.context['request'].user + if not user: # FIXME and user.is_authenticated): + return Like() + obj_type = validated_data.pop('obj_type') + obj_id = validated_data.pop('obj_id') + if obj_type == self.OBJ_TYPE_CONTEST_WORK: + contest_work = ContestWork.objects.get(pk=obj_id) + # FIXME in prod: + # if contest_work.user == user or contest_work.likes.filter(user=user).exists(): + if contest_work.likes.filter(user=user).exists(): + return Like() + like = Like.objects.create(user=user) + contest_work.likes.add(like) + return like + + def to_representation(self, instance): + return LikeSerializer(instance, context=self.context).to_representation(instance) + + class CategorySerializer(serializers.ModelSerializer): class Meta: diff --git a/api/v1/serializers/mixins.py b/api/v1/serializers/mixins.py index 0f055f00..40584503 100644 --- a/api/v1/serializers/mixins.py +++ b/api/v1/serializers/mixins.py @@ -4,7 +4,7 @@ from apps.school.models import LiveLesson from apps.content.models import ( Content, Image, Text, ImageText, Video, Gallery, GalleryImage, ImageObject, -) + Contest) from .content import ( TextCreateSerializer, ImageCreateSerializer, ImageTextCreateSerializer, VideoCreateSerializer, @@ -25,6 +25,8 @@ class DispatchContentMixin(object): obj_type = 'lesson' elif isinstance(obj, LiveLesson): obj_type = 'live_lesson' + elif isinstance(obj, Contest): + obj_type = 'contest' cdata[obj_type] = obj.id if ctype == 'text': if 'id' in cdata and cdata['id']: diff --git a/api/v1/urls.py b/api/v1/urls.py index 409ed79f..51610cb6 100644 --- a/api/v1/urls.py +++ b/api/v1/urls.py @@ -18,7 +18,7 @@ from .views import ( UserViewSet, LessonViewSet, ImageObjectViewSet, SchoolScheduleViewSet, LiveLessonViewSet, PaymentViewSet, -) + ContestViewSet, ContestWorkViewSet) router = DefaultRouter() router.register(r'author-requests', AuthorRequestViewSet, base_name='author-requests') @@ -44,6 +44,9 @@ router.register(r'school-schedules', SchoolScheduleViewSet, base_name='school-sc router.register(r'users', UserViewSet, base_name='users') +router.register(r'contests', ContestViewSet, base_name='contests') +router.register(r'contest-works', ContestWorkViewSet, base_name='contest_works') + # router.register(r'configs', ConfigViewSet, base_name='configs') diff --git a/api/v1/views.py b/api/v1/views.py index bb5eb6ed..e9fa7859 100644 --- a/api/v1/views.py +++ b/api/v1/views.py @@ -14,7 +14,7 @@ from .serializers.course import ( CommentSerializer, MaterialSerializer, MaterialCreateSerializer, LessonSerializer, LessonCreateSerializer, -) + LikeCreateSerializer) from .serializers.content import ( BanerSerializer, ImageSerializer, ImageCreateSerializer, @@ -39,6 +39,9 @@ from .serializers.user import ( AuthorRequestSerializer, UserSerializer, UserPhotoSerializer, ) +from .serializers.contest import ( + ContestCreateSerializer, ContestSerializer, ContestWorkSerializer, ContestWorkCreateSerializer +) from .permissions import ( IsAdmin, IsAdminOrIsSelf, @@ -56,7 +59,7 @@ from apps.config.models import Config from apps.content.models import ( Baner, Image, Text, ImageText, Video, Gallery, GalleryImage, ImageObject, -) + Contest, ContestWork) from apps.payment.models import ( AuthorBalance, Payment, CoursePayment, SchoolPayment, @@ -110,6 +113,8 @@ class BanerViewSet(ExtendedModelViewSet): class ImageObjectViewSet(ExtendedModelViewSet): queryset = ImageObject.objects.all() serializer_class = ImageObjectSerializer + # FIXME + authentication_classes = [] # permission_classes = (IsAuthorOrAdmin,) @@ -127,10 +132,16 @@ class MaterialViewSet(ExtendedModelViewSet): class LikeViewSet(ExtendedModelViewSet): queryset = Like.objects.select_related('user').all() - serializer_class = LikeSerializer + serializer_class = LikeCreateSerializer + serializer_class_map = { + 'list': LikeSerializer, + 'retrieve': LikeSerializer, + } search_fields = ('user__email', 'user__firstname', 'user__lastname',) ordering_fields = ('created_at', 'update_at',) # permission_classes = (IsAdmin,) + # FIXME + authentication_classes = [] class CategoryViewSet(ExtendedModelViewSet): @@ -440,3 +451,33 @@ class PaymentViewSet(ExtendedModelViewSet): queryset = SchoolPayment.objects.filter(weekdays__overlap=weekdays) return queryset.filter(status__isnull=False).order_by('-created_at') + +class ContestViewSet(ExtendedModelViewSet): + queryset = Contest.objects.all() + serializer_class = ContestCreateSerializer + serializer_class_map = { + 'list': ContestSerializer, + 'retrieve': ContestSerializer, + } + filter_fields = ('active',) + search_fields = ('description', 'title', 'slug',) + ordering_fields = ('id', 'title', 'active', 'date_start', 'date_end',) + permission_classes = (IsAdmin,) + + +class ContestWorkViewSet(ExtendedModelViewSet): + queryset = ContestWork.objects.all() + serializer_class = ContestWorkCreateSerializer + serializer_class_map = { + 'list': ContestWorkSerializer, + 'retrieve': ContestWorkSerializer, + } + filter_fields = ('contest',) + # FIXME + authentication_classes = [] + + def create(self, request, *args, **kwargs): + # FIXME in prod: + # if ContestWork.objects.filter(user=request.user).exists(): + # return Response(status=status.HTTP_400_BAD_REQUEST) + return super().create(request, *args, **kwargs) diff --git a/apps/content/migrations/0021_auto_20180813_1306.py b/apps/content/migrations/0021_auto_20180813_1306.py new file mode 100644 index 00000000..66033641 --- /dev/null +++ b/apps/content/migrations/0021_auto_20180813_1306.py @@ -0,0 +1,48 @@ +# Generated by Django 2.0.6 on 2018-08-13 13:06 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('course', '0040_course_age'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('content', '0020_auto_20180424_1607'), + ] + + operations = [ + migrations.CreateModel( + name='Contest', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=255)), + ('description', models.TextField(blank=True, default='', max_length=1000)), + ('slug', models.SlugField(allow_unicode=True, blank=True, max_length=100, null=True, unique=True)), + ('date_start', models.DateField(blank=True, null=True, verbose_name='Дата начала')), + ('date_end', models.DateField(blank=True, null=True, verbose_name='Дата окончания')), + ('active', models.BooleanField(default=True)), + ('cover', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='contest_covers', to='content.ImageObject', verbose_name='Фоновая картинка')), + ], + ), + migrations.CreateModel( + name='ContestWork', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('child_full_name', models.CharField(max_length=255)), + ('age', models.SmallIntegerField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('contest', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='content.Contest')), + ('image', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contest_work_images', to='content.ImageObject', verbose_name='Работа участника')), + ('likes', models.ManyToManyField(blank=True, to='course.Like')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.AddField( + model_name='content', + name='contest', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='content', to='content.Contest', verbose_name='Конкурс'), + ), + ] diff --git a/apps/content/migrations/0022_auto_20180815_2129.py b/apps/content/migrations/0022_auto_20180815_2129.py new file mode 100644 index 00000000..92cc567d --- /dev/null +++ b/apps/content/migrations/0022_auto_20180815_2129.py @@ -0,0 +1,17 @@ +# Generated by Django 2.0.6 on 2018-08-15 21:29 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('content', '0021_auto_20180813_1306'), + ] + + operations = [ + migrations.AlterModelOptions( + name='contestwork', + options={'ordering': ('-created_at',), 'verbose_name': 'Конкурсная работа', 'verbose_name_plural': 'Конкурсные работы'}, + ), + ] diff --git a/apps/content/models.py b/apps/content/models.py index 468058eb..bbf023d8 100644 --- a/apps/content/models.py +++ b/apps/content/models.py @@ -1,9 +1,14 @@ from urllib.parse import urlparse from django.db import models +from django.contrib.auth import get_user_model +from django.urls import reverse_lazy from polymorphic.models import PolymorphicModel +User = get_user_model() + + class ImageObject(models.Model): image = models.ImageField('Изображение', upload_to='content/imageobject') @@ -36,6 +41,12 @@ class Content(PolymorphicModel): verbose_name='Урок онлайн школы', related_name='content', ) + contest = models.ForeignKey( + 'Contest', on_delete=models.CASCADE, + null=True, blank=True, + verbose_name='Конкурс', + related_name='content', + ) title = models.CharField('Заголовок', max_length=100, default='') position = models.PositiveSmallIntegerField( 'Положение на странице', @@ -131,3 +142,57 @@ class Baner(models.Model): if self.use: Baner.objects.filter(use=True).update(use=False) return super().save(*args, **kwargs) + + +class Contest(models.Model): + title = models.CharField(max_length=255) + description = models.TextField(max_length=1000, blank=True, default='') + slug = models.SlugField( + allow_unicode=True, null=True, blank=True, + max_length=100, unique=True, db_index=True, + ) + cover = models.ForeignKey( + ImageObject, related_name='contest_covers', + verbose_name='Фоновая картинка', on_delete=models.CASCADE, + null=True, blank=True, + ) + date_start = models.DateField('Дата начала', null=True, blank=True) + date_end = models.DateField('Дата окончания', null=True, blank=True) + active = models.BooleanField(default=True) + # TODO? baner + + def save(self, *args, **kwargs): + if self.active: + Contest.objects.filter(active=True).update(active=False) + return super().save(*args, **kwargs) + + +class ContestWork(models.Model): + user = models.ForeignKey( + User, on_delete=models.CASCADE + ) + contest = models.ForeignKey(Contest, on_delete=models.CASCADE) + image = models.ForeignKey( + ImageObject, related_name='contest_work_images', + verbose_name='Работа участника', on_delete=models.CASCADE, + ) + child_full_name = models.CharField(max_length=255) + age = models.SmallIntegerField() + created_at = models.DateTimeField(auto_now_add=True) + likes = models.ManyToManyField('course.Like', blank=True) + + class Meta: + verbose_name = 'Конкурсная работа' + verbose_name_plural = 'Конкурсные работы' + ordering = ('-created_at',) + + @property + def img_width(self): + return self.image.image.width if self.image and self.image.image else None + + @property + def img_height(self): + return self.image.image.height if self.image and self.image.image else None + + def get_absolute_url(self): + return reverse_lazy('contest_work', args=[self.id]) diff --git a/apps/course/templates/course/content/gallery.html b/apps/content/templates/content/blocks/gallery.html similarity index 97% rename from apps/course/templates/course/content/gallery.html rename to apps/content/templates/content/blocks/gallery.html index 1116322a..69ad0386 100644 --- a/apps/course/templates/course/content/gallery.html +++ b/apps/content/templates/content/blocks/gallery.html @@ -1,32 +1,32 @@ -{% load thumbnail %} -{% if results %} -
Галерея итогов обучения
- -{% else %} -
-
-
{{ content.title }}
- -
-
-{% endif %} +{% load thumbnail %} +{% if results %} +
Галерея итогов обучения
+ +{% else %} +
+
+
{{ content.title }}
+ +
+
+{% endif %} diff --git a/apps/course/templates/course/content/image.html b/apps/content/templates/content/blocks/image.html similarity index 96% rename from apps/course/templates/course/content/image.html rename to apps/content/templates/content/blocks/image.html index 4ab24601..5d515430 100644 --- a/apps/course/templates/course/content/image.html +++ b/apps/content/templates/content/blocks/image.html @@ -1,10 +1,10 @@ -
-
-
- {{ content.title }} -
-
- -
-
-
+
+
+
+ {{ content.title }} +
+
+ +
+
+
diff --git a/apps/course/templates/course/content/imagetext.html b/apps/content/templates/content/blocks/imagetext.html similarity index 100% rename from apps/course/templates/course/content/imagetext.html rename to apps/content/templates/content/blocks/imagetext.html diff --git a/apps/course/templates/course/content/text.html b/apps/content/templates/content/blocks/text.html similarity index 94% rename from apps/course/templates/course/content/text.html rename to apps/content/templates/content/blocks/text.html index 18ce2235..59e46ded 100644 --- a/apps/course/templates/course/content/text.html +++ b/apps/content/templates/content/blocks/text.html @@ -1,10 +1,10 @@ -
-
-
- {{ content.title }} -
-
- {{ content.txt | safe }} -
-
+
+
+
+ {{ content.title }} +
+
+ {{ content.txt | safe }} +
+
\ No newline at end of file diff --git a/apps/course/templates/course/content/video.html b/apps/content/templates/content/blocks/video.html similarity index 95% rename from apps/course/templates/course/content/video.html rename to apps/content/templates/content/blocks/video.html index 3527507f..fe741465 100644 --- a/apps/course/templates/course/content/video.html +++ b/apps/content/templates/content/blocks/video.html @@ -1,43 +1,43 @@ -
-
-
- {{ 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/content/templates/content/contest.html b/apps/content/templates/content/contest.html new file mode 100644 index 00000000..0d8a4ad4 --- /dev/null +++ b/apps/content/templates/content/contest.html @@ -0,0 +1,41 @@ +{% extends "templates/lilcity/index.html" %} + + +{% block content %} + +
+
+
+ {{ contest.title }} +
+
+ {{ contest.description }} +
+ +
+
+
+{% for content in contest.content.all %} + + {% with template="content/blocks/"|add:content.ctype|add:".html" %} + {% include template %} + {% endwith %} + +{% endfor %} + +
+{% endblock content %} diff --git a/apps/content/templates/content/contest_edit.html b/apps/content/templates/content/contest_edit.html new file mode 100644 index 00000000..d1c26b62 --- /dev/null +++ b/apps/content/templates/content/contest_edit.html @@ -0,0 +1,18 @@ +{% extends "templates/lilcity/edit_index.html" %} +{% load static %} + +{% block title %} + {% if object %} + Редактирование конкурса {{ object.title }} + {% else %} + Создание конкурса + {% endif %} +{% endblock title %} + +{% block content %} + +{% endblock content %} +{% block foot %} + + +{% endblock foot %} diff --git a/apps/content/templates/content/contest_work.html b/apps/content/templates/content/contest_work.html new file mode 100644 index 00000000..1fe09942 --- /dev/null +++ b/apps/content/templates/content/contest_work.html @@ -0,0 +1,112 @@ +{% extends "templates/lilcity/index.html" %} +{% load static %} +{% block title %}{{ contest_work.child_full_name }}, {{ contest_work.age }} лет{% endblock title %} + +{% block ogimage %}http://{{request.META.HTTP_HOST}}{{ contest_work.image.image.url }}{% endblock ogimage %} + +{% block content %} +
+ +
+
+ +
+
+
+
{{ contest_work.child_full_name }}
+
{{ contest_work.age }} {% if contest_work.age < 5 %}года{% else %}лет{% endif %}
+
+ +
+
+
+ +
+
+
+ {% if prev_contest_work %} + +
+ + + +
+
Предыдущая работа
+
+ {% else %} +
+ {% endif %} + {% if next_contest_work %} + +
Следующая работа
+
+ + + +
+
+ {% else %} +
+ {% endif %} +
+
+
+ +
+
+ {% include 'templates/blocks/share.html' with share_object_name='работой' %} +
+
+ +
+
+
Задавайте вопросы:
+
+ {% if request.user.is_authenticated %} +
+ +
+ +
+
+ +
+ +
+ +
+
+ {% else %} +
Только зарегистрированные пользователи могут оставлять комментарии.
+ {% endif %} +
+ {% include "templates/blocks/comments.html" with object=contest_work %} +
+
+
+
+{% endblock content %} diff --git a/apps/content/views.py b/apps/content/views.py index 28002783..ff3a4deb 100644 --- a/apps/content/views.py +++ b/apps/content/views.py @@ -1,2 +1,110 @@ -from django.shortcuts import render +from django.contrib.auth.decorators import login_required +from django.http import JsonResponse +from django.shortcuts import get_object_or_404 +from django.template import loader +from django.utils.decorators import method_decorator +from django.views.decorators.csrf import csrf_exempt +from django.views.decorators.http import require_http_methods +from django.views.generic import TemplateView, DetailView +from apps.content.models import Contest, ContestWork +from apps.course.models import ContestWorkComment + + +@method_decorator(login_required, name='dispatch') +class ContestEditView(TemplateView): + template_name = 'content/contest_edit.html' + + def get(self, request, pk=None, lesson=None): + if pk: + self.object = get_object_or_404(Contest, pk=pk) + else: + self.object = Contest() + + return super().get(request) + + def get_context_data(self): + context = super().get_context_data() + context['object'] = self.object + return context + + +class ContestView(DetailView): + model = Contest + context_object_name = 'contest' + template_name = 'content/contest.html' + query_pk_and_slug = True + + def get_context_data(self, *args, **kwargs): + context = super().get_context_data() + if self.request.user.is_authenticated: + context['contest_work_uploaded'] = ContestWork.objects.filter(user=self.request.user).exists() + return context + + +class ContestWorkView(DetailView): + model = ContestWork + context_object_name = 'contest_work' + template_name = 'content/contest_work.html' + + def get_context_data(self, *args, **kwargs): + context = super().get_context_data() + prev_contest_work = ContestWork.objects.filter(created_at__gt=self.object.created_at)[:1] + if prev_contest_work: + context['prev_contest_work'] = prev_contest_work[0] + next_contest_work = ContestWork.objects.filter(created_at__lt=self.object.created_at)[:1] + if next_contest_work: + context['next_contest_work'] = next_contest_work[0] + + context['user_liked'] = self.object.likes.filter(user=self.request.user).exists() \ + if self.request.user.is_authenticated else False + return context + + +@login_required +@csrf_exempt +@require_http_methods(['POST']) +def contest_work_comment(request, contest_work_id): + try: + contest_work = ContestWork.objects.get(id=contest_work_id) + except ContestWork.DoesNotExist: + return JsonResponse({ + 'success': False, + 'errors': ['Contest_work with id f{contest_work_id} not found'] + }, status=400) + else: + reply_to = request.POST.get('reply_id', 0) + comment = request.POST.get('comment', '') + if not comment: + return JsonResponse({ + 'success': False, + 'errors': ['Comment can not be empty'] + }, status=400) + + if not int(reply_to): + contest_work_comment = ContestWorkComment.objects.create( + author=request.user, + content=comment, + contest_work=contest_work, + ) + else: + try: + _contest_work_comment = ContestWorkComment.objects.get(id=reply_to) + except ContestWorkComment.DoesNotExist: + return JsonResponse({ + 'success': False, + 'errors': ['LessonComment with id f{reply_to} not found'] + }, status=400) + else: + contest_work_comment = ContestWorkComment.objects.create( + author=request.user, + content=comment, + contest_work=contest_work, + parent=_contest_work_comment, + ) + ctx = {'node': contest_work_comment, 'user': request.user} + html = loader.render_to_string('templates/blocks/comment.html', ctx) + return JsonResponse({ + 'success': True, + 'comment': html, + }) diff --git a/apps/course/migrations/0040_course_age.py b/apps/course/migrations/0040_course_age.py new file mode 100644 index 00000000..f8426fd7 --- /dev/null +++ b/apps/course/migrations/0040_course_age.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.6 on 2018-08-08 01:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('course', '0039_lesson_position'), + ] + + operations = [ + migrations.AddField( + model_name='course', + name='age', + field=models.SmallIntegerField(choices=[(0, ''), (1, 'до 5'), (2, '5-7'), (3, '7-9'), (4, '9-12'), (5, '12-15'), (6, '15-18'), (7, 'от 18')], default=0), + ), + ] diff --git a/apps/course/migrations/0041_auto_20180813_1306.py b/apps/course/migrations/0041_auto_20180813_1306.py new file mode 100644 index 00000000..d2596544 --- /dev/null +++ b/apps/course/migrations/0041_auto_20180813_1306.py @@ -0,0 +1,36 @@ +# Generated by Django 2.0.6 on 2018-08-13 13:06 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('content', '0021_auto_20180813_1306'), + ('course', '0040_course_age'), + ] + + operations = [ + migrations.CreateModel( + name='ContestWorkComment', + fields=[ + ('comment_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='course.Comment')), + ('contest_work', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='content.ContestWork')), + ], + options={ + 'ordering': ('tree_id', 'lft'), + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('course.comment',), + ), + migrations.AlterModelOptions( + name='course', + options={'ordering': ['-is_featured'], 'verbose_name': 'Курс', 'verbose_name_plural': 'Курсы'}, + ), + migrations.RemoveField( + model_name='course', + name='age', + ), + ] diff --git a/apps/course/models.py b/apps/course/models.py index d4966b3c..6492b999 100644 --- a/apps/course/models.py +++ b/apps/course/models.py @@ -15,7 +15,7 @@ from project.mixins import BaseModel, DeactivatedMixin from .manager import CategoryQuerySet -from apps.content.models import ImageObject, Gallery, Video +from apps.content.models import ImageObject, Gallery, Video, ContestWork User = get_user_model() @@ -284,3 +284,7 @@ class LessonComment(Comment): class Meta(Comment.Meta): verbose_name = 'Комментарий урока' verbose_name_plural = 'Комментарии уроков' + + +class ContestWorkComment(Comment): + contest_work = models.ForeignKey(ContestWork, on_delete=models.CASCADE, related_name='comments') diff --git a/apps/course/templates/course/course.html b/apps/course/templates/course/course.html index 53a56abc..c3a35c4f 100644 --- a/apps/course/templates/course/course.html +++ b/apps/course/templates/course/course.html @@ -227,7 +227,7 @@
{% for content in course.content.all %} - {% with template="course/content/"|add:content.ctype|add:".html" %} + {% with template="content/blocks/"|add:content.ctype|add:".html" %} {% include template %} {% endwith %} diff --git a/apps/course/templates/course/lesson.html b/apps/course/templates/course/lesson.html index 73b70f0a..94d6b89b 100644 --- a/apps/course/templates/course/lesson.html +++ b/apps/course/templates/course/lesson.html @@ -63,7 +63,7 @@ {% for content in lesson.content.all %} - {% with template="course/content/"|add:content.ctype|add:".html" %} + {% with template="content/blocks/"|add:content.ctype|add:".html" %} {% include template %} {% endwith %} diff --git a/project/templates/blocks/header.html b/project/templates/blocks/header.html index 5be001d8..29eb2180 100644 --- a/project/templates/blocks/header.html +++ b/project/templates/blocks/header.html @@ -50,6 +50,11 @@
БЛОГ
+ {% comment %} +
+ КОНКУРС +
+ {% endcomment %} {% include 'templates/blocks/user_menu.html' %} diff --git a/project/templates/blocks/lil_store_js.html b/project/templates/blocks/lil_store_js.html new file mode 100644 index 00000000..cb5eb6f8 --- /dev/null +++ b/project/templates/blocks/lil_store_js.html @@ -0,0 +1,10 @@ +{% load static %} + diff --git a/project/templates/blocks/popup_auth.html b/project/templates/blocks/popup_auth.html index cf01d86b..50a62b55 100644 --- a/project/templates/blocks/popup_auth.html +++ b/project/templates/blocks/popup_auth.html @@ -3,7 +3,7 @@