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.
395 lines
17 KiB
395 lines
17 KiB
from io import BytesIO
|
|
from PIL import Image
|
|
from uuid import uuid4
|
|
from os.path import splitext
|
|
from datetime import timedelta
|
|
from paymentwall import Pingback
|
|
|
|
from django.conf import settings
|
|
from django.contrib.auth import login
|
|
from django.shortcuts import reverse, redirect
|
|
from django.views import View
|
|
from django.views.generic import DetailView, UpdateView, TemplateView, FormView
|
|
from django.contrib import messages
|
|
from django.contrib.auth import get_user_model
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.db.models import F, Func, Sum, Min, Max, Q
|
|
from django.urls import reverse_lazy
|
|
from django.utils.decorators import method_decorator
|
|
from django.utils.timezone import now
|
|
|
|
from apps.auth.tokens import verification_email_token
|
|
from apps.config.models import Config
|
|
from apps.course.models import Course
|
|
from apps.notification.utils import send_email
|
|
from apps.school.models import SchoolSchedule
|
|
from apps.payment.models import AuthorBalance, CoursePayment, SchoolPayment, Payment, UserGiftCertificate, UserBonus, \
|
|
DrawingCampPayment
|
|
from apps.user.models import AuthorRequest, EmailSubscription, SubscriptionCategory, Child
|
|
|
|
from .forms import AuthorRequesForm, UserEditForm, WithdrawalForm, ChildForm
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
@login_required
|
|
def resend_email_verify(request):
|
|
token = verification_email_token.make_token(request.user)
|
|
url = request.scheme + '://' + request.get_host() + str(reverse_lazy('lilcity:verification-email', args=[token, request.user.id]))
|
|
send_email('Вы успешно прошли регистрацию', request.user.email, "notification/email/verification_email.html", url=url)
|
|
messages.info(request, 'Письмо подтверждения отправлено.')
|
|
return redirect('user-edit-profile')
|
|
|
|
|
|
@method_decorator(login_required, name='dispatch')
|
|
class ProfileView(TemplateView):
|
|
model = User
|
|
template_name = 'user/profile.html'
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
self.object = self.request.user
|
|
context = self.get_context_data(object=self.object)
|
|
return self.render_to_response(context)
|
|
|
|
def get_context_data(self, object):
|
|
context = super().get_context_data()
|
|
context['user'] = self.request.user
|
|
|
|
context['published'] = Course.objects.filter(
|
|
author=self.object,
|
|
)
|
|
context['is_author'] = context['published'] or self.request.user.role == User.AUTHOR_ROLE
|
|
context['user_gift_certificates'] = UserGiftCertificate.objects.filter(user=self.request.user)
|
|
context['paid'] = Course.objects.filter(
|
|
payments__in=CoursePayment.objects.filter(
|
|
user=self.object,
|
|
status__in=[
|
|
Pingback.PINGBACK_TYPE_REGULAR,
|
|
Pingback.PINGBACK_TYPE_GOODWILL,
|
|
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
|
|
],
|
|
),
|
|
).distinct()
|
|
school_payment = SchoolPayment.objects.filter(
|
|
user=self.object,
|
|
date_end__gte=now() - timedelta(7),
|
|
status__in=[
|
|
Pingback.PINGBACK_TYPE_REGULAR,
|
|
Pingback.PINGBACK_TYPE_GOODWILL,
|
|
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
|
|
],
|
|
)
|
|
context['is_school_purchased'] = school_payment.exists()
|
|
if context['is_school_purchased']:
|
|
school_schedules_purchased = school_payment.annotate(
|
|
joined_weekdays=Func(F('weekdays'), function='unnest',)
|
|
).values_list('joined_weekdays', flat=True).distinct()
|
|
aggregated = school_payment.aggregate(Sum('amount'), Min('date_start'), Max('date_end'),)
|
|
context['school_purchased_weekdays'] = '-'.join(map(lambda wd: SchoolSchedule.WEEKDAY_SHORT_NAMES[wd-1],
|
|
set(sorted(school_schedules_purchased))))
|
|
context['school_purchased_price'] = aggregated.get('amount__sum') or 0
|
|
context['school_purchased_dates'] = [aggregated.get('date_start__min'), aggregated.get('date_end__max')]
|
|
|
|
camp_payment = DrawingCampPayment.objects.filter(
|
|
user=self.object,
|
|
date_end__gte=now() - timedelta(7),
|
|
status__in=DrawingCampPayment.PW_PAID_STATUSES,
|
|
)
|
|
context['is_camp_purchased'] = camp_payment.exists()
|
|
if context['is_camp_purchased']:
|
|
aggregated = camp_payment.aggregate(Sum('amount'), Min('date_start'), Max('date_end'),)
|
|
context['camp_purchased_price'] = aggregated.get('amount__sum') or 0
|
|
context['camp_purchased_dates'] = [aggregated.get('date_start__min'), aggregated.get('date_end__max')]
|
|
|
|
context['profile'] = True
|
|
|
|
return context
|
|
|
|
|
|
class UserView(DetailView):
|
|
model = User
|
|
template_name = 'user/author_profile.html'
|
|
query_pk_and_slug = True
|
|
|
|
def get_context_data(self, object):
|
|
context = super().get_context_data()
|
|
context['published'] = Course.objects.filter(
|
|
author=self.object, status=Course.PUBLISHED,
|
|
)
|
|
return context
|
|
|
|
|
|
class SubscribeView(View):
|
|
|
|
def post(self, request, pk=None, **kwargs):
|
|
refferer = request.META.get('HTTP_REFERER')
|
|
if request.user.is_authenticated:
|
|
messages.info(request, 'Вы уже подписаны на рассылки.')
|
|
return redirect(refferer)
|
|
email = request.POST.get('email', None)
|
|
if email:
|
|
if EmailSubscription.objects.filter(email=email).exists():
|
|
messages.error(request, 'Вы уже подписаны!')
|
|
return redirect(refferer)
|
|
|
|
email_subscription = EmailSubscription.objects.create(
|
|
email=email,
|
|
)
|
|
email_subscription.categories.set(
|
|
SubscriptionCategory.objects.filter(auto_add=True)
|
|
)
|
|
messages.info(request, 'Вы подписаны на новости.')
|
|
return redirect('subscribe-success')
|
|
else:
|
|
messages.error(request, 'Введите адрес электронной почты.')
|
|
return redirect(refferer)
|
|
|
|
|
|
@method_decorator(login_required, name='dispatch')
|
|
class NotificationEditView(TemplateView):
|
|
template_name = 'user/notification-settings.html'
|
|
|
|
def get(self, request, pk=None):
|
|
return super().get(request)
|
|
|
|
def post(self, request, pk=None):
|
|
categories = request.POST.getlist('category', [])
|
|
request.user.email_subscription.categories.set(
|
|
SubscriptionCategory.objects.filter(id__in=categories)
|
|
)
|
|
return redirect('user-edit-notifications')
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context['subscription_categories'] = SubscriptionCategory.objects.all()
|
|
return context
|
|
|
|
|
|
@method_decorator(login_required, name='dispatch')
|
|
class PaymentHistoryView(FormView):
|
|
template_name = 'user/payment-history.html'
|
|
form_class = WithdrawalForm
|
|
|
|
def get(self, request, pk=None):
|
|
return super().get(request)
|
|
|
|
def post(self, request, pk=None):
|
|
form = self.get_form()
|
|
if not settings.DEBUG and AuthorBalance.objects.filter(created_at__gte=now() - timedelta(days=30)).exists():
|
|
messages.error(request, 'Запрос на вывод средств можно сделать только один раз в 30 дней.')
|
|
return self.form_invalid(form)
|
|
if form.is_valid():
|
|
if request.user.balance < form.cleaned_data['amount']:
|
|
form.errors['amount'] = 'Сумма для вывода не может быть меньше средств на счету'
|
|
return self.form_invalid(form)
|
|
AuthorBalance.objects.create(
|
|
author=request.user,
|
|
type=AuthorBalance.OUT,
|
|
amount=form.cleaned_data['amount'],
|
|
status=AuthorBalance.PENDING,
|
|
card=form.cleaned_data['card'],
|
|
)
|
|
return self.form_valid(form)
|
|
else:
|
|
return self.form_invalid(form)
|
|
|
|
def get_success_url(self):
|
|
success_url = reverse_lazy('user-edit-payments')
|
|
return success_url
|
|
|
|
|
|
@method_decorator(login_required, name='dispatch')
|
|
class ProfileEditView(UpdateView):
|
|
model = User
|
|
template_name = 'user/profile-settings.html'
|
|
form_class = UserEditForm
|
|
|
|
def get_object(self, queryset=None):
|
|
return self.request.user
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
self.object = self.get_object()
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context['is_teacher'] = self.object.role == User.TEACHER_ROLE
|
|
return context
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
context = self.get_context_data(**kwargs)
|
|
if self.object.childs.all().count():
|
|
context['childs'] = [{
|
|
'id': c.id,
|
|
'first_name': c.first_name,
|
|
'last_name': c.last_name,
|
|
'gender': c.gender or 'n',
|
|
'birthday': c.birthday.strftime('%Y-%m-%d') if c.birthday else '',
|
|
} for c in self.object.childs.all()]
|
|
else:
|
|
context['childs'] = [{
|
|
'id': '',
|
|
'first_name': '',
|
|
'last_name': '',
|
|
'gender': 'n',
|
|
'birthday': '',
|
|
}]
|
|
return self.render_to_response(context)
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
# it's magic *-*-*-*-*
|
|
if 'photo' in request.FILES:
|
|
photo_fp = request.FILES.pop('photo')[0]
|
|
fname = photo_fp.name
|
|
photo = Image.open(photo_fp)
|
|
lowest_side = min(photo.size)
|
|
horizontal_padding = (lowest_side - photo.size[0]) / 2
|
|
vertical_padding = (lowest_side - photo.size[1]) / 2
|
|
photo = photo.crop(
|
|
(
|
|
-horizontal_padding,
|
|
-vertical_padding,
|
|
photo.size[0] + horizontal_padding,
|
|
photo.size[1] + vertical_padding
|
|
)
|
|
)
|
|
if photo.size[0] > 512:
|
|
photo = photo.resize((512, 512,))
|
|
buffer = BytesIO()
|
|
ext = splitext(fname)[1][1:].upper()
|
|
if ext == 'JPG':
|
|
ext = 'JPEG'
|
|
photo.save(buffer, ext)
|
|
fname = str(uuid4()) + '.' + ext.lower()
|
|
self.object.photo.save(fname, buffer)
|
|
buffer.close()
|
|
if not request.POST._mutable:
|
|
request.POST._mutable = True
|
|
old_password = request.POST.pop('old_password')[0]
|
|
new_password1 = request.POST.pop('new_password1')[0]
|
|
new_password2 = request.POST.pop('new_password2')[0]
|
|
if old_password:
|
|
if request.user.check_password(old_password) and new_password1 == new_password2:
|
|
request.user.set_password(new_password1)
|
|
request.user.save()
|
|
login(request, request.user)
|
|
else:
|
|
messages.error(request, 'Неверный пароль.')
|
|
|
|
child_ids = request.POST.getlist('child_id')
|
|
child_first_names = request.POST.getlist('child_first_name')
|
|
child_last_names = request.POST.getlist('child_last_name')
|
|
child_genders = request.POST.getlist('child_gender')
|
|
child_birthdays = request.POST.getlist('child_birthday')
|
|
ziped = zip(child_ids, child_first_names, child_last_names, child_genders, child_birthdays)
|
|
childs = [dict(zip(['id', 'first_name', 'last_name', 'gender', 'birthday'], z)) for z in ziped]
|
|
childs_saved = []
|
|
child_errors = False
|
|
for child in childs:
|
|
child_instance = None
|
|
if child.get('id'):
|
|
child_instance = Child.objects.get(pk=child.get('id'))
|
|
child['user'] = request.user.id
|
|
child['gender'] = child['gender'] or 'n'
|
|
child_form = ChildForm(data=child, instance=child_instance)
|
|
if child_form.is_valid():
|
|
childs_saved.append(child_form.save().id)
|
|
elif not(len(childs) == 1 and not child.get('id') and not child.get('first_name')):
|
|
child_errors = True
|
|
child['errors'] = {f: e[0] for f, e in child_form.errors.items()}
|
|
if not child_errors:
|
|
request.user.childs.exclude(id__in=childs_saved).delete()
|
|
|
|
form = self.get_form()
|
|
if form.is_valid() and not child_errors:
|
|
messages.info(request, 'Данные сохранены.')
|
|
response = self.form_valid(form)
|
|
# начисляем бонусы, если заполнил профиль
|
|
if self.object.phone and self.object.first_name and self.object.last_name and not self.object.bonuses.filter(
|
|
is_service=True, action_name=UserBonus.ACTION_FILL_PROFILE).count():
|
|
UserBonus.objects.create(user=self.object, amount=UserBonus.AMOUNT_FILL_PROFILE, is_service=True,
|
|
action_name=UserBonus.ACTION_FILL_PROFILE)
|
|
return response
|
|
|
|
else:
|
|
context = self.get_context_data(**kwargs)
|
|
context['childs'] = childs
|
|
return self.render_to_response(context)
|
|
|
|
|
|
def get_success_url(self):
|
|
return reverse('user-edit-profile')
|
|
|
|
|
|
class AuthorRequestView(FormView):
|
|
template_name = 'user/become-author.html'
|
|
form_class = AuthorRequesForm
|
|
success_url = reverse_lazy('author-request-success')
|
|
|
|
def post(self, request, pk=None):
|
|
form = self.get_form()
|
|
if form.is_valid():
|
|
if request.user.is_authenticated:
|
|
email = request.user.email
|
|
if request.user.role in [User.AUTHOR_ROLE, User.ADMIN_ROLE]:
|
|
messages.info(request, 'Вы уже являетесь автором')
|
|
return self.form_invalid(form)
|
|
else:
|
|
email = form.cleaned_data['email']
|
|
|
|
if AuthorRequest.objects.filter(email=email).exists():
|
|
messages.error(request, 'Вы уже отправили заявку на преподавателя')
|
|
return self.form_invalid(form)
|
|
|
|
AuthorRequest.objects.create(
|
|
first_name=form.cleaned_data['first_name'],
|
|
last_name=form.cleaned_data['last_name'],
|
|
email=email,
|
|
about=form.cleaned_data['about'],
|
|
facebook=form.cleaned_data['facebook'],
|
|
)
|
|
return self.form_valid(form)
|
|
else:
|
|
return self.form_invalid(form)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
if self.request.user.is_authenticated:
|
|
self.initial = {
|
|
'first_name': self.request.user.first_name,
|
|
'last_name': self.request.user.last_name,
|
|
'email': self.request.user.email,
|
|
'about': self.request.user.about,
|
|
'facebook': self.request.user.facebook,
|
|
}
|
|
return super().get_context_data(**kwargs)
|
|
|
|
|
|
@method_decorator(login_required, name='dispatch')
|
|
class BonusHistoryView(TemplateView):
|
|
model = User
|
|
template_name = 'user/bonus-history.html'
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
config = Config.load()
|
|
context = self.get_context_data(**kwargs)
|
|
context['bonuses'] = request.user.bonuses.filter(
|
|
Q(payment__isnull=False, payment__status__in=Payment.PW_PAID_STATUSES) | Q(is_service=True),
|
|
)
|
|
context['referrer_url'] = request.user.ref_url
|
|
context['share_text'] = '%s приглашает вас и ваших детей в первую онлайн школу креативного мышления Lil School. ' \
|
|
'Занимайтесь с ребёнком творчеством, не выходя из дома. ' \
|
|
'Перейдите по ссылке и получите скидку %d%% на первую покупку' \
|
|
% (request.user.get_full_name(), config.REFERRAL_BONUS)
|
|
context.update({
|
|
'amount_have_review': UserBonus.AMOUNT_HAVE_REVIEW,
|
|
'amount_fill_profile': UserBonus.AMOUNT_FILL_PROFILE,
|
|
'amount_paid_one_more': UserBonus.AMOUNT_PAID_ONE_MORE,
|
|
'amount_child_birthday': UserBonus.AMOUNT_CHILD_BIRTHDAY,
|
|
|
|
})
|
|
return self.render_to_response(context)
|
|
|
|
|
|
@method_decorator(login_required, name='dispatch')
|
|
class UserGalleryEditView(TemplateView):
|
|
template_name = 'user/edit-gallery.html'
|
|
|