All checks were successful
Build and Push Docker Image / build (push) Successful in 30s
- Introduced `Subcategory` model with migration and unique slug constraint per category - Enabled subcategory assignment for items with validation to ensure category matching - Updated catalog views to handle subcategories, filtering items accordingly - Enhanced templates (`index.html`, `side-bar.html`, `item-list.html`, `detail.html`) to display subcategories - Added dynamic subcategory dropdown in Django admin with category filtering - Created JavaScript for admin subcategory handling and AJAX population - Wrote comprehensive tests for subcategory functionality in views and admin
94 lines
3.4 KiB
Python
94 lines
3.4 KiB
Python
from django.db import models
|
||
from django.core.exceptions import ValidationError
|
||
|
||
|
||
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='Цена')
|
||
is_available = models.BooleanField(default=True, 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)
|
||
|
||
|
||
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}'
|