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.
Per il nostro esempio Hand
, potremmo convertire i dati della carta in una stringa di 104 caratteri concatenando tutte le carte insieme in un ordine predeterminato – diciamo, prima tutte le carte nord, poi est, * carte sud* e ovest. In questo modo gli oggetti Hand
possono essere salvati in colonne testuali o di caratteri nel 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:
verbose_name
name
primary_key
max_length
unique
blank
null
db_index
rel
: Usato per campi collegati (comeForeignKey
). Solo per utenti avanzati.default
editable
serialize
: SeFalse
, il campo non verrà serializzato quando viene passato ai serializers di Django. Di default aTrue
.unique_for_date
unique_for_month
unique_for_year
choices
help_text
db_column
db_tablespace
: solo per la creazione dell’indice, se il backend supporta tablespaces. Generalmente puoi ignorare questa opzione.auto_created
:True
se il campo è stato creato automaticamente, come perOneToOneField
, usato per l’ereditarietà del model. Solo per uso avanzato.
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¶
Il duale rispetto allo scrivere il metodo __init__()
è scrivere il metodo deconstruct()
. Viene utilizzato durante model migrations per dire a Django come prendere un’istanza del tuo nuovo campo e ridurlo ad una forma serializzata, in particolare, quali argomenti passare a __init__()
per ricrearlo.
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)
Attributi di campo che non interessano la definizione di colonne di database¶
Puoi sovrascrivere Field.non_db_attrs
per personalizzare gli attributi di un campo che non influiscono sulla definizione di una colonna. Viene utilizzato durante le migrazioni dei modelli per rilevare AlterField
no-op.
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"
The db_type()
and rel_db_type()
methods are called by
Django when the framework constructs the CREATE TABLE
statements for your
application – that is, when you first create your tables. The methods are also
called when constructing a WHERE
clause that includes the model field –
that is, when you retrieve data using QuerySet methods like get()
,
filter()
, and exclude()
and have the model field as an argument.
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 permettenull=True
)
Nella nostra classe HandField
, stiamo immagazinando i dati come un campo VARCHAR
nel database, quindi dobbiamo essere in grado di processare string e None
in from_db_value()`. In to_python()
, dovremo anche gestire le istanze di 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 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:
- Look at the existing Django fields (in django/db/models/fields/__init__.py) for inspiration. Try to find a field that’s similar to what you want and extend it a little bit, instead of creating an entirely new field from scratch.
- 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 chaimarestr()
sul valore. (Negli esempi in questo documento, value` potrebbe essere una istanza diHand
, non unHandField
). 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.
- The source for Django’s own
ImageField
(in django/db/models/fields/files.py) is a great example of how to subclassFileField
to support a particular type of file, as it incorporates all of the techniques described above. - 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.