commit
bbccc9996d
58 changed files with 1942 additions and 193 deletions
@ -0,0 +1,74 @@ |
||||
import imghdr |
||||
import base64 |
||||
import six |
||||
import uuid |
||||
|
||||
from django.core.files.base import ContentFile |
||||
|
||||
from rest_framework import serializers, viewsets |
||||
from rest_framework.response import Response |
||||
|
||||
# https://gist.github.com/ivlevdenis/a0c8f5b472b6b8550bbb016c6a30e0be |
||||
|
||||
|
||||
class ExtendViewSet(object): |
||||
""" |
||||
This viewset mixin class with extended options list. |
||||
""" |
||||
permission_map = {} |
||||
throttle_scope_map = {} |
||||
serializer_class_map = {} |
||||
|
||||
def get_serializer_class(self): |
||||
ser = self.serializer_class_map.get(self.action, None) |
||||
self.serializer_class = ser or self.serializer_class |
||||
return super().get_serializer_class() |
||||
|
||||
def initialize_request(self, request, *args, **kwargs): |
||||
request = super().initialize_request(request, *args, **kwargs) |
||||
throttle_scope = self.throttle_scope_map.get(self.action, None) |
||||
cls_throttle_scope = getattr(self, 'throttle_scope', None) |
||||
self.throttle_scope = throttle_scope or cls_throttle_scope or '' |
||||
return request |
||||
|
||||
def get_permissions(self): |
||||
perms = self.permission_map.get(self.action, None) |
||||
if perms and not isinstance(perms, (tuple, list)): |
||||
perms = [perms, ] |
||||
self.permission_classes = perms or self.permission_classes |
||||
return super().get_permissions() |
||||
|
||||
def options(self, request, *args, **kwargs): |
||||
if self.metadata_class is None: |
||||
return self.http_method_not_allowed(request, *args, **kwargs) |
||||
data = self.metadata_class().determine_metadata(request, self) |
||||
data['actions']['GET'] = self.query_metadata |
||||
return Response(data, status=status.HTTP_200_OK) |
||||
|
||||
|
||||
class ExtendedModelViewSet(ExtendViewSet, viewsets.ModelViewSet): |
||||
pass |
||||
|
||||
|
||||
class Base64ImageField(serializers.ImageField): |
||||
|
||||
def to_internal_value(self, data): |
||||
if isinstance(data, six.string_types): |
||||
if 'data:' in data and ';base64,' in data: |
||||
header, data = data.split(';base64,') |
||||
try: |
||||
decoded_file = base64.b64decode(data) |
||||
except TypeError: |
||||
self.fail('invalid_image') |
||||
|
||||
file_name = str(uuid.uuid4())[:12] |
||||
file_extension = self.get_file_extension( |
||||
file_name, decoded_file) |
||||
complete_file_name = "%s.%s" % (file_name, file_extension,) |
||||
data = ContentFile(decoded_file, name=complete_file_name) |
||||
return super().to_internal_value(data) |
||||
|
||||
def get_file_extension(self, file_name, decoded_file): |
||||
extension = imghdr.what(file_name, decoded_file) |
||||
extension = "jpg" if extension == "jpeg" else extension |
||||
return extension |
||||
@ -0,0 +1,35 @@ |
||||
from django.contrib.auth import get_user_model |
||||
|
||||
from rest_framework.permissions import BasePermission |
||||
|
||||
User = get_user_model() |
||||
|
||||
|
||||
class IsAdmin(BasePermission): |
||||
def has_permission(self, request, view): |
||||
return request.user.is_authenticated and ( |
||||
request.user.role == User.ADMIN_ROLE or request.user.is_staff or request.user.is_superuser |
||||
) |
||||
|
||||
|
||||
class IsAdminOrIsSelf(BasePermission): |
||||
def has_object_permission(self, request, view, user): |
||||
return request.user.is_authenticated and ( |
||||
user == request.user or request.user.is_staff or request.user.is_superuser |
||||
) |
||||
|
||||
|
||||
class IsAuthorOrAdmin(BasePermission): |
||||
def has_permission(self, request, view): |
||||
return request.user.is_authenticated and ( |
||||
request.user.role in [ |
||||
User.AUTHOR_ROLE, User.ADMIN_ROLE |
||||
] or request.user.is_staff or request.user.is_superuser |
||||
) |
||||
|
||||
|
||||
class IsAuthorObjectOrAdmin(BasePermission): |
||||
def has_object_permission(self, request, view, obj): |
||||
return request.user.is_authenticated and ( |
||||
request.user.role == User.ADMIN_ROLE or request.user.is_staff or request.user.is_superuser |
||||
) and request.user == obj.author |
||||
@ -0,0 +1,337 @@ |
||||
from django.contrib.auth import get_user_model |
||||
from rest_framework import serializers |
||||
|
||||
from . import Base64ImageField |
||||
|
||||
from apps.course.models import Category, Course, Material, Lesson, Like |
||||
from apps.content.models import ( |
||||
Image, Text, ImageText, Video, |
||||
Gallery, GalleryImage, ImageObject, |
||||
) |
||||
|
||||
User = get_user_model() |
||||
|
||||
|
||||
class ImageObjectSerializer(serializers.ModelSerializer): |
||||
image = Base64ImageField( |
||||
required=True, allow_empty_file=False, allow_null=False, read_only=False, |
||||
) |
||||
|
||||
class Meta: |
||||
model = ImageObject |
||||
fields = ( |
||||
'id', |
||||
'image', |
||||
'created_at', |
||||
'update_at', |
||||
) |
||||
|
||||
read_only_fields = ( |
||||
'id', |
||||
'created_at', |
||||
'update_at', |
||||
) |
||||
|
||||
|
||||
class MaterialSerializer(serializers.ModelSerializer): |
||||
|
||||
class Meta: |
||||
model = Material |
||||
fields = ( |
||||
'id', |
||||
'title', |
||||
'cover', |
||||
'short_description', |
||||
'created_at', |
||||
'update_at', |
||||
) |
||||
|
||||
read_only_fields = ( |
||||
'id', |
||||
'cover', |
||||
'created_at', |
||||
'update_at', |
||||
) |
||||
|
||||
|
||||
class LikeSerializer(serializers.ModelSerializer): |
||||
|
||||
class Meta: |
||||
model = Like |
||||
fields = ( |
||||
'id', |
||||
'user', |
||||
'created_at', |
||||
'update_at', |
||||
) |
||||
|
||||
read_only_fields = ( |
||||
'id', |
||||
'created_at', |
||||
'update_at', |
||||
) |
||||
|
||||
|
||||
class CategorySerializer(serializers.ModelSerializer): |
||||
|
||||
class Meta: |
||||
model = Category |
||||
fields = ( |
||||
'id', |
||||
'title', |
||||
) |
||||
|
||||
read_only_fields = ( |
||||
'id', |
||||
) |
||||
|
||||
|
||||
class CourseSerializer(serializers.ModelSerializer): |
||||
|
||||
class Meta: |
||||
model = Course |
||||
fields = ( |
||||
'id', |
||||
'author', |
||||
'title', |
||||
'short_description', |
||||
'from_author', |
||||
'cover', |
||||
'price', |
||||
'is_infinite', |
||||
'deferred_start_at', |
||||
'category', |
||||
'duration', |
||||
'is_featured', |
||||
'url', |
||||
'status', |
||||
'likes', |
||||
'materials', |
||||
'created_at', |
||||
'update_at', |
||||
'content', |
||||
) |
||||
|
||||
read_only_fields = ( |
||||
'id', |
||||
'cover', |
||||
'content', |
||||
'created_at', |
||||
'update_at', |
||||
) |
||||
|
||||
|
||||
class CourseRetrieveSerializer(CourseSerializer): |
||||
category = CategorySerializer() |
||||
materials = MaterialSerializer(many=True) |
||||
|
||||
|
||||
class LessonSerializer(serializers.ModelSerializer): |
||||
|
||||
class Meta: |
||||
model = Lesson |
||||
fields = ( |
||||
'id', |
||||
'title', |
||||
'short_description', |
||||
'course', |
||||
'cover', |
||||
'created_at', |
||||
'update_at', |
||||
) |
||||
|
||||
read_only_fields = ( |
||||
'id', |
||||
'cover', |
||||
'created_at', |
||||
'update_at', |
||||
) |
||||
|
||||
|
||||
class ImageSerializer(serializers.ModelSerializer): |
||||
|
||||
class Meta: |
||||
model = Image |
||||
fields = ( |
||||
'id', |
||||
'course', |
||||
'lesson', |
||||
'title', |
||||
'position', |
||||
'created_at', |
||||
'update_at', |
||||
) + ('img',) |
||||
|
||||
read_only_fields = ( |
||||
'id', |
||||
'img', |
||||
'created_at', |
||||
'update_at', |
||||
) |
||||
|
||||
|
||||
class TextSerializer(serializers.ModelSerializer): |
||||
|
||||
class Meta: |
||||
model = Text |
||||
fields = ( |
||||
'id', |
||||
'course', |
||||
'lesson', |
||||
'title', |
||||
'position', |
||||
'created_at', |
||||
'update_at', |
||||
) + ('txt',) |
||||
|
||||
read_only_fields = ( |
||||
'id', |
||||
'created_at', |
||||
'update_at', |
||||
) |
||||
|
||||
|
||||
class ImageTextSerializer(serializers.ModelSerializer): |
||||
|
||||
class Meta: |
||||
model = ImageText |
||||
fields = ( |
||||
'id', |
||||
'course', |
||||
'lesson', |
||||
'title', |
||||
'position', |
||||
'created_at', |
||||
'update_at', |
||||
) + ('img', 'txt',) |
||||
|
||||
read_only_fields = ( |
||||
'id', |
||||
'img', |
||||
'created_at', |
||||
'update_at', |
||||
) |
||||
|
||||
|
||||
class VideoSerializer(serializers.ModelSerializer): |
||||
|
||||
class Meta: |
||||
model = Video |
||||
fields = ( |
||||
'id', |
||||
'course', |
||||
'lesson', |
||||
'title', |
||||
'position', |
||||
'created_at', |
||||
'update_at', |
||||
) + ('url',) |
||||
|
||||
read_only_fields = ( |
||||
'id', |
||||
'created_at', |
||||
'update_at', |
||||
) |
||||
|
||||
|
||||
class GallerySerializer(serializers.ModelSerializer): |
||||
|
||||
class Meta: |
||||
model = Gallery |
||||
fields = ( |
||||
'id', |
||||
'course', |
||||
'title', |
||||
'created_at', |
||||
'update_at', |
||||
) |
||||
|
||||
read_only_fields = ( |
||||
'id', |
||||
'created_at', |
||||
'update_at', |
||||
) |
||||
|
||||
|
||||
class GalleryImageSerializer(serializers.ModelSerializer): |
||||
|
||||
class Meta: |
||||
model = GalleryImage |
||||
fields = ( |
||||
'id', |
||||
'gallery', |
||||
'image', |
||||
'created_at', |
||||
'update_at', |
||||
) |
||||
|
||||
read_only_fields = ( |
||||
'id', |
||||
'image', |
||||
'created_at', |
||||
'update_at', |
||||
) |
||||
|
||||
|
||||
class UserSerializer(serializers.ModelSerializer): |
||||
|
||||
class Meta: |
||||
model = User |
||||
fields = ( |
||||
'id', |
||||
'username', |
||||
'email', |
||||
'first_name', |
||||
'last_name', |
||||
'is_staff', |
||||
'is_active', |
||||
'date_joined', |
||||
'role', |
||||
'gender', |
||||
'country', |
||||
'city', |
||||
'about', |
||||
'instagram', |
||||
'facebook', |
||||
'twitter', |
||||
'pinterest', |
||||
'youtube', |
||||
'vkontakte', |
||||
'fb_id', |
||||
'fb_data', |
||||
'is_email_proved', |
||||
'photo', |
||||
) |
||||
|
||||
read_only_fields = ( |
||||
'id', |
||||
'photo', |
||||
'date_joined', |
||||
'is_staff', |
||||
'fb_id', |
||||
'fb_data', |
||||
) |
||||
|
||||
|
||||
class CoverImageSerializer(serializers.Serializer): |
||||
cover = Base64ImageField( |
||||
required=False, allow_empty_file=True, allow_null=True |
||||
) |
||||
|
||||
|
||||
class UserPhotoSerializer(serializers.Serializer): |
||||
photo = Base64ImageField( |
||||
required=False, allow_empty_file=True, allow_null=True |
||||
) |
||||
|
||||
|
||||
class ContentImageSerializer(serializers.Serializer): |
||||
img = Base64ImageField( |
||||
required=False, allow_empty_file=True, allow_null=True |
||||
) |
||||
|
||||
|
||||
class GalleryImageSerializer(serializers.Serializer): |
||||
image = Base64ImageField( |
||||
required=False, allow_empty_file=True, allow_null=True |
||||
) |
||||
@ -0,0 +1,51 @@ |
||||
from django.urls import path, include |
||||
|
||||
from rest_framework import permissions |
||||
from rest_framework.routers import DefaultRouter |
||||
|
||||
from drf_yasg.views import get_schema_view |
||||
from drf_yasg import openapi |
||||
|
||||
from .views import ( |
||||
CategoryViewSet, CourseViewSet, |
||||
MaterialViewSet, LikeViewSet, |
||||
ImageViewSet, TextViewSet, |
||||
ImageTextViewSet, VideoViewSet, |
||||
GalleryViewSet, GalleryImageViewSet, |
||||
UserViewSet, LessonViewSet, ImageObjectViewSet, |
||||
) |
||||
|
||||
router = DefaultRouter() |
||||
router.register(r'courses', CourseViewSet, base_name='courses') |
||||
router.register(r'categories', CategoryViewSet, base_name='categories') |
||||
router.register(r'materials', MaterialViewSet, base_name='materials') |
||||
router.register(r'lessons', LessonViewSet, base_name='lessons') |
||||
router.register(r'likes', LikeViewSet, base_name='likes') |
||||
|
||||
router.register(r'image-objects', ImageObjectViewSet, base_name='image-objects') |
||||
router.register(r'images', ImageViewSet, base_name='images') |
||||
router.register(r'texts', TextViewSet, base_name='texts') |
||||
router.register(r'image-texts', ImageTextViewSet, base_name='image-texts') |
||||
router.register(r'videos', VideoViewSet, base_name='videos') |
||||
router.register(r'galleries', GalleryViewSet, base_name='galleries') |
||||
router.register(r'gallery-images', GalleryImageViewSet, base_name='gallery-images') |
||||
router.register(r'users', UserViewSet, base_name='users') |
||||
|
||||
|
||||
schema_view = get_schema_view( |
||||
openapi.Info( |
||||
title="Lil Sity API", |
||||
default_version='v1', |
||||
description="Routes of Lil City project", |
||||
), |
||||
validators=['flex', 'ssv'], |
||||
public=False, |
||||
permission_classes=(permissions.AllowAny,), |
||||
) |
||||
|
||||
urlpatterns = [ |
||||
path('swagger(<str:format>.json|.yaml)', schema_view.without_ui(cache_timeout=None), name='schema-json'), |
||||
path('swagger/', schema_view.with_ui('swagger', cache_timeout=None), name='schema-swagger-ui'), |
||||
path('redoc/', schema_view.with_ui('redoc', cache_timeout=None), name='schema-redoc'), |
||||
path('', include((router.urls, 'api-root')), name='api-root') |
||||
] |
||||
@ -0,0 +1,269 @@ |
||||
from django.contrib.auth import get_user_model |
||||
|
||||
from rest_framework import status |
||||
from rest_framework import viewsets |
||||
from rest_framework.decorators import detail_route, list_route |
||||
from rest_framework.response import Response |
||||
|
||||
from . import ExtendedModelViewSet |
||||
from .serializers import ( |
||||
CategorySerializer, CourseSerializer, |
||||
MaterialSerializer, LikeSerializer, |
||||
ImageSerializer, TextSerializer, |
||||
ImageTextSerializer, VideoSerializer, |
||||
GallerySerializer, GalleryImageSerializer, |
||||
UserSerializer, UserPhotoSerializer, |
||||
LessonSerializer, ContentImageSerializer, |
||||
GalleryImageSerializer, CoverImageSerializer, |
||||
CourseRetrieveSerializer, ImageObjectSerializer, |
||||
) |
||||
from .permissions import IsAdmin, IsAdminOrIsSelf, IsAuthorOrAdmin, IsAuthorObjectOrAdmin |
||||
|
||||
from apps.course.models import Category, Course, Material, Lesson, Like |
||||
from apps.content.models import ( |
||||
Image, Text, ImageText, Video, |
||||
Gallery, GalleryImage, ImageObject, |
||||
) |
||||
|
||||
User = get_user_model() |
||||
|
||||
|
||||
class ImageObjectViewSet(ExtendedModelViewSet): |
||||
queryset = ImageObject.objects.all() |
||||
serializer_class = ImageObjectSerializer |
||||
# permission_classes = (IsAuthorOrAdmin,) |
||||
|
||||
|
||||
class MaterialViewSet(ExtendedModelViewSet): |
||||
queryset = Material.objects.all() |
||||
serializer_class = MaterialSerializer |
||||
search_fields = ('title', 'short_description',) |
||||
ordering_fields = ('title', 'created_at', 'update_at',) |
||||
# permission_classes = (IsAdmin,) |
||||
|
||||
|
||||
class LikeViewSet(ExtendedModelViewSet): |
||||
queryset = Like.objects.select_related('user').all() |
||||
serializer_class = LikeSerializer |
||||
search_fields = ('user__email', 'user__firstname', 'user__lastname',) |
||||
ordering_fields = ('created_at', 'update_at',) |
||||
# permission_classes = (IsAdmin,) |
||||
|
||||
|
||||
class CategoryViewSet(ExtendedModelViewSet): |
||||
queryset = Category.objects.all() |
||||
serializer_class = CategorySerializer |
||||
search_fields = ('title',) |
||||
ordering_fields = ('title',) |
||||
# permission_classes = (IsAdmin,) |
||||
|
||||
|
||||
class CourseViewSet(ExtendedModelViewSet): |
||||
queryset = Course.objects.select_related( |
||||
'author', 'category' |
||||
).prefetch_related( |
||||
'likes', 'materials', 'content', |
||||
).all() |
||||
serializer_class = CourseSerializer |
||||
serializer_class_map = { |
||||
'list': CourseRetrieveSerializer, |
||||
'retrieve': CourseRetrieveSerializer, |
||||
'upload_photo': CoverImageSerializer, |
||||
} |
||||
filter_fields = ('category', 'status', 'is_infinite', 'is_featured',) |
||||
search_fields = ('author__email', 'title', 'category__title',) |
||||
ordering_fields = ('title', 'created_at', 'update_at',) |
||||
# permission_classes = (IsAuthorObjectOrAdmin,) |
||||
# permission_map = { |
||||
# 'create': IsAuthorOrAdmin, |
||||
# 'delete': IsAdmin, |
||||
# } |
||||
|
||||
@detail_route(methods=['post'], url_path='upload-photo') |
||||
def upload_photo(self, request, pk=None): |
||||
course = self.get_object() |
||||
serializer = self.get_serializer() |
||||
serialized_data = serializer(data=request.data) |
||||
if serialized_data.is_valid(): |
||||
course.cover = serialized_data['cover'] |
||||
course.save() |
||||
return Response({'success': True}) |
||||
else: |
||||
return Response({'success': False}, status=status.HTTP_400_BAD_REQUEST) |
||||
|
||||
|
||||
class LessonViewSet(ExtendedModelViewSet): |
||||
queryset = Lesson.objects.select_related('course').all() |
||||
serializer_class = LessonSerializer |
||||
serializer_class_map = { |
||||
'upload_photo': CoverImageSerializer, |
||||
} |
||||
filter_fields = ('course',) |
||||
search_fields = ('title', 'short_description',) |
||||
ordering_fields = ('title', 'created_at', 'update_at',) |
||||
# permission_classes = (IsAuthorObjectOrAdmin,) |
||||
# permission_map = { |
||||
# 'create': IsAuthorOrAdmin, |
||||
# 'delete': IsAdmin, |
||||
# } |
||||
|
||||
@detail_route(methods=['post'], url_path='upload-photo') |
||||
def upload_photo(self, request, pk=None): |
||||
lesson = self.get_object() |
||||
serializer = self.get_serializer() |
||||
serialized_data = serializer(data=request.data) |
||||
if serialized_data.is_valid(): |
||||
lesson.cover = serialized_data['cover'] |
||||
lesson.save() |
||||
return Response({'success': True}) |
||||
else: |
||||
return Response({'success': False}, status=status.HTTP_400_BAD_REQUEST) |
||||
|
||||
|
||||
class ImageViewSet(ExtendedModelViewSet): |
||||
queryset = Image.objects.select_related( |
||||
'course', 'lesson' |
||||
).all() |
||||
serializer_class = ImageSerializer |
||||
serializer_class_map = { |
||||
'upload_photo': ContentImageSerializer, |
||||
} |
||||
search_fields = ('title',) |
||||
ordering_fields = ('title', 'created_at', 'update_at', 'position',) |
||||
# permission_classes = (IsAuthorOrAdmin,) |
||||
# permission_map = { |
||||
# 'delete': IsAdmin, |
||||
# } |
||||
|
||||
@detail_route(methods=['post'], url_path='upload-photo') |
||||
def upload_photo(self, request, pk=None): |
||||
image = self.get_object() |
||||
serializer = self.get_serializer() |
||||
serialized_data = serializer(data=request.data) |
||||
if serialized_data.is_valid(): |
||||
image.img = serialized_data['img'] |
||||
image.save() |
||||
return Response({'success': True}) |
||||
else: |
||||
return Response({'success': False}, status=status.HTTP_400_BAD_REQUEST) |
||||
|
||||
|
||||
class TextViewSet(ExtendedModelViewSet): |
||||
queryset = Text.objects.select_related( |
||||
'course', 'lesson' |
||||
).all() |
||||
serializer_class = TextSerializer |
||||
search_fields = ('title',) |
||||
ordering_fields = ('title', 'created_at', 'update_at', 'position',) |
||||
# permission_classes = (IsAuthorOrAdmin,) |
||||
# permission_map = { |
||||
# 'delete': IsAdmin, |
||||
# } |
||||
|
||||
|
||||
class ImageTextViewSet(ExtendedModelViewSet): |
||||
queryset = ImageText.objects.select_related( |
||||
'course', 'lesson' |
||||
).all() |
||||
serializer_class = ImageTextSerializer |
||||
serializer_class_map = { |
||||
'upload_photo': ContentImageSerializer, |
||||
} |
||||
search_fields = ('title',) |
||||
ordering_fields = ('title', 'created_at', 'update_at', 'position',) |
||||
# permission_classes = (IsAuthorOrAdmin,) |
||||
# permission_map = { |
||||
# 'delete': IsAdmin, |
||||
# } |
||||
|
||||
@detail_route(methods=['post'], url_path='upload-photo') |
||||
def upload_photo(self, request, pk=None): |
||||
image_text = self.get_object() |
||||
serializer = self.get_serializer() |
||||
serialized_data = serializer(data=request.data) |
||||
if serialized_data.is_valid(): |
||||
image_text.img = serialized_data['img'] |
||||
image_text.save() |
||||
return Response({'success': True}) |
||||
else: |
||||
return Response({'success': False}, status=status.HTTP_400_BAD_REQUEST) |
||||
|
||||
|
||||
class VideoViewSet(ExtendedModelViewSet): |
||||
queryset = Video.objects.select_related( |
||||
'course', 'lesson' |
||||
).all() |
||||
serializer_class = VideoSerializer |
||||
search_fields = ('title',) |
||||
ordering_fields = ('title', 'created_at', 'update_at', 'position',) |
||||
# permission_classes = (IsAuthorOrAdmin,) |
||||
# permission_map = { |
||||
# 'delete': IsAdmin, |
||||
# } |
||||
|
||||
|
||||
class GalleryViewSet(ExtendedModelViewSet): |
||||
queryset = Gallery.objects.select_related('course').all() |
||||
serializer_class = GallerySerializer |
||||
search_fields = ('title',) |
||||
ordering_fields = ('title', 'created_at', 'update_at',) |
||||
# permission_classes = (IsAuthorOrAdmin,) |
||||
# permission_map = { |
||||
# 'delete': IsAdmin, |
||||
# } |
||||
|
||||
|
||||
class GalleryImageViewSet(ExtendedModelViewSet): |
||||
queryset = GalleryImage.objects.select_related('gallery').all() |
||||
serializer_class = GalleryImageSerializer |
||||
serializer_class_map = { |
||||
'upload_photo': GalleryImageSerializer, |
||||
} |
||||
search_fields = ('gallery__title',) |
||||
# permission_classes = (IsAuthorOrAdmin,) |
||||
# permission_map = { |
||||
# 'delete': IsAdmin, |
||||
# } |
||||
|
||||
@detail_route(methods=['post'], url_path='upload-photo') |
||||
def upload_photo(self, request, pk=None): |
||||
gallery_image = self.get_object() |
||||
serializer = self.get_serializer() |
||||
serialized_data = serializer(data=request.data) |
||||
if serialized_data.is_valid(): |
||||
gallery_image.image = serialized_data['image'] |
||||
gallery_image.save() |
||||
return Response({'success': True}) |
||||
else: |
||||
return Response({'success': False}, status=status.HTTP_400_BAD_REQUEST) |
||||
|
||||
|
||||
class UserViewSet(ExtendedModelViewSet): |
||||
queryset = User.objects.all() |
||||
serializer_class = UserSerializer |
||||
serializer_class_map = { |
||||
'upload_photo': UserPhotoSerializer, |
||||
} |
||||
filter_fields = ('is_staff', 'is_active', 'role', |
||||
'gender', 'is_email_proved', 'fb_id',) |
||||
search_fields = ('email', 'first_name', 'last_name', |
||||
'country', 'city', 'fb_id',) |
||||
ordering_fields = ('email', 'first_name', 'last_name', |
||||
'country', 'city', 'date_joined',) |
||||
|
||||
# permission_classes = (IsAdminOrIsSelf,) |
||||
# permission_map = { |
||||
# 'delete': IsAdmin, |
||||
# } |
||||
|
||||
@detail_route(methods=['post'], url_path='upload-photo') |
||||
def upload_photo(self, request, pk=None): |
||||
user = self.get_object() |
||||
serializer = self.get_serializer() |
||||
serialized_data = serializer(data=request.data) |
||||
if serialized_data.is_valid(): |
||||
user.photo = serialized_data['photo'] |
||||
user.save() |
||||
return Response({'success': True}) |
||||
else: |
||||
return Response({'success': False}, status=status.HTTP_400_BAD_REQUEST) |
||||
@ -0,0 +1,66 @@ |
||||
from django.contrib import admin |
||||
from polymorphic.admin import ( |
||||
PolymorphicParentModelAdmin, |
||||
PolymorphicChildModelAdmin, |
||||
PolymorphicChildModelFilter, |
||||
) |
||||
|
||||
from apps.content.models import ( |
||||
Content, Image, Text, ImageText, Video, |
||||
Gallery, GalleryImage, ImageObject, |
||||
) |
||||
|
||||
|
||||
@admin.register(ImageObject) |
||||
class ImageObjectAdmin(admin.ModelAdmin): |
||||
list_display = ( |
||||
'id', |
||||
'image', |
||||
'created_at', |
||||
'update_at', |
||||
) |
||||
|
||||
|
||||
class ContentChildAdmin(PolymorphicChildModelAdmin): |
||||
base_model = Content |
||||
|
||||
|
||||
@admin.register(Image) |
||||
class ImageAdmin(ContentChildAdmin): |
||||
base_model = Image |
||||
|
||||
|
||||
@admin.register(Text) |
||||
class TextAdmin(ContentChildAdmin): |
||||
base_model = Text |
||||
|
||||
|
||||
@admin.register(ImageText) |
||||
class ImageTextAdmin(ContentChildAdmin): |
||||
base_model = ImageText |
||||
|
||||
|
||||
@admin.register(Video) |
||||
class VideoAdmin(ContentChildAdmin): |
||||
base_model = Video |
||||
|
||||
|
||||
@admin.register(Content) |
||||
class ContentAdmin(PolymorphicParentModelAdmin): |
||||
base_model = Content |
||||
child_models = ( |
||||
Image, |
||||
Text, |
||||
ImageText, |
||||
Video |
||||
) |
||||
|
||||
|
||||
@admin.register(Gallery) |
||||
class GalleryAdmin(admin.ModelAdmin): |
||||
pass |
||||
|
||||
|
||||
@admin.register(GalleryImage) |
||||
class GalleryImageAdmin(admin.ModelAdmin): |
||||
pass |
||||
@ -0,0 +1,6 @@ |
||||
from django.apps import AppConfig |
||||
|
||||
|
||||
class ContentConfig(AppConfig): |
||||
name = 'content' |
||||
verbose_name = 'Контент' |
||||
@ -0,0 +1,93 @@ |
||||
# Generated by Django 2.0.2 on 2018-02-05 12:05 |
||||
|
||||
from django.db import migrations, models |
||||
import django.db.models.deletion |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
initial = True |
||||
|
||||
dependencies = [ |
||||
('course', '0020_auto_20180202_1716'), |
||||
('contenttypes', '0002_remove_content_type_name'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.CreateModel( |
||||
name='Content', |
||||
fields=[ |
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
('title', models.CharField(default='', max_length=100, verbose_name='Заголовок')), |
||||
('position', models.PositiveSmallIntegerField(default=1, unique=True, verbose_name='Положение на странице')), |
||||
], |
||||
options={ |
||||
'abstract': False, |
||||
'base_manager_name': 'objects', |
||||
}, |
||||
), |
||||
migrations.CreateModel( |
||||
name='Image', |
||||
fields=[ |
||||
('content_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='content.Content')), |
||||
('img', models.ImageField(upload_to='content/images', verbose_name='Изображение')), |
||||
], |
||||
options={ |
||||
'abstract': False, |
||||
'base_manager_name': 'objects', |
||||
}, |
||||
bases=('content.content',), |
||||
), |
||||
migrations.CreateModel( |
||||
name='ImageText', |
||||
fields=[ |
||||
('content_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='content.Content')), |
||||
('img', models.ImageField(upload_to='content/images', verbose_name='Изображение')), |
||||
('txt', models.TextField(default='', verbose_name='Текст')), |
||||
], |
||||
options={ |
||||
'abstract': False, |
||||
'base_manager_name': 'objects', |
||||
}, |
||||
bases=('content.content',), |
||||
), |
||||
migrations.CreateModel( |
||||
name='Text', |
||||
fields=[ |
||||
('content_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='content.Content')), |
||||
('txt', models.TextField(default='', verbose_name='Текст')), |
||||
], |
||||
options={ |
||||
'abstract': False, |
||||
'base_manager_name': 'objects', |
||||
}, |
||||
bases=('content.content',), |
||||
), |
||||
migrations.CreateModel( |
||||
name='Video', |
||||
fields=[ |
||||
('content_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='content.Content')), |
||||
('url', models.URLField(verbose_name='Ссылка')), |
||||
], |
||||
options={ |
||||
'abstract': False, |
||||
'base_manager_name': 'objects', |
||||
}, |
||||
bases=('content.content',), |
||||
), |
||||
migrations.AddField( |
||||
model_name='content', |
||||
name='course', |
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='course.Course', verbose_name='Курс'), |
||||
), |
||||
migrations.AddField( |
||||
model_name='content', |
||||
name='lesson', |
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='course.Lesson', verbose_name='Урок'), |
||||
), |
||||
migrations.AddField( |
||||
model_name='content', |
||||
name='polymorphic_ctype', |
||||
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_content.content_set+', to='contenttypes.ContentType'), |
||||
), |
||||
] |
||||
@ -0,0 +1,29 @@ |
||||
# Generated by Django 2.0.2 on 2018-02-05 12:12 |
||||
|
||||
from django.db import migrations, models |
||||
import django.utils.timezone |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('content', '0001_initial'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AlterModelOptions( |
||||
name='content', |
||||
options={'verbose_name': 'Контент', 'verbose_name_plural': 'Контент'}, |
||||
), |
||||
migrations.AddField( |
||||
model_name='content', |
||||
name='created_at', |
||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), |
||||
preserve_default=False, |
||||
), |
||||
migrations.AddField( |
||||
model_name='content', |
||||
name='update_at', |
||||
field=models.DateTimeField(auto_now=True), |
||||
), |
||||
] |
||||
@ -0,0 +1,17 @@ |
||||
# Generated by Django 2.0.2 on 2018-02-05 12:46 |
||||
|
||||
from django.db import migrations |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('content', '0002_auto_20180205_1212'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AlterModelOptions( |
||||
name='content', |
||||
options={'ordering': ('-created_at',), 'verbose_name': 'Контент', 'verbose_name_plural': 'Контент'}, |
||||
), |
||||
] |
||||
@ -0,0 +1,45 @@ |
||||
# Generated by Django 2.0.2 on 2018-02-05 13:09 |
||||
|
||||
from django.db import migrations, models |
||||
import django.db.models.deletion |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('course', '0020_auto_20180202_1716'), |
||||
('content', '0003_auto_20180205_1246'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.CreateModel( |
||||
name='Gallery', |
||||
fields=[ |
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
('title', models.CharField(default='', max_length=100, verbose_name='Заголовок')), |
||||
('created_at', models.DateTimeField(auto_now_add=True)), |
||||
('update_at', models.DateTimeField(auto_now=True)), |
||||
('course', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='course.Course', verbose_name='Курс')), |
||||
], |
||||
options={ |
||||
'verbose_name': 'Галерея', |
||||
'verbose_name_plural': 'Галереи', |
||||
'ordering': ('-created_at',), |
||||
}, |
||||
), |
||||
migrations.CreateModel( |
||||
name='GalleryImage', |
||||
fields=[ |
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
('image', models.ImageField(upload_to='content/gallery_images', verbose_name='Изображение')), |
||||
('created_at', models.DateTimeField(auto_now_add=True)), |
||||
('update_at', models.DateTimeField(auto_now=True)), |
||||
('gallery', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='content.Gallery', verbose_name='Галерея')), |
||||
], |
||||
options={ |
||||
'verbose_name': 'Изображение в галерее', |
||||
'verbose_name_plural': 'Изображения в галерее', |
||||
'ordering': ('-created_at',), |
||||
}, |
||||
), |
||||
] |
||||
@ -0,0 +1,31 @@ |
||||
# Generated by Django 2.0.2 on 2018-02-08 05:20 |
||||
|
||||
from django.db import migrations, models |
||||
import django.db.models.deletion |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('content', '0004_gallery_galleryimage'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.CreateModel( |
||||
name='ImageObject', |
||||
fields=[ |
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
('image', models.ImageField(upload_to='content/imageobject', verbose_name='Изображение')), |
||||
], |
||||
), |
||||
migrations.AlterField( |
||||
model_name='content', |
||||
name='course', |
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='content', to='course.Course', verbose_name='Курс'), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='content', |
||||
name='lesson', |
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='content', to='course.Lesson', verbose_name='Урок'), |
||||
), |
||||
] |
||||
@ -0,0 +1,29 @@ |
||||
# Generated by Django 2.0.2 on 2018-02-08 05:51 |
||||
|
||||
from django.db import migrations, models |
||||
import django.utils.timezone |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('content', '0005_auto_20180208_0520'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AlterModelOptions( |
||||
name='imageobject', |
||||
options={'ordering': ('-created_at',), 'verbose_name': 'Объект изображения', 'verbose_name_plural': 'Объекты изображения'}, |
||||
), |
||||
migrations.AddField( |
||||
model_name='imageobject', |
||||
name='created_at', |
||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), |
||||
preserve_default=False, |
||||
), |
||||
migrations.AddField( |
||||
model_name='imageobject', |
||||
name='update_at', |
||||
field=models.DateTimeField(auto_now=True), |
||||
), |
||||
] |
||||
@ -0,0 +1,97 @@ |
||||
from django.db import models |
||||
|
||||
from polymorphic.models import PolymorphicModel |
||||
|
||||
from apps.course.models import Course, Lesson |
||||
|
||||
|
||||
class ImageObject(models.Model): |
||||
image = models.ImageField('Изображение', upload_to='content/imageobject') |
||||
|
||||
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 Content(PolymorphicModel): |
||||
course = models.ForeignKey( |
||||
Course, on_delete=models.CASCADE, |
||||
null=True, blank=True, |
||||
verbose_name='Курс', |
||||
related_name='content', |
||||
) |
||||
lesson = models.ForeignKey( |
||||
Lesson, on_delete=models.CASCADE, |
||||
null=True, blank=True, |
||||
verbose_name='Урок', |
||||
related_name='content', |
||||
) |
||||
title = models.CharField('Заголовок', max_length=100, default='') |
||||
position = models.PositiveSmallIntegerField( |
||||
'Положение на странице', |
||||
default=1, unique=True |
||||
) |
||||
|
||||
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 Image(Content): |
||||
img = models.ImageField('Изображение', upload_to='content/images') |
||||
|
||||
|
||||
class Text(Content): |
||||
txt = models.TextField('Текст', default='') |
||||
|
||||
|
||||
class ImageText(Content): |
||||
img = models.ImageField('Изображение', upload_to='content/images') |
||||
txt = models.TextField('Текст', default='') |
||||
|
||||
|
||||
class Video(Content): |
||||
url = models.URLField('Ссылка') |
||||
|
||||
|
||||
class Gallery(models.Model): |
||||
course = models.ForeignKey( |
||||
Course, on_delete=models.CASCADE, |
||||
null=True, blank=True, |
||||
verbose_name='Курс' |
||||
) |
||||
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 GalleryImage(models.Model): |
||||
gallery = models.ForeignKey( |
||||
Gallery, on_delete=models.CASCADE, |
||||
verbose_name='Галерея' |
||||
) |
||||
image = models.ImageField( |
||||
'Изображение', upload_to='content/gallery_images' |
||||
) |
||||
|
||||
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',) |
||||
@ -0,0 +1,3 @@ |
||||
from django.test import TestCase |
||||
|
||||
# Create your tests here. |
||||
@ -0,0 +1,2 @@ |
||||
from django.shortcuts import render |
||||
|
||||
@ -0,0 +1,48 @@ |
||||
# Generated by Django 2.0.2 on 2018-02-02 17:16 |
||||
|
||||
from django.conf import settings |
||||
from django.db import migrations, models |
||||
import django.db.models.deletion |
||||
import polymorphic_tree.models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('contenttypes', '0002_remove_content_type_name'), |
||||
('course', '0019_auto_20180130_1630'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AlterModelOptions( |
||||
name='coursecomment', |
||||
options={'base_manager_name': 'objects', 'ordering': ('tree_id', 'lft'), 'verbose_name': 'Комментарий курса', 'verbose_name_plural': 'Комментарии курсов'}, |
||||
), |
||||
migrations.AlterModelOptions( |
||||
name='lessoncomment', |
||||
options={'base_manager_name': 'objects', 'ordering': ('tree_id', 'lft'), 'verbose_name': 'Комментарий урока', 'verbose_name_plural': 'Комментарии уроков'}, |
||||
), |
||||
migrations.RemoveField( |
||||
model_name='coursecomment', |
||||
name='parent', |
||||
), |
||||
migrations.RemoveField( |
||||
model_name='lessoncomment', |
||||
name='parent', |
||||
), |
||||
migrations.AddField( |
||||
model_name='comment', |
||||
name='parent', |
||||
field=polymorphic_tree.models.PolymorphicTreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='children', to='course.Comment'), |
||||
), |
||||
migrations.AddField( |
||||
model_name='comment', |
||||
name='polymorphic_ctype', |
||||
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_course.comment_set+', to='contenttypes.ContentType'), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='comment', |
||||
name='author', |
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), |
||||
), |
||||
] |
||||
@ -0,0 +1,17 @@ |
||||
# Generated by Django 2.0.2 on 2018-02-06 06:32 |
||||
|
||||
from django.db import migrations |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('course', '0020_auto_20180202_1716'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AlterModelOptions( |
||||
name='category', |
||||
options={'ordering': ['title'], 'verbose_name': 'Категория', 'verbose_name_plural': 'Категории'}, |
||||
), |
||||
] |
||||
@ -1,5 +1,5 @@ |
||||
{% for cat in category_items %} |
||||
<div class="select__option js-select-option{% if category and category.0 == cat.title %} active{% endif %}" data-category-option data-category-url="{% url 'courses' %}?category={{ cat.title }}"> |
||||
<div class="select__option js-select-option{% if category and category.0 == cat.title %} active{% endif %}" data-category-option data-category-name="{{ cat.title }}" data-category-url="{% url 'courses' %}?category={{ cat.title }}"> |
||||
<div class="select__title">{{ cat.title }}</div> |
||||
</div> |
||||
{% endfor %} |
||||
@ -0,0 +1,49 @@ |
||||
from django import forms |
||||
from django.contrib.auth import get_user_model |
||||
|
||||
User = get_user_model() |
||||
|
||||
|
||||
class UserEditForm(forms.ModelForm): |
||||
# first_name = forms.CharField() |
||||
# last_name = forms.CharField() |
||||
# email = forms.CharField() |
||||
# city = forms.CharField() |
||||
# country = forms.CharField() |
||||
birthday = forms.DateField(input_formats=['%d.%m.%Y']) |
||||
# gender = forms.ChoiceField(choices=User.GENDER_CHOICES, required=False) |
||||
gender = forms.CharField(required=False) |
||||
# about = forms.CharField() |
||||
old_password = forms.CharField(required=False) |
||||
new_password1 = forms.CharField(required=False) |
||||
new_password2 = forms.CharField(required=False) |
||||
instagram = forms.URLField(required=False) |
||||
facebook = forms.URLField(required=False) |
||||
twitter = forms.URLField(required=False) |
||||
pinterest = forms.URLField(required=False) |
||||
youtube = forms.URLField(required=False) |
||||
vkontakte = forms.URLField(required=False) |
||||
photo = forms.ImageField(required=False) |
||||
|
||||
class Meta: |
||||
model = User |
||||
fields = ( |
||||
'first_name', |
||||
'last_name', |
||||
'email', |
||||
'city', |
||||
'country', |
||||
'birthday', |
||||
'gender', |
||||
'about', |
||||
'old_password', |
||||
'new_password1', |
||||
'new_password2', |
||||
'instagram', |
||||
'facebook', |
||||
'twitter', |
||||
'pinterest', |
||||
'youtube', |
||||
'vkontakte', |
||||
'photo', |
||||
) |
||||
@ -0,0 +1,18 @@ |
||||
# Generated by Django 2.0.2 on 2018-02-06 13:22 |
||||
|
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('user', '0004_auto_20180129_1259'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AddField( |
||||
model_name='user', |
||||
name='birthday', |
||||
field=models.DateField(blank=True, null=True, verbose_name='День рождения'), |
||||
), |
||||
] |
||||
@ -0,0 +1,18 @@ |
||||
# Generated by Django 2.0.2 on 2018-02-06 13:52 |
||||
|
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('user', '0005_user_birthday'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AlterField( |
||||
model_name='user', |
||||
name='about', |
||||
field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='О себе'), |
||||
), |
||||
] |
||||
@ -0,0 +1,23 @@ |
||||
# Generated by Django 2.0.2 on 2018-02-07 08:08 |
||||
|
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('user', '0006_auto_20180206_1352'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AlterField( |
||||
model_name='user', |
||||
name='city', |
||||
field=models.CharField(blank=True, max_length=85, null=True, verbose_name='Город'), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='user', |
||||
name='country', |
||||
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='Страна'), |
||||
), |
||||
] |
||||
@ -0,0 +1,209 @@ |
||||
{% extends "templates/lilcity/index.html" %} {% load static %} {% block content %} |
||||
<div class="section section_gray section_menu"> |
||||
<div class="section__center center center_xs"> |
||||
<div class="menu"> |
||||
<a class="menu__link active" href="{% url 'user-edit' user.id %}">Профиль</a> |
||||
<a class="menu__link" href="#">Уведомления</a> |
||||
<a class="menu__link" href="#">Платежи</a> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
{% comment %} |
||||
<!-- <div class="section section_confirm"> |
||||
<div class="section__center center center_xs"> |
||||
<div class="confirm"> |
||||
<div class="confirm__title title">Подтверждение почты</div> |
||||
<div class="confirm__content">На электронный адрес |
||||
<strong>sasha@lil.city</strong> отправлено письмо с кодом подтверждения. Введите код, чтобы подтвердить почту.</div> |
||||
<div class="confirm__form"> |
||||
<div class="confirm__field field field_code"> |
||||
<div class="field__wrap"> |
||||
<input class="field__input" type="text" placeholder="Введите код подтверждения"> |
||||
</div> |
||||
</div> |
||||
<button class="confirm__btn btn btn_dark">ПОДТВЕРДИТЬ</button> |
||||
</div> |
||||
<div class="confirm__content">Если у вас нет кода или письмо где-то затерялось, вы можете получить новый код подтверждения. Отправить новый код?</div> |
||||
</div> |
||||
</div> |
||||
</div> --> |
||||
{% endcomment %} |
||||
{% if messages %} |
||||
<div class="section section_gray section_menu"> |
||||
<div class="section__center center center_xs"> |
||||
{% for message in messages %} |
||||
<div class="message message_{{ message.tags }}">{{ message }}</div> |
||||
{% endfor %} |
||||
</div> |
||||
</div> |
||||
{% endif %} |
||||
{{form.errors}} |
||||
<div class="section section_gray"> |
||||
<div class="section__center center center_xs"> |
||||
<div class="form"> |
||||
<form action="" method="POST" enctype="multipart/form-data"> |
||||
{% csrf_token %} |
||||
<div class="form__group"> |
||||
<div class="form__title">Личные данные</div> |
||||
<div class="form__ava ava"> |
||||
{% if user.photo %} |
||||
<img id="photo" class="ava__pic" src="{{user.photo.url}}"> |
||||
{% else %} |
||||
<img id="photo" class="ava__pic" src="{% static 'img/user.jpg' %}"> |
||||
{% endif %} |
||||
<input name="photo" class="ava__input" type="file" accept='image/*' onchange='openFile(event)'> |
||||
<div class="ava__icon"> |
||||
<svg class="icon icon-photo"> |
||||
<use xlink:href="{% static 'img/sprite.svg' %}#icon-photo"></use> |
||||
</svg> |
||||
</div> |
||||
</div> |
||||
<div class="form__fieldset"> |
||||
<div class="form__field field"> |
||||
<div class="field__label">ИМЯ</div> |
||||
<div class="field__wrap"> |
||||
<input name='first_name' class="field__input" type="text" placeholder="Имя" value="{{ user.first_name }}"> |
||||
</div> |
||||
</div> |
||||
<div class="form__field field"> |
||||
<div class="field__label">ФАМИЛИЯ</div> |
||||
<div class="field__wrap"> |
||||
<input name='last_name' class="field__input" type="text" placeholder="Фамилия" value="{{ user.last_name }}"> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="form__field field"> |
||||
<div class="field__label">Почта</div> |
||||
<div class="field__wrap"> |
||||
<input name='email' class="field__input" type="email" placeholder="Почта" value="{{ user.email }}"> |
||||
</div> |
||||
</div> |
||||
<div class="form__fieldset"> |
||||
<div class="form__field field"> |
||||
<div class="field__label">ГОРОД</div> |
||||
<div class="field__wrap"> |
||||
<input name='city' class="field__input" type="text" placeholder="Город" value="{% if user.city %}{{ user.city }}{% endif %}"> |
||||
</div> |
||||
</div> |
||||
<div class="form__field field"> |
||||
<div class="field__label">СТРАНА</div> |
||||
<div class="field__wrap"> |
||||
<input name='country' class="field__input" type="text" placeholder="Страна" value="{% if user.country %}{{ user.country }}{% endif %}"> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="form__fieldset"> |
||||
<div class="form__field field"> |
||||
<div class="field__label">ДАТА РОЖДЕНИЯ</div> |
||||
<div class="field__wrap"> |
||||
<input name='birthday' class="field__input" type="text" placeholder="dd.mm.yyyy" value="{% if user.birthday %}{{ user.birthday | date:'d.m.Y' }}{% endif %}"> |
||||
</div> |
||||
</div> |
||||
<div class="form__field field"> |
||||
<div class="field__label">ПОЛ</div> |
||||
<div class="field__wrap"> |
||||
<div class="field__select select js-select{% if user.gender and user.gender != 'n' %} selected{% endif %}"> |
||||
<div class="select__head js-select-head"> |
||||
{% if user.gender == 'f' %}Ж{% elif user.gender == 'm' %}M{% else %}М / Ж{% endif %} |
||||
</div> |
||||
<div class="select__drop js-select-drop"> |
||||
<div class="select__option js-select-option" data-gender-option data-gender="m"> |
||||
<div class="select__title">М</div> |
||||
</div> |
||||
<div class="select__option js-select-option" data-gender-option data-gender="f"> |
||||
<div class="select__title">Ж</div> |
||||
</div> |
||||
</div> |
||||
<input id="gender" name='gender' class="select__input" type="hidden"> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="form__field field"> |
||||
<div class="field__label">О себе</div> |
||||
<div class="field__wrap"> |
||||
<textarea name='about' class="field__textarea" placeholder="Расскажите о себе и своем опыте">{% if user.about %}{{ user.about }}{% endif %}</textarea> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="form__group"> |
||||
<div class="form__title">Пароль</div> |
||||
<div class="form__field field"> |
||||
<div class="field__label">ТЕКУЩИЙ ПАРОЛЬ</div> |
||||
<div class="field__wrap"> |
||||
<input name='old_password' class="field__input" type="password" placeholder="Введите текущий пароль"> |
||||
</div> |
||||
</div> |
||||
<div class="form__field field"> |
||||
<div class="field__label">НОВЫЙ ПАРОЛЬ</div> |
||||
<div class="field__wrap"> |
||||
<input name='new_password1' class="field__input" type="password" placeholder="Введите новый пароль"> |
||||
</div> |
||||
</div> |
||||
<div class="form__field field"> |
||||
<div class="field__label">ПОДТВЕРДИТЬ НОВЫЙ ПАРОЛЬ</div> |
||||
<div class="field__wrap"> |
||||
<input name='new_password2' class="field__input" type="password" placeholder="Подтвердите новый пароль"> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="form__group"> |
||||
<div class="form__title">Соцсети</div> |
||||
<div class="form__field field"> |
||||
<div class="field__label">INSTAGRAM</div> |
||||
<div class="field__wrap"> |
||||
<input name='instagram' class="field__input" type="text" placeholder="https://instagram.com/school.lil.city" value="{{ user.instagram }}"> |
||||
</div> |
||||
</div> |
||||
<div class="form__field field"> |
||||
<div class="field__label">FACEBOOK</div> |
||||
<div class="field__wrap"> |
||||
<input name='facebook' class="field__input" type="text" placeholder="https://facebook.com/lilcitycompany" value="{{ user.facebook }}"> |
||||
</div> |
||||
</div> |
||||
<div class="form__field field"> |
||||
<div class="field__label">TWITTER</div> |
||||
<div class="field__wrap"> |
||||
<input name='twitter' class="field__input" type="text" placeholder="https://twitter.com/lilcitycompany" value="{{ user.twitter }}"> |
||||
</div> |
||||
</div> |
||||
<div class="form__field field"> |
||||
<div class="field__label">PINTEREST</div> |
||||
<div class="field__wrap"> |
||||
<input name='pinterest' class="field__input" type="text" placeholder="https://pinterest.com/lilcitycompany" value="{{ user.pinterest }}"> |
||||
</div> |
||||
</div> |
||||
<div class="form__field field"> |
||||
<div class="field__label">YOUTUBE</div> |
||||
<div class="field__wrap"> |
||||
<input name='youtube' class="field__input" type="text" placeholder="https://youtube.com/lilcitycompany" value="{{ user.youtube }}"> |
||||
</div> |
||||
</div> |
||||
<div class="form__field field"> |
||||
<div class="field__label">VKONTAKTE</div> |
||||
<div class="field__wrap"> |
||||
<input name='vkontakte' class="field__input" type="text" placeholder="https://vk.com/lilcitycompany" value="{{ user.vkontakte }}"> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="form__foot"> |
||||
<button type="submit" class="form__btn btn btn_md">СОХРАНИТЬ</button> |
||||
</div> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<script> |
||||
var openFile = function(file) { |
||||
var input = file.target; |
||||
|
||||
var reader = new FileReader(); |
||||
reader.onload = function(){ |
||||
var dataURL = reader.result; |
||||
var output = document.getElementById('photo'); |
||||
output.src = dataURL; |
||||
}; |
||||
reader.readAsDataURL(input.files[0]); |
||||
}; |
||||
</script> |
||||
{% endblock content %} |
||||
@ -0,0 +1,21 @@ |
||||
import os |
||||
from celery import Celery |
||||
|
||||
# set the default Django settings module for the 'celery' program. |
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings') |
||||
|
||||
app = Celery('project') |
||||
|
||||
# Using a string here means the worker don't have to serialize |
||||
# the configuration object to child processes. |
||||
# - namespace='CELERY' means all celery-related configuration keys |
||||
# should have a `CELERY_` prefix. |
||||
app.config_from_object('project.celery_settings') |
||||
|
||||
# Load task modules from all registered Django app configs. |
||||
app.autodiscover_tasks() |
||||
|
||||
|
||||
@app.task(bind=True) |
||||
def debug_task(self): |
||||
return f'Request: {self.request}' |
||||
@ -0,0 +1,3 @@ |
||||
broker_url = 'redis://redis:6379/0' |
||||
result_backend = 'redis://redis:6379/1' |
||||
task_serializer = 'json' |
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 793 B |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 793 B |
@ -0,0 +1,14 @@ |
||||
import $ from 'jquery'; |
||||
|
||||
$(document).ready(function () { |
||||
// Обработчик выбора пола
|
||||
let genderInput = $('#gender') |
||||
|
||||
$('div.js-select-option[data-gender-option]').on('click', function (e) { |
||||
e.preventDefault(); |
||||
const currentGender = $(this).attr('data-gender'); |
||||
$('[data-gender]').removeClass('active'); |
||||
$(`[data-gender=${currentGender}]`).addClass('active'); |
||||
genderInput.val(currentGender) |
||||
}); |
||||
}) |
||||
Loading…
Reference in new issue