Cómo crear campos de modelo personalizados

Introducción

La documentación del referencia de modelos explica cómo utilizar las clases de campo estándar de Django – CharField, DateField, etc. Para muchos fines, esas clases son todas lo que necesitarás. A veces, sin embargo, la versión de Django no cumplirá con tus requisitos precisos o desecharás utilizar un campo que sea completamente diferente a los embarcados con Django.

Los tipos de campo integrados de Django no cubren todos los posibles tipos de columna de base de datos – solo los tipos comunes, como VARCHAR y INTEGER. Para columnas más obscuras, como polígonos geográficos o incluso tipos creados por el usuario como tipos personalizados de PostgreSQL, puedes definir tus propios subclases de Django Field.

Alternativamente, es posible que tengas un objeto Python complejo que pueda ser serializado para ajustarse a un tipo de columna de base de datos estándar. Este es otro caso en el que una subclase de Field te ayudará a utilizar tu objeto con tus modelos.

Nuestro ejemplo de objeto

Crear campos personalizados requiere un poco de atención al detalle. Para hacer las cosas más fáciles de seguir, utilizaremos un ejemplo consistente a lo largo de este documento: envolver un objeto Python que representa el trato de cartas en una mano de Bridge. No te preocupes, no necesitas saber cómo jugar Bridge para seguir este ejemplo. Solo necesitas saber que 52 cartas se reparten igualmente entre cuatro jugadores, quienes tradicionalmente se llaman norte, este, sur y oeste. Nuestra clase parece algo así:

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) ...

Esto es una clase Python ordinaria, con nada específico de Django. Queremos poder hacer cosas como esto en nuestros modelos (asumimos que el atributo hand del modelo es una instancia de Hand):

example = MyModel.objects.get(pk=1)
print(example.hand.north)

new_hand = Hand(north, east, south, west)
example.hand = new_hand
example.save()

Asignamos a y recuperamos desde el atributo hand de nuestro modelo igual que cualquier otra clase Python. La trampa es decirle a Django cómo manejar la guardado y carga de tal objeto.

Para utilizar la clase Hand en nuestros modelos, no tenemos que cambiar esta clase en absoluto. Esto es ideal, porque significa que puedes escribir fácilmente el soporte de modelo para clases existentes donde no puedas cambiar el código fuente.

Nota

Puedes estar solo deseando aprovechar los tipos de columna de base de datos personalizados y tratar los datos como tipos estándar de Python en tus modelos; cadenas, o flotantes, por ejemplo. Este caso es similar a nuestro ejemplo Hand y notaremos cualquier diferencia a medida que avanzamos.

Teoría de fondo

Almacenamiento de base de datos

Comencemos con los campos de modelo. Si lo desglosamos, un campo de modelo proporciona una forma de tomar un objeto Python normal – cadena, booleano, datetime, o algo más complejo como Hand – y convertirlo a y desde un formato que es útil cuando se trabaja con la base de datos. (Un tal formato también es útil para la serialización, pero como veremos más adelante, eso es más fácil una vez que tengas el lado de la base de datos bajo control).

Los campos de un modelo deben ser convertidos de alguna manera para ajustarse a un tipo de columna existente en la base de datos. Los diferentes sistemas de bases de datos proporcionan conjuntos distintos de tipos de columnas válidos, pero la regla sigue siendo la misma: son los únicos tipos con los que debes trabajar. Cualquier cosa que desees almacenar en la base de datos debe ajustarse a uno de esos tipos.

Normalmente, estás escribiendo un campo de Django para coincidir con un tipo específico de columna de base de datos, o necesitarás una forma de convertir tus datos a, por ejemplo, una cadena.

Para nuestro ejemplo de Hand, podríamos convertir los datos de la carta a una cadena de 104 caracteres concatenando todas las cartas en un orden predeterminado – digamos, primero las cartas norte, luego las este, sur y oeste. Así, objetos Hand pueden ser guardados en columnas de texto o caracteres en la base de datos.

