Spec-Zone.ru › Django 1.9

Создание пользовательских полей модели

Введение

Документация по моделям базы данных объясняет, как использовать стандартные классы полей Django — CharField, DateField и т.д. Для многих задач этих классов достаточно. Однако иногда стандартные типы полей Django не удовлетворяют вашим потребностям, или вам нужно использовать поле, отличное от тех, которые поставляются с Django.

Встроенные типы полей Django не охватывают все возможные типы столбцов базы данных — только общие типы, такие как VARCHAR и INTEGER. Для менее распространённых типов столбцов, таких как географические полигоны или даже пользовательские типы, такие как пользовательские типы PostgreSQL, вы можете определять собственные подклассы полей Django Field.

В качестве альтернативы, у вас может быть сложный объект Python, который можно каким-то образом сериализовать, чтобы он поместился в стандартный тип столбца базы данных. Это ещё один случай, когда подкласс Field поможет вам использовать ваш объект в ваших моделях.

Пример объекта

Создание пользовательских полей требует некоторого внимания к деталям. Чтобы упростить понимание, мы будем использовать согласованный пример на протяжении всего документа: обертка объекта Python, представляющего раздачу карт в партии бриджа. Не волнуйтесь, вам не нужно знать правила бриджа, чтобы понять пример. Вам нужно только знать, что 52 карты раздаются поровну четырём игрокам, которых традиционно называют север, восток, юг и запад. Наш класс выглядит примерно так:

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 — в формат, полезный при работе с базой данных (и сериализацией, но, как мы увидим позже, это довольно естественно, когда вы освоились с базой данных).

Поля в модели должны быть каким-то образом преобразованы для соответствия существующему типу столбца базы данных. Разные базы данных предоставляют разные наборы допустимых типов столбцов, но правило остаётся неизменным: это единственные типы, с которыми вам нужно работать. Всё, что вы хотите сохранить в базе данных, должно уместиться в одном из этих типов.

Обычно вы либо создаёте поле Django, соответствующее определённому типу столбца базы данных, либо существует довольно простой способ преобразовать ваши данные, скажем, в строку.

В нашем примере Hand мы можем преобразовать данные о картах в строку из 104 символов, объединив все карты в определённом порядке — например, сначала все карты севера, затем востока, юга и запада. Таким образом, объекты Hand могут быть сохранены в текстовых или символьных столбцах базы данных.

Что делает класс поля?

Все поля Django (и когда мы говорим о «полях» в этом документе, мы всегда имеем в виду поля модели, а не поля форм) являются подклассами django.db.models.Field. Большая часть информации, которую Django записывает о поле, общая для всех полей — имя, подсказка, уникальность и так далее. Хранение всей этой информации обрабатывается классом Field. Мы рассмотрим подробности того, что может делать Field позже; пока достаточно сказать, что всё происходит от Field и затем настраивает ключевые части поведения класса.

Важно понимать, что класс поля Django не хранится в атрибутах вашей модели. Атрибуты модели содержат обычные объекты Python. Классы полей, которые вы определяете в модели, фактически хранятся в классе Meta при создании класса модели (точность того, как это делается, здесь не важна). Это связано с тем, что классы полей не нужны, когда вы просто создаёте и изменяете атрибуты. Вместо этого они обеспечивают механизм преобразования между значением атрибута и тем, что хранится в базе данных или отправляется в сериализатор.

Помните об этом, создавая собственные пользовательские поля. Подкласс Django Field , который вы создаёте, обеспечивает механизм преобразования между вашими экземплярами Python и значениями базы данных/сериализатора различными способами (существуют различия между сохранением значения и использованием значения для поиска, например). Если это звучит сложно, не беспокойтесь — в примерах ниже станет понятнее. Просто помните, что вы часто будете создавать два класса, когда вам нужно пользовательское поле:

  • Первый класс — это объект Python, с которым будут работать пользователи. Они будут назначать его атрибуту модели, читать из него для отображения и т. д. Это класс Hand в нашем примере.
  • Второй класс — это подкласс Field. Это класс, который знает, как преобразовывать ваш первый класс в перманентную форму хранения и обратно в форму Python.

Создание подкласса поля

