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.
 
 

303 lines
12 KiB

import random
import secrets
from dj_language.field import LanguageField
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'
web = 'web', 'web'
class UserType(models.TextChoices):
PROFESSOR = 'professor', 'Professor'
CLIENT = 'client', 'Client'
STUDENT = 'student', "Student"
ADMIN = 'admin', 'Admin'
SUPER_ADMIN = 'super_admin', 'Super Admin'
class GenderChoices(models.TextChoices):
MALE = 'male', 'Male'
FEMALE = 'female', 'Female'
last_name = None
first_name = None
username = models.CharField(unique=True, null=True, blank=True, max_length=150)
email = models.EmailField(unique=True, verbose_name="Email Address", help_text="Enter the user's email address.", null=True, blank=True)
fullname = models.CharField(max_length=255, verbose_name="Full Name", help_text="Enter the 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/')
phone_number = PhoneNumberField(
validators=[validate_possible_number],
null=True,
blank=True,
verbose_name=_('Phone Number'),
help_text="e.g., +49 151 12345678"
)
language = LanguageField(null=True)
gender = models.CharField(max_length=20, choices=GenderChoices.choices, null=True, blank=True, verbose_name=_('Gender'), help_text="Select the user's gender.")
user_type = models.CharField(max_length=20, choices=UserType.choices, default=UserType.CLIENT, verbose_name="User Type", help_text="Type of the user.")
date_joined = models.DateTimeField(auto_now_add=True, verbose_name="Date Joined", help_text="The date and time the user registered.")
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, max_length=16)
user_agent = models.TextField(verbose_name=_('user agent'), null=True, blank=True)
client_ip = models.TextField(verbose_name=_('client ip'), null=True, blank=True)
fcm = models.CharField(max_length=512, null=True, blank=True)
slug = models.SlugField(max_length=255, unique=True, null=True, blank=True)
experience_years = models.PositiveIntegerField(default=0, verbose_name=_('Experience years'))
is_staff = models.BooleanField(default=False)
is_active = models.BooleanField(default=True, verbose_name="Active", help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.")
deleted_at = models.DateTimeField(null=True, blank=True)
info = models.TextField(verbose_name="Info", null=True, blank=True)
skill = models.CharField(max_length=512, null=True, blank=True)
objects = UserManager()
EMAIL_FIELD = "email"
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
def __str__(self):
username = self.email or self.fullname or self.device_id
return f"{username}"
def soft_delete(self):
self.deleted_at = timezone.now()
self.is_active = False
self.fullname = f'{self.fullname}:deleted'
number = str(random.randint(1000000000, 9999999999))
self.phone_number = f'{self.phone_number}:deleted{number}'
self.email = f'{self.email}:deleted{number}' if self.email else None
self.device_id = f'{self.device_id}:deleted{number}' if self.device_id else None
self.save()
def save(self, *args, **kwargs):
self.username = self.email
if User.objects.filter(username=self.email).exclude(pk=self.pk).exists():
self.username = f'{self.email}:{self.id}'
if self.user_type == self.UserType.PROFESSOR:
self._ensure_professor_slug()
return super().save(*args, **kwargs)
def get_full_name(self):
return self.fullname
@property
def is_guest(self):
return self.email is None
@property
def user_type_based_on_groups(self):
if self.groups.filter(name="Student Group").exists():
return self.UserType.STUDENT
elif self.groups.filter(name="Professor Group").exists():
return self.UserType.PROFESSOR
else:
return self.UserType.CLIENT
@property
def primary_role(self):
"""نقش اصلی کاربر بر اساس اولویت"""
if self.groups.filter(name="Professor Group").exists():
return self.UserType.PROFESSOR
elif self.groups.filter(name="Student Group").exists():
return self.UserType.STUDENT
elif self.groups.filter(name="Admin Group").exists():
return self.UserType.ADMIN
elif self.groups.filter(name="Super Admin Group").exists():
return self.UserType.SUPER_ADMIN
else:
return self.UserType.CLIENT
def has_role(self, role_name):
"""چک کردن داشتن نقش خاص"""
if isinstance(role_name, str):
# اگر نام نقش به صورت string داده شده
group_name = f"{role_name.capitalize()} Group"
else:
# اگر از enum استفاده شده
group_name = f"{role_name.value.capitalize()} Group"
return self.groups.filter(name=group_name).exists()
def add_role(self, role_name):
"""اضافه کردن نقش جدید بدون حذف نقش‌های قبلی"""
from django.contrib.auth.models import Group
if isinstance(role_name, str):
group_name = f"{role_name.capitalize()} Group"
else:
group_name = f"{role_name.value.capitalize()} Group"
group, created = Group.objects.get_or_create(name=group_name)
self.groups.add(group)
# بروزرسانی user_type اگر نقش جدید اولویت بالاتری دارد
if role_name in ['professor', self.UserType.PROFESSOR] and self.user_type != self.UserType.PROFESSOR:
self.user_type = self.UserType.PROFESSOR
self.save()
elif role_name in ['student', self.UserType.STUDENT] and self.user_type == self.UserType.CLIENT:
self.user_type = self.UserType.STUDENT
self.save()
def remove_role(self, role_name):
"""حذف نقش خاص"""
from django.contrib.auth.models import Group
if isinstance(role_name, str):
group_name = f"{role_name.capitalize()} Group"
else:
group_name = f"{role_name.value.capitalize()} Group"
try:
group = Group.objects.get(name=group_name)
self.groups.remove(group)
# بروزرسانی user_type بر اساس نقش‌های باقی‌مانده
self.user_type = self.primary_role
self.save()
except Group.DoesNotExist:
pass
def get_all_roles(self):
"""دریافت لیست تمام نقش‌های کاربر"""
return [group.name.replace(' Group', '').lower()
for group in self.groups.all()]
def can_teach_course(self):
"""آیا می‌تواند دوره تدریس کند؟"""
# اولویت اول: staff یا admin
if self.is_staff or self.has_role('admin') or self.has_role('super_admin'):
return True
# اولویت دوم: professor
return self.has_role('professor')
def can_enroll_course(self):
"""آیا می‌تواند در دوره ثبت‌نام کند؟"""
return True # همه می‌توانند دانش‌آموز باشند
def can_manage_course(self, course=None):
"""آیا می‌تواند دوره خاصی را مدیریت کند؟"""
# اولویت اول: staff یا admin - دسترسی کامل
if self.is_staff or self.has_role('admin') or self.has_role('super_admin'):
return True
# اولویت دوم: professor - فقط دوره‌های خودش
if course and self.has_role('professor'):
return course.professor == self
return False
def ensure_professor_profile(self, commit: bool = True) -> bool:
"""تضمین می‌کند کاربر نقش استاد دارد، اسلاگ دارد و در گروه استاد است."""
updated_fields = set()
if self.user_type != self.UserType.PROFESSOR:
self.user_type = self.UserType.PROFESSOR
updated_fields.add('user_type')
if not self.slug:
self._ensure_professor_slug()
if self.slug:
updated_fields.add('slug')
from django.contrib.auth.models import Group
group, _ = Group.objects.get_or_create(name="Professor Group")
group_added = False
if not self.groups.filter(id=group.id).exists():
self.groups.add(group)
group_added = True
if commit and updated_fields:
self.save(update_fields=list(updated_fields))
return bool(updated_fields or group_added)
def _ensure_professor_slug(self):
if self.slug:
return
base_candidates = [
self.fullname,
(self.email.split('@')[0] if self.email else None),
self.username,
]
for candidate in base_candidates:
if candidate:
self.slug = self._build_unique_slug(candidate)
if self.slug:
return
self.slug = self._build_unique_slug(f"professor-{secrets.token_hex(4)}")
def _build_unique_slug(self, seed: str) -> str:
base_slug = slugify(seed, allow_unicode=True)
if not base_slug:
base_slug = f"professor-{secrets.token_hex(4)}"
slug = base_slug
counter = 1
qs = User.objects.all()
if self.pk:
qs = qs.exclude(pk=self.pk)
while qs.filter(slug=slug).exists():
slug = f"{base_slug}-{counter}"
counter += 1
return slug[:255]
class Meta:
ordering = ("-id",)
verbose_name = "All Users"
verbose_name_plural = "All Users"
unique_together = (
'email',
)
class LoginHistory(models.Model):
user = models.ForeignKey("account.User", on_delete=models.CASCADE, related_name='login_history')
lat = models.FloatField(verbose_name=_('lat'), null=True, blank=True)
lon = models.FloatField(verbose_name=_('lon'), 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)
timezone = models.CharField(max_length=100, null=True, blank=True)
user_agent = models.TextField(verbose_name=_('user agent'), null=True, blank=True)
device_os = models.CharField(max_length=16, null=True, blank=True)
at_time = models.DateTimeField(auto_now_add=True)
class LocationHistory(models.Model):
user = models.ForeignKey("account.User", on_delete=models.CASCADE, related_name='location_history')
lat = models.FloatField(verbose_name=_('lat'))
lon = models.FloatField(verbose_name=_('lon'))
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(null=True, blank=True)
ip = models.CharField(max_length=255, null=True, blank=True)
timezone = models.CharField(null=True, blank=True, max_length=60)
at_time = models.DateTimeField(auto_now_add=True)