Django_-_podrobnoe_rukovodstvo
.pdf
420 |
Приложение A. Справочник по моделям |
Если вы хотите изменить поведение класса модели на уровне Python, то можете задать атрибут managed=False и создать копию существующей модели. Но лучше в такой ситуации воспользоваться прокси-моделями.
Параметр ordering
Подразумеваемый по умолчанию порядок сортировки при получении списков объектов:
ordering = [‘-order_date’]
Значением должен быть кортеж или список строк. Каждая строка содержит имя поля с необязательным префиксом -, который означает сортировку по убыванию. Если префикс отсутствует, объекты сортируются по возрастанию. Для сортировки в случайном порядке задайте строку ?.
Примечание
Сколько бы полей ни было задано в параметре ordering, в административном интерфейсе используется только первое.
Например, для сортировки по полю pub_date в порядке возрастания задайте:
ordering = [‘pub_date’]
Адля сортировки по тому же полю в порядке убывания: ordering = [‘-pub_date’]
Чтобы отсортировать по полю pub_date в порядке убывания, а затем по полю author в порядке возрастания, задайте:
ordering = [‘-pub_date’, ‘author’]
Параметр proxy
Если имеет значение True, то модель, являющаяся подклассом другой модели, будет рассматриваться как прокси-модель. Подробнее о проксимоделях см. документацию по Django.
Параметр unique_together
Набор имен полей, значения которых в совокупности должны быть уникальны:
unique_together = ((“driver”, “restaurant”),)
Это список списков. Используется в формах, созданных с помощью ModelForm (в том числе в административном интерфейсе Django), и проверяется на уровне базы данных (то есть в команду CREATE TABLE включаются соответствующие фразы UNIQUE).
Метаданные модели |
421 |
Для удобства значением параметра unique_together может быть единственная последовательность, если определяется всего один набор полей:
unique_together = (“driver”, “restaurant”)
Параметр verbose_name
Понятное человеку название объекта в единственном числе:
verbose_name = “pizza”
Если не задано, Django будет использовать преобразованное имя класса: CamelCase превратится в camel case.
Параметр verbose_name_plural
Название объекта во множественном числе:
verbose_name_plural = “stories”
Если не задано, Django будет использовать verbose_name + “s”.
B
Справочник по API доступа к базе данных
API доступа к базе данных в Django является дополнением к API моделей, рассмотренному в приложении A. После того как модель определена, для доступа к базе используется именно этот API. Несколько примеров его использования было приведено в основном тексте книги, а сейчас мы углубимся в детали.
API доступа к базе данных, как и API моделей, считается весьма стабильным, тем не менее разработчики Django все время добавляют новые вспомогательные функции и средства. Поэтому рекомендуем почаще заглядывать в актуальную документацию на сайте http://docs. djangoproject.com/. В этом приложении в качестве сквозного примера мы будем использовать следующие модели, которые могли бы лечь в основу простого приложения для ведения блогов:
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100) tagline = models.TextField()
def __unicode__(self): return self.name
class Author(models.Model):
name = models.CharField(max_length=50) email = models.EmailField()
def __unicode__(self): return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog)
headline = models.CharField(max_length=255)
Создание объектов |
423 |
body_text = models.TextField() pub_date = models.DateTimeField()
authors = models.ManyToManyField(Author)
def __unicode__(self): return self.headline
Создание объектов
Для создания объекта в базе данных сначала вызывается конструктор соответствующего класса модели, которому передаются именованные аргументы, а затем метод save(), выполняющий запись объекта в базу:
>>>from mysite.blog.models import Blog
>>>b = Blog(name=’Beatles Blog’, tagline=’All the latest Beatles news.’)
>>>b.save()
За кулисами при этом выполняется SQL-команда INSERT. Django не обращается к базе данных, пока явно не будет вызван метод save(). Этот метод не имеет возвращаемого значения.
О том, как создать и сохранить объект за одну операцию, см. описание метода менеджера create.
Что происходит при сохранении?
При сохранении объекта Django выполняет следующие действия:
1.Посылает сигнал pre_save: тем самым рассылается извещение о том, что объект готов к сохранению. Вы можете зарегистрировать обработчик, который будет вызван в ответ на этот сигнал. Дополнительные сведения о сигналах см. в документации.
2.Осуществляет предварительную обработку данных: система предлагает каждому полю объекта выполнить автоматическую модификацию по своему усмотрению.
Большинство полей ничего не делают на этом этапе – данные сохраняются как есть. Предварительная обработка необходима лишь для полей с особым поведением, например, типа FileField.
3.Подготавливает данные для записи в базу: система предлагает каждому полю представить свое значение в виде, пригодном для записи в базу данных.
Для большинства полей никакой подготовки не нужно. Такие простые типы данных, как целые числа и строки, уже готовы к записи. Но для более сложных типов часто требуется выполнить некоторые преобразования. Например, в полях типа DateField данные хранятся в виде объектов Python datetime. Но в базе данных сохранить объекты типа datetime нельзя, поэтому предварительно требуется преобразовать их в строку в ISO-совместимом формате.
424 |
Приложение B. Справочник по API доступа к базе данных |
4.Вставляет данные в базу: предварительно обработанные и подготовленные данные включаются в SQL-команду INSERT.
5.Посылает сигнал post_save: как и сигнал pre_save, он извещает зарегистрированные обработчики о том, что объект успешно сохранен.
Автоинкрементирование первичных ключей
Для удобства каждая модель снабжается автоинкрементным полем первичного ключа с именем id, если только в каком-нибудь поле явно не определен параметр primary_key=True (см. раздел «Класс AutoField» в приложении A).
Если в модели имеется поле типа AutoField, то автоматически инкрементированное значение сохраняется в атрибуте объекта при первом вызове save():
>>>b2 = Blog(name=’Cheddar Talk’, tagline=’Thoughts on cheese.’)
>>>b2.id # None, так как поле id объекта b2 еще не имеет значения None
>>>b2.save()
>>>b2.id # Числовой идентификатор нового объекта.
14
Невозможно заранее сказать, каким будет значение поля id после сохранения, так как оно вычисляется СУБД, а не Django.
Если в модели имеется поле типа AutoField, но при сохранении вы хотите явно присвоить новому объекту свое значение ID, то запишите его в атрибут id до вызова save(), не полагаясь на автоматическое присваивание:
>>>b3 = Blog(id=3, name=’Cheddar Talk’, tagline=’Thoughts on cheese.’)
>>>b3.id
3
>>>b3.save()
>>>b3.id
3
Однако, решившись на ручное присваивание значения автоинкрементному первичному ключу, побеспокойтесь о разрешении конфликтов с уже существующими значениями! Если новому объекту явно присвоить значение первичного ключа, уже имеющееся в базе, то Django посчитает, что вы хотите изменить существующую запись, а не вставить новую.
Так, следующие предложения модифицируют запись о блоге ‘Cheddar Talk’, созданную в предыдущем примере:
>>>b4 = Blog(id=3, name=’Not Cheddar’, tagline=’Anything but cheese.’)
>>>b4.save() # Затрет прежнюю запись со значением ID=3!
Сохранение измененных объектов |
425 |
Явное определение значения для поля автоинкрементного первичного ключа обычно используется при массовом сохранении множества объектов, когда есть уверенность, что конфликта ключей не возникнет.
Сохранение измененных объектов
Метод save() также используется для сохранения изменений в объекте, который уже присутствует в базе данных.
Если экземпляр b5 класса Blog уже был сохранен в базе данных, то следующие инструкции изменят значение поля name и обновят запись
вбазе данных:
>>>b5.name = ‘New name’
>>>b5.save()
Врезультате будет выполнена SQL-команда UPDATE. В этом случае Django также не обращается к базе данных, пока явно не будет вызван метод
save().
Как Django определяет, когда использовать UPDATE, а когда INSERT
Возможно, вы обратили внимание, что для создания и обновления объектов в базе данных применяется один и тот же метод save(). Django различает необходимость использования SQLкоманды INSERT или команды UPDATE. А именно, при вызове save() Django действует по следующему алгоритму:
•• Если атрибут первичного ключа объекта принимает значение, которое в булевском контексте равно True (то есть любое значение, кроме None и пустой строки), то Django выполняет коман- ду SELECT, проверяя наличие записи с этим значением первичного ключа.
•• Если такая запись уже существует, то Django выполняет ко- манду UPDATE.
•• Если атрибут первичного ключа еще не установлен или записи с таким ключом не существует, то Django выполняет команду INSERT.
Не указывайте явно значение первичного ключа при сохранении новых объектов, если не уверены, что это значение не используется.
Обновление поля типа ForeignKey производится точно так же; достаточно присвоить этому полю объект соответствующего типа:
426 |
Приложение B. Справочник по API доступа к базе данных |
>>>joe = Author.objects.create(name=”Joe”)
>>>entry.author = joe
>>>entry.save()
При попытке присвоить полю значение неподходящего типа Django возбудит исключение.
Выборка объектов
Вы уже не раз видели, как производится выборка объектов из базы:
>>> blogs = Blog.objects.filter(author__name__contains=”Joe”)
За кулисами при этом происходит много интересного. Для выборки объектов используется менеджер модели, который строит объект QuerySet. Этот объект знает, как выполнить SQL-запрос и вернуть требуемые объекты.
В приложении A мы рассматривали классы QuerySet и Manager с точки зрения определения модели, а теперь разберемся, как они работают.
QuerySet представляет набор объектов из базы данных. В нем может быть определен нуль, один или несколько фильтров – критериев, позволяющих сузить набор с учетом заданных параметров. В терминах SQL объект QuerySet эквивалентен команде SELECT, а фильтр – предложению WHERE.
Для получения QuerySet используется менеджер модели – объект класса Manager. У каждой модели имеется хотя бы один менеджер, который по умолчанию называется objects. К нему можно обратиться напрямую с помощью класса модели:
>>> Blog.objects
<django.db.models.manager.Manager object at 0x137d00d>
Менеджеры доступны только через сами классы моделей, а не их экземпляры; тем самым проводится разграничение между операциями на уровне записей и таблиц:
>>>b = Blog(name=’Foo’, tagline=’Bar’)
>>>b.objects
Traceback (most recent call last):
File “<stdin>”, line 1, in <module>
AttributeError: Manager isn’t accessible via Blog instances.
Менеджер – основной источник объектов QuerySet для модели. Он играет роль «корневого» QuerySet, описывающего все объекты в таблице базы данных. Например, Blog.objects – это QuerySet, содержащий все объекты Blog, которые существуют в базе данных.
Объекты QuerySet и кэширование |
427 |
Объекты QuerySet и кэширование
С каждым объектом QuerySet ассоциирован кэш, цель которого – минимизировать количество обращений к базе данных. Для написания эффективного кода важно понимать, как действует этот механизм кэширования.
В только что созданном объекте QuerySet кэш пуст. При первом обращении к QuerySet, то есть в момент обращения к базе данных, Django сохраняет результаты запроса в кэше и возвращает результаты, запрошенные явно (например, следующий элемент, если программа выполняет обход записей в 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 = Poll.objects.all()
print [p.headline for p in queryset] # Получить набор данных.
print [p.pub_date for p in queryset] # Повторно использовать данные из кэша.
Фильтрация объектов
Проще всего выбрать из таблицы все объекты. Для этого служит метод менеджера all():
>>> Entry.objects.all()
Этот метод возвращает объект QuerySet, содержащий все объекты в таблице базы данных.
Но обычно требуется выбрать только подмножество объектов. Для этого необходимо уточнить исходный QuerySet, добавив фильтры с условиями. Как правило, для этого применяются методы filter() и (или) exclude():
>>>y2006 = Entry.objects.filter(pub_date__year=2006)
>>>not2006 = Entry.objects.exclude(pub_date__year=2006)
428 |
Приложение B. Справочник по API доступа к базе данных |
Оба метода принимают в качестве аргументов условия поиска по полю, которые рассматриваются в разделе «Поиск по полям» ниже.
Объединение фильтров
Результатом фильтрации QuerySet является другой объект QuerySet; благодаря этому имеется возможность составлять цепочки фильтров:
>>>qs = Entry.objects.filter(headline__startswith=’What’)
>>>qs = qs.exclude(pub_date__gte=datetime.datetime.now())
>>>qs = qs.filter(pub_date__gte=datetime.datetime(2005, 1, 1))
Здесь мы начинаем с объекта QuerySet, содержащего все записи, затем добавляем фильтр, затем условие исключения и, наконец, еще один фильтр. Результирующий объект QuerySet будет содержать все записи, в которых значение поля заголовка начинается словом «What», опубликованные между 1 января 2005 года и сегодняшней датой.
Подчеркнем, что при создании объектов QuerySet используется механизм отложенного вызова, то есть сам факт создания QuerySet еще не означает обращения к базе данных. Ни одна из показанных выше строк не приводит к обращениям к базе; вы можете день напролет составлять цепочки из фильтров, но Django не станет выполнять запрос до момента обращения к объекту QuerySet.
Обратиться к объекту QuerySet можно несколькими способами:
•• Обход: QuerySet – итерируемый объект, который обращается к базе на первой итерации. В следующем примере обращение к объекту QuerySet происходит при выполнении первой итерации цикла for:
qs = Entry.objects.filter(pub_date__year=2006) qs = qs.filter(headline__icontains=”bill”)
for e in qs:
print e.headline
В результате будут выведены значения поля headline всех записей, добавленных начиная с 2006 года и содержащих слово «bill», но при этом будет произведено только одно обращение к базе.
•• Печать: обращение к объекту QuerySet происходит при вызове его метода repr(). Это сделано для удобства работы в интерактивном интерпретаторе Python, чтобы можно было сразу же увидеть результаты.
•• Извлечение сегмента: как объясняется в разделе «Ограничение QuerySet» ниже, к объектам QuerySet можно применять операцию извлечения сегмента, как к массивам Python. Обычно при этом возвращается новый объект QuerySet (без обращения к базе данных), но если в операторе извлечения сегмента задан параметр step, то Django обратится с запросом к базе данных.
•• Преобразование в список: форсировать обращение к объекту QuerySet можно вызовом метода list(), например:
Фильтрация объектов |
429 |
>>> entry_list = list(Entry.objects.all())
Однако имейте в виду, что на это расходуется много памяти, так как все элементы списка Django сохранит в памяти. Напротив, в случае обхода 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())
Все три объекта QuerySet различны. Первый содержит записи, в которых значение поля headline начинается словом «What». Второй является подмножеством первого, из которого исключены записи с pub_date позже текущей даты. Третий – подмножество первого с дополнительным условием: включаются только записи, со значением поля pub_date позже текущей даты. Фильтрация никак не отражается на исходном QuerySet (q1).
Ограничение QuerySet
Чтобы ограничить объект QuerySet, оставив в нем определенное количество записей, можно воспользоваться синтаксисом извлечения сегмента из массива. Результат эквивалентен применению фраз LIMIT и OFFSET в SQL-команде SELECT.
Так, в следующем примере возвращаются первые пять записей (LIMIT 5):
>>>Entry.objects.all()[:5]
Аздесь – записи с шестой по десятую (OFFSET 5 LIMIT 5):
>>>Entry.objects.all()[5:10]
Вобщем случае операция извлечения сегмента из QuerySet возвращает новый QuerySet, но при этом запрос к базе данных не производится. Исключением является случай, когда в операторе извлечения сегмента используется параметр step. Так, в примере ниже будет выполнен запрос и возвращен список, содержащий каждый второй объект из первых десяти:
>>>Entry.objects.all()[:10:2]
