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()