Source code for plata.fields

from __future__ import absolute_import, unicode_literals

import datetime
import logging
import re

from django import forms
from django.core.exceptions import ValidationError
from django.db import models
from django.utils import six
from django.utils.dateparse import parse_date, parse_datetime, parse_time
from django.utils.functional import curry
from django.utils.translation import ugettext_lazy as _


import plata
import simplejson as json


try:
    json.dumps([42], use_decimal=True)
except TypeError:  # pragma: no cover
    raise Exception("simplejson>=2.1 with support for use_decimal required.")


#: Field offering all defined currencies
CurrencyField = curry(
    models.CharField,
    _("currency"),
    max_length=3,
    choices=list(zip(plata.settings.CURRENCIES, plata.settings.CURRENCIES)),
)


def json_encode_default(o):
    # See "Date Time String Format" in the ECMA-262 specification.
    if isinstance(o, datetime.datetime):
        return o.strftime("%Y-%m-%dT%H:%M:%S.%f%z")
    elif isinstance(o, datetime.date):
        return o.strftime("%Y-%m-%d")
    elif isinstance(o, datetime.time):
        return o.strftime("%H:%M:%S.%f%z")
    raise TypeError("Cannot encode %r" % o)


_PATTERNS = [
    (
        re.compile(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}"),
        (
            lambda value: datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f"),
            lambda value: datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S"),
            lambda value: parse_datetime(value),
        ),
    ),
    (re.compile(r"\d{4}-\d{2}-\d{2}"), (lambda value: parse_date(value),)),
    (re.compile(r"\d{2}:\d{2}:\d{2}"), (lambda value: parse_time(value),)),
]


def json_decode_hook(data):
    for key, value in list(data.items()):
        if not isinstance(value, six.string_types):
            continue

        for regex, fns in _PATTERNS:
            if regex.match(value):
                for fn in fns:
                    try:
                        data[key] = fn(value)
                        break
                    except ValueError:
                        pass

                break

    return data


class JSONFormField(forms.fields.CharField):
    def clean(self, value, *args, **kwargs):
        if value:
            try:
                # Run the value through JSON so we can normalize formatting
                # and at least learn about malformed data:
                value = json.dumps(
                    json.loads(value, use_decimal=True, object_hook=json_decode_hook),
                    use_decimal=True,
                    default=json_encode_default,
                )
            except ValueError:
                raise forms.ValidationError("Invalid JSON data!")

        return super(JSONFormField, self).clean(value, *args, **kwargs)


[docs]class JSONField(models.TextField): """ TextField which transparently serializes/unserializes JSON objects See: http://www.djangosnippets.org/snippets/1478/ """ formfield = JSONFormField
[docs] def to_python(self, value): """Convert our string value to JSON after we load it from the DB""" if isinstance(value, dict): return value elif isinstance(value, six.string_types): # Avoid asking the JSON decoder to handle empty values: if not value: return {} try: return json.loads(value, use_decimal=True, object_hook=json_decode_hook) except ValueError: logging.getLogger("plata.fields").exception( "Unable to deserialize stored JSONField data: %s", value ) return {} else: assert value is None return {}
[docs] def get_prep_value(self, value): """Convert our JSON object to a string before we save""" return self._flatten_value(value)
[docs] def value_to_string(self, obj): """ Extract our value from the passed object and return it in string form """ if hasattr(obj, self.attname): value = getattr(obj, self.attname) else: assert isinstance(obj, dict) value = obj.get(self.attname, "") return self._flatten_value(value)
def _flatten_value(self, value): """Return either a string, JSON-encoding dict()s as necessary""" if not value: return "" if isinstance(value, dict): value = json.dumps(value, use_decimal=True, default=json_encode_default) assert isinstance(value, six.string_types) return value def value_from_object(self, obj): return json.dumps( super(JSONField, self).value_from_object(obj), default=json_encode_default, use_decimal=True, )
[docs] def from_db_value(self, value, expression, connection, context): """ Convert the input JSON value into python structures, raises django.core.exceptions.ValidationError if the data can't be converted. """ if self.blank and not value: return {} value = value or "{}" if isinstance(value, six.binary_type): value = six.text_type(value, "utf-8") if isinstance(value, six.string_types): try: # with django 1.6 i have '"{}"' as default value here if value[0] == value[-1] == '"': value = value[1:-1] return json.loads(value, use_decimal=True, object_hook=json_decode_hook) except Exception as err: raise ValidationError(str(err)) else: return value