diff --git a/apps/auth/views.py b/apps/auth/views.py index 03bafabb..3d4ba19d 100644 --- a/apps/auth/views.py +++ b/apps/auth/views.py @@ -5,6 +5,7 @@ from facepy.exceptions import FacepyError from django.contrib.auth import get_user_model, logout, login, views from django.contrib.auth.forms import AuthenticationForm +from django.core.files.base import ContentFile from django.http import JsonResponse from django.urls import reverse_lazy from django.utils.decorators import method_decorator @@ -135,27 +136,42 @@ class FacebookLoginOrRegistration(View): graph = GraphAPI(access_token) try: data = graph.get('/me?fields=email, first_name, last_name') + photo_data = graph.get('/me/picture?height=120') except FacepyError: return JsonResponse({"success": False}) fb_id = data.get('id') - lilcity_user_settings = User.objects.filter(fb_id=fb_id) - if lilcity_user_settings.count(): - login(requests, user=lilcity_user_settings[0]) - return JsonResponse({"success": True}) - - email = requests.POST.get('email') or data.get('email') - if not email: - return JsonResponse({"success": False, - "errors": {"email": 'is field required'} - }) + try: + user = User.objects.get(fb_id=fb_id) + except User.DoesNotExist: + email = requests.POST.get('email') or data.get('email') + if not email: + return JsonResponse({"success": False, + "errors": {"email": 'is field required'} + }) + else: + try: + user = User.objects.get(email=email) + except User.DoesNotExist: + first_name = data.get('first_name', '') + last_name = data.get('last_name', '') + + user = User.objects.create_user(username=email, email=email, first_name=first_name, last_name=last_name, password=uuid4().hex) + user.is_email_proved = True + user.fb_id = fb_id + if photo_data: + photo = ContentFile(photo_data) + fname = str(fb_id) + '.jpg' + user.photo.save(fname, photo, save=True) + user.save() + + login(requests, user=user) + return JsonResponse({"success": True}) else: - first_name = data.get('first_name', '') - last_name = data.get('last_name', '') - - user = User.objects.create_user(username=email, email=email, first_name=first_name, last_name=last_name, password=uuid4().hex) - user.is_email_proved = True - user.fb_id = fb_id - user.save() + if not user.photo and photo_data: + photo = ContentFile(photo_data) + fname = str(fb_id) + '.jpg' + user.photo.save(fname, photo, save=True) + login(requests, user=user) return JsonResponse({"success": True}) diff --git a/apps/course/migrations/0020_auto_20180202_1716.py b/apps/course/migrations/0020_auto_20180202_1716.py new file mode 100644 index 00000000..1b7f63d8 --- /dev/null +++ b/apps/course/migrations/0020_auto_20180202_1716.py @@ -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), + ), + ] diff --git a/apps/course/models.py b/apps/course/models.py index 2cbc6d27..954a8160 100644 --- a/apps/course/models.py +++ b/apps/course/models.py @@ -3,7 +3,7 @@ from django.db import models from django.utils import timezone from django.contrib.auth import get_user_model -from mptt.models import MPTTModel, TreeForeignKey +from polymorphic_tree.models import PolymorphicMPTTModel, PolymorphicTreeForeignKey from .manager import CategoryQuerySet @@ -123,9 +123,10 @@ class Material(models.Model): ordering = ('title',) -class Comment(MPTTModel): - author = models.ForeignKey(User, on_delete=models.PROTECT) +class Comment(PolymorphicMPTTModel): content = models.TextField('Текст комментария', default='') + author = models.ForeignKey(User, on_delete=models.CASCADE) + parent = PolymorphicTreeForeignKey('self', null=True, blank=True, related_name='children', db_index=True, on_delete=models.PROTECT) created_at = models.DateTimeField(auto_now_add=True) update_at = models.DateTimeField(auto_now=True) @@ -142,11 +143,9 @@ class Comment(MPTTModel): class MPTTMeta: order_insertion_by = ['-created_at'] - abstract = True class CourseComment(Comment): - parent = TreeForeignKey('self', null=True, blank=True, related_name='children', db_index=True, on_delete=models.PROTECT) course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='comments') class Meta(Comment.Meta): @@ -155,7 +154,6 @@ class CourseComment(Comment): class LessonComment(Comment): - parent = TreeForeignKey('self', null=True, blank=True, related_name='children', db_index=True, on_delete=models.PROTECT) lesson = models.ForeignKey(Lesson, on_delete=models.CASCADE, related_name='comments') class Meta(Comment.Meta): diff --git a/apps/course/templates/course/_items.html b/apps/course/templates/course/_items.html index af350f5c..1866fbcb 100644 --- a/apps/course/templates/course/_items.html +++ b/apps/course/templates/course/_items.html @@ -6,27 +6,27 @@ data-course data-course-id={{ course.id }} {% if course.is_deferred_start %}data-future-course data-future-course-time={{ course.deferred_start_at.timestamp }}{% endif %} > - - -
Подробнее
- {% if course.is_featured %} -
- {% endif %} {% if course.is_deferred_start %} -
-
Курс начнется:
-
{{ course.deferred_start_at_humanize }}
+
+ +
Подробнее
+ {% if course.is_featured %} +
+ {% endif %} {% if course.is_deferred_start %} +
+
Курс начнется:
+
{{ course.deferred_start_at_humanize }}
{% endif %} -
-
+ +
{{ course.category | upper }} {% if not course.is_free %}
{{ course.price|floatformat:"-2" }}₽
{% endif %}
- {{ course.title }} + {{ course.title }}
{{ course.short_description }}
diff --git a/apps/course/templates/course/course.html b/apps/course/templates/course/course.html index cf994653..2176d023 100644 --- a/apps/course/templates/course/course.html +++ b/apps/course/templates/course/course.html @@ -7,7 +7,7 @@
- +
diff --git a/apps/course/templates/course/inclusion/category_menu_items.html b/apps/course/templates/course/inclusion/category_menu_items.html index 80a82615..b09f7a8a 100644 --- a/apps/course/templates/course/inclusion/category_menu_items.html +++ b/apps/course/templates/course/inclusion/category_menu_items.html @@ -1,5 +1,5 @@ -{% for category in category_items %} - -
{{ category.title }}
+{% for cat in category_items %} +
+
{{ cat.title }}
{% endfor %} \ No newline at end of file diff --git a/apps/course/templates/course/result.html b/apps/course/templates/course/result.html index d639b043..ed95d042 100644 --- a/apps/course/templates/course/result.html +++ b/apps/course/templates/course/result.html @@ -22,9 +22,11 @@
{% include "./course_items.html" %}
+ {% if next_page %}
+ {% endif %} {% else %}
К сожалению, по вашему запросу ничего не найдено.
{% endif %} diff --git a/apps/course/views.py b/apps/course/views.py index 78dcce12..c3f6f05b 100644 --- a/apps/course/views.py +++ b/apps/course/views.py @@ -90,7 +90,7 @@ def coursecomment(request, course_id): @login_required @csrf_exempt @require_http_methods(['POST']) -def lessoncomment(request, Lesson): +def lessoncomment(request, lesson_id): try: lesson = Lesson.objects.get(id=lesson_id) except Lesson.DoesNotExist: @@ -111,7 +111,7 @@ def lessoncomment(request, Lesson): lessoncomment = LessonComment.objects.create( author=request.user, content=comment, - course=course, + lesson=lesson, ) else: try: @@ -119,13 +119,13 @@ def lessoncomment(request, Lesson): except LessonComment.DoesNotExist: return JsonResponse({ 'success': False, - 'errors': ['CourseComment with id f{reply_to} not found'] + 'errors': ['LessonComment with id f{reply_to} not found'] }, status=400) else: lessoncomment = LessonComment.objects.create( author=request.user, content=comment, - course=course, + lesson=lesson, parent=_lessoncomment, ) ctx = {'node': lessoncomment, 'user': request.user} @@ -141,6 +141,11 @@ class CourseView(DetailView): context_object_name = 'course' template_name = 'course/course.html' + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['next'] = self.request.GET.get('next', None) + return context + class CoursesView(ListView): model = Course @@ -153,7 +158,7 @@ class CoursesView(ListView): if request.is_ajax(): context = self.get_context_data() template_name = self.get_template_names() - html = loader.render_to_string(template_name, context) + html = loader.render_to_string(template_name, context, request=request) is_paginated = context.get('is_paginated') if is_paginated: page_obj = context.get('page_obj') @@ -207,11 +212,17 @@ class SearchView(CoursesView): queryset = queryset.none() return queryset - def get_context_data(self): - context = super().get_context_data() + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) context['q'] = self.request.GET.get('q', None) or '' - context['page'] = self.request.GET.get('page', None) or 1 - context['next_page'] = str(int(context['page']) + 1) + return context + if 'is_paginated' in context and context['is_paginated']: + page_obj = context.get('page_obj') + context['page'] = page_obj.number + context['next_page'] = str(page_obj.next_page_number()) if page_obj.has_next() else None + else: + context['page'] = 1 + context['next_page'] = None return context def get_template_names(self): diff --git a/docker-compose.yml b/docker-compose.yml index d4cda7ed..9465b2a4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,18 +12,26 @@ services: ports: - "5432:5432" + redis: + image: redis:3-alpine + ports: + - "6379:6379" + web: build: . restart: always volumes: - .:/lilcity - command: bash -c "python manage.py migrate && python manage.py loaddata /lilcity/apps/*/fixtures/*.json && python manage.py runserver 0.0.0.0:8000" + command: bash -c "python manage.py migrate && python manage.py loaddata /lilcity/apps/*/fixtures/*.json && python manage.py runserver 0.0.0.0:8000 && celery worker -A project -Q web" environment: - DJANGO_SETTINGS_MODULE=project.settings - DATABASE_SERVICE_HOST=db + - REDIS_SERVICE_HOST=redis ports: - "8000:8000" depends_on: - db + - redis links: - - db \ No newline at end of file + - db + - redis \ No newline at end of file diff --git a/project/celery.py b/project/celery.py new file mode 100644 index 00000000..8cbb640c --- /dev/null +++ b/project/celery.py @@ -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}' diff --git a/project/celery_settings.py b/project/celery_settings.py new file mode 100644 index 00000000..8afa610e --- /dev/null +++ b/project/celery_settings.py @@ -0,0 +1,3 @@ +broker_url = 'redis://redis:6379/0' +result_backend = 'redis://redis:6379/1' +task_serializer = 'json' diff --git a/project/settings.py b/project/settings.py index f55487d1..c78c1fce 100644 --- a/project/settings.py +++ b/project/settings.py @@ -40,6 +40,8 @@ INSTALLED_APPS = [ 'anymail', 'active_link', 'django_filters', + 'polymorphic_tree', + 'polymorphic', 'mptt', ] + [ 'apps.auth.apps', diff --git a/project/templates/lilcity/index.html b/project/templates/lilcity/index.html index 331c4eb0..4d12cfa8 100644 --- a/project/templates/lilcity/index.html +++ b/project/templates/lilcity/index.html @@ -33,6 +33,7 @@ +