¿Qué hace una clase de campo?

Todas las propiedades de Django (y cuando decimos propiedades en este documento, siempre nos referimos a campos de modelo y no campos de formulario ) son subclases de django.db.models.Field. La mayoría de la información que Django registra sobre un campo es común a todos los campos – nombre, texto de ayuda, unicidad y demás. Almacenar toda esa información se gestiona mediante Field. Entraremos en detalles precisos de lo que puede hacer Field más adelante; por ahora, basta con decir que todo desciende de Field y luego personaliza piezas clave del comportamiento de la clase.

Es importante darse cuenta de que una clase de campo Django no es lo que se almacena en tus atributos del modelo. Los atributos del modelo contienen objetos Python normales. Las clases de campo que defines en un modelo se almacenan realmente en la clase Meta cuando se crea la clase del modelo (los detalles precisos de cómo se hace esto son innecesarios aquí). Esto es porque las clases de campo no son necesarias cuando solo estás creando y modificando atributos. En su lugar, proporcionan la maquinaria para convertir entre el valor del atributo y lo que se almacena en la base de datos o se envía a un serializador.

Ten en cuenta esto cuando estés creando tus propios campos personalizados. La clase Field de Django que escribas proporciona la maquinaria para convertir entre tus instancias Python y los valores de base de datos/serializador de varias maneras (hay diferencias entre almacenar un valor y utilizarlo para consultas, por ejemplo). Si esto suena un poco complicado, no te preocupes – se aclarará en las siguientes ejemplos. Solo recuerda que a menudo terminarás creando dos clases cuando quieras un campo personalizado:

  • La primera clase es el objeto Python con el que manipularán tus usuarios. Les asignarán a los atributos del modelo, leerán de ellos para fines de visualización, cosas así. Esta es la clase Hand en nuestro ejemplo.

  • La segunda clase es la subclase Field. Esta es la clase que sabe cómo convertir tu primera clase entre su forma de almacenamiento permanente y la forma Python.

Escribir una subclase de campo

Cuando estés planeando tu subclase de Field, primero piensa en qué clase de campo existente de Django es a la que tu nuevo campo se parece más. ¿Puedes heredar de un campo existente de Django y ahorrarte trabajo? Si no, deberías heredar directamente de la clase Field, de la cual todo desciende.

Iniciar tu nuevo campo es una cuestión de separar los argumentos que son específicos de tu caso de los argumentos comunes y pasar los últimos a la método __init__() de Field (o tu clase padre).

En nuestro ejemplo, llamaremos a nuestro campo HandField. (Es una buena idea llamar a tu subclase de Field <Algo>Field, para que sea fácilmente identificable como una subclase de Field). No se comporta como ningún campo existente, por lo que heredaremos directamente de la clase 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)

Nuestro HandField acepta la mayoría de las opciones de campos estándar (ver la lista a continuación), pero aseguramos que tiene una longitud fija, ya que solo necesita contener 52 valores de cartas más sus trajes; 104 caracteres en total.

Nota

Muchos de los campos de modelo de Django aceptan opciones que no hacen nada con ellas. Por ejemplo, puedes pasar tanto editable como auto_now a un campo DateField y lo ignorará el parámetro editable (auto_now establecido implica editable=False). No se levanta ningún error en este caso.

Este comportamiento simplifica las clases de campo, ya que no necesitan comprobar opciones que no son necesarias. Pasan todas las opciones a la clase padre y luego no las utilizan más adelante. Es cuestión tuya si quieres que tus campos sean más estrictos sobre las opciones que seleccionan o si prefieres utilizar el comportamiento más permisivo de los campos actuales.

El método Field.__init__() toma los siguientes parámetros:

Todas las opciones sin explicación en la lista anterior tienen el mismo significado que para los campos Django normales. Consulta la documentación del campo <ref/models/fields> para ejemplos y detalles.

Descomposición de campos

