from json import dumps import short_url 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.conf import settings 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 from project.utils.db import SafeImageField 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) phone2 = models.CharField(max_length=20, null=True, blank=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 ) if settings.DEV_SERVER: photo = SafeImageField('Фото', null=True, blank=True, upload_to='users') else: 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, ) review_url = models.URLField('Ссылка на видеоотзыв', blank=True, default='') objects = UserManager() USERNAME_FIELD = 'email' REQUIRED_FIELDS = ['username'] class Meta(AbstractUser.Meta): ordering = ('-date_joined',) @cached_property def has_child(self): return self.childs.all().count() > 0 def child_filled(self): child = self.childs.all().first() return child and child.first_name and child.last_name and child.birthday @property def url(self): return reverse('user', args=[self.slug or self.id]) @property def ref_url(self): code = short_url.encode_url(self.id) return 'https://%s%s?referrer=%s&utm_source=referral&utm_medium=referral&utm_content=%s&utm_campaign=ref_campaign' % ( settings.MAIN_HOST, reverse('index'), code, code ) if self.id else '' 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) @property def new_bonuses(self): from apps.payment.models import Payment old_bonuses = int(self.bonuses.filter( Q(payment__isnull=False, payment__status__in=Payment.PW_PAID_STATUSES) | Q(is_service=True), Q(amount__lte=0) | Q(amount__gt=0, notified_at__isnull=False), ).aggregate(models.Sum('amount')).get('amount__sum') or 0) return max(self.bonus - old_bonuses, 0) @property def paid_one_more(self): from apps.payment.models import SchoolPayment, CoursePayment, Payment, DrawingCampPayment payments_cnt = SchoolPayment.objects.filter(status__in=Payment.PW_PAID_STATUSES, user=self, add_days=False).count() if payments_cnt > 1: return True payments_cnt += CoursePayment.objects.filter(status__in=Payment.PW_PAID_STATUSES, user=self).count() if payments_cnt > 1: return True payments_cnt += DrawingCampPayment.objects.filter(status__in=Payment.PW_PAID_STATUSES, user=self).count() return payments_cnt > 1 @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) class Child(models.Model): NOT_DEFINED = 'n' MALE = 'm' FEMALE = 'f' GENDER_CHOICES = ( (NOT_DEFINED, 'не указан'), (MALE, 'Мужчина'), (FEMALE, 'Женщина'), ) user = models.ForeignKey(User, related_name='childs', on_delete=models.CASCADE) first_name = models.CharField('Имя ребенка', max_length=30) last_name = models.CharField('Фамилия ребенка', max_length=150, blank=True, default='') gender = models.CharField( 'Пол ребенка', max_length=1, default='n', choices=GENDER_CHOICES) birthday = models.DateField('День рождения ребенка', null=True, blank=True) class Meta: ordering = ('id',)