Создание моделей данных блога
Модель Post
Сердце блога — пост. Опишем его в blog/models.py:
from django.conf import settings
from django.db import models
from django.utils import timezone
class Post(models.Model):
class Status(models.TextChoices):
DRAFT = "DF", "Черновик"
PUBLISHED = "PB", "Опубликован"
title = models.CharField("Заголовок", max_length=250)
slug = models.SlugField("Слаг", max_length=250)
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="blog_posts",
verbose_name="Автор",
)
body = models.TextField("Текст")
publish = models.DateTimeField("Дата публикации", default=timezone.now)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
status = models.CharField(
"Статус", max_length=2, choices=Status.choices, default=Status.DRAFT
)
class Meta:
ordering = ["-publish"]
indexes = [
models.Index(fields=["-publish"]),
]
verbose_name = "пост"
verbose_name_plural = "посты"
def __str__(self):
return self.title
Разберём каждое решение.
Заголовок и слаг
title—CharField, в базеVARCHAR(250).slug— короткая метка для URL из латиницы, цифр, дефисов:kak-ustroen-django. Делает адреса читаемыми и полезными для SEO:/blog/2026/10/25/kak-ustroen-django/вместо/blog/17/.
Автор
Связь «многие к одному» с пользователем. Ссылаемся через settings.AUTH_USER_MODEL (по умолчанию "auth.User"), а не импортируем User напрямую: если проект перейдёт на собственную модель пользователя, код блога менять не придётся.
related_name="blog_posts" позволит писать user.blog_posts.all().
Даты
| Поле | Значение |
|---|---|
publish |
Когда пост опубликован. default=timezone.now — по умолчанию «сейчас», но можно изменить |
created |
Когда создан. auto_now_add — заполняется один раз |
updated |
Когда изменён. auto_now — обновляется при каждом save() |
Используйте django.utils.timezone.now, а не datetime.now: при USE_TZ = True Django ожидает даты с часовым поясом.
Статус
TextChoices — перечисление вариантов. В базе хранится "DF" или "PB", людям показывается «Черновик» / «Опубликован».
post.status == Post.Status.PUBLISHED # сравнение
post.get_status_display() # "Опубликован"
Post.Status.choices # [("DF", "Черновик"), ("PB", "Опубликован")]
Post.Status.labels # ["Черновик", "Опубликован"]
Meta: сортировка и индекс
ordering = ["-publish"]— по умолчанию свежие посты первыми.- Индекс по
-publishускоряет именно такую сортировку и фильтрацию по дате. Большинство запросов блога будут использовать это поле.
Миграции
python manage.py makemigrations blog
Migrations for 'blog':
blog/migrations/0001_initial.py
+ Create model Post
+ Create index blog_post_publish_bb7600_idx on field(s) -publish of model post
Посмотрим SQL:
python manage.py sqlmigrate blog 0001
CREATE TABLE "blog_post" (
"id" bigint NOT NULL PRIMARY KEY AUTOINCREMENT,
"title" varchar(250) NOT NULL,
"slug" varchar(250) NOT NULL,
"body" text NOT NULL,
"publish" datetime NOT NULL,
...
"author_id" integer NOT NULL REFERENCES "auth_user" ("id")
);
CREATE INDEX "blog_post_slug_..." ON "blog_post" ("slug");
CREATE INDEX "blog_post_publish_..." ON "blog_post" ("publish" DESC);
Обратите внимание: SlugField по умолчанию создаёт индекс, а ForeignKey — столбец author_id с индексом.
python manage.py migrate
Пробуем в shell
>>> from django.contrib.auth.models import User
>>> from blog.models import Post
>>> user = User.objects.create_superuser("admin", password="admin") # или через createsuperuser
>>> post = Post.objects.create(title="Первый пост", slug="pervyj-post", body="Привет!", author=user)
>>> post.status
'DF'
>>> post.get_status_display()
'Черновик'
Итог
Post: заголовок, слаг, автор (черезAUTH_USER_MODEL), текст, три даты, статус.TextChoicesописывает варианты статуса.Meta.orderingиMeta.indexes— сортировка и индекс по дате публикации.makemigrations→sqlmigrate(посмотреть) →migrate.
Практика решено 0 из 6
Добавьте в модель Post поле author — связь с моделью пользователя через settings.AUTH_USER_MODEL,
удаление каскадом, обратная связь — blog_posts.
from django.conf import settings
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=250)
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="blog_posts",
)
Почему для даты публикации используют default=timezone.now, а не auto_now_add=True?
Опишите вложенный класс Status (models.TextChoices) с вариантами
DRAFT = "DF", "Черновик" и PUBLISHED = "PB", "Опубликован",
и поле status (CharField, длина 2, варианты из Status, по умолчанию черновик).
class Post(models.Model):
class Status(models.TextChoices):
DRAFT = "DF", "Черновик"
PUBLISHED = "PB", "Опубликован"
status = models.CharField(max_length=2, choices=Status.choices, default=Status.DRAFT)
Добавьте в Meta модели Post сортировку по убыванию publish и индекс по этому же полю (по убыванию).
class Meta:
ordering = ["-publish"]
indexes = [
models.Index(fields=["-publish"]),
]
Зачем нужен индекс по полю publish?
Как называется метод, который для поля с choices возвращает человекочитаемую подпись? Для поля status — post.???()
Войдите, чтобы отмечать пройденные уроки.