views:

222

answers:

5

I'm developing a web-app where the user can create multiple related elements and then save those elements to a database. The webapp has no idea of what the primary keys will be in the database so it assigns each element a UUID. These UUIDs are saved with each element in the database. When the webapp sends the data to the webserver to be placed in the database it encodes everything using JSON. The JSON is then deserialized by the webserver using serializers.deserialize('json', DATA). However, some of the models have foreign keys, which are sent in the JSON payload as references to the UUIDs of the associated elements rather than the database ids. For example, we may have a simple link object:

class Link(models.Model):
    uuid = models.CharField(max_length=32)
    source = models.ForeignKey(Node, related_name='source')
    target = models.ForeignKey(Node, related_name='target')

If the source had an id value of 2 and the target had an id value of 12, this would be serialized to JSON as this:

{"uuid": "[some long uuid]",
 "source": 2,
 "target": 12}

However, because in this case we don't know the database ids of source and target, probably because they haven't been set yet, the best I can do is pass in the UUID's like this:

{"uuid": "[some long uuid]", 
 "sourceuuid": "[uuid of source]", 
 "targetuuid": "[uuid of target]"}

Unfortunately, this raises an exception of FieldDoesNotExist: Link has no field named u'sourceuuid' when I call serializers.deserialize on the data.

I'd like to find a way that I can pass the UUID's in and then have the database fill in the id's once it's saved the appropriate parts or look them up where necessary. I don't want to save sourceuuid and targetuuid in the database as it's a lot less space to save an integer and the indicies should be faster too.

So, what I'm looking for is a temporary field. One that I can instantiate and reference, but that is never saved back to the database. Any ideas on how I would create such a thing?

UPDATE with more clarification

Thanks for the help so far, I'm aware that they're Python objects and I can assign arbitrary fields to the objects. However, the problem is that serializers.deserialize('json', INDATA) throws errors.

For reference here's a hunk of JSON that the deserializer likes, it has the foreign keys with their IDs:

ser='''[{"pk": 1, "model": "myapp.link", 
         "fields": {"uuid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
                    "source": 2,
                    "target": 12}}]'''
serializer.deserialize('json',ser)

However, what I can provide is this:

ser='''[{"pk": 1, "model": "myapp.link",
         "fields": {"uuid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
                    "sourceuuid": "11111111-2222-3333-4444-555555555555",
                    "targetuuid": "66666666-7777-8888-9999-000000000000"}}]'''
serializer.deserialize('json',ser)
FieldDoesNotExist: Link has no field named u'sourceuuid'

The reason I need to dummy field to be an actual field is because the deserialize requires actual fields.

A: 

I don't know of such a temporary field. It would be nice :)

But my suggestion is this: In the view that receives the JSON and makes it into objects, you check the attributes. You create an object, with only the "real" attributes (not foreign keys or M2M). You can assign arbitrary attributes to them (instance.arbitrary_attribute = something) but you won't get the nice lookup mechanism (Model.objects.filter(...)). After you created all the objects, you loop over the objects again, and for each FK you look up the object that has the link's UUID, and link to it (using the instance.id).

Ofri Raviv
I'm trying to avoid going all muckity muck with the JSON. That's another spot where I can introduce errors and problems with my code. It will work, but it's a messy last resort.
Pridkett
A: 

You can try creating plain python attributes, they are not saved to db. Like:

class Link(models.Model):
    sourceuuid = None
kibitzer
See my update for clarification. The `deserializer` method refuses to handle any data that isn't a `Field`. So this method throws an exception.
Pridkett
A: 

I'm not sure exactly what you're trying to do. But remember that Django models are just Python objects - and Python objects are completely dynamic. So it is perfectly valid to do obj.uuid = whatever even if there is no uuid field defined on the model.

Daniel Roseman
I've updated the question with some more details. I need it to be an actual `Field` because otherwise `deserialize` will throw an exception.
Pridkett
+1  A: 

You can define a foreign key to non-materialized django model which stores the info you require.

To do so, in the new model to which foreign key (or a OnetoOne Key) is defined, just place

class _meta:
    managed = False
Lakshman Prasad
This comes closest, but not in the way you'd expect. A ForeignKey to a non-materialized Django model still adds a column in the referencing table. However, if I create an entire non-materialized subclass with `sourceuuid` and `targetuuid` as new fields, I can get somewhere. I deserialize in a new `LinkJSON` class, then map the UUIDs, then copy the fields over to a `Link` class. It's not pretty, but it kinda works. It would be much better if Python had an easy way to type cast to the base class.
Pridkett
I'm accepting this answer because it came closest to what I wanted to do. I had to do some funky subclassing and then create a subclass of the JSONSerializer to handle everything, but it works now. Unfortunately, it's become a pretty specific solution, so I can't post the entirety of it to StackOverflow.
Pridkett
A: 

Why not just use UIDS as primary keys ?

set primary_key to True

class Node(models.Model):
    uuid = models.CharField(max_length=32, primary_key=True)
    [...]


class Link(models.Model):
    uuid = models.CharField(max_length=32, primary_key=True)
Pydev UA
I've thought about that, and if I can't find another way that's what I might do. However, the UUIDs take up a lot more room in the database and are slower to index and search through than ints.
Pridkett
It's very unclear what you are trying to do.. maybe if you bring more details about you task there will be a good solution
Pydev UA