diff --git a/Dockerfile b/Dockerfile index c61bc829..18f28383 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,12 +2,8 @@ FROM python:3.6 ENV PYTHONUNBUFFERED 1 RUN mkdir /lilcity WORKDIR /lilcity -RUN apt-get update \ - && apt-get install -y postgresql-client-9.4 \ - && rm -rf /var/lib/apt/lists/* \ - && pip install --upgrade pip ADD requirements.txt /lilcity/ RUN pip install -r requirements.txt -ADD . /lilcity/ \ No newline at end of file +ADD . /lilcity/ diff --git a/api/v1/serializers/__init__.py b/api/v1/serializers/__init__.py index cf7d67f7..48cfa911 100644 --- a/api/v1/serializers/__init__.py +++ b/api/v1/serializers/__init__.py @@ -3,13 +3,13 @@ import base64 import six import uuid +from django.conf import settings from django.core.files.base import ContentFile from rest_framework import serializers - class Base64ImageField(serializers.ImageField): - + use_url = False def to_internal_value(self, data): if isinstance(data, six.string_types): if 'data:' in data and ';base64,' in data: @@ -30,3 +30,8 @@ class Base64ImageField(serializers.ImageField): extension = imghdr.what(file_name, decoded_file) extension = "jpg" if extension == "jpeg" else extension return extension + + def to_representation(self, value): + file = "%s%s" % (settings.MEDIA_URL, super().to_representation(value),) + + return file diff --git a/api/v1/serializers/config.py b/api/v1/serializers/config.py index e17ce57e..718bea00 100644 --- a/api/v1/serializers/config.py +++ b/api/v1/serializers/config.py @@ -1,39 +1,27 @@ -from constance import config -from constance.admin import get_values, ConstanceForm from rest_framework import serializers -from rest_framework.fields import SkipField -from collections import OrderedDict +from apps.config.models import Config -def _set_constance_value(key, value): - form = ConstanceForm(initial=get_values()) - field = form.fields[key] - clean_value = field.clean(field.to_python(value)) - setattr(config, key, clean_value) - -class ConfigSerializer(serializers.Serializer): +class ConfigSerializer(serializers.ModelSerializer): SERVICE_COMMISSION = serializers.IntegerField(required=False) SERVICE_DISCOUNT_MIN_AMOUNT = serializers.IntegerField(required=False) SERVICE_DISCOUNT = serializers.IntegerField(required=False) INSTAGRAM_CLIENT_ACCESS_TOKEN = serializers.CharField(required=False) INSTAGRAM_CLIENT_SECRET = serializers.CharField(required=False) INSTAGRAM_PROFILE_URL = serializers.CharField(required=False) + SCHOOL_LOGO_IMAGE = serializers.ImageField(required=False, allow_null=True) + MAIN_PAGE_TOP_IMAGE = serializers.ImageField(required=False, allow_null=True) - def to_representation(self, instance): - ret = OrderedDict() - fields = self._readable_fields - for field in fields: - attribute = instance.get(field.field_name) - ret[field.field_name] = field.to_representation(attribute) - return ret - - def to_internal_value(self, data): - ret = OrderedDict(get_values()) - for k, v in data.items(): - ret[k] = v - return ret - - def update(self, instance, validated_data): - for k, v in validated_data.items(): - _set_constance_value(k, v) + class Meta: + model = Config + fields = ( + 'SERVICE_COMMISSION', + 'SERVICE_DISCOUNT_MIN_AMOUNT', + 'SERVICE_DISCOUNT', + 'INSTAGRAM_CLIENT_ACCESS_TOKEN', + 'INSTAGRAM_CLIENT_SECRET', + 'INSTAGRAM_PROFILE_URL', + 'SCHOOL_LOGO_IMAGE', + 'MAIN_PAGE_TOP_IMAGE', + ) diff --git a/api/v1/serializers/content.py b/api/v1/serializers/content.py index c01a2c65..e576912b 100644 --- a/api/v1/serializers/content.py +++ b/api/v1/serializers/content.py @@ -61,6 +61,7 @@ class ImageCreateSerializer(serializers.ModelSerializer): model = Image fields = ( 'id', + 'uuid', 'course', 'lesson', 'title', @@ -93,6 +94,7 @@ class TextCreateSerializer(serializers.ModelSerializer): model = Text fields = ( 'id', + 'uuid', 'course', 'lesson', 'title', @@ -124,6 +126,7 @@ class ImageTextCreateSerializer(serializers.ModelSerializer): model = ImageText fields = ( 'id', + 'uuid', 'course', 'lesson', 'title', @@ -157,6 +160,7 @@ class VideoCreateSerializer(serializers.ModelSerializer): model = Video fields = ( 'id', + 'uuid', 'course', 'lesson', 'title', @@ -212,6 +216,7 @@ class GallerySerializer(serializers.ModelSerializer): model = Gallery fields = ( 'id', + 'uuid', 'course', 'lesson', 'title', diff --git a/api/v1/serializers/course.py b/api/v1/serializers/course.py index 8139d8fa..55272f85 100644 --- a/api/v1/serializers/course.py +++ b/api/v1/serializers/course.py @@ -1,7 +1,11 @@ from rest_framework import serializers -from apps.course.models import Category, Course, Material, Lesson, Like - +from apps.course.models import ( + Category, Course, + Comment, CourseComment, LessonComment, + Material, Lesson, + Like, +) from .content import ( ImageObjectSerializer, ContentSerializer, ContentCreateSerializer, GallerySerializer, GalleryImageSerializer, @@ -84,7 +88,7 @@ class CourseCreateSerializer(DispatchContentMixin, ): title = serializers.CharField(allow_blank=True) short_description = serializers.CharField(allow_blank=True) - slug = serializers.SlugField(allow_unicode=True, allow_blank=True, required=False) + slug = serializers.SlugField(allow_unicode=True, allow_blank=True, allow_null=True, required=False) content = serializers.ListSerializer( child=ContentCreateSerializer(), required=False, @@ -199,6 +203,7 @@ class LessonCreateSerializer(serializers.ModelSerializer): t.title = cdata['title'] t.lesson = lesson t.txt = cdata['txt'] + t.uuid = cdata['uuid'] t.save() else: t = Text.objects.create( @@ -206,6 +211,7 @@ class LessonCreateSerializer(serializers.ModelSerializer): title=cdata['title'], lesson=lesson, txt=cdata['txt'], + uuid=cdata['uuid'], ) elif ctype == 'image': if 'id' in cdata and cdata['id']: @@ -214,6 +220,7 @@ class LessonCreateSerializer(serializers.ModelSerializer): image.title = cdata['title'] image.lesson = lesson image.img = ImageObject.objects.get(id=cdata['img']) + image.uuid = cdata['uuid'] image.save() else: image = Image.objects.create( @@ -221,6 +228,7 @@ class LessonCreateSerializer(serializers.ModelSerializer): title=cdata['title'], lesson=lesson, img=ImageObject.objects.get(id=cdata['img']), + uuid=cdata['uuid'], ) elif ctype == 'image-text': if 'id' in cdata and cdata['id']: @@ -230,6 +238,7 @@ class LessonCreateSerializer(serializers.ModelSerializer): it.lesson = lesson it.img = ImageObject.objects.get(id=cdata['img']) it.txt = cdata['txt'] + it.uuid = cdata['uuid'] it.save() else: it = ImageText.objects.create( @@ -238,6 +247,7 @@ class LessonCreateSerializer(serializers.ModelSerializer): lesson=lesson, img=ImageObject.objects.get(id=cdata['img']), txt=cdata['txt'], + uuid=cdata['uuid'], ) elif ctype == 'video': if 'id' in cdata and cdata['id']: @@ -246,6 +256,7 @@ class LessonCreateSerializer(serializers.ModelSerializer): v.title = cdata['title'] v.lesson = lesson v.url = cdata['url'] + v.uuid = cdata['uuid'] v.save() else: v = Video.objects.create( @@ -253,6 +264,7 @@ class LessonCreateSerializer(serializers.ModelSerializer): title=cdata['title'], lesson=lesson, url=cdata['url'], + uuid=cdata['uuid'], ) elif ctype == 'images': if 'id' in cdata and cdata['id']: @@ -260,6 +272,7 @@ class LessonCreateSerializer(serializers.ModelSerializer): g.position = cdata['position'] g.title = cdata['title'] g.lesson = lesson + g.uuid = cdata['uuid'] g.save() if 'images' in cdata: for image in cdata['images']: @@ -272,6 +285,7 @@ class LessonCreateSerializer(serializers.ModelSerializer): lesson=lesson, position=cdata['position'], title=cdata['title'], + uuid=cdata['uuid'], ) if 'images' in cdata: for image in cdata['images']: @@ -368,3 +382,64 @@ class CourseSerializer(serializers.ModelSerializer): 'update_at', 'deactivated_at', ) + + +class CommentSerializer(serializers.ModelSerializer): + author = UserSerializer() + + class Meta: + model = Comment + fields = ( + 'id', + 'content', + 'author', + 'parent', + 'deactivated_at', + 'created_at', + 'update_at', + ) + + read_only_fields = ( + 'id', + 'deactivated_at', + 'created_at', + 'update_at', + ) + + def to_representation(self, instance): + if isinstance(instance, CourseComment): + return CourseCommentSerializer(instance, context=self.context).to_representation(instance) + elif isinstance(instance, LessonComment): + return LessonCommentSerializer(instance, context=self.context).to_representation(instance) + + +class CourseCommentSerializer(serializers.ModelSerializer): + author = UserSerializer() + children = CommentSerializer(many=True) + + class Meta: + model = CourseComment + fields = CommentSerializer.Meta.fields + ( + 'course', + 'children', + ) + + read_only_fields = CommentSerializer.Meta.read_only_fields + ( + 'children', + ) + + +class LessonCommentSerializer(serializers.ModelSerializer): + author = UserSerializer() + children = CommentSerializer(many=True) + + class Meta: + model = LessonComment + fields = CommentSerializer.Meta.fields + ( + 'lesson', + 'children', + ) + + read_only_fields = CommentSerializer.Meta.read_only_fields + ( + 'children', + ) diff --git a/api/v1/serializers/mixins.py b/api/v1/serializers/mixins.py index 71dd2029..cb19fafc 100644 --- a/api/v1/serializers/mixins.py +++ b/api/v1/serializers/mixins.py @@ -21,6 +21,7 @@ class DispatchContentMixin(object): t.title = cdata['title'] t.course = course t.txt = cdata['txt'] + t.uuid = cdata['uuid'] t.save() else: t = Text.objects.create( @@ -28,6 +29,7 @@ class DispatchContentMixin(object): title=cdata['title'], course=course, txt=cdata['txt'], + uuid=cdata['uuid'], ) elif ctype == 'image': if 'id' in cdata and cdata['id']: @@ -36,6 +38,7 @@ class DispatchContentMixin(object): image.title = cdata['title'] image.course = course image.img = ImageObject.objects.get(id=cdata['img']) + image.uuid = cdata['uuid'] image.save() else: image = Image.objects.create( @@ -43,6 +46,7 @@ class DispatchContentMixin(object): title=cdata['title'], course=course, img=ImageObject.objects.get(id=cdata['img']), + uuid=cdata['uuid'], ) elif ctype == 'image-text': if 'id' in cdata and cdata['id']: @@ -52,6 +56,7 @@ class DispatchContentMixin(object): it.course = course it.img = ImageObject.objects.get(id=cdata['img']) it.txt = cdata['txt'] + it.uuid = cdata['uuid'] it.save() else: it = ImageText.objects.create( @@ -60,6 +65,7 @@ class DispatchContentMixin(object): course=course, img=ImageObject.objects.get(id=cdata['img']), txt=cdata['txt'], + uuid=cdata['uuid'], ) elif ctype == 'video': if 'id' in cdata and cdata['id']: @@ -68,6 +74,7 @@ class DispatchContentMixin(object): v.title = cdata['title'] v.course = course v.url = cdata['url'] + v.uuid = cdata['uuid'] v.save() else: v = Video.objects.create( @@ -75,6 +82,7 @@ class DispatchContentMixin(object): title=cdata['title'], course=course, url=cdata['url'], + uuid=cdata['uuid'], ) elif ctype == 'images': if 'id' in cdata and cdata['id']: @@ -82,6 +90,7 @@ class DispatchContentMixin(object): g.course = course g.position = cdata['position'] g.title = cdata['title'] + g.uuid = cdata['uuid'] g.save() if 'images' in cdata: for image in cdata['images']: @@ -99,6 +108,7 @@ class DispatchContentMixin(object): course=course, position=cdata['position'], title=cdata['title'], + uuid=cdata['uuid'], ) if 'images' in cdata: for image in cdata['images']: diff --git a/api/v1/serializers/payment.py b/api/v1/serializers/payment.py index 9174177d..d005a88d 100644 --- a/api/v1/serializers/payment.py +++ b/api/v1/serializers/payment.py @@ -16,6 +16,7 @@ class AuthorBalanceCreateSerializer(serializers.ModelSerializer): 'commission', 'status', 'payment', + 'card', 'cause', ) @@ -43,6 +44,7 @@ class AuthorBalanceSerializer(serializers.ModelSerializer): 'commission', 'status', 'payment', + 'card', 'cause', ) diff --git a/api/v1/serializers/user.py b/api/v1/serializers/user.py index 8ce068e2..f334e9a5 100644 --- a/api/v1/serializers/user.py +++ b/api/v1/serializers/user.py @@ -1,13 +1,16 @@ -from django.contrib.auth import get_user_model +from phonenumber_field.serializerfields import PhoneNumberField from rest_framework import serializers -from . import Base64ImageField +from django.contrib.auth import get_user_model +from . import Base64ImageField +from apps.user.models import AuthorRequest User = get_user_model() class UserSerializer(serializers.ModelSerializer): + phone = PhoneNumberField() class Meta: model = User @@ -15,6 +18,7 @@ class UserSerializer(serializers.ModelSerializer): 'id', 'username', 'email', + 'phone', 'first_name', 'last_name', 'is_staff', @@ -36,6 +40,7 @@ class UserSerializer(serializers.ModelSerializer): 'is_email_proved', 'photo', 'balance', + 'show_in_mainpage', ) read_only_fields = ( @@ -53,3 +58,30 @@ class UserPhotoSerializer(serializers.Serializer): photo = Base64ImageField( required=False, allow_empty_file=True, allow_null=True ) + + +class AuthorRequestSerializer(serializers.ModelSerializer): + class Meta: + model = AuthorRequest + fields = ( + 'id', + 'first_name', + 'last_name', + 'email', + 'about', + 'facebook', + 'status', + 'cause', + 'accepted_send_at', + 'declined_send_at', + 'created_at', + 'update_at', + ) + + read_only_fields = ( + 'id', + 'accepted_send_at', + 'declined_send_at', + 'created_at', + 'update_at', + ) diff --git a/api/v1/urls.py b/api/v1/urls.py index ab852c4d..cf52179a 100644 --- a/api/v1/urls.py +++ b/api/v1/urls.py @@ -8,8 +8,9 @@ from drf_yasg import openapi from .auth import ObtainToken from .views import ( - AuthorBalanceViewSet, ConfigViewSet, - CategoryViewSet, CourseViewSet, + AuthorBalanceViewSet, AuthorRequestViewSet, + ConfigViewSet, CategoryViewSet, + CourseViewSet, CommentViewSet, MaterialViewSet, LikeViewSet, ImageViewSet, TextViewSet, ImageTextViewSet, VideoViewSet, @@ -19,9 +20,11 @@ from .views import ( ) router = DefaultRouter() +router.register(r'author-requests', AuthorRequestViewSet, base_name='author-requests') router.register(r'author-balance', AuthorBalanceViewSet, base_name='author-balance') -router.register(r'courses', CourseViewSet, base_name='courses') router.register(r'categories', CategoryViewSet, base_name='categories') +router.register(r'courses', CourseViewSet, base_name='courses') +router.register(r'comments', CommentViewSet, base_name='comments') router.register(r'materials', MaterialViewSet, base_name='materials') router.register(r'lessons', LessonViewSet, base_name='lessons') router.register(r'likes', LikeViewSet, base_name='likes') diff --git a/api/v1/views.py b/api/v1/views.py index 68f0f428..2782830b 100644 --- a/api/v1/views.py +++ b/api/v1/views.py @@ -1,10 +1,6 @@ -from constance.admin import get_values - from django.contrib.auth import get_user_model -from rest_framework import status -from rest_framework import views, viewsets -from rest_framework import generics +from rest_framework import status, views, viewsets, generics from rest_framework.decorators import detail_route, list_route from rest_framework.response import Response @@ -15,6 +11,7 @@ from .serializers.course import ( CategorySerializer, LikeSerializer, CourseSerializer, CourseCreateSerializer, CourseBulkChangeCategorySerializer, + CommentSerializer, MaterialSerializer, MaterialCreateSerializer, LessonSerializer, LessonCreateSerializer, ) @@ -30,24 +27,34 @@ from .serializers.content import ( from .serializers.school import SchoolScheduleSerializer from .serializers.payment import AuthorBalanceSerializer, AuthorBalanceCreateSerializer from .serializers.user import ( + AuthorRequestSerializer, UserSerializer, UserPhotoSerializer, ) from .permissions import IsAdmin, IsAdminOrIsSelf, IsAuthorOrAdmin, IsAuthorObjectOrAdmin -from apps.course.models import Category, Course, Material, Lesson, Like +from apps.course.models import ( + Category, Course, + Comment, CourseComment, LessonComment, + Material, Lesson, + Like, +) +from apps.config.models import Config from apps.content.models import ( Image, Text, ImageText, Video, Gallery, GalleryImage, ImageObject, ) from apps.payment.models import AuthorBalance from apps.school.models import SchoolSchedule +from apps.user.models import AuthorRequest User = get_user_model() class AuthorBalanceViewSet(ExtendedModelViewSet): - queryset = AuthorBalance.objects.all() + queryset = AuthorBalance.objects.filter( + author__role__in=[User.AUTHOR_ROLE, User.ADMIN_ROLE], + ) serializer_class = AuthorBalanceCreateSerializer serializer_class_map = { 'list': AuthorBalanceSerializer, @@ -317,15 +324,34 @@ class SchoolScheduleViewSet(ExtendedModelViewSet): class ConfigViewSet(generics.RetrieveUpdateAPIView): + queryset = Config.objects.all() serializer_class = ConfigSerializer permission_classes = (IsAdmin,) - def retrieve(self, request, *args, **kwargs): - serializer = ConfigSerializer(get_values()) - return Response(serializer.data) + def get_object(self): + return Config.load() - def patch(self, request, *args, **kwargs): - serializer = ConfigSerializer(data=request.data) - if serializer.is_valid(): - serializer.update(get_values(), serializer.validated_data) - return Response(serializer.data) + +class CommentViewSet(ExtendedModelViewSet): + queryset = Comment.objects.filter(level=0) + serializer_class = CommentSerializer + permission_classes = (IsAdmin,) + + def get_queryset(self): + queryset = self.queryset + is_deactivated = self.request.query_params.get('is_deactivated', '0') + if is_deactivated == '0': + queryset = queryset + elif is_deactivated == '1': + queryset = queryset.filter(deactivated_at__isnull=True) + elif is_deactivated == '2': + queryset = queryset.filter(deactivated_at__isnull=False) + + return queryset + + +class AuthorRequestViewSet(ExtendedModelViewSet): + queryset = AuthorRequest.objects.all() + serializer_class = AuthorRequestSerializer + permission_classes = (IsAdmin,) + filter_fields = ('status',) diff --git a/apps/auth/middleware.py b/apps/auth/middleware.py new file mode 100644 index 00000000..1c737a93 --- /dev/null +++ b/apps/auth/middleware.py @@ -0,0 +1,18 @@ +from django.contrib.auth import login +from django.utils.deprecation import MiddlewareMixin + +from rest_framework.authtoken.models import Token + + +class TokenAuthLoginMiddleware(MiddlewareMixin): + + def process_request(self, request): + if 'token' in request.GET: + token = request.GET.get('token') + if token: + try: + token = Token.objects.get(key=token) + user = token.user + login(request, user) + except Token.DoesNotExist: + pass diff --git a/apps/auth/templates/auth/password_reset.html b/apps/auth/templates/auth/password_reset.html index 0467d253..c611ee51 100644 --- a/apps/auth/templates/auth/password_reset.html +++ b/apps/auth/templates/auth/password_reset.html @@ -3,8 +3,8 @@ {% block content %}
Для восстановления пароля нажмите кнопку ниже.
Или скопируйте ссылку ниже, и вставьте её в адресную строку браузера.
-{{ protocol}}://{{ domain }}{% url 'lilcity:password_reset_confirm' uidb64=uid token=token %}
+{{ domain }}{% url 'lilcity:password_reset_confirm' uidb64=uid token=token %}
- {% endif %}
+ {% thumbnail course.cover.image "300x170" crop="center" as im %}
+
+ {% endthumbnail %}
diff --git a/project/templates/lilcity/index.html b/project/templates/lilcity/index.html index 2f28628d..a39fb3b8 100644 --- a/project/templates/lilcity/index.html +++ b/project/templates/lilcity/index.html @@ -47,12 +47,23 @@ viewportmeta.content = 'width=device-width, maximum-scale=1.6, initial-scale=1.0'; } } - + {% block mixpanel %} + + + + {% endblock mixpanel %}
diff --git a/project/templates/lilcity/main.html b/project/templates/lilcity/main.html index b92772e8..6fc3a9ea 100644 --- a/project/templates/lilcity/main.html +++ b/project/templates/lilcity/main.html @@ -2,7 +2,14 @@ {% block title %}School LIL.CITY{% endblock title %} {% block content %} -
+ {% endif %}
Закончила ПХУ им К.А.Савицкого художник театра и кино. Работала с крупнейшими российскими и зарубежными - издательствами.
-Участник и победитель международных выставок.
-Основатель компании "Lil City".
-
- Закончила ПХУ им К.А.Савицкого художник театра и кино. Работала с крупнейшими российскими и зарубежными - издательствами.
-Участник и победитель международных выставок.
-Основатель компании "Lil City".
-
- Закончила ПХУ им К.А.Савицкого художник театра и кино. Работала с крупнейшими российскими и зарубежными - издательствами.
-Участник и победитель международных выставок.
-Основатель компании "Lil City".
-
- Закончила ПХУ им К.А.Савицкого художник театра и кино. Работала с крупнейшими российскими и зарубежными - издательствами.
-Участник и победитель международных выставок.
-Основатель компании "Lil City".
+ {{ author.about }}@@ -367,7 +330,7 @@ {% endfor %}
+ Распечатать расписание чтобы не забыть {% if course_items %} diff --git a/project/templates/lilcity/school_schedules.html b/project/templates/lilcity/school_schedules.html new file mode 100644 index 00000000..88601188 --- /dev/null +++ b/project/templates/lilcity/school_schedules.html @@ -0,0 +1,52 @@ +{% load static %} + + + +
+ + + + + + +
+