from django.contrib.auth.models import AbstractUser from django.db import models from django.db.models import Q from django.db.models.functions import Lower from core.models.base import BaseModel from core.utils import calculate_age, common_datetime_str from apps.users.email_identity import normalize_email_identity from apps.users.services.managers import UserManager class User(AbstractUser, BaseModel): username = None mobile = models.CharField(max_length=11, unique=True) email = models.EmailField(blank=True, null=True, default=None) description = models.TextField(blank=True, default="") profile_picture = models.ImageField(upload_to="profile/users/", blank=True, null=True) birth_date = models.DateField(blank=True, null=True) password_updated_at = models.DateTimeField(blank=True, null=True) is_verified = models.BooleanField(default=False) is_demo = models.BooleanField(default=False) demo_expires_at = models.DateTimeField(blank=True, null=True) USERNAME_FIELD = "mobile" REQUIRED_FIELDS = [] objects = UserManager(alive_only=True) @property def full_name(self): full_name = f"{self.first_name} {self.last_name}".strip() return full_name if full_name else "Anonymous" @property def age(self): return calculate_age(self.birth_date) @property def created_at_display(self): return common_datetime_str(self.created_at) def __str__(self): return self.full_name or self.mobile def save(self, *args, **kwargs): self.email = normalize_email_identity(self.email) super().save(*args, **kwargs) class Meta: verbose_name = "user" verbose_name_plural = "users" db_table = "user" ordering = ("-updated_at", "-created_at") constraints = ( models.UniqueConstraint( Lower("email"), condition=Q(email__isnull=False), name="user_email_ci_uniq", ), ) indexes = ( models.Index(fields=["id"], name="user_id_idx"), models.Index(fields=["mobile"], name="user_mobile_idx"), models.Index(fields=["is_demo", "demo_expires_at"], name="user_demo_expires_idx"), ) class LoginAttempt(BaseModel): class StatusType(models.IntegerChoices): FAILED = 0, "failed" SUCCESS = 1, "success" user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True) status = models.PositiveSmallIntegerField(choices=StatusType.choices, default=StatusType.FAILED) ip_address = models.GenericIPAddressField(null=True, blank=True) class Meta: verbose_name = "login_attempts" verbose_name_plural = "login_attempts" db_table = "login_attempt" ordering = ("-updated_at", "-created_at") def __str__(self): return f"LoginAttempt for User: {self.user} ({'✅' if self.status else '❌'})" class UserSocialAccount(BaseModel): class ProviderType(models.TextChoices): GOOGLE = "google", "google" user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="social_accounts") provider = models.CharField(max_length=32, choices=ProviderType.choices) provider_user_id = models.CharField(max_length=255) email = models.EmailField(blank=True, null=True, default=None) email_verified = models.BooleanField(default=False) avatar_url = models.URLField(blank=True, default="") class Meta: verbose_name = "user_social_account" verbose_name_plural = "user_social_accounts" db_table = "user_social_account" ordering = ("-updated_at", "-created_at") constraints = ( models.UniqueConstraint( fields=("provider", "provider_user_id"), name="user_social_account_provider_uid_uniq", ), ) indexes = ( models.Index(fields=["provider", "provider_user_id"], name="user_social_provider_uid_idx"), models.Index(fields=["provider", "email"], name="user_social_provider_email_idx"), ) def __str__(self): return f"{self.provider}:{self.provider_user_id}" def save(self, *args, **kwargs): self.email = normalize_email_identity(self.email) super().save(*args, **kwargs)