Django Book: выборка объектов

Автор: | 08/03/2015
 

django_logo_2Предыдущая часть.

Знать как создавать и обновлять данные в базе данных необходимо. Однако, скорее всего ваше веб-приложение будет выполнять больше запросов на получение данных из базы, чем на добавление их. Мы уже встречали пример того, как получить все записи для определённой модели:

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 и endswithrangeSQLBETWEEN). Все они описываются в Приложении С.

Получение отдельного объекта

В примерах с 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 его ID52)

В этом примере вы можете увидеть, что 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: удаление объектов