💻 Примеры: модели и Admin практикума 5

Показательные примеры ключевых паттернов

⚡ Ключевые паттерны

# Модель с ForeignKey PROTECT
class Product(models.Model):
    category = models.ForeignKey(Category, on_delete=models.PROTECT, related_name='products')
    price = models.DecimalField(max_digits=10, decimal_places=2)
    available = models.BooleanField(default=True)
    class Meta:
        ordering = ['category', 'quantity']

# OneToOne с CASCADE
class ProductDetail(models.Model):
    product = models.OneToOneField(Product, on_delete=models.CASCADE, related_name='details')

# Admin с list_editable
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    list_display = ['name', 'price', 'quantity', 'available']
    list_editable = ['price', 'quantity', 'available']

Пример 1: Цепочка связей Category → Product → ProductDetail

Демонстрирует ForeignKey с PROTECT и OneToOne с CASCADE. При попытке удалить Category с продуктами — Django выбросит ProtectedError. Удаление Product автоматически удалит его ProductDetail.

from django.db import models

class Category(models.Model):
    name = models.CharField(max_length=40, unique=True)

    def __str__(self):
        return self.name

    class Meta:
        ordering = ['name']
        verbose_name_plural = 'categories'


class Supplier(models.Model):
    name = models.CharField(max_length=100, unique=True)
    contact_email = models.EmailField(unique=True)
    phone_number = models.CharField(max_length=20, unique=True)

    def __str__(self):
        return self.name


class Product(models.Model):
    name = models.CharField(max_length=100)
    category = models.ForeignKey(
        Category, on_delete=models.PROTECT, related_name='products'
    )
    supplier = models.ForeignKey(
        Supplier, on_delete=models.PROTECT, related_name='products'
    )
    price = models.DecimalField(max_digits=10, decimal_places=2)
    quantity = models.PositiveSmallIntegerField()
    article = models.CharField(
        max_length=100, unique=True,
        help_text='Unique string product id',
        db_index=True
    )
    available = models.BooleanField(default=True)

    def __str__(self):
        return self.name

    class Meta:
        ordering = ['category', 'quantity']


class ProductDetail(models.Model):
    product = models.OneToOneField(
        Product, on_delete=models.CASCADE, related_name='details'
    )
    description = models.TextField(null=True, blank=True)
    manufacturing_date = models.DateField(null=True, blank=True)
    expiration_date = models.DateField(null=True, blank=True)
    weight = models.DecimalField(
        null=True, blank=True, max_digits=5, decimal_places=2
    )

    def __str__(self):
        return f"Details of {self.product.name}"

Пример 2: Customer с мягким удалением

Паттерн soft delete: запись не удаляется физически, а помечается флагом deleted=True. Поле deleted_at фиксирует момент удаления.

class Address(models.Model):
    country = models.CharField(max_length=100)
    city = models.CharField(max_length=100)
    street = models.CharField(max_length=255)
    house = models.CharField(max_length=6)

    def __str__(self):
        return f"{self.street}, {self.house}"

    class Meta:
        verbose_name_plural = 'addresses'


class Customer(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    email = models.EmailField(unique=True)
    phone_number = models.CharField(max_length=15)
    address = models.OneToOneField(
        Address, null=True, on_delete=models.SET_NULL, related_name='customer'
    )
    date_joined = models.DateTimeField(auto_now_add=True)
    deleted = models.BooleanField(default=False)
    deleted_at = models.DateTimeField(null=True, blank=True)

    def __str__(self):
        return f"{self.first_name} {self.last_name}"

    class Meta:
        ordering = ['-date_joined']
        get_latest_by = 'date_joined'

Пример 3: Admin с list_editable и поиском по связанной модели

from django.contrib import admin
from .models import Category, Supplier, Product, Customer, Order

@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    list_display = ('name',)
    search_fields = ('name',)
    ordering = ('name',)


@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    list_display = ['name', 'category', 'supplier', 'price', 'quantity', 'article', 'available']
    list_filter = ['category', 'supplier', 'available']
    search_fields = ['name', 'article']
    ordering = ['category', 'quantity']
    list_editable = ['price', 'quantity', 'available']


@admin.register(Customer)
class CustomerAdmin(admin.ModelAdmin):
    list_display = ('first_name', 'last_name', 'email', 'phone_number', 'date_joined', 'deleted')
    search_fields = ('first_name', 'last_name', 'email', 'phone_number')
    ordering = ('-date_joined',)
    list_filter = ('deleted',)
    list_editable = ('deleted',)


@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
    list_display = ('id', 'order_date', 'customer')
    # Поиск по полям связанной модели Customer через __
    search_fields = ('customer__first_name', 'customer__last_name', 'customer__email')
    ordering = ('-order_date',)

Пример 4: Доступ через related_name

После создания связей с related_name можно обращаться «в обратную сторону».

# После создания объектов в shell:
# python manage.py shell

from myapp.models import Category, Product, Customer, Order

electronics = Category.objects.get(name='Electronics')

# Все товары из категории
products = electronics.products.all()

# Детали продукта через OneToOne
product = Product.objects.first()
details = product.details  # ProductDetail

# Все заказы покупателя
customer = Customer.objects.first()
orders = customer.orders.all()

# Последний заказ (благодаря get_latest_by)
latest = Order.objects.latest()