Pull to refresh

Сглаживая кривую

Reading time 5 min
Views 1.6K
Original author: Sean O'Connor
Практически для всех возможностей Django существуют способы модификации и расширения. Крайне редко может понадобиться переписывать существенную часть функционала Django только для того, чтобы изменить действие какого-то инструмента. Например, если вы хотите изменить внешний вид формы, вы можете отказаться от внешнего вида «по умолчанию» и создать собственное поле, или даже просто использовать собственный HTML. В обоих случаях вы выигрываете по спектру возможностей, сохраняя все остальные преимущества библиотеки форм.

Это расширение возможностей можно представить в виде кривой. На одном конце базовый вариант, который прост в написании, но ограничивает возможности контроля. На противоположном конце кривой будет специально созданный класс для форм со статическим у HTML-кодом, который обеспечивает больший уровень управления, но который и более сложен в создании. В примере с созданием форм эта кривая будет довольно гладкая, поскольку для всего спектра функций библиотеки форм существуют возможности постепенного замещения базовых опций на собственные пользовательские расширения.

До выхода в свет версии Django 1.2 для ORM такая кривая имела аналогичный вид, за одним исключением: существенный скачок в конце. Этот скачок появлялся в связи с тем, что при необходимости создания нестандартного SQL-запроса требовалось выйти за пределы ORM. Чтобы использовать определённый функционал ORM, пользователю пришлось бы создавать его заново самостоятельно, хоть это и не является само по себе катастрофой. В версии Django 1.2 добавлен метод Model.objects.raw (), который решает эту проблему и, таким образом, сглаживает эту кривую для ORM.

Старый способ

До выхода версии Django 1.2 при необходимости создать SQL запрос, нужно было написать нечто подобное:

from django.db import connection
from library.models import Author 

cursor = connection.cursor()
query = "SELECT * FROM library_author"
cursor.execute(query)
results = cursor.fetchall() 

authors = []
for result in results:
    author = Author(*result)
    authors.append(author)


Не то чтобы это было совсем ужасно, но здесь мы теряем доступ к функционалу ORM во всём, что не касается создания SQL запросов. В частности, недоступна автоматическая трансформация результатов запроса в экземпляр модели. В меру трудоёмкие способы восстановить утраченную функциональность, конечно, существуют, но фактически это будет изобретением велосипеда.

Новый способ

В версии Django 1.2 для создания прямого SQL запроса нужно написать следующее:

from library.models import Author 

query = "SELECT * FROM library_author"
authors = Author.objects.raw(query)


authors здесь будет экземпляром RawQuerySet. RawQuerySet во многом похож на QuerySet. В частности, сходство в том, что это — итерируемый объект, который возвращает экземпляр модели из результатов запроса с каждой итерацией. Отличие его от QuerySet состоит в том, что его нельзя встроить в цепочку. Но здесь это и не важно, поскольку запросы больше не формируются автоматически.

Как и при использовании БД курсора мы можем передать набор параметров запроса, Django заботливо их экранирует.

query = "SELECT * FROM library_author WHERE first_name = %s"
params = ('bob',)
authors = Author.objects.raw(query, params)


Так это же здорово! Код SQL защищён от атак, у нас экземпляр модели, который мы хотели, и никакого изобретения велосипеда.

«Но и это ещё не всё!»

Как и большинство инструментов Django, метод raw () оставляет пространство для использования дополнительных функций в неудобных ситуациях или для особо сложных запросов:

Независимый порядок полей

Для метода Model.objects.raw () неважно, в каком порядке возвращаются поля по запросу. Единственное, что важно — соответствуют ли имена полей запроса полям в модели.

# All of these queries will work the same
Author.objects.raw("SELECT * FROM library_author")
Author.objects.raw("SELECT id, first_name, last_name FROM library_author")
Author.objects.raw("SELECT last_name, id, first_name FROM library_author")


Аннотации

Если в ответ на запрос мы получаем поля, которые не существуют в классе модели, они добавляются в качестве аннотаций к тем экземплярам модели, которые возвращает метод RawQueryset. Это с лёгкостью позволяет использовать все преимущества действий или вычислений, выполнение которых более эффективно на уровне базы данных.

>>> authors = Author.objects.raw("SELECT *, age(birth_date) as age FROM library_authors")
>>> for author in authors:
...     print "%s is %s." % (author.first_name, author.age)
John is 37.
Jane is 42.
...


Определения соотношения между полями модели и запроса

Если по какой-либо причине не удаётся точно сопоставить имена поля запросов и имена поля модели, метод Model.objects.raw () предоставляет возможность указать его вручную.

Чтобы сопоставить поля запроса с полями модели, нужно просто использовать словарь, содержащий требуемые соответствия, в качестве одного из параметров метода raw (). Соответствия необходимо определить только для тех полей, которые не удалось сопоставить с полями модели.

field_map = {'first': 'first_name', 'last': 'last_name'}
    query = 'SELECT id, first_name AS first, last_name as last FROM library_author'
    authors = Author.objects.raw(query, translations=field_map)


Отложенные поля

Те поля, которые предполагаются в модели, но не возвращаются запросом, отмечаются как отложенные. Они будут заполнены только при запросе к полю экземпляра модели. Это полезно в тех случаях, когда данные запрашиваются не из «реальной» таблицы для модели, или когда сами таблицы достаточно большие. Здесь надо иметь в виду, что первичный ключ не может быть отложен и должен быть возвращён всеми запросами. Если запрос не возвращает первичный ключ, будет вызвано исключение InvalidQuery.

Ограничения

На действия метода raw () накладываются некоторые ограничения. Самое существенное из них заключается в том, что метод raw () поддерживает только  SELECT запросы. При попытке использовать любой другой тип запроса, будет вызвано исключение InvalidQuery. Первоначально это было сделано с целью защиты, но отчасти это сделано так и потому что нет смысла возвращать экземпляр модели для чего-либо ещё кроме запроса типа SELECT. Изменение данных с помощью прямого SQL — это последнее, что стоит делать с помощью Django. Чтобы не провоцировать эти действия, мы ни в коем случае не хотим делать их удобнее для пользователя. Если вам требуется использовать именно прямые запросы SQL кроме запроса типа SELECT, у вас всегда остаётся возможность создать курсор БД и работать оттуда.

Вот и всё

Ну вот и всё. В версии Django 1.2 существенно упрощена работа с SQL запросами там, где это необходимо. Кривая, о которой мы говорили выше, имеет гораздо более гладкий вид. Официальную документацию для этой функции можно найти в разделе, посвящённому SQL.
Tags:
Hubs:
+54
Comments 9
Comments Comments 9

Articles