Skip to content
Projeler
Gruplar
Parçacıklar
Yardım
Yükleniyor...
Oturum aç / Kaydol
Gezinmeyi değiştir
D
django
Proje
Proje
Ayrıntılar
Etkinlik
Cycle Analytics
Depo (repository)
Depo (repository)
Dosyalar
Kayıtlar (commit)
Dallar (branch)
Etiketler
Katkıda bulunanlar
Grafik
Karşılaştır
Grafikler
Konular (issue)
0
Konular (issue)
0
Liste
Pano
Etiketler
Kilometre Taşları
Birleştirme (merge) Talepleri
0
Birleştirme (merge) Talepleri
0
CI / CD
CI / CD
İş akışları (pipeline)
İşler
Zamanlamalar
Grafikler
Paketler
Paketler
Wiki
Wiki
Parçacıklar
Parçacıklar
Üyeler
Üyeler
Collapse sidebar
Close sidebar
Etkinlik
Grafik
Grafikler
Yeni bir konu (issue) oluştur
İşler
Kayıtlar (commit)
Konu (issue) Panoları
Kenar çubuğunu aç
Batuhan Osman TASKAYA
django
Commits
312eb5cb
Kaydet (Commit)
312eb5cb
authored
Tem 13, 2018
tarafından
Peter Inglesby
Kaydeden (comit)
Tim Graham
Tem 13, 2018
Dosyalara gözat
Seçenekler
Dosyalara Gözat
İndir
Eposta Yamaları
Sade Fark
Fixed #26291 -- Allowed loaddata to handle forward references in natural_key fixtures.
üst
8f75d21a
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
352 additions
and
25 deletions
+352
-25
loaddata.py
django/core/management/commands/loaddata.py
+6
-0
base.py
django/core/serializers/base.py
+37
-5
python.py
django/core/serializers/python.py
+13
-5
xml_serializer.py
django/core/serializers/xml_serializer.py
+41
-5
2.2.txt
docs/releases/2.2.txt
+4
-1
serialization.txt
docs/topics/serialization.txt
+58
-7
forward_reference_fk.json
tests/fixtures/fixtures/forward_reference_fk.json
+20
-0
forward_reference_m2m.json
tests/fixtures/fixtures/forward_reference_m2m.json
+23
-0
models.py
tests/fixtures/models.py
+18
-0
tests.py
tests/fixtures/tests.py
+20
-1
natural.py
tests/serializers/models/natural.py
+18
-0
test_natural.py
tests/serializers/test_natural.py
+94
-1
No files found.
django/core/management/commands/loaddata.py
Dosyayı görüntüle @
312eb5cb
...
@@ -109,8 +109,11 @@ class Command(BaseCommand):
...
@@ -109,8 +109,11 @@ class Command(BaseCommand):
return
return
with
connection
.
constraint_checks_disabled
():
with
connection
.
constraint_checks_disabled
():
self
.
objs_with_deferred_fields
=
[]
for
fixture_label
in
fixture_labels
:
for
fixture_label
in
fixture_labels
:
self
.
load_label
(
fixture_label
)
self
.
load_label
(
fixture_label
)
for
obj
in
self
.
objs_with_deferred_fields
:
obj
.
save_deferred_fields
(
using
=
self
.
using
)
# Since we disabled constraint checks, we must manually check for
# Since we disabled constraint checks, we must manually check for
# any invalid keys that might have been added
# any invalid keys that might have been added
...
@@ -163,6 +166,7 @@ class Command(BaseCommand):
...
@@ -163,6 +166,7 @@ class Command(BaseCommand):
objects
=
serializers
.
deserialize
(
objects
=
serializers
.
deserialize
(
ser_fmt
,
fixture
,
using
=
self
.
using
,
ignorenonexistent
=
self
.
ignore
,
ser_fmt
,
fixture
,
using
=
self
.
using
,
ignorenonexistent
=
self
.
ignore
,
handle_forward_references
=
True
,
)
)
for
obj
in
objects
:
for
obj
in
objects
:
...
@@ -189,6 +193,8 @@ class Command(BaseCommand):
...
@@ -189,6 +193,8 @@ class Command(BaseCommand):
'error_msg'
:
e
,
'error_msg'
:
e
,
},)
},)
raise
raise
if
obj
.
deferred_fields
:
self
.
objs_with_deferred_fields
.
append
(
obj
)
if
objects
and
show_progress
:
if
objects
and
show_progress
:
self
.
stdout
.
write
(
''
)
# add a newline after progress indicator
self
.
stdout
.
write
(
''
)
# add a newline after progress indicator
self
.
loaded_object_count
+=
loaded_objects_in_fixture
self
.
loaded_object_count
+=
loaded_objects_in_fixture
...
...
django/core/serializers/base.py
Dosyayı görüntüle @
312eb5cb
...
@@ -3,8 +3,11 @@ Module for abstract serializer/unserializer base classes.
...
@@ -3,8 +3,11 @@ Module for abstract serializer/unserializer base classes.
"""
"""
from
io
import
StringIO
from
io
import
StringIO
from
django.core.exceptions
import
ObjectDoesNotExist
from
django.db
import
models
from
django.db
import
models
DEFER_FIELD
=
object
()
class
SerializerDoesNotExist
(
KeyError
):
class
SerializerDoesNotExist
(
KeyError
):
"""The requested serializer was not found."""
"""The requested serializer was not found."""
...
@@ -201,9 +204,10 @@ class DeserializedObject:
...
@@ -201,9 +204,10 @@ class DeserializedObject:
(and not touch the many-to-many stuff.)
(and not touch the many-to-many stuff.)
"""
"""
def
__init__
(
self
,
obj
,
m2m_data
=
None
):
def
__init__
(
self
,
obj
,
m2m_data
=
None
,
deferred_fields
=
None
):
self
.
object
=
obj
self
.
object
=
obj
self
.
m2m_data
=
m2m_data
self
.
m2m_data
=
m2m_data
self
.
deferred_fields
=
deferred_fields
def
__repr__
(
self
):
def
__repr__
(
self
):
return
"<
%
s:
%
s(pk=
%
s)>"
%
(
return
"<
%
s:
%
s(pk=
%
s)>"
%
(
...
@@ -225,6 +229,25 @@ class DeserializedObject:
...
@@ -225,6 +229,25 @@ class DeserializedObject:
# the m2m data twice.
# the m2m data twice.
self
.
m2m_data
=
None
self
.
m2m_data
=
None
def
save_deferred_fields
(
self
,
using
=
None
):
self
.
m2m_data
=
{}
for
field
,
field_value
in
self
.
deferred_fields
.
items
():
opts
=
self
.
object
.
_meta
label
=
opts
.
app_label
+
'.'
+
opts
.
model_name
if
isinstance
(
field
.
remote_field
,
models
.
ManyToManyRel
):
try
:
values
=
deserialize_m2m_values
(
field
,
field_value
,
using
,
handle_forward_references
=
False
)
except
M2MDeserializationError
as
e
:
raise
DeserializationError
.
WithData
(
e
.
original_exc
,
label
,
self
.
object
.
pk
,
e
.
pk
)
self
.
m2m_data
[
field
.
name
]
=
values
elif
isinstance
(
field
.
remote_field
,
models
.
ManyToOneRel
):
try
:
value
=
deserialize_fk_value
(
field
,
field_value
,
using
,
handle_forward_references
=
False
)
except
Exception
as
e
:
raise
DeserializationError
.
WithData
(
e
,
label
,
self
.
object
.
pk
,
field_value
)
setattr
(
self
.
object
,
field
.
attname
,
value
)
self
.
save
()
def
build_instance
(
Model
,
data
,
db
):
def
build_instance
(
Model
,
data
,
db
):
"""
"""
...
@@ -244,7 +267,7 @@ def build_instance(Model, data, db):
...
@@ -244,7 +267,7 @@ def build_instance(Model, data, db):
return
obj
return
obj
def
deserialize_m2m_values
(
field
,
field_value
,
using
):
def
deserialize_m2m_values
(
field
,
field_value
,
using
,
handle_forward_references
):
model
=
field
.
remote_field
.
model
model
=
field
.
remote_field
.
model
if
hasattr
(
model
.
_default_manager
,
'get_by_natural_key'
):
if
hasattr
(
model
.
_default_manager
,
'get_by_natural_key'
):
def
m2m_convert
(
value
):
def
m2m_convert
(
value
):
...
@@ -262,10 +285,13 @@ def deserialize_m2m_values(field, field_value, using):
...
@@ -262,10 +285,13 @@ def deserialize_m2m_values(field, field_value, using):
values
.
append
(
m2m_convert
(
pk
))
values
.
append
(
m2m_convert
(
pk
))
return
values
return
values
except
Exception
as
e
:
except
Exception
as
e
:
raise
M2MDeserializationError
(
e
,
pk
)
if
isinstance
(
e
,
ObjectDoesNotExist
)
and
handle_forward_references
:
return
DEFER_FIELD
else
:
raise
M2MDeserializationError
(
e
,
pk
)
def
deserialize_fk_value
(
field
,
field_value
,
using
):
def
deserialize_fk_value
(
field
,
field_value
,
using
,
handle_forward_references
):
if
field_value
is
None
:
if
field_value
is
None
:
return
None
return
None
model
=
field
.
remote_field
.
model
model
=
field
.
remote_field
.
model
...
@@ -273,7 +299,13 @@ def deserialize_fk_value(field, field_value, using):
...
@@ -273,7 +299,13 @@ def deserialize_fk_value(field, field_value, using):
field_name
=
field
.
remote_field
.
field_name
field_name
=
field
.
remote_field
.
field_name
if
(
hasattr
(
default_manager
,
'get_by_natural_key'
)
and
if
(
hasattr
(
default_manager
,
'get_by_natural_key'
)
and
hasattr
(
field_value
,
'__iter__'
)
and
not
isinstance
(
field_value
,
str
)):
hasattr
(
field_value
,
'__iter__'
)
and
not
isinstance
(
field_value
,
str
)):
obj
=
default_manager
.
db_manager
(
using
)
.
get_by_natural_key
(
*
field_value
)
try
:
obj
=
default_manager
.
db_manager
(
using
)
.
get_by_natural_key
(
*
field_value
)
except
ObjectDoesNotExist
:
if
handle_forward_references
:
return
DEFER_FIELD
else
:
raise
value
=
getattr
(
obj
,
field_name
)
value
=
getattr
(
obj
,
field_name
)
# If this is a natural foreign key to an object that has a FK/O2O as
# If this is a natural foreign key to an object that has a FK/O2O as
# the foreign key, use the FK value.
# the foreign key, use the FK value.
...
...
django/core/serializers/python.py
Dosyayı görüntüle @
312eb5cb
...
@@ -83,6 +83,7 @@ def Deserializer(object_list, *, using=DEFAULT_DB_ALIAS, ignorenonexistent=False
...
@@ -83,6 +83,7 @@ def Deserializer(object_list, *, using=DEFAULT_DB_ALIAS, ignorenonexistent=False
It's expected that you pass the Python objects themselves (instead of a
It's expected that you pass the Python objects themselves (instead of a
stream or a string) to the constructor
stream or a string) to the constructor
"""
"""
handle_forward_references
=
options
.
pop
(
'handle_forward_references'
,
False
)
field_names_cache
=
{}
# Model: <list of field_names>
field_names_cache
=
{}
# Model: <list of field_names>
for
d
in
object_list
:
for
d
in
object_list
:
...
@@ -101,6 +102,7 @@ def Deserializer(object_list, *, using=DEFAULT_DB_ALIAS, ignorenonexistent=False
...
@@ -101,6 +102,7 @@ def Deserializer(object_list, *, using=DEFAULT_DB_ALIAS, ignorenonexistent=False
except
Exception
as
e
:
except
Exception
as
e
:
raise
base
.
DeserializationError
.
WithData
(
e
,
d
[
'model'
],
d
.
get
(
'pk'
),
None
)
raise
base
.
DeserializationError
.
WithData
(
e
,
d
[
'model'
],
d
.
get
(
'pk'
),
None
)
m2m_data
=
{}
m2m_data
=
{}
deferred_fields
=
{}
if
Model
not
in
field_names_cache
:
if
Model
not
in
field_names_cache
:
field_names_cache
[
Model
]
=
{
f
.
name
for
f
in
Model
.
_meta
.
get_fields
()}
field_names_cache
[
Model
]
=
{
f
.
name
for
f
in
Model
.
_meta
.
get_fields
()}
...
@@ -118,17 +120,23 @@ def Deserializer(object_list, *, using=DEFAULT_DB_ALIAS, ignorenonexistent=False
...
@@ -118,17 +120,23 @@ def Deserializer(object_list, *, using=DEFAULT_DB_ALIAS, ignorenonexistent=False
# Handle M2M relations
# Handle M2M relations
if
field
.
remote_field
and
isinstance
(
field
.
remote_field
,
models
.
ManyToManyRel
):
if
field
.
remote_field
and
isinstance
(
field
.
remote_field
,
models
.
ManyToManyRel
):
try
:
try
:
values
=
base
.
deserialize_m2m_values
(
field
,
field_value
,
using
)
values
=
base
.
deserialize_m2m_values
(
field
,
field_value
,
using
,
handle_forward_references
)
except
base
.
M2MDeserializationError
as
e
:
except
base
.
M2MDeserializationError
as
e
:
raise
base
.
DeserializationError
.
WithData
(
e
.
original_exc
,
d
[
'model'
],
d
.
get
(
'pk'
),
e
.
pk
)
raise
base
.
DeserializationError
.
WithData
(
e
.
original_exc
,
d
[
'model'
],
d
.
get
(
'pk'
),
e
.
pk
)
m2m_data
[
field
.
name
]
=
values
if
values
==
base
.
DEFER_FIELD
:
deferred_fields
[
field
]
=
field_value
else
:
m2m_data
[
field
.
name
]
=
values
# Handle FK fields
# Handle FK fields
elif
field
.
remote_field
and
isinstance
(
field
.
remote_field
,
models
.
ManyToOneRel
):
elif
field
.
remote_field
and
isinstance
(
field
.
remote_field
,
models
.
ManyToOneRel
):
try
:
try
:
value
=
base
.
deserialize_fk_value
(
field
,
field_value
,
using
)
value
=
base
.
deserialize_fk_value
(
field
,
field_value
,
using
,
handle_forward_references
)
except
Exception
as
e
:
except
Exception
as
e
:
raise
base
.
DeserializationError
.
WithData
(
e
,
d
[
'model'
],
d
.
get
(
'pk'
),
field_value
)
raise
base
.
DeserializationError
.
WithData
(
e
,
d
[
'model'
],
d
.
get
(
'pk'
),
field_value
)
data
[
field
.
attname
]
=
value
if
value
==
base
.
DEFER_FIELD
:
deferred_fields
[
field
]
=
field_value
else
:
data
[
field
.
attname
]
=
value
# Handle all other fields
# Handle all other fields
else
:
else
:
try
:
try
:
...
@@ -137,7 +145,7 @@ def Deserializer(object_list, *, using=DEFAULT_DB_ALIAS, ignorenonexistent=False
...
@@ -137,7 +145,7 @@ def Deserializer(object_list, *, using=DEFAULT_DB_ALIAS, ignorenonexistent=False
raise
base
.
DeserializationError
.
WithData
(
e
,
d
[
'model'
],
d
.
get
(
'pk'
),
field_value
)
raise
base
.
DeserializationError
.
WithData
(
e
,
d
[
'model'
],
d
.
get
(
'pk'
),
field_value
)
obj
=
base
.
build_instance
(
Model
,
data
,
using
)
obj
=
base
.
build_instance
(
Model
,
data
,
using
)
yield
base
.
DeserializedObject
(
obj
,
m2m_data
)
yield
base
.
DeserializedObject
(
obj
,
m2m_data
,
deferred_fields
)
def
_get_model
(
model_identifier
):
def
_get_model
(
model_identifier
):
...
...
django/core/serializers/xml_serializer.py
Dosyayı görüntüle @
312eb5cb
...
@@ -8,6 +8,7 @@ from xml.sax.expatreader import ExpatParser as _ExpatParser
...
@@ -8,6 +8,7 @@ from xml.sax.expatreader import ExpatParser as _ExpatParser
from
django.apps
import
apps
from
django.apps
import
apps
from
django.conf
import
settings
from
django.conf
import
settings
from
django.core.exceptions
import
ObjectDoesNotExist
from
django.core.serializers
import
base
from
django.core.serializers
import
base
from
django.db
import
DEFAULT_DB_ALIAS
,
models
from
django.db
import
DEFAULT_DB_ALIAS
,
models
from
django.utils.xmlutils
import
(
from
django.utils.xmlutils
import
(
...
@@ -151,6 +152,7 @@ class Deserializer(base.Deserializer):
...
@@ -151,6 +152,7 @@ class Deserializer(base.Deserializer):
def
__init__
(
self
,
stream_or_string
,
*
,
using
=
DEFAULT_DB_ALIAS
,
ignorenonexistent
=
False
,
**
options
):
def
__init__
(
self
,
stream_or_string
,
*
,
using
=
DEFAULT_DB_ALIAS
,
ignorenonexistent
=
False
,
**
options
):
super
()
.
__init__
(
stream_or_string
,
**
options
)
super
()
.
__init__
(
stream_or_string
,
**
options
)
self
.
handle_forward_references
=
options
.
pop
(
'handle_forward_references'
,
False
)
self
.
event_stream
=
pulldom
.
parse
(
self
.
stream
,
self
.
_make_parser
())
self
.
event_stream
=
pulldom
.
parse
(
self
.
stream
,
self
.
_make_parser
())
self
.
db
=
using
self
.
db
=
using
self
.
ignore
=
ignorenonexistent
self
.
ignore
=
ignorenonexistent
...
@@ -181,6 +183,7 @@ class Deserializer(base.Deserializer):
...
@@ -181,6 +183,7 @@ class Deserializer(base.Deserializer):
# Also start building a dict of m2m data (this is saved as
# Also start building a dict of m2m data (this is saved as
# {m2m_accessor_attribute : [list_of_related_objects]})
# {m2m_accessor_attribute : [list_of_related_objects]})
m2m_data
=
{}
m2m_data
=
{}
deferred_fields
=
{}
field_names
=
{
f
.
name
for
f
in
Model
.
_meta
.
get_fields
()}
field_names
=
{
f
.
name
for
f
in
Model
.
_meta
.
get_fields
()}
# Deserialize each field.
# Deserialize each field.
...
@@ -200,9 +203,26 @@ class Deserializer(base.Deserializer):
...
@@ -200,9 +203,26 @@ class Deserializer(base.Deserializer):
# As is usually the case, relation fields get the special treatment.
# As is usually the case, relation fields get the special treatment.
if
field
.
remote_field
and
isinstance
(
field
.
remote_field
,
models
.
ManyToManyRel
):
if
field
.
remote_field
and
isinstance
(
field
.
remote_field
,
models
.
ManyToManyRel
):
m2m_data
[
field
.
name
]
=
self
.
_handle_m2m_field_node
(
field_node
,
field
)
value
=
self
.
_handle_m2m_field_node
(
field_node
,
field
)
if
value
==
base
.
DEFER_FIELD
:
deferred_fields
[
field
]
=
[
[
getInnerText
(
nat_node
)
.
strip
()
for
nat_node
in
obj_node
.
getElementsByTagName
(
'natural'
)
]
for
obj_node
in
field_node
.
getElementsByTagName
(
'object'
)
]
else
:
m2m_data
[
field
.
name
]
=
value
elif
field
.
remote_field
and
isinstance
(
field
.
remote_field
,
models
.
ManyToOneRel
):
elif
field
.
remote_field
and
isinstance
(
field
.
remote_field
,
models
.
ManyToOneRel
):
data
[
field
.
attname
]
=
self
.
_handle_fk_field_node
(
field_node
,
field
)
value
=
self
.
_handle_fk_field_node
(
field_node
,
field
)
if
value
==
base
.
DEFER_FIELD
:
deferred_fields
[
field
]
=
[
getInnerText
(
k
)
.
strip
()
for
k
in
field_node
.
getElementsByTagName
(
'natural'
)
]
else
:
data
[
field
.
attname
]
=
value
else
:
else
:
if
field_node
.
getElementsByTagName
(
'None'
):
if
field_node
.
getElementsByTagName
(
'None'
):
value
=
None
value
=
None
...
@@ -213,7 +233,7 @@ class Deserializer(base.Deserializer):
...
@@ -213,7 +233,7 @@ class Deserializer(base.Deserializer):
obj
=
base
.
build_instance
(
Model
,
data
,
self
.
db
)
obj
=
base
.
build_instance
(
Model
,
data
,
self
.
db
)
# Return a DeserializedObject so that the m2m data has a place to live.
# Return a DeserializedObject so that the m2m data has a place to live.
return
base
.
DeserializedObject
(
obj
,
m2m_data
)
return
base
.
DeserializedObject
(
obj
,
m2m_data
,
deferred_fields
)
def
_handle_fk_field_node
(
self
,
node
,
field
):
def
_handle_fk_field_node
(
self
,
node
,
field
):
"""
"""
...
@@ -229,7 +249,13 @@ class Deserializer(base.Deserializer):
...
@@ -229,7 +249,13 @@ class Deserializer(base.Deserializer):
if
keys
:
if
keys
:
# If there are 'natural' subelements, it must be a natural key
# If there are 'natural' subelements, it must be a natural key
field_value
=
[
getInnerText
(
k
)
.
strip
()
for
k
in
keys
]
field_value
=
[
getInnerText
(
k
)
.
strip
()
for
k
in
keys
]
obj
=
model
.
_default_manager
.
db_manager
(
self
.
db
)
.
get_by_natural_key
(
*
field_value
)
try
:
obj
=
model
.
_default_manager
.
db_manager
(
self
.
db
)
.
get_by_natural_key
(
*
field_value
)
except
ObjectDoesNotExist
:
if
self
.
handle_forward_references
:
return
base
.
DEFER_FIELD
else
:
raise
obj_pk
=
getattr
(
obj
,
field
.
remote_field
.
field_name
)
obj_pk
=
getattr
(
obj
,
field
.
remote_field
.
field_name
)
# If this is a natural foreign key to an object that
# If this is a natural foreign key to an object that
# has a FK/O2O as the foreign key, use the FK value
# has a FK/O2O as the foreign key, use the FK value
...
@@ -264,7 +290,17 @@ class Deserializer(base.Deserializer):
...
@@ -264,7 +290,17 @@ class Deserializer(base.Deserializer):
else
:
else
:
def
m2m_convert
(
n
):
def
m2m_convert
(
n
):
return
model
.
_meta
.
pk
.
to_python
(
n
.
getAttribute
(
'pk'
))
return
model
.
_meta
.
pk
.
to_python
(
n
.
getAttribute
(
'pk'
))
return
[
m2m_convert
(
c
)
for
c
in
node
.
getElementsByTagName
(
"object"
)]
values
=
[]
try
:
for
c
in
node
.
getElementsByTagName
(
'object'
):
values
.
append
(
m2m_convert
(
c
))
except
Exception
as
e
:
if
isinstance
(
e
,
ObjectDoesNotExist
)
and
self
.
handle_forward_references
:
return
base
.
DEFER_FIELD
else
:
raise
base
.
M2MDeserializationError
(
e
,
c
)
else
:
return
values
def
_get_model_from_node
(
self
,
node
,
attr
):
def
_get_model_from_node
(
self
,
node
,
attr
):
"""
"""
...
...
docs/releases/2.2.txt
Dosyayı görüntüle @
312eb5cb
...
@@ -184,7 +184,10 @@ Requests and Responses
...
@@ -184,7 +184,10 @@ Requests and Responses
Serialization
Serialization
~~~~~~~~~~~~~
~~~~~~~~~~~~~
* ...
* You can now deserialize data using natural keys containing :ref:`forward
references <natural-keys-and-forward-references>` by passing
``handle_forward_references=True`` to ``serializers.deserialize()``.
Additionally, :djadmin:`loaddata` handles forward references automatically.
Signals
Signals
~~~~~~~
~~~~~~~
...
...
docs/topics/serialization.txt
Dosyayı görüntüle @
312eb5cb
...
@@ -514,17 +514,68 @@ command line flags to generate natural keys.
...
@@ -514,17 +514,68 @@ command line flags to generate natural keys.
natural keys during serialization, but *not* be able to load those
natural keys during serialization, but *not* be able to load those
key values, just don't define the ``get_by_natural_key()`` method.
key values, just don't define the ``get_by_natural_key()`` method.
.. _natural-keys-and-forward-references:
Natural keys and forward references
-----------------------------------
.. versionadded:: 2.2
Sometimes when you use :ref:`natural foreign keys
<topics-serialization-natural-keys>` you'll need to deserialize data where
an object has a foreign key referencing another object that hasn't yet been
deserialized. This is called a "forward reference".
For instance, suppose you have the following objects in your fixture::
...
{
"model": "store.book",
"fields": {
"name": "Mostly Harmless",
"author": ["Douglas", "Adams"]
}
},
...
{
"model": "store.person",
"fields": {
"first_name": "Douglas",
"last_name": "Adams"
}
},
...
In order to handle this situation, you need to pass
``handle_forward_references=True`` to ``serializers.deserialize()``. This will
set the ``deferred_fields`` attribute on the ``DeserializedObject`` instances.
You'll need to keep track of ``DeserializedObject`` instances where this
attribute isn't ``None`` and later call ``save_deferred_fields()`` on them.
Typical usage looks like this::
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()
For this to work, the ``ForeignKey`` on the referencing model must have
``null=True``.
Dependencies during serialization
Dependencies during serialization
---------------------------------
---------------------------------
Since natural keys rely on database lookups to resolve references, it
It's often possible to avoid explicitly having to handle forward references by
is important that the data exists before it is referenced. You can't make
taking care with the ordering of objects within a fixture.
a "forward reference" with natural keys -- the data you're referencing
must exist before you include a natural key reference to that data.
To
accommodate this limitation, calls to :djadmin:`dumpdata` that use
To
help with this, calls to :djadmin:`dumpdata` that use the :option:`dumpdata
the :option:`dumpdata --natural-foreign` option will serialize any model with a
--natural-foreign` option will serialize any model with a ``natural_key()``
``natural_key()``
method before serializing standard primary key objects.
method before serializing standard primary key objects.
However, this may not always be enough. If your natural key refers to
However, this may not always be enough. If your natural key refers to
another object (by using a foreign key or natural key to another object
another object (by using a foreign key or natural key to another object
...
...
tests/fixtures/fixtures/forward_reference_fk.json
0 → 100644
Dosyayı görüntüle @
312eb5cb
[
{
"model"
:
"fixtures.naturalkeything"
,
"fields"
:
{
"key"
:
"t1"
,
"other_thing"
:
[
"t2"
]
}
},
{
"model"
:
"fixtures.naturalkeything"
,
"fields"
:
{
"key"
:
"t2"
,
"other_thing"
:
[
"t1"
]
}
}
]
tests/fixtures/fixtures/forward_reference_m2m.json
0 → 100644
Dosyayı görüntüle @
312eb5cb
[
{
"model"
:
"fixtures.naturalkeything"
,
"fields"
:
{
"key"
:
"t1"
,
"other_things"
:
[
[
"t2"
],
[
"t3"
]
]
}
},
{
"model"
:
"fixtures.naturalkeything"
,
"fields"
:
{
"key"
:
"t2"
}
},
{
"model"
:
"fixtures.naturalkeything"
,
"fields"
:
{
"key"
:
"t3"
}
}
]
tests/fixtures/models.py
Dosyayı görüntüle @
312eb5cb
...
@@ -116,3 +116,21 @@ class Book(models.Model):
...
@@ -116,3 +116,21 @@ class Book(models.Model):
class
PrimaryKeyUUIDModel
(
models
.
Model
):
class
PrimaryKeyUUIDModel
(
models
.
Model
):
id
=
models
.
UUIDField
(
primary_key
=
True
,
default
=
uuid
.
uuid4
)
id
=
models
.
UUIDField
(
primary_key
=
True
,
default
=
uuid
.
uuid4
)
class
NaturalKeyThing
(
models
.
Model
):
key
=
models
.
CharField
(
max_length
=
100
)
other_thing
=
models
.
ForeignKey
(
'NaturalKeyThing'
,
on_delete
=
models
.
CASCADE
,
null
=
True
)
other_things
=
models
.
ManyToManyField
(
'NaturalKeyThing'
,
related_name
=
'thing_m2m_set'
)
class
Manager
(
models
.
Manager
):
def
get_by_natural_key
(
self
,
key
):
return
self
.
get
(
key
=
key
)
objects
=
Manager
()
def
natural_key
(
self
):
return
(
self
.
key
,)
def
__str__
(
self
):
return
self
.
key
tests/fixtures/tests.py
Dosyayı görüntüle @
312eb5cb
...
@@ -17,7 +17,8 @@ from django.db import IntegrityError, connection
...
@@ -17,7 +17,8 @@ from django.db import IntegrityError, connection
from
django.test
import
TestCase
,
TransactionTestCase
,
skipUnlessDBFeature
from
django.test
import
TestCase
,
TransactionTestCase
,
skipUnlessDBFeature
from
.models
import
(
from
.models
import
(
Article
,
Category
,
PrimaryKeyUUIDModel
,
ProxySpy
,
Spy
,
Tag
,
Visa
,
Article
,
Category
,
NaturalKeyThing
,
PrimaryKeyUUIDModel
,
ProxySpy
,
Spy
,
Tag
,
Visa
,
)
)
...
@@ -780,3 +781,21 @@ class FixtureTransactionTests(DumpDataAssertMixin, TransactionTestCase):
...
@@ -780,3 +781,21 @@ class FixtureTransactionTests(DumpDataAssertMixin, TransactionTestCase):
'<Article: Time to reform copyright>'
,
'<Article: Time to reform copyright>'
,
'<Article: Poker has no place on ESPN>'
,
'<Article: Poker has no place on ESPN>'
,
])
])
class
ForwardReferenceTests
(
TestCase
):
def
test_forward_reference_fk
(
self
):
management
.
call_command
(
'loaddata'
,
'forward_reference_fk.json'
,
verbosity
=
0
)
self
.
assertEqual
(
NaturalKeyThing
.
objects
.
count
(),
2
)
t1
,
t2
=
NaturalKeyThing
.
objects
.
all
()
self
.
assertEqual
(
t1
.
other_thing
,
t2
)
self
.
assertEqual
(
t2
.
other_thing
,
t1
)
def
test_forward_reference_m2m
(
self
):
management
.
call_command
(
'loaddata'
,
'forward_reference_m2m.json'
,
verbosity
=
0
)
self
.
assertEqual
(
NaturalKeyThing
.
objects
.
count
(),
3
)
t1
=
NaturalKeyThing
.
objects
.
get_by_natural_key
(
't1'
)
self
.
assertQuerysetEqual
(
t1
.
other_things
.
order_by
(
'key'
),
[
'<NaturalKeyThing: t2>'
,
'<NaturalKeyThing: t3>'
]
)
tests/serializers/models/natural.py
Dosyayı görüntüle @
312eb5cb
...
@@ -19,3 +19,21 @@ class NaturalKeyAnchor(models.Model):
...
@@ -19,3 +19,21 @@ class NaturalKeyAnchor(models.Model):
class
FKDataNaturalKey
(
models
.
Model
):
class
FKDataNaturalKey
(
models
.
Model
):
data
=
models
.
ForeignKey
(
NaturalKeyAnchor
,
models
.
SET_NULL
,
null
=
True
)
data
=
models
.
ForeignKey
(
NaturalKeyAnchor
,
models
.
SET_NULL
,
null
=
True
)
class
NaturalKeyThing
(
models
.
Model
):
key
=
models
.
CharField
(
max_length
=
100
)
other_thing
=
models
.
ForeignKey
(
'NaturalKeyThing'
,
on_delete
=
models
.
CASCADE
,
null
=
True
)
other_things
=
models
.
ManyToManyField
(
'NaturalKeyThing'
,
related_name
=
'thing_m2m_set'
)
class
Manager
(
models
.
Manager
):
def
get_by_natural_key
(
self
,
key
):
return
self
.
get
(
key
=
key
)
objects
=
Manager
()
def
natural_key
(
self
):
return
(
self
.
key
,)
def
__str__
(
self
):
return
self
.
key
tests/serializers/test_natural.py
Dosyayı görüntüle @
312eb5cb
...
@@ -2,7 +2,7 @@ from django.core import serializers
...
@@ -2,7 +2,7 @@ from django.core import serializers
from
django.db
import
connection
from
django.db
import
connection
from
django.test
import
TestCase
from
django.test
import
TestCase
from
.models
import
Child
,
FKDataNaturalKey
,
NaturalKeyAnchor
from
.models
import
Child
,
FKDataNaturalKey
,
NaturalKeyAnchor
,
NaturalKeyThing
from
.tests
import
register_tests
from
.tests
import
register_tests
...
@@ -93,7 +93,100 @@ def natural_pk_mti_test(self, format):
...
@@ -93,7 +93,100 @@ def natural_pk_mti_test(self, format):
self
.
assertEqual
(
child
.
child_data
,
child
.
parent_data
)
self
.
assertEqual
(
child
.
child_data
,
child
.
parent_data
)
def
forward_ref_fk_test
(
self
,
format
):
t1
=
NaturalKeyThing
.
objects
.
create
(
key
=
't1'
)
t2
=
NaturalKeyThing
.
objects
.
create
(
key
=
't2'
,
other_thing
=
t1
)
t1
.
other_thing
=
t2
t1
.
save
()
string_data
=
serializers
.
serialize
(
format
,
[
t1
,
t2
],
use_natural_primary_keys
=
True
,
use_natural_foreign_keys
=
True
,
)
NaturalKeyThing
.
objects
.
all
()
.
delete
()
objs_with_deferred_fields
=
[]
for
obj
in
serializers
.
deserialize
(
format
,
string_data
,
handle_forward_references
=
True
):
obj
.
save
()
if
obj
.
deferred_fields
:
objs_with_deferred_fields
.
append
(
obj
)
for
obj
in
objs_with_deferred_fields
:
obj
.
save_deferred_fields
()
t1
=
NaturalKeyThing
.
objects
.
get
(
key
=
't1'
)
t2
=
NaturalKeyThing
.
objects
.
get
(
key
=
't2'
)
self
.
assertEqual
(
t1
.
other_thing
,
t2
)
self
.
assertEqual
(
t2
.
other_thing
,
t1
)
def
forward_ref_fk_with_error_test
(
self
,
format
):
t1
=
NaturalKeyThing
.
objects
.
create
(
key
=
't1'
)
t2
=
NaturalKeyThing
.
objects
.
create
(
key
=
't2'
,
other_thing
=
t1
)
t1
.
other_thing
=
t2
t1
.
save
()
string_data
=
serializers
.
serialize
(
format
,
[
t1
],
use_natural_primary_keys
=
True
,
use_natural_foreign_keys
=
True
,
)
NaturalKeyThing
.
objects
.
all
()
.
delete
()
objs_with_deferred_fields
=
[]
for
obj
in
serializers
.
deserialize
(
format
,
string_data
,
handle_forward_references
=
True
):
obj
.
save
()
if
obj
.
deferred_fields
:
objs_with_deferred_fields
.
append
(
obj
)
obj
=
objs_with_deferred_fields
[
0
]
msg
=
'NaturalKeyThing matching query does not exist'
with
self
.
assertRaisesMessage
(
serializers
.
base
.
DeserializationError
,
msg
):
obj
.
save_deferred_fields
()
def
forward_ref_m2m_test
(
self
,
format
):
t1
=
NaturalKeyThing
.
objects
.
create
(
key
=
't1'
)
t2
=
NaturalKeyThing
.
objects
.
create
(
key
=
't2'
)
t3
=
NaturalKeyThing
.
objects
.
create
(
key
=
't3'
)
t1
.
other_things
.
set
([
t2
,
t3
])
string_data
=
serializers
.
serialize
(
format
,
[
t1
,
t2
,
t3
],
use_natural_primary_keys
=
True
,
use_natural_foreign_keys
=
True
,
)
NaturalKeyThing
.
objects
.
all
()
.
delete
()
objs_with_deferred_fields
=
[]
for
obj
in
serializers
.
deserialize
(
format
,
string_data
,
handle_forward_references
=
True
):
obj
.
save
()
if
obj
.
deferred_fields
:
objs_with_deferred_fields
.
append
(
obj
)
for
obj
in
objs_with_deferred_fields
:
obj
.
save_deferred_fields
()
t1
=
NaturalKeyThing
.
objects
.
get
(
key
=
't1'
)
t2
=
NaturalKeyThing
.
objects
.
get
(
key
=
't2'
)
t3
=
NaturalKeyThing
.
objects
.
get
(
key
=
't3'
)
self
.
assertCountEqual
(
t1
.
other_things
.
all
(),
[
t2
,
t3
])
def
forward_ref_m2m_with_error_test
(
self
,
format
):
t1
=
NaturalKeyThing
.
objects
.
create
(
key
=
't1'
)
t2
=
NaturalKeyThing
.
objects
.
create
(
key
=
't2'
)
t3
=
NaturalKeyThing
.
objects
.
create
(
key
=
't3'
)
t1
.
other_things
.
set
([
t2
,
t3
])
t1
.
save
()
string_data
=
serializers
.
serialize
(
format
,
[
t1
,
t2
],
use_natural_primary_keys
=
True
,
use_natural_foreign_keys
=
True
,
)
NaturalKeyThing
.
objects
.
all
()
.
delete
()
objs_with_deferred_fields
=
[]
for
obj
in
serializers
.
deserialize
(
format
,
string_data
,
handle_forward_references
=
True
):
obj
.
save
()
if
obj
.
deferred_fields
:
objs_with_deferred_fields
.
append
(
obj
)
obj
=
objs_with_deferred_fields
[
0
]
msg
=
'NaturalKeyThing matching query does not exist'
with
self
.
assertRaisesMessage
(
serializers
.
base
.
DeserializationError
,
msg
):
obj
.
save_deferred_fields
()
# Dynamically register tests for each serializer
# Dynamically register tests for each serializer
register_tests
(
NaturalKeySerializerTests
,
'test_
%
s_natural_key_serializer'
,
natural_key_serializer_test
)
register_tests
(
NaturalKeySerializerTests
,
'test_
%
s_natural_key_serializer'
,
natural_key_serializer_test
)
register_tests
(
NaturalKeySerializerTests
,
'test_
%
s_serializer_natural_keys'
,
natural_key_test
)
register_tests
(
NaturalKeySerializerTests
,
'test_
%
s_serializer_natural_keys'
,
natural_key_test
)
register_tests
(
NaturalKeySerializerTests
,
'test_
%
s_serializer_natural_pks_mti'
,
natural_pk_mti_test
)
register_tests
(
NaturalKeySerializerTests
,
'test_
%
s_serializer_natural_pks_mti'
,
natural_pk_mti_test
)
register_tests
(
NaturalKeySerializerTests
,
'test_
%
s_forward_references_fks'
,
forward_ref_fk_test
)
register_tests
(
NaturalKeySerializerTests
,
'test_
%
s_forward_references_fk_errors'
,
forward_ref_fk_with_error_test
)
register_tests
(
NaturalKeySerializerTests
,
'test_
%
s_forward_references_m2ms'
,
forward_ref_m2m_test
)
register_tests
(
NaturalKeySerializerTests
,
'test_
%
s_forward_references_m2m_errors'
,
forward_ref_m2m_with_error_test
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment