Раздел о моделях описывает, как использовать стандартные поля модели 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__() :class:`~django.db.models.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`). Вы не получите ошибку.
Такое поведение упрощает классы полей, так как они не должны проверять все аргументы. Они просто передают их в родительский класс и больше не используют. Вы можете использовать более строгие правила и проверять все входящие аргументы или же использовать более простой и гибкий путь, как это делают встроенные классы.
Метод Field.__init__() принимает следующие параметры:
rel: Используется для внешних ключей (таких как ForeignKey). Для опытных пользователей.
serialize: При False, поле не будет сериализовано при передачи модели в сериализатор Django. По умолчанию True.
db_tablespace: Используется только при создании индекса, если база данных поддерживает табличное пространство. В большинстве случаев вы можете проигнорировать этот параметр.
auto_created: True, если поле создается автоматически, например, используется полем OneToOneField. Для опытных пользователей.
Аргументы без описания аналогичны соответствующим аргументам стандартных полей, смотрите раздел о полях модели for examples and details.
deconstruct() – часть приложения миграций в Django 1.7 и выше. Если вы используете поля из предыдущих версий, вам необходимо добавить этот метод перед тем, как использовать их в миграциях.
Метод deconstruct() является дополнением к методу __init__(). Этот метод указывает Django как сериализировать экземпляр поля, а точнее – какие аргументы передать в __init__() чтобы воссоздать его.
Если вы не добавляли аргументы в дочерний класс встроенного поля, вам не нужно переопределять метод deconstruct(). Однако, если вы изменили аргументы __init__() (как мы сделали это в поле HandField), вам необходимо добавить их.
Формат ответа deconstruct() простой. Он должен возвращать кортеж из четырех элементов: имя атрибута поля, полный путь импорта класса поля, позиционные аргументы (списком) и именованные аргументы (словарем). Обратите внимание, это отличается от метода deconstruct() для собственного класса, которые должен вернуть кортеж из трех элементов.
Создавая свое поле, вам не стоит беспокоится о первых двух значениях, базовый класс Field сам определит их. Вам необходимо указать позиционные и именованные аргументы, скорее всего именно их вы и будете переопределять в своем поле.
Например, в нашем классе HandField мы определяем max_length в __init__(). Метод deconstruct() базового класса Field вернет его в именованных аргументах. Но мы можем удалить его для читабельности:
from django.db import models
class HandField(models.Field):
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 104
super(HandField, self).__init__(*args, **kwargs)
def deconstruct(self):
name, path, args, kwargs = super(HandField, self).deconstruct()
del kwargs["max_length"]
return name, path, args, kwargs
Если вы добавили именованный аргумент, вам следует вернуть его в kwargs:
from django.db import models
class CommaSepField(models.Field):
"Implements comma-separated storage of lists"
def __init__(self, separator=",", *args, **kwargs):
self.separator = separator
super(CommaSepField, self).__init__(*args, **kwargs)
def deconstruct(self):
name, path, args, kwargs = super(CommaSepField, self).deconstruct()
# Only include kwarg if it's not the default
if self.separator != ",":
kwargs['separator'] = self.separator
return name, path, args, kwargs
Более сложные примеры выходят за рамки этой документации, но помните - для любой конфигурации поля, deconstruct() должен вернуть аргументы, которые можно передать в __init__, чтобы воссоздать экземпляр поля.
Обратите внимание на новые значения по умолчанию для аргументов Field. Вы захотите чтобы они сохранились, а не перезаписались старыми значениями по умолчанию.
Также, старайтесь не возвращать позиционные аргументы, при возможности используйте именованные для максимальной совместимости в будущем. Конечно, если вы меняете названия аргументов чаще, чем их порядок, вам лучше использовать позиционные аргументы. Но не забываете, что люди будут воссоздавать ваше поле из сериализованного состояния в течении достаточно долгого периода (возможно годами), в зависимости от того, как долго будут существовать миграции.
Вы можете посмотреть результат деконструкции в миграции, которая включает поле. Вы можете проверить деконструкцию в тестах просто воссоздавая поле:
name, path, args, kwargs = my_field_instance.deconstruct()
new_instance = MyField(*args, **kwargs)
self.assertEqual(my_field_instance.some_attribute, new_instance.some_attribute)
Конечно же вам необходимо задокументировать ваше поле, чтобы пользователи знали как его использовать. В дополнение к docstring, который удобен для разработчиков, вы можете предоставить описание поля, которое будет отображаться в разделе документации в интерфейсе администратора, созданном с django.contrib.admindocs. Для этого укажите описание в атрибуте description класса поля. В нашем примере описание поля HandField в приложении admindocs будет - ‘A hand of cards (bridge style)’.
На страницах django.contrib.admindocs описание поля включает field.__dict__, что позволяет включить описание аргументов. Например, описание CharField выглядит следующим образом:
description = _("String (up to %(max_length)s)")
После того как вы создали свой подкласс Field и указали __metaclass__, можно переходить к переопределению методов, которые определяют поведение вашего поля. Методы описанные ниже идут в порядке убывания важности.
Предположим вы создали собственный тип поля для 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)
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 запросов – когда вы создаете таблицы в базе данных для приложения. Также при создании условий в WHERE, которые используют поле – это когда вы используете методы QuerySet для получения данных, такие как get(), filter() или exclude(), и используете ваше поле в качестве аргумента. Больше нигде этот метод не используется, вы можете использовать достаточно сложный код, как проверка 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()
Лучше позволить указывать параметр при определении поля – то есть при создании класса модели. Для этого переопределите метод 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 пропустит создание этого поля в базе данных. Вам придется создать поле каким либо другим способом.
Исторически Django предоставляет метакласс SubfieldBase, который вызывает to_python() при каждом указании значения поля. Такой подход не очень хорошо работал с преобразованиями на уровне базы данных, агрегациями, или получением данных через values(), и был заменен методом from_db_value().
Если ваш подкласс Field работает со структурами более сложными, чем строка, дата и число, вам следует переопределить from_db_value() и to_python().
Если поле содержит метод from_db_value(), он будет вызываться при всех операциях загрузки данных с базы данных, включая агрегации и вызовы values().
to_python() вызывается при десериализации и при вызове метода clean() в формах.
Метод to_python() должен корректно обрабатывать следующие типы значения:
Объект нужного типа (например, Hand в нашем примере).
Строка
None (если поле содержит null=True)
В нашем HandField мы сохраняем значение в поле VARCHAR, и в from_db_value() должны обрабатывать строки и None. В to_python() также объекты Hand:
import re
from django.core.exceptions import ValidationError
from django.db import models
def parse_hand(hand_string):
"""Takes a string of cards and splits into a full hand."""
p1 = re.compile('.{26}')
p2 = re.compile('..')
args = [p2.findall(x) for x in p1.findall(hand_string)]
if len(args) != 4:
raise ValidationError("Invalid input for a Hand instance")
return Hand(*args)
class HandField(models.Field):
# ...
def from_db_value(self, value, expression, connection, context):
if value is None:
return value
return parse_hand(value)
def to_python(self, value):
if isinstance(value, Hand):
return value
if value is None:
return value
return parse_hand(value)
Помните, что мы всегда возвращаем объект Hand из этого метода. Это объект Python, который мы хотим сохранить в модели.
Если to_python() не может выполнить преобразование значения, вызовите исключение ValidationError.
Т.к. использование базы данных требует преобразования значения в оба ннаправления, если вы переопределили to_python() ва следует переопределить и get_prep_value() чтобы преобразовать объект Python обратно в значение для запроса.
Например:
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)])
Предупреждение
Если ваше поле использует типы CHAR, VARCHAR или TEXT MySQL, метод get_prep_value() всегда должен возвращать строку. MySQL выполняет довольно непредсказуемое сравнение типов, если передать число, что может привести к неожиданными результатам запроса. Этой проблемы можно избежать возвращая всегда строку из get_prep_value().
Некоторые типы данных (например, даты) должны быть в определенном формате при передаче в бэкенд базы данных. Эти преобразования должны быть выполнены в get_db_prep_value(). Объект подключения к базе данных передается в аргументе connection. Это позволяет выполнить преобразование, которое зависит от используемой базы данных.
Например, Django использует следующий метод для BinaryField:
def get_db_prep_value(self, value, connection, prepared=False):
value = super(BinaryField, self).get_db_prep_value(value, connection, prepared)
if value is not None:
return connection.Database.Binary(value)
return value
Если ваше поле требует дополнительного преобразования данных при сохранении, переопределите для этого метод get_db_prep_save().
Вы должны переопределить метод pre_save(), если хотите изменить значение перед сохранением. Например, поле DateTimeField использует этот метод для установки значения при auto_now или auto_now_add.
Если вы переопределяете этот метод, необходимо вернуть значение атрибута в конце. Вы также должны обновить атрибут модели, если изменяли значение.
Как и преобразование значения поля, преобразование значения для поиска(WHERE) в базе данных выполняется в две фазы.
get_prep_lookup() выполняет первую фазу подготовки параметров фильтрации: преобразование типа и проверку данных.
Подготавливает value для передачи в фильтр запроса (WHERE в SQL). 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 может быть любой lookup_name, который используется дополнительными фильтрами.
Ваш метод должен учитывать все возможные значения 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_lookup().
Чтобы переопределить поле формы, которое будет использоваться ModelForm, вы можете переопределить formfield().
Класс поля формы можно указать аргументами form_class и ``choices_form_class``(используется, если для поля указан список возможных значений). Если аргументы не указаны, будут использоваться CharField или TypedChoiceField.
Словарь kwargs передается в конструктор __init__() поля формы. Скорее всего вам понадобится определить необходимые аргументы для form_class``(и возможно ``choices_form_class) и передать дальнейшую обработку в метод родительского класса. Возможно вам понадобиться создать собственный тип поля формы (и возможно даже свой виджет). Смотрите раздел о формах.
Продолжая наш пример, мы можем создать следующий метод 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 (который содержит свой собственный виджет). Этот раздел не описывает создание собственного поля формы.
Если вы определили метод db_type(), нет необходимости использовать get_internal_type() – он не будет использоваться. Иногда одни типы полей работают так же, как и другие на уровне базы данных, в таких случаях вы можете использовать этот метод.
Например:
class HandField(models.Field):
# ...
def get_internal_type(self):
return 'CharField'
Без разницы какую базу данных мы используем, migrate и другие SQL выберут правильный тип поля в базе данных.
Если get_internal_type() возвращает строку, которая неизвестна Django – то есть отсутствует в django.db.backends.<db_name>.base.DatabaseWrapper.data_types – она все равно будет использована сериализатором, но метод db_type() по умолчанию вернет None. Смотрите описание db_type() чтобы понять, в каких случаях это может быть полезно. Возвращение строки, описывающей поле для сериализатора, может быть полезным, если вы собираетесь использовать результат сериализации не только в Django.
Чтобы указать как значения сериализуются сериализатором, переопределите метод value_to_string(). Вызов 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_prep_value(value)
Создание собственного поля может быть непростой задачей, особенно при сложном преобразовании данных в объекты Python, значения для базы данных и сериализатора. Вот несколько советов как упросить эту задачу:
Посмотрите на существующие поля в Django (в django/db/models/fields/__init__.py). Постарайтесь найти поле, похожее на то, что вам необходимо, это лучше, чем создавать свое поле с нуля.
Добавьте метод __str__() (__unicode__() в Python 2) в класс, который вы используете для значений вашего поля. Во многих случаях используется функция force_text() при обработке значений. (В нашем примере, value будет объект Hand, не HandField). Если метод __str__()``(``__unicode__ для Python 2) преобразует объект Python в строку, это сохранит вам много времени.
В дополнение к вышеописанным методам, поля, которые работают с файлами, требуют дополнительной работы. Основной функционал FileField, такой как сохранение и получения данных в БД, можно оставить без изменений, определив лишь операции, необходимые для работы с различными типами файлов.
Django предоставляет класс File, который используется как прокси при работе с файлами. Можно унаследоваться от него и переопределить работу с файлом. Он находится в django.db.models.fields.files и описан в разделе о файлах.
После создания подкласса File новый подкласс FileField может использовать его. Просто укажите подкласс File в атрибуте attr_class подкласса FileField.
В дополнение ко всему сказанному выше, вот несколько советов, которые помогут улучшить ваш код.
Пример встроенного в Django поля ImageField (в django/db/models/fields/files.py) - хороший пример переопределения FileField, изучите его.
Кэшируйте объект файла при любой возможности. Так как файлы могут хранится во внешнем хранилище, получение их может потребовать дополнительного времени и даже денег. После получения файла закэшируйте как можно большую его часть, чтобы сократить количество операций необходимых для его последующего чтения.
Jun 02, 2016