1. Схема данных практикума
В практикуме 5 мы строим систему управления складом. Вот схема связей между моделями:
Category ←── Product ──→ Supplier
│
└──(OneToOne)── ProductDetail
Address ←──(OneToOne)── Customer ──→ Order ──→ OrderItem ──→ Product
- Category — категория товара (уникальное имя, сортировка по алфавиту)
- Supplier — поставщик (имя, email, телефон — все уникальные)
- Product — товар (связан с Category и Supplier через ForeignKey PROTECT)
- ProductDetail — детали продукта (OneToOne → Product, CASCADE)
- Address — адрес доставки
- Customer — покупатель (OneToOne → Address, SET_NULL; поддержка мягкого удаления)
- Order — заказ (ForeignKey → Customer, PROTECT)
- OrderItem — позиция заказа (ForeignKey → Order CASCADE, ForeignKey → Product PROTECT)
2. on_delete — стратегии при удалении родителя
| Стратегия | Поведение | Пример применения |
|---|---|---|
CASCADE |
Удалить дочерние записи | ProductDetail при удалении Product |
PROTECT |
Запретить удаление родителя | Category нельзя удалить, пока есть товары |
SET_NULL |
Обнулить поле (нужен null=True) | Customer.address при удалении адреса |
SET_DEFAULT |
Установить значение по умолчанию | Редко используется |
3. DecimalField для денег
Для хранения денежных значений всегда используйте DecimalField, а не FloatField. Float имеет погрешности при округлении, что недопустимо для финансовых данных.
price = models.DecimalField(max_digits=10, decimal_places=2)
# max_digits=10 — всего 10 цифр (включая после точки)
# decimal_places=2 — 2 знака после точки
# Диапазон: до 99_999_999.99
4. Мягкое удаление (soft delete)
В модели Customer реализован паттерн мягкого удаления: вместо физического удаления записи устанавливается флаг deleted=True и дата deleted_at. Это позволяет восстанавливать данные и сохранять историю.
class Customer(models.Model):
deleted = models.BooleanField(default=False)
deleted_at = models.DateTimeField(null=True, blank=True)
# Пример использования:
# customer.deleted = True
# customer.deleted_at = timezone.now()
# customer.save()
5. Meta: get_latest_by
Атрибут get_latest_by указывает поле для метода Model.objects.latest(). Без него .latest() требует явного аргумента.
class Order(models.Model):
order_date = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-order_date']
get_latest_by = 'order_date'
# Использование:
latest_order = Order.objects.latest() # работает без аргумента
6. Admin: list_editable
Атрибут list_editable позволяет редактировать поля прямо в списке записей, без открытия формы редактирования каждой записи. Важно: поле должно присутствовать в list_display.
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ['name', 'price', 'quantity', 'available']
list_editable = ['price', 'quantity', 'available']
# ✅ Все поля из list_editable есть в list_display
7. db_index на поле
Параметр db_index=True создаёт индекс БД по этому полю. Ускоряет выборки по полю, но замедляет вставку/обновление. Применяйте к полям, по которым часто фильтруете или ищете.
article = models.CharField(
max_length=100,
unique=True,
help_text='Unique string product id',
db_index=True # уже создаётся при unique=True, здесь явно для наглядности
)
unique=True автоматически получает индекс. Указывать db_index=True дополнительно избыточно, но допустимо — как в задании практикума.