El contrapunto a escribir tu método __init__() es escribir el método deconstruct(). Se utiliza durante las migraciones de modelos para decirle a Django cómo tomar una instancia de tu nuevo campo y reducirla a una forma serializada - en particular, qué argumentos pasar a __init__() para recrearla.

Si no has agregado ninguna opción adicional encima del campo que heredaste, entonces no hay necesidad de escribir un nuevo método deconstruct(). Si, sin embargo, estás cambiando los argumentos pasados en __init__() (como estamos haciendo en HandField), deberás suplementar los valores que se están pasando.

deconstruct() devuelve una tupla de cuatro elementos: el nombre del atributo del campo, la ruta de importación completa de la clase del campo, los argumentos posicionales (como una lista) y los argumentos clave (como un diccionario). Tenga en cuenta que esto es diferente al método deconstruct() para clases personalizadas que devuelve una tupla de tres cosas.

Como autor de campos personalizados, no necesitas preocuparte por los primeros dos valores; la clase base Field tiene todo el código para trabajar el nombre del atributo y la ruta de importación. Sin embargo, debes cuidar de los argumentos posicionales y clave, ya que estos son probablemente las cosas que estás cambiando.

Por ejemplo, en nuestra clase HandField siempre estamos estableciendo max_length en __init__() de manera forzosa. El método deconstruct() en la clase base Field verá esto y tratará de devolverlo en los argumentos clave; así que podemos eliminarlo de los argumentos clave para legibilidad:

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

Si agregas un nuevo argumento clave, necesitarás escribir código en deconstruct() que ponga su valor en kwargs tú mismo. También debes omitir el valor de kwargs cuando no sea necesario reconstruir el estado del campo, como cuando se utiliza el valor por defecto:

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

Los textos traducidos son:

Presta especial atención si estableces nuevos valores por defecto para los argumentos en la superclase Field; quieres asegurarte de que siempre estén incluidos, en lugar de desaparecer si toman el valor por defecto antiguo.

Además, trata de evitar devolver valores como argumentos posicionales; donde sea posible, devuelve los valores como argumentos clave para una mayor compatibilidad futura. Si cambias más a menudo los nombres de las cosas que su posición en la lista de argumentos del constructor, puedes preferir los argumentos posicionales, pero ten en cuenta que las personas estarán reconstruyendo tu campo desde la versión serializada durante un buen rato (posiblemente años), dependiendo de cuánto tiempo viva tu migración.

Puedes ver los resultados de la descomposición mirando en las migraciones que incluyen el campo, y puedes probar la descomposición en pruebas unitarias descomponiendo y reconstruyendo el campo:

name, path, args, kwargs = my_field_instance.deconstruct()
new_instance = MyField(*args, **kwargs)
self.assertEqual(my_field_instance.some_attribute, new_instance.some_attribute)

Atributos del Field que no afectan a la definición de columna de la base de datos

Puedes sobreescribir Field.non_db_attrs para personalizar atributos de un campo que no afectan a una definición de columna. Se utiliza durante las migraciones de modelos para detectar operaciones AlterField sin efecto.

Por ejemplo:

class CommaSepField(models.Field):
    @property
    def non_db_attrs(self):
        return super().non_db_attrs + ("separator",)

Cambiar la clase base de un campo personalizado

No puedes cambiar la clase base de un campo personalizado porque Django no detectará el cambio y creará una migración para él. Por ejemplo, si comienzas con:

class CustomCharField(models.CharField): ...

y luego decides que quieres usar TextField en lugar de eso, no puedes cambiar la subclase como sigue:

class CustomCharField(models.TextField): ...

En su lugar, debes crear una nueva clase de campo personalizado y actualizar tus modelos para que lo refieran.

class CustomCharField(models.CharField): ...


class CustomTextField(models.TextField): ...

Debido a lo discutido en eliminación de campos, debes mantener la clase original CustomCharField mientras tengas migraciones que se refieran a ella.

