Как создать пользовательские поля модели
Введение
Документация справочника по моделям объясняет, как использовать стандартные классы полей Django – CharField, DateField и т. д. Для многих задач этих классов достаточно. Однако иногда версии Django не удовлетворяют вашим точным требованиям, или вам нужно использовать поле, которое полностью отличается от полей, поставляемых с Django.
Встроенные типы полей Django не покрывают все возможные типы столбцов базы данных — только общие типы, такие как VARCHAR и INTEGER. Для более специфических типов столбцов, таких как географические полигоны или даже созданные пользователем типы, например, пользовательские типы PostgreSQL, вы можете определить свои собственные подклассы Django Field.
В качестве альтернативы у вас может быть сложный объект Python, который можно каким-то образом сериализовать, чтобы он подошёл под стандартный тип столбца базы данных. Это ещё один случай, когда подкласс Field поможет вам использовать ваш объект в ваших моделях.
Наш пример объекта
Создание пользовательских полей требует внимательного отношения к деталям. Для лучшего понимания мы будем использовать последовательный пример в этом документе: обертка объекта Python, представляющего раздачу карт в партии бриджа. Не беспокойтесь, вам не нужно знать правила бриджа, чтобы понять этот пример. Вам нужно только знать, что 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 сериализаторов. По умолчанию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
Если вы добавите новый именованный аргумент, вам нужно написать код в deconstruct() , который поместит его значение в 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)
Атрибуты поля, не влияющие на определение столбца базы данных
Вы можете переопределить Field.non_db_attrs для настройки атрибутов поля, которые не влияют на определение столбца. Он используется во время миграций моделей для обнаружения операций AlterField без изменений.
Например:
class CommaSepField(models.Field):
@property
def non_db_attrs(self):
return super().non_db_attrs + ("separator",)
Изменение базового класса пользовательского поля
Вы не можете изменить базовый класс пользовательского поля, потому что 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.vendor. Текущие встроенные имена поставщиков: sqlite, postgresql, mysql, и oracle.
Например:
class MyDateField(models.Field):
def db_type(self, connection):
if connection.vendor == "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 в значения запроса
Поскольку использование базы данных требует преобразования в обоих направлениях, если вы переопределяете from_db_value(), вы также должны переопределить 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/4.2/howto/custom-model-fields/