/
HitriyKrab
/
DjangoProjectShop
Обзор
Документация
Войти
/
HitriyKrab
/
DjangoProjectShop
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
MyShop/models.py
221 строка
10 KB
ХитрыйКраб
Добавлено: ограниченный тираж и популярные товары для стартовой страницы
16 ноя 2025, 15:22
16 ноя 2025, 15:22
ed89bf2
Код
Авторство
О чём код?
from django.db import models from django.forms import ValidationError from django.contrib.auth.models import User # Функция-валидатор (проверка корректности) цены товара def price_valid(field): if field < 0: raise ValidationError('Цена не может быть отрицательной.') # Функция-валидатор скидки на товар def discount_valid(field): if field < 0 or field > 50: raise ValidationError('Скидка должна быть в диапазоне от 1 до 49%.') # Функция генерации пути к картинке товара при ее сохранении def prod_preview_dir_path(instance: 'Product', filename: str) -> str: return f'product_images/product_{instance.pk}/preview_{filename}' class Category(models.Model): '''Модель "Категории товаров"''' class Meta: ordering = ('name',) verbose_name = 'Категория' verbose_name_plural = 'Категории' # Название категории name = models.CharField(max_length=150, db_index=True, verbose_name='Название категории') # Название категории, которое будет в url-строке slug = models.SlugField(max_length=150, unique=True, verbose_name='URL') def __str__(self): return f'Категория: {self.name}' class Product(models.Model): '''Модель "Товар"''' # Мета - добавление всяких прикольных штук к моделям class Meta: # Сортировка по убывающей цене (работает и в связанных моделях из-за many-to-many) ordering = ['-sort_index', '-purchase_count', '-price'] # Отображение данных во множественном числе в административной панели verbose_name_plural = 'Товары' # Название товара (добавлен индекс для ускорения выполнения запросов к БД) name = models.CharField(max_length=45, db_index=True, verbose_name='Название') # Описание товара (может быть пустым, добавлен индекс для ускорения выполнения запросов к БД) description = models.TextField(null=False, blank=True, db_index=True, verbose_name='Описание') # Цена (по умолч. = 0, округл. до 100х, цифр >= 9, валидация (не может быть отрицательной)) price = models.DecimalField(default=0, decimal_places=2, max_digits=9, validators=[price_valid], verbose_name='Цена') # Скидка discount = models.SmallIntegerField(default=0, validators=[discount_valid], verbose_name='Скидка') # Дата создания (автоматически устанавливается при создании продукта) created_at = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания') # Картинка\фото товара (для его просмотра в списке; указана функция генерации пути к картинке) preview = models.ImageField(null=True, blank=True, upload_to=prod_preview_dir_path, verbose_name='Изображение') # Категория товара category = models.ForeignKey(Category, related_name='products', on_delete=models.CASCADE, verbose_name='Категория') sort_index = models.IntegerField(default=0, verbose_name='Индекс сортировки') purchase_count = models.PositiveIntegerField(default=0, verbose_name='Количество покупок') limited_edition = models.BooleanField(default=False, verbose_name='Ограниченный тираж') # Строковое представление артефакта в админке''' def __str__(self) -> str: return f'Товар: {self.name!r}' class Order(models.Model): '''Модель "Заказ"''' class Meta: ordering = ['-created_at',] verbose_name_plural = 'Заказы' FST_DELIVERY = 'Доставка (+200₽)' SEC_DELIVERY = 'Экспресс-доставка (+500₽)' DELIVERY_CHOICES = [(FST_DELIVERY, 'Доставка (+200₽)'), (SEC_DELIVERY, 'Экспресс-доставка (+500₽)')] STATUS_NEW = 'Новый' STATUS_PAID = 'Оплачен' STATUS_SHIPPED = 'Отправлен' STATUS_CHOICES = [(STATUS_NEW, 'Новый'), (STATUS_PAID, 'Оплачен'), (STATUS_SHIPPED, 'Отправлен')] # Город для доставки заказа city = models.CharField(max_length=100, verbose_name='Город') # Адрес доставки заказа delivery_address = models.TextField(null=True, blank=True, db_index=True, verbose_name='Адрес') # Дата создания заказа created_at = models.DateTimeField(auto_now_add=True, db_index=True) # Заказчик (связана с пользователем по внешн\кл, блокирует удаление выполненных заказов при удалении пользователя) user = models.ForeignKey(User, on_delete=models.PROTECT, db_index=True) # Товары в заказе products = models.ManyToManyField(Product, related_name="orders") # Итоговая цена заказа (товары + доставка) total_price = models.DecimalField(max_digits=10, decimal_places=2) # Способы доставки заказа (можно выбрать через всплывающий список) delivery_method = models.CharField( max_length=49, choices=DELIVERY_CHOICES, default=FST_DELIVERY, verbose_name='Способ доставки' ) # Статус заказа (меняется администратором при оплате или отправке) status = models.CharField( max_length=49, choices=STATUS_CHOICES, default=STATUS_NEW, verbose_name='Статус заказа' ) # Строковое отображение модели "Заказ" (например для админки) def __str__(self) -> str: return f'Заказ №{self.pk!r}' # Подсчёт стоимости доставки (200₽, экспресс - 500₽, если больше 2000 - бесплатно) def delivery_price(self): settings = DeliverySettings.objects.first() if not settings: return 0 if self.delivery_method == Order.SEC_DELIVERY: return settings.express_delivery_extra_price elif self.delivery_method == Order.FST_DELIVERY: if self.total_price < settings.free_delivery_threshold: return settings.standard_delivery_price else: return 0 return 0 # Подсчёт общей стоимости заказа (товар + доставка) def calculate_total_price(self): total = sum(item.price * item.quantity for item in self.items.all()) total += self.delivery_price() return total # Внесение общей стоимости заказа в поле "total_price" в заказе def update_total_price(self): self.total_price = self.calculate_total_price() self.save(update_fields=['total_price']) class Cart(models.Model): '''Модель "Корзина"''' # Пользователь, что добавляет товары в корзину user = models.ForeignKey(User, on_delete=models.PROTECT) # Товары в корзине products = models.ManyToManyField(Product) class Review(models.Model): '''Модель "Отзыв"''' class Meta: ordering = ['-created_at',] verbose_name_plural = 'Отзывы' # Товар, на который будет оставлен отзыв product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='reviews') # Пользователь, который оставит отзыв user = models.ForeignKey(User, on_delete=models.CASCADE) # Текст отзыва comment = models.TextField() # Дата написания отзыва created_at = models.DateTimeField(auto_now_add=True) def __str__(self) -> str: return f'Отзыв от {self.user} на {self.product}' class Payment(models.Model): '''Модель "Оплата"''' PAYMENT_CHOICES = [ ('card', 'Оплата картой'), ('transfer', 'Денежный перевод по номеру телефона'), ('cash', 'Наличными при получении'), ] # Заказ для оплаты (объект модели "Заказ") order = models.ForeignKey(Order, on_delete=models.CASCADE) # Создатель заказа (объект модели "Пользователь") user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) # Размер оплаты заказа (стоимость) amount = models.DecimalField(max_digits=10, decimal_places=2) # Способ оплаты method = models.CharField(max_length=20, choices=PAYMENT_CHOICES) # Статус оплаты status = models.CharField(max_length=20, default='Новый') # Дата оплаты created_at = models.DateTimeField(auto_now_add=True) class DeliverySettings(models.Model): '''Модель "Настройки доставки"''' # Решил не подписывать - в verbose_name все понятно standard_delivery_price = models.PositiveIntegerField(default=200, verbose_name='Стоимость обычной доставки') free_delivery_threshold = models.PositiveIntegerField(default=2000, verbose_name='Порог бесплатной доставки') express_delivery_extra_price = models.PositiveIntegerField(default=500, verbose_name='Доплата за экспресс-доставку') def __str__(self): return "Настройки доставки"