Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Model deserialization with empty PK

I've serialized my django model:

serializers.serialize(MyModel.objects.filter(color="Red"))

and got this output:

<object model="example.example" pk="133">
    <field name="name" type="CharField">John Smith</field>
    <field name="color" type="CharField">Red</field>
    ... #more fields
</object>

So you can see I have pk="133":

And now I want to deserialize this into django model again and save() into database but with different pk so it should create new record with new id.

I'm trying to parse XML and change pk using:

  • pk="" - parser complains that pk should be integer
  • pk="-1" or "0" - actually creates record with id/pk = "1" or "0"
  • pk="None" or None or "null" - parser complains that pk should be integer
  • remove "pk" attribute - parser complains that attribute is mandatory

In Django Serialization article there is an example how to deserialize from JSON with null "pk".

# You can easily create new objects by deserializing data with an empty PK
# (It's easier to demo this with JSON...)
>>> new_author_json = '[{"pk": null, "model": "serializers.author", "fields": {"name": "Bill"}}]'
>>> for obj in serializers.deserialize("json", new_author_json):
...     obj.save()

(It's actually for 0.96, but I'm assuming it should work for 1.* also)

So in JSON pk can be null but in XML it complains. How to set pk to null for XML?

Thanks

like image 287
tefozi Avatar asked Jul 16 '09 01:07

tefozi


1 Answers

Looks like bug in Django. There are no way to provide empty (or null/None) "pk" for XML serialized object.

From django/core/serializers/xml_serializer.py:

class Deserializer(base.Deserializer):
    ...
    def _handle_object(self, node):
    ...
        pk = node.getAttribute("pk")
        if not pk:
            raise base.DeserializationError("<object> node is missing the 'pk' attribute")

        data = {Model._meta.pk.attname : Model._meta.pk.to_python(pk)}
    ...

If pk attribute is missing - exception is raised. So we have to provide some pk.

From django/models/fields/init.py

class AutoField(Field):
    ...
    def to_python(self, value):
        if value is None:
            return value
        try:
            return int(value)
        except (TypeError, ValueError):
            raise exceptions.ValidationError(
                _("This value must be an integer."))
    ...

If pk is not integer - also exception.

It looks like there are no way to provide empty pk.

Workaround could be:

  1. get maximum id from MyModel
  2. id += 1
  3. set "pk" in my xml with new id
  4. deserialize into model
  5. save()

It's little bit tricky because during steps 1-5 table should be locked.. somehow.. just to avoid id collision.

EDIT:

Workaround is:

  1. Set pk="999999" (some temporary integer value)
  2. During iteration set id and pk to None and later save()

    mymodels_iterator = serializers.deserialize("xml", fixed_pk_serialized_xml_model)

    for mymodel in mymodels_iterator:

    mymodel.object.id = None
    mymodel.object.pk = None
    mymodel.save()
    

And it works!

Thanks to Evgeny's comment about clone() method.

like image 88
tefozi Avatar answered Oct 31 '22 17:10

tefozi