Documentando su campo personalizado

Siempre debes documentar el tipo de campo, para que los usuarios sepan qué es. Además de proporcionar una cadena de documentación para él, lo cual es útil para los desarrolladores, también puedes permitir a los usuarios del aplicativo administrativo ver una breve descripción del tipo de campo mediante la aplicación django.contrib.admindocs. Para hacer esto proporciona texto descriptivo en un atributo de clase description de tu campo personalizado. En el ejemplo anterior, la descripción mostrada por la aplicación admindocs para un HandField será “Una mano de cartas (estilo bridge)”.

En la django.contrib.admindocs se intercala la descripción del campo con field.__dict__, lo que permite a la descripción incorporar argumentos del campo. Por ejemplo, la descripción para CharField es:

description = _("String (up to %(max_length)s)")

Métodos útiles

Una vez que hayas creado tu subclase de Field, podrías considerar sobreescribir unos pocos métodos estándar, dependiendo del comportamiento de tu campo. La lista de métodos a continuación se encuentra en aproximadamente orden decreciente de importancia, así que comienza desde la parte superior.

Tipos de base de datos personalizados

Dile que has creado un tipo personalizado de PostgreSQL llamado mytype. Puedes heredar de Field y implementar el método db_type(), como se muestra a continuación:

from django.db import models


class MytypeField(models.Field):
    def db_type(self, connection):
        return "mytype"

Una vez que tengas MytypeField, puedes utilizarlo en cualquier modelo, exactamente como cualquier otro tipo de Field.

class Person(models.Model):
    name = models.CharField(max_length=80)
    something_else = MytypeField()

Si deseas construir una aplicación independiente de la base de datos, debes tener en cuenta las diferencias en los tipos de columnas de la base de datos. Por ejemplo, el tipo de columna de fecha/hora en PostgreSQL se llama timestamp, mientras que el mismo campo en MySQL se llama datetime. Puedes manejar esto mediante un método db_type() al verificar la propiedad connection.vendor. Los nombres de los proveedores actuales integrados son: sqlite, postgresql, mysql y oracle.

Por ejemplo:

class MyDateField(models.Field):
    def db_type(self, connection):
        if connection.vendor == "mysql":
            return "datetime"
        else:
            return "timestamp"

Los métodos db_type() y rel_db_type() se llaman por Django cuando el marco construye las sentencias CREATE TABLE para tu aplicación – es decir, cuando creas tus tablas por primera vez. Los métodos también se llaman al construir una cláusula WHERE que incluya el campo del modelo – es decir, cuando recuperas datos utilizando los métodos de QuerySet como get(), filter() y exclude() y tienes el campo del modelo como argumento.

Algunos tipos de columnas de la base de datos aceptan parámetros, como CHAR(25), donde el parámetro 25 representa la longitud máxima del campo. En casos como estos, es más flexible si se especifica el parámetro en el modelo en lugar de estar codificado en el método db_type(). Por ejemplo, no tendría mucho sentido tener un campo CharMaxlength25Field, como se muestra aquí:

# 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()

La mejor forma de hacer esto sería hacer que el parámetro sea especificable en tiempo de ejecución – es decir, cuando la clase se instancia. Para hacer eso, implementa Field.__init__(), como se muestra a continuación:

# 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)

Finalmente, si tu campo requiere una configuración SQL verdaderamente compleja, devuelve None desde db_type(). Esto hará que el código de creación de SQL de Django pase por alto este campo. Luego eres responsable de crear el campo en la tabla correcta de alguna otra manera, pero esto te da una forma de decirle a Django que se retire.

El método rel_db_type() se llama por campos como ForeignKey y OneToOneField que apuntan a otro campo para determinar sus tipos de datos de columna de la base de datos. Por ejemplo, si tienes un campo UnsignedAutoField, también necesitas las claves foráneas que apuntan a ese campo utilizar el mismo tipo de dato:

# 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"

Conversión de valores a objetos Python

