Files
vendora/Backend/app/models.py
T
2025-04-23 22:16:54 +05:45

334 lines
12 KiB
Python

from django.db import models
from django.contrib.auth.hashers import make_password, check_password
from django.dispatch import receiver
from rest_framework.authtoken.models import Token
from django.db.models.signals import post_save
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.core.validators import MinValueValidator, MaxValueValidator
class UserManager(BaseUserManager):
def create_user(self, username, email, password=None, **extra_fields):
if not email:
raise ValueError('Users must have an email address')
if not username:
raise ValueError('Users must have a username')
email = self.normalize_email(email)
user = self.model(
username=username,
email=email,
**extra_fields
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, username, email, password=None, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
return self.create_user(username, email, password, **extra_fields)
class User(AbstractBaseUser, PermissionsMixin):
id = models.BigAutoField(primary_key=True)
username = models.CharField(max_length=150, unique=True)
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
email = models.EmailField(unique=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
phone = models.CharField(max_length=15, blank=True, null=True)
address = models.TextField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
phone_number = models.CharField(max_length=15, blank=True, null=True)
objects = UserManager()
USERNAME_FIELD = 'username'
EMAIL_FIELD = 'email'
REQUIRED_FIELDS = ['email', 'first_name', 'last_name']
def __str__(self):
return self.username
def set_password(self, raw_password):
self.password = make_password(raw_password)
def check_password(self, raw_password):
return check_password(raw_password, self.password)
class Meta:
verbose_name = 'User'
verbose_name_plural = 'Users'
ordering = ['username']
def get_full_name(self):
return f"{self.first_name} {self.last_name}"
def get_short_name(self):
return self.first_name
@receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Token.objects.create(user=instance)
class Category(models.Model):
id = models.BigAutoField(primary_key=True)
name = models.CharField(max_length=100, unique=True)
image = models.ImageField(upload_to='categories/', blank=True, null=True)
description = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
class Meta:
verbose_name = 'Category'
verbose_name_plural = 'Categories'
ordering = ['name']
@property
def image_url(self):
if self.image:
return f'/media/{self.image.name}'
return None
class Product(models.Model):
id = models.BigAutoField(primary_key= True)
name = models.CharField(max_length=100)
description = models.TextField()
price = models.DecimalField(max_digits=10, decimal_places=2)
image = models.ImageField(upload_to='products/', blank=True, null=True)
category = models.ForeignKey(Category, related_name='products', on_delete=models.CASCADE)
size = models.CharField(max_length=10, blank=True, null=True)
color = models.CharField(max_length=20, blank=True, null=True)
stock = models.IntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
average_rating = models.DecimalField(max_digits=3, decimal_places=2, default=0)
def __str__(self):
return self.name
class Meta:
verbose_name = 'Product'
verbose_name_plural = 'Products'
ordering = ['name']
@property
def image_url(self):
if self.image:
return f'/media/{self.image.name}'
return None
def update_average_rating(self):
avg_rating = self.ratings.aggregate(avg_rating=models.Avg('rating'))['avg_rating']
self.average_rating = avg_rating or 0
self.save()
class Rating(models.Model):
id = models.BigAutoField(primary_key= True)
product = models.ForeignKey(Product, related_name='ratings', on_delete=models.CASCADE)
user = models.ForeignKey(User, related_name='ratings', on_delete=models.CASCADE)
rating = models.IntegerField(validators=[MinValueValidator(1), MaxValueValidator(5)])
comment = models.TextField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
unique_together = ('product', 'user')
verbose_name = 'Rating'
verbose_name_plural = 'Ratings'
ordering = ['-created_at']
def __str__(self):
return f'{self.user.username} - {self.product.name} - {self.rating}'
class Order(models.Model):
id = models.AutoField(primary_key=True)
user = models.ForeignKey(User, related_name='orders', on_delete=models.CASCADE)
total_price = models.DecimalField(max_digits=10, decimal_places=2, default=0)
status = models.CharField(max_length=20, default='Pending', choices=[
('Pending', 'Pending'),
('Shipped', 'Shipped'),
('Delivered', 'Delivered'),
('Cancelled', 'Cancelled')
])
shipping_address = models.CharField(max_length=255, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f'Order {self.id} by {self.user.username}'
def update_total(self):
"""Automatically calculate total price from order items"""
order_items = self.order_items.all()
if not order_items.exists():
self.total_price = 0
else:
self.total_price = sum(item.get_subtotal() for item in order_items)
self.save()
def add_product(self, product, quantity=1):
"""Add a product to the order"""
if product is None:
return None
if quantity is None or quantity <= 0:
quantity = 1
order_item, created = OrderItem.objects.get_or_create(
order=self,
product=product,
defaults={
'quantity': quantity,
'price': product.price if product.price else 0
}
)
if not created:
order_item.quantity += quantity
order_item.save()
self.update_total()
return order_item
class Meta:
verbose_name = 'Order'
verbose_name_plural = 'Orders'
ordering = ['-created_at']
class OrderItem(models.Model):
order = models.ForeignKey(Order, related_name='order_items', on_delete=models.CASCADE)
product = models.ForeignKey(Product, related_name='order_items', on_delete=models.CASCADE)
quantity = models.IntegerField()
price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
def get_subtotal(self):
"""Calculate the subtotal for this item"""
if self.price is None or self.quantity is None:
return 0
return self.price * self.quantity
def __str__(self):
return f'{self.product.name} - {self.quantity}'
def save(self, *args, **kwargs):
# Ensure price is set correctly if it's none or a new order item
if self.price is None or (not self.pk and not self.price):
self.price = self.product.price if self.product and self.product.price else 0
# Ensure quantity is never None
if self.quantity is None:
self.quantity = 1
super().save(*args, **kwargs)
# Update the order total when an item is saved
if self.order:
self.order.update_total()
def delete(self, *args, **kwargs):
order = self.order
super().delete(*args, **kwargs)
# Update the order total when an item is deleted
if order:
order.update_total()
class Meta:
verbose_name = 'Order Item'
verbose_name_plural = 'Order Items'
ordering = ['order']
@receiver(post_save, sender=Order)
def update_order_status(sender, instance, created, **kwargs):
"""When an order status changes to Delivered, update product stock"""
if not created and instance.status == 'Delivered':
for item in instance.order_items.all():
product = item.product
# Only update stock if we haven't already updated it
if product.stock >= item.quantity:
product.stock -= item.quantity
product.save()
class Cart(models.Model):
user = models.OneToOneField(User, related_name='cart', on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f"Cart for {self.user.username}"
def get_total(self):
"""Calculate total price of all items in cart"""
return sum(item.get_subtotal() for item in self.items.all())
def add_product(self, product, quantity=1):
"""Add a product to the cart"""
if quantity <= 0:
return None
cart_item, created = CartItem.objects.get_or_create(
cart=self,
product=product,
defaults={'quantity': quantity}
)
if not created:
cart_item.quantity += quantity
cart_item.save()
return cart_item
def remove_product(self, product):
"""Remove a product from the cart"""
try:
cart_item = CartItem.objects.get(cart=self, product=product)
cart_item.delete()
return True
except CartItem.DoesNotExist:
return False
def clear(self):
"""Remove all items from the cart"""
self.items.all().delete()
class Meta:
verbose_name = 'Cart'
verbose_name_plural = 'Carts'
ordering = ['-updated_at']
class CartItem(models.Model):
cart = models.ForeignKey(Cart, related_name='items', on_delete=models.CASCADE)
product = models.ForeignKey(Product, related_name='cart_items', on_delete=models.CASCADE)
quantity = models.PositiveIntegerField(default=1)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f"{self.quantity} x {self.product.name} in {self.cart}"
def get_subtotal(self):
"""Calculate the subtotal for this item"""
return self.product.price * self.quantity
class Meta:
verbose_name = 'Cart Item'
verbose_name_plural = 'Cart Items'
ordering = ['-updated_at']
unique_together = ['cart', 'product']
@receiver(post_save, sender=User)
def create_user_cart(sender, instance, created, **kwargs):
"""Create a cart for new users"""
if created:
Cart.objects.create(user=instance)
class Favorite(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='favorites')
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='favorites')
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = ['user', 'product']
def __str__(self):
return f"{self.user.username}'s favorite: {self.product.name}"