Spec-Zone.ru › Django 5.2

Как создавать пользовательские поля модели

Введение

В документации по ссылке на модели объясняется, как использовать стандартные классы полей Django – CharField, DateField и т.д. Для многих целей этих классов будет достаточно. Однако иногда версии Django не будут соответствовать вашим точным требованиям, или вы захотите использовать поле, которое полностью отличается от тех, что поставляются с Django.

Встроенные типы полей Django не охватывают все возможные типы столбцов базы данных – только распространенные типы, такие как VARCHAR и INTEGER. Для более необычных типов столбцов, таких как географические полигоны или даже созданные пользователем типы, такие как пользовательские типы PostgreSQL, вы можете определить свои собственные подклассы Field Django.

В качестве альтернативы, у вас может быть сложный объект 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 при создании класса модели (точные детали того, как это делается, здесь не важны). Это потому, что классы полей не нужны, когда вы просто создаете и изменяете атрибуты. Вместо этого они предоставляют механизм для преобразования значения атрибута и того, что хранится в базе данных или отправляется в сериализатор.

Помните об этом при создании собственных пользовательских полей. Подкласс Field Django, который вы пишете, предоставляет механизм для преобразования ваших экземпляров 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)

Атрибуты поля, не влияющие на определение столбца базы данных

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

Некоторые типы столбцов баз данных принимают параметры, такие как 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), а затем делегировать дальнейшую обработку родительскому классу. Это может потребовать написания пользовательского поля формы (и даже виджета формы). См. документацию по формам для получения информации об этом.

Если вы хотите исключить поле из ModelForm, вы можете переопределить метод formfield(), чтобы вернуть None.

Продолжая наш текущий пример, мы можем написать метод formfield() как:

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

    def formfield(self, **kwargs):
        # Exclude the field from the ModelForm when some condition is met.
        some_condition = kwargs.get("some_condition", False)
        if some_condition:
            return None

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

Spec-Zone.ru

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