Si tu clase personalizada Field maneja estructuras de datos más complejas que cadenas, fechas, enteros o flotantes, entonces puede que necesites sobreescribir los métodos from_db_value() y to_python().

Si está presente para la subclase del campo, from_db_value() se llamará en todas las circunstancias cuando el datos se cargan desde la base de datos, incluyendo en agregados y llamadas a values().

to_python() se llama por deserialización y durante el método clean() utilizado desde formularios.

Como un traductor técnico profesional, te proporciono las traducciones de los textos originales manteniendo todas sus etiquetas intactas.

  • Una instancia del tipo correcto (por ejemplo, Hand en nuestro ejemplo en curso).

  • Un string

  • None (si el campo permite null=True)

En nuestra clase HandField, estamos almacenando los datos como un campo VARCHAR en la base de datos, por lo que necesitamos poder procesar cadenas y None en from_db_value(). En to_python(), también debemos manejar instancias de 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)

Nota que siempre se devuelve una instancia de Hand desde estos métodos. Eso es el tipo de objeto Python que queremos almacenar en la atributo del modelo.

Para to_python(), si algo sale mal durante la conversión de valor, debes levantar un excepción ValidationError.

Conversión de objetos Python a valores de consulta

Dado que se requiere conversión en ambos sentidos al utilizar una base de datos, si sobreescribes el método from_db_value(), también debes sobreescribir el método get_prep_value() para convertir los objetos Python nuevamente a valores de consulta.

Por ejemplo:

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)]
        )

Advertencia

Si tu campo personalizado utiliza los tipos CHAR, VARCHAR o TEXT para MySQL, debes asegurarte de que get_prep_value siempre devuelve un tipo de cadena. MySQL realiza una coincidencia flexible y inesperada cuando se ejecuta una consulta en estos tipos y el valor proporcionado es un entero, lo que puede causar que las consultas incluyan objetos inesperados en sus resultados. Este problema no puede ocurrir si siempre devuelve un tipo de cadena desde get_prep_value.

Convierte los valores de consulta a valores de base de datos

Algunos tipos de datos (por ejemplo, fechas) necesitan estar en un formato específico antes de que puedan ser utilizados por un backend de base de datos. get_db_prep_value() es el método donde se deben realizar esas conversiones. La conexión específica que se utilizará para la consulta se pasa como parámetro connection. Esto permite utilizar lógica de conversión específica del backend si es necesario.

Por ejemplo, Django utiliza el siguiente método para su 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

En caso de que tu campo personalizado necesite una conversión especial cuando se guarde que no sea la misma que la utilizada para los parámetros de consulta normales, puedes sobreescribir get_db_prep_save().

Preprocesamiento de valores antes de guardar

Si deseas preprocesar el valor justo antes de guardar, puedes utilizar pre_save(). Por ejemplo, Django’s DateTimeField utiliza este método para establecer la atributo correctamente en el caso de auto_now o auto_now_add.

Si sobreescribes este método, debes devolver el valor del atributo al final. Debes actualizar también el atributo del modelo si haces algún cambio al valor para que el código que tenga referencias a la instancia del modelo siempre vea el valor correcto.

Especificar el campo de formulario para un campo de modelo

Para personalizar el campo de formulario utilizado por ModelForm, puedes sobreescribir formfield().

La clase del campo de formulario se puede especificar a través de los argumentos form_class y choices_form_class; el último se utiliza si el campo tiene opciones especificadas, el primero en caso contrario. Si no se proporcionan estos argumentos, se utilizarán CharField o TypedChoiceField.

Los textos traducidos son:

Si deseas excluir el campo del ModelForm, puedes sobrescribir el método formfield() para devolver None.

Continuando con nuestro ejemplo en curso, podemos escribir el método formfield() como:

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)

Esto asume que hemos importado una clase de campo de formulario personalizado MyFormField (que tiene su propio widget por defecto). Este documento no cubre los detalles de la creación de campos de formulario personalizados.

