Spec-Zone.ru › Django 2.2

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

Введение

Документация по моделям объясняет, как использовать стандартные классы полей 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 serializers. По умолчанию 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 в значения запроса

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

  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/2.2/howto/custom-model-fields/

Spec-Zone.ru

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