#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.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/django-updates/0107019affc877ea-972130a0-545b-4631-9a14-059ff824dfff-000000%40eu-central-1.amazonses.com.

Reply via email to