Emulando tipos de campos integrados

Si has creado un método db_type(), no necesitas preocuparte por get_internal_type() – no se utilizará mucho. A veces, sin embargo, tu almacenamiento en la base de datos es similar al tipo de algún otro campo, así que puedes usar esa lógica para crear el tipo de columna adecuado.

Por ejemplo:

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

    def get_internal_type(self):
        return "CharField"

Independientemente del backend de bases de datos que estemos utilizando, esto significará que migrate y otros comandos SQL creen el tipo de columna correcto para almacenar una cadena.

Si get_internal_type() devuelve una cadena que no es conocida por Django para el backend de bases de datos que estás utilizando – es decir, no aparece en django.db.backends.<db_name>.base.DatabaseWrapper.data_types – la cadena se utilizará aún así por el serializador, pero el método db_type() predeterminado devolverá None. Consulta la documentación del método db_type() para saber por qué esto puede ser útil. Colocar una cadena descriptiva como tipo de campo para el serializador es una buena idea si alguna vez vas a utilizar la salida del serializador en algún otro lugar, fuera de Django.

Conversión de datos de campos para serialización

Para personalizar cómo se serializan los valores por un serializador, puedes sobrescribir value_to_string(). Utilizando value_from_object() es la mejor manera de obtener el valor del campo antes de la serialización. Por ejemplo, ya que HandField utiliza cadenas para su almacenamiento de datos de cualquier forma, podemos reutilizar algún código de conversión existente:

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

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

Algunos consejos generales

Escribir un campo personalizado puede ser un proceso complicado, especialmente si estás realizando conversiones complejas entre tus tipos de Python y tus formatos de base de datos y serialización. Aquí tienes unos pocos consejos para que las cosas fluyan más suavemente:

  1. Mira los campos existentes de Django (en django/db/models/fields/__init__.py) para obtener inspiración. Intenta encontrar un campo similar a lo que quieres y extiéndelo un poco en lugar de crear un campo completamente nuevo desde cero.

  2. Pon un método __str__() en la clase que estás envolviendo como campo. Hay muchos lugares donde el comportamiento predeterminado del código del campo es llamar a str() sobre el valor. (En nuestros ejemplos en este documento, value sería una instancia de Hand, no un HandField). Si tu método __str__() convierte automáticamente la forma de cadena de tu objeto Python, puedes ahorrarte mucho trabajo.

Escribir una clase heredada de FileField

Además de los métodos anteriores, los campos que tratan con archivos tienen algunas otras exigencias especiales que deben tenerse en cuenta. La mayoría de las mecánicas proporcionadas por FileField, como el control del almacenamiento y recuperación de la base de datos, pueden permanecer sin cambios, dejando a las subclases manejar el desafío de apoyar un tipo particular de archivo.

Django proporciona una clase File, que se utiliza como proxy para los contenidos y operaciones del archivo. Esto puede heredarse para personalizar cómo se accede al archivo y qué métodos están disponibles. Vive en django.db.models.fields.files y su comportamiento predeterminado se explica en la documentación de archivos.

Una vez creada una subclase de File, la nueva clase heredada de FileField debe ser informada sobre ella. Para hacerlo, asigne la nueva subclase de File a la especialidad attr_class del atributo de la clase heredada de FileField.

Algunas sugerencias

Además de los detalles anteriores, hay unas pocas directrices que pueden mejorar grandemente la eficiencia y legibilidad del código del campo.

  1. El texto traducido es el siguiente:

  2. Almacena atributos de archivos donde sea posible. Dado que los archivos pueden estar almacenados en sistemas de almacenamiento remotos, recuperarlos puede costar tiempo adicional o incluso dinero, que no siempre es necesario. Una vez que se recupera un archivo para obtener algunos datos sobre su contenido, almacénate lo máximo posible de esos datos para reducir el número de veces que se debe recuperar el archivo en llamadas posteriores a esa información.