Add recipient notification detail and filtering, a queued admin composer, and contextual automatic titles. Retire email delivery, obsolete auth routes, newsletter data, and related templates and settings.
181 lines
6.3 KiB
Python
181 lines
6.3 KiB
Python
from django.contrib.auth.models import AbstractUser, UserManager as DjangoUserManager
|
|
from django.db import models
|
|
|
|
from core.media import (
|
|
delete_image_derivatives_by_name,
|
|
get_image_previous_name,
|
|
safe_process_public_image,
|
|
)
|
|
from core.models import BaseModel
|
|
from apps.users.email_identity import normalize_email_identity, normalize_mobile_number
|
|
|
|
|
|
class UserManager(DjangoUserManager):
|
|
def _normalize_required_mobile(self, mobile):
|
|
normalized = normalize_mobile_number(mobile)
|
|
if not normalized:
|
|
raise ValueError("The mobile number must be set")
|
|
return normalized
|
|
|
|
def create_user(self, username, email=None, password=None, **extra_fields):
|
|
extra_fields["mobile"] = self._normalize_required_mobile(extra_fields.get("mobile"))
|
|
return super().create_user(username, email=email, password=password, **extra_fields)
|
|
|
|
def create_superuser(self, username, email=None, password=None, **extra_fields):
|
|
extra_fields["mobile"] = self._normalize_required_mobile(extra_fields.get("mobile"))
|
|
extra_fields.setdefault("is_active", True)
|
|
extra_fields.setdefault("is_mobile_verified", True)
|
|
return super().create_superuser(username, email=email, password=password, **extra_fields)
|
|
|
|
|
|
class University(BaseModel):
|
|
code = models.CharField(max_length=64, unique=True)
|
|
name = models.CharField(max_length=255)
|
|
is_active = models.BooleanField(default=True)
|
|
|
|
class Meta:
|
|
ordering = ["name"]
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
class Major(BaseModel):
|
|
code = models.CharField(max_length=64, unique=True)
|
|
name = models.CharField(max_length=255)
|
|
is_active = models.BooleanField(default=True)
|
|
|
|
class Meta:
|
|
ordering = ["name"]
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
class User(AbstractUser, BaseModel):
|
|
email = models.EmailField(unique=True, null=True, blank=True, default=None)
|
|
mobile = models.CharField(max_length=11, unique=True, null=True, blank=True, default=None)
|
|
bio = models.TextField(null=True, blank=True)
|
|
profile_picture = models.ImageField(upload_to='profile_pictures/', null=True, blank=True)
|
|
|
|
student_id = models.CharField(max_length=20, null=True)
|
|
year_of_study = models.IntegerField(null=True, blank=True)
|
|
major = models.ForeignKey(
|
|
Major,
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
related_name='users',
|
|
)
|
|
university = models.ForeignKey(
|
|
University,
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
related_name='users',
|
|
)
|
|
is_email_verified = models.BooleanField(default=False)
|
|
is_mobile_verified = models.BooleanField(default=False)
|
|
|
|
USERNAME_FIELD = 'username'
|
|
REQUIRED_FIELDS = ['mobile']
|
|
|
|
objects = UserManager()
|
|
|
|
class Meta:
|
|
db_table = 'users'
|
|
verbose_name = 'User'
|
|
verbose_name_plural = 'Users'
|
|
permissions = [('send_notification', 'Can send in-app notifications')]
|
|
|
|
def __str__(self):
|
|
identity = self.mobile or self.email or self.username
|
|
return f"{self.get_full_name() or self.username} ({identity})"
|
|
|
|
def get_full_name(self):
|
|
return f"{self.first_name} {self.last_name}".strip()
|
|
|
|
def get_major_display(self):
|
|
if self.major:
|
|
return self.major.name
|
|
return None
|
|
|
|
def get_university_display(self):
|
|
if self.university:
|
|
return self.university.name
|
|
return None
|
|
|
|
@property
|
|
def requires_mobile_verification(self):
|
|
return not self.is_mobile_verified
|
|
|
|
@property
|
|
def has_google_link(self):
|
|
return self.social_accounts.filter(
|
|
provider=UserSocialAccount.ProviderType.GOOGLE,
|
|
is_active=True,
|
|
).exists()
|
|
|
|
def save(self, *args, **kwargs):
|
|
previous_image_name = get_image_previous_name(self, "profile_picture")
|
|
current_image_name = self.profile_picture.name if self.profile_picture else None
|
|
self.email = normalize_email_identity(self.email)
|
|
self.mobile = normalize_mobile_number(self.mobile)
|
|
if kwargs.get('update_fields') is None or 'email' in kwargs['update_fields']:
|
|
self.is_email_verified = bool(self.email)
|
|
if kwargs.get('update_fields') is not None:
|
|
kwargs['update_fields'] = set(kwargs['update_fields']) | {'is_email_verified'}
|
|
|
|
super().save(*args, **kwargs)
|
|
|
|
if previous_image_name != current_image_name and previous_image_name:
|
|
delete_image_derivatives_by_name(
|
|
self.profile_picture.storage if self.profile_picture else None,
|
|
previous_image_name,
|
|
"profile_picture",
|
|
delete_original=True,
|
|
)
|
|
|
|
if previous_image_name != current_image_name and self.profile_picture:
|
|
safe_process_public_image(self.profile_picture, "profile_picture")
|
|
|
|
|
|
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="")
|
|
is_active = models.BooleanField(default=True)
|
|
|
|
class Meta:
|
|
verbose_name = "User Social Account"
|
|
verbose_name_plural = "User Social Accounts"
|
|
db_table = "user_social_accounts"
|
|
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)
|