1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91 | # --------------------------------------- fields.py --------------------------------------- #
from django.db import models
try:
import cPickle as pickle
except ImportError:
import pickle
class PickledObject(str):
"""A subclass of string so it can be told whether a string is
a pickled object or not (if the object is an instance of this class
then it must [well, should] be a pickled one)."""
pass
class PickledObjectField(models.Field):
__metaclass__ = models.SubfieldBase
def to_python(self, value):
if isinstance(value, PickledObject):
# If the value is a definite pickle; and an error is raised in de-pickling
# it should be allowed to propogate.
return pickle.loads(str(value))
else:
try:
return pickle.loads(str(value))
except:
# If an error was raised, just return the plain value
return value
def get_db_prep_save(self, value):
if value is not None and not isinstance(value, PickledObject):
value = PickledObject(pickle.dumps(value))
return value
def get_internal_type(self):
return 'TextField'
def get_db_prep_lookup(self, lookup_type, value):
if lookup_type == 'exact':
value = self.get_db_prep_save(value)
return super(PickledObjectField, self).get_db_prep_lookup(lookup_type, value)
elif lookup_type == 'in':
value = [self.get_db_prep_save(v) for v in value]
return super(PickledObjectField, self).get_db_prep_lookup(lookup_type, value)
else:
raise TypeError('Lookup type %s is not supported.' % lookup_type)
# --------------------------------------- tests.py --------------------------------------- #
# -*- coding: utf-8 -*-
"""Unit testing for this module."""
from django.test import TestCase
from django.db import models
from fields import PickledObjectField
class TestingModel(models.Model):
pickle_field = PickledObjectField()
class TestCustomDataType(str):
pass
class PickledObjectFieldTests(TestCase):
def setUp(self):
self.testing_data = (
{1:1, 2:4, 3:6, 4:8, 5:10},
'Hello World',
(1, 2, 3, 4, 5),
[1, 2, 3, 4, 5],
TestCustomDataType('Hello World'),
)
return super(PickledObjectFieldTests, self).setUp()
def testDataIntegriry(self):
"""Tests that data remains the same when saved to and fetched from the database."""
for value in self.testing_data:
model_test = TestingModel(pickle_field=value)
model_test.save()
model_test = TestingModel.objects.get(id__exact=model_test.id)
self.assertEquals(value, model_test.pickle_field)
model_test.delete()
def testLookups(self):
"""Tests that lookups can be performed on data once stored in the database."""
for value in self.testing_data:
model_test = TestingModel(pickle_field=value)
model_test.save()
self.assertEquals(value, TestingModel.objects.get(pickle_field__exact=value).pickle_field)
model_test.delete()
|
Comments
I'm having problems with this...
It works fine until I save an object and load it back:
... as you can see, once it's gone via the database I get the pickle instead of the object.
I'm using the svn version of django.
#
This works, but not for string / unicode objects.
#
Not working :o(
Traceback (most recent call last): File "", line 1, in File "/home/niksite/lib/site-python/django/db/models/base.py", line 261, in save ','.join(placeholders)), db_values) File "/home/niksite/lib/site-python/django/db/backends/util.py", line 18, in execute return self.cursor.execute(sql, params) psycopg2.ProgrammingError: can't adapt
#
yaxu: I'll take a look at this problem now and see what I can come up with. I probably ought to write some unit tests while I'm at it, too…
nikolay: I don't use PostgreSQL (I use MySQL), but I'll have to install it and give it a go.
#
Right, I've revised this code, and added some unit tests. (Please notice that these are two separate files now!)
yaxu: Your issue should be fixed.
nikolay: Haven't tested yours yet, though it's on my to-do list!
#
Oh, and the unit tests do pass over here!
#
Very nice snippet, I use it a lot. I have one suggestion which is to use the highest protocol available for any given system when dumping objects to pickles. This can be accomplished by using pickle.HIGHEST_PROTOCOL when dumping. For this case, line #33 should look something like
value = PickledObject(pickle.dumps(value,pickle.HIGHEST_PROTOCOL))In my testing this boosted performance 28 percent. Thanks again!
#
PostgreSQL Users
To get this to work with PostgreSQL you need to register PickledObject with it's marshaler:
Admin Site Users
Mark your field editable=False to retain data while editing the object with admin.
#
I am having trouble pickling, and then saving to the DB, django models with Unicode text in them.
I created a model that holds each object's state at a certain datetime, like so:
from fields import PickledObjectField
class UndoRecord(models.Model): user = models.ForeignKey(User) pickle = PickledObjectField() date = models.DateTimeField(auto_now_add=True)
and then in my views, whenever a user changes something, I save an UndoRecord like so:
undo = UndoRecord(user=request.user, pickle=obj) undo.save()
This works fine, except when the object that I am saving has non-ASCII characters in it, which case I get a lovely:
DjangoUnicodeDecodeError: 'utf8' codec can't decode bytes in position 372-373: invalid data. You passed in "crbml_core.modelsnMusiconp1n (tRp2n(dp3nS'foto'np4nccopy_regn_reconstructornp5n cdjango.db.models.fields.filesnImageFieldFilenp6ncbuiltinnobjectnp7nNtRp8n (dp9nS'_committed'np10nI01nsS'_file'np11nNsS'name'np12nVuploads/musicos/235470296_54ae5af3e9_o.jpg np13nsS'closed'np14nI00nsbsS'user_id'np15nNsS'visible'np16nI1nsS'influencias'np17nVFaith No Morenp18nsS'nombre'np19nVxd1xedxfaxf6np20nsS'id'np21nL2L nsb." ()
I have been trying to fix this using the approach show here: http://www.mail-archive.com/django-users@googlegroups.com/msg67883.html, but no such luck.
Any help is much appreciated.
#