Методы выбора записей из таблиц. Fields lookups

Курс по Django: https://stepik.org/a/183363

Архив проекта: 20_sitewomen.zip

Продолжаем знакомиться с базовыми возможностями ORM Django. Перейдем в консоль shell_plus:

python manage.py shell_plus --print-sql

(в оболочке shell_plus модели импортируются автоматически).

Каждый класс модели содержит специальный статический объект objects, который наследуется от базового класса Model и представляет собой ссылку на специальный класс Manager. В этом легко убедиться, если выполнить строчку:

Women.objects

В консоли увидим:

<django.db.models.manager.Manager object at 0x0399CA00>

Этот объект objects еще называют менеджером записей и у него есть несколько весьма полезных методов. Начнем с метода create() создания новой записи в таблице:

w = Women.objects.create(title='Ума Турман', content='Биография Ума Турман')

Если теперь обратиться к свойству:

w.pk

то увидим значение 5, то есть, запись была автоматически добавлена в БД. Нам здесь не нужно отдельно вызывать метод save(), все происходит «на лету», что бывает весьма полезно. Давайте добавим еще одну запись, не присваивая результат какой-либо переменной:

Women.objects.create(title='Кира Найтли', content='Биография Киры Найтли')

В таблице появилась еще одна запись с идентификатором 6. Вот так довольно просто можно добавлять новые записи в таблицы, используя ORM Django.

Выборка записей из таблицы методом all()

Как теперь можно прочитать данные из таблицы women? Для этого воспользуемся встроенным менеджером записей – объектом objects и выполним метод all():

Women.objects.all()

На выходе получаем список QuerySet:

<QuerySet [<Women: Women object (1)>, <Women: Women object (2)>, <Women: Women object (3)>, <Women: Women object (4)>, <Women: Women object (5)>, <Women: Women object (6)>]>

Но такая информация, когда показываются записи только с идентификаторами, не очень информативна. Давайте вместо идентификаторов будем выводить заголовок – поле title. Для этого в модели достаточно переопределить магический метод __str__:

class Women(models.Model):
...
    def __str__(self):
        return self.title

Чтобы изменения вступили в силу, выйдем из консоли Django (команда exit) и снова зайти в shell_plus. Импортируем модель:

from women.models import Women

И выполняем команду выбора всех записей:

Women.objects.all()

Теперь в консоли отображаются заголовки записей, а не их id:

<QuerySet [<Women: Анджелина Джоли>, <Women: Энн Хэтэуэй>, <Women: Джулия Робертс>, <Women: Екатерина Гусева>, <Women: Ума Турман>, <Women: Кира Найтли>]>

Как из полученного списка получить отдельную запись? Это можно сделать либо по индексу, например:

w = Women.objects.all()[0]

Причем, w – это ссылка на объект класса Women, у которого имеются указанные нами атрибуты. Эти атрибуты содержат информацию о текущей записи:

w.title
w.content

Или же можно указать срез, например:

w = Women.objects.all()[:3]

Обратите внимание, на данный момент ORM не делает обращения к БД, т.к. мы не обращаемся к записям в консоли. Но если отобразить переменную w:

w

то увидим SQL-запрос и отображение коллекции QuerySet для первых трех записей.

Также список записей можно получить и затем перебрать циклом for, например, так:

ws = Women.objects.all()
for w in ws:
     print(w)

Выборка записей по фильтру (критерию)

Как вы понимаете, если таблица будет состоять из тысяч, а то и миллиона записей, то все они будут возвращаться методом all(). (Хотя в консоли стоит ограничение на 21 запись.) Это не очень хорошо с точки зрения расхода памяти и ресурсов процессора. И обычно в программе нам требуется всего несколько записей, выбранных по какому-либо критерию (условию). Для этого вместо метода all() следует использовать метод filter(), например, так:

Women.objects.filter(title='Энн Хэтэуэй')

На выходе получим одну запись, у которой id равен 2. Сам же SQL-запрос, выглядит следующим образом:

'SELECT "women_women"."id", "women_women"."title", "women_women"."content", "women_women"."photo", "women_women"."time_create", "women_women"."time_update", "women_women"."is_published" FROM "women_women" WHERE "women_women"."title" = \'Энн Хэтэуэй\' LIMIT 21'

