Создание пользовательских полей модели
Введение
Документация по моделям описывает использование стандартных классов полей 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 <Something>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. По умолчанию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 будет ‘Колода карт (в стиле бридж)’.
В отображении приложения 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(). Например, DateTimeField Django использует этот метод для корректной установки атрибута в случае 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.11/howto/custom-model-fields/