Работа с Django ORM
Привет, сегодня мы поговорим о работе с Django ORM и разберём самые востребованные методы.
Что такое ORM?
ORM (англ. Object-Relational Mapping, рус. объектно-реляционное отображение, или преобразование) — технология программирования, которая связывает базы данных с концепциями объектно-ориентированных языков программирования, создавая «виртуальную объектную базу данных».
Таким образом, получается, что мы имеем возможность описания таблицы в базе данных с помощью класса, внутри которого мы задаём определённые атрибуты. Каждый из этих атрибутов соответствует столбцу с тем же именем, что дано атрибуту. Каждая строка, каждая запись в такой таблице отдаётся через ORM отдельным объектом.
python manage.py shell
Показывает интерактивную консоль, в которой можно взаимодействовать со всем, что происходит у вас внутри проекта, например, можно поделать там запросы через ORM, чем мы сегодня и займёмся.
Запрос objects.all()
Запрос objects.all()
возвращает нам queryset
, внутри которого лежит массив объектов с данными, к каждому из которых мы можем потом обратиться и получить данные, используя в качестве ключей имена столбцов.
>>> from cbv_app import models >>> publishers = models.Publisher.objects.all() >>> publishers <QuerySet [<Publisher: Vasya Pupkin>, <Publisher: Александр Фёдоров>]> >>> vasya = publishers[0] >>> vasya <Publisher: Vasya Pupkin> >>> "{} {}".format(vasya.first_name, vasya.last_name) 'Vasya Pupkin'
Queryset - это набор данных, который вернулся вам из БД. По факту, его можно приравнять к оператору SELECT
из SQL. У одного queryset’а может быть сколько угодно фильтров, в том числе и ноль, разумеется. Фильтры приравниваются к оператору WHERE
.
Таким образом, следующий фильтр:
>>> publishers.filter(first_name="Vasya") <QuerySet [<Publisher: Vasya Pupkin>]>
Приравнивается к такому SQL-запросу:
SELECT * FROM "cbv_app_publisher" WHERE "first_name" = 'Vasya';
Разница get и filter
Get возвращает только одно значение. Поэтому, если мы делаем get
не по уникальному ключу, у нас ничего не выйдет:
>>> publishers.get(pk=1) <Publisher: Vasya Pupkin> >>> publishers.get(first_name="Vasya") Traceback (most recent call last): File "<console>", line 1, in <module> File "/home/kantegory/.local/share/virtualenvs/01_views_examples-3A-j_8Ab/lib/python3.6/site-packages/django/db/models/query.py", line 436, in get num if not limit or num < limit else 'more than %s' % (limit - 1), cbv_app.models.Publisher.MultipleObjectsReturned: get() returned more than one Publisher -- it returned 2!
Filter же возвращает queryset
:
>>> publishers.filter(first_name="Vasya") <QuerySet [<Publisher: Vasya Pupkin>, <Publisher: Vasya Maximov>]>
Другие важные методы
Чтобы исключить какое-то значение (WHERE NOT
), можно использовать метод exclude
:
>>> publishers.exclude(first_name="Vasya") <QuerySet [<Publisher: Александр Фёдоров>]>
Annotate
позволяет добавить какое-нибудь дополнительное поле в queryset
, для вывода, например, количества значений в связной таблице (по внешнему ключу):
>>> alex_counter = publishers.filter(pk=2).annotate(Count("book")) >>> alex_counter[0].book__count 2
order_by
абсолютно аналогичен оператору ORDER BY
из SQL, позволяет отсортировать queryset в определённом порядке по любому из столбцов:
>>> publishers.order_by("birthdate") <QuerySet [<Publisher: Александр Фёдоров>, <Publisher: Vasya Pupkin>, <Publisher: Vasya Maximov>]> >>> publishers.order_by("-birthdate") <QuerySet [<Publisher: Vasya Pupkin>, <Publisher: Vasya Maximov>, <Publisher: Александр Фёдоров>]>
values
вернёт вам queryset
, объекты в котором будут возвращены в виде словарей:
>>> publishers.values() <QuerySet [{'id': 1, 'first_name': 'Vasya', 'last_name': 'Pupkin', 'birthdate': datetime.date(2020, 9, 30)}, {'id': 2, 'first_name': 'Александр', 'last_name': 'Фёдоров', 'birthdate': datetime.date(1975, 7, 7)}, {'id': 3, 'first_name': 'Vasya', 'last_name': 'Maximov', 'birthdate': datetime.date(2020, 9, 30)}]>
Создать новую запись, можно создав новый объект класса, и вызвав после метод save()
:
>>> fedor = models.Publisher(first_name="Fedor", last_name="Alekseev", birthdate="1975-05-05") >>> fedor.save() >>> publishers.all() <QuerySet [<Publisher: Vasya Pupkin>, <Publisher: Александр Фёдоров>, <Publisher: Vasya Maximov>, <Publisher: Fedor Alekseev>]>
Чтобы обновить уже существующую запись нужно просто переопределить параметры класса и сохранить:
>>> fedor = publishers.get(pk=5) >>> fedor <Publisher: Fedor Alekseev> >>> fedor.first_name = "Фёдор" >>> fedor.last_name = "Алексеев" >>> fedor.save() >>> publishers.all() <QuerySet [<Publisher: Vasya Pupkin>, <Publisher: Александр Фёдоров>, <Publisher: Vasya Maximov>, <Publisher: Фёдор Алексеев>]>
Для удаления нужно вызвать у объекта класса метод delete():
>>> fedor.delete() (1, {'cbv_app.Publisher': 1}) >>> publishers.all() <QuerySet [<Publisher: Vasya Pupkin>, <Publisher: Александр Фёдоров>, <Publisher: Vasya Maximov>]>
Построение условий в запросах (обращение к полям текущей таблицы и связных)
В запросе мы можем использовать несколько фильтров, как уже говорилось ранее:
>>> publishers.filter(first_name="Vasya", birthdate__gte="1975-01-01").values() <QuerySet [{'id': 1, 'first_name': 'Vasya', 'last_name': 'Pupkin', 'birthdate': datetime.date(2020, 9, 30)}, {'id': 3, 'first_name': 'Vasya', 'last_name': 'Maximov', 'birthdate': datetime.date(1980, 9, 9)}]>
__gte
, в данном случае это сокращение от “Greater than or equal”.
Также, мы можем сделать фильтр и по значениям из связанной таблицы:
>>> books = models.Book.objects.all() >>> books.filter(publisher__last_name="Фёдоров").values() <QuerySet [{'id': 5, 'name': 'Книга Фёдорова', 'desc': 'Очень длинная книга Фёдорова', 'publisher_id': 2}, {'id': 6, 'name': 'Вторая книга Фёдорова', 'desc': 'Книга Фёдорова покороче', 'publisher_id': 2}]>
Django manager
Manager - это интерфейс, через который для моделей Django, делаются запросы в БД. У каждой модели уже определён, как минимум, один manager. По умолчанию, он находится в свойстве objects. Его можно переопределять для каждой из модели, например, чтобы он возвращал объекты уже отсортированными, либо выводил количество записей по определённому запросу.
Простое переопределение manager для возврата отсортированных по датам рождения Publisher’ов:
class PublisherFilterManager(models.Manager): def get_queryset(self): return super().get_queryset().order_by("-birthdate") class Publisher(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) birthdate = models.DateField() objects = models.Manager() sorted_objects = PublisherFilterManager() def __str__(self): return "{} {}".format(self.first_name, self.last_name)
Функция super() возвращает объект, представляющий родительский класс.
>>> from cbv_app import models >>> sorted_publishers = models.Publisher.sorted_objects.all() >>> sorted_publishers.values() <QuerySet [{'id': 1, 'first_name': 'Vasya', 'last_name': 'Pupkin', 'birthdate': datetime.date(2020, 9, 30)}, {'id': 3, 'first_name': 'Vasya', 'last_name': 'Maximov', 'birthdate': datetime.date(1980, 9, 9)}, {'id': 2, 'first_name': 'Александр', 'last_name': 'Фёдоров', 'birthdate': datetime.date(1975, 7, 7)}]>
Переопределение manage, чтобы вывести количество опубликованных книг из связной модели Book:
class BookCountManager(models.Manager): def get_queryset(self): return super().get_queryset().annotate(published_books_count=models.Count('book')) class Publisher(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) birthdate = models.DateField() objects = models.Manager() sorted_objects = PublisherFilterManager() book_count_objects = BookCountManager() def __str__(self): return "{} {}".format(self.first_name, self.last_name)
>>> from cbv_app import models >>> publishers_book_count = models.Publisher.book_count_objects.all().values() >>> publishers_book_count <QuerySet [{'id': 1, 'first_name': 'Vasya', 'last_name': 'Pupkin', 'birthdate': datetime.date(2020, 9, 30), 'published_books_count': 1}, {'id': 2, 'first_name': 'Александр', 'last_name': 'Фёдоров', 'birthdate': datetime.date(1975, 7, 7), 'published_books_count': 2}, {'id': 3, 'first_name': 'Vasya', 'last_name': 'Maximov', 'birthdate': datetime.date(1980, 9, 9), 'published_books_count': 0}]>