diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..883f1f80 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{py,rst,ini,ino}] +indent_style = space +indent_size = 4 + +[*.{html,css,scss,js,json,yml,vue}] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab diff --git a/.gitignore b/.gitignore index 38adbfe2..a250ef4d 100644 --- a/.gitignore +++ b/.gitignore @@ -86,7 +86,7 @@ celerybeat-schedule .env .venv env/ -venv/ +venv*/ ENV/ env.bak/ venv.bak/ diff --git a/api/v1/serializers/course.py b/api/v1/serializers/course.py index bacd4b30..8139d8fa 100644 --- a/api/v1/serializers/course.py +++ b/api/v1/serializers/course.py @@ -72,6 +72,11 @@ class CategorySerializer(serializers.ModelSerializer): ) +class CourseBulkChangeCategorySerializer(serializers.Serializer): + old_category = serializers.IntegerField() + new_category = serializers.IntegerField() + + class CourseCreateSerializer(DispatchContentMixin, DispatchGalleryMixin, DispatchMaterialMixin, diff --git a/api/v1/serializers/school.py b/api/v1/serializers/school.py new file mode 100644 index 00000000..29e76ad1 --- /dev/null +++ b/api/v1/serializers/school.py @@ -0,0 +1,22 @@ +from rest_framework import serializers + +from apps.school.models import SchoolSchedule + + +class SchoolScheduleSerializer(serializers.ModelSerializer): + + class Meta: + model = SchoolSchedule + fields = ( + 'id', + 'weekday', + 'title', + 'description', + 'materials', + 'age', + 'month_price', + ) + + read_only_fields = ( + 'id', + ) diff --git a/api/v1/urls.py b/api/v1/urls.py index 305b053d..171b7794 100644 --- a/api/v1/urls.py +++ b/api/v1/urls.py @@ -14,6 +14,7 @@ from .views import ( ImageTextViewSet, VideoViewSet, GalleryViewSet, GalleryImageViewSet, UserViewSet, LessonViewSet, ImageObjectViewSet, + SchoolScheduleViewSet, ) router = DefaultRouter() @@ -30,24 +31,27 @@ 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'school-schedules', SchoolScheduleViewSet, base_name='school-schedules') + 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,), + 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(.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('api-token-auth/', ObtainToken.as_view(), name='api-token-auth'), - path('', include((router.urls, 'api-root')), name='api-root'), + path('swagger(.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('api-token-auth/', ObtainToken.as_view(), name='api-token-auth'), + path('', include((router.urls, 'api-root')), name='api-root'), ] diff --git a/api/v1/views.py b/api/v1/views.py index 16a0fc6f..2d5add55 100644 --- a/api/v1/views.py +++ b/api/v1/views.py @@ -10,6 +10,7 @@ from . import ExtendedModelViewSet from .serializers.course import ( CategorySerializer, LikeSerializer, CourseSerializer, CourseCreateSerializer, + CourseBulkChangeCategorySerializer, MaterialSerializer, MaterialCreateSerializer, LessonSerializer, LessonCreateSerializer, ) @@ -22,6 +23,7 @@ from .serializers.content import ( GalleryImageSerializer, GalleryImageCreateSerializer, ImageObjectSerializer, ) +from .serializers.school import SchoolScheduleSerializer from .serializers.user import ( UserSerializer, UserPhotoSerializer, ) @@ -33,7 +35,7 @@ from apps.content.models import ( Image, Text, ImageText, Video, Gallery, GalleryImage, ImageObject, ) - +from apps.school.models import SchoolSchedule User = get_user_model() @@ -64,7 +66,7 @@ class LikeViewSet(ExtendedModelViewSet): class CategoryViewSet(ExtendedModelViewSet): - queryset = Category.objects.all() + queryset = Category.objects.order_by('-id') serializer_class = CategorySerializer search_fields = ('title',) ordering_fields = ('title',) @@ -82,6 +84,7 @@ class CourseViewSet(ExtendedModelViewSet): 'list': CourseSerializer, 'retrieve': CourseSerializer, 'draft': CourseSerializer, + 'change_category_bulk': CourseBulkChangeCategorySerializer, } filter_fields = ('category', 'status', 'is_infinite', 'is_featured',) search_fields = ('author__email', 'title', 'category__title',) @@ -99,6 +102,40 @@ class CourseViewSet(ExtendedModelViewSet): serialized_data = serializer(instance=drafts.last()) return Response(serialized_data.data) + @list_route(methods=['patch'], url_path='change-category-bulk') + def change_category_bulk(self, request): + serializer_class = self.get_serializer_class() + serializer = serializer_class(data=request.data) + if serializer.is_valid(): + old_category_id = serializer.validated_data['old_category'] + new_category_id = serializer.validated_data['new_category'] + try: + old_category = Category.objects.get(id=old_category_id) + except Category.DoesNotExist: + return Response( + {'success': False, 'detail': f'Category with id {old_category_id} not found'}, + status=status.HTTP_400_BAD_REQUEST, + ) + try: + new_category = Category.objects.get(id=new_category_id) + except Category.DoesNotExist: + return Response( + {'success': False, 'detail': f'Category with id {new_category_id} not found'}, + status=status.HTTP_400_BAD_REQUEST, + ) + + c = Course.objects.filter( + category=old_category, + ).update( + category=new_category + ) + return Response({'success': True}) + else: + return Response( + {'success': False}, + status=status.HTTP_400_BAD_REQUEST, + ) + class LessonViewSet(ExtendedModelViewSet): queryset = Lesson.objects.select_related( @@ -249,3 +286,9 @@ class UserViewSet(ExtendedModelViewSet): return Response({'success': True}) else: return Response({'success': False}, status=status.HTTP_400_BAD_REQUEST) + + +class SchoolScheduleViewSet(ExtendedModelViewSet): + queryset = SchoolSchedule.objects.all() + serializer_class = SchoolScheduleSerializer + permission_classes = (IsAdmin,) diff --git a/apps/content/tasks.py b/apps/content/tasks.py new file mode 100644 index 00000000..898266bf --- /dev/null +++ b/apps/content/tasks.py @@ -0,0 +1,19 @@ +import json +from time import sleep +from project.celery import app +from constance import config +from InstagramAPI import InstagramAPI + + +@app.task +def retrieve_photos(): + instagram = InstagramAPI( + config.INSTAGRAM_CLIENT_LOGIN, + config.INSTAGRAM_CLIENT_PASSWORD, + ) + instagram.login() + sleep(1) + if instagram.isLoggedIn and instagram.getHashtagFeed(config.INSTAGRAM_RESULTS_TAG): + with open('s.json', 'w') as f: + f.write(json.dumps(instagram.LastJson)) + return instagram.LastJson diff --git a/apps/course/templates/course/school.html b/apps/course/templates/course/school.html new file mode 100644 index 00000000..e0eaecf7 --- /dev/null +++ b/apps/course/templates/course/school.html @@ -0,0 +1,34 @@ +
+
+
+
+
Январь
+
Осталось 14 дней
+
+ +
+
+
+ + +
+
+ {% for course in course_items %} +
+
Понедельник
+
Пластилиновая живопись.
+
+

Научимся смешивать цвета, получать красивые оттенки и создавать картины из пластилина разными техниками. Узнаем как хранить и ухаживать за такими работами.

+
+
+ {% endfor %} +
+
\ No newline at end of file diff --git a/apps/school/__init__.py b/apps/school/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/school/admin.py b/apps/school/admin.py new file mode 100644 index 00000000..ee2e42ca --- /dev/null +++ b/apps/school/admin.py @@ -0,0 +1,12 @@ +from django.contrib import admin + +from .models import SchoolSchedule + + +@admin.register(SchoolSchedule) +class SchoolScheduleAdmin(admin.ModelAdmin): + list_display = ( + 'weekday', + 'title', + 'month_price', + ) diff --git a/apps/school/apps.py b/apps/school/apps.py new file mode 100644 index 00000000..5ad827d7 --- /dev/null +++ b/apps/school/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class SchoolConfig(AppConfig): + name = 'school' diff --git a/apps/school/migrations/0001_initial.py b/apps/school/migrations/0001_initial.py new file mode 100644 index 00000000..08113b95 --- /dev/null +++ b/apps/school/migrations/0001_initial.py @@ -0,0 +1,30 @@ +# Generated by Django 2.0.2 on 2018-02-20 14:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='SchoolSchedule', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('weekday', models.PositiveSmallIntegerField(choices=[(1, 'понедельник'), (2, 'вторник'), (3, 'среда'), (4, 'четверг'), (5, 'пятница'), (6, 'суббота'), (7, 'воскресенье')], verbose_name='День недели')), + ('title', models.CharField(db_index=True, default='', max_length=100, verbose_name='Заголовок')), + ('description', models.TextField(verbose_name='Описание')), + ('materials', models.TextField(verbose_name='Материалы')), + ('month_price', models.DecimalField(decimal_places=2, default=0, max_digits=8, verbose_name='Цена')), + ], + options={ + 'verbose_name': 'Рассписание', + 'verbose_name_plural': 'Рассписания', + 'ordering': ('weekday',), + }, + ), + ] diff --git a/apps/school/migrations/0002_schoolschedule_age.py b/apps/school/migrations/0002_schoolschedule_age.py new file mode 100644 index 00000000..3f15d01c --- /dev/null +++ b/apps/school/migrations/0002_schoolschedule_age.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.2 on 2018-02-21 08:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('school', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='schoolschedule', + name='age', + field=models.PositiveSmallIntegerField(default=0, verbose_name='Возраст'), + ), + ] diff --git a/apps/school/migrations/0003_auto_20180221_0901.py b/apps/school/migrations/0003_auto_20180221_0901.py new file mode 100644 index 00000000..3df9e183 --- /dev/null +++ b/apps/school/migrations/0003_auto_20180221_0901.py @@ -0,0 +1,17 @@ +# Generated by Django 2.0.2 on 2018-02-21 09:01 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('school', '0002_schoolschedule_age'), + ] + + operations = [ + migrations.AlterModelOptions( + name='schoolschedule', + options={'ordering': ('weekday',), 'verbose_name': 'Расписание', 'verbose_name_plural': 'Расписания'}, + ), + ] diff --git a/apps/school/migrations/__init__.py b/apps/school/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/school/models.py b/apps/school/models.py new file mode 100644 index 00000000..9eb3d83e --- /dev/null +++ b/apps/school/models.py @@ -0,0 +1,24 @@ +from django.db import models + + +class SchoolSchedule(models.Model): + WEEKDAY_CHOICES = ( + (1, 'понедельник'), + (2, 'вторник'), + (3, 'среда'), + (4, 'четверг'), + (5, 'пятница'), + (6, 'суббота'), + (7, 'воскресенье'), + ) + weekday = models.PositiveSmallIntegerField('День недели', choices=WEEKDAY_CHOICES) + title = models.CharField('Заголовок', default='', max_length=100, db_index=True) + description = models.TextField('Описание') + materials = models.TextField('Материалы') + age = models.PositiveSmallIntegerField('Возраст', default=0) + month_price = models.DecimalField('Цена', max_digits=8, decimal_places=2, default=0) + + class Meta: + ordering = ('weekday',) + verbose_name = 'Расписание' + verbose_name_plural = 'Расписания' diff --git a/apps/school/views.py b/apps/school/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/apps/school/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/apps/user/templates/user/profile.html b/apps/user/templates/user/profile.html index 9af22213..aeec9433 100644 --- a/apps/user/templates/user/profile.html +++ b/apps/user/templates/user/profile.html @@ -71,6 +71,7 @@ + {% comment %}