Создание пользовательских полей модели
Введение
Документация по моделям объясняет, как использовать стандартные типы полей Django — CharField, DateField и др. Для многих задач этих типов достаточно. Однако иногда стандартные типы Django не удовлетворяют специфическим требованиям или требуется поле, отличное от тех, что поставляются с Django.
Встроенные типы полей Django не охватывают все возможные типы столбцов базы данных — только распространённые, такие как VARCHAR и INTEGER. Для более редких типов столбцов, таких как геометрические полигоны или пользовательские типы, например, пользовательские типы PostgreSQL, можно определять собственные подклассы Django Field.
Также у вас может быть сложное Python-объект, который можно как-то сериализовать, чтобы он поместился в стандартный тип столбца базы данных. В этом случае подкласс Field поможет вам использовать этот объект в ваших моделях.
Наш пример объекта
Создание пользовательских полей требует некоторой внимательности к деталям. Для большей наглядности мы будем использовать пример, описывающий раздачу карт в игре бридж. Вам не обязательно уметь играть в бридж, чтобы понять пример. Вам нужно лишь знать, что 52 карты случайным образом раздаются четырём игрокам, традиционно называемым север, восток, юг и запад. Наш класс выглядит примерно так:
class Hand:
"""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().__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 serializers. По умолчанию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().__init__(*args, **kwargs)
def deconstruct(self):
name, path, args, kwargs = super().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().__init__(*args, **kwargs)
def deconstruct(self):
name, path, args, kwargs = super().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().__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'
Преобразование значений в объекты 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 gettext_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):
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().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().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__()в класс, который вы оборачиваете как поле. Существует много мест, где по умолчанию код поля вызываетstr()для значения. (В наших примерах в этом документеvalueбудет экземпляромHand, а неHandField). Поэтому, если ваш метод__str__()автоматически преобразует объект 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/2.1/howto/custom-model-fields/