#36786: XML serializer mishandles nullable elements of a related object's natural
key
-------------------------------------+-------------------------------------
Reporter: Jacob Walls | Type: Bug
Status: new | Component: Core
| (Serialization)
Version: 5.2 | Severity: Normal
Keywords: xml | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
If a field on your model's natural key is nullable, a dumpdata/loaddata
roundtrip works in JSON but fails in XML because the XML fixture contains
`<natural>None</natural>`, which deserializes to `"None"`, which is !=
`None`.
Elsewhere there is an `addQuickElement("None")` that produces a clear
`<None></None>` value, but nothing like that is used for nullable elements
of a natural key.
----
models
{{{#!py
from django.db import models
class WidgetManager(models.Manager):
def get_by_natural_key(self, foo):
self.get(foo=foo)
class Widget(models.Model):
foo = models.UUIDField(null=True)
objects = WidgetManager()
def natural_key(self):
return (self.foo,)
}}}
{{{
./manage.py makemigrations
./manage.py migrate
./manage.py shell
}}}
{{{#!py
Gadget.objects.create(widget=Widget.objects.create())
}}}
{{{
./manage.py dumpdata myapp --format=xml --natural-foreign > fixture.xml
./manage.py loaddata fixture.xml
}}}
Fixture content:
{{{#!xml
<?xml version="1.0" encoding="utf-8"?>
<django-objects version="1.0">
<object model="myapp.widget" pk="1">
<field name="name" type="CharField">default</field>
<field name="foo" type="UUIDField">
<None></None>
</field>
</object>
<object model="myapp.gadget" pk="1">
<field name="widget" rel="ManyToOneRel" to="myapp.widget">
<natural>default</natural>
<natural>None</natural>
</field>
</object>
</django-objects>
}}}
loaddata error:
{{{#!py
Traceback (most recent call last):
File "/Users/jwalls/django/django/db/models/fields/__init__.py", line
2766, in to_python
return uuid.UUID(**{input_form: value})
~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
File
"/Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/uuid.py",
line 219, in __init__
raise ValueError('badly formed hexadecimal UUID string')
ValueError: badly formed hexadecimal UUID string
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/jwalls/zed/./manage.py", line 22, in <module>
main()
~~~~^^
File "/Users/jwalls/zed/./manage.py", line 18, in main
execute_from_command_line(sys.argv)
~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
File "/Users/jwalls/django/django/core/management/__init__.py", line
443, in execute_from_command_line
utility.execute()
~~~~~~~~~~~~~~~^^
File "/Users/jwalls/django/django/core/management/__init__.py", line
437, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
File "/Users/jwalls/django/django/core/management/base.py", line 416, in
run_from_argv
self.execute(*args, **cmd_options)
~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
File "/Users/jwalls/django/django/core/management/base.py", line 460, in
execute
output = self.handle(*args, **options)
File "/Users/jwalls/django/django/core/management/commands/loaddata.py",
line 103, in handle
self.loaddata(fixture_labels)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
File "/Users/jwalls/django/django/core/management/commands/loaddata.py",
line 164, in loaddata
self.load_label(fixture_label)
~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^
File "/Users/jwalls/django/django/core/management/commands/loaddata.py",
line 252, in load_label
for obj in objects:
^^^^^^^
File "/Users/jwalls/django/django/core/serializers/xml_serializer.py",
line 235, in __next__
return self._handle_object(node)
~~~~~~~~~~~~~~~~~~~^^^^^^
File "/Users/jwalls/django/django/core/serializers/xml_serializer.py",
line 293, in _handle_object
value = self._handle_fk_field_node(field_node, field)
File "/Users/jwalls/django/django/core/serializers/xml_serializer.py",
line 332, in _handle_fk_field_node
obj = model._default_manager.db_manager(
self.db
).get_by_natural_key(*field_value)
File "/Users/jwalls/zed/myapp/models.py", line 6, in get_by_natural_key
return self.get(name=name, foo=foo)
~~~~~~~~^^^^^^^^^^^^^^^^^^^^
File "/Users/jwalls/django/django/db/models/manager.py", line 87, in
manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
File "/Users/jwalls/django/django/db/models/query.py", line 625, in get
clone = self._chain() if self.query.combinator else self.filter(*args,
**kwargs)
~~~~~~~~~~~^^^^^^^^^^^^^^^^^
File "/Users/jwalls/django/django/db/models/query.py", line 1542, in
filter
return self._filter_or_exclude(False, args, kwargs)
~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
File "/Users/jwalls/django/django/db/models/query.py", line 1560, in
_filter_or_exclude
clone._filter_or_exclude_inplace(negate, args, kwargs)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
File "/Users/jwalls/django/django/db/models/query.py", line 1570, in
_filter_or_exclude_inplace
self._query.add_q(Q(*args, **kwargs))
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
File "/Users/jwalls/django/django/db/models/sql/query.py", line 1671, in
add_q
clause, _ = self._add_q(q_object, can_reuse)
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
File "/Users/jwalls/django/django/db/models/sql/query.py", line 1703, in
_add_q
child_clause, needed_inner = self.build_filter(
~~~~~~~~~~~~~~~~~^
child,
^^^^^^
...<7 lines>...
update_join_types=update_join_types,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/Users/jwalls/django/django/db/models/sql/query.py", line 1613, in
build_filter
condition = self.build_lookup(lookups, col, value)
File "/Users/jwalls/django/django/db/models/sql/query.py", line 1440, in
build_lookup
lookup = lookup_class(lhs, rhs)
File "/Users/jwalls/django/django/db/models/lookups.py", line 35, in
__init__
self.rhs = self.get_prep_lookup()
~~~~~~~~~~~~~~~~~~~~^^
File "/Users/jwalls/django/django/db/models/lookups.py", line 391, in
get_prep_lookup
return super().get_prep_lookup()
~~~~~~~~~~~~~~~~~~~~~~~^^
File "/Users/jwalls/django/django/db/models/lookups.py", line 93, in
get_prep_lookup
return self.lhs.output_field.get_prep_value(self.rhs)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
File "/Users/jwalls/django/django/db/models/fields/__init__.py", line
2750, in get_prep_value
return self.to_python(value)
~~~~~~~~~~~~~~^^^^^^^
File "/Users/jwalls/django/django/db/models/fields/__init__.py", line
2768, in to_python
raise exceptions.ValidationError(
...<3 lines>...
)
django.core.exceptions.ValidationError: ['“None” is not a valid UUID.']
}}}
--
Ticket URL: <
https://code.djangoproject.com/ticket/36786>
Django <
https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.