from django.contrib.auth.base_user import AbstractBaseUser from django.core.validators import MinValueValidator, MaxValueValidator from django.db import models from phonenumber_field.modelfields import PhoneNumberField from django.conf import settings from django.db.models.signals import post_save from django.dispatch import receiver from django.contrib.auth.models import BaseUserManager class UserManager(BaseUserManager): def create_user(self, phone, email=None, password=None, **extra_fields): if not phone: raise ValueError('The Phone field must be set') user = self.model(phone=phone, email=email, **extra_fields) user.set_password(password) user.save(using=self._db) return user def create_superuser(self, phone, email=None, password=None, **extra_fields): extra_fields.setdefault('is_staff', True) extra_fields.setdefault('is_superuser', True) if extra_fields.get('is_staff') is not True: raise ValueError('Superuser must have is_staff=True.') if extra_fields.get('is_superuser') is not True: raise ValueError('Superuser must have is_superuser=True.') return self.create_user(phone, email, password, **extra_fields) class User(AbstractBaseUser): name = models.CharField(max_length=255, blank=True, null=True) email = models.EmailField(unique=False, null=True, blank=True) phone = PhoneNumberField(unique=True) about_user = models.TextField(null=True, blank=True) work_experience = models.FloatField(null=True, blank=True) role = models.CharField(max_length=50, choices=[('client', 'Client'), ('executor', 'Executor')], default='client') password = models.CharField(max_length=255) created_at = models.DateTimeField(auto_now_add=True) region = models.CharField(max_length=100, null=True, blank=True) is_staff = models.BooleanField(default=False) is_superuser = models.BooleanField(default=False) telegram_username = models.CharField(max_length=255, null=True, blank=True) gender = models.CharField(max_length=10, choices=[('male', 'Male'), ('female', 'Female')], null=True, blank=True) executor_rating = models.FloatField(default=0.0, validators=[MinValueValidator(0.0), MaxValueValidator(5.0)]) client_rating = models.FloatField(default=0.0, validators=[MinValueValidator(0.0), MaxValueValidator(5.0)]) avatar = models.ImageField(upload_to='avatars/', null=True, blank=True) birthday = models.DateField(null=True, blank=True) objects = UserManager() USERNAME_FIELD = 'phone' REQUIRED_FIELDS = [] class Meta: db_table = 'users' def __str__(self): return str(self.phone) def has_perm(self, perm, obj=None): return self.is_superuser def has_module_perms(self, app_label): return self.is_superuser def update_ratings(self): # Calculate average executor rating executor_reviews = self.executor_reviews_as_executor.all() if executor_reviews.exists(): self.executor_rating = executor_reviews.aggregate(avg_rating=models.Avg('rating'))['avg_rating'] or 0.0 else: self.executor_rating = 0.0 # Calculate average client rating client_reviews = self.client_reviews_as_client.all() if client_reviews.exists(): self.client_rating = client_reviews.aggregate(avg_rating=models.Avg('rating'))['avg_rating'] or 0.0 else: self.client_rating = 0.0 self.save() class OTP_table(models.Model): phone = PhoneNumberField() code = models.CharField(max_length=6) created_at = models.DateTimeField(auto_now_add=True) expires_at = models.DateTimeField() class Meta: db_table = 'otp_table' def __str__(self): return f"Code {self.code} for {self.phone}" class Category(models.Model): name = models.CharField(max_length=255) display_ru = models.CharField(max_length=255, null=True, blank=True) display_uz = models.CharField(max_length=255, null=True, blank=True) service_count = models.IntegerField(default=0) class SubCategory(models.Model): name = models.CharField(max_length=255) display_ru = models.CharField(max_length=255, blank=True, null=True) display_uz = models.CharField(max_length=255, blank=True, null=True) category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='sub_categories') class Meta: db_table = 'sub_categories' def __str__(self): return self.name class Service(models.Model): MODERATION_CHOICES = ( ('Pending', 'Pending'), ('Approved', 'Approved'), ('Rejected', 'Rejected'), ) name = models.CharField(max_length=255) description = models.TextField() category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='services') sub_categories = models.ManyToManyField(SubCategory, related_name='services') price = models.DecimalField(max_digits=10, decimal_places=2) executor = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='services') moderation = models.CharField(max_length=20, choices=MODERATION_CHOICES, default='Pending', null=True, blank=True) class Meta: db_table = 'services' def __str__(self): return self.name class ServiceImage(models.Model): service = models.ForeignKey(Service, on_delete=models.CASCADE, related_name='images') image = models.ImageField(upload_to='services/') uploaded_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = 'service_images' def __str__(self): return f"Image for {self.service.name} uploaded at {self.uploaded_at}" class Vacancy(models.Model): name = models.CharField(max_length=255, null=False, blank=False) description = models.CharField(max_length=512, null=True, blank=True) price = models.DecimalField(max_digits=10, decimal_places=2) category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='category') sub_categories = models.ForeignKey(SubCategory, on_delete=models.CASCADE, related_name="sub_categories") client = models.ForeignKey(User, on_delete=models.CASCADE, related_name="client") images = models.ImageField(upload_to='vacancies/') class VacancyImage(models.Model): vacancy = models.ForeignKey( Vacancy, on_delete=models.CASCADE, related_name="images" ) image = models.ImageField(upload_to="vacancies/") uploaded_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Image for {self.vacancy.name}" class ExecutorReview(models.Model): order = models.ForeignKey('Order', on_delete=models.CASCADE, related_name='executor_reviews') executor = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='executor_reviews_as_executor' ) client = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='executor_reviews_as_client' ) rating = models.FloatField( validators=[MinValueValidator(1.0), MaxValueValidator(5.0)], help_text="Rating between 1.0 and 5.0" ) review = models.TextField(max_length=250, help_text="Review text up to 250 characters") created_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = 'executor_reviews' unique_together = ('order', 'client') # Prevent multiple reviews by the same client for an order def __str__(self): return f"Review by {self.client} for executor {self.executor} on order {self.order.id}" vacancy = models.ForeignKey(Vacancy, on_delete=models.CASCADE, related_name='images') image = models.ImageField(upload_to='vacancies/') uploaded_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = 'vacancy_images' def __str__(self): return f"Image for {self.vacancy.name} uploaded at {self.uploaded_at}" class ClientReview(models.Model): order = models.ForeignKey('Order', on_delete=models.CASCADE, related_name='client_reviews') executor = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='client_reviews_as_executor' ) client = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='client_reviews_as_client' ) rating = models.FloatField( validators=[MinValueValidator(1.0), MaxValueValidator(5.0)], help_text="Rating between 1.0 and 5.0" ) review = models.TextField(max_length=250, help_text="Review text up to 250 characters") created_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = 'client_reviews' unique_together = ('order', 'executor') # Prevent multiple reviews by the same executor for an order def __str__(self): return f"Review by {self.executor} for client {self.client} on order {self.order.id}" class ChatRoom(models.Model): name = models.CharField(max_length=255, blank=True, null=True) created_at = models.DateTimeField(auto_now_add=True) participants = models.ManyToManyField( settings.AUTH_USER_MODEL, related_name="chat_rooms", blank=True ) def __str__(self): return self.name or f"ChatRoom {self.id}" class Chat_table(models.Model): user1 = models.ForeignKey(User, on_delete=models.CASCADE, related_name="chats_started") user2 = models.ForeignKey(User, on_delete=models.CASCADE, related_name="chats_received") created_at = models.DateTimeField(auto_now_add=True) last_message = models.TextField(blank=True, null=True) def __str__(self): return f"Chat {self.id}: {self.user1} - {self.user2}" class Order(models.Model): service = models.ForeignKey(Service, on_delete=models.CASCADE, related_name='orders', null=True, blank=True) vacancy = models.ForeignKey(Vacancy, on_delete=models.CASCADE, related_name='orders', null=True, blank=True) client = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='client_orders') status = models.CharField(max_length=50, choices=[ ('Awaiting', 'Awaiting'), ('Pending', 'Pending'), ('Completed', 'Completed'), ('Cancelled', 'Cancelled') ], default='Awaiting') class Meta: db_table = 'orders' def __str__(self): return f"Order {self.id} for {self.service.name if self.service else self.vacancy.name}" def clean(self): from django.core.exceptions import ValidationError if (self.service is None and self.vacancy is None) or (self.service is not None and self.vacancy is not None): raise ValidationError("An order must reference exactly one service or vacancy.") room = models.ForeignKey(ChatRoom, on_delete=models.CASCADE, related_name='messages') sender = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='sent_messages') content = models.TextField(null=True, blank=True) file = models.FileField(upload_to='chat_files/', null=True, blank=True) timestamp = models.DateTimeField(auto_now_add=True) class Meta: db_table = 'messages' ordering = ['timestamp'] def __str__(self): return f"Message in {self.room.name} by {self.sender.name}" class Reklama(models.Model): image = models.ImageField(upload_to='reklamas/', verbose_name="Reklama Banner") start_date = models.DateTimeField(verbose_name="Start of Showing Reklama") end_date = models.DateTimeField(verbose_name="End of Showing Reklama") region = models.CharField(max_length=500, null=True, blank=True, verbose_name="Regions (comma-separated)", help_text="Enter regions separated by commas, e.g., 'Region1, Region2'") link = models.URLField(max_length=2000, null=True, blank=True, verbose_name="Click Link", help_text="URL where the banner should redirect when clicked") class Meta: db_table = 'reklamas' verbose_name = "Reklama" verbose_name_plural = "Reklamas" def __str__(self): return f"Reklama from {self.start_date} to {self.end_date}" # New Boost-related models class Boost(models.Model): name = models.CharField(max_length=100, unique=True) # e.g., "Top 1 Day", "Turbo 1 Week" boost_type = models.CharField(max_length=20, choices=[('Top', 'Top'), ('Turbo', 'Turbo')]) duration_days = models.PositiveIntegerField() # Duration in days (e.g., 1, 7, 14, 30) price = models.DecimalField(max_digits=10, decimal_places=2) discount = models.DecimalField(max_digits=5, decimal_places=2, default=0.00) # Percentage discount (e.g., 10.00 for 10%) applies_to = models.CharField(max_length=20, choices=[('Service', 'Service'), ('Vacancy', 'Vacancy')]) class Meta: db_table = 'boosts' def __str__(self): return f"{self.name} ({self.boost_type}) for {self.applies_to} - {self.duration_days} days" class BoostPayment(models.Model): amount = models.DecimalField(max_digits=10, decimal_places=2) service = models.ForeignKey('Service', on_delete=models.SET_NULL, null=True, blank=True) vacancy = models.ForeignKey('Vacancy', on_delete=models.SET_NULL, null=True, blank=True) boost = models.ForeignKey('Boost', on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now_add=True) status = models.CharField(max_length=20, default='pending') # pending, completed, cancelled boost_payment_id = models.IntegerField(editable=False, unique=True, null=True, blank=True) def save(self, *args, **kwargs): super().save(*args, **kwargs) if self.boost_payment_id != self.id: self.boost_payment_id = self.id super().save(update_fields=['boost_payment_id']) def __str__(self): return f"Order #{self.id}" @receiver(post_save, sender=ExecutorReview) @receiver(post_save, sender=ClientReview) def update_user_ratings(sender, instance, created, **kwargs): if created: if sender == ExecutorReview: instance.executor.update_ratings() elif sender == ClientReview: instance.client.update_ratings()