Предыдущая часть.
Знать как создавать и обновлять данные в базе данных необходимо. Однако, скорее всего ваше веб-приложение будет выполнять больше запросов на получение данных из базы, чем на добавление их. Мы уже встречали пример того, как получить все записи для определённой модели:
In [12]: Publisher.objects.all() Out[12]: [<Publisher: Apress>, <Publisher: O'Reilly>, <Publisher: Apress Publishing>]
В SQL это выглядело примерно так:
SELECT id, name, address, city, state_province, country, website FROM books_publisher;
Примечание
Обратите внимание, что Django не выполняет запрос вида SELECT *
во время поиска, а перечисляет все поля явно. Это сделано намеренно — SELECT *
иногда может работать медленнее, и (что более важно) — перечисление всех полей более соответствует одному из догматов Python: «Явное лучше, чем неявное«.
Что бы больше узнать о догмах Python — введите import this
в командной строке Python.
Давайте рассмотрим подробнее каждую часть из строки Publisher.objects.all()
:
-
Первым идёт имя модели, которую мы определили —
Publisher
. Ничего удивительного — когда мы хотим получить определённые данные, мы используем модель для этих данных. -
Далее идёт атрибут
objects
. Он называется менеджер (manager). Менеджеры будет рассмотрены более подробно далее в нашей книге. Пока что — всё, что вам стоит знать, это то что менеджер занимается всеми данными и операциями уровня «таблица».Все модели автоматически получают свой объект-менеджер, и вы будете пользоваться им каждый раз, когда вы занимаетесь объектами моделей.
-
Последним идёт
all()
. Это метод менеджераobject
, который возвращает все строки в таблице базы. Хотя объект выглядит как список — на самом деле это Набор Запросов (QuerySet) — объект, который представляет специальный набор строк таблицы. В Приложении СQuerySet
рассматривается более подробно. В этой главе мы будем их рассматривать как обычные списки.
Каждый поиск в базе будет соответствовать этому шаблону — мы вызываем метод менеджера, который связан с моделью, к которой нам необходимо применить запрос.
Содержание
Фильтрация данных
На самом деле — вам очень редко требуется получить все данные из базы. Как правило — вам требуется какая-то определённая выборка из этих данных. С помощью Django API вы можете отфильтровать данные с помощью метода filter()
:
In [14]: Publisher.objects.filter(name='Apress') Out[14]: [<Publisher: Apress>]
Аргумент, переданный filter()
, транслируется в соответствующий запрос SQL WHERE
. Предыдущий пример можно представить в виде SQL примерно так:
SELECT id, name, address, city, state_province, country, website FROM books_publisher WHERE name = 'Apress';
Вы можете передавать несколько аргументов, что бы уточнить выборку фильтра:
In [15]: Publisher.objects.filter(country="U.S.A.", state_province="CA") Out[15]: [<Publisher: Apress>, <Publisher: Apress Publishing>]
Несколько аргументов будут сформированы в SQL запрос с помощью AND
:
SELECT id, name, address, city, state_province, country, website FROM books_publisher WHERE country = 'U.S.A.' AND state_province = 'CA';
Обратите внимание, что по умолчанию в запросах используется SQL оператор «=». Можно выполнить так:
In [16]: Publisher.objects.filter(name__contains="press") Out[16]: [<Publisher: Apress>, <Publisher: Apress Publishing>]
Двойной символ подчёркивания между name
и contains
, как в Python, используется Django для обозначения чего-то «магического» — в данном случае, __contains
указывает на использование оператор LIKE
в SQL-запросе:
SELECT id, name, address, city, state_province, country, website FROM books_publisher WHERE name LIKE '%press%';
Имеется большое количество подобных типов запросов, такие как icontains
(регистро-зависимый LIKE
), startswith
и endswith
, range
(в SQL — BETWEEN
). Все они описываются в Приложении С.
Получение отдельного объекта
В примерах с filter()
практически всё, что он возвращает — это набор объектов, которые могут быть обработаны как Python-список. Однако, иногда лучше получить отдельный объект, а не целый список. Для этого в Django имеется метод get()
:
In [17]: Publisher.objects.get(name="Apress") Out[17]: <Publisher: Apress>
Вместо списка (или QuerySet) — вы получаете отдельный объект. Поэтому вызов, результатом которого будет несколько объектов вызовет ошибку:
In [18]: Publisher.objects.get(country="U.S.A.") --------------------------------------------------------------------------- MultipleObjectsReturned Traceback (most recent call last) ... MultipleObjectsReturned: get() returned more than one Publisher -- it returned 3! Lookup parameters were {'country': 'U.S.A.'}
Вызов, который не возвращает ни одного объекта — так же вызове ошибку:
In [19]: Publisher.objects.get(name="Penguin") --------------------------------------------------------------------------- DoesNotExist Traceback (most recent call last) ... DoesNotExist: Publisher matching query does not exist.
Исключение DoesNotExist
является атрибутом класса модели — Publisher.DoesNotExist
. В вашем приложении вы можете перехватить такие исключения, например так:
In [20]: try: ....: p = Publisher.objects.get(name='Apress') ....: except Publisher.DoesNotExist: ....: print "Apress isn't in the database yet." ....: else: ....: print "Apress is in the database." ....: Apress is in the database.
Сортировка данных
Как вы могли заметить в предыдущих примерах — объекты возвращаются в произвольном порядке. В самом деле — мы пока не указывали в запросах как мы хотим отсортировать результат, мы просто получали в том виде, в каком их выдавала база данных.
В ваших Django приложениях вы скорее всего захотите выполнять какую-то сортировку, например — в алфавитном порядке. Что бы сделать это — используйте метод order()
:
In [21]: Publisher.objects.order_by("name") Out[21]: [<Publisher: Apress>, <Publisher: Apress Publishing>, <Publisher: O'Reilly>]
Особой разницы с результатами all()
не видно, но теперь наш SQL запрос включается в себя сортировку:
SELECT id, name, address, city, state_province, country, website FROM books_publisher ORDER BY name;
Вы можете выполнять сортировку по любому полю:
In [22]: Publisher.objects.order_by("address") Out[22]: [<Publisher: O'Reilly>, <Publisher: Apress Publishing>, <Publisher: Apress>]
In [23]: Publisher.objects.order_by("state_province") Out[23]: [<Publisher: Apress>, <Publisher: Apress Publishing>, <Publisher: O'Reilly>]
Для сортировки по нескольким полям, в которой второе поле будет уточнять результат первого, используйте несколько аргументов к order_by()
:
In [24]: Publisher.objects.order_by("state_province", "address") Out[24]: [<Publisher: Apress Publishing>, <Publisher: Apress>, <Publisher: O'Reilly>]
Вы можете использовать обратную (reverse
) сортировку, добавив символ «-
«:
In [25]: Publisher.objects.order_by("-name") Out[25]: [<Publisher: O'Reilly>, <Publisher: Apress Publishing>, <Publisher: Apress>] In [26]: Publisher.objects.order_by("name") Out[26]: [<Publisher: Apress>, <Publisher: Apress Publishing>, <Publisher: O'Reilly>]
Хотя такая возможность является очень полезной — использовать её каждый раз может быть неразумным решением. В большинстве случаев вы заранее знаете поля, по которым вы хотели бы выполнять сортировку. В таком случае — вы можете указать сортировку по умолчанию в описании модели Django:
class Publisher(models.Model): name = models.CharField(max_length=30) address = models.CharField(max_length=50) city = models.CharField(max_length=60) state_province = models.CharField(max_length=30) country = models.CharField(max_length=50) website = models.URLField() def __unicode__(self): return self.name class Meta: ordering = ['name']
Тут вы встречаетесь с новой концепцией — класс Meta
, который является вложенным классом в описании класса Publisher
. Вы можете использовать класс Meta
в любой модели, что бы какие-то специфичные опции для модели. Полное описание опций Meta
есть в Приложении B, а в данный момент нас интересует опция ordering
. При её использовании — Django будет использовать сортировку по полю name
, пока вы явно не укажете другое с помощью опции order_by()
.
Изменение поиска
Вы уже видели как выполнить фильтрацию данных, и как выполнить их сортировку. Часто, конечно, нужно сделать их вместе. В таком случае — вы можете создать «цепочку» из правил, например:
In [27]: Publisher.objects.filter(country="U.S.A.").order_by("-name") Out[27]: [<Publisher: O'Reilly>, <Publisher: Apress Publishing>, <Publisher: Apress>]
Как вы догадываетесь — в SQL запросе будет применяться и WHERE
и ORDER BY
:
SELECT id, name, address, city, state_province, country, website FROM books_publisher WHERE country = 'U.S.A' ORDER BY name DESC;
«Срезы» данных
Часто так же необходимо получить только определённое количество строк. Допустим, у вас есть тысяча издателей в базе данных, но вам необходимо получить только первого. Вы можете выполнить это с помощью обычного в Python способа «срезов» по индексам (slicing):
In [28]: Publisher.objects.order_by('name')[0] Out[28]: <Publisher: Apress>
В виде SQL-запроса это будет выглядеть примерно так:
SELECT id, name, address, city, state_province, country, website FROM books_publisher ORDER BY name LIMIT 1;
Также вы можете получить определённый набор данных, используя срез по диапазону индексов:
In [29]: Publisher.objects.order_by('name')[0:2] Out[29]: [<Publisher: Apress>, <Publisher: Apress Publishing>]
Что в виде SQL будет выглядеть так:
SELECT id, name, address, city, state_province, country, website FROM books_publisher ORDER BY name OFFSET 0 LIMIT 2;
Помните, что отрицательные срезы не поддерживаются:
In [30]: Publisher.objects.order_by('name')[-1] --------------------------------------------------------------------------- AssertionError Traceback (most recent call last) ... AssertionError: Negative indexing is not supported.
Но это можно выполнить с помощью order_by()
и обратной сортировкой, например:
In [31]: Publisher.objects.order_by('-name')[0] Out[31]: <Publisher: O'Reilly>
Обновление нескольких объектов одним запросом
В статье Django Book: добавление и обновление данных мы уже упоминали, что метод модели save()
обновляет все колонки в строке. В зависимости от вашего приложения — вы, возможно, захотите обновить только некоторые из этих колонок.
Например, мы хотим обновить издателя Apress, и изменить имя с ‘Apress
‘ на ‘Apress Publishing
‘. Используя метод save()
это выглядело бы так:
>>> p = Publisher.objects.get(name='Apress') >>> p.name = 'Apress Publishing' >>> p.save()
А в виде SQL-запроса — примерно так:
SELECT id, name, address, city, state_province, country, website FROM books_publisher WHERE name = 'Apress'; UPDATE books_publisher SET name = 'Apress Publishing', address = '2855 Telegraph Ave.', city = 'Berkeley', state_province = 'CA', country = 'U.S.A.', website = 'http://www.apress.com' WHERE id = 52;
(в этом примере у издателя Apress его ID
— 52
)
В этом примере вы можете увидеть, что save()
задаёт все значения всех колонок, а не только колонку name
. Если в вашей базе данных остальные колонки могут меняться из-за действий других процессов — то было бы логичнее изменить только ту колонку, которая вам действительно необходима. Что бы реализовать это — используйте метод update()
. Например:
In [32]: Publisher.objects.filter(id=52).update(name='Apress Publishing') Out[32]: 0
В SQL этот способ будет выглядеть более эффективным и не затронет остальные поля:
UPDATE books_publisher SET name = 'Apress Publishing' WHERE id = 52;
Метод update()
применим к любому набору запросов, т.е. — вы можете изменять любые записи в наборе данных. Например, вот как вы можете изменить колонку с ‘U.S.A.
‘ на 'USA'
для всех издателей:
In [33]: Publisher.objects.all().update(country='USA') Out[33]: 3L
Метод update()
возвращает значение — целое число, указывающее сколько записей было изменено. В данном случае — изменение коснулось 3-х записей.
Продолжение — Django Book: удаление объектов