Сохранение данных с помощью моделей Django

В предыдущей статье "Понять Django" мы познакомились с формами и тем, как формы позволяют вашему приложению получать данные от пользователей, использующих ваш сайт. В этой статье вы увидите, как взять эти данные и сохранить их в базе данных, чтобы ваше приложение могло использовать эти данные или отобразить их позже.

Установка

Давайте разберемся, где хранятся ваши данные, прежде чем углубиться в то, как с ними работать. Django использует базы данных для хранения данных. Более конкретно, Django использует реляционные базы данных. Рассмотрение реляционных баз данных было бы гигантской темой, поэтому вам придется довольствоваться очень сокращенной версией.

Реляционная база данных похожа на набор электронных таблиц. Каждая электронная таблица называется таблицей. Таблица имеет набор столбцов для отслеживания различных частей данных. Каждая строка в таблице представляет собой связанную группу. Например, представьте, что у нас есть таблица сотрудников компании. Столбцы таблицы сотрудников могут включать имя, фамилию и должность. Каждая строка будет представлять отдельного сотрудника.

First name | Last name | Job title
-----------|-----------|----------
John       | Smith     | Software Engineer
-----------|-----------|----------
Peggy      | Jones     | Software Engineer

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

Почему бы не поместить номер телефона в ту же таблицу сотрудников? Ну, а что было бы, если бы компании требовался номер сотового телефона и номер домашнего телефона? Имея отдельные таблицы, мы могли бы поддерживать отслеживание нескольких типов телефонных номеров. Возможность разделить эти различные типы данных дает огромные возможности. Мы увидим силу реляционных баз данных, когда будем изучать, как Django раскрывает эту силу.

Django использует реляционную базу данных, поэтому фреймворк должен обладать некоторыми возможностями для настройки этой базы данных. Конфигурация базы данных находится в настройке DATABASES в вашем settings.py файле. Запустив startproject, вы обнаружите:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

Как мы видели в системе шаблонов, Django поддерживает несколько баз данных. В отличие от системы шаблонов, в настройках базы данных каждый поддерживаемый бэкенд называется "движком", а не "бэкендом". Движок базы данных по умолчанию startproject установлен на использование SQLite. SQLite является отличным стартовым выбором, потому что он вмещает всю реляционную базу данных в один файл, который в настройках называется db.sqlite3. Такой выбор движка действительно снижает барьер для начала работы с Django, поскольку новым разработчикам Django не нужно скачивать дополнительные инструменты, чтобы попробовать Django.

SQLite - это удивительная маленькая база данных и, вероятно, самая широко используемая база данных в мире. Эта база данных существует в каждом смартфоне, о котором вы только можете подумать. Несмотря на то, что SQLite удивительна, она не подходит для многих сценариев, в которых вы захотите использовать Django. Для начала, база данных позволяет записывать в нее данные только одному пользователю одновременно. Это огромная проблема, если вы планируете сделать сайт, который будет обслуживать множество людей одновременно.

Поскольку SQLite не подходит для веб-приложений, вам, скорее всего, придется перейти на другую реляционную базу данных. Я бы рекомендовал PostgreSQL. Postgres (как его часто "сокращают") - это очень популярная база данных с открытым исходным кодом, которая очень хорошо поддерживается. В сочетании с psycopg2 в качестве движка Django, вы обнаружите, что многие места, где можно разместить ваше приложение Django, будут хорошо работать с Postgres.

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

Моделирование ваших данных

Теперь, когда вы имеете представление о том, где Django будет хранить ваши данные, давайте сосредоточимся на как Django будет хранить данные.

Django представляет данные для базы данных в классах Python, называемых моделями. Модели Django похожи на классы форм, которые мы рассматривали в прошлой статье. Модель Django объявляет данные, которые вы хотите хранить в базе данных, как атрибуты уровня класса, точно так же, как и класс формы. На самом деле, типы полей чрезвычайно похожи на их аналоги в форме, и не зря! Мы часто хотим сохранять данные формы и хранить их, поэтому имеет смысл, чтобы модели были похожи на формы по структуре. Давайте рассмотрим пример.

# application/models.py
from django.db import models

