Så här skapar du anpassade modellfält¶
Introduktion¶
Dokumentationen modellreferens förklarar hur man använder Djangos standardfältklasser - CharField
, DateField
, etc. För många ändamål är dessa klasser allt du behöver. Ibland kommer dock Django-versionen inte att uppfylla dina exakta krav, eller så vill du använda ett fält som är helt annorlunda än de som levereras med Django.
Djangos inbyggda fälttyper täcker inte alla möjliga databaskolonntyper - bara de vanliga typerna, till exempel VARCHAR
och INTEGER
. För mer obskyra kolumntyper, till exempel geografiska polygoner eller till och med användarskapade typer som PostgreSQL anpassade typer, kan du definiera dina egna Django Field
underklasser.
Alternativt kan du ha ett komplext Python-objekt som på något sätt kan serialiseras för att passa in i en standardkolumntyp i databasen. Detta är ett annat fall där en Field
subklass kommer att hjälpa dig att använda ditt objekt med dina modeller.
Vårt exempelobjekt¶
Att skapa anpassade fält kräver lite uppmärksamhet på detaljer. För att göra det lättare att följa använder vi ett konsekvent exempel i hela det här dokumentet: att paketera ett Python-objekt som representerar kortutdelningen i en hand Bridge. Oroa dig inte, du behöver inte veta hur man spelar bridge för att följa detta exempel. Du behöver bara veta att 52 kort delas ut lika till fyra spelare, som traditionellt kallas north, east, south och west. Vår klass ser ut ungefär så här:
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) ...
Detta är en vanlig Python-klass, utan något Django-specifikt med den. Vi skulle vilja kunna göra saker som detta i våra modeller (vi antar att hand
-attributet på modellen är en instans av Hand
):
example = MyModel.objects.get(pk=1)
print(example.hand.north)
new_hand = Hand(north, east, south, west)
example.hand = new_hand
example.save()
Vi tilldelar och hämtar från hand
-attributet i vår modell precis som alla andra Python-klasser. Tricket är att berätta för Django hur man hanterar att spara och ladda ett sådant objekt.
För att kunna använda klassen Hand
i våra modeller behöver vi inte ändra den här klassen överhuvudtaget. Detta är idealiskt, eftersom det innebär att du enkelt kan skriva modellstöd för befintliga klasser där du inte kan ändra källkoden.
Observera
Du kanske bara vill dra nytta av anpassade kolumntyper i databasen och hantera data som standard Python-typer i dina modeller; strängar eller flottörer, till exempel. Det här fallet liknar vårt Hand
-exempel och vi kommer att notera eventuella skillnader när vi går vidare.
Bakgrundsteori¶
Databaslagring¶
Låt oss börja med modellfält. Om du bryter ner det, ger ett modellfält ett sätt att ta ett normalt Python-objekt - sträng, boolean, datetime
eller något mer komplext som Hand
- och konvertera det till och från ett format som är användbart när du hanterar databasen. (Ett sådant format är också användbart för serialisering, men som vi kommer att se senare är det lättare när du har databassidan under kontroll).
Fält i en modell måste på något sätt konverteras så att de passar in i en befintlig kolumntyp i databasen. Olika databaser har olika uppsättningar av giltiga kolumntyper, men regeln är ändå densamma: det är de enda typerna du behöver arbeta med. Allt som du vill lagra i databasen måste passa in i någon av dessa typer.
Normalt skriver du antingen ett Django-fält för att matcha en viss kolumntyp i databasen, eller så behöver du ett sätt att konvertera dina data till exempelvis en sträng.
I vårt exempel med Hand
kan vi konvertera kortdata till en sträng med 104 tecken genom att sammanfoga alla kort i en förutbestämd ordning - t.ex. alla north-kort först, sedan east-, south- och west-kort. Så Hand
-objekt kan sparas i text- eller teckenkolumner i databasen.
Vad gör en fältklass?¶
Alla Djangos fält (och när vi säger fält i detta dokument menar vi alltid modellfält och inte formulärsfält) är underklasser till django.db.models.Field
. Det mesta av den information som Django registrerar om ett fält är gemensam för alla fält - namn, hjälptext, unikhet och så vidare. Lagring av all denna information hanteras av Field
. Vi kommer att gå in på de exakta detaljerna om vad Field
kan göra senare; för nu räcker det att säga att allt härstammar från Field
och sedan anpassar viktiga delar av klassens beteende.
Det är viktigt att inse att en Django-fältklass inte är det som lagras i dina modellattribut. Modellattributen innehåller normala Python-objekt. De fältklasser som du definierar i en modell lagras faktiskt i klassen Meta
när modellklassen skapas (de exakta detaljerna om hur detta görs är oviktiga här). Detta beror på att fältklasserna inte är nödvändiga när du bara skapar och ändrar attribut. Istället tillhandahåller de maskineriet för att konvertera mellan attributvärdet och det som lagras i databasen eller skickas till serializer.
Tänk på detta när du skapar dina egna anpassade fält. Den Django Field
subklass du skriver tillhandahåller maskineriet för att konvertera mellan dina Python-instanser och databas/serializer-värden på olika sätt (det finns skillnader mellan att lagra ett värde och använda ett värde för uppslagningar, till exempel). Om det här låter lite knepigt, oroa dig inte - det kommer att bli tydligare i exemplen nedan. Kom bara ihåg att du ofta kommer att skapa två klasser när du vill ha ett anpassat fält:
Den första klassen är det Python-objekt som dina användare kommer att manipulera. De kommer att tilldela det till model-attributet, de kommer att läsa från det för visningsändamål, saker som det. Detta är klassen
Hand
i vårt exempel.Den andra klassen är underklassen
Field
. Det här är klassen som vet hur man konverterar den första klassen fram och tillbaka mellan dess permanenta lagringsform och Python-form.
Skriva en fältunderklass¶
När du planerar din Field
-underklass, tänk först på vilken befintlig Field
-klass ditt nya fält är mest lik. Kan du subklassa ett befintligt Django-fält och spara dig lite arbete? Om inte, bör du subklassa Field
-klassen, från vilken allt härstammar.
Initialisering av ditt nya fält är en fråga om att separera alla argument som är specifika för ditt fall från de vanliga argumenten och skicka de senare till metoden __init__()
i Field
(eller din överordnade klass).
I vårt exempel kommer vi att kalla vårt fält HandField
. (Det är en bra idé att kalla din Field
subklass för <Something>Field
, så att den lätt kan identifieras som en Field
subklass) Det beter sig inte som något befintligt fält, så vi kommer att subklassa direkt från 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)
Vårt HandField
accepterar de flesta av standardfältalternativen (se listan nedan), men vi ser till att det har en fast längd, eftersom det bara behöver innehålla 52 kortvärden plus deras färger; totalt 104 tecken.
Observera
Många av Djangos modellfält accepterar alternativ som de inte gör något med. Till exempel kan du skicka både editable
och auto_now
till en django.db.models.DateField
och den kommer att ignorera parametern editable
(auto_now
som ställs in innebär editable=False
). Inget fel uppstår i detta fall.
Detta beteende förenklar fältklasserna, eftersom de inte behöver kontrollera om det finns alternativ som inte är nödvändiga. De skickar alla alternativ till överordnad klass och använder dem inte senare. Det är upp till dig om du vill att dina fält ska vara mer strikta när det gäller de alternativ de väljer, eller om du vill använda det mer tillåtande beteendet hos de aktuella fälten.
Metoden Field.__init__()
tar emot följande parametrar:
namn
rel
: Används för relaterade fält (somForeignKey
). Endast för avancerad användning.serialize
: OmFalse
, kommer fältet inte att serialiseras när modellen skickas till Djangos serializers. Standardvärde ärTrue
.db_tablespace
: Endast för skapande av index, om backend stöder tablespaces. Du kan vanligtvis ignorera detta alternativ.auto_created
:True
om fältet skapades automatiskt, som förOneToOneField
som används av modellarv. Endast för avancerad användning.
Alla alternativ utan förklaring i listan ovan har samma betydelse som de har för vanliga Django-fält. Se fältdokumentation för exempel och detaljer.
Dekonstruktion av fält¶
Motpolen till att skriva din __init__()
-metod är att skriva deconstruct()
-metoden. Den används under modellmigreringar för att tala om för Django hur man tar en instans av ditt nya fält och reducerar det till en serialiserad form - i synnerhet vilka argument som ska skickas till __init__()
för att återskapa det.
Om du inte har lagt till några extra alternativ utöver det fält du ärvde från, behöver du inte skriva en ny metod deconstruct()
. Om du däremot ändrar de argument som skickas i __init__()
(som vi gör i HandField
), måste du komplettera de värden som skickas.
deconstruct()
returnerar en tupel med fyra objekt: fältets attributnamn, fältklassens fullständiga importsökväg, de positionella argumenten (som en lista) och nyckelordsargumenten (som en dict). Observera att detta skiljer sig från metoden deconstruct()
:ref:``for custom classes <custom-deconstruct-method>` som returnerar en tupel av tre saker.
Som författare till ett anpassat fält behöver du inte bry dig om de två första värdena; basklassen Field
har all kod för att räkna ut fältets attributnamn och importsökväg. Du måste dock bry dig om positions- och nyckelordsargumenten, eftersom det sannolikt är dessa saker du ändrar.
Till exempel, i vår HandField
-klass ställer vi alltid in max_length i __init__()
. Metoden deconstruct()
på basklassen Field
kommer att se detta och försöka returnera det i nyckelordsargumenten; därför kan vi ta bort det från nyckelordsargumenten för läsbarhetens skull:
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
Om du lägger till ett nytt nyckelordsargument måste du själv skriva kod i deconstruct()
som lägger in dess värde i kwargs
. Du bör också utelämna värdet från kwargs
när det inte är nödvändigt att rekonstruera fältets tillstånd, till exempel när standardvärdet används:
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
Mer komplexa exempel ligger utanför ramen för detta dokument, men kom ihåg - för varje konfiguration av din Field-instans måste deconstruct()
returnera argument som du kan skicka till __init__
för att rekonstruera det tillståndet.
Var extra uppmärksam om du anger nya standardvärden för argument i superklassen Field
; du vill vara säker på att de alltid inkluderas, snarare än att försvinna om de får det gamla standardvärdet.
Dessutom bör du försöka undvika att returnera värden som positionella argument; när det är möjligt, returnera värden som nyckelordsargument för maximal framtida kompatibilitet. Om du ändrar namn på saker oftare än deras position i konstruktörens argumentlista kanske du föredrar positionella, men kom ihåg att människor kommer att rekonstruera ditt fält från den serialiserade versionen under ett bra tag (kanske år), beroende på hur länge dina migreringar pågår.
Du kan se resultatet av dekonstruktionen genom att titta i migreringar som innehåller fältet, och du kan testa dekonstruktionen i enhetstester genom att dekonstruera och rekonstruera fältet:
name, path, args, kwargs = my_field_instance.deconstruct()
new_instance = MyField(*args, **kwargs)
self.assertEqual(my_field_instance.some_attribute, new_instance.some_attribute)
Fältattribut påverkar inte databasens kolumndefinition¶
Du kan åsidosätta Field.non_db_attrs
för att anpassa attribut för ett fält som inte påverkar en kolumndefinition. Det används under modellmigreringar för att upptäcka no-op AlterField
-operationer.
Till exempel:
class CommaSepField(models.Field):
@property
def non_db_attrs(self):
return super().non_db_attrs + ("separator",)
Ändra basklass för ett anpassat fält¶
Du kan inte ändra basklassen för ett anpassat fält eftersom Django inte kommer att upptäcka ändringen och göra en migrering för den. Till exempel, om du börjar med:
class CustomCharField(models.CharField): ...
och sedan bestämmer dig för att du vill använda TextField
istället, kan du inte ändra underklassen så här:
class CustomCharField(models.TextField): ...
Istället måste du skapa en ny klass för anpassade fält och uppdatera dina modeller så att de refererar till den:
class CustomCharField(models.CharField): ...
class CustomTextField(models.TextField): ...
Som diskuterades i :ref:Ta bort fält <migrations-removing-model-fields>
måste du behålla den ursprungliga klassen CustomCharField
så länge du har migreringar som refererar till den.
Dokumentera ditt anpassade fält¶
Som alltid bör du dokumentera din fälttyp så att användarna vet vad det är. Förutom att tillhandahålla en docstring för den, vilket är användbart för utvecklare, kan du också låta användare av admin-appen se en kort beskrivning av fälttypen via django.contrib.admindocs-applikationen. För att göra detta tillhandahåller du beskrivande text i ett description
klassattribut för ditt anpassade fält. I exemplet ovan kommer den beskrivning som visas av applikationen admindocs
för ett HandField
att vara ”En hand med kort (bridgestil)”.
I visningen django.contrib.admindocs
interpoleras fältbeskrivningen med field.__dict__
vilket gör att beskrivningen kan innehålla fältets argument. Till exempel är beskrivningen för CharField
:
description = _("String (up to %(max_length)s)")
Användbara metoder¶
När du har skapat din Field
-underklass kan du överväga att åsidosätta några standardmetoder, beroende på ditt fälts beteende. Listan över metoder nedan är i ungefärlig fallande ordning efter betydelse, så börja från toppen.
Anpassade databastyper¶
Säg att du har skapat en PostgreSQL anpassad typ som heter mytype
. Du kan underklassa Field
och implementera db_type()
-metoden, så här:
from django.db import models
class MytypeField(models.Field):
def db_type(self, connection):
return "mytype"
När du har MytypeField
kan du använda den i vilken modell som helst, precis som vilken annan Field
-typ som helst:
class Person(models.Model):
name = models.CharField(max_length=80)
something_else = MytypeField()
Om du strävar efter att bygga en databasagnostisk applikation bör du ta hänsyn till skillnader i databaskolonntyper. Till exempel kallas kolumntypen datum / tid i PostgreSQL timestamp
, medan samma kolumn i MySQL kallas datetime
. Du kan hantera detta i en db_type()
-metod genom att kontrollera attributet connection.vendor
. Nuvarande inbyggda leverantörsnamn är: sqlite
, postgresql
, mysql
och oracle
.
Till exempel:
class MyDateField(models.Field):
def db_type(self, connection):
if connection.vendor == "mysql":
return "datetime"
else:
return "timestamp"
Metoderna db_type()
och rel_db_type()
anropas av Django när ramverket konstruerar CREATE TABLE
-satserna för din applikation - det vill säga när du först skapar dina tabeller. Metoderna anropas också när du konstruerar en WHERE
-sats som innehåller modellfältet - det vill säga när du hämtar data med QuerySet-metoder som get()
, filter()
och exclude()
och har modellfältet som ett argument.
Vissa kolumntyper i databaser accepterar parametrar, t.ex. CHAR(25)
, där parametern 25
representerar den maximala kolumnlängden. I sådana fall är det mer flexibelt om parametern anges i modellen i stället för att hårdkodas i metoden db_type()
. Det skulle till exempel inte vara särskilt meningsfullt att ha en CharMaxlength25Field
, som visas här:
# 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()
Ett bättre sätt att göra detta vore att göra parametern specificerbar vid körning, dvs. när klassen instansieras. För att göra det, implementera Field.__init__()
, så här:
# 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)
Slutligen, om din kolumn kräver en verkligt komplex SQL-konfiguration, returnera None
från db_type()
. Detta kommer att leda till att Djangos SQL-skapande kod hoppar över detta fält. Du är sedan ansvarig för att skapa kolumnen i rätt tabell på något annat sätt, men det här ger dig ett sätt att berätta för Django att komma ur vägen.
Metoden rel_db_type()
anropas av fält som ForeignKey
och OneToOneField
som pekar på ett annat fält för att bestämma datatyperna för deras databaskolumner. Om du till exempel har ett UnsignedAutoField
behöver du också de främmande nycklar som pekar på det fältet för att använda samma datatyp:
# 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"
Konvertera värden till Python-objekt¶
Om din anpassade klass Field
hanterar datastrukturer som är mer komplexa än strängar, datum, heltal eller flyttal kan du behöva åsidosätta from_db_value()
och to_python()
.
Om det finns för fältunderklassen kommer from_db_value()
att anropas under alla omständigheter när data laddas från databasen, inklusive i aggregat och values()
-anrop.
to_python()
anropas vid deserialisering och under clean()
-metoden som används från formulär.
Som en allmän regel bör to_python()
hantera något av följande argument på ett elegant sätt:
En instans av rätt typ (t.ex.
Hand
i vårt pågående exempel).En sträng
None
(om fältet tillåternull=True
)
I vår HandField
-klass lagrar vi data som ett VARCHAR
-fält i databasen, så vi måste kunna bearbeta strängar och None
i from_db_value()
. I to_python()
måste vi också hantera Hand
-instanser:
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)
Lägg märke till att vi alltid returnerar en Hand
-instans från dessa metoder. Det är Python-objekttypen som vi vill lagra i modellens attribut.
För to_python()
, om något går fel under värdekonvertering, bör du höja ett ValidationError
undantag.
Konvertera Python-objekt till frågevärden¶
Eftersom användning av en databas kräver konvertering i båda riktningarna, om du åsidosätter from_db_value()
måste du också åsidosätta get_prep_value()
för att konvertera Python-objekt tillbaka till frågevärden.
Till exempel:
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)]
)
Varning
Om ditt anpassade fält använder typerna CHAR
, VARCHAR
eller TEXT
för MySQL måste du se till att get_prep_value()
alltid returnerar en strängtyp. MySQL utför flexibel och oväntad matchning när en fråga utförs på dessa typer och det angivna värdet är ett heltal, vilket kan leda till att frågor inkluderar oväntade objekt i sina resultat. Det här problemet kan inte uppstå om du alltid returnerar en strängtyp från get_prep_value()
.
Konvertering av frågevärden till databasvärden¶
Vissa datatyper (t.ex. datum) måste vara i ett visst format innan de kan användas av en databas. get_db_prep_value()
är den metod där dessa konverteringar ska göras. Den specifika anslutning som kommer att användas för frågan skickas som parametern connection
. Detta gör att du kan använda backend-specifik konverteringslogik om det krävs.
Till exempel använder Django följande metod för sin 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
Om ditt anpassade fält behöver en särskild konvertering när det sparas som inte är densamma som den konvertering som används för normala frågeparametrar kan du åsidosätta get_db_prep_save()
.
Förbehandling av värden före lagring¶
Om du vill förbehandla värdet precis innan du sparar det kan du använda pre_save()
. Till exempel använder Djangos DateTimeField
den här metoden för att ställa in attributet korrekt i fallet med auto_now
eller auto_now_add
.
Om du åsidosätter den här metoden måste du returnera attributets värde i slutet. Du bör också uppdatera modellens attribut om du gör några ändringar i värdet så att kod som har referenser till modellen alltid ser rätt värde.
Ange formulärfältet för ett modellfält¶
För att anpassa det formulärfält som används av ModelForm
kan du åsidosätta formfield()
.
Klassen för formulärfältet kan specificeras via argumenten form_class
och choices_form_class
; det senare används om fältet har specificerade val, det förra annars. Om dessa argument inte anges kommer CharField
eller TypedChoiceField
att användas.
Hela kwargs
-ordlistan skickas direkt till formulärfältets __init__()
-metod. Normalt behöver du bara skapa en bra standard för argumentet form_class
(och kanske choices_form_class
) och sedan delegera vidare hantering till överordnad klass. Detta kan kräva att du skriver ett anpassat formulärfält (och till och med en formulärwidget). Se :doc:``formulärdokumentation </topics/forms/index>` för information om detta.
Om du vill utesluta fältet från ModelForm
kan du åsidosätta metoden formfield()
så att den returnerar None
.
Fortsätter vi med vårt pågående exempel kan vi skriva formfield()
-metoden som:
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)
Detta förutsätter att vi har importerat fältklassen MyFormField
(som har sin egen standardwidget). Detta dokument täcker inte detaljerna i att skriva anpassade formulärfält.
Emulering av inbyggda fälttyper¶
Om du har skapat en db_type()
-metod behöver du inte oroa dig för get_internal_type()
- den kommer inte att användas särskilt mycket. Ibland är dock databaslagringen av samma typ som något annat fält, och då kan du använda det andra fältets logik för att skapa rätt kolumn.
Till exempel:
class HandField(models.Field):
# ...
def get_internal_type(self):
return "CharField"
Oavsett vilken databasbackend vi använder kommer detta att innebära att migrate
och andra SQL-kommandon skapar rätt kolumntyp för lagring av en sträng.
Om get_internal_type()
returnerar en sträng som inte är känd för Django för den databasbackend du använder - det vill säga, den visas inte i django.db.backends.<db_name>.base.DatabaseWrapper.data_types
- kommer strängen fortfarande att användas av serialiseraren, men standardmetoden db_type()
kommer att returnera None
. Se dokumentationen av db_type()
för skäl till varför detta kan vara användbart. Att lägga in en beskrivande sträng som typ av fält för serializer är en bra idé om du någonsin kommer att använda serializer-utdata på någon annan plats utanför Django.
Konvertering av fältdata för serialisering¶
Om du vill anpassa hur värdena serialiseras av en serializer kan du åsidosätta value_to_string()
. Att använda value_from_object()
är det bästa sättet att få fältets värde före serialisering. Till exempel, eftersom HandField
ändå använder strängar för sin datalagring, kan vi återanvända en del befintlig konverteringskod:
class HandField(models.Field):
# ...
def value_to_string(self, obj):
value = self.value_from_object(obj)
return self.get_prep_value(value)
Några allmänna råd¶
Att skriva ett anpassat fält kan vara en knepig process, särskilt om du gör komplexa konverteringar mellan dina Python-typer och dina databas- och serialiseringsformat. Här är ett par tips för att få saker och ting att gå smidigare:
Titta på de befintliga Django-fälten (i django/db/models/fields/__init__.py) för inspiration. Försök att hitta ett fält som liknar det du vill ha och utöka det lite, istället för att skapa ett helt nytt fält från grunden.
Lägg till en
__str__()
-metod på den klass som du paketerar som ett fält. Det finns många ställen där standardbeteendet för fältkoden är att anropastr()
på värdet. (I våra exempel i detta dokument skullevärde
vara enHand
-instans, inte enHandField
). Så om din__str__()
-metod automatiskt konverterar till strängformen av ditt Python-objekt kan du spara dig mycket arbete.
Skriva en underklass till FileField
¶
Förutom ovanstående metoder har fält som hanterar filer några andra speciella krav som måste tas i beaktande. Majoriteten av den mekanik som tillhandahålls av FileField
, t.ex. styrning av databaslagring och hämtning, kan förbli oförändrad, vilket gör att underklasser kan hantera utmaningen att stödja en viss typ av fil.
Django tillhandahåller en File
-klass, som används som en proxy för filens innehåll och operationer. Detta kan underklassificeras för att anpassa hur filen nås och vilka metoder som är tillgängliga. Den lever på django.db.models.fields.files
, och dess standardbeteende förklaras i fil dokumentation.
När en subklass av File
har skapats måste den nya subklassen FileField
få veta att den ska använda den. Detta görs genom att tilldela den nya subklassen File
det speciella attributet attr_class
för subklassen FileField
.
Några förslag¶
Utöver ovanstående detaljer finns det några riktlinjer som avsevärt kan förbättra effektiviteten och läsbarheten i fältets kod.
Källan till Djangos egen
ImageField
(i django/db/models/fields/files.py) är ett bra exempel på hur man kan underklassaFileField
för att stödja en viss typ av fil, eftersom den innehåller alla de tekniker som beskrivs ovan.Cacha filattribut när det är möjligt. Eftersom filer kan lagras i fjärrlagringssystem kan det kosta extra tid, eller till och med pengar, att hämta dem, vilket inte alltid är nödvändigt. När en fil har hämtats för att få information om dess innehåll bör så mycket som möjligt av informationen cachas för att minska antalet gånger som filen måste hämtas vid senare anrop för att få samma information.