Написание пользовательских полей модели
Введение
Документация по моделям базы данных описывает, как использовать стандартные классы полей 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_namenameprimary_keymax_lengthuniqueblanknulldb_index-
rel: Используется для связанных полей (например,ForeignKey). Только для продвинутого использования. defaulteditable-
serialize: ЕслиFalse, поле не будет сериализовано, когда модель передаётся в сериализаторы Django's сериализаторы. По умолчаниюTrue. unique_for_dateunique_for_monthunique_for_yearchoiceshelp_textdb_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 будет ‘A hand of cards (bridge style)’.
В представлении 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() и rel_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, чтобы он отошёл в сторону.
Метод rel_db_type() вызывается такими полями, как ForeignKey и OneToOneField, которые указывают на другое поле, чтобы определить типы данных столбцов базы данных. Например, если у вас есть UnsignedAutoField, вам также нужны внешние ключи, которые указывают на это поле, чтобы использовать тот же тип данных:
# MySQL unsigned integer (range 0 to 4294967295).
class UnsignedAutoField(models.AutoField):
def db_type(self, connection):
return 'integer UNSIGNED AUTO_INCREMENT'
def rel_db_type(self, connection):
return 'integer UNSIGNED'
Метод rel_db_type() был добавлен.
Преобразование значений в объекты Python
Если ваш пользовательский класс 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.
Если вы переопределяете этот метод, вы должны вернуть значение атрибута в конце. Вы также должны обновить атрибут модели, если внесёте изменения в значение, чтобы код, хранящий ссылки на модель, всегда видел правильное значение.
Указание поля формы для поля модели
Для настройки поля формы, используемого 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 и форматами данных базы данных и сериализации. Вот несколько советов, чтобы упростить задачу:
- Обратите внимание на существующие поля Django (в
django/db/models/fields/__init__.py) для получения вдохновения. Попробуйте найти поле, аналогичное тому, что вам нужно, и немного расширить его, вместо того, чтобы создавать совершенно новое поле с нуля. - Добавьте метод
__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.
Несколько рекомендаций
В дополнение к вышеуказанным деталям существуют несколько рекомендаций, которые могут значительно повысить эффективность и читаемость кода поля.
- Исходный код собственного поля
ImageFieldDjango (вdjango/db/models/fields/files.py) является отличным примером того, как подклассироватьFileFieldдля поддержки определенного типа файла, поскольку он включает в себя все описанные выше методы. - Кэшировать атрибуты файла, где это возможно. Поскольку файлы могут храниться в удаленных системах хранения, их извлечение может занимать дополнительное время или даже деньги, которые не всегда необходимы. После извлечения файла для получения некоторых данных о его содержимом, кэшируйте как можно больше этих данных, чтобы уменьшить количество обращений к файлу при последующих запросах этой информации.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.10/howto/custom-model-fields/