При планировании подкласса Field, сначала подумайте, какой существующий класс Field наиболее похож на ваше новое поле. Можно ли создать подкласс существующего поля Django и сэкономить время? Если нет, то необходимо создать подкласс класса Field, от которого всё происходит.

Инициализация нового поля заключается в разделении аргументов, специфичных для вашего случая, от общих аргументов и передаче последних методу __init__() класса Field (или родительского класса).

В нашем примере мы назовём наше поле HandField. (Хорошо называть подкласс Field так, чтобы его легко было идентифицировать как подкласс Field.) Он не ведёт себя как существующее поле, поэтому мы создадим подкласс напрямую от 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__() принимает следующие параметры:

  • verbose_name
  • name
  • primary_key
  • max_length
  • unique
  • blank
  • null
  • db_index
  • rel: Используется для связанных полей (например, ForeignKey). Только для продвинутого использования.
  • default
  • editable
  • serialize: Если False, поле не будет сериализовано, когда модель передаётся в сериализаторы Django. По умолчанию True.
  • unique_for_date
  • unique_for_month
  • unique_for_year
  • choices
  • help_text
  • db_column
  • db_tablespace: Только для создания индексов, если бэкенд поддерживает пространства имен таблиц. Обычно можно игнорировать этот параметр.
  • auto_created: True если поле было создано автоматически, например, для OneToOneField, используемого наследованием моделей. Только для продвинутого использования.

Все параметры без описания в вышеприведённом списке имеют такое же значение, как и для обычных полей Django. См. документацию по полям для примеров и деталей.

Распаковка полей

Противоположностью написанию вашего метода __init__() является написание метода deconstruct(). Этот метод сообщает 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)

Изменение базового класса пользовательского поля

Вы не можете изменить базовый класс пользовательского поля, так как Django не обнаружит изменения и не создаст миграцию для этого. Например, если вы начинаете с:

class CustomCharField(models.CharField):
    ...

и затем решаете, что хотите использовать TextField вместо этого, вы не можете изменить подкласс следующим образом:

class CustomCharField(models.TextField):
    ...

Вместо этого вы должны создать новый класс пользовательского поля и обновить ваши модели для ссылки на него:

class CustomCharField(models.CharField):
    ...

class CustomTextField(models.TextField):
    ...

Как обсуждалось в удалении полей, вы должны сохранить исходный класс CustomCharField до тех пор, пока у вас есть миграции, которые ссылаются на него.

Документирование пользовательского поля

Как всегда, вы должны документировать тип вашего поля, чтобы пользователи понимали, что это такое. Помимо предоставления для него docstring, что полезно для разработчиков, вы также можете позволить пользователям приложения администрирования видеть краткое описание типа поля через приложение django.contrib.admindocs. Для этого просто предоставьте описательный текст в атрибуте description вашего пользовательского поля. В приведённом выше примере описание, отображаемое приложением admindocs для HandField будет ‘Колода карт (стиль бридж)’.

В отображении приложения django.contrib.admindocs, описание поля интерполируется с field.__dict__, что позволяет описанию включать аргументы поля. Например, описание для CharField:

description = _("String (up to %(max_length)s)")

Полезные методы

После создания подкласса Field, вы можете рассмотреть возможность переопределения нескольких стандартных методов, в зависимости от поведения вашего поля. Список методов ниже приблизительно в порядке убывания важности, поэтому начните с верхнего.

Пользовательские типы баз данных

Предположим, вы создали пользовательский тип PostgreSQL под названием mytype. Вы можете создать подкласс 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. Простейший способ обработки этого в методе db_type() заключается в проверке атрибута connection.settings_dict['ENGINE'].

Например:

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(). Это заставит код создания SQL Django пропустить это поле. Вы затем сами отвечаете за создание столбца в нужной таблице другим способом, но это даёт вам способ указать Django «отойти в сторону».

Преобразование значений в объекты Python

Исторически Django предоставлял метакласс, называемый SubfieldBase, который всегда вызывал to_python() при присваивании. Это не работало хорошо с пользовательскими преобразованиями базы данных, агрегированием или запросами значений, поэтому он был заменён на from_db_value().

Если ваш пользовательский класс Field работает со структурами данных, более сложными, чем строки, даты, целые числа или числа с плавающей запятой, тогда вам может потребоваться переопределить from_db_value() и to_python().