class Employee(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    job_title = models.CharField(max_length=200)

Этот класс модели описывает части, которые мы хотим включить в таблицу базы данных. Каждый класс модели представляет одну таблицу базы данных. Если бы нам нужны были номера телефонов, о которых я упоминал ранее, мы бы создали отдельный класс PhoneNumber. Обычно при именовании класса мы используем единственное число вместо множественного. Мы делаем это потому, что каждая строка в таблице представлена как экземпляр объекта.

>>> from application.models import Employee
>>> employee = Employee(
...     first_name='Tom',
...     last_name='Bombadil',
...     job_title='Old Forest keeper')
>>> employee.first_name
'Tom'

В этом примере, похоже, создается новый сотрудник, но в нем отсутствует ключевой элемент. Мы не сохранили экземпляр employee в базе данных. Мы можем сделать это с помощью employee.save(), но, если вы следите за развитием событий и попытаетесь вызвать это прямо сейчас, произойдет сбой с ошибкой, говорящей о том, что таблица сотрудников не существует.

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

Подготовка базы данных с помощью миграций

Теперь мы знаем, что модели Django - это классы Python, которые отображаются на таблицы базы данных. Таблицы базы данных не появляются волшебным образом. Нам нужна возможность настроить таблицы так, чтобы они соответствовали структуре, определенной в классе Python. Инструмент, который Django предоставляет для синхронизации моделей Django и базы данных, называется системой миграций.

Миграции - это файлы Python, которые описывают последовательность операций с базой данных, необходимых для того, чтобы база данных соответствовала любым определениям модели, которые есть в вашем проекте.

Поскольку Django работает со многими базами данных, эти операции с базами данных определены в файлах Python, чтобы операции могли быть абстрактными. Используя абстрактные операции, система миграции Django может подключать конкретные команды базы данных для любой используемой вами базы данных. Если вы начинаете с SQLite, а затем переходите на PostgreSQL, когда вы будете готовы разместить свое приложение в интернете, то система миграции сделает все возможное, чтобы сгладить различия, чтобы минимизировать количество работы, которая вам потребуется при переходе.

На начальном этапе вы можете довольно далеко продвинуться без понимания внутренних принципов работы файлов миграции. На базовом уровне вам нужно выучить пару команд Django: makemigrations и migrate.

makemigrations

Команда makemigrations создаст любые файлы миграции, если есть какие-либо ожидающие изменения модели. Чтобы создать наш файл миграции для модели Employee, мы можем выполнить:

(venv) $ ./manage.py makemigrations
Migrations for 'application':
  application/migrations/0001_initial.py
    - Create model Employee

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

  • Добавление новых моделей или новых полей
  • Модификация существующих полей
  • Удаление существующих полей
  • Изменение некоторых метаданных модели и несколько других крайних случаев

Если вы не сделаете миграцию, вы, скорее всего, столкнетесь с ошибками при получении данных из базы данных. Это происходит потому, что Django строит запросы только на основе того, что определено в коде Python. Система предполагает, что база данных находится в надлежащем состоянии. Django попытается сделать запрос к таблицам базы данных, даже если эти таблицы еще не существуют!

migrate

Другая команда, migrate, берет файлы миграции и применяет их к базе данных. Например:

(venv) $ ./manage.py migrate
Operations to perform:
  Apply all migrations: admin, application, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying application.0001_initial... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying sessions.0001_initial... OK

Вот вывод, который происходит при применении нашей новой миграции. Что все это значит?

Система миграции также используется встроенными приложениями Django. В моем примере проекта я использовал startproject, который включает набор включенных приложений в списке INSTALLED_APPS. Мы можем наблюдать, что наш образец application применил свою миграцию, и миграции других Django-приложений, которые мы включили, также применяются.

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

Работа с моделями

После выполнения миграций ваша база данных будет подготовлена для правильного взаимодействия с Django.

Для создания новых строк в наших новых таблицах базы данных мы можем использовать метод модели save. Когда вы сохраняете экземпляр модели, Django отправляет сообщение в базу данных, которое фактически говорит "добавьте эти новые данные в эту таблицу базы данных". Эти "сообщения" базы данных на самом деле называются запросы.

Как я уже упоминал в разделе настроек, Django взаимодействует с базой данных через движок базы данных. Движок базы данных использует язык структурированных запросов (SQL) для связи с реальной базой данных. SQL - это общий стандарт, на котором "говорят" все поддерживаемые Django базы данных. Поскольку Django использует SQL, именно поэтому сообщения называются "запрос".

Как выглядит SQL-запрос? Если мы сохраним наш пример модели, приведенный ранее, он будет выглядеть примерно так:

INSERT INTO "application_employee" ("first_name", "last_name", "job_title")
    VALUES ('Tom', 'Bombadil', 'Old Forest keeper')

Обратите внимание, что этот пример является запросом INSERT при использовании движка SQLite. Как и естественные языки, SQL имеет множество диалектов в зависимости от выбранной вами базы данных. Базы данных делают все возможное, чтобы придерживаться некоторых стандартов, но у каждой базы данных есть свои причуды, и задача движка базы данных - сгладить эти различия, где это возможно.

Это еще одна область, где Django выполняет тонну тяжелой работы от вашего имени. Движок базы данных переводит ваш вызов save в правильный SQL-запрос. SQL - это глубокая тема, которую мы не сможем полностью осветить в этой статье. К счастью, нам и не придется этого делать благодаря ORM в Django!

ORM означает Object Relational Mapper. Работа ORM заключается в отображении (или переводе) из Python объектов в базу данных реляционную. Это означает, что мы проводим время, работая в коде Python, и позволяем Django выяснить, как получить и поместить данные в базу данных.

Использование save на записи модели - это такой маленький пример использования Django ORM. Что еще мы можем сделать? Мы можем делать такие вещи, как:

  • Получить все строки из базы данных.
  • Получение отфильтрованного набора строк на основе некоторых критериев фильтрации.
  • Обновить набор строк одновременно.
  • Удалять строки из базы данных.

Большая часть этих операций в Django ORM работает через класс Manager. Если наш предыдущий пример показывал, как манипулировать одним рядом, то менеджер моделей имеет методы, предназначенные для взаимодействия с несколькими рядами.

Мы можем проанализировать нашу вымышленную таблицу сотрудников. Менеджер для модели прикрепляется к классу модели как атрибут с именем objects. Давайте посмотрим код:

>>> from application.models import Employee
>>> bobs = Employee.objects.filter(first_name='Bob')
>>> for bob in bobs:
...     print(f"{bob.first_name} {bob.last_name}")
...
Bob Ross
Bob Barker
Bob Marley
Bob Dylan
>>> print(bobs.query)
SELECT "application_employee"."id",
    "application_employee"."first_name",
    "application_employee"."last_name",
    "application_employee"."job_title"
    FROM "application_employee"
    WHERE "application_employee"."first_name" = Bob

В этом примере мы используем менеджер для фильтрации по подмножеству сотрудников в таблице. Переменная bobs, возвращаемая методом filter, представляет собой QuerySet. Как можно догадаться, она представляет собой набор строк, которые возвращает SQL-запрос. Когда у вас есть набор запросов, вы можете распечатать запрос, чтобы увидеть точный SQL-запрос, который Django выполнит от вашего имени.

Что делать, если вы хотите удалить запись о сотруднике?

>>> from application.models import Employee
>>> # The price is wrong, Bob!
>>> Employee.objects.filter(first_name='Bob', last_name='Barker').delete()
(1, {'application.Employee': 1})

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

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

from application.models import Employee

employees = Employee.objects.all()  # employees is a QuerySet of all rows!

if should_find_the_bobs:
    employees = employees.filter(first_name='Bob')  # New queryset!

Вот некоторые другие QuerySet методы, которые я использую постоянно:

  • create - В качестве альтернативы созданию экземпляра записи и вызову save, менеджер может создать запись напрямую.
Employee.objects.create(first_name='Bobby', last_name='Tables')
  • get - Используйте этот метод, когда вам нужна одна и точно одна запись. Если ваш запрос не соответствует или вернет несколько записей, вы получите исключение.
the_bob = Employee.objects.get(first_name='Bob', last_name='Marley')

Employee.objects.get(first_name='Bob')
# Raises application.models.Employee.MultipleObjectsReturned

Employee.objects.get(first_name='Bob', last_name='Sagat')
# Raises application.models.Employee.DoesNotExist
  • exclude - Этот метод позволяет исключить строки, которые могут быть частью существующего набора запросов.
the_other_bobs = (
    Employee.objects.filter(first_name='Bob')
    .exclude(last_name='Ross')
)
  • update - С помощью этого метода можно обновить группу строк за одну операцию.
Employee.objects.filter(first_name='Bob').update('Robert')
  • exists - Используйте этот метод, если вы хотите проверить, существуют ли в базе данных строки, соответствующие условию, которое вы хотите проверить.
has_bobs = Employee.objects.filter(first_name='Bob').exists()
  • count - проверка количества строк, удовлетворяющих условию. Из-за того, как работает SQL, обратите внимание, что это более эффективно, чем попытка использовать len на наборе запросов.
how_many_bobs = Employee.objects.filter(first_name='Bob').count()
  • none - Возвращает пустой кверисет для модели. Как это может быть полезно? Я использую это, когда мне нужно защитить доступ к определенным данным.
employees = Employee.objects.all()

if not is_hr:
    employees = Employee.objects.none()
  • first / last - Эти методы возвращают отдельный экземпляр модели, если он совпадает. Методы используют упорядочивание моделей для получения желаемого результата. Мы используем order_by, чтобы указать, как мы хотим упорядочить результаты.
>>> a_bob = Employee.objects.filter(first_name='Bob').order_by(
...     'last_name').last()
>>> print(a_bob.last_name)
Ross

Зная, как можно взаимодействовать с моделями, мы можем более подробно остановиться на том, какие данные можно хранить в моделях (и, следовательно, в вашей базе данных).

Типы модельных данных

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

В статье о формах мы видели, что система форм Django включает в себя большое разнообразие полей формы. Если вы посмотрите на справочник Form fields и сравните список типов со списком типов в справочнике Model field reference, вы можете заметить много совпадений.

Как и их аналоги формы, модели имеют CharField, BooleanField, DateField, DateTimeField и многие другие подобные типы. Типы полей имеют много общих атрибутов. Чаще всего, я думаю, вы будете использовать или встречать следующие атрибуты.

  • default - Если вы хотите иметь возможность создать запись модели без указания определенных значений, то вы можете использовать default. Значение может быть либо буквальным значением, либо вызываемой функцией, которая производит значение.
# application/models.py
import random

from django.db import models

def strength_generator():
    return random.randint(1, 20)

class DungeonsAndDragonsCharacter(models.Model):
    name = models.CharField(max_length=100, default='Conan')
    # Important to note: Pass the function, do not *call* the function!
    strength = models.IntegerField(default=strength_generator)
  • unique - Когда значение поля должно быть уникальным для всех строк в таблице базы данных, используйте unique. Это хороший атрибут для идентификаторов, где вы не ожидаете дубликатов.
class ImprobableHero(models.Model):
    name = models.CharField(max_length=100, unique=True)

# There can be only one.
ImprobableHero.objects.create(name='Connor MacLeod')
  • null - Реляционная база данных имеет возможность хранить отсутствие данных. В базе данных это значение воспринимается как NULL. Иногда это является важным отличием от пустого значения. Например, в модели Person целочисленное поле типа number_of_children будет означать совершенно разные вещи для значения 0 и значения NULL. Первое означает, что у человека нет детей, а второе - что количество детей неизвестно. Наличие нулевых условий требует дополнительной проверки в вашем коде, поэтому Django по умолчанию заставляет null быть False. Это означает, что поле не допускает NULL. Нулевые значения могут быть полезны при необходимости, но я думаю, что лучше избегать их, если это возможно, и стараться хранить фактические данные о поле.
class Person(models.Model):
    # This field would always have a value since it can't be null.
    # Zero counts as a value and is not NULL.
    age = models.IntegerField()
    # This field could be unknown and contain NULL.
    # In Python, a NULL db value will appear as None.
    weight = models.IntegerField(null=True)
  • blank - Атрибут blank часто используется в сочетании с атрибутом null. В то время как атрибут null позволяет базе данных хранить NULL для поля, blank позволяет валидации формы разрешить пустое поле. Это используется в формах, которые автоматически генерируются Django, как в Django administrator site, о котором мы поговорим в следующей статье.
class Pet(models.Model):
    # Not all pets have tails so we want auto-generated forms
    # to allow no value.
    length_of_tail = models.IntegerField(null=True, blank=True)
  • choices - Мы рассматривали choices в статье о формах как технику, помогающую пользователям выбрать нужное значение из ограниченного набора. choices может быть задано в модели. Django может выполнять валидацию на модели, которая будет гарантировать, что только определенные значения будут сохранены в поле базы данных.
class Car(models.Model):
    COLOR_CHOICES = [
        (1, 'Black'),
        (2, 'Red'),
        (3, 'Blue'),
        (4, 'Green'),
        (5, 'White'),
    ]
    color = models.IntegerField(choices=COLOR_CHOICES, default=1)
  • help_text - По мере того, как приложения становятся больше или если вы работаете в большой команде с большим количеством людей, создающих модели Django, потребность в документации возрастает. Django допускает текст справки, который может быть отображен вместе со значением поля на сайте администратора Django. Этот справочный текст полезен для того, чтобы напомнить о себе в будущем или просветить коллегу.
class Policy(models.Model):
    is_section_987_123_compliant = models.BooleanField(
        default=False,
        help_text=(
            'For policies that only apply on leap days'
            ' in accordance with Section 987.123'
            ' of the Silly Draconian Order'
        )
    )

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

Что делает базу данных "реляционной?"

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

Слишком упрощенная модель для сотрудника с несколькими телефонными номерами может выглядеть следующим образом:

# application/models.py
from django.db import models

class Employee(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    job_title = models.CharField(max_length=200)
    phone_number_1 = models.CharField(max_length=32)
    phone_number_2 = models.CharField(max_length=32)

В одной таблице может храниться несколько чисел, но это решение имеет некоторые недостатки.

  • Что делать, если у сотрудника более двух телефонных номеров? У человека может быть несколько сотовых телефонов, стационарная линия по месту жительства, номер пейджера, номер факса и т.д.
  • .
  • Как узнать, какой тип телефонного номера указан в phone_number_1 и phone_number_2? Если вы вытащите запись о сотруднике, чтобы попытаться позвонить ему, а вместо этого наберете номер факса, вам будет трудно с ним разговаривать.

Вместо этого, что если бы у нас было две отдельные модели?

# application/models.py
from django.db import models

class Employee(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    job_title = models.CharField(max_length=200)

class PhoneNumber(models.Model):
    number = models.CharField(max_length=32)
    PHONE_TYPES = (
        (1, 'Mobile'),
        (2, 'Home'),
        (3, 'Pager'),
        (4, 'Fax'),
    )
    phone_type = models.IntegerField(choices=PHONE_TYPES, default=1)

У нас есть две отдельные таблицы. Как мы можем связать эти таблицы, чтобы у сотрудника было один, два или двести телефонных номеров? Для этого мы можем использовать реляционный тип поля ForeignKey. Вот немного обновленная версия PhoneNumber.

...

class PhoneNumber(models.Model):
    number = models.CharField(max_length=32)
    PHONE_TYPES = (
        (1, 'Mobile'),
        (2, 'Home'),
        (3, 'Pager'),
        (4, 'Fax'),
    )
    phone_type = models.IntegerField(choices=PHONE_TYPES, default=1)
    employee = models.ForeignKey(Employee, on_delete=models.CASCADE)

В этом обновлении говорится, что каждый номер телефона теперь должен быть связан с записью о сотруднике. on_delete - это обязательный атрибут, который определяет, что произойдет при удалении записи сотрудника. В данном случае CASCADE означает, что удаление строки о сотруднике приведет к каскадному удалению всех связанных с ним телефонных номеров.

Ключ к тому, как это работает, лежит в понимании keys. Когда вы создаете модель Django, фреймворк добавляет к вашей модели дополнительное поле. Это поле называется AutoField.

# This is what Django adds to your model.
id = models.AutoField(primary_key=True)

Добавление столбца AutoField в таблицу базы данных, который присваивает каждой строке в таблице уникальное целое число. Каждая новая строка увеличивается по сравнению с предыдущей, а нумерация начинается с единицы. Это число является идентификатором строки и называется первичным ключом.

Если Том Бомбадил - первый сотрудник в таблице, то значение строки id будет 1.

Зная о первичных ключах, мы можем понять поля внешнего ключа. В случае с полем внешнего ключа PhoneNumber.employee любая строка номера телефона будет хранить значение первичного ключа некоторой строки сотрудника. Это обычно называется отношением "один ко многим".

А ForeignKey - это отношение "один ко многим", поскольку несколько строк из таблицы (в данном случае PhoneNumber) могут ссылаться на одну строку в другой таблице, а именно Employee. Другими словами, у сотрудника может быть несколько телефонных номеров. Если бы мы хотели получить номера телефонов Тома, то одним из возможных способов было бы:

tom = Employee.objects.get(first_name='Tom', last_name='Bombadil')
phone_numbers = PhoneNumber.objects.filter(employee=tom)

Запрос для phone_numbers будет таким:

SELECT
    "application_phonenumber"."id",
    "application_phonenumber"."number",
    "application_phonenumber"."phone_type",
    "application_phonenumber"."employee_id"
    FROM "application_phonenumber"
    WHERE "application_phonenumber"."employee_id" = 1

В базе данных Django будет хранить столбец таблицы для внешнего ключа как employee_id. Запрос запрашивает все строки телефонных номеров, которые совпадают, когда ID сотрудника равен 1. Поскольку первичные ключи должны быть уникальными, значение 1 может соответствовать только Тому Бомбадилу, поэтому результирующие строки будут телефонными номерами, связанными с этим сотрудником.

Существует еще один тип реляционного поля, которому мы должны уделить время. Это поле ManyToManyField. Как вы уже догадались, это поле используется, когда два типа данных связаны друг с другом по принципу "многие ко многим".

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

Что если мы попытаемся смоделировать это с помощью полей ForeignKey?

# application/models.py
from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=128)

class House(models.Model):
    address = models.CharField(max_length=256)
    resident = models.ForeignKey(Person, on_delete=models.CASCADE)

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

.

# application/models.py
from django.db import models

class House(models.Model):
    address = model.CharField(max_length=256)

class Person(models.Model):
    name = models.CharField(max_length=128)
    house = models.ForeignKey(House, on_delete_models.CASCADE)

В этой версии дом может иметь несколько жителей, но человек может принадлежать только одному дому.

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

С помощью ManyToManyField можно добавить поле с любой стороны. Вот новое моделирование.

# application/models.py
from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=128)

class House(models.Model):
    address = models.CharField(max_length=256)
    residents = models.ManyToManyField(Person)

Как это работает на уровне базы данных? На примере внешних ключей мы видели, что одна таблица может хранить первичный ключ строки другой таблицы в своих собственных данных. К сожалению, один столбец базы данных не может содержать несколько внешних ключей. Это означает, что приведенное выше моделирование не добавляет residents к таблице House. Вместо этого связь обрабатывается путем добавления новой таблицы базы данных. Эта новая таблица содержит отображение между людьми и домами и хранит строки, содержащие первичные ключи от каждой модели.

Давайте вспомним пример, чтобы увидеть, как это выглядит. Предположим, что есть три записи о людях с первичными ключами 1, 2 и 3. Предположим также, что есть три дома с первичными ключами 97, 98 и 99. Чтобы доказать, что связь "многие-ко-многим" работает в обоих направлениях, предположим, что эти условия верны:

  • Люди с первичными ключами 1 и 2 проживают в доме 97.
  • Человек с первичным ключом 3 владеет домами 98 и 99.

Данные в новой таблице сопоставления между Person и House будут содержать данные типа:

Person | House
-------|------
1      | 97
2      | 97
3      | 98
3      | 99

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

Мы можем получить доступ ко многим сторонам каждой модели, используя кверисет. residents будет ManyRelatedManager и, как и другие менеджеры, может предоставлять кверисеты, используя определенные методы менеджера.

Получение обратного направления немного менее очевидно. Django автоматически добавит еще один менеджер ManyRelatedManager к модели Person. Имя этого менеджера - это имя модели, соединенное с _set. В данном случае это имя house_set. Вы также можете предоставить атрибут related_name к ManyToManyField, если вам нужно другое имя, например, если вы хотите назвать его houses вместо этого.

house = House.objects.get(address='123 Main St.')
# Note the use of `all()`!
for resident in house.residents.all():
    print(resident.name)

person = Person.objects.get(name='Joe')
for house in person.house_set.all():
    print(house.address)

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

Итоги

В этой статье мы рассмотрели:

  • Как создать базу данных для вашего проекта.
  • Как Django использует специальные классы, называемые моделями, для хранения данных.
  • Выполнение команд, которые подготовят базу данных для моделей, которые вы хотите использовать.
  • Сохранение новой информации в базе данных.
  • Запрашивание базы данных о сохраненной информации.
  • Сложные типы полей для моделирования проблем реального мира.

Благодаря этой возможности хранить данные в базе данных, у вас есть все основные инструменты для создания интерактивного веб-сайта для ваших пользователей! В этой серии мы рассмотрели:

  • обработка URL
  • представления для выполнения вашего кода и бизнес-логики
  • шаблоны для отображения пользовательского интерфейса
  • формы, позволяющие пользователям вводить данные и взаимодействовать с вашим сайтом
  • модели для хранения данных в базе данных для долгосрочного хранения

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

Первым в списке идет встроенный сайт администраторов Django, который позволяет вам исследовать данные, хранящиеся в вашей базе данных. Мы рассмотрим:

  • Что такое админка сайта Django
  • Как заставить ваши модели отображаться в админке
  • Как создать дополнительные действия, которые могут выполнять ваши пользователи администратора

https://www.mattlayman.com/understand-django/store-data-with-models/

Вернуться на верх