1. Curtis Maloney
  2. gnocchi-ajax


gnocchi-ajax / gnocchi / ajax / serialiser.py

from django.core.serializers.python import Serializer
from django.utils.encoding import smart_unicode
from django.utils.simplejson.encoder import INFINITY, encode_basestring, JSONEncoder as BaseEncoder

import datetime
from itertools import chain, repeat
from decimal import Decimal

def digattr(obj, attr, default=None):
    '''Perform template-style dotted lookup'''
    steps = attr.split('.')
    for step in steps:
        try:    # dict lookup
            obj = obj[step]
        except (TypeError, AttributeError, KeyError):
            try:    # attribute lookup
                obj = getattr(obj, step)
            except (TypeError, AttributeError):
                try:    # list index lookup
                    obj = obj[int(step)]
                except (IndexError, ValueError, KeyError, TypeError):
                    return default
        if callable(obj):
            obj = obj()
    return obj

class SimpleSerialiser(Serializer):
    '''A simple JSON serialiser, supporting recursive serialising'''
    fields = None       # Allow subclasses to define default field lists
    follow_related = {} # Map of field->serialiser for recursion
    extra_fields = {}   # Set of extra, non-field lookups to include

    def serialize(self, queryset, **options):
        options.setdefault('fields', self.fields)
        return super(SimpleSerialiser, self).serialize(queryset, **options)

    def handle_fk_field(self, obj, field):
        '''If listed, use a sub-serialiser for related models'''
        related = getattr(obj, field.name)
        if related:
            # Do we put the object inline?
            ser = self.follow_related.get(field.name)
            if ser:
                related = ser.serialize([related])
                related = related._get_pk_val()
        self._current[field.name] = related

    def handle_m2m_field(self, obj, field):
        if field.rel.through._meta.auto_created:
            ser = self.follow_related.get(field.name)
            related = getattr(obj, field.name).iterator()
            if ser:
                # Sub-serialiser approach
                self._current[field.name] = ser.serialize(related)
                # Traditional approach
                if self.use_natural_keys and hasattr(field.rel.to, 'natural_key'):
                    m2m_value = lambda value: value.natural_key()
                    m2m_value = lambda value: smart_unicode(value._get_pk_val(), strings_only=True)
                self._current[field.name] = [
                    for related in getattr(obj, field.name).iterator()

    def handle_fk_target(self, obj, field):
        ser = self.follow_related[field.var_name]
        qset = getattr(obj, field.get_accessor_name())
        self._current[field.var_name] = ser.serialize(qset.all())

    def end_object(self, obj):
        # Handle FKeys pointing to us
        for field in obj._meta.get_all_related_objects():
            if not field.var_name in self.follow_related:
            if self.selected_fields is None or field.var_name in self.selected_fields:
                self.handle_fk_target(obj, field)
        # Add PK
        self._current['id'] = smart_unicode(obj._get_pk_val(), strings_only=True)
        # Add any non-model data
        for field, source in self.extra_fields.iteritems():
            self._current[field] = digattr(obj, source)
        self._current = None

class SingleSerialiser(object):
    '''Mixin for a serialiser that returns only the first item'''
    def serialize(self, obj, **options):
        # Only pass one item to serialise
        return super(SingleSerialiser, self).serialize([obj], **options)

    def end_serialization(self):
        self.objects = self.objects[0]

# From simplejson
    '\\': '\\\\',
    '"': '\\"',
    '\b': '\\b',
    '\f': '\\f',
    '\n': '\\n',
    '\r': '\\r',
    '\t': '\\t',
    u'\u2028': '\\u2028',
    u'\u2029': '\\u2029',
for i in range(0x20):
    ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % i)
# end simplejson

class JSONEncoder(BaseEncoder):
    '''JSON Encoder class with added type casting

    Formats datetime and date objects to iso8601 format.
    Turns Django models into their unicode form.
    def encode(self, obj):

        def _encode(obj):
            FIRST_SEP = ['']
            NEXT_SEP = ','
            if isinstance(obj, basestring):
                yield '"' + ''.join([ESCAPE_DCT.get(x,x) for x in obj]) + '"'
            elif obj is None:
                yield 'null'
            elif obj is True:
                yield 'true'
            elif obj is False:
                yield 'false'
            elif isinstance(obj, (int, long)):
                yield unicode(obj)
            elif isinstance(obj, float):
                if obj != obj:
                    yield 'NaN'
                elif obj == INFINITY:
                    yield 'Infinity'
                elif obj == -INFINITY:
                    yield '-Infinity'
                    yield unicode(obj)
            elif isinstance(obj, Decimal):
                yield unicode(Decimal)
            elif isinstance(obj, datetime.datetime):
                yield '"'+obj.replace(microsecond=0).isoformat(' ')+'"'
            elif isinstance(obj, datetime.date):
                yield '"'+obj.isoformat()+'"'
            elif isinstance(obj, dict):
                yield '{'
                sep = chain(FIRST_SEP, repeat(NEXT_SEP))
                for key, val in obj.iteritems():
                    for chunk in chain(sep.next(), _encode(key), [': '], _encode(val)):
                        yield chunk
                yield '}'
            elif isinstance(obj, (list, tuple)) or hasattr(obj, '__iter__'):
                yield '['
                sep = chain(FIRST_SEP, repeat(NEXT_SEP))
                for item in obj:
                    for chunk in chain(sep.next(), _encode(item)):
                        yield chunk
                yield ']'
                obj = self.default(obj)
                for chunk in _encode(obj):
                    yield chunk

        for chunk in _encode(obj):
            yield chunk