Spec-Zone.ru › Django 3.0

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

Введение

Документация справочника по моделям объясняет, как использовать стандартные классы полей 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 <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().__init__(*args, **kwargs)

Наш класс HandField принимает большинство стандартных параметров поля (см. список ниже), но мы гарантируем, что он имеет фиксированную длину, так как ему нужно хранить только 52 значения карт плюс их масти; 104 символа в общей сложности.

Примечание

Многие поля моделей Django принимают параметры, с которыми они ничего не делают. Например, вы можете передать и editable, и auto_now в django.db.models.DateField, и он проигнорирует параметр editable (auto_now подразумевает editable=False). В этом случае ошибка не возникает.

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

Метод Field.__init__() принимает следующие параметры:

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

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

Деконструкция поля

Противоположность методу __init__() — это метод deconstruct(). Он используется во время миграций моделей, чтобы сообщить Django, как преобразовать экземпляр вашего нового поля в сериализованную форму — в частности, какие аргументы передать в __init__() для его повторного создания.

Если вы не добавили дополнительных параметров по сравнению с наследуемым полем, то нет необходимости писать новый метод deconstruct(). Однако, если вы изменяете аргументы, передаваемые в __init__() (как мы делаем в HandField), вам нужно дополнить передаваемые значения.

deconstruct() возвращает кортеж из четырёх элементов: имя атрибута поля, полный путь импорта класса поля, позиционные аргументы (в виде списка) и ключевые аргументы (в виде словаря). Обратите внимание, что это отличается от метода deconstruct() для пользовательских классов, который возвращает кортеж из трёх элементов.

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

Например, в нашем классе HandField мы всегда принудительно устанавливаем max_length в __init__(). Метод deconstruct() базового класса Field увидит это и попытается вернуть его в ключевые аргументы; таким образом, мы можем убрать его из ключевых аргументов для улучшения читабельности:

from django.db import models

class HandField(models.Field):

    def __init__(self, *args, **kwargs):
        kwargs['max_length'] = 104
        super().__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)

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

Вы не можете изменить базовый класс пользовательского поля, потому что 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 в значения запроса

Поскольку использование базы данных требует преобразования в обе стороны, если вы переопределяете 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(). Например, 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().formfield(**defaults)

Это предполагает, что мы импортировали класс поля MyFormField (который имеет собственный виджет по умолчанию). Этот документ не охватывает детали написания пользовательских полей формы.

Эмуляция встроенных типов полей

Если вы создали метод db_type(), вам не нужно беспокоиться о get_internal_type() — он вряд ли будет использоваться. Иногда, однако, хранение данных в базе данных подобно типу другого поля, поэтому вы можете использовать логику этого другого поля для создания нужного столбца.

Например:

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

    def get_internal_type(self):
        return 'CharField'

Независимо от используемой базы данных, это означает, что migrate и другие SQL-команды создают правильный тип столбца для хранения строки.

Если get_internal_type() возвращает строку, которая не известна Django для используемой базы данных – то есть, она не появляется в django.db.backends.<db_name>.base.DatabaseWrapper.data_types – строка всё равно будет использоваться сериализатором, но метод по умолчанию db_type() вернёт None. Обратитесь к документации db_type() для понимания, почему это может быть полезно. Вставка описательной строки в качестве типа поля для сериализатора – полезная идея, если вы планируете использовать вывод сериализатора в другом месте, вне Django.

Преобразование данных поля для сериализации

Чтобы настроить способ сериализации значений сериализатором, вы можете переопределить value_to_string(). Использование value_from_object() – лучший способ получить значение поля до сериализации. Например, так как HandField использует строки для хранения данных, мы можем повторно использовать некоторый существующий код преобразования:

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

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

Общие рекомендации

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

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

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

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

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

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

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

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

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

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

Spec-Zone.ru

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