То есть, метод filter добавляет ключевое слово WHERE для формирования выборки по условию. Причем, если условию не будет удовлетворять ни одна запись, то получим пустой список:

Women.objects.filter(title='Энн')

Этот пример также показывает, что ищется строка целиком, а не ее часть.

Далее, если мы хотим сделать выборку по записям, у которых id больше или равен 2, то прописать условие в виде:

Women.objects.filter(pk > 2)

нельзя, так как pk – это именованный параметр и ему нужно явно присваивать определенное значение. Поэтому в Django атрибутам, определенным в модели, можно дополнительно прописывать следующие lookup’ы:

  • <имя атрибута>__gte – сравнение больше или равно (>=);
  • <имя атрибута>__gt – сравнение больше (>);
  • <имя атрибута>__lte – сравнение меньше или равно (<=);
  • <имя атрибута>__lt – сравнение меньше (<).

Подробную информацию о них можно посмотреть в документации:

https://docs.djangoproject.com/en/4.2/ref/models/querysets/#field-lookups

В результате, искомый фильтр можно записать следующим образом:

Women.objects.filter(pk__gte=2)

Рассмотрим еще несколько интересных примеров lookups. Если мы запишем команду в виде:

Women.objects.filter(title='ли')

то будут выбираться все записи, у которых заголовок точно соответствует строке «ли». Если же нам нужно выбрать те, у которых в заголовке присутствует этот фрагмент, то следует воспользоваться фильтром contains. Он позволяет находить строки по их фрагменту, учитывая регистр букв. Например, команда:

Women.objects.filter(title__contains='ли')

выдаст список всех женщин, в заголовке у которых присутствует фрагмент «ли». Опять же, на уровне SQL-запроса это делается с помощью фрагмента:

«WHERE title LIKE '%ли%'»

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

Women.objects.filter(title__icontains='ЛИ')

то получим пустой список. Почему? Дело в том, что СУБД SQLite не поддерживает регистронезависимый поиск для русских символов (вообще, для всех не ASCII-символов), поэтому получаем пустой список. Другие СУБД, как правило, отрабатывают все это корректно. В случае с латинскими символами в SQLite поиск всегда проходит как регистронезависимый.

Следующий полезный фильтр in позволяет указывать через список выбираемые записи по значениям. Например, выберем записи с id равными 2, 5, 11, 12:

Women.objects.filter(pk__in=[2,5,11,12])

Если по условию нужно отработать сразу несколько фильтров, то они указываются через запятую:

Women.objects.filter(pk__in=[2,5,11,12], is_published=1)

обратите внимание, указывая два критерия через запятую, на уровне SQL-запросов формируется связка через AND (логическое И):

WHERE ("women_women"."is_published" AND "women_women"."id" IN (2, 5, 11, 12))

то есть, запись выбирается, если оба критерия срабатывают одновременно. Чтобы определять условия через OR  (логическое ИЛИ) используется специальный класс Q. Речь о нем пойдет дальше. По аналогии используются и все остальные фильтры фреймворка Django.

Противоположный по действию является метод exclude(). Он выбирает записи не удовлетворяющие указанному условию. Например:

Women.objects.exclude(pk=2)

Будут выбраны все записи, кроме записи с id равным 2.

Итак, методы filter и exclude позволяют выбирать совокупность записей по определенному условию. Но что если нам нужно выбрать только одну, строго определенную запись обычно, используя, ее идентификационный номер id. Конечно, здесь мы также можем использовать метод filter, например, так:

Women.objects.filter(pk=2)

Получим список из одной записи с id равным 2. Но в ORM для этих целей есть другой метод get(). Например:

Women.objects.get(pk=2)

Получим саму запись (без списка). В чем разница между этими двумя методами? Смотрите, если по условию записей будет несколько:

Women.objects.get(pk__gte=2)

или они не будут существовать:

Women.objects.get(pk=20)

то метод get() генерирует исключения. А метод filter() вернет несколько записей или пустой список. То есть, используя метод get() мы уверены, что была получена только одна запись и в таблице по указанному условию других записей нет. Часто это бывает очень важно, например, при авторизации пользователя мы должны найти одну уникальную запись, связанную именно с ним и никакую другую. В этом случае метод get() незаменим.

Курс по Django: https://stepik.org/a/183363

Видео по теме