Сериализация моделей в django с правильными десятичными типами данных в json - PullRequest
2 голосов
/ 23 ноября 2011

Вот пример модели:

class FooModel(models.Model):
    foo = models.DecimalField(max_digits=6, decimal_places=3, null=True)

Сериализация:

from django.core import serializers
obj = get_object_or_404(FooModel, pk=1)
data = serializers.serialize("json", [obj])

Это вернет что-то вроде:

[
    {
        "pk": 1,
        "model": "app.foomodel",
        "fields": {
            "foo": "50"
        }
    }
]

Вопрос

Как сделать поле foo сериализованным как float, а не как string? Я не хочу использовать тип модели float, поскольку float иногда неправильно хранит десятичные числа.

Заранее спасибо.

1 Ответ

4 голосов
/ 23 ноября 2011

Если значение действительно 50 против 50.0?Объект Decimal поддерживает то, что было изначально введено, например, Decimal('50') yields 50.

>>> from decimal import Decimal
>>> d = Decimal('50')
>>> print d
50

К сожалению, аргументы, предоставленные конструктору DecimalField, предназначены только для ограничений на хранение значения, а не их отображение.

Теперь, поскольку Django просто использует стандартную библиотеку json / simplejson, вы можете указать собственный кодировщик при сериализации, например, предложенный в этот вопрос :

import decimal
import json
class DecimalEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, decimal.Decimal):
            return '%.2f' % obj # Display Decimal obj as float
        return json.JSONEncoder.default(self, obj)

Но это не заканчивается.Как подробно описано в этом сообщении в блоге , Django явно передает cls=DjangoJSONEncoder в simplejson.dump(...), поэтому мы должны обойти это, также создав пользовательский объект сериализатора, ссылающийся на DecimalEncoder, который мы создали:

from django.core.serializers.json import Serializer as JSONSerializer
class DecimalSerializer(JSONSerializer):
    def end_serialization(self):
        self.options.pop('stream', None)
        self.options.pop('fields', None)
        json.dump(self.objects, self.stream, cls=DecimalEncoder, **self.options)

Далее вы создаете экземпляр DecimalSerializer как свой собственный объект сериализатора, и происходит волшебство:

my_serializer = DecimalSerializer()
print my_serializer.serialize([obj], indent=4)

, что приводит к:

[
    {
        "pk": 1, 
        "model": "app.foomodel", 
        "fields": {
            "foo": "50.00"
        }
    }
]

Это похоже на большую работу,Возможно, будет проще использовать проверку модели Django , чтобы убедиться, что поле FooModel.foo всегда является числом с плавающей запятой перед сохранением.Вот попытка грубой силы:

from django.core.exceptions import ValidationError

class FooModel(models.Model):
    foo = models.DecimalField(max_digits=6, decimal_places=3, null=True)

    def clean(self):
        if '.' not in str(self.foo):
            raise ValidationError('Input must be float!')

    def save(self, *args, **kwargs):
        self.full_clean()
        super(FooModel, self).save(*args, **kwargs)

А затем:

>>> f = FooModel(foo='1')
>>> f.save()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/jathan/sandbox/foo/app/models.py", line 15, in save
    self.full_clean()
  File "/usr/local/lib/python2.6/dist-packages/django/db/models/base.py", line 828, in full_clean
    raise ValidationError(errors)
ValidationError: {'__all__': [u'Input must be float!']}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...