Come creare model field personalizzati

Introduzione

La documentazione model reference spiega come usare i campi delle classi standard di Django – CharField, DateField, ecc. La maggior parte delle volte, queste classi sono tutto ciò di cui hai bisogno. A volte, però, la versione di Django non incontrerà precisamente i tuoi requisiti o vorrai usare un campo che è totalmente differente da quelli che sono forniti con Django.

I tipi di campi built-in di Django non coprono ogni possibile tipologia di colonna di database – sono i tipi più comuni, come VARCHAR e INTEGER. Per tipi di colonne più oscuri, come poligoni geografici o anche tipi creati dall’utente come i PostgreSQL custom types, puoi definire le tue sottoclassi di Django Field.

Alternativamente, puoi avere un oggetto Python complesso che può essere serializzato in qualche modo per essere accolto in un tipo di colonna standard di database. Questo è un ulteriore caso in cui una sottoclasse di Field ti aiuterà ad usare il tuo oggetto con i tuoi modelli.

Il nostro oggetto di esempio

Creare campi personalizzati richiede un po” di attenzione ai dettagli. Per rendere le cose più facili, in questo documento useremo un esempio consistente: avvolgere un oggetto Python che rappresenta il mazzo di carte in una mano di Bridge. Non preoccuparti, non è necessario che tu sappia come giocare a Bridge per seguire questo esempio. Devi sono sapere che 52 carte sono ripartite ugualmente per quattro giocatori, che sono tradizionalmente chiamati nord, est, sud e ovest. La nostra classe è fatta più o meno così:

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

Questa è una classe Python ordinaria, senza niente che sia specifico di Django. Ci piacerebbe fare cose come questa nel nostro modello (assumiamo che l’attributo «hand» sul modello sia istanza di Hand):

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

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

Assegniamo e ritiriamo l’attributo hand nel nostro modello proprio come qualsiasi altra classe Python. Il trucco è dire a Django come gestire il salvataggio ed il caricamento su e da questo oggetto.

Per usare la classe Hand nei nostri models, non dobbiamo cambiare assolutamente la classe. Questo è l’ideale, perchè significa che puoi scrivere facilmente modelli che supportano classi esistenti dove non puoi cambiare il codice sorgente.

Nota

Potresti anche solo voler trarre vantaggio dai tipi custom per le colonne di database ed avere a che fare con i dati come tipi standard di Python nei modelli; stringhe o float, per esempio. Questo caso è simile al nostro esempio Hand e terremo nota delle differenze andando avanti.

Teoria di Background

Stoccaggio DB

Cominciamo con i campi di model. Se lo riduci ai minimi termini, un campo model offre un modo di prendere un normale oggetto Python – stringa, booleano, datetime or qualcosa di più complesso come Hand – e convertirlo a e da un formato che è utile quando si ha a che fare con il database. (Questo formato è anche utile per la serializzazione ma, come vedremo più tardi, è una cosa facile una volta che hai sotto controllo il database).

I campi di un modello devono in qualche modo essere convertiti per stare in un tipo esistente di colonna di database. Diversi database offrono diversi set di tipi di colonna validi ma la regola è sempre la stessa: sono gli unici tipi con cui puoi lavorare. Qualsiasi cosa tu voglia scrivere sul database, deve poter essere rappresentato con quei tipi.

Normalmente, stai scrivendo un campo Django che sia consono per uno dei tipi di colonna del database oppure ti servirà un modo di convertire i tuoi dati, per dire, in una stringa.

For our Hand example, we could convert the card data to a string of 104 characters by concatenating all the cards together in a predetermined order – say, all the north cards first, then the east, south and west cards. So Hand objects can be saved to text or character columns in the database.

Cosa fa una classe di campo?

