feat(backend): migrate auth and notifications off email
This commit is contained in:
@@ -11,6 +11,7 @@ from core.media import (
|
||||
safe_process_public_image,
|
||||
)
|
||||
from core.models import BaseModel
|
||||
from apps.users.email_identity import normalize_email_identity, normalize_mobile_number
|
||||
|
||||
|
||||
class University(BaseModel):
|
||||
@@ -38,7 +39,8 @@ class Major(BaseModel):
|
||||
|
||||
|
||||
class User(AbstractUser, BaseModel):
|
||||
email = models.EmailField(unique=True)
|
||||
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)
|
||||
|
||||
@@ -59,14 +61,15 @@ class User(AbstractUser, BaseModel):
|
||||
related_name='users',
|
||||
)
|
||||
is_email_verified = models.BooleanField(default=False)
|
||||
is_mobile_verified = models.BooleanField(default=False)
|
||||
email_verification_token = models.UUIDField(default=uuid.uuid4, unique=True)
|
||||
email_verification_sent_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
password_reset_token = models.UUIDField(null=True, blank=True, unique=True)
|
||||
password_reset_token_expires_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
USERNAME_FIELD = 'email'
|
||||
REQUIRED_FIELDS = ['username']
|
||||
USERNAME_FIELD = 'username'
|
||||
REQUIRED_FIELDS = []
|
||||
|
||||
class Meta:
|
||||
db_table = 'users'
|
||||
@@ -74,7 +77,8 @@ class User(AbstractUser, BaseModel):
|
||||
verbose_name_plural = 'Users'
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.get_full_name()} ({self.email})"
|
||||
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()
|
||||
@@ -89,6 +93,17 @@ class User(AbstractUser, BaseModel):
|
||||
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 regenerate_verification_token(self):
|
||||
self.email_verification_token = uuid.uuid4()
|
||||
self.save(update_fields=['email_verification_token'])
|
||||
@@ -102,12 +117,8 @@ class User(AbstractUser, BaseModel):
|
||||
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
|
||||
send_verified_success = False
|
||||
|
||||
if self.pk is not None:
|
||||
prev = type(self).objects.filter(pk=self.pk).values_list('is_email_verified', flat=True).first()
|
||||
if prev is not None and prev is False and self.is_email_verified is True:
|
||||
send_verified_success = True
|
||||
self.email = normalize_email_identity(self.email)
|
||||
self.mobile = normalize_mobile_number(self.mobile)
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@@ -122,9 +133,42 @@ class User(AbstractUser, BaseModel):
|
||||
if previous_image_name != current_image_name and self.profile_picture:
|
||||
safe_process_public_image(self.profile_picture, "profile_picture")
|
||||
|
||||
if send_verified_success:
|
||||
try:
|
||||
from apps.users.tasks import send_email_verified_success
|
||||
send_email_verified_success.delay(self.id)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user