Opened 56 minutes ago
Last modified 10 minutes ago
#36786 new Bug
XML serializer mishandles nullable elements of a related object's natural key
| Reported by: | Jacob Walls | Owned by: | |
|---|---|---|---|
| Component: | Core (Serialization) | Version: | 5.2 |
| Severity: | Normal | Keywords: | xml |
| Cc: | Triage Stage: | Accepted | |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description (last modified by )
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
from django.db import models class WidgetManager(models.Manager): def get_by_natural_key(self, name, foo): return self.get(name=name, foo=foo) class Widget(models.Model): name = models.CharField(default="default") foo = models.UUIDField(null=True) objects = WidgetManager() def natural_key(self): return (self.name, self.foo) class Gadget(models.Model): widget = models.ForeignKey(Widget, on_delete=models.CASCADE, null=True)
./manage.py makemigrations ./manage.py migrate ./manage.py shell
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 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:
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.']
Change History (2)
comment:1 by , 54 minutes ago
| Description: | modified (diff) |
|---|
comment:2 by , 10 minutes ago
| Triage Stage: | Unreviewed → Accepted |
|---|
Note:
See TracTickets
for help on using tickets.