Раздел о моделях описывает как использовать стандартные поля модели Django – CharField, DateField, и тд. В большинстве случаев эти классы - все что вам будет нужно. Однако в некоторых случаях предоставленные Django поля модели не предоставляют необходимый функционал.
Встроенные поля не покрывают все возможные типы полей базы данных – только стандартные типы, такие как VARCHAR и INTEGER. Для остальных типов полей, такие как хранящие географические полигоны или собственные типы полей в PostgreSQL, вы можете создать собственный подкласс для Field.
Так же вы можете создать поле для хранения сложного Python объекта в стандартном поле. Это другая проблема, которую помогает решит собственный подкласс Field.
Создание собственного поля требует внимания к деталям. Для простоты понимания мы будем использовать один и тот же пример в этом разделе: объект, который содержит состояние карт на руках для карточной игры Бридж. Не беспокойтесь, вам не обязательно знать правила этой игры. Все что вам необходимо знать – 52 делятся поровну между четырьмя игроками, которых традиционно называют north, east, south и west. Наш класс выглядит следующим образом:
class Hand(object):
"""A hand of cards (bridge style)"""
def __init__(self, north, east, south, west):
# Input parameters are lists of cards ('Ah', '9s', etc)
self.north = north
self.east = east
self.south = south
self.west = west
# ... (other possibly useful methods omitted) ...
Это простой класс Python, ничего Django-специфического. Мы ходим использовать нашу модель следующим образом (предполагается что атрибут модели hand это объект Hand):
example = MyModel.objects.get(pk=1)
print example.hand.north
new_hand = Hand(north, east, south, west)
example.hand = new_hand
example.save()
Получение и назначение значений атрибута hand нашей модели аналогично любому другому классу в Python. Хитрость заключается в том, что бы научить Django сохранять и загружать наш объект.
Для использования класса Hand в наших моделях, мы не должны изменять этот класс. Таким образом можно использовать в моделях существующие классы, которые мы не можем изменить.
Примечание
В некоторых случаях вы захотите использовать возможности определенных типов полей базы данных, но использовать стандартные типы Python: строки, числа и др. Этот случай похож на наш пример с классом Hand и мы укажем на все отличия.
Основное предназначение поля модели – это преобразование объекта Python (строка, булево значение, datetime, или что-либо более сложное, как Hand) в формат удобный для хранения в базе данных и обратно (и сериализация, но, как мы увидим далее, это решается естественным способом при решении проблем преобразования данных для базы данных).
Значение поля модели должно быть преобразовано в один из доступных тип полей базы данных. Различные базы данных предоставляют различные типы полей, но правило остается одно: вы можете работать только с этими типами. Все, что вы хотите сохранить в базе данных, должно быть преобразовано в один из доступных типов данных.
Скорее всего, вы создаете поле модели для использования типа поля конкретной базы данных, или же преобразуете данные, предположим, в строку.
Для нашего примера с Hand, мы можем преобразовать данные о картах в строку из 104 символов соединив все карты вместе в определенном порядке – скажем, сначала все карты north, затем карты east, south и west. Таким образом объект Hand будет сохранен в текстовом поле базы данных.
Все поля в Django(и когда мы говорим поля в этом разделе, мы всегда подразумеваем поля модели а не поля формы) являются подклассами django.db.models.Field. Большинство информации о поле, которую хранит Django, общая для всех типов полей – название, описание, уникальность и др. Вся эта информация хранится в Field. Мы рассмотрим возможности Field чуть позже, сейчас же запомним, что все поля наследуются от Field и переопределяют поведение этого класса.
Важно понять, что класс поля это не то, что хранится в атрибуте модели. Атрибуты модели содержат объекты Python. Классы полей, которые вы указали в модели, на самом деле сохраняются в классе Meta при создании класса модели. Вот почему мы не используем классы полей при редактировании атрибутов экземпляра модели, их задача преобразовывать значение атрибутов в данные сохраняемые в базе данных или передаваемые в сериализатор.
Будьте внимательны при создании собственного поля. Подкласс Field несколько способов преобразования объектов Python в значение для базы/сериализаци (например, сохраняемое значение и значение для фильтра по полю отличаются). Не волнуйтесь если звучит слишком сложно – мы во всем разберемся на примере чуть ниже. Просто запомните что скорее всего вам придется создавать два класса:
Первый класс будет использоваться пользователями. Экземпляр этого класса будет использоваться для отображения, работы с данными, при изменении значения поля модели. В нашем примере это класс Hand.
Второй класс – это подкласс Field. Это класс, который отвечает за преобразование вашего первого класса в значение для хранения в базе данных и обратно в объект Python.
При создании подкласса Field, сначала подумайте не похож ли он на уже существующее поле. Можете ли унаследоваться от существующего поля Django и сэкономить этим свое время? Если нет, создавайте подкласс Field.
При создании конструктора важно разделить аргументы специфические для вашего поля и те, которые следует передать в метод __init__() родительского класса Field.
Назовем наше поле HandField. (Хорошая практика называть подклассы Field как <Something>Field, таким образом легко определить какой класс является подклассом Field.) Оно не похоже ни на одно встроенное в Django поле, по этому мы создаем подкласс Field:
from django.db import models
class HandField(models.Field):
description = "A hand of cards (bridge style)"
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 104
super(HandField, self).__init__(*args, **kwargs)
HandField принимает большинство стандартных аргументов (смотрите список ниже), но мы явно указываем длинную поля так как нам необходимо хранить только значения 52 карт и их принадлежность, всего 104 символа.
Примечание
Большинство полей модели в Django принимают параметры, которые они совсем не используют. Например, вы можете передать editable и auto_now в django.db.models.DateField, аргумент editable будет проигнорирован (auto_now`устанавливает ``editable=False`). Вы не получите ошибку.
Такое поведение упрощает классы полей, так как они не должны проверять все аргументы. Они просто передают их в родительский класс и больше не используют. Вы можете использовать более строгие правила и проверять все входящие аргументы или же использовать более простой и гибкий путь, как это делают встроенные классы.
Метод __init__() принимает следующие параметры:
rel: используется полям обрабатывающими связи между моделями (например ForeignKey). Для опытных пользователей.
serialize: При False, поле не будет сериализовано при передачи модели в сериализатор Django. По умолчанию True.
db_tablespace: Используется только при создании индекса, если база данных поддерживает табличное пространство. В большинстве случаев вы можете проигнорировать этот параметр.
auto_created: True, если поле создается автоматически, например, используется полем OneToOneField. Для опытных пользователей.
Аргументы без описания аналогичны соответствующим аргументам стандартных полей, смотрите раздел о полях модели for examples and details.
Как уже упоминалось в Предисловии, собственные поля используются в двух случаях: для использования типа поля для определенной базы данных и для работы со сложными объектами Python. Если вам необходимо работать с определенным типом поля в базе данных и использовать встроенные типы данных Python, вы можете пропустить этот раздел.
Если мы используем собственный тип Python, такой как наш класс Hand, мы должны убедиться что Django, при создании экземпляра модели и добавления значения из базы данных в атрибут поля, преобразует значение в правильный объект Python. Полное описание этого процесса с легко сложное для понимания, но код, который вам необходимо написать в классе поля, простой: убедитесь, что вам подкласс использует специальный метакласс:
Например:
class HandField(models.Field):
description = "A hand of cards (bridge style)"
__metaclass__ = models.SubfieldBase
def __init__(self, *args, **kwargs):
# ...
Теперь мы можем быть уверенны, что метод to_python(), описанный ниже, будет всегда вызываться при инициализации атрибута.
Если вы используете SubfieldBase, to_python() будет вызываться при добавления значения в поле. Это означает, что каждый раз при названии знании значения полю, вы может можете проверить правильного ли типа значение и обработать ошибки.
Это особо важно при использовании ModelForms. При сохранении ModelForm, Django использует данные из формы при создании экземпляра модели. Если проверенные данные из формы не могут быть использовать в качестве значения для поля модели, стандартный процесс проверки данных в форме будет нарушен.
Поэтому вы должны убедиться, что поле формы, которое отображает ваше поле модели, выполняет все необходимые проверки данных и преобразует их в тип аналогичный используемыму методом to_python() вашего поля модели. Для этого, возможно, вам придется создать собственный класс поля формы и/или переопределить метод formfield() поля модели, что бы метод to_python() класса поля формы возвращал данные правильного типа.
Конечно же вам необходимо задокументировать ваше поле, что бы пользователи знали как его использовать. В дополнение к docstring, который удобен для разработчиков, вы можете предоставить описание поля, которое будет отображаться в разделе документации в интерфейсе администратора, созданном с django.contrib.admindocs. Для этого укажите описание в атрибуте description класса поля. В нашем примере описание поля HandField в приложении admindocs будет - ‘A hand of cards (bridge style)’.
После того как вы создали свой подкласс Field и указали __metaclass__, можно переходить к переопределению методов, которые определяют поведение вашего поля. Методы описанные ниже идут в порядке убывания важности.
Возвращает типа поля в базе данных для Field, учитывая настройки подключения и параметры поля.
Предположим вы создали собственный тип поля для PostgreSQL - mytype. Вы можете использовать его в Django унаследовав Field и добавив следующий метод db_type():
from django.db import models
class MytypeField(models.Field):
def db_type(self, connection):
return 'mytype'
Создав MytypeField вы можете использовать его в моделях так же как и другие подтипы Field:
class Person(models.Model):
name = models.CharField(max_length=80)
gender = models.CharField(max_length=1)
something_else = MytypeField()
Если вы создаете приложение не зависимое от типа базы данных, учитывайте что разные базы данных используют различные типа полей. Например, поле даты/времени в PostgreSQL называется timestamp, а в MySQL – datetime. Самый простой способ: проверять значнеие connection.settings_dict[‘ENGINE’]` в методе db_type().
Например:
class MyDateField(models.Field):
def db_type(self, connection):
if connection.settings_dict['ENGINE'] == 'django.db.backends.mysql':
return 'datetime'
else:
return 'timestamp'
Метод db_type() используется Django только при создании CREATE TABLE запросов – когда вы создаете таблицы в базе данных для приложения. Больше нигде этот метод не используется, по этому вы можете использовать достаточно сложный код, как проверка connection.settings_dict в примере выше.
Некоторые типы полей принимают параметры, например CHAR(25), где 25 указывают максимальный размер колонки. В этом случае лучше указывать параметр в модели, чем хардкодить в методе db_type(). Например, глупо создавать поле CharMaxlength25Field:
# This is a silly example of hard-coded parameters.
class CharMaxlength25Field(models.Field):
def db_type(self, connection):
return 'char(25)'
# In the model:
class MyModel(models.Model):
# ...
my_field = CharMaxlength25Field()
Лучше позволить указывать параметр при определении поля – то есть при создании класса модели. Для этого переопределите метод django.db.models.Field.__init__():
# This is a much more flexible example.
class BetterCharField(models.Field):
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
super(BetterCharField, self).__init__(*args, **kwargs)
def db_type(self, connection):
return 'char(%s)' % self.max_length
# In the model:
class MyModel(models.Model):
# ...
my_field = BetterCharField(25)
В конце концов, если поле требует действительно сложный SQL код при создании, верните None в методе db_type(). В этом случае Django пропустит создание этого поля в базе данных. Вам придется создать поле каким либо другим способом.
Преобразует значение, которое вернула база данных (или сериалайзер), в объект Python.
Реализация по умолчанию возвращает value без изменений, так как в большинстве случаев бэкэнд базы данных возвращает значение в нужном формате (например, строка Python string).
Если ваш подкласс Field работает со структурами более сложными чем строка, дата и числа, вам следует переопределить этот метод. Метод должен корректно работать со следующими аргументами:
Объект нужного типа (например, Hand в нашем примере).
Строка (например, при десериализации).
Значение возвращаемое базой данных.
В нашем HandField мы сохраняем значение в поле VARCHAR, по этому должны обрабатывать строки и объекты Hand в методе to_python():
import re
class HandField(models.Field):
# ...
def to_python(self, value):
if isinstance(value, Hand):
return value
# The string case.
p1 = re.compile('.{26}')
p2 = re.compile('..')
args = [p2.findall(x) for x in p1.findall(value)]
return Hand(*args)
Помните, что мы всегда возвращаем объект Hand из этого метода. Это объект Python который мы хотим сохранить в модели.
Помните: если вашему полю необходим вызов to_python() при создании, используйте вышеупомянутый `метакласс The SubfieldBase`_. Иначе метод to_python() не будет автоматически вызван.
Есть метод обратный to_python() при работе с бэкэндом базы данных (но не сериализатором). Аргумент value - это значение атрибута модели (поле не содержит ссылку на модель, по этому не может получить значение самостоятельно), метод должен вернуть данные для подстановки в запрос.
Это преобразование не должно выполнять ничего что, зависит от типа базы данных. Если необходимо преобразование специфическое для какой либо базы данных, его необходимо выполнить в методе get_db_prep_value().
Например:
class HandField(models.Field):
# ...
def get_prep_value(self, value):
return ''.join([''.join(l) for l in (value.north,
value.east, value.south, value.west)])
Некоторые типа данных (например, даты) должны быть в определенном формате при передаче в бэкэнд базы данных. Эти преобразования должны быть выполнены в get_db_prep_value(). Объект подключения к базе данных передается в аргументе connection. Это позволяет выполнить преобразование, которое зависит от используемой базы данных.
Аргумент prepared указывает было ли значение обработано get_prep_value(). При prepared равном False get_db_prep_value() по умолчанию вызовет get_prep_value() перед дальнейшим преобразованием.
Аналогичен предыдущему методу, но вызывается когда значение Field сохраняется в БД. По умолчанию вызывается метод get_db_prep_value(), вы не должны ничего менять, если нет необходимости выполнять дополнительное преобразование значение именно при сохранении, а не каких либо других запросах (что выполняется в get_db_prep_value()).
Этот метод вызывается перед get_db_prep_save() и должен вернуть значение атрибута из model_instance для этого поля. Название атрибута хранится в self.attname (устанавливается в Field). При сохранении данных в базу данных первый раз, аргумент add будет равен True, иначе - False.
Вы должны переопределить этот метод если хотите изменить значение перед сохранением. Например, поле DateTimeField использует этот метод для установки значения при auto_now или auto_now_add.
Если вы переопределяете это метод, необходимо вернуть значение атрибута в конце. Вы также должны обновить атрибут модели, если изменяли значение.
Как и преобразование значения поля, преобразование значения для поиска(WHERE) в базе данных выполняется в две фазы.
get_prep_lookup() выполняет первую фазу проверяя значение
Подготавливает value для передачи в фильтр запроса (WHERE). lookup_type содержит один из фильтров Django: exact, iexact, contains, icontains, gt, gte, lt, lte, in, startswith, istartswith, endswith, iendswith, range, year, month, day, isnull, search, regex и iregex.
Ваш метод должен учитывать все возможные значения lookup_type и вызвать исключение ValueError, если value содержит неверное значение (например, список в то время, когда вы ожидаете объект) или TypeError, если ваше поле не поддерживает данный тип фильтра. Для большинства полей вы можете добавить обработку определенных фильтров, для всех остальных использовать метод get_db_prep_lookup() родительского класса.
Если вы переопределяете get_db_prep_save(), скорее всего вам необходимо переопределить и метод get_prep_lookup(). Если этого не сделать, будет использовать реалзиация get_prep_value по умолчанию для обработки фильтров exact, gt, gte, lt, lte, in и range.
Вы можете использовать это метод, что бы ограничить типы фильтров, используемых с вашим полем.
Заметьте, что для range и in метод get_prep_lookup принимает список объектов (предположительно правильного типа) и должен вернуть список параметров для запроса. В большинстве случаев вы можете использовать get_prep_value() для объектов списка.
Например, следующий код реализует метод get_prep_lookup ограничивая используемые фильтры до exact и in:
class HandField(models.Field):
# ...
def get_prep_lookup(self, lookup_type, value):
# We only handle 'exact' and 'in'. All others are errors.
if lookup_type == 'exact':
return self.get_prep_value(value)
elif lookup_type == 'in':
return [self.get_prep_value(v) for v in value]
else:
raise TypeError('Lookup type %r not supported.' % lookup_type)
Выполняет преобразование параметров фильтра с учетом типа базы данных. Как и в метод get_db_prep_value() передается аргумент connection. Параметр prepared указывает было ли значение преобразовано методом get_prep_lookup().
Возвращает поле формы, которое будет использовано при генерации формы для модели. Этот метод используется в ModelForm.
Словарь kwargs передается в конструктор Field__init__() поля формы. Скорее всего вам понадобится определить необходимые аргументы для form_class и передать дальнешую обработку в метод родительского класса. Возможно вам понадобиться создать собственный тип поля формы (и возможно даже свой виджет). Смотрите раздел о формах, примеры кода можно найти в приложении django.contrib.localflavor.
Продолжая наш пример, мы можем создать следующий метод formfield():
class HandField(models.Field):
# ...
def formfield(self, **kwargs):
# This is a fairly standard way to set up some defaults
# while letting the caller override them.
defaults = {'form_class': MyFormField}
defaults.update(kwargs)
return super(HandField, self).formfield(**defaults)
Подразумевается, что мы уже импортировать класс поля MyFormField (который содержит свой собственный виджет). Этот раздел не описывает создание собственного поля формы.
Возвращает название субкласса Field, который мы эмулируем на уровне базы данных. Это позволяет определить тип поля в базе данных для простых случаев.
Если вы определил метод db_type(), нет необходимости использовать get_internal_type() – он не будет использоваться. Иногда одни типы полей работают так же как и другие на уровне базы данных, в таких случаях вы можете использовать этот метод.
Например:
class HandField(models.Field):
# ...
def get_internal_type(self):
return 'CharField'
Без разницы какую базу данных мы используем, syncdb и другие SQL выберут правильным тип поля в базе данных.
Если get_internal_type() возвращает название неизвестное Django – то есть его нет в django.db.backends.<db_name>.creation.DATA_TYPES – оно будет использовано сериализатором, но метод db_type() по умолчанию вернет None. Смотрите описание db_type() что бы понять, в каких случаях это может быть полезно. Возвращение строки описывающей поле для сериализатора, может быть хорошей практикой.
Этот метод используется сериализатором. Вызов Field._get_val_from_obj(obj)() - лучший способ получить значение для сериализатора. Например, так как HandField использует строку для хранения в базе данных, мы можем использовать существующий код:
class HandField(models.Field):
# ...
def value_to_string(self, obj):
value = self._get_val_from_obj(obj)
return self.get_db_prep_value(value)
Создание собственно поля может быть непростой задачей, особенно при сложном преобразовании данных в объекты Python, значения для базы данных и сериализатиора. Вот несколько советов как упросить эту задачу:
Посмотрите на существующие поля в Django (в django/db/models/fields/__init__.py). Постарайтесь найти поле похожее на то, что вм необходимо, это лучше чем создавать свое поля с нуля.
Добавьте метод __str__() или __unicode__() в класс, который вы используется для значений вашего поля. Во многих случаях используется функция force_unicode() при обработке значений. (В нашем примере, value будет объект Hand, не HandField). Если метод __unicode__() преобразует объект Python в строку, это сохранит вам много времени.
В дополнение к вышеописанным методам поля, которые работают с файлами, требуют дополнительной работы. Основной функционал FileField, такая как сохранение и получения данных в БД, можно оставить без изменений, определив лишь операции необходимые для работы с различными типами файлов.
Django pпердоставляет класс File, который используется как прокси при работе с файлами. Можно унаследоваться от него и переопределить работу с файлом. Он находится в django.db.models.fields.files и описан в разделе о файлах.
После создания подкласса File новый подкласс FileField может использовать его. Просто укажите подкласс File в атрибуте attr_class подкласса FileField.
В дополнение ко всему выше сказанному, вот несколько советов, которые помогут улучшить ваш код.
Пример встроенного в Django поля ImageField (в django/db/models/fields/files.py) - хороший пример переопределения FileField, изучите его.
Кэшируйте объект файла при любой возможности. Так как файлы могут хранится во внешнем хранилище, получение их может потребовать дополнительного времени и даже денег. После получения файла закэшируйте как можно большую его часть, что бы сократить количество операций необходимых для его последующего чтения.
Mar 30, 2016