Модели и базы данных

В этой главе вы научитесь использовать Django ORM для определения моделей данных, создания таблиц базы данных и вставки тестовых данных.


Зачем нужны модели?

В предыдущей главе мы жёстко закодировали текст страницы. Настоящему блогу необходимо хранить несколько статей, а содержимое может меняться в любое время.

МодельЭто слой базы данных Django: используйте классы Python для определения структур данных, и Django автоматически создаёт соответствующие таблицы базы данных.

Вам не нужно писать ни одной строки SQL — в этомORM (Object-Relational Mapping)основная ценность.


Определите модель Post

Openblog/models.py, определите модель статьи.

Пример

# Путь к файлу: blog/models.py
from django.db import models
from django.utils import timezone

class Category(models.Model):
    """Категория статьи"""
    name = models.CharField('название категории', max_length=50, unique=True)
    # slug используется для английских идентификаторов в URL (например, /category/django/)
    slug = models.SlugField('URL-идентификатор', max_length=50, unique=True)

    class Meta:
        verbose_name = 'Категории'
        verbose_name_plural = 'Категории'

    def __str__(self):
        return self.name


class Post(models.Model):
    """Запись блога"""
    # Заголовок: CharField используется для короткого текста; max_length обязателен.
    title = models.CharField('Заголовок', max_length=200)

    # slug: английский идентификатор статьи в URL
    slug = models.SlugField('URL-идентификатор', max_length=200, unique=True)

    # Краткое описание: TextField используется для длинного текста, blank=True означает, что поле необязательно
    summary = models.TextField('Краткое описание', blank=True)

    # Содержимое: TextField без ограничения длины
    content = models.TextField('Содержимое')

    # Категория: ForeignKey — связь один-ко-многим, статья принадлежит одной категории
    # on_delete=models.CASCADE означает, что при удалении категории все статьи в этой категории также удаляются
    category = models.ForeignKey(
        Категория,
        on_delete=models.CASCADE,
        verbose_name='Категории',
        related_name='posts'    # Обратный запрос: category.posts.all()
    )

    # auto_now_add: автоматически устанавливает текущее время при создании
    created_at = models.DateTimeField('Время создания', auto_now_add=True)

    # auto_now: автоматически обновляется до текущего времени при каждом сохранении
    updated_at = models.DateTimeField('Время обновления', auto_now=True)

    class Meta:
        verbose_name = 'Статья'
        verbose_name_plural = 'Статьи'
        # По умолчанию сортировка по времени создания в порядке убывания
        ordering = ['-created_at']

    def __str__(self):
        # Отображать заголовок при показе объектов в админ-панели и оболочке Shell
        return self.title

Краткая справка по распространённым типам полей

Тип поляСоответствующие типы баз данныхСценарий использования
CharField(max_length=N)VARCHAR(N)Короткий текст для заголовков, тегов, имён и т. д.
TextField()TEXTТело статьи, введение, комментарии и другой контент
IntegerField()INTEGERЦелые числа для количества, возраста и т. д.
DateTimeField()DATETIMEВремя публикации, время обновления
ForeignKey()Ограничение внешнего ключаСвязь один-ко-многим (article → category)
ManyToManyField()связующая таблицаСвязь многие-ко-многим (article ←→ tag)
SlugField()VARCHARИдентификатор, удобный для URL
ImageField()VARCHAR(path)Обложка, аватар (требуется библиотека Pillow)

__str__Метод определяет, как объект отображается в виде строки. Он вызывается в раскрывающихся списках выбора административной панели и выводе Shell для отображения имени объекта. Если вы не напишете этот метод, вы увидитеPost object (1)такой бессмысленный текст.


Миграция: синхронизация моделей с базой данных

После написания классов моделей нужно использоватьмиграцию, чтобы создать таблицы базы данных.

(venv) $ python manage.py makemigrations   # 生成迁移文件(检测模型变化)
Migrations for 'blog':
  blog/migrations/0001_initial.py
    - Create model Category
    - Create model Post

