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.
317 lines
12 KiB
317 lines
12 KiB
from json import dumps
|
|
|
|
from django.db.models import Q
|
|
from django.utils.functional import cached_property
|
|
from rest_framework.authtoken.models import Token
|
|
from phonenumber_field.modelfields import PhoneNumberField
|
|
|
|
from django.db import models
|
|
from django.db.models.signals import post_save
|
|
from django.dispatch import receiver
|
|
from django.contrib.auth.models import AbstractUser, UserManager as BaseUserManager
|
|
from django.contrib.postgres import fields as pgfields
|
|
from django.utils.timezone import now
|
|
from django.utils.translation import gettext_lazy as _
|
|
from django.urls import reverse
|
|
|
|
from api.v1 import serializers
|
|
from apps.notification.utils import send_email
|
|
from apps.user.tasks import user_to_mixpanel
|
|
|
|
|
|
class UserManager(BaseUserManager):
|
|
use_in_migrations = True
|
|
|
|
def _create_user(self, username, email, password, **extra_fields):
|
|
if not username:
|
|
username = email
|
|
if not password:
|
|
password = self.make_random_password()
|
|
email = self.normalize_email(email)
|
|
username = self.model.normalize_username(username)
|
|
user = self.model(username=username, email=email, **extra_fields)
|
|
user.set_password(password)
|
|
user.save(using=self._db)
|
|
return user
|
|
|
|
|
|
class User(AbstractUser):
|
|
USER_ROLE = 0
|
|
AUTHOR_ROLE = 1
|
|
TEACHER_ROLE = 2
|
|
ADMIN_ROLE = 3
|
|
ROLE_CHOICES = (
|
|
(USER_ROLE, 'пользователь'),
|
|
(AUTHOR_ROLE, 'автор'),
|
|
(TEACHER_ROLE, 'преподаватель'),
|
|
(ADMIN_ROLE, 'администратор'),
|
|
)
|
|
NOT_DEFINED = 'n'
|
|
MALE = 'm'
|
|
FEMALE = 'f'
|
|
GENDER_CHOICES = (
|
|
(NOT_DEFINED, 'не указан'),
|
|
(MALE, 'Мужчина'),
|
|
(FEMALE, 'Женщина'),
|
|
)
|
|
email = models.EmailField(_('email address'), unique=True)
|
|
phone = PhoneNumberField(null=True, blank=True, unique=True)
|
|
role = models.PositiveSmallIntegerField(
|
|
'Роль', default=0, choices=ROLE_CHOICES)
|
|
gender = models.CharField(
|
|
'Пол', max_length=1, default='n', choices=GENDER_CHOICES)
|
|
birthday = models.DateField('День рождения', null=True, blank=True)
|
|
country = models.CharField('Страна', max_length=50, null=True, blank=True)
|
|
city = models.CharField('Город', max_length=85, null=True, blank=True)
|
|
about = models.CharField('О себе', max_length=1000, null=True, blank=True)
|
|
instagram = models.URLField(default='', null=True, blank=True)
|
|
instagram_hashtag = models.CharField('Инстаграм хештег', max_length=100, null=True, blank=True)
|
|
facebook = models.URLField(default='', null=True, blank=True)
|
|
twitter = models.URLField(default='', null=True, blank=True)
|
|
pinterest = models.URLField(default='', null=True, blank=True)
|
|
youtube = models.URLField(default='', null=True, blank=True)
|
|
vkontakte = models.URLField('ВКонтакте', default='', null=True, blank=True)
|
|
site = models.URLField(default='', null=True, blank=True)
|
|
fb_id = models.BigIntegerField(null=True, blank=True, unique=True)
|
|
fb_data = pgfields.JSONField(default={}, null=True, blank=True)
|
|
is_email_proved = models.BooleanField(
|
|
'Верифицирован по email', default=False
|
|
)
|
|
photo = models.ImageField('Фото', null=True, blank=True, upload_to='users')
|
|
show_in_mainpage = models.BooleanField('Показывать на главной странице', default=False)
|
|
trial_lesson = models.URLField(default='', null=True, blank=True)
|
|
slug = models.SlugField(
|
|
allow_unicode=True, null=True, blank=True,
|
|
max_length=100, unique=True, db_index=True,
|
|
)
|
|
gallery = models.ForeignKey(
|
|
'content.Gallery', on_delete=models.CASCADE,
|
|
verbose_name='Галерея', null=True, blank=True,
|
|
)
|
|
|
|
objects = UserManager()
|
|
|
|
USERNAME_FIELD = 'email'
|
|
REQUIRED_FIELDS = ['username']
|
|
|
|
class Meta(AbstractUser.Meta):
|
|
ordering = ('-date_joined',)
|
|
|
|
@property
|
|
def url(self):
|
|
return reverse('user', args=[self.slug or self.id])
|
|
|
|
def serialized(self):
|
|
user_data = serializers.user.UserSerializer(instance=self).data
|
|
user_data = dumps(user_data, ensure_ascii=False)
|
|
return user_data
|
|
|
|
@cached_property
|
|
def balance(self):
|
|
from apps.payment.models import Payment, AuthorBalance
|
|
income = self.balances.filter(
|
|
type=AuthorBalance.IN,
|
|
payment__isnull=False,
|
|
payment__status__in=Payment.PW_PAID_STATUSES,
|
|
).aggregate(
|
|
models.Sum('amount'),
|
|
models.Sum('commission'),
|
|
)
|
|
income_amount = income.get('amount__sum') or 0
|
|
income_commission = income.get('commission__sum') or 0
|
|
|
|
payout = self.balances.filter(type=AuthorBalance.OUT, status=AuthorBalance.ACCEPTED).aggregate(models.Sum('amount'))
|
|
payout_amount = payout.get('amount__sum') or 0
|
|
|
|
return income_amount - income_commission - payout_amount
|
|
|
|
@cached_property
|
|
def bonus(self):
|
|
from apps.payment.models import Payment
|
|
return int(self.bonuses.filter(
|
|
Q(payment__isnull=False, payment__status__in=Payment.PW_PAID_STATUSES) | Q(is_service=True),
|
|
).aggregate(models.Sum('amount')).get('amount__sum') or 0)
|
|
|
|
|
|
@receiver(post_save, sender=User)
|
|
def create_auth_token(sender, instance=None, created=False, **kwargs):
|
|
if (
|
|
(instance.is_active or instance.fb_id) and
|
|
instance.role in [User.AUTHOR_ROLE, User.TEACHER_ROLE, User.ADMIN_ROLE] and not
|
|
hasattr(instance, 'auth_token')
|
|
):
|
|
Token.objects.create(user=instance)
|
|
elif (
|
|
not (instance.is_active or instance.fb_id) or
|
|
instance.role not in [User.AUTHOR_ROLE, User.TEACHER_ROLE, User.ADMIN_ROLE]
|
|
) and hasattr(instance, 'auth_token'):
|
|
instance.auth_token.delete()
|
|
|
|
|
|
@receiver(post_save, sender=User)
|
|
def send_user_info_to_mixpanel(sender, instance=None, created=False, **kwargs):
|
|
user_to_mixpanel.delay(
|
|
instance.id,
|
|
instance.email,
|
|
str(instance.phone),
|
|
instance.first_name,
|
|
instance.last_name,
|
|
instance.date_joined,
|
|
dict(User.ROLE_CHOICES).get(instance.role),
|
|
[subscription.title.lower() for subscription in instance.email_subscription.categories.all()] if hasattr(instance, 'email_subscription') else [],
|
|
)
|
|
|
|
|
|
@receiver(post_save, sender=User)
|
|
def auto_create_subscription(sender, instance=None, created=False, **kwargs):
|
|
if created:
|
|
es, created = EmailSubscription.objects.get_or_create(email=instance.email)
|
|
es.user = instance
|
|
es.save()
|
|
instance.email_subscription.categories.set(SubscriptionCategory.objects.filter(auto_add=True))
|
|
|
|
|
|
class AuthorRequestManager(models.Manager):
|
|
def create_by_user(self, user):
|
|
obj = self.model(
|
|
first_name=user.first_name,
|
|
last_name=user.last_name,
|
|
email=user.email,
|
|
about=user.about,
|
|
facebook=user.facebook,
|
|
)
|
|
self._for_write = True
|
|
obj.save(force_insert=True, using=self.db)
|
|
return obj
|
|
|
|
|
|
class AuthorRequest(models.Model):
|
|
PENDING = 0
|
|
ACCEPTED = 1
|
|
DECLINED = 2
|
|
STATUS_CHOICES = (
|
|
(PENDING, 'pending'),
|
|
(ACCEPTED, 'accepted'),
|
|
(DECLINED, 'declined'),
|
|
)
|
|
|
|
first_name = models.CharField(_('first name'), max_length=30)
|
|
last_name = models.CharField(_('last name'), max_length=150)
|
|
email = models.EmailField(_('email address'), unique=True)
|
|
about = models.CharField('О себе', max_length=1000, null=True, blank=True)
|
|
facebook = models.URLField(default='', null=True, blank=True)
|
|
status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES, default=PENDING)
|
|
cause = models.TextField('Причина отказа', null=True, blank=True)
|
|
|
|
accepted_send_at = models.DateTimeField(null=True, blank=True)
|
|
declined_send_at = models.DateTimeField(null=True, blank=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
update_at = models.DateTimeField(auto_now=True)
|
|
|
|
objects = AuthorRequestManager()
|
|
|
|
class Meta:
|
|
verbose_name = 'Заявка не преподавателя'
|
|
verbose_name_plural = 'Заявки не преподавателя'
|
|
ordering = ('-created_at',)
|
|
|
|
|
|
@receiver(post_save, sender=AuthorRequest)
|
|
def handle_authorrequest_update(sender, instance=None, created=False, update_fields=[], **kwargs):
|
|
if not created:
|
|
if instance.status == AuthorRequest.DECLINED and not instance.declined_send_at:
|
|
send_email.delay(
|
|
'Отказ заявки на преподавателя',
|
|
instance.email,
|
|
'notification/email/decline_author.html',
|
|
cause=instance.cause,
|
|
)
|
|
instance.declined_send_at = now()
|
|
instance.save()
|
|
elif instance.status == AuthorRequest.ACCEPTED and not instance.accepted_send_at:
|
|
email = None
|
|
password = None
|
|
try:
|
|
user = User.objects.get(email=instance.email)
|
|
except User.DoesNotExist:
|
|
email = instance.email
|
|
password = User.objects.make_random_password()
|
|
user = User.objects.create(
|
|
first_name=instance.first_name,
|
|
last_name=instance.last_name,
|
|
username=instance.email,
|
|
email=instance.email,
|
|
about=instance.about,
|
|
facebook=instance.facebook,
|
|
is_active=True,
|
|
is_email_proved=True,
|
|
)
|
|
user.set_password(password)
|
|
user.role = User.AUTHOR_ROLE
|
|
user.save()
|
|
send_email.delay(
|
|
'Заявка на преподавателя одобрена',
|
|
instance.email,
|
|
'notification/email/accept_author.html',
|
|
email=email,
|
|
password=password,
|
|
)
|
|
instance.accepted_send_at = now()
|
|
instance.save()
|
|
|
|
|
|
class SubscriptionCategory(models.Model):
|
|
title = models.CharField(max_length=100)
|
|
auto_add = models.BooleanField(default=False)
|
|
|
|
class Meta:
|
|
verbose_name = 'Категория подписки'
|
|
verbose_name_plural = 'Категории подписки'
|
|
ordering = ('title',)
|
|
|
|
def __str__(self):
|
|
return self.title
|
|
|
|
|
|
class EmailSubscription(models.Model):
|
|
ERROR = 0
|
|
SENT = 1
|
|
MAILCHIMP_STATUS_CHOICES = (
|
|
(ERROR, 'error'),
|
|
(SENT, 'sent'),
|
|
)
|
|
|
|
user = models.OneToOneField(User, null=True, blank=True, on_delete=models.CASCADE, related_name='email_subscription')
|
|
email = models.EmailField(_('email address'), unique=True)
|
|
categories = models.ManyToManyField(SubscriptionCategory)
|
|
mailchimp_status = models.PositiveSmallIntegerField(choices=MAILCHIMP_STATUS_CHOICES, default=ERROR)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
sendgrid_sent = models.BooleanField(default=False)
|
|
|
|
|
|
class Referral(models.Model):
|
|
referral = models.OneToOneField(User, on_delete=models.CASCADE, related_name='referral')
|
|
referrer = models.ForeignKey(User, on_delete=models.CASCADE, related_name='referrals')
|
|
bonus = models.IntegerField()
|
|
referrer_bonus = models.IntegerField()
|
|
payment = models.OneToOneField('payment.Payment', null=True, blank=True, on_delete=models.CASCADE)
|
|
|
|
class Meta:
|
|
verbose_name = 'Реферал'
|
|
verbose_name_plural = 'Рефералы'
|
|
|
|
def send_bonuses(self, referral_bonus, referrer_bonus):
|
|
from apps.payment.models import UserBonus
|
|
UserBonus.objects.create(user=self.referral, amount=referral_bonus, payment=self.payment, referral=self)
|
|
UserBonus.objects.create(user=self.referrer, amount=referrer_bonus, payment=self.payment, referral=self)
|
|
|
|
|
|
class EmailLog(models.Model):
|
|
SOURCE_TRIAL_LESSON = 1
|
|
SOURCE_CHOICES = (
|
|
(SOURCE_TRIAL_LESSON, 'Пробный урок'),
|
|
)
|
|
|
|
email = models.EmailField(_('email address'))
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
source = models.PositiveSmallIntegerField(choices=SOURCE_CHOICES)
|
|
|