All checks were successful
Build and Push Docker Image / build (push) Successful in 26s
- Updated `Item` model to use a ManyToManyField for colors, replacing the old ForeignKey relationship - Modified templates to handle multiple color swatches in product details - Updated admin for managing colors with `filter_horizontal` - Adjusted views to prefetch related `colors` data for items - Added migration to apply database changes
129 lines
4.5 KiB
Python
129 lines
4.5 KiB
Python
from decimal import Decimal
|
||
|
||
from django.db import models
|
||
from django.core.exceptions import ValidationError
|
||
|
||
|
||
class Color(models.Model):
|
||
name = models.CharField(max_length=100, verbose_name='Название цвета')
|
||
hex_code = models.CharField(max_length=7, verbose_name='Цвет (HEX)', help_text='Например, #FF5500')
|
||
|
||
class Meta:
|
||
ordering = ('name',)
|
||
verbose_name = 'Цвет'
|
||
verbose_name_plural = 'Цвета'
|
||
|
||
def __str__(self):
|
||
return f'{self.name} ({self.hex_code})'
|
||
|
||
|
||
class Category(models.Model):
|
||
name = models.CharField(max_length=255, verbose_name='Название')
|
||
slug = models.SlugField(unique=True, verbose_name='URL')
|
||
|
||
class Meta:
|
||
verbose_name = 'Категория'
|
||
verbose_name_plural = 'Категории'
|
||
|
||
def __str__(self):
|
||
return self.name
|
||
|
||
|
||
class Subcategory(models.Model):
|
||
category = models.ForeignKey(
|
||
Category,
|
||
on_delete=models.CASCADE,
|
||
related_name='subcategories',
|
||
verbose_name='Категория',
|
||
)
|
||
name = models.CharField(max_length=255, verbose_name='Название')
|
||
slug = models.SlugField(verbose_name='URL')
|
||
|
||
class Meta:
|
||
ordering = ('category__name', 'name')
|
||
constraints = [
|
||
models.UniqueConstraint(
|
||
fields=('category', 'slug'),
|
||
name='unique_subcategory_slug_per_category',
|
||
)
|
||
]
|
||
verbose_name = 'Подкатегория'
|
||
verbose_name_plural = 'Подкатегории'
|
||
|
||
def __str__(self):
|
||
return self.name
|
||
|
||
|
||
class Item(models.Model):
|
||
name = models.CharField(max_length=255, verbose_name='Название')
|
||
slug = models.SlugField(unique=True, verbose_name='URL')
|
||
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='items', verbose_name='Категория')
|
||
subcategory = models.ForeignKey(
|
||
Subcategory,
|
||
on_delete=models.SET_NULL,
|
||
related_name='items',
|
||
null=True,
|
||
blank=True,
|
||
verbose_name='Подкатегория',
|
||
)
|
||
description = models.TextField(blank=True, verbose_name='Описание')
|
||
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='Цена')
|
||
discount_price = models.DecimalField(
|
||
max_digits=10, decimal_places=2,
|
||
null=True, blank=True,
|
||
verbose_name='Цена со скидкой',
|
||
)
|
||
is_available = models.BooleanField(default=True, verbose_name='В наличии')
|
||
is_new = models.BooleanField(default=False, verbose_name='Новинка')
|
||
colors = models.ManyToManyField(
|
||
'Color',
|
||
blank=True,
|
||
related_name='items',
|
||
verbose_name='Цвета',
|
||
)
|
||
created_at = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')
|
||
updated_at = models.DateTimeField(auto_now=True, verbose_name='Дата обновления')
|
||
|
||
class Meta:
|
||
verbose_name = 'Товар'
|
||
verbose_name_plural = 'Товары'
|
||
|
||
def __str__(self):
|
||
return self.name
|
||
|
||
def clean(self):
|
||
super().clean()
|
||
|
||
if self.subcategory_id and self.category_id and self.subcategory.category_id != self.category_id:
|
||
raise ValidationError({
|
||
'subcategory': 'Подкатегория должна относиться к выбранной категории.'
|
||
})
|
||
|
||
@property
|
||
def primary_image(self):
|
||
return next(iter(self.images.all()), None)
|
||
|
||
@property
|
||
def display_price(self) -> Decimal:
|
||
return self.discount_price if self.discount_price is not None else self.price
|
||
|
||
@property
|
||
def has_discount(self) -> bool:
|
||
return self.discount_price is not None
|
||
|
||
|
||
class ItemImage(models.Model):
|
||
item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='images', verbose_name='Товар')
|
||
image = models.ImageField(upload_to='items/', verbose_name='Изображение')
|
||
alt = models.CharField(max_length=255, blank=True, verbose_name='Alt-текст')
|
||
sort_order = models.PositiveIntegerField(default=0, verbose_name='Порядок')
|
||
is_primary = models.BooleanField(default=False, verbose_name='Главное изображение')
|
||
|
||
class Meta:
|
||
ordering = ('-is_primary', 'sort_order', 'id')
|
||
verbose_name = 'Изображение товара'
|
||
verbose_name_plural = 'Изображения товара'
|
||
|
||
def __str__(self):
|
||
return self.alt or f'Изображение товара {self.item.name}'
|