(venv) $ python manage.py migrate           # 执行迁移(创建数据库表)
Operations to perform:
  Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
  Applying blog.0001_initial... OK

Разделение труда между двумя командами:

  • makemigrationsСканирует models.py и генерирует скрипты миграций (файлы Python).
  • migrate: Запускает скрипты миграций для создания фактических таблиц в базе данных.

После каждого изменения models.py необходимо запуститьmakemigrations + migrateЭти две команды одинаково необходимы: запуск только makemigrations без migrate означает, что таблицы не будут созданы; запуск migrate напрямую без makemigrations означает, что Django не узнает, что вы изменили модели.


Используйте Django Shell, чтобы вставить тестовые данные.

Прежде чем начать писать страницы, сначала с помощью Django Shell добавьте несколько записей в базу данных, чтобы убедиться, что модель работает правильно.

(venv) $ python manage.py shell

Откроется интерактивная оболочка Python Shell, в которой можно работать с базой данных.

Пример

# Вводите построчно в Django Shell
from blog.models import Category, Post

# 1. Создайте категорию
cat_django = Category.objects.create(name='Django', slug='django')
cat_python = Category.objects.create(name='Python', slug='python')
cat_css = Category.objects.create(name='CSS', slug='css')

# 2. Создание статьи
Post.objects.create(
    title='Полное руководство по Django для начинающих',
    slug='django-beginner-guide',
    summary='Изучите Django с нуля, охватывая основные концепции, такие как модели, представления, шаблоны и т. д.',
    content='<h2>Почему стоит изучать Django?</h2><p>Django — один из самых популярных веб-фреймворков Python...</p>',
    category=cat_django
)

Post.objects.create(
    title='Подробное объяснение асинхронного программирования на Python',
    slug='python-async',
    summary=Разберитесь с asyncio, корутинами и циклами событий в одной статье.,
    content=<h2>Что такое корутина?</h2><p>Корутина — это более лёгкое решение для конкурентности, чем потоки...</p>,
    категория=cat_python
)

Post.objects.create(
    заголовок=Практическое руководство по CSS Grid Layout,
    slug='css-grid-layout',
    краткое описание=Легко реализуйте сложные адаптивные макеты с помощью CSS Grid.,
    содержание=<h2>Начало работы с Grid</h2><p>Grid — это двумерная система компоновки...</p>,
    категория=cat_css
)

# 3. Проверка запросов
print(Post.objects.all())           # Вывод всех статей
print(Post.objects.count())         # Всего статей
print(Post.objects.filter(category=cat_django))  # Статьи в категории Django

Базовые запросы Django ORM

ORM позволяет работать с базой данных, используя цепочки методов Python, без написания SQL.

МетодыЭквивалентный SQLВозвращаемое значение
Post.objects.all()SELECT * FROM blog_postQuerySet (все статьи)
Post.objects.get(id=1)SELECT ... WHERE id=1Один объект (выбрасывает исключение, если не найден)
Post.objects.filter(category__slug='django')WHERE category.slug='django'QuerySet
Post.objects.order_by('-created_at')ORDER BY created_at DESCQuerySet
Post.objects.exclude(status='draft')WHERE status != 'draft'QuerySet

QuerySet — этоЛенивое вычислениеЭто означает: запрос не выполняется сразу; SQL отправляется только тогда, когда данные действительно нужны (например, при итерации, печати или преобразовании в список). Поэтому можно объединять несколько вызовов filter, не создавая несколько запросов.


Практика: подготовка данных для последующих глав

В Shell введите ещё несколько статей и категорий, относящихся к трём категориям: Django, Python и CSS, не менее 2 статей на каждую категорию.

Вы могли бы написать команду управления или скрипт для пакетной вставки, но на этом этапе достаточно вставить их по одной с помощью Shell.


Итоги главы

В этой главе вы освоили основной рабочий процесс Django ORM: определение Python-классов (моделей) в models.py, создание файлов миграций с помощью makemigrations, синхронизацию с базой данных с помощью migrate и использование Shell и ORM для запроса и вставки данных.

На данный момент структура статей и категорий уже создана в базе данных.

Другие расширения