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.
 
 

238 lines
9.2 KiB

import random
import base64
import hashlib
from cryptography.fernet import Fernet
from django.conf import settings
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
from django.utils import timezone
from phonenumber_field.modelfields import PhoneNumberField
from utils.validators import validate_possible_number
from apps.account.manager import UserManager
class User(AbstractUser):
class DeviceOs(models.TextChoices):
android = 'android', _('Android')
apple = 'apple', _('Apple iOS')
web = 'web', _('Web')
class UserType(models.TextChoices):
CLIENT = 'client', _('Client')
ADMIN = 'admin', _('Admin')
SUPER_ADMIN = 'super_admin', _('Super Admin')
class GenderChoices(models.TextChoices):
MALE = 'male', _('Male')
FEMALE = 'female', _('Female')
OTHER = 'other', _('Other')
last_name = None
first_name = None
username = models.CharField(
unique=True,
null=True,
blank=True,
max_length=150,
verbose_name=_("Username"),
error_messages={'unique': _("A user with that username already exists.")}
)
email = models.EmailField(
unique=True,
verbose_name=_("Email Address"),
help_text=_("User primary email address."),
null=True,
blank=True,
error_messages={'unique': _("A user with that email already exists.")}
)
fullname = models.CharField(
max_length=255,
verbose_name=_("Full Name"),
help_text=_("Full name of the user."),
null=True,
blank=True
)
birthdate = models.DateField(verbose_name=_('Birthdate'), null=True, blank=True)
avatar = models.ImageField(
max_length=512,
null=True,
blank=True,
upload_to='users/avatars/%Y/%m/',
verbose_name=_('Avatar')
)
phone_number = PhoneNumberField(
validators=[validate_possible_number],
null=True,
blank=True,
verbose_name=_('Phone Number'),
help_text=_("e.g., +1 555 1234567")
)
language = models.CharField(
max_length=10,
default='en',
choices=settings.LANGUAGES,
null=True,
blank=True,
verbose_name=_('Language')
)
gender = models.CharField(
max_length=20,
choices=GenderChoices.choices,
null=True,
blank=True,
verbose_name=_('Gender')
)
user_type = models.CharField(
max_length=20,
choices=UserType.choices,
default=UserType.CLIENT,
verbose_name=_("User Type")
)
date_joined = models.DateTimeField(
auto_now_add=True,
verbose_name=_("Date Joined")
)
city = models.CharField(verbose_name=_('City'), max_length=255, null=True, blank=True)
country = models.CharField(max_length=255, verbose_name=_('Country'), null=True, blank=True)
device_id = models.CharField(verbose_name=_('Device ID'), max_length=255, null=True, blank=True)
device_os = models.CharField(choices=DeviceOs.choices, null=True, blank=True, max_length=16, verbose_name=_('Device OS'))
user_agent = models.TextField(verbose_name=_('User Agent'), null=True, blank=True)
client_ip = models.CharField(max_length=64, verbose_name=_('Client IP'), null=True, blank=True)
fcm = models.CharField(max_length=512, null=True, blank=True, verbose_name=_('FCM Token'))
slug = models.SlugField(max_length=255, unique=True, null=True, blank=True, verbose_name=_('Slug'))
is_staff = models.BooleanField(default=False, verbose_name=_('Is Staff'))
is_active = models.BooleanField(
default=True,
verbose_name=_("Active"),
help_text=_("Designates whether this user should be treated as active.")
)
deleted_at = models.DateTimeField(null=True, blank=True, verbose_name=_('Deleted At'))
info = models.TextField(verbose_name=_("Bio / Info"), null=True, blank=True)
skill = models.CharField(max_length=512, null=True, blank=True, verbose_name=_('Skill / Role'))
password_enc = models.CharField(
max_length=512,
blank=True,
default='',
verbose_name=_("Encrypted Password"),
help_text=_("Encrypted copy of password for displaying in the admin panel if required.")
)
objects = UserManager()
EMAIL_FIELD = "email"
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
def __str__(self):
return self.email or self.fullname or self.username or f"User #{self.id}"
def soft_delete(self):
self.deleted_at = timezone.now()
self.is_active = False
self.fullname = f'{self.fullname}:deleted' if self.fullname else 'deleted_user'
number = str(random.randint(1000000000, 9999999999))
if self.phone_number:
self.phone_number = f'{self.phone_number}:deleted{number}'
if self.email:
self.email = f'{self.email}:deleted{number}'
if self.device_id:
self.device_id = f'{self.device_id}:deleted{number}'
self.save()
def save(self, *args, **kwargs):
if not self.username and self.email:
self.username = self.email
elif self.email and User.objects.filter(username=self.email).exclude(pk=self.pk).exists():
self.username = f'{self.email}:{self.id or random.randint(1000, 9999)}'
return super().save(*args, **kwargs)
def get_full_name(self):
return self.fullname or self.email or ""
@property
def is_guest(self):
return self.email is None
def has_role(self, role_name):
return self.groups.filter(name__iexact=f"{role_name} Group").exists() or self.user_type == role_name
def _get_fernet(self):
key_bytes = settings.SECRET_KEY.encode('utf-8')
hashed_key = hashlib.sha256(key_bytes).digest()
fernet_key = base64.urlsafe_b64encode(hashed_key)
return Fernet(fernet_key)
def set_plain_password(self, plain_password):
if not plain_password:
self.password_enc = ''
return
try:
password_bytes = plain_password.encode('utf-8')
fernet = self._get_fernet()
encrypted_token = fernet.encrypt(password_bytes)
self.password_enc = encrypted_token.decode('utf-8')
except Exception:
self.password_enc = ''
def get_plain_password(self):
if not self.password_enc:
return ''
try:
encrypted_bytes = self.password_enc.encode('utf-8')
fernet = self._get_fernet()
decrypted_bytes = fernet.decrypt(encrypted_bytes)
return decrypted_bytes.decode('utf-8')
except Exception:
return ''
class Meta:
ordering = ("-id",)
verbose_name = _("User")
verbose_name_plural = _("Users")
class LoginHistory(models.Model):
user = models.ForeignKey("account.User", on_delete=models.CASCADE, related_name='login_history', verbose_name=_('User'))
lat = models.FloatField(verbose_name=_('Latitude'), null=True, blank=True)
lon = models.FloatField(verbose_name=_('Longitude'), null=True, blank=True)
country = models.CharField(max_length=255, verbose_name=_('Country'), null=True, blank=True)
city = models.CharField(max_length=255, verbose_name=_('City'), null=True, blank=True)
ip = models.CharField(max_length=255, null=True, blank=True, verbose_name=_('IP Address'))
timezone = models.CharField(max_length=100, null=True, blank=True, verbose_name=_('Timezone'))
user_agent = models.TextField(verbose_name=_('User Agent'), null=True, blank=True)
device_os = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('Device OS'))
at_time = models.DateTimeField(auto_now_add=True, verbose_name=_('Timestamp'))
class Meta:
verbose_name = _('Login History')
verbose_name_plural = _('Login Histories')
ordering = ('-at_time',)
def __str__(self):
return f"{self.user} - {self.ip} ({self.at_time})"
class LocationHistory(models.Model):
user = models.ForeignKey("account.User", on_delete=models.CASCADE, related_name='location_history', verbose_name=_('User'))
lat = models.FloatField(verbose_name=_('Latitude'), null=True, blank=True)
lon = models.FloatField(verbose_name=_('Longitude'), null=True, blank=True)
country = models.CharField(max_length=255, verbose_name=_('Country'), null=True, blank=True)
city = models.CharField(max_length=255, verbose_name=_('City'), null=True, blank=True)
selected_manually = models.BooleanField(default=False, verbose_name=_('Selected Manually'))
ip = models.CharField(max_length=255, null=True, blank=True, verbose_name=_('IP Address'))
timezone = models.CharField(null=True, blank=True, max_length=60, verbose_name=_('Timezone'))
at_time = models.DateTimeField(auto_now_add=True, verbose_name=_('Timestamp'))
class Meta:
verbose_name = _('Location History')
verbose_name_plural = _('Location History')
ordering = ('-at_time',)
def __str__(self):
return f"{self.user} - {self.city}, {self.country}"