Django objects filter: Работа с запросами | Документация Django 3.0
Django | CRUD. Операции с моделями
CRUD. Операции с моделями
Последнее обновление: 26.02.2018
При создании моделей они наследуют поведение от класса django.db.models.Model, который предоставляет ряд базовых операций с данными.
Рассмотрим ряд операций на примере модели Person:
from django.db import models class Person(models.Model): name = models.CharField(max_length=20) age = models.IntegerField()
Добавление данных
create
Для добавления данных применяется метод create():
tom = Person.objects.create(name="Tom", age=23)
Если добавление пройдет успешно, то объект будет иметь id, который можно получить через tom.id
.
save
Однако в своей сути метод create()
использует другой метод — save(), который мы также можем использовать отдельно
для добавления объекта:
tom = Person(name="Tom", age=23) tom.save()
После успешного добавления также можно получить идентификатор добавленной записи с помощью tom.id
.
Получение из бд
Получение одного объекта
Метод get() возвращает один объект по определенному условию, которое передается в качестве параметра:
tom = Person.objects.get(name="Tom") # получаем запись, где name="Tom" bob = Person.objects.get(age=23) # получаем запись, где age=23 tim = Person.objects.get(name="Tim", age=23) # запись, где name="Tim" и age=23
При использовании этого метода надо учитывать, что он предназначен для выборки таких объектов, которые имеются в единичном числе в базе данных. Если
в таблице не окажется подобного объекта, то мы получим ошибку имя_модели.DoesNotExist
. Если же в таблице будет несколько объектов, которые соответствуют
условию, то будет сгенерированно исключение MultipleObjectsReturned
. Поэтому следует применять данный метод с осторожностью.
Метод get_or_create() возвращает объект, а если его нет в бд, то добавляет в бд новый объект.
bob, created = Person.objects.get_or_create(name="Bob", age=24) print(bob.name) print(bob.age)
Метод возвращает добавленный объект (в данном случае переменная bob) и булевое значение (created), которое хранит True, если добавление прошло успешно.
all()
Если необходимо получить все имеющиеся объекты, то применяется метод all():
people = Person.objects.all()
filter()
Если надо получить все объекты, которые соответствуют определенному критерию, то применяется метод filter(),
который в качестве параметра принимает критерий выборки:
people = Person.objects.filter(age=23) # использование нескольких криетриев people2 = Person.objects.filter(name="Tom", age=23)
exclude()
Метод exclude() позволяют исключить из выборки записи, которые соответвуют переданному в качестве параметра критерию:
# исключаем пользователей, у которых age=23 people = Person.objects.exclude(age=23)
Можно комбинировать два выше рассмотренных метода:
# выбираем всех пользователей, у которых name="Tom" кроме тех, у которых age=23 people = Person.objects.filter(name="Tom").exclude(age=23)
in_bulk()
Метод in_bulk() является более эффективным способом для чтения большого количества записей. Он возвращает словарь,
то есть объект dict, тогда как методы all()
, filter()
и exclude()
возвращают объект QuerySet:
# получаем все объекты people = Person.objects.in_bulk() for id in people: print(people[id].name) print(people[id].age) # получаем объекты с id=1 и id=3 people2 = Person.objects.in_bulk([1,3]) for id in people2: print(people2[id].name) print(people2[id].age)
Метод in_bulk возвращает словарь, где ключи представляют id объектов, а значения по этим ключам — собственно эти объекты, то есть в данном случае объекты Person.
Обновление
save()
Для обновления объекта также применяется метод save():
bob = Person.objects.get(id=2) bob.name = "Bob" bob.save()
В этом случае Django полностью обновляет объект, все его свойства, даже если мы их не изменяли. Чтобы указать, что нам надо обновить только
определенные поля, следует использовать параметр update_fields
:
bob = Person.objects.get(id=2) bob.name = "Bobic" bob.save(update_fields=["name"])
Это позволит повысить производительность.
update()
Другой способ обновления объектов представляет метод update() в сочетании с методом filter,
которые вместе выполняют один запрос к базе данных:
Person.objects.filter(id=2).update(name="Mike")
Если нам не надо получать обновляемый объект, то данный способ позволит нам увеличить производительность взаимодействия с бд.
Иногда бывает необходимо изменить значение столбца в бд на основании уже имеющегося значения. В этом случае мы можем использовать функцию
F():
from django.db.models import F Person.objects.all(id=2).update(age = F("age") + 1)
В данном случае полю age присваивается уже имеющееся значение, увеличенное на единицу.
При этом важно учитывать, что метод update обновляет все записи в таблице, которые соответствуют условию.
Если надо обновить вообще все записи, вне зависимости от условия, то необходимо комбинировать метод update с методом all()
:
from django.db.models import F Person.objects.all().update(name="Mike") Person.objects.all().update(age = F("age") + 1)
update_or_create()
Метод update_or_create обновляет запись, а если ее нет, то добавляет ее в таблицу:
values_for_update={"name":"Bob", "age": 31} bob, created = Person.objects.update_or_create(id=2, defaults = values_for_update)
Метод update_or_create()
принимает два параметра. Первый параметр представляет критерий выборки объектов, которые будут обновляться.
Второй параметр представляет объект со значениями, которые будут переданы записям, которые соответствуют китерию из первого параметра.
Если критерию не соответствует никаких записей, то в таблицу добавляется новый объект, а переменная created будет равна True.
Удаление
Для удаления мы можем вызвать метод delete() у удаляемого объекта:
person = Person.objects.get(id=2) person.delete()
Если не требуется получение отдельного объекта из базы данных, тогда можно удалить объект с помощью комбинации методов filter()
и delete()
:
Person.objects.filter(id=4).delete()
Просмотр строки запроса
С помощью свойства query у результата запроса мы можем получить SQL-запрос, который выполнялся. Например:
people = Person.objects.filter(name="Tom").exclude(age=34) print(people.query)
Данный код отобразит на консоли SQL-запрос типа следующего:
SELECT "firstapp_person"."id", "firstapp_person"."name", "firstapp_person"."age" FROM "firstapp_person" WHERE ("firstapp_person"."name" = Tom AND NOT ("firstapp_person"."age" = 34))
Сравнение разных django filter на примере демо базы PostgreSQL / Хабр
Началось всё с того, что мне предложили в рамках предмета «Основы веб-программирования» поучаствовать в проекте, вместо проделывания лабораторных работ и курсовой, поскольку я заявил о том, что хотел быть делать нечто отдалённое от общего курса (и так уже достаточно знаний было по связке DRF + Vue, хотелось чего-то нового). И вот в одном из своих PR на github я решил использовать полнотекстовый поиск (задание намекало на это) для фильтрации контента, что заставило меня обратиться к документации Django в поисках того, каким же образом лучше это дело реализовать. Думаю, вы знаете большую часть из тех методов, что были там предложены (contains, icontains, trigram_similar). Все они подходят для каких-то конкретных задач, но не слишком хороши в, именно, полнотекстовом поиске. Пролистав чуть ниже, я наткнулся на раздел, в котором говорилось о взаимодействии Django и Pgsql для реализации document-based поиска, что меня привлекло, поскольку в постгре встроен инструмент для реализации этого самого [полнотекстового] поиска. И я решил, что скорее всего, django просто предоставляет API к этому поиску, исходя из чего такое решение должно работать и точнее и быстрее, чем любые другие варианты. Преподаватель мне не слишком поверил, мы с ним поспорили, и он предложил провести исследование на эту тему. И вот я здесь.
Начало работы
Первая проблема, которая передо мной встала — поиск мокапа БД, чтобы не придумать каких-нибудь непонятных штук самому и я отправился гуглить и читать вики постгреса. В итоге остановился на их демо базе о полётах по России.
Хорошо, база найдена. Теперь нужно определиться в том, какие способы фильтрации будут использоваться для сравнения. Первое, что я бы хотел использовать, разумеется, стандартный метод search из django.contrib.postgres.search. Второе — contains (ищет слово в строке) и icontains (предоставляет данные, игнорируя акценты, например: по запросу «Helen» будет результат: <Author: Helen Mirren>, <Author: Helena Bonham Carter>, <Author: Hélène Joy>), которые предоставляет сам django. Все эти способы фильтрации я так же хочу сравнить со встроенным поиском внутри postgresql. Искать я решил по таблице tickets в версии small она содержит 366733 записей. Поиск будет происходить по полю passenger_name, где, как нетрудно догадаться, содержится имя пассажира. Написано оно транслитом.
Дать django возможность работать с уже существующей БД
Вторая проблема — разрешить django только чтение данных из нашей демонстрационной БД. Покопавшись ещё в документации django я нашёл каким же образом, можно составить модельки по существующей БД, чтобы не перепечатывать ручками всё:
$ python manage.py inspectdb > models.py
При этом, разумеется, сама БД должна быть обозначена в settings.py. Всего пару ошибочек мне пришлось поправить и всё заработало как следует. Сразу после этого я решил написать простенькую вьюшку, которая сможет нам эти данные вернуть. Браузер, разумеется очень напрягся (что и не мудрено), когда я пытался открыть адрес, по которому должно было вернуться 300к+ записей, поэтому я ограничил их число для 10, чтобы удостовериться, что они там хотя бы есть. А вообще, совершенно точно понятно, что запрос лучше отправлять через curl. Это явно скушает в разы меньше оперативной памяти.
Выбор метрик
Изначально я подумал, что считать время фильтрации в питоне получится, используя таймер для получения времени исполнения скрипта, и дополнительной метрикой должно было стать время исполнения запроса через curl, поскольку это показывает приблизительное время, за которое отфильтрованные данные дойдут до конечного пользователя. Кроме этого, следует сравнивать это время с эталонным (прямым исполнением соответствующих запросов в БД).
Фильтруем в django
Предвосхищая вопросы, касательно кода из спойлера — время исполнения скрипта является обоснованной метрикой в том и только том случае, если с queryset были проведены какие-либо операции. Поэтому я вывожу длину получившегося ответа.
Выдержка отсюда:
A QuerySet is iterable, and it executes its database query the first time you iterate over it. For example, this will print the headline of all entries in the database:
for e in Entry.objects.all(): print(e.headline)```
Итоговая view для contains
class TicketListView(g.ListAPIView):
serializer_class = TicketSerializer
def get_queryset(self):
queryset = ''
params = self.request.query_params
name = params.get('name', None)
if name:
start_time = d.datetime.now()
queryset = queryset.filter(passenger_name__contains=name)
print('len of result is {} rows'.format(len(queryset)))
end_time = d.datetime.now()
time_diff = (end_time - start_time)
execution_time = time_diff.total_seconds() * 1000
print("Filter execution time {} ms".format(execution_time))
return queryset
Contains
Начнём с contains, по сути, он работает как WHERE LIKE.
Запрос в Django ORM/Запрос в sql для contains
queryset = queryset.filter(passenger_name__contains=name)
SELECT "tickets"."ticket_no", "tickets"."book_ref", "tickets"."passenger_id", "tickets"."passenger_name", "tickets"."contact_data" FROM "tickets" WHERE "tickets"."passenger_name"::text LIKE %IVAN%
Для того, чтобы получить результат из curl я выполнял запрос следующим образом (считается в секундах):
$ curl -w "%{time_total}\n" -o /dev/null -s http://127.0.0.1:8000/api/tickets/?name=IVAN
1,242888
Свёл всё в таблице, на соответствующем листе.
Но если резюмировать — отклонение от скорости фильтрации внутри самого постгреса достаточно большое, и по факту исполнение такого запроса к серверу займёт от 140 до 1400 мс. Не претендую на истину, но работает всё приблизительно так. А время самой фильтрации через ORM займёт от 73 до 600 мс, в то время как такая же фильтрация внутри постгреса выполняется за промежуток от 55 до 100 мс.
Icontains
Icontains работает несколько по-другому (он приводит всё к одному виду, чтобы сравнение было более близким). Код для вьюшки использовался почти аналогичный, только вместо contains — icontains. Вот и вся разница.
Запрос в Django ORM/Запрос в sql для icontains
queryset = queryset.filter(passenger_name__icontains=name)
SELECT "tickets"."ticket_no", "tickets"."book_ref", "tickets"."passenger_id", "tickets"."passenger_name", "tickets"."contact_data" FROM "tickets" WHERE UPPER("tickets"."passenger_name"::text) LIKE UPPER(%IVAN%)
По итогу, отклонение в данном случае уже меньше, поскольку и сам постгрес начал тратить намного большее времени на исполнение запроса (порядка 300 мс), а исполнение такого запроса к серверу займёт у клиента от 200 до 1500 мс. Фильтрация через ORM — от до 200 до 700 мс.
Full text search (через django.contrib.postgres)
Поскольку индексов никаких создано не было, full text search довольно сильно и вполне ощутимо проигрывает прошлым вариантам. Время исполнения запроса в постгресе колеблется около 1300 мс, а запрос к серверу занимает от 1000 до 1700 мс. При этом, фильтрация через ORM — укладывается в промежуток от 1000 до 1450 мс.
Код
class TicketListView(g.ListAPIView):
serializer_class = TicketSerializer
def get_queryset(self):
# queryset = Tickets.objects.all()
queryset = ''
params = self.request.query_params
name = params.get('name', None)
if name:
start_time = d.datetime.now()
queryset = Tickets.objects.filter(passenger_name__search=name)
end_time = d.datetime.now()
time_diff = (end_time - start_time)
execution_time = time_diff.total_seconds() * 1000
print("Filter execution time {} ms".format(execution_time))
f = open('results.txt', 'a')
f.write('{}'.format(execution_time))
f.write('\n')
f.close()
return queryset
Full text search (через rest_framework.filters, точнее — SearchFilter)
Если не использвоать именно FTS, то результаты получаются сравнимыми с FTS внутри постгре, но хуже, чем contains и icontains. От 200 до 1710 мс.
А с использованием FTS эффективность повышается, отклонение сводится к минимальному. В среднем, это займёт от 800 до 1120 мс.
Код
...
from rest_framework import filters as f
class TicketListView(g.ListAPIView):
queryset = Tickets.objects.all()
serializer_class = TicketSerializer
filter_backends = [f.SearchFilter]
search_fields = ['@passenger_name']
Использование фильтров через модуль django-filter
Результаты почти совпали со стандартными contains и icontains, поэтому смысла детально это рассматривать не вижу. Да и в целом, модуль django-filter не показал какого-то ощутимого преимущества перед стандартными средствами фильтрации Django ORM.
Так что в итоге?
Если у вас есть большой объём данных — нужно прописывать нормальные индексы и использовать полнотекстовый поиск (разумеется, только в том случае, когда соответствует вашим целям) с радостью и счастьем, потому что он решает довольно широкий круг проблем. Но вот всегда ли в нём есть необходимость — уже решать вам. Я усвоил для себя, что в некоторых случаях (когда не стоит задачи именно полнотекстового поиска, а есть поиск по подстроке, который реализуется с помощью contains/icontains) лучше вовсе не использовать полнотекстовый поиск, потому что индексы в определённый момент начинают кушать всё больше и больше памяти вашего сервера, что, скорее всего, негативно скажется на работе вашего сервера.
В целом, моё понимание некоторых внутренних механизмов работы django благодаря этому исследованию устаканилось. И пришло, наконец, осознание разницы между поиском по подстроке и полнотекстовым поиском. Разнице в их реализации через Django ORM.
Выполнение запросов — Документация Django 1.4
После создания модели, Django автоматически создает API для работы с базой данных, который позволяет вам создавать, получать, изменять и удалять объекты. Этот раздел расскажет вам как использовать этот API. В описании моделей вы можете найти список всех существующих опций поиска.
В этом разделе(и последующих) мы будем использовать такие модели:
Создание объектов
Для представления данных таблицы в виде объектов Python, Django использует интуитивно понятную систему: класс модели представляет таблицу, а экземпляр модели — запись в этой таблице.
Что бы создать объект, создайте экземпляр класса модели, указав необходимые поля в аргументах и вызовите метод save() что бы сохранить его в базе данной.
Вы можете импортировать класс модели как и любой другой класс в Python. (Мы упомянули это потому что в ранних версиях Django импорт моделей выглядел немного необычно.)
Предположим, что модель находится в mysite/blog/models.py:
>>> from blog.models import Blog >>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.') >>> b.save()
В результате выполнения этого кода будет создан INSERT SQL запрос. Django не выполняет запросов к базе данных, пока не будет вызван метод save().
Метод save() ничего не возвращает.
См.также
save() принимает ряд аргументов не описанных в этом разделе. Смотрите документацию о методе save() для подробностей.
Что бы создать и сохранить объект используйте метод create().
Сохранение изменений в объектах
Для сохранения изменений в объект, который уже существует в базе данных, используйте save().
В данном примере изменяется название объекта b5 модели «Blog«и обновляется запись в базе данных:
>> b5.name = 'New name' >> b5.save()
В результате выполнения этого кода будет создан UPDATE SQL запрос. Django не выполняет каких либо запросов к базе данных, пока не будет вызван метод save().
Сохранение полей ForeignKey и ManyToManyField
Обновление ForeignKey работает так же как и сохранение обычных полей; просто назначьте полю объект необходимого типа. В этом примере обновляется атрибут blog модели Entry объектом entry:
>>> from blog.models import Entry >>> entry = Entry.objects.get(pk=1) >>> cheese_blog = Blog.objects.get(name="Cheddar Talk") >>> entry.blog = cheese_blog >>> entry.save()
Обновление ManyToManyField работает немного по другому; используйте метод add() поля, что бы добавить связанный объект. В этом примере объект joe модели Author добавляется к объекту entry:
>>> from blog.models import Author >>> joe = Author.objects.create(name="Joe") >>> entry.authors.add(joe)
Для добавления сразу нескольких объектов в ManyToManyField, добавьте несколько аргументов в метод add(). Например:
>>> john = Author.objects.create(name="John") >>> paul = Author.objects.create(name="Paul") >>> george = Author.objects.create(name="George") >>> ringo = Author.objects.create(name="Ringo") >>> entry.authors.add(john, paul, george, ringo)
Django вызовет исключение, если вы попытаетесь добавить объект неверного типа.
Получение объектов
Для получения объектов из базы данных, создается QuerySet через Manager модели.
QuerySet представляет выборку объектов из базы данных. Он может не содержать, или содержать один или несколько фильтров – критерии для ограничения выборки по определенным параметрам. В терминах SQL, QuerySet — это оператор SELECT, а фильтры — условия такие, как WHERE или LIMIT.
Вы получаете QuerySet используя Manager. Каждая модель содержит как минимум один Manager, и он называется objects по-умолчанию. Обратиться к нему можно непосредственно через класс модели:
>>> Blog.objects <django.db.models.manager.Manager object at ...> >>> b = Blog(name='Foo', tagline='Bar') >>> b.objects Traceback: ... AttributeError: "Manager isn't accessible via Blog instances."
Примечание
Обратиться к менеджерам можно только через модель и нельзя через ее экземпляр. Это сделано для разделения “table-level” операций и “record-level” операций.
Manager — главный источник QuerySet для модели. Он является “корнем” QuerySet, который описывает все записи таблицы в базе данных. Например, Blog.objects — первоначальный QuerySet, который содержит все объекты Blog из базы данных.
Получение всех объектов
Самый простой способ получить объекты из таблицы — это получить их всех. Для этого используйте метод all() менеджера(Manager):
>>> all_entries = Entry.objects.all()
Метод all() возвращает QuerySet всех объектов в базе данных.
(Если Entry.objects — это QuerySet, почему мы не можем просто использовать Entry.objects? Потому что Entry.objects является фабрикой объектов QuerySet. Для получения QuerySet необходимо использовать метод all().)
Получение объектов через фильтры
Корень QuerySet, предоставленный Manager, описывает все объекты в таблице базы данных. Обычно вам нужно выбрать только подмножество всех объектов.
Для создания такого подмножества, вы можете изменить QuerySet добавив условия фильтрации. Два самых простых метода изменить QuerySet — это:
- filter(**kwargs)
Возвращает новый QuerySet, который содержит объекты удовлетворяющие параметрам фильтрации.
- exclude(**kwargs)
Возвращает новый QuerySet содержащий объекты, которые не удовлетворяют параметры фильтрации.
Параметры фильтрации (**kwargs в определении функций выше) должны быть в формате описанном в разделе `Field lookups`_.
Например, для создания QuerySet что бы получить записи с 2006, используйте filter() таким образом:
Entry.objects.filter(pub_date__year=2006)
Мы не должны добавлять метод all() – Entry.objects.all().filter(…). Это будет работать, но вы должны использовать all(), когда хотите получить все объекты базового QuerySet.
Цепочка фильтров
Результат изменения QuerySet — это новый QuerySet, по этому можно использовать цепочки фильтров. Например:
>>> Entry.objects.filter( ... headline__startswith='What' ... ).exclude( ... pub_date__gte=datetime.now() ... ).filter( ... pub_date__gte=datetime(2005, 1, 1) ... )
В этом примере к начальному QuerySet, который возвращает все объекты, добавляется фильтр, затем исключающий фильтр, и еще один фильтр. Полученный QuerySet содержит все объекты, у которых заголовок начинается с “What”, и которые были опубликованы между 1-го января 2005 и текущей датой.
Отфильтрованный QuerySets – уникален
После каждого изменения QuerySet, вы получаете новый QuerySet, который никак не связан с предыдущим QuerySet. Каждый раз создается отдельный QuerySet, который может быть сохранен и использован.
Например:
>> q1 = Entry.objects.filter(headline__startswith="What") >> q2 = q1.exclude(pub_date__gte=datetime.now()) >> q3 = q1.filter(pub_date__gte=datetime.now())
Эти три QuerySets независимы. Первый – это базовый QuerySet, который содержит все объекты с заголовками, которые начинаются с “What”. Второй – это множество первых с дополнительным критерием фильтрации, который исключает объекты с pub_date больше, чем текущая дата. Третий – это множество первого, с отфильтрованными объектами, у которых pub_date больше, чем текущая дата. Первоначальный QuerySet (q1) не изменяется последующим добавлением фильтров.
QuerySets – ленивы
QuerySets – ленивы, создание QuerySet не выполняет запросов к базе данных. Вы можете добавлять фильтры хоть весь день и Django не выполнит ни один запрос пока QuerySet не вычислен. Разберем такой пример:
>>> q = Entry.objects.filter(headline__startswith="What") >>> q = q.filter(pub_date__lte=datetime.now()) >>> q = q.exclude(body_text__icontains="food") >>> print q
Глядя на это можно подумать что было выполнено три запроса в базу данных. На самом деле был выполнен один запрос, в последней строке (print q). Результат QuerySet не будет получен из базы данных пока вы не “попросите” об этом. Когда вы делаете это, QuerySet вычисляется запросом к базе данных. Для подробностей, в какой момент выполняется запрос, смотрите Когда вычисляется QuerySets.
Получение одного объекта с помощью get
filter() всегда возвращает QuerySet, даже если только один объект возвращен запросом — в этом случае, это будет QuerySet содержащий один объект.
Если вы знаете, что только один объект возвращается запросом, вы можете использовать метод get() менеджера, который возвращает непосредственно объект:
>>> one_entry = Entry.objects.get(pk=1)
Вы можете использовать для get() аргументы, такие же как и для filter() — смотрите `Field lookups`_ далее.
Учтите, что есть разница между использованием get() и filter() с [0]. Если результат пустой, get() вызовет исключение DoesNotExist. Это исключение является атрибутом модели для которой выполняется запрос. Если в примере выше, не существует объекта Entry с первичным ключом равным 1, Django вызовет исключение Entry.DoesNotExist.
Также Django отреагирует, если запрос get() вернет не один объект. В этом случае будет вызвано исключение MultipleObjectsReturned, которое так же является атрибутом класса модели.
Другие методы QuerySet
В большинстве случаев вы будете использовать all(), get(), filter() и exclude() для получения объектов из базы данных. Однако это не все доступные возможности; смотрите документацию о QuerySet API для получения информации о всех существующих методах QuerySet.
Ограничение выборки
Используйте синтаксис срезов для списков Python для ограничения результата выборки QuerySet. Это эквивалент таких операторов SQL как LIMIT и OFFSET.
Например, этот код возвращает 5 первых объектов (LIMIT 5):
>>> Entry.objects.all()[:5]
Этот возвращает с шестого по десятый (OFFSET 5 LIMIT 5):
>>> Entry.objects.all()[5:10]
Отрицательные индексы (например, Entry.objects.all()[-1]) не поддерживается.
На самом деле, срез QuerySet возвращает новый QuerySet – запрос не выполняется. При использовании аргумента “шаг” синтаксиса срезов в Python будет вызвано исключение. Например, этот пример выполнил бы запрос, возвращающий каждый второй объект из первых 10:
>>> Entry.objects.all()[:10:2]
Для получения одного объекта, а не списка (например, SELECT foo FROM bar LIMIT 1), используйте индекс вместо среза. Например, этот код возвращает первый объект Entry в базе данных, после сортировки записей по заголовку:
>>> Entry.objects.order_by('headline')[0]
Это эквивалент:
>>> Entry.objects.order_by('headline')[0:1].get()
Заметим, что первый пример вызовет IndexError в то время как второй — DoesNotExist, если запрос вернет ни один объект. Смотрите get() для подробностей.
Фильтры полей
Фильтры полей – это “операторы” для составления условий SQL WHERE. Они задаются как именованные аргументы для метода filter(), exclude() и get() в QuerySet.
Фильтры полей выглядят как field__lookuptype=value. (Используется двойное подчеркивание). Например:
>>> Entry.objects.filter(pub_date__lte='2006-01-01')
будет транслировано в SQL:
SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';
How this is possible
Python позволяет определить функции, которые принимают именованные аргументы с динамически вычисляемыми названиями и значениями. Подробности смотрите в разделе Именованные аргументы в официальной документации Python.
При передаче неверного именованного аргумента, будет вызвано исключение TypeError.
API базы данных поддерживает около двух дюжин фильтров; полный список можно найти в разделе о фильтрах полей. Вот пример самых используемых фильтров:
- exact
“Точное” совпадение. Например:
>>> Entry.objects.get(headline__exact="Man bites dog")
Создаст такой SQL запрос:
SELECT ... WHERE headline = 'Man bites dog';
Если вы не укажите фильтр – именованный аргумент не содержит двойное подчеркивание – будет использован фильтр exact.
Например, эти два выражения идентичны:
>>> Blog.objects.get(id__exact=14) # Explicit form >>> Blog.objects.get(id=14) # __exact is implied
Это сделано для удобства, т.к. exact самый распространенный фильтр.
- iexact
Регистро-независимый совпадение. Такой запрос:
>>> Blog.objects.get(name__iexact="beatles blog")
Найдет Blog с названием “Beatles Blog”, “beatles blog”, и даже “BeAtlES blOG”.
- contains
Регистро-зависимая проверка на вхождение. Например:
Entry.objects.get(headline__contains='Lennon')
Будет конвертировано в такой SQL запрос:
SELECT ... WHERE headline LIKE '%Lennon%';
Заметим что этот пример найдет заголовок ‘Today Lennon honored’, но не найдет ‘today lennon honored’.
Существует так же регистро-независимые версии, icontains.
- startswith, endswith
Поиск по началу и окончанию соответственно. Существуют также регистро-независимые версии istartswith и iendswith.
Это только основные фильтры. Полный список ищите в разделе о фильтрах по полям.
Фильтры по связанным объектам
Django предлагает удобный и понятный интерфейс для фильтрации по связанным объектам, самостоятельно заботясь о JOIN в SQL. Для фильтра по полю из связанных моделей, используйте имена связывающих полей разделенных двойным нижним подчеркиванием, пока вы не достигните нужного поля.
Этот пример получает все объекты Entry с Blog, name которого равен ‘Beatles Blog’:
>>> Entry.objects.filter(blog__name__exact='Beatles Blog')
Этот поиск может быть столь глубоким, как вам будет угодно.
Все работает и в другую сторону. Что бы обратиться к “обратной” связи, просто используйте имя модели в нижнем регистре.
Этот пример получает все объект Blog, которые имеют хотя бы один связанный объект Entry с headline содержащим ‘Lennon’:
>>> Blog.objects.filter(entry__headline__contains='Lennon')
Если вы используйте фильтр через несколько связей и одна из промежуточных моделей не содержит подходящей связи, Django расценит это как пустое значение (все значения равны NULL). Исключение не будет вызвано. Например, в этом фильтре:
Blog.objects.filter(entry__authors__name='Lennon')
(при связанной модели Author), если нет объекта author связанного с entry, это будет расценено как отсутствие name, вместо вызова исключения т.к. author отсутствует. В большинстве случаев это то, что вам нужно. Единственный случай, когда это может работать не однозначно — при использовании isnull. Например:
Blog.objects.filter(entry__authors__name__isnull=True)
вернет объекты Blog у которого пустое поле name у author и также объекты, у которых пустой author«в «entry. Если вы не хотите включать вторые объекты, используйте:
Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True)
Фильтрация по связям много-ко-многим
Когда вы используете фильтрация по связанным через ManyToManyField объектам или по обратной связи для ForeignKey, может быть два вида фильтров. Рассмотрим свзять Blog/Entry (от Blog к Entry – это связь один-ко-многим). Нам может понадобиться получить блоги с записями, у которых заголовок содержит “Lennon” и которые были опубликованы в 2008. Или нам могут понадобиться блоги с записями с “Lennon” в заголовке и в то же время блоги с записями опубликованными до 2008. Т.к. один Blog может иметь несколько связанных Entry, оба варианта возможны.
Аналогичная ситуация и с ManyToManyField. Например, если Entry имеет ManyToManyField названное tags, нам могут понадобиться записи связанные с тегами “music” и “bands” или нам может понадобиться запись содержащая тег “music” и статусом “public”.
Что бы обеспечить оба варианта, Django использует определенные правила для вызовов filter() и exclude(). Все, что в одном вызове filter(), применяется одновременно, чтобы отфильтровать все объекты, соответствующие этим параметрам фильтрации. Успешные вызовы filter() каждый раз сокращают выборку объектов, но для множественных связей, они применяются каждый раз ко всем связанным объектам, а не только к объектам отфильтрованным предыдущим вызовом filter().
Звучит немного непонятно, но пример должен все прояснить. Для выбора всех блогов, содержащих записи и с “Lennon” в заголовке и опубликованные в 2008 (запись должна удовлетворять оба условия), мы будем использовать такой код:
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)
Для выбора блогов с записями, у которых заголовок содержит “Lennon”, а так же с записями опубликованными в 2008, мы напишем:
Blog.objects.filter(entry__headline__contains='Lennon').filter( entry__pub_date__year=2008)
В это примере, первый фильтр ограничит выборку блогами с определенными связанными записями. Второй фильтр расширит выборку блогами с записями второго типа. Записи выбранные вторым фильтром могут быть такими же как и из первого фильтра, а могут и не быть. Мы фильтруем объекты Blog с каждым вызовом filter(), а не объекты Entry.
Все эти правила так же распространяются и на exclude(): все условия в одном exclude() применяются к одному объекту (если это условия относятся к одной связи). Условия в последующих вызовах filter() или exclude() с условия для одной связи могут применяться к различным связанным объектам.
Фильтры могут ссылаться на поля на модели
В примерах выше мы использовали фильтры, которые приравнивали поля к определенным значениям(константам). Но что, если вы хотите сравнить одно поле с другим полем одной модели?
Django предоставляет объект F() для таких сравнений. Экземпляр F() рассматривается как ссылка на другое поле модели. Эти ссылки могут быть использованы для сравнения значений двух разных полей одного объекта модели.
Например, что бы выбрать все записи, у которых количество комментариев больше, чем “pingback”, мы создаем объект F() с ссылкой на поле “pingback”, и использовать этот объект F() в запросе:
>>> from django.db.models import F >>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))
Django поддерживает операции суммирования, вычитания, умножения, деления и арифметический модуль для объектов F(), с константами или другими объектами F(). Что бы найти все записи с количеством комментариев в два раза больше чем “pingbacks”, используем такой запрос:
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)
Что бы найти все записи с рейтингом ниже суммы “pingback” и количества комментариев, необходимо выполнить такой запрос:
>>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))
Вы можете использовать два нижних подчеркивания для использования полей связанных объектов в F(). Объект F() с двойным нижним подчеркиванием обеспечит все необходимые JOIN для получения необходимых связанных объектов. Например, что бы получить все записи, у которых имя автора совпадает с названием блога, нужно выполнить такой запрос:
>>> Entry.objects.filter(authors__name=F('blog__name'))
Добавлено в Django 1.3.
Для полей даты и времени вы можете использовать сумму или разницу объектов timedelta. Этот код вернет все записи, которые были отредактированы через 3 дня после публикации:
>>> from datetime import timedelta >>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
“Shortcut” для фильтрации по первичному ключу
Для удобства, Django предоставляет специальный фильтр pk для работы с первичным ключом.
Например, первичный ключ модели Blog – поле id. Эти три запроса идентичны:
>>> Blog.objects.get(id__exact=14) # Explicit form >>> Blog.objects.get(id=14) # __exact is implied >>> Blog.objects.get(pk=14) # pk implies id__exact
Использование pk не ограничено только фильтром __exact – любой фильтр может быть использован с pk:
# Get blogs entries with id 1, 4 and 7 >>> Blog.objects.filter(pk__in=[1,4,7]) # Get all blog entries with id > 14 >>> Blog.objects.filter(pk__gt=14)
pk работает так же и для связей. Например, эти три запроса идентичны:
>>> Entry.objects.filter(blog__id__exact=3) # Explicit form >>> Entry.objects.filter(blog__id=3) # __exact is implied >>> Entry.objects.filter(blog__pk=3) # __pk implies __id__exact
Экранирование знака процента и нижнего подчеркивания для оператора LIKE
Фильтры, эквивалентые оператору LIKE в SQL(iexact, contains, icontains, startswith, istartswith, endswith и iendswith), автоматически экранируют два символа используемых оператором LIKE – знак процента и нижнего подчеркивания. (В операторе LIKE, знак процента означает “wildcard” из нескольких символов, нижнего подчеркивания — одно-символьный “wildcard”.)
Это делает работу с API интуитивно-понятной. Например, что бы получить все записи со знаком процента, просто используйте символ знака процента как любой другой символ:
>>> Entry.objects.filter(headline__contains='%')
Django самостоятельно позаботится об экранировании; полученный SQL будет выглядеть приблизительно вот так:
SELECT ... WHERE headline LIKE '%\%%';
Так же работает и символ нижнего подчеркивания. Оба, знак процента и нижнего подчеркивания, обрабатываются автоматически, прозрачно для вас.
Кеширование и QuerySets
Каждый QuerySet содержит кеш, для уменьшения количества запросов. Очень важно знать как он работает, для эффективного использования Django.
В только что созданном QuerySet кеш пустой. После вычисления QuerySet – и будет выполнен запрос к базе данных – Django сохраняет результат запроса в кеше QuerySet и возвращает необходимый результат (например, следующий элемент при итерации по QuerySet). Последующие вычисления QuerySet используют кеш.
Помните о кешировании, что бы использовать QuerySet правильно. Например, этот код создаст два экземпляра QuerySet и вычислит их не сохраняя:
>>> print [e.headline for e in Entry.objects.all()] >>> print [e.pub_date for e in Entry.objects.all()]
Это означает, что один и тот же запрос будет выполнен дважды, удваивая нагрузку на базу данных. Также, есть вероятность, что списки могут содержать разные результаты, потому что запись Entry может быть добавлена или удалена в доли секунды между запросами.
Что бы избежать этой проблемы, просто сохраните QuerySet и используйте его повторно:
>>> queryset = Entry.objects.all() >>> print [p.headline for p in queryset] # Evaluate the query set. >>> print [p.pub_date for p in queryset] # Re-use the cache from the evaluation.
Сложные запросы с помощью объектов Q
Именованные аргументы функции filter() и др. – объединяются оператором “AND”. Если вам нужны более сложные запросы (например, запросы с оператором OR), вы можете использовать объекты Q.
Объект Q`) – объект, используемый для инкапсуляции множества именованных аргументов для фильтрации. Аргументы определяются так же как и в примерах выше.
Например, этот объект Q определяет запрос LIKE:
from django.db.models import Q Q(question__startswith='What')
Объекты Q могут быть объединены операторами & и |, при этом будет создан новый объект Q.
Например, это определение представляет объект Q, который представляет операцию “OR” двух фильтров с «question__startswith»:
Q(question__startswith='Who') | Q(question__startswith='What')
Этот фильтр равнозначен такому оператору SQL WHERE:
WHERE question LIKE 'Who%' OR question LIKE 'What%'
Вы можете комбинировать различные объекты Q с операторами & и | и использовать скобки. Можно использовать оператор ~ для отрицания(NOT) в запросе:
Q(question__startswith='Who') | ~Q(pub_date__year=2005)
Каждый метод для фильтрации, который принимает именованные аргументы (например, filter(), exclude(), get()get()) так же может принимать объекты Q. Если вы передадите несколько объектов Q как аргументы, они будут объединены оператором “AND”. Например:
Poll.objects.get( Q(question__startswith='Who'), Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)) )
… примерно переводится в SQL:
SELECT * from polls WHERE question LIKE 'Who%' AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
Вы можете использовать одновременно объекты Q и именованные аргументы. Все аргументы(будь то именованные аргументы или объекты Q) объединяются оператором “AND”. Однако, если присутствует объект Q, он должен следовать перед именованными аргументами. Например:
Poll.objects.get( Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)), question__startswith='Who')
… правильный запрос, идентичный предыдущему примеру; но:
# INVALID QUERY Poll.objects.get( question__startswith='Who', Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)))
… будет не правильный`(Вообще Django здесь не причем. Синтаксис Python не позволяет передавать именованные аргументы перед позиционными – прим. переводчика)`.
Удаление объектов
Метод удаления соответственно называется delete(). Этот метод сразу удаляет объект и ничего не возвращает. Например:
Можно так же удалить несколько объектов сразу. Каждый QuerySet имеет метод delete(), который удаляет все объекты из QuerySet.
Например, этот код удаляет все объекты Entry у годом pub_date равным 2005:
Entry.objects.filter(pub_date__year=2005).delete()
Учтите, что при любой возможности будет использован непосредственно SQL запрос, то есть метод delete() объекта может и не использоваться при удалении. Если вы переопределяете метод delete() модели и хотите быть уверенным, что он будет вызван, вы должны “самостоятельно” удалить объект модели (например, использовать цикл по QuerySet и вызывать метод delete() для каждого объекта) не используя метод delete() QuerySet.
При удалении Django повторяет поведение SQL выражения ON DELETE CASCADE – другими словами, каждый объект, имеющий связь(ForeignKey) с удаляемым объектом, будет так же удален. Например:
b = Blog.objects.get(pk=1) # This will delete the Blog and all of its Entry objects. b.delete()
Метод delete() содержится только в QuerySet и не существует в Manager. Это сделано, что бы вы случайно не выполнили Entry.objects.delete(), и не удалили все записи. Если вы на самом деле хотите удалить все объекты, сначала явно получите QuerySet содержащий все записи:
Entry.objects.all().delete()
Изменение нескольких объектов
Если вам понадобиться установить значение поля для всех объектов в QuerySet, используйте метод update(). Например:
# Update all the headlines with pub_date in 2007. Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')
Вы можете изменить только обычные поля или ForeignKey используя этот метод. Для обычным полей просто определите новое значение как константу. Что бы обновить ForeignKey, укажите объект связанной модели. Например:
>>> b = Blog.objects.get(pk=1) # Change every Entry so that it belongs to this Blog. >>> Entry.objects.all().update(blog=b)
Метод update() применяется мгновенно и возвращает количество измененных запросом объектов. Единственное ограничение для изменяемого QuerySet – он может изменять только главную таблицу модели. Вы можете использовать фильтры по связанным полям, но вы можете изменять поля только таблицы изменяемой модели. Например:
>>> b = Blog.objects.get(pk=1) # Update all the headlines belonging to this Blog. >>> Entry.objects.select_related().filter(blog=b).update(headline='Everything is the same')
Учтите, что метод update() использует непосредственно SQL запрос. Это операция для массового изменения. Метод save() модели не будет вызван, сигналы pre_save или post_save не будут вызваны (которые являются следствием вызова save()). Если вы хотите сохранить каждый объект в QuerySet и удостовериться что метод save() вызван для каждого объекта, вы не должны использовать какой-либо специальный метод. Просто используйте цикл и вызовите метод save():
for item in my_queryset: item.save()
Метод update() может использовать объект F() для обновления одного поля значением другого поля модели. Это особенно полезно для изменения счетчика. Например, например увеличить значение n_pingbacks на один для каждой записи:
>>> Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)
Однако, в отличии от использования объектов F() в методах filter() и exclude(), вы не можете использовать связанные поля при обновлении. Если вы будете использовать связанное поле в объекте F(), буде вызвано исключение FieldError:
# THIS WILL RAISE A FieldError >>> Entry.objects.update(headline=F('blog__name'))
Связанные объекты
Используя связанные объекты в модели (например, ForeignKey, OneToOneField или ManyToManyField), в объект модели будет добавлен API для работы со связанными объектами.
Используя модели из примеров выше, например, объект e модели Entry может получить связанные объекты Blog используя атрибут blog: e.blog.
(Это все работает благодаря дескрипторам Python. Это совсем не важно и упоминается для любознательных.)
Django так же предоставляет доступ к связанным объектам с “другой” стороны – ссылка с связанного объекта на объект, который определяет связь. Например, объект b модели Blog имеет доступ ко всем связанным объектам Entry через атрибут entry_set: b.entry_set.all().
Все примеры в этом разделе используют вышеупомянутые модели Blog, Author и Entry.
Связь один-к-одному
Прямая
Если модель содержит ForeignKey, объект этой модели может получить связанный объект через атрибут модели.
Например:
>>> e = Entry.objects.get(id=2) >>> e.blog # Returns the related Blog object.
Вы можете получить и изменить его через атрибут внешнего ключа. Как вы можете предполагать, изменения не будут сохранены в базу данных пока не будет вызван метод meth:~django.db.models.Model.save. Например:
>>> e = Entry.objects.get(id=2) >>> e.blog = some_blog >>> e.save()
Если поле ForeignKey содержит null=True (то есть разрешено значение NULL), вы можете указать None для этого поля. Например:
>>> e = Entry.objects.get(id=2) >>> e.blog = None >>> e.save() # "UPDATE blog_entry SET blog_id = NULL ...;"
Прямой доступ для связи один-ко-многим кеширует полученное значение при первом обращении. Последующие обращения к внешнему ключу этого же объекта будут использовать кешированое значение. Например:
>>> e = Entry.objects.get(id=2) >>> print e.blog # Hits the database to retrieve the associated Blog. >>> print e.blog # Doesn't hit the database; uses cached version.
Запомните, что вызов метода select_related() QuerySet рекурсивно заполняет кеш значениями для всех связей один-ко-многим. Например:
>>> e = Entry.objects.select_related().get(id=2) >>> print e.blog # Doesn't hit the database; uses cached version. >>> print e.blog # Doesn't hit the database; uses cached version.
“Обратная” связь
Если модель содержит Manager, который вернет все связанные объекты первой модели. По-умолчанию, этот Manager называется FOO_set, где FOO названием основной модели в нижнем регистре. Этот Manager возвращает QuerySets, который может быть отфильтрован и изменен как было описано в разделе “Получение объектов”.
Например:
>>> b = Blog.objects.get(id=1) >>> b.entry_set.all() # Returns all Entry objects related to Blog. # b.entry_set is a Manager that returns QuerySets. >>> b.entry_set.filter(headline__contains='Lennon') >>> b.entry_set.count()
Вы можете переопределить название FOO_set установив параметр related_name при определении ForeignKey(). Например, если бы модель Entry содержала blog = ForeignKey(Blog, related_name=’entries’), пример выше выглядел бы как:
>>> b = Blog.objects.get(id=1) >>> b.entries.all() # Returns all Entry objects related to Blog. # b.entries is a Manager that returns QuerySets. >>> b.entries.filter(headline__contains='Lennon') >>> b.entries.count()
Вы не можете обращаться к “обратному” менеджеру через класс модели; вы должны использовать экземпляр модели:
>>> Blog.entry_set Traceback: ... AttributeError: "Manager must be accessed via instance".
В дополнение к методам QuerySet описанным в разделе “Получение объектов”, “обратный” менеджер ForeignKey содержит дополнительные методы для работы со связанными объектами. Краткий список находится ниже, полное описание смотрите в разделе о связанных объектах.
- add(obj1, obj2, …)
Добавляет указанный объект к связанным объектам.
- create(**kwargs)
Создает новый объект, сохраняет его и добавляет к связанным объектам. Возвращает созданный объект.
- remove(obj1, obj2, …)
Удаляет указанный объект из списка связанных объектов.
- clear()
Удаляет все объекты из списка связанных.
Что бы добавить несколько связанных объектов одним махом, просто установите любой итератор. Итератор должен содержать объекты или просто значения первичных ключей. Например:
b = Blog.objects.get(id=1) b.entry_set = [e1, e2]
В этом примере, e1 и e2 могут быть экземплярами Entry, или значениями первичного ключа.
Если доступен метод clear(), все связанные объекты будут удаленны из entry_set перед тем, как все объекты из итератора(в примере это список) будут добавлены в список связанных объектов. Если метод clear() не доступен, все объекты из итератора будут добавлены без удаления существующих объектов.
Все вышеупомянутые методы сохраняют результат в базу. Каждое добавление, создание или удаление сразу и автоматически выполняет соответствующий запрос в базу данных.
Связь много-ко-многим
Обе “стороны” связи многое-ко-многим автоматически получает API для работы со связанными объектами. Этот API работает так же, как и “обратный” менеджер для связи один-ко-многим описанный выше.
Единственное отличие: Модель содержащая ManyToManyField использует имя атрибута этого поля, в то время, как “обратная” модель использует название состоящее из названия модели в нижнем регистре плюс ‘_set’ (так же как и для связи один-ко-многим).
Пример все разъяснит:
e = Entry.objects.get(id=3) e.authors.all() # Returns all Author objects for this Entry. e.authors.count() e.authors.filter(name__contains='John') a = Author.objects.get(id=5) a.entry_set.all() # Returns all Entry objects for this Author.
Так же как и ForeignKey, ManyToManyField позволяет определить related_name. В примере выше, если поле ManyToManyField модели Entry содержит related_name=’entries’, тогда каждый объект модели Author будет с атрибутом entries вместо entry_set.
Связь один-к-одному
Связь один-к-одному похожа на связь многое-к-одному. При добавлении OneToOneField в модель, объект этой модели будет содержать ссылку на связанный объект через атрибут модели.
Например:
class EntryDetail(models.Model): entry = models.OneToOneField(Entry) details = models.TextField() ed = EntryDetail.objects.get(id=2) ed.entry # Returns the related Entry object.
Разница в обратной связи. Связанная модель так же имеет доступ к объекту Manager, но Manager представляет один объект, а не множество объектов:
e = Entry.objects.get(id=2) e.entrydetail # returns the related EntryDetail object
Если ни один объект не добавлен в связь, Django вызовет исключение DoesNotExist.
Объект может быть назначен через обратную связь так же как и через прямую:
Как работает обратная связь?
Другие ORM требуют определять связь с обоих сторон. Разработчики Django считают, что это противоречит принципу DRY (Don’t Repeat Yourself — не повторяй себя), по этому Django требует определить связь только для одной модели.
Но как это возможно, учитывая, что модель не знает, какие другие модели связаны с ним, пока классы этих моделей не будут загружены?
Суть ответа в настройке INSTALLED_APPS. При загрузке любой модели в первый раз, Django перебирает все модели из приложений в INSTALLED_APPS и создает обратные связи для каждой модели.По сути, это одна из функций INSTALLED_APPS – определить все используемые модели для Django.
Django ORM для начинающих | Оптимизируем запросы / Хабр
Django ORM (Object Relational Mapping) является одной из самых мощных особенностей Django. Это позволяет нам взаимодействовать с базой данных, используя код Python, а не SQL.
Based on schegel.net
Для демонстрации опишу такую модель:
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=250)
url = models.URLField()
def __str__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=250)
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=250)
content = models.TextField()
published = models.BooleanField(default=True)
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
authors = models.ManyToManyField(Author, related_name="posts")
Я буду использовать django-extentions, чтобы получить полезную информацию с помощью:
python manage.py shell_plus --print-sql
И так начнем:
>>> post = Post.objects.all()
>>> post
SELECT "blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."blog_id"
FROM "blog_post"
LIMIT 21
Execution time: 0.000172s [Database: default]
<QuerySet [<Post: Post object (1)>]>
1. Используем ForeignKey значения непосредственно
>>> Post.objects.first().blog.id
SELECT "blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."blog_id"
FROM "blog_post"
ORDER BY "blog_post"."id" ASC
LIMIT 1
Execution time: 0.000225s [Database: default]
SELECT "blog_blog"."id",
"blog_blog"."name",
"blog_blog"."url"
FROM "blog_blog"
WHERE "blog_blog"."id" = 1
LIMIT 21
Execution time: 0.000144s [Database: default]
1
А так получаем 1 запрос в БД:
>>> Post.objects.first().blog_id
SELECT "blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."blog_id"
FROM "blog_post"
ORDER BY "blog_post"."id" ASC
LIMIT 1
Execution time: 0.000155s [Database: default]
1
2. OneToMany Relations
Если мы используем OneToMany отношения мы используем ForeignKey поля и запрос выглядит примерно так:
>>> post = Post.objects.get(id=1)
SELECT "blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."blog_id"
FROM "blog_post"
WHERE "blog_post"."id" = 1
LIMIT 21
Execution time: 0.000161s [Database: default]
И если мы хотим получить доступ к объекту блога из объекта поста, мы можем сделать:
>>> post.blog
SELECT "blog_blog"."id",
"blog_blog"."name",
"blog_blog"."url"
FROM "blog_blog"
WHERE "blog_blog"."id" = 1
LIMIT 21
Execution time: 0.000211s [Database: default]
<Blog: Django tutorials>
Тем не менее, это вызвало новый запрос, чтобы получить информацию из блога. Так что используйте select_related, чтобы избежать этого. Чтобы использовать его, мы можем обновить наш оригинальный запрос:
>>> post = Post.objects.select_related("blog").get(id=1)
SELECT "blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."blog_id",
"blog_blog"."id",
"blog_blog"."name",
"blog_blog"."url"
FROM "blog_post"
INNER JOIN "blog_blog"
ON ("blog_post"."blog_id" = "blog_blog"."id")
WHERE "blog_post"."id" = 1
LIMIT 21
Execution time: 0.000159s [Database: default]
Обратите внимание, что Django использует JOIN сейчас! И время выполнения запроса меньше, чем раньше. Кроме того, теперь post.blog будет кэширован!
>>> post.blog
<Blog: Django tutorials>
select_related так же работает с QurySets:
>>> posts = Post.objects.select_related("blog").all()
>>> for post in posts:
... post.blog
...
SELECT "blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."blog_id",
"blog_blog"."id",
"blog_blog"."name",
"blog_blog"."url"
FROM "blog_post"
INNER JOIN "blog_blog"
ON ("blog_post"."blog_id" = "blog_blog"."id")
Execution time: 0.000241s [Database: default]
<Blog: Django tutorials>
3. ManyToMany Relations
Чтобы получить авторов постов мы используем что-то вроде этого:
>>> for post in Post.objects.all():
... post.authors.all()
...
SELECT "blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."blog_id"
FROM "blog_post"
Execution time: 0.000242s [Database: default]
SELECT "blog_author"."id",
"blog_author"."name"
FROM "blog_author"
INNER JOIN "blog_post_authors"
ON ("blog_author"."id" = "blog_post_authors"."author_id")
WHERE "blog_post_authors"."post_id" = 1
LIMIT 21
Execution time: 0.000125s [Database: default]
<QuerySet [<Author: Dmytro Parfeniuk>, <Author: Will Vincent>, <Author: Guido van Rossum>]>
SELECT "blog_author"."id",
"blog_author"."name"
FROM "blog_author"
INNER JOIN "blog_post_authors"
ON ("blog_author"."id" = "blog_post_authors"."author_id")
WHERE "blog_post_authors"."post_id" = 2
LIMIT 21
Execution time: 0.000109s [Database: default]
<QuerySet [<Author: Dmytro Parfeniuk>, <Author: Will Vincent>]>
Похоже, мы получили запрос для каждого объекта поста. По этому, мы должны использовать prefetch_related. Это похоже на select_related но используется с ManyToMany Fields:
>>> for post in Post.objects.prefetch_related("authors").all():
... post.authors.all()
...
SELECT "blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."blog_id"
FROM "blog_post"
Execution time: 0.000300s [Database: default]
SELECT ("blog_post_authors"."post_id") AS "_prefetch_related_val_post_id",
"blog_author"."id",
"blog_author"."name"
FROM "blog_author"
INNER JOIN "blog_post_authors"
ON ("blog_author"."id" = "blog_post_authors"."author_id")
WHERE "blog_post_authors"."post_id" IN (1, 2)
Execution time: 0.000379s [Database: default]
<QuerySet [<Author: Dmytro Parfeniuk>, <Author: Will Vincent>, <Author: Guido van Rossum>]>
<QuerySet [<Author: Dmytro Parfeniuk>, <Author: Will Vincent>]>
Что только что произошло??? Мы сократили количество запросов с 2 до 1, чтобы получить 2 QuerySet-a!
4. Prefetch object
prefetch_related достаточно для большинства случаев, но это не всегда помогает избежать дополнительных запросовю К примеру, если мы используем фильтрацию Django не может использовать наши кэшированные posts, так как они не были отфильтрованы, когда они были запрошены в первом запросе. И мы получим:
>>> authors = Author.objects.prefetch_related("posts").all()
>>> for author in authors:
... print(author.posts.filter(published=True))
...
SELECT "blog_author"."id",
"blog_author"."name"
FROM "blog_author"
Execution time: 0.000580s [Database: default]
SELECT ("blog_post_authors"."author_id") AS "_prefetch_related_val_author_id",
"blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."published",
"blog_post"."blog_id"
FROM "blog_post"
INNER JOIN "blog_post_authors"
ON ("blog_post"."id" = "blog_post_authors"."post_id")
WHERE "blog_post_authors"."author_id" IN (1, 2, 3)
Execution time: 0.000759s [Database: default]
SELECT "blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."published",
"blog_post"."blog_id"
FROM "blog_post"
INNER JOIN "blog_post_authors"
ON ("blog_post"."id" = "blog_post_authors"."post_id")
WHERE ("blog_post_authors"."author_id" = 1 AND "blog_post"."published" = 1)
LIMIT 21
Execution time: 0.000299s [Database: default]
<QuerySet [<Post: Post object (1)>, <Post: Post object (2)>]>
SELECT "blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."published",
"blog_post"."blog_id"
FROM "blog_post"
INNER JOIN "blog_post_authors"
ON ("blog_post"."id" = "blog_post_authors"."post_id")
WHERE ("blog_post_authors"."author_id" = 2 AND "blog_post"."published" = 1)
LIMIT 21
Execution time: 0.000336s [Database: default]
<QuerySet [<Post: Post object (1)>, <Post: Post object (2)>]>
SELECT "blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."published",
"blog_post"."blog_id"
FROM "blog_post"
INNER JOIN "blog_post_authors"
ON ("blog_post"."id" = "blog_post_authors"."post_id")
WHERE ("blog_post_authors"."author_id" = 3 AND "blog_post"."published" = 1)
LIMIT 21
Execution time: 0.000412s [Database: default]
<QuerySet [<Post: Post object (1)>]>
То есть, мы использовали prefetch_related, чтобы уменьшить количество запросов, но мы фактически увеличили его. Чтобы этого избежать, мы можем настроить запрос с помощью объекта Prefetch:
>>> authors = Author.objects.prefetch_related(
... Prefetch(
... "posts",
... queryset=Post.objects.filter(published=True),
... to_attr="published_posts",
... )
... )
>>> for author in authors:
... print(author.published_posts)
...
SELECT "blog_author"."id",
"blog_author"."name"
FROM "blog_author"
Execution time: 0.000183s [Database: default]
SELECT ("blog_post_authors"."author_id") AS "_prefetch_related_val_author_id",
"blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."published",
"blog_post"."blog_id"
FROM "blog_post"
INNER JOIN "blog_post_authors"
ON ("blog_post"."id" = "blog_post_authors"."post_id")
WHERE ("blog_post"."published" = 1 AND "blog_post_authors"."author_id" IN (1, 2, 3))
Execution time: 0.000404s [Database: default]
[<Post: Post object (1)>, <Post: Post object (2)>]
[<Post: Post object (1)>, <Post: Post object (2)>]
[<Post: Post object (1)>]
Мы использовали определенный запрос для получения постов через параметр запроса и сохранили отфильтрованные сообщения в новом атрибуте. Как мы видим, теперь у нас есть только 2 запроса в базу данных.
python — фильтр объектов Django в шаблоне
Переполнение стека
- Около
Продукты
- Для команд
Переполнение стека
Общественные вопросы и ответыПереполнение стека для команд
Где разработчики и технологи делятся частными знаниями с коллегамиВакансии
Программирование и связанные с ним технические возможности карьерного ростаТалант
Нанимайте технических специалистов и создавайте свой бренд работодателяРеклама
Обратитесь к разработчикам и технологам со всего мира- О компании
Загрузка…
.
Фильтрация — Django REST framework
GitHub
Следующие
Предыдущая
Поиск
Фреймворк Django REST
- Главная
- Учебник
- Быстрый старт
- 1 — Сериализация
- 2 — Запросы и ответы
- 3 — Просмотры на основе классов
- 4 — Аутентификация и разрешения
- 5 — Отношения и гиперссылки API
- 6 — Viewsets и маршрутизаторы
- Руководство API
- Запросы
- Ответы
- Просмотры
- Общие представления
- Наборы просмотров
- Маршрутизаторы
- Парсеры
- Рендереры
- Сериализаторы
- Поля сериализатора
- Отношения сериализатора
- Валидаторы
- Аутентификация
- Разрешения
- Кеширование
- Дросселирование
- Фильтрация
- Пагинация
- Управление версиями
- Согласование содержания
- Метаданные
- Схемы
- Суффиксы формата
- Возврат URL-адресов
- Исключения
- Коды состояния
- Тестирование
- Настройки
- Темы
- Документирование вашего API
- Клиенты API
- Интернационализация
- AJAX, CSRF и CORS
- HTML и формы
- Улучшения браузера
- Доступный для просмотра API
- ОТДЫХ, гипермедиа и ненависть
- Сообщество
- Учебники и ресурсы
- Сторонние пакеты
- Участие в REST framework
- Управление проектом
- Примечания к выпуску
- 3.12 Объявление
- 3.11 Объявление
- 3.10 Объявление
- 3.9 Объявление
- 3.8 Объявление
- 3.7 Объявление
- 3.6 Объявление
- 3.5 Объявление
- 3.4 Объявление
- 3.3 Объявление
- 3.2 Объявление
- 3.1 Объявление
- 3.0 Объявление
- Объявление на Kickstarter
- Mozilla Grant
- Финансирование
- Вакансии
×
Поиск по документации
Закрыть
- Фильтрация
- Фильтрация против текущего пользователя
- Фильтрация по URL
- Фильтрация по параметрам запроса
- Общая фильтрация
- Настройка бэкэндов фильтра
- Фильтрация и поиск объектов
- Переопределение исходного набора запросов
- Руководство по API
- DjangoFilterBackend
- SearchFilter
- ЗаказФильтр
- Пользовательская универсальная фильтрация
- пример
- Настройка интерфейса
- Разбиение на страницы и схемы
- Сторонние пакеты
- Пакет фильтров Django REST framework
- Фильтр поиска по полному слову Django REST framework
- Фильтр URL-адресов Django
.