Если присутствует для подкласса поля, from_db_value() будет вызываться во всех случаях, когда данные загружаются из базы данных, включая агрегаты и values() вызовы.

to_python() вызывается при десериализации и во время метода clean(), используемого из форм.

В качестве общего правила, to_python() должен корректно обрабатывать любые из следующих аргументов:

  • Экземпляр соответствующего типа (например, Hand в нашем текущем примере).
  • Строка
  • None (если поле допускает null=True).

В нашем HandField классе, мы храним данные как поле VARCHAR в базе данных, поэтому нам нужно уметь обрабатывать строки и None в from_db_value(). В to_python(), нам также нужно обработать экземпляры Hand.

import re

from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import ugettext_lazy as _

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.

Преобразование объектов Python в значения запроса

Поскольку использование базы данных требует преобразования в обе стороны, если вы переопределяете 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(). Например, Django использует этот метод для DateTimeField, чтобы правильно установить атрибут в случае auto_now или auto_now_add.

Если вы переопределите этот метод, вы должны вернуть значение атрибута в конце. Вы также должны обновить атрибут модели, если внесли какие-либо изменения в значение, чтобы код, содержащий ссылки на модель, всегда видел правильное значение.

Подготовка значений для использования в поиске по базе данных

Как и при преобразовании значений, подготовка значения для поиска по базе данных — это двухэтапный процесс.

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() родительского класса.

END_OF_DOCUMENT_MARKER

Если вам нужно реализовать get_db_prep_save(), обычно вам потребуется реализовать get_prep_lookup(). Если нет, get_prep_value() будет вызван реализацией по умолчанию для управления exact, gt, gte, lt, lte, in и in операциями поиска.

Возможно, вам также захочется реализовать этот метод для ограничения типов поиска, которые могут использоваться с вашим типом пользовательского поля.

Обратите внимание, что для "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(). Лучший способ получить значение поля до сериализации — использовать value_from_object(). Например, поскольку наш HandField использует строки для хранения данных, мы можем повторно использовать некоторый существующий код преобразования:

class HandField(models.Field):
    # ...

    def value_to_string(self, obj):
        value = self.value_from_object(obj)
        return self.get_prep_value(value)

Некоторые общие рекомендации

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

  1. Обратите внимание на существующие поля Django (в django/db/models/fields/__init__.py). Попытайтесь найти поле, похожее на то, что вам нужно, и немного расширить его, вместо того, чтобы создавать совершенно новое поле с нуля.
  2. Добавьте метод __str__() (__unicode__() на Python 2) в класс, который вы оборачиваете как поле. В большом количестве мест по умолчанию код поля вызывает force_text() над значением. (В наших примерах в этом документе value будет экземпляром Hand, а не HandField). Таким образом, если ваш метод __str__() (__unicode__() на Python 2) автоматически преобразует значение в строковый вид вашего Python-объекта, вы можете сэкономить много работы.

Написание подкласса FileField

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

Django предоставляет класс File, который используется как прокси для содержимого и операций с файлом. Его можно расширить для настройки доступа к файлу и доступных методов. Он расположен в django.db.models.fields.files, и его поведение по умолчанию описано в документации по файлам.

После создания подкласса File, новый подкласс FileField должен быть уведомлен об использовании. Для этого просто назначьте новый подкласс File специальному атрибуту attr_class подкласса FileField.

Несколько рекомендаций

В дополнение к вышеупомянутым деталям есть несколько рекомендаций, которые могут значительно повысить эффективность и читаемость кода поля.

  1. Исходный код собственного ImageField Django (в django/db/models/fields/files.py ) — отличный пример того, как наследоваться от FileField для поддержки определённого типа файла, поскольку он включает в себя все описанные выше техники.
  2. Кэшируйте атрибуты файлов, где это возможно. Поскольку файлы могут храниться в удалённых системах хранения, их извлечение может занимать дополнительное время или даже деньги, что не всегда необходимо. Как только файл был извлечён для получения некоторых данных о его содержимом, кэшируйте как можно больше этих данных, чтобы сократить количество обращений к файлу при последующих вызовах за этой информацией.

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.9/howto/custom-model-fields/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API