|
@@ -12,30 +12,32 @@ from celery.seralization import pickle
|
|
from django.db import models
|
|
from django.db import models
|
|
from django.utils.encoding import force_unicode
|
|
from django.utils.encoding import force_unicode
|
|
|
|
|
|
|
|
+
|
|
class PickledObject(str):
|
|
class PickledObject(str):
|
|
- """
|
|
|
|
- A subclass of string so it can be told whether a string is a pickled
|
|
|
|
|
|
+ """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
|
|
object or not (if the object is an instance of this class then it must
|
|
[well, should] be a pickled one).
|
|
[well, should] be a pickled one).
|
|
-
|
|
|
|
|
|
+
|
|
Only really useful for passing pre-encoded values to ``default``
|
|
Only really useful for passing pre-encoded values to ``default``
|
|
with ``dbsafe_encode``, not that doing so is necessary. If you
|
|
with ``dbsafe_encode``, not that doing so is necessary. If you
|
|
remove PickledObject and its references, you won't be able to pass
|
|
remove PickledObject and its references, you won't be able to pass
|
|
in pre-encoded values anymore, but you can always just pass in the
|
|
in pre-encoded values anymore, but you can always just pass in the
|
|
python objects themselves.
|
|
python objects themselves.
|
|
-
|
|
|
|
|
|
+
|
|
"""
|
|
"""
|
|
pass
|
|
pass
|
|
|
|
|
|
|
|
+
|
|
def dbsafe_encode(value, compress_object=False):
|
|
def dbsafe_encode(value, compress_object=False):
|
|
- """
|
|
|
|
- We use deepcopy() here to avoid a problem with cPickle, where dumps
|
|
|
|
|
|
+ """We use deepcopy() here to avoid a problem with cPickle, where dumps
|
|
can generate different character streams for same lookup value if
|
|
can generate different character streams for same lookup value if
|
|
- they are referenced differently.
|
|
|
|
-
|
|
|
|
|
|
+ they are referenced differently.
|
|
|
|
+
|
|
The reason this is important is because we do all of our lookups as
|
|
The reason this is important is because we do all of our lookups as
|
|
simple string matches, thus the character streams must be the same
|
|
simple string matches, thus the character streams must be the same
|
|
- for the lookups to work properly. See tests.py for more information.
|
|
|
|
|
|
+ for the lookups to work properly. See tests.py for more
|
|
|
|
+ information.
|
|
|
|
+
|
|
"""
|
|
"""
|
|
if not compress_object:
|
|
if not compress_object:
|
|
value = b64encode(pickle.dumps(deepcopy(value)))
|
|
value = b64encode(pickle.dumps(deepcopy(value)))
|
|
@@ -43,6 +45,7 @@ def dbsafe_encode(value, compress_object=False):
|
|
value = b64encode(compress(pickle.dumps(deepcopy(value))))
|
|
value = b64encode(compress(pickle.dumps(deepcopy(value))))
|
|
return PickledObject(value)
|
|
return PickledObject(value)
|
|
|
|
|
|
|
|
+
|
|
def dbsafe_decode(value, compress_object=False):
|
|
def dbsafe_decode(value, compress_object=False):
|
|
if not compress_object:
|
|
if not compress_object:
|
|
value = pickle.loads(b64decode(value))
|
|
value = pickle.loads(b64decode(value))
|
|
@@ -50,39 +53,38 @@ def dbsafe_decode(value, compress_object=False):
|
|
value = pickle.loads(decompress(b64decode(value)))
|
|
value = pickle.loads(decompress(b64decode(value)))
|
|
return value
|
|
return value
|
|
|
|
|
|
|
|
+
|
|
class PickledObjectField(models.Field):
|
|
class PickledObjectField(models.Field):
|
|
- """
|
|
|
|
- A field that will accept *any* python object and store it in the
|
|
|
|
|
|
+ """A field that will accept *any* python object and store it in the
|
|
database. PickledObjectField will optionally compress it's values if
|
|
database. PickledObjectField will optionally compress it's values if
|
|
declared with the keyword argument ``compress=True``.
|
|
declared with the keyword argument ``compress=True``.
|
|
-
|
|
|
|
|
|
+
|
|
Does not actually encode and compress ``None`` objects (although you
|
|
Does not actually encode and compress ``None`` objects (although you
|
|
can still do lookups using None). This way, it is still possible to
|
|
can still do lookups using None). This way, it is still possible to
|
|
use the ``isnull`` lookup type correctly. Because of this, the field
|
|
use the ``isnull`` lookup type correctly. Because of this, the field
|
|
defaults to ``null=True``, as otherwise it wouldn't be able to store
|
|
defaults to ``null=True``, as otherwise it wouldn't be able to store
|
|
None values since they aren't pickled and encoded.
|
|
None values since they aren't pickled and encoded.
|
|
-
|
|
|
|
|
|
+
|
|
"""
|
|
"""
|
|
__metaclass__ = models.SubfieldBase
|
|
__metaclass__ = models.SubfieldBase
|
|
-
|
|
|
|
|
|
+
|
|
def __init__(self, *args, **kwargs):
|
|
def __init__(self, *args, **kwargs):
|
|
self.compress = kwargs.pop('compress', False)
|
|
self.compress = kwargs.pop('compress', False)
|
|
self.protocol = kwargs.pop('protocol', 2)
|
|
self.protocol = kwargs.pop('protocol', 2)
|
|
kwargs.setdefault('null', True)
|
|
kwargs.setdefault('null', True)
|
|
kwargs.setdefault('editable', False)
|
|
kwargs.setdefault('editable', False)
|
|
super(PickledObjectField, self).__init__(*args, **kwargs)
|
|
super(PickledObjectField, self).__init__(*args, **kwargs)
|
|
-
|
|
|
|
|
|
+
|
|
def get_default(self):
|
|
def get_default(self):
|
|
- """
|
|
|
|
- Returns the default value for this field.
|
|
|
|
-
|
|
|
|
|
|
+ """Returns the default value for this field.
|
|
|
|
+
|
|
The default implementation on models.Field calls force_unicode
|
|
The default implementation on models.Field calls force_unicode
|
|
on the default, which means you can't set arbitrary Python
|
|
on the default, which means you can't set arbitrary Python
|
|
objects as the default. To fix this, we just return the value
|
|
objects as the default. To fix this, we just return the value
|
|
without calling force_unicode on it. Note that if you set a
|
|
without calling force_unicode on it. Note that if you set a
|
|
callable as a default, the field will still call it. It will
|
|
callable as a default, the field will still call it. It will
|
|
*not* try to pickle and encode it.
|
|
*not* try to pickle and encode it.
|
|
-
|
|
|
|
|
|
+
|
|
"""
|
|
"""
|
|
if self.has_default():
|
|
if self.has_default():
|
|
if callable(self.default):
|
|
if callable(self.default):
|
|
@@ -92,15 +94,15 @@ class PickledObjectField(models.Field):
|
|
return super(PickledObjectField, self).get_default()
|
|
return super(PickledObjectField, self).get_default()
|
|
|
|
|
|
def to_python(self, value):
|
|
def to_python(self, value):
|
|
- """
|
|
|
|
- B64decode and unpickle the object, optionally decompressing it.
|
|
|
|
-
|
|
|
|
|
|
+ """B64decode and unpickle the object, optionally decompressing it.
|
|
|
|
+
|
|
If an error is raised in de-pickling and we're sure the value is
|
|
If an error is raised in de-pickling and we're sure the value is
|
|
a definite pickle, the error is allowed to propogate. If we
|
|
a definite pickle, the error is allowed to propogate. If we
|
|
aren't sure if the value is a pickle or not, then we catch the
|
|
aren't sure if the value is a pickle or not, then we catch the
|
|
error and return the original value instead.
|
|
error and return the original value instead.
|
|
-
|
|
|
|
|
|
+
|
|
"""
|
|
"""
|
|
|
|
+
|
|
if value is not None:
|
|
if value is not None:
|
|
try:
|
|
try:
|
|
value = dbsafe_decode(value, self.compress)
|
|
value = dbsafe_decode(value, self.compress)
|
|
@@ -112,23 +114,23 @@ class PickledObjectField(models.Field):
|
|
return value
|
|
return value
|
|
|
|
|
|
def get_db_prep_value(self, value):
|
|
def get_db_prep_value(self, value):
|
|
- """
|
|
|
|
- Pickle and b64encode the object, optionally compressing it.
|
|
|
|
-
|
|
|
|
|
|
+ """Pickle and b64encode the object, optionally compressing it.
|
|
|
|
+
|
|
The pickling protocol is specified explicitly (by default 2),
|
|
The pickling protocol is specified explicitly (by default 2),
|
|
rather than as -1 or HIGHEST_PROTOCOL, because we don't want the
|
|
rather than as -1 or HIGHEST_PROTOCOL, because we don't want the
|
|
protocol to change over time. If it did, ``exact`` and ``in``
|
|
protocol to change over time. If it did, ``exact`` and ``in``
|
|
lookups would likely fail, since pickle would now be generating
|
|
lookups would likely fail, since pickle would now be generating
|
|
- a different string.
|
|
|
|
-
|
|
|
|
|
|
+ a different string.
|
|
|
|
+
|
|
"""
|
|
"""
|
|
|
|
+
|
|
if value is not None and not isinstance(value, PickledObject):
|
|
if value is not None and not isinstance(value, PickledObject):
|
|
- # We call force_unicode here explicitly, so that the encoded string
|
|
|
|
- # isn't rejected by the postgresql_psycopg2 backend. Alternatively,
|
|
|
|
- # we could have just registered PickledObject with the psycopg
|
|
|
|
- # marshaller (telling it to store it like it would a string), but
|
|
|
|
- # since both of these methods result in the same value being stored,
|
|
|
|
- # doing things this way is much easier.
|
|
|
|
|
|
+ # We call force_unicode here explicitly, so that the encoded
|
|
|
|
+ # string isn't rejected by the postgresql_psycopg2 backend.
|
|
|
|
+ # Alternatively, we could have just registered PickledObject with
|
|
|
|
+ # the psycopg marshaller (telling it to store it like it would a
|
|
|
|
+ # string), but since both of these methods result in the same
|
|
|
|
+ # value being stored, doing things this way is much easier.
|
|
value = force_unicode(dbsafe_encode(value, self.compress))
|
|
value = force_unicode(dbsafe_encode(value, self.compress))
|
|
return value
|
|
return value
|
|
|
|
|
|
@@ -136,12 +138,13 @@ class PickledObjectField(models.Field):
|
|
value = self._get_val_from_obj(obj)
|
|
value = self._get_val_from_obj(obj)
|
|
return self.get_db_prep_value(value)
|
|
return self.get_db_prep_value(value)
|
|
|
|
|
|
- def get_internal_type(self):
|
|
|
|
|
|
+ def get_internal_type(self):
|
|
return 'TextField'
|
|
return 'TextField'
|
|
-
|
|
|
|
|
|
+
|
|
def get_db_prep_lookup(self, lookup_type, value):
|
|
def get_db_prep_lookup(self, lookup_type, value):
|
|
if lookup_type not in ['exact', 'in', 'isnull']:
|
|
if lookup_type not in ['exact', 'in', 'isnull']:
|
|
raise TypeError('Lookup type %s is not supported.' % lookup_type)
|
|
raise TypeError('Lookup type %s is not supported.' % lookup_type)
|
|
# The Field model already calls get_db_prep_value before doing the
|
|
# The Field model already calls get_db_prep_value before doing the
|
|
# actual lookup, so all we need to do is limit the lookup types.
|
|
# actual lookup, so all we need to do is limit the lookup types.
|
|
- return super(PickledObjectField, self).get_db_prep_lookup(lookup_type, value)
|
|
|
|
|
|
+ return super(PickledObjectField, self).get_db_prep_lookup(lookup_type,
|
|
|
|
+ value)
|