Serialisering av Django-objekt¶
Djangos serialiseringsramverk tillhandahåller en mekanism för att ”översätta” Django-modeller till andra format. Vanligtvis kommer dessa andra format att vara textbaserade och användas för att skicka Django-data över en tråd, men det är möjligt för en serializer att hantera vilket format som helst (textbaserat eller inte).
Se även
Om du bara vill få lite data från dina tabeller till en serialiserad form kan du använda kommandot dumpdata
.
Serialisering av data¶
På den högsta nivån kan du serialisera data så här:
from django.core import serializers
data = serializers.serialize("xml", SomeModel.objects.all())
Argumenten till funktionen serialize
är formatet att serialisera data till (se Serialiseringsformat) och en QuerySet
att serialisera. (Egentligen kan det andra argumentet vara vilken iterator som helst som ger Django-modellinstanser, men det kommer nästan alltid att vara en QuerySet).
- django.core.serializers.get_serializer(format)¶
Du kan också använda ett serializer-objekt direkt:
XMLSerializer = serializers.get_serializer("xml")
xml_serializer = XMLSerializer()
xml_serializer.serialize(queryset)
data = xml_serializer.getvalue()
Detta är användbart om du vill serialisera data direkt till ett filliknande objekt (som innehåller en HttpResponse
):
with open("file.xml", "w") as out:
xml_serializer.serialize(SomeModel.objects.all(), stream=out)
Observera
Anrop av get_serializer()
med en okänd format kommer att ge upphov till ett django.core.serializers.SerializerDoesNotExist
undantag.
Delmängd av fält¶
Om du bara vill att en delmängd av fälten ska serialiseras kan du ange ett fields
-argument till serializer:
from django.core import serializers
data = serializers.serialize("xml", SomeModel.objects.all(), fields=["name", "size"])
I det här exemplet serialiseras endast attributen name
och size
för varje modell. Primärnyckeln serialiseras alltid som elementet pk
i resultatet; den visas aldrig i delen fields
.
Observera
Beroende på din modell kan det hända att det inte går att deserialisera en modell som bara serialiserar en delmängd av sina fält. Om ett serialiserat objekt inte anger alla fält som krävs av en modell kommer deserialiseraren inte att kunna spara deserialiserade instanser.
Nedärvda modeller¶
Om du har en modell som definieras med hjälp av en abstrakt basklass behöver du inte göra något särskilt för att serialisera den modellen. Anropa serialiseraren på det objekt (eller de objekt) som du vill serialisera, så kommer utdata att vara en fullständig representation av det serialiserade objektet.
Men om du har en modell som använder multi-table inheritance, måste du också serialisera alla basklasser för modellen. Detta beror på att endast de fält som är lokalt definierade i modellen kommer att serialiseras. Tänk till exempel på följande modeller:
class Place(models.Model):
name = models.CharField(max_length=50)
class Restaurant(Place):
serves_hot_dogs = models.BooleanField(default=False)
Om du bara serialiserar restaurangmodellen:
data = serializers.serialize("xml", Restaurant.objects.all())
fälten på den serialiserade utdata kommer endast att innehålla attributet serves_hot_dogs
. Attributet name
för basklassen kommer att ignoreras.
För att fullt ut kunna serialisera dina Restaurant
-instanser måste du också serialisera Place
-modellerna:
all_objects = [*Restaurant.objects.all(), *Place.objects.all()]
data = serializers.serialize("xml", all_objects)
Deserialisering av data¶
Deserialisering av data är mycket likt serialisering av dem:
for obj in serializers.deserialize("xml", data):
do_something_with(obj)
Som du kan se tar funktionen deserialize
samma formatargument som serialize
, en sträng eller dataström, och returnerar en iterator.
Här blir det dock något komplicerat. De objekt som returneras av deserialize
iterator är inte vanliga Django-objekt. Istället är de speciella DeserializedObject
-instanser som omsluter ett skapat - men osparat - objekt och alla associerade relationsdata.
Genom att anropa DeserializedObject.save()
sparas objektet i databasen.
Observera
Om attributet pk
i de serialiserade uppgifterna inte finns eller är null, sparas en ny instans i databasen.
Detta säkerställer att deserialisering är en icke-destruktiv operation även om data i din serialiserade representation inte matchar vad som för närvarande finns i databasen. Vanligtvis ser arbetet med dessa DeserializedObject
-instanser ut ungefär som:
for deserialized_object in serializers.deserialize("xml", data):
if object_should_be_saved(deserialized_object):
deserialized_object.save()
Med andra ord är den vanliga användningen att undersöka de deserialiserade objekten för att se till att de är ”lämpliga” att spara innan man gör det. Om du litar på din datakälla kan du istället spara objektet direkt och gå vidare.
Django-objektet i sig kan inspekteras som deserialized_object.object
. Om fält i de serialiserade data inte finns på en modell, kommer ett DeserializationError
att uppstå om inte ignorenonexistent
argumentet skickas in som True
:
serializers.deserialize("xml", data, ignorenonexistent=True)
Format för serialisering¶
Django stöder ett antal serialiseringsformat, varav vissa kräver att du installerar Python-moduler från tredje part:
Identifierare |
Information |
---|---|
|
Serialiserar till och från en enkel XML-dialekt. |
|
Serialiserar till och från JSON. |
|
Serialiserar till och från JSONL. |
|
Serialiserar till YAML (YAML är inte ett märkspråk). Denna serialiserare är endast tillgänglig om PyYAML är installerat. |
XML¶
Det grundläggande XML-serialiseringsformatet ser ut så här:
<?xml version="1.0" encoding="utf-8"?>
<django-objects version="1.0">
<object pk="123" model="sessions.session">
<field type="DateTimeField" name="expire_date">2013-01-16T08:16:59.844560+00:00</field>
<!-- ... -->
</object>
</django-objects>
Hela samlingen av objekt som antingen serialiseras eller deserialiseras representeras av en <django-objects>
-tagg som innehåller flera <object>
-element. Varje sådant objekt har två attribut: ”pk” och ”model”, där det senare representeras av appens namn (”sessions”) och modellens namn (”session”) med gemener, åtskilda av en punkt.
Varje fält i objektet serialiseras som ett <field>
-element med fälten ”type” och ”name”. Elementets textinnehåll representerar det värde som ska lagras.
Utländska nycklar och andra relationsfält behandlas på ett lite annorlunda sätt:
<object pk="27" model="auth.permission">
<!-- ... -->
<field to="contenttypes.contenttype" name="content_type" rel="ManyToOneRel">9</field>
<!-- ... -->
</object>
I det här exemplet anger vi att objektet auth.Permission
med PK 27 har en främmande nyckel till instansen contenttypes.ContentType
med PK 9.
ManyToMany-relationer exporteras för den modell som binder dem. Till exempel har modellen auth.User
en sådan relation till modellen auth.Permission
:
<object pk="1" model="auth.user">
<!-- ... -->
<field to="auth.permission" name="user_permissions" rel="ManyToManyRel">
<object pk="46"></object>
<object pk="47"></object>
</field>
</object>
I det här exemplet kopplas den angivna användaren till behörighetsmodellerna med PK 46 och 47.
Kontrollera tecken
Om innehållet som skall serialiseras innehåller kontrolltecken som inte accepteras i XML 1.0-standarden, kommer serialiseringen att misslyckas med ett ValueError
-undantag. Läs även W3C:s förklaring av HTML, XHTML, XML and Control Codes.
JSON¶
Om vi fortsätter med samma exempel på data som tidigare skulle de serialiseras som JSON på följande sätt:
[
{
"pk": "4b678b301dfd8a4e0dad910de3ae245b",
"model": "sessions.session",
"fields": {
"expire_date": "2013-01-16T08:16:59.844Z",
# ...
},
}
]
Formateringen här är lite enklare än med XML. Hela samlingen representeras bara som en array och objekten representeras av JSON-objekt med tre egenskaper: ”pk”, ”model” och ”fields”. ”fields” är återigen ett objekt som innehåller varje fälts namn och värde som property respektive property-value.
Foreign keys har PK för det länkade objektet som egenskapsvärde. ManyToMany-relationer serialiseras för den modell som definierar dem och representeras som en lista med PK:er.
Var medveten om att inte alla Django-utdata kan skickas oförändrade till json
. Om du till exempel har någon anpassad typ i ett objekt som ska serialiseras, måste du skriva en anpassad json
-kodare för det. Något liknande detta kommer att fungera:
from django.core.serializers.json import DjangoJSONEncoder
class LazyEncoder(DjangoJSONEncoder):
def default(self, obj):
if isinstance(obj, YourCustomType):
return str(obj)
return super().default(obj)
Du kan sedan skicka cls=LazyEncoder
till funktionen serializers.serialize()
:
from django.core.serializers import serialize
serialize("json", SomeModel.objects.all(), cls=LazyEncoder)
Observera också att GeoDjango tillhandahåller en customized GeoJSON serializer.
DjangoJSONEncoder
¶
- class django.core.serializers.json.DjangoJSONEncoder¶
JSON-serialisatorn använder DjangoJSONEncoder
för kodning. Den är en underklass till JSONEncoder
och hanterar dessa ytterligare typer:
- :klass:`~datetime.datetime`
En sträng av formen
YYYY-MM-DDTHH:mm:ss.sssZ
ellerYYY-MM-DDTHH:mm:ss.sss+HH:MM
enligt definitionen i ECMA-262.- :klass:`~datetime.date`
En sträng av formen
YYYY-MM-DD
enligt definitionen i ECMA-262.- :klass:`~datetime.time`
En sträng av formen
HH:MM:ss.sss
enligt definitionen i ECMA-262.- :klass:`~datetime.timedelta`
En sträng som representerar en varaktighet enligt definitionen i ISO-8601. Till exempel representeras
timedelta(days=1, hours=2, seconds=3.4)
som'P1DT02H00M03.400000S'
.Decimal
,Promise
(django.utils.functional.lazy()
objekt),UUID
En strängrepresentation av objektet.
JSONL¶
JSONL står för JSON Lines. I det här formatet separeras objekt med nya rader och varje rad innehåller ett giltigt JSON-objekt. JSONL-serialiserade data ser ut så här:
{"pk": "4b678b301dfd8a4e0dad910de3ae245b", "model": "sessions.session", "fields": {...}}
{"pk": "88bea72c02274f3c9bf1cb2bb8cee4fc", "model": "sessions.session", "fields": {...}}
{"pk": "9cf0e26691b64147a67e2a9f06ad7a53", "model": "sessions.session", "fields": {...}}
JSONL kan vara användbart för att fylla på stora databaser, eftersom data kan bearbetas rad för rad i stället för att laddas in i minnet på en gång.
YAML¶
YAML-serialisering ser ganska lik ut som JSON. Objektlistan serialiseras som en sekvens mappningar med nycklarna ”pk”, ”model” och ”fields”. Varje fält är återigen en mappning där nyckeln är namnet på fältet och värdet är värdet:
- model: sessions.session
pk: 4b678b301dfd8a4e0dad910de3ae245b
fields:
expire_date: 2013-01-16 08:16:59.844560+00:00
Referensfält representeras återigen av PK eller en sekvens av PK.
Anpassade serialiseringsformat¶
Förutom standardformaten kan du skapa ett anpassat serialiseringsformat.
Låt oss till exempel titta på en serializer och deserializer för csv. Definiera först en klass för Serializer
och en för Deserializer
. Dessa kan åsidosätta befintliga serialiseringsformatklasser:
väg/till/custom_csv_serializer.py
¶ import csv
from django.apps import apps
from django.core import serializers
from django.core.serializers.base import DeserializationError
class Serializer(serializers.python.Serializer):
def get_dump_object(self, obj):
dumped_object = super().get_dump_object(obj)
row = [dumped_object["model"], str(dumped_object["pk"])]
row += [str(value) for value in dumped_object["fields"].values()]
return ",".join(row), dumped_object["model"]
def end_object(self, obj):
dumped_object_str, model = self.get_dump_object(obj)
if self.first:
fields = [field.name for field in apps.get_model(model)._meta.fields]
header = ",".join(fields)
self.stream.write(f"model,{header}\n")
self.stream.write(f"{dumped_object_str}\n")
def getvalue(self):
return super(serializers.python.Serializer, self).getvalue()
class Deserializer(serializers.python.Deserializer):
def __init__(self, stream_or_string, **options):
if isinstance(stream_or_string, bytes):
stream_or_string = stream_or_string.decode()
if isinstance(stream_or_string, str):
stream_or_string = stream_or_string.splitlines()
try:
objects = csv.DictReader(stream_or_string)
except Exception as exc:
raise DeserializationError() from exc
super().__init__(objects, **options)
def _handle_object(self, obj):
try:
model_fields = apps.get_model(obj["model"])._meta.fields
obj["fields"] = {
field.name: obj[field.name]
for field in model_fields
if field.name in obj
}
yield from super()._handle_object(obj)
except (GeneratorExit, DeserializationError):
raise
except Exception as exc:
raise DeserializationError(f"Error deserializing object: {exc}") from exc
Lägg sedan till modulen som innehåller serializer-definitionerna i din SERIALIZATION_MODULES
setting:
SERIALIZATION_MODULES = {
"csv": "path.to.custom_csv_serializer",
"json": "django.core.serializers.json",
}
En klassdefinition för Deserializer
har lagts till för vart och ett av de serialiseringsformat som tillhandahålls.
Naturliga nycklar¶
Standardstrategin för serialisering av främmande nycklar och många-till-många-relationer är att serialisera värdet på primärnyckeln för objekten i relationen. Denna strategi fungerar bra för de flesta objekt, men den kan orsaka problem under vissa omständigheter.
Tänk på fallet med en lista över objekt som har en främmande nyckel som refererar till ContentType
. Om du ska serialisera ett objekt som hänvisar till en innehållstyp måste du ha ett sätt att hänvisa till den innehållstypen till att börja med. Eftersom ContentType
-objekt automatiskt skapas av Django under databassynkroniseringsprocessen är primärnyckeln för en viss innehållstyp inte lätt att förutsäga; det beror på hur och när migrate
kördes. Detta gäller för alla modeller som automatiskt genererar objekt, särskilt inklusive Permission
, Group
och User
.
Varning
Du bör aldrig inkludera automatiskt genererade objekt i en fixtur eller andra serialiserade data. Av en slump kan de primära nycklarna i fixturen matcha dem i databasen och laddning av fixturen kommer inte att ha någon effekt. I det mer sannolika fallet att de inte matchar, kommer fixturladdningen att misslyckas med ett IntegrityError
.
Det är också en fråga om bekvämlighet. Ett heltals-id är inte alltid det mest praktiska sättet att referera till ett objekt; ibland skulle en mer naturlig referens vara till hjälp.
Det är av dessa skäl som Django tillhandahåller naturliga nycklar. En naturlig nyckel är en tupel av värden som kan användas för att unikt identifiera en objektinstans utan att använda det primära nyckelvärdet.
Deserialisering av naturliga nycklar¶
Tänk på följande två modeller:
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
birthdate = models.DateField()
class Meta:
constraints = [
models.UniqueConstraint(
fields=["first_name", "last_name"],
name="unique_first_last_name",
),
]
class Book(models.Model):
name = models.CharField(max_length=100)
author = models.ForeignKey(Person, on_delete=models.CASCADE)
Normalt skulle serialiserade data för Book
använda ett heltal för att hänvisa till författaren. I JSON kan en bok t.ex. serialiseras som:
...
{"pk": 1, "model": "store.book", "fields": {"name": "Mostly Harmless", "author": 42}}
...
Det här är inte ett särskilt naturligt sätt att hänvisa till en författare. Det kräver att du känner till det primära nyckelvärdet för författaren; det kräver också att detta primära nyckelvärde är stabilt och förutsägbart.
Men om vi lägger till naturlig nyckelhantering i Person blir fixturen mycket mer human. För att lägga till hantering av naturliga nycklar definierar du en standardhanterare för Person med en get_by_natural_key()
-metod. I fallet med en Person kan en bra naturlig nyckel vara paret av för- och efternamn:
from django.db import models
class PersonManager(models.Manager):
def get_by_natural_key(self, first_name, last_name):
return self.get(first_name=first_name, last_name=last_name)
class Person(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
birthdate = models.DateField()
objects = PersonManager()
class Meta:
constraints = [
models.UniqueConstraint(
fields=["first_name", "last_name"],
name="unique_first_last_name",
),
]
Nu kan böcker använda den naturliga nyckeln för att hänvisa till Person
-objekt:
...
{
"pk": 1,
"model": "store.book",
"fields": {"name": "Mostly Harmless", "author": ["Douglas", "Adams"]},
}
...
När du försöker ladda dessa serialiserade data kommer Django att använda metoden get_by_natural_key()
för att lösa ["Douglas", "Adams"]
till primärnyckeln för ett faktiskt Person
-objekt.
Observera
Oavsett vilka fält du använder för en naturlig nyckel måste de kunna identifiera ett objekt på ett unikt sätt. Detta innebär vanligtvis att din modell kommer att ha en unikhetsklausul (antingen unique=True
på ett enda fält, eller en UniqueConstraint
eller unique_together
över flera fält) för fältet eller fälten i din naturliga nyckel. Unikhet behöver dock inte genomdrivas på databasnivå. Om du är säker på att en uppsättning fält kommer att vara effektivt unika kan du fortfarande använda dessa fält som en naturlig nyckel.
Deserialisering av objekt utan primärnyckel kontrollerar alltid om modellens manager har en get_by_natural_key()
-metod och om så är fallet används den för att fylla i det deserialiserade objektets primärnyckel.
Serialisering av naturliga nycklar¶
Så hur får man Django att ge ut en naturlig nyckel när man serialiserar ett objekt? För det första måste du lägga till en annan metod - den här gången till själva modellen:
class Person(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
birthdate = models.DateField()
objects = PersonManager()
class Meta:
constraints = [
models.UniqueConstraint(
fields=["first_name", "last_name"],
name="unique_first_last_name",
),
]
def natural_key(self):
return (self.first_name, self.last_name)
Den metoden ska alltid returnera en tupel med naturliga nycklar - i det här exemplet (förnamn, efternamn)
. När du sedan anropar serializers.serialize()
ger du argumenten use_natural_foreign_keys=True
eller use_natural_primary_keys=True
:
>>> serializers.serialize(
... "json",
... [book1, book2],
... indent=2,
... use_natural_foreign_keys=True,
... use_natural_primary_keys=True,
... )
När use_natural_foreign_keys=True
anges kommer Django att använda metoden natural_key()
för att serialisera alla främmande nyckelreferenser till objekt av den typ som definierar metoden.
När use_natural_primary_keys=True
anges kommer Django inte att tillhandahålla den primära nyckeln i de serialiserade data för detta objekt eftersom den kan beräknas under deserialisering:
...
{
"model": "store.person",
"fields": {
"first_name": "Douglas",
"last_name": "Adams",
"birth_date": "1952-03-11",
},
}
...
Detta kan vara användbart när du behöver ladda serialiserade data i en befintlig databas och du inte kan garantera att det serialiserade primärnyckelvärdet inte redan används, och inte behöver säkerställa att deserialiserade objekt behåller samma primärnycklar.
Om du använder dumpdata
för att generera serialiserade data kan du använda kommandoradsflaggorna dumpdata --natural-foreign
och dumpdata --natural-primary
för att generera naturliga nycklar.
Observera
Du behöver inte definiera både natural_key()
och get_by_natural_key()
. Om du inte vill att Django ska mata ut naturliga nycklar under serialisering, men du vill behålla möjligheten att ladda naturliga nycklar, kan du välja att inte implementera metoden natural_key()
.
Omvänt, om du (av någon konstig anledning) vill att Django ska mata ut naturliga nycklar under serialisering, men inte kunna ladda dessa nyckelvärden, definierar du bara inte metoden get_by_natural_key()
.
Naturliga nycklar och framåtriktade referenser¶
Ibland när du använder natural foreign keys behöver du deserialisera data där ett objekt har en främmande nyckel som refererar till ett annat objekt som ännu inte har deserialiserats. Detta kallas för en ”framåtriktad referens”.
Anta t.ex. att du har följande objekt i din fixtur:
...
{
"model": "store.book",
"fields": {"name": "Mostly Harmless", "author": ["Douglas", "Adams"]},
},
...
{"model": "store.person", "fields": {"first_name": "Douglas", "last_name": "Adams"}},
...
För att hantera denna situation måste du skicka handle_forward_references=True
till serializers.deserialize()
. Detta kommer att ställa in attributet deferred_fields
på DeserializedObject
-instanser. Du kommer att behöva hålla reda på DeserializedObject
instanser där detta attribut inte är None
och senare anropa save_deferred_fields()
på dem.
Typisk användning ser ut så här:
objs_with_deferred_fields = []
for obj in serializers.deserialize("xml", data, handle_forward_references=True):
obj.save()
if obj.deferred_fields is not None:
objs_with_deferred_fields.append(obj)
for obj in objs_with_deferred_fields:
obj.save_deferred_fields()
För att detta ska fungera måste ForeignKey
på referensmodellen ha null=True
.
Beroenden under serialisering¶
Det är ofta möjligt att undvika att uttryckligen behöva hantera framåtriktade referenser genom att vara försiktig med ordningsföljden för objekt inom en fixtur.
För att hjälpa till med detta kommer anrop till dumpdata
som använder alternativet dumpdata --natural-foreign
att serialisera alla modeller med en natural_key()
-metod före serialisering av vanliga primärnyckelobjekt.
Detta kanske dock inte alltid är tillräckligt. Om din naturliga nyckel hänvisar till ett annat objekt (genom att använda en främmande nyckel eller en naturlig nyckel till ett annat objekt som en del av en naturlig nyckel), måste du kunna säkerställa att de objekt som en naturlig nyckel är beroende av förekommer i de serialiserade data innan den naturliga nyckeln kräver dem.
För att kontrollera denna ordning kan du definiera beroenden på dina natural_key()
-metoder. Detta gör du genom att ställa in ett dependencies
attribut på själva natural_key()
metoden.
Låt oss till exempel lägga till en naturlig nyckel i modellen Book
från exemplet ovan:
class Book(models.Model):
name = models.CharField(max_length=100)
author = models.ForeignKey(Person, on_delete=models.CASCADE)
def natural_key(self):
return (self.name,) + self.author.natural_key()
Den naturliga nyckeln för en Book
är en kombination av dess namn och dess författare. Detta innebär att Person
måste serialiseras före Book
. För att definiera detta beroende lägger vi till en extra rad:
def natural_key(self):
return (self.name,) + self.author.natural_key()
natural_key.dependencies = ["example_app.person"]
Denna definition säkerställer att alla Person
-objekt serialiseras före alla Book
-objekt. I sin tur kommer alla objekt som refererar till Book
att serialiseras efter att både Person
och Book
har serialiserats.