13 февраля 2009 г.

По-настоящему, рабочий Django PickleField

Важнее некуда: В PickleField добавлена возможность полноценного хранения объектов моделей и поддержка питоновских значений по умолчанию.

Важно: предыдущая версия PickleField'а была практически неработающей в большинстве случаев. Теперь же появилось время в связи с ангиной, и я быстро исправил эту оплошность.

Иногда возникает необходимость хранить какие-то данные, "сериализированные" с помощью pickle или cPickle, в базе данных. Для этих случаев весьма кстати будет PickleField. Вообщем, может быть кому-то пригодится.

from django.conf import settings
from django.db import models

if getattr(settings, 'USE_CPICKLE', False):
    import cPickle as pickle
else:
    import pickle

class PickleField(models.TextField):
    __metaclass__ = models.SubfieldBase

    editable = False
    serialize = False

    def get_db_prep_value(self, value):
        return pickle.dumps(value)

    def to_python(self, value):
        if not isinstance(value, basestring):
            return value

        # Tries to convert unicode objects to string, cause loads pickle from
        # unicode excepts ugly ``KeyError: '\x00'``.
        #
        # If not possible, return this value, cause it's not pickled yet.
        if isinstance(value, unicode):
            try:
                str_value = str(value)
            except UnicodeEncodeError:
                return value
        else:
            str_value = value

        try:
            return pickle.loads(str_value)
        except ValueError:
            # If pickle could not loads from string it's means that it's Python
            # string saved to PickleField.
            return value

Также добавил простейший тест для показания работы PickleField'a:

import unittest

from django.db import models

from fields import PickleField

class PickleObject(models.Model):
    name = models.CharField(max_length=16)
    data = PickleField()

class TestPickleField(unittest.TestCase):
    def setUp(self):
        PickleObject.objects.all().delete()

    def test_not_string_data(self):
        items = [
            'Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5'
        ]

        obj = PickleObject.objects.create(name='default', data=items)
        self.assertEqual(PickleObject.objects.count(), 1)

        self.assertEqual(obj.data, items)

        obj = PickleObject.objects.get(name='default')
        self.assertEqual(obj.data, items)

    def test_string_and_unicode_data(self):
        DATA = (
            ('string', 'Simple string'),
            ('unicode', u'Simple unicode string'),
        )

        for name, data in DATA:
            obj = PickleObject.objects.create(name=name, data=data)
            self.assertEqual(obj.data, data)

        self.assertEqual(PickleObject.objects.count(), 2)

        for name, data in DATA:
            obj = PickleObject.objects.get(name=name)
            self.assertEqual(obj.data, data)

Надеюсь, что в скором времени эти изменения будут добавлены в django-fields.

4 комментариев:

Big 40wt Svetlyak комментирует...

Прикольно. Это твое? Не хочешь добавить в http://github.com/svetlyak40wt/django-fields/

Google loves me комментирует...

Спасибо, мне пригодился. А где вы храните описания полей? Просто, на мой взгляд, смешивать их с моделями как-то быдловато.

Unknown комментирует...

fields.py? :)

Unknown комментирует...

https://github.com/shrubberysoft/django-picklefield - "по-настоящему рабочий PickledObjectField, инфа 100%", у вашего примера некоторые проблемы с не-ASCII символами.

http://bugs.python.org/issue2980 - а вот связанный недобаг.