Тысячи шедевров уже созданы благодаря Lil School. Более 10000 работ можно + увидеть в Инстаграм
+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..3d76244d --- /dev/null +++ b/api/v1/serializers/contest.py @@ -0,0 +1,45 @@ +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): + class Meta: + model = ContestWork + fields = '__all__' 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..0e8cab3d 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 41cd881a..ac9d70fe 100644 --- a/api/v1/views.py +++ b/api/v1/views.py @@ -39,6 +39,7 @@ from .serializers.user import ( AuthorRequestSerializer, UserSerializer, UserPhotoSerializer, ) +from .serializers.contest import ContestCreateSerializer, ContestSerializer, ContestWorkSerializer from .permissions import ( IsAdmin, IsAdminOrIsSelf, @@ -56,7 +57,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, @@ -429,3 +430,21 @@ class PaymentViewSet(ExtendedModelViewSet): 'amount', 'created_at', ) search_fields = ('user__email', 'user__first_name', 'user__last_name',) + + +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 = ContestWorkSerializer 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/models.py b/apps/content/models.py index 468058eb..3f4992ea 100644 --- a/apps/content/models.py +++ b/apps/content/models.py @@ -1,9 +1,13 @@ from urllib.parse import urlparse from django.db import models +from django.contrib.auth import get_user_model from polymorphic.models import PolymorphicModel +User = get_user_model() + + class ImageObject(models.Model): image = models.ImageField('Изображение', upload_to='content/imageobject') @@ -36,6 +40,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 +141,41 @@ 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) diff --git a/apps/content/templates/content/blocks/contest_work.html b/apps/content/templates/content/blocks/contest_work.html new file mode 100644 index 00000000..b80e6e06 --- /dev/null +++ b/apps/content/templates/content/blocks/contest_work.html @@ -0,0 +1,3 @@ +
Тысячи шедевров уже созданы благодаря Lil School. Более 10000 работ можно + увидеть в Инстаграм
+