Tutti i campi Django (e quando parliamo di campi in questo documento, intendiamo sempre campi del modello e non form fields) sono sottoclassi di django.db.models.Field. La maggior parte delle informazioni che Django registra su un campo sono comuni per tutti i campi – nome, testo di aiuto, unicità e così via. La registrazione di tutte quelle informazioni è gestita da Field. Ci addentreremo nei dettagli di ciò che Field può fare più tardi; per ora, è sufficiente dire che ogni cosa discende da Field e poi personalizza pezzi chiave del comportamento della classe.

E” importante capire che una classe per un campo di Django non è quel che è salvato negli attributi del model. Gli attributi del model contengono oggetti Python normali. Le classi per i campi che definisci in un model sono salvati nella classe Meta quando la classe del model viene creata (i dettagli precisi di come questo avvenga ora non sono importanti). Questo accade perchè le classi dei campi non sono necessarie quando stai creando o modificando attributi. Invece, offrono i meccanismi per convertire il valore dell’attributo in quel che viene salvato sul database o mandato al serializzatore.

Tieni a mente questo quando crei i tuoi campi personalizzati. La sottoclasse Field di Django che scrivi offre i meccanismi per la conversione tra le tue istanze di Python ed i valori del database/serializzatore in diversi modi (ci sono differenze tra salvare un valore ed usarlo per un lookup, per esempio). Se ti sembra un po” complicato, non preoccuparti – diverrà più chiaro negli esempi che seguono. Ricordati solo che finirai spesso a creare due classi quando vuoi un campo personalizzato:

  • La prima classe è l’oggetto Python che i tuoi utenti manipoleranno. Lo assegneranno all’attributo model, leggeranno da esso per la visualizzazione, cose del genere. Questa è la classe Hand nel nostro esempio.
  • La seconda classe è la sottoclasse Field. Questa è la classe che sa come riconvertire la tua prima classe avanti ed indietro tra la sua forma di salvataggio permanente e la sua forma in Python.

Scrivere una sottoclasse di campo

Quando pianifichi la tua sottoclasse Field, prova a pensare a quale classe Field esistente somigli. Puoi fare una sottoclasse di un campo Django esistente e risparmiarti un po” di lavoro? Se non puoi, dovresti fare una sottoclasse di Field, dalla quale tutto discende.

Inizializzare il tuo nuovo campo è questione di fare una separazione tra gli argomenti che sono specifici per il tuo caso da quelli comuni e passare questi ultimi nel metodo __init__() di Field (o della tua classe padre).

Nel nostro esempio, chiameremo il nostro campo HandField. (E” una buona idea chiamare la tua sottoclasse di Field <Something>Field, così che sia facilmente identificabile come Field subclass.). Non deve comportarsi come alcun campo esistente, così faremo direttamente una sottoclasse di 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)

Il nostro HandField accetta la maggior parte delle opzioni standard per un campo (vedi la lista qui sotto) ma ci assicuriamo che abbia una lunghezza fissa, perchè necessita di tenere solo i valori delle 52 carte più i loro semi; 104 caratteri in tutto.

Nota

Molti dei modelli di Django accettano opzioni con le quali poi non fanno niente. Per esempio, puoi passare sia editable che auto_now ad una django.db.models.DateField ed ignorerà il parametro editable (impostare auto_now implica editable=False). Non viene sollevato alcun errore in questo caso.

Questo comportamento semplifica le classi per i campi perchè non hanno bisogno di controllare le opzioni che non sono necessarie. Passano tutte le opzioni al padre e poi non le usano. Sta a te essere più o meno esigente riguardo alle opzioni che selezionano o utilizzare il comportamento più permissivo per i campi correnti.

Il metodo Field.__init__() accetta i seguenti parametri:

Tutte le opzioni che non hanno una spiegazione nella lista precedente hanno lo stesso significato che avrebbero nei normali campi Django. Vedi la documentazione dei campi per esempi e dettagli.

Scomposizione di un campo

The counterpoint to writing your __init__() method is writing the deconstruct() method. It’s used during model migrations to tell Django how to take an instance of your new field and reduce it to a serialized form - in particular, what arguments to pass to __init__() to recreate it.

Se non hai aggiunto opzioni extra sul campo dal quale hai ereditato, allora non c’è bisogno di scrivere un nuovo metodo deconstruct(). Se, comunque, stai cambiando gli argomenti da passare ad __init__() (come stiamo facendo in HandField), avrai bisogno di integrare i valori da passare.

deconstruct() restituisce una tupla di 4 elementi: il nome dell’attributo del campo, l’import path completo della classe del campo, i positional arguments (come lista) ed i keyword arguments (come dizionario). Nota che questo è differente dal metodo deconstruct() per la classi custom che restituisce una tupla di tre cose.

Come autore di un campo custom, non devi preoccuparti dei primi due valori; la classe di base Field ha tutto il codice per gestire l’attributo relativo al nome del campo ed al percorso di importazione. Devi, in ogni caso, fare attenzione ai positional e keyword arguments, perchè molto probabilmente sono quelli che stai cambiando.

Per esempio, nella nostra classe HandField stiamo già forzosamente impostando max_length in __init__(). Il metodo deconstruct() sulla classe base Field ne terrà conto e cercherà di restituirlo tra i keyword arguments; quindi, possiamo eliminarlo dai keyword arguments per questioni di leggibilità:

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

Se aggiungi un nuovo keyword argument, dovrai scrivere da te del codice in deconstruct() che metta il suo valore in kwargs. Dovresti anche omettere il valore da kwargs  quando non sia necessario ricostruire lo stato del campo, per esempio quando se ne usa il valore predefinito:

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

Esempi più complessi sono oltre le finalità di questo documento ma ricorda - per ogni configurazione della tua istanza di Field, deconstruct() deve restituire argomenti che si possano passare ad __init__ per ricostruire lo stato.

Fai particolarmente attenzione se imposti nuovi valori di default nella superclasse di Field; vuoi essere sicuro che siano sempre inclusi piuttosto che sparire se dovessero riprendere il vecchio valore di default.

Inoltre, cerca di evitare di restituire valori come positional arguments; dove possibile, restituisci valori come argomenti keyword per massimizzare la compatibilità futura. Se cambi il nome delle cose più spesso della loro posizione nella lista di argomenti del costruttore, potresti preferire i positional ma tieni in mente che molte persone ricostruiranno il campo a partire dalla versione serializzata per un po” (anche anni), dipendentemente da quanto a lungo vivranno le tue migrazioni.

Puoi osservare i risultati della decostruzione guardando nelle migrazioni che includono il campo, e puoi testare la decostruzione negli unit test, decostruendo e ricostruendo il 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)

Field attributes not affecting database column definition

New in Django 4.1.

You can override Field.non_db_attrs to customize attributes of a field that don’t affect a column definition. It’s used during model migrations to detect no-op AlterField operations.

Per esempio:

class CommaSepField(models.Field):

    @property
    def non_db_attrs(self):
        return super().non_db_attrs + ("separator",)

Cambiare la classe base di un campo personalizzato

Non puoi cambiare la classe base di un campo custom perchè Django non si accorgerà del cambiamento e non potrà creare una migrazione di conseguenza. Per esempio, se cominci con:

class CustomCharField(models.CharField):
    ...

e poi decidi che vuoi usare un TextField, non puoi cambiare la sottoclasse così:

class CustomCharField(models.TextField):
    ...

Invece, devi creare una nuova classe per il campo personalizzato ed aggiornare il modello per farvi riferimento:

class CustomCharField(models.CharField):
    ...

class CustomTextField(models.TextField):
    ...

Come discusso in rimuovere campi, devi mantenere la classe originale CustomCharField fintanto che hai migrazione che vi facciano riferimento.

Documentare il tuo campo personalizzato

Come sempre, dovresti documentare il tuo tipo di campo, così che gli utenti possano sapere cosa sia. Oltre a fornire una docstring, che è utile per gli sviluppatori, dovresti anche mettere in condizioni gli utenti dell’amministrazione di vedere una piccola descrizione del tipo di campo tramite l’applicazione django.contrib.admindocs . Per far questo, fornisci un testo descrittivo nell’attributo di classe description del tuo campo personalizzato. Nell’esempio sopracitato, la descrizione mostrata dall’applicazione admindocs per un HandField sarà “Una mano di carte (stile bridge)”.

Nella visualizzazione in django.contrib.admindocs, la descrizione del campo è interpolata con field.__dict__, che consente che la descrizione incorpori gli argomenti del campo. Per esempio, la descrizione di CharField è:

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

Metodi utili

Una volta che hai creato la tua sottoclasse di Field, puoi considerare di fare override di alcuni metodi standard, dipendentemente dal comportamento del tuo campo. La lista di metodi che segue è ordinata approssimativamente in ordine decrescente di importanza, quindi inizia dall’alto.

Tipi di database personalizzati

Supponiamo che tu abbia creato un tipo PostgreSQL custom chiamato mytype`. Puoi fare una sottoclasse di Field ed implementare il metodo db_type(), nel modo seguente:

from django.db import models

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

Una volta che hai MytypeField, puoi usarlo in ogni modello, come qualsiasi altro tipo Field:

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

Se punti ad implementare una applicazione agnostica rispetto al database, dovresti tenere in considerazione le differenze nei tipi di colonna del database. Per esempio, il tipo date/time in PostgreSQL è chiamato timestamp, mentre la stessa colonna in MySQL viene chiamata datetime. Puoi gestire tutto questo in un metodo db_type() controllando l’attributo connection.vendor. I nomi di vendor correntemente disponibili sono: sqlite, postgresql, mysql e``oracle``.

Per esempio:

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

I metodi db_type() e rel_db_type() sono chiamati da Django quando il framework costruisce gli statement CREATE TABLE per la tua applicazione – cioè, quando crei le tabelle la prima volta. I metodi vengono anche chiamati quando viene costruita una clausola WHERE che include il model field – cioè, quando richiedi dei dati con i metodi QuerySet come get(), filter() ed exclude() ed hai il model field come argomento. Non sono chiamati in nessun’altra situazione, così si può permettere di eseguire codice un po” più complesso, come il controllo connection.settings_dict dell’esempio citato sopra.

Alcuni tipi di colonne di database accettano parametri, come CHAR(25), dove il parametro 25 rappresenta la lunghezza massima della colonna. In casi come questi, c’è più flessibilità se il parametro è specificato nel modello piuttosto che essere codificato nel metodo db_type(). Per esempio, non avrebbe senso avere un CharMaxlength25Field, mostrato qui:

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

Il miglior modo di fare una cosa del genere potrebbe essere rendere il parametro specificabile a run time – per esempio, quando la classe viene istanziata. Per fare ciò, implementa così 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)

Infine, se la tua colonna richiede davvero un setup SQL complesso, restituisci None da db_type(). Questo farà in modo che Django salti questo campo durante la creazione del codice SQL. A questo punto sei in qualche modo il responsabile della creazione della colonna nella giusta tabella ma questo ti offre un modo di dire a Django di non agire.

Il metodo rel_db_type() è chiamato da campi come ForeignKey e OneToOneField che puntano ad altri campi per determinare il proprio tipo di dato per la colonna del database. Per esempio, se hai un UnsignedAutoField, hai anche bisogno delle foreign keys che puntano a quel campo e che usano lo stesso tipo di 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'

Conversione di valori a oggetti Python

Se la tua classe personalizzata Field ha a che fare con strutture di dati che sono più complesse di stringhe, date, interi o numeri in virgola mobile, allora potresti aver bisogno di fare override di from_db_value() e to_python().

Se presente per la sottoclasse del campo, from_db_value() sarà chiamato in tutte le circostanze in cui i dati sono caricati dal database, incluse le aggregazioni e le chiamate values().

to_python() è chiamato per la deserializzazione e nel metodo clean() usato dai form.

Come regola generale, to_python() dovrebbe avere a che fare in modo graceful con i seguenti argomenti:

  • Una istanza del tipo corretto (per esempio Hand nel nostro esempio).
  • Una stringa
  • None (se il campo permette null=True)

In our HandField class, we’re storing the data as a VARCHAR field in the database, so we need to be able to process strings and None in the from_db_value(). In to_python(), we need to also handle Hand instances:

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 che restituiamo sempre una istanza di Hand da questi metodi. Quello è il tipo di oggetto Python che vogliamo salvare nell’attributo del model.

Per to_python(), se qualcosa va storto durante una conversione di valore, dovresti lanciare una eccezione ValidationError.

Convertire oggetti Python in query di valori

Poichè l’utilizzo di un database richiede conversioni in entrambi i sensi, se fai override di from_db_value() devi anche fare override di get_prep_value() per riconvertire gli oggetti Python in query values.

Per esempio:

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

Avvertimento

Se il tuo campo custom usa tipi CHAR, VARCHAR o TEXT per MySQL, devi assicurarti che get_prep_value() restituisca sempre un tipo stringa. MySQL esegue match flessibili ed inaspettati quando si esegue una query su questi tipi ed il valore che si fornisce è un integer, che può causare l’inclusione di valori inattesi nei risultati. Questo problema non si pone se restituisci sempre un tipo stringa da get_prep_value().

Convertire query values in valori di database

Alcuni tipi di dato (per esempio, date) devono trovarsi in uno specifico formato prima di poter essere usati da un backend database. get_db_prep_value() è il metodo in cui si dovrebbero effettuare queste conversioni. La connessione specifica che sarà usata per la query è passata come parametro connection. Questo ti permette usare logica specifica del backend per la coversione, se questa è richiesta.

Per esempio, Django usa il metodo seguente per il suo 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

Nel caso che il tuo campo personalizzato richieda una conversione speciale al salvataggio che non sia la stessa usata per i parametri di query, puoi fare override di get_db_prep_save().

Pre elaborazione dei valori prima del salvataggio

Se vuoi preprocessare il valore proprio prima di salvarlo, puoi sare pre_save(). Per esempio, DateTimeField di Django usa questo metodo per impostare l’attributo correttamente in caso di auto_now o auto_now_add.

Se fai override di questo metodo, alla fine devi restituire il valore dell’attributo. Dovresti anche aggiornare l’attributo del model se fai cambiamenti al valore, così che il codice che abbia riferimenti al model possa sempre vedere il valore corretto.

Specificare il form field per un model field

Per personalizzare il campo del form usato da ModelForm, puoi fare override di formfield().

La classe del campo del form può essere specificata con gli argomenti form_class e choices_form_class; il secondo è usato se il campo specifica delle scelte, altrimenti il primo. Se questi argomenti non sono forniti, saranno usati CharField or TypedChoiceField.

Tutto il dizionario kwargs viene passato direttamente nel metodo __init__() del form. Normalmente, tutto quel che devi fare è impostare un buon default per l’argomento form_class (e magari choices_form_class). Questo ti potrebbe richiedere di scrivere un campo form personalizzato (ed anche un form widget). Vedi documentazione dei form per avere informazioni su questo.

Continuando con il nostro esempio, possiamo scrivere il metodo formfield() come:

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)

Questo presume che abbiamo importato una classe per il campo MyFormField (che ha il proprio widget predefinito). Questo documento non copre i dettagli relativi alla scrittura di form field personalizzati.

Emulare tipi di campo built-in

Se hai creato un metodo db_type(), non devi preoccuparti del get_internal_type() – non verrà usato spesso. Qualche volta, però, lo storage del tuo database è di tipo simile a quello di qualche altro campo, quindi puoi usare la logica di quest’altro campo per creare la giusta colonna.

Per esempio:

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

    def get_internal_type(self):
        return 'CharField'

Non importa quale database stiamo usando per il backend, questo significa che migrate e altri comandi SQL creano il giusto tipo di colonna per il salvataggio di una stringa.

Se get_internal_type() ritorna una stringa che non è nota a Django per il database di backend che stai utilizzando – cioè, non appare in django.db.backends.<db_name>.base.DatabaseWrapper.data_types – la stringa sarà ugualmente usata dal serializzatore, ma il metodo di default db_type() ritornerà None. Vedi la documentazione di db_type() per capire i motivi per cui questo potrebbe essere utile. Inserire una stringa descrittiva come tipo del campo per il serializzatore è una buona idea semmai dovessi usare l’output del serializzatore da qualche altra parte, al di fuori di Django.

Conversione dei dati del campo per la serializzazione

Per personalizzare il modo in cui i valori vengono serializzati da un serializzatore, puoi fare l’override di value_to_string(). Usare value_from_object() è il modo migliore di ottenere il valore del campo prima della serializzazione. Ad esempio, dato che HandField usa delle stringhe per il salvataggio dei dati, possiamo riutilizzare una parte del codice di conversione esistente.

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

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

Alcuni consigli generali

Scrivere un campo personalizzato può essere un processo complesso, in particolar modo se stai facendo delle conversioni complesse tra i tipi di dati Python e il tuo database e i formati di serializzazione. Ecco alcuni suggerimenti per rendere le cose più semplici:

  1. Guarda i campi Django esistenti (in django/db/models/fields/__init__.py) per prendere spunto. Cerca di trovare un campo che è simile a quello che vuoi ed estendilo un po”, invece di creare un campo interamente nuovo da zero.
  2. Metti un metodo __str__() sulla classe che stai incapsulando come campo. Ci sono molti casi in cui il comportamento di default del campo è quello di chaimare str() sul valore. (Negli esempi in questo documento, value` potrebbe essere una istanza di Hand, non un HandField). Così, se il tuo metodo __str__() converte automaticamente in stringa il tuo oggetto Python, puoi risparmiarti un sacco di lavoro.

Scrivere una sottoclasse FileField

Oltre ai metodi sopra, i campi che hanno a che fare con i file hanno pochi altri requisiti speciali che è necessario prendere in considerazione. La maggior parte delle meccaniche fornite da FileField, come il controllo dello storage e del ritiro su e dal database, possono rimanere inalterate, lasciando alle sottoclassi l’onere di supportare un determinato tipo di file.

Django fornisce una classe File, che viene utilizzata come proxy per accedere ai contenuti del file e compiere operazioni. A partire da questa si possono creare delle sottoclassi per personalizzare le modalità di accesso al file ed i metodi disponibili. Si tratta di django.db.models.fields.files ed il suo comportamento standard viene spiegato nella documentazione dei file.

Una volta creata una sottoclasse di File`, è necessario dire alla nuova sottoclasse di FileField di usarla. Per farlo, assegna la nuova sottoclasse File all’attributo speciale attr_class della sottoclasse FileField.

Alcuni suggerimenti

In aggiunta ai dettagli precedentemente esposti, ci sono alcune linee guida che possono migliorare notevolmente l’efficienza e la leggibilità del codice del campo.

  1. I sorgenti della ImageField di Django (in django/db/models/fields/files.py) sono un ottimo esempio di come realizzare sottoclassi di FileField per supportare particolari tipi di file, poichè incorpora tutte le tecniche descritte in precedenza.
  2. Metti in cache i file quando possibile. Dal momento che i file potrebbero trovarsi su sistemi di storage remoti, ritirarli potrebbe costare tempo o addirittura denaro aggiuntivo, cosa che non è sempre necessaria. Una volta che un file è stato ritirato per ottenere dati relativi al suo contenuto, metti in cache quanti più dati possibile per ridurre il numero di volte che il file deve essere ritirato per chiamate susseguenti relative a quelle informazioni.
Back to Top