Один ко Многим

Эта статья — продолжение статьи о моделях данных.

У нас есть блог с постами:

class Post(models.Model):
    text = models.TextField()
    title = models.CharField(max_length=200)
    ...

Теперь вы хотите пригласить других авторов и зовёте их зарегистрироваться на сайте. У каждого поста теперь свой автор. Как сделать так, чтобы пост помнил, кто его написал? Самое простое решение — хранить логин автора в таблице поста:

class Post(models.Model):
    author_login = models.CharField(max_length=200)
    ...

Но вообще, хорошо бы хранить где-то почту автора, его день рождения и аватарку. Кажется, понадобится новая таблица и модель данных к ней:

class User(models.Model):
    login = models.CharField(max_length=200)
    email = models.EmailField()
    birthday = models.DateField()
    avatar = models.ImageField()
    ...

Теперь, зная логин автора поста, можно найти всю информацию о нём:

author_login = post.author_login
author = User.objects.filter(login=author_login)[0]

А как сделать нормально?

Всё работает куда проще, когда вместо логина мы храним ссылку на пользователя:

class Post(models.Model):
    author = models.ForeignKey(User, on_delete=models.SET_NULL)
    ...

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

author = post.author
print("Логин автора:", author.login)
print("Почта автора:", author.email)
print("День рождения автора:", author.birthday)

ForeignKey представляет собой связь "один ко многим". Один автор может написать много статей, но у статьи может быть только один автор:

скриншот

Как этим пользоваться?

Например, мы хотим найти все статьи автора с логином voron434:

author = User.objects.filter(login="voron434")[0]
posts_by_this_author = Post.objects.filter(author=author)

По-хорошему, этот код следует упаковывать в один-единственный вызов filter, но как это делать мы рассмотрим в статье [здесь будет ссылка].

Что читать

В этой статье мы используем filter(...)[0]. Вместо этого лучше использовать get(), как делается в статье про CRUD.

Также мы не уделяли внимание странному on_delete

А ещё у нас есть отдельная статья о фильтрации по ForeignKey с кучей интересных нюансов [здесь будет ссылка].