You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

437 lines
17 KiB

from datetime import timedelta
from itertools import groupby
from paymentwall import Pingback
from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required
from django.db.models import Q
from django.http import JsonResponse, Http404
from django.shortcuts import get_object_or_404
from django.template import loader, Context, Template
from django.views.generic import View, CreateView, DetailView, ListView, TemplateView
from django.utils.cache import add_never_cache_headers
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from django.utils.translation import gettext as _
from django.utils.timezone import now
from apps.content.models import Banner
from apps.payment.models import AuthorBalance, CoursePayment
from .models import Course, Like, Lesson, CourseComment, LessonComment, Category, CourseTags, Tag
from .filters import CourseFilter
from project.utils.db import ModelFieldsNames, format_sql, execute_sql
User = get_user_model()
@login_required
@csrf_exempt
@require_http_methods(['POST'])
def likes(request, course_id):
try:
course = Course.objects.prefetch_related('likes').get(id=course_id)
except Course.DoesNotExist:
return JsonResponse({
'success': False,
'errors': ['Course with id f{course_id} not found']
}, status=400)
else:
course_user_likes = course.likes.filter(user=request.user)
if course_user_likes.exists():
is_liked = False
for _course in course_user_likes.all():
course.likes.remove(_course)
course_user_likes.delete()
else:
is_liked = True
course.likes.add(Like.objects.create(user=request.user))
count = course.likes.count()
return JsonResponse({
"success": True,
"likes_count": count,
"is_liked": is_liked,
})
@login_required
@csrf_exempt
@require_http_methods(['POST'])
def coursecomment(request, course_id):
try:
course = Course.objects.get(id=course_id)
except Course.DoesNotExist:
return JsonResponse({
'success': False,
'errors': ['Course with id f{course_id} not found']
}, status=400)
else:
reply_to = request.POST.get('reply_id', 0)
comment = request.POST.get('comment', '')
if not comment:
return JsonResponse({
'success': False,
'errors': ['Comment can not be empty']
}, status=400)
if not int(reply_to):
coursecomment = CourseComment.objects.create(
author=request.user,
content=comment,
course=course,
)
else:
try:
_coursecomment = CourseComment.objects.get(id=reply_to)
except CourseComment.DoesNotExist:
return JsonResponse({
'success': False,
'errors': ['CourseComment with id f{reply_to} not found']
}, status=400)
else:
coursecomment = CourseComment.objects.create(
author=request.user,
content=comment,
course=course,
parent=_coursecomment,
)
ctx = {'node': coursecomment, 'user': request.user}
html = loader.render_to_string('templates/blocks/comment.html', ctx)
return JsonResponse({
'success': True,
'comment': html,
})
@login_required
@csrf_exempt
@require_http_methods(['POST'])
def lessoncomment(request, lesson_id):
try:
lesson = Lesson.objects.get(id=lesson_id)
except Lesson.DoesNotExist:
return JsonResponse({
'success': False,
'errors': ['Lesson with id f{lesson_id} not found']
}, status=400)
else:
reply_to = request.POST.get('reply_id', 0)
comment = request.POST.get('comment', '')
if not comment:
return JsonResponse({
'success': False,
'errors': ['Comment can not be empty']
}, status=400)
if not int(reply_to):
lessoncomment = LessonComment.objects.create(
author=request.user,
content=comment,
lesson=lesson,
)
else:
try:
_lessoncomment = LessonComment.objects.get(id=reply_to)
except LessonComment.DoesNotExist:
return JsonResponse({
'success': False,
'errors': ['LessonComment with id f{reply_to} not found']
}, status=400)
else:
lessoncomment = LessonComment.objects.create(
author=request.user,
content=comment,
lesson=lesson,
parent=_lessoncomment,
)
ctx = {'node': lessoncomment, 'user': request.user}
html = loader.render_to_string('templates/blocks/comment.html', ctx)
return JsonResponse({
'success': True,
'comment': html,
})
@method_decorator(login_required, name='dispatch')
class CourseOnModerationView(TemplateView):
template_name = 'course/course_on_moderation.html'
@method_decorator(login_required, name='dispatch')
class CourseEditView(TemplateView):
template_name = 'course/course_edit.html'
def get(self, request, pk=None, lesson=None):
drafts = Course.objects.filter(
author=request.user, status=Course.DRAFT
)
if pk:
self.object = get_object_or_404(Course, pk=pk)
elif drafts.exists():
self.object = drafts.last()
else:
self.object = Course.objects.create(
author=request.user,
)
if request.user != self.object.author and request.user.role != User.ADMIN_ROLE:
raise Http404
return super().get(request)
def get_context_data(self):
context = super().get_context_data()
context['live'] = 'false'
context['object'] = self.object
return context
# @method_decorator(login_required, name='dispatch')
class CourseView(DetailView):
model = Course
context_object_name = 'course'
template_name = 'course/course.html'
only_lessons = False
def get(self, request, *args, **kwargs):
response = super().get(request, *args, **kwargs)
context = self.get_context_data()
# если это не админ или автор
if not context.get('has_full_access'):
# если это не опубл курс или это страница уроков, курс платный, а юзер не оплатил курс - 404
if (self.object.status != Course.PUBLISHED) or \
(self.only_lessons and self.object.price and not context.get('paid')):
raise Http404
return response
# ((self.object.status != Course.PUBLISHED and request.user.role != User.ADMIN_ROLE) or
# (self.object.status != Course.PUBLISHED and request.user.role != User.AUTHOR_ROLE and self.object.author != request.user)):
def get_object(self, queryset=None):
if queryset is None:
queryset = self.get_queryset()
pk = self.kwargs.get(self.pk_url_kwarg)
slug = self.kwargs.get(self.slug_url_kwarg)
if pk is not None:
queryset = queryset.filter(pk=pk)
if slug is not None and (pk is None or self.query_pk_and_slug):
slug_field = self.get_slug_field()
queryset = queryset.filter(**{'%s__iexact' % slug_field: slug})
if pk is None and slug is None:
raise AttributeError("Generic detail view %s must be called with "
"either an object pk or a slug."
% self.__class__.__name__)
try:
# Get the single item from the filtered queryset
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404(_("No %(verbose_name)s found matching the query") %
{'verbose_name': queryset.model._meta.verbose_name})
return obj
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.request.user.is_authenticated:
context['next'] = self.request.GET.get('next', None)
# берем последнюю оплату курса
payments = self.object.payments.filter(
user=self.request.user,
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
])
payment = payments.filter(access_expire__gte=now().date()).order_by('-access_expire').first()
context['payment'] = payment
context['access_duration'] = ((payment.access_expire - now().date()).days + 1) if payment else self.object.access_duration
context['paid'] = bool(payment)
context['can_buy_again'] = bool(self.object.price) and (context['access_duration'] <= 7 if payment else
payments.filter(access_expire__lt=now().date()).exists())
context['pending'] = self.object.payments.filter(
user=self.request.user,
status=Pingback.PINGBACK_TYPE_RISK_UNDER_REVIEW,
).exists()
context['only_lessons'] = self.only_lessons
if self.only_lessons:
context['lessons'] = self.object.lessons.order_by('position')
context['is_owner'] = self.object.author == self.request.user
context['is_admin'] = self.request.user.role == User.ADMIN_ROLE
context['has_full_access'] = context['is_owner'] or context['is_admin']
context['course_price'] = self.object.price / 2 if context.get('can_buy_again') else self.object.price
return context
def get_queryset(self):
queryset = super().get_queryset().select_related(
'author', 'category',
).prefetch_related(
'likes', 'materials', 'content',
)
return queryset
class CoursesView(ListView):
model = Course
context_object_name = 'course_items'
paginate_by = 12
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
if request.is_ajax():
context = self.get_context_data()
template_name = self.get_template_names()
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')
prev_url = request.path + '?page=' + \
str(page_obj.previous_page_number()
) if page_obj.has_previous() else None
next_url = request.path + '?page=' + \
str(page_obj.next_page_number()
) if page_obj.has_next() else None
else:
prev_url = None
next_url = None
response = JsonResponse({
'success': True,
'content': html,
'prev_url': prev_url,
'next_url': next_url,
})
add_never_cache_headers(response)
return response
else:
return super().get(request, args, kwargs)
def get_queryset(self):
queryset = super().get_queryset().select_related(
'author', 'category', 'cover',
).prefetch_related(
'likes', 'materials', 'content',
).filter(status=Course.PUBLISHED)
q = self.request.GET.get('q')
if q:
if q.startswith('#'):
queryset = queryset.filter(tags__tag__istartswith=q[1:]).distinct()
else:
queryset = queryset.filter(Q(tags__tag__icontains=q) | Q(title__icontains=q) | Q(short_description__icontains=q)
| Q(author__first_name__icontains=q) | Q(author__last_name__icontains=q)
| Q(author__email__icontains=q)).distinct()
filtered = CourseFilter(self.request.GET, queryset=queryset)
return filtered.qs
def get_context_data(self):
context = super().get_context_data()
filtered = CourseFilter(self.request.GET)
context.update(filtered.data)
sql = format_sql('''
select {ct.tag_id}
from {ct}
group by {ct.tag_id}
order by count(*) desc
limit 15''', ct=CourseTags)
tags = [t[0] for t in execute_sql(sql)]
context['tags'] = Tag.objects.filter(id__in=tags).order_by('tag')
context['search_query'] = self.request.GET.get('q', '')
context['banners'] = Banner.get_for_page(Banner.PAGE_COURSES)
context['course_items'] = Course.shuffle(context.get('course_items'))
context['new_courses'] = Course.objects.filter(status=Course.PUBLISHED).order_by('-created_at')[:4]
context['ages'] = Course.AGE_CHOICES[1:]
age = context.get('age')
if age and age[0]:
age = int(age[0])
context['age'] = [age]
context['age_name'] = dict(Course.AGE_CHOICES).get(age, '')
else:
context['age_name'] = ''
if not context.get('category') and context.get('cat'):
context['category'] = Category.objects.filter(title__iexact=context.get('cat')[0]).values_list(
'id', flat=True)[:1]
if self.request.user.is_authenticated:
can_buy_again_courses = list(CoursePayment.objects.filter(user=self.request.user,
status__in=CoursePayment.PW_PAID_STATUSES,
access_expire__lte=now().date() + timedelta(7)).values_list('course_id', flat=True))
for course in context['course_items']:
if course.id in can_buy_again_courses:
course.buy_again_price = course.price / 2
for course in context['course_items']:
course.cover = None
return context
def get_template_names(self):
if self.request.is_ajax():
return 'course/course_items.html'
return 'course/courses.html'
@method_decorator(login_required, name='dispatch')
class LessonView(DetailView):
model = Lesson
context_object_name = 'lesson'
template_name = 'course/lesson.html'
def get(self, request, *args, **kwargs):
response = super().get(request, *args, **kwargs)
paid = request.user.is_authenticated and self.object.course.payments.filter(
user=self.request.user,
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
access_expire__gte=now().date(),
).exists()
# если это не автор или админ
if not (request.user.is_authenticated and
(request.user.role == User.ADMIN_ROLE or self.object.course.author == request.user)):
# если курс не опубликован или он платный и не оплачен - 404
if self.object.course.status != Course.PUBLISHED or (self.object.course.price and not paid):
raise Http404
return response
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['next'] = self.request.GET.get('next', None)
lessons = list(self.object.course.lessons.values_list('id', flat=True))
index = lessons.index(self.object.id)
context['next_lesson'] = lessons[index + 1] if index < len(lessons) - 1 else None
return context
class SearchView(CoursesView):
template_name = 'course/result.html'
def get_queryset(self):
search_query = self.request.GET.get('q', None)
queryset = super().get_queryset()
if search_query:
query = Q(title__icontains=search_query) | Q(
short_description__icontains=search_query)
queryset = queryset.filter(query)
else:
queryset = queryset.none()
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['q'] = self.request.GET.get('q', None) or ''
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):
if self.request.is_ajax():
return 'course/course_items.html'
return 'course/result.html'