From add7147006e1338f8fd7fb9e3ad1b8e6fcde9bbe Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Mon, 21 Apr 2014 08:19:29 -0500 Subject: [PATCH] Check for json in standard lib, if not check simplejson in django.utils --- dynamicresponse/emitters.py | 120 +++++++++++--------- dynamicresponse/middleware/dynamicformat.py | 46 ++++---- 2 files changed, 89 insertions(+), 77 deletions(-) diff --git a/dynamicresponse/emitters.py b/dynamicresponse/emitters.py index 5c41c9e..e9c19b5 100644 --- a/dynamicresponse/emitters.py +++ b/dynamicresponse/emitters.py @@ -4,9 +4,19 @@ """ from __future__ import generators + +import decimal +import re +import inspect +import copy + +try: + import json +except ImportError: + from django.utils import simplejson as json + from django.db.models.query import QuerySet from django.db.models import Model, permalink -from django.utils import simplejson from django.utils.xmlutils import SimplerXMLGenerator from django.utils.encoding import smart_unicode from django.core.urlresolvers import reverse, NoReverseMatch @@ -17,8 +27,6 @@ from django.core import serializers from django.core.paginator import Page -import decimal, re, inspect -import copy class Emitter(object): """ @@ -28,7 +36,7 @@ class Emitter(object): usually the only method you want to use in your emitter. See below for examples. """ - + EMITTERS = {} RESERVED_FIELDS = set([ 'read', @@ -41,48 +49,48 @@ class Emitter(object): 'fields', 'exclude' ]) - + def __init__(self, payload, typemapper, handler, fields=(), anonymous=True): - + self.typemapper = typemapper self.data = payload self.handler = handler self.fields = fields self.anonymous = anonymous - + if isinstance(self.data, Exception): raise - + def method_fields(self, handler, fields): - + if not handler: return {} - + ret = dict() for field in fields - Emitter.RESERVED_FIELDS: t = getattr(handler, str(field), None) - + if t and callable(t): ret[field] = t - + return ret - + def construct(self): """ Recursively serialize a lot of types, and in cases where it doesn't recognize the type, it will fall back to Django's `smart_unicode`. - + Returns `dict`. """ - + def _any(thing, fields=()): """ Dispatch, all types are routed through here. """ - + ret = None - + if isinstance(thing, QuerySet): ret = _qs(thing, fields=fields) elif isinstance(thing, Page): @@ -106,59 +114,59 @@ def _any(thing, fields=()): ret = _any(thing.all()) else: ret = smart_unicode(thing, strings_only=True) - + return ret - + def _fk(data, field): """ Foreign keys. """ - + return _any(getattr(data, field.name)) - + def _related(data, fields=()): """ Foreign keys. """ - + return [ _model(m, fields) for m in data.iterator() ] - + def _m2m(data, field, fields=()): """ Many to many (re-route to `_model`.) """ - + return [ _model(m, fields) for m in getattr(data, field.name).iterator() ] - + def _model(data, fields=()): """ Models. Will respect the `fields` and/or `exclude` on the handler (see `typemapper`.) """ - + ret = { } handler = None - + # Does the model implement get_serialization_fields() or serialize_fields()? # We should only serialize these fields. if hasattr(data, 'get_serialization_fields'): fields = set(data.get_serialization_fields()) if hasattr(data, 'serialize_fields'): fields = set(data.serialize_fields()) - + # Is the model a Django user instance? # Ensure that only core (non-sensitive fields) are serialized if isinstance(data, User): fields = getattr(settings, 'DYNAMICRESPONSE_DJANGO_USER_FIELDS', ('id', 'email', 'first_name', 'last_name')) - + # Should we explicitly serialize specific fields? if fields: - + v = lambda f: getattr(data, f.attname) - + get_fields = set(fields) met_fields = self.method_fields(handler, get_fields) - + # Serialize normal fields for f in data._meta.local_fields: if f.serialize and not any([ p in met_fields for p in [ f.attname, f.name ]]): @@ -170,20 +178,20 @@ def _model(data, fields=()): if f.attname[:-3] in get_fields: ret[f.name] = _fk(data, f) get_fields.remove(f.name) - + # Serialize many-to-many fields for mf in data._meta.many_to_many: if mf.serialize and mf.attname not in met_fields: if mf.attname in get_fields: ret[mf.name] = _m2m(data, mf) get_fields.remove(mf.name) - + # Try to get the remainder of fields for maybe_field in get_fields: if isinstance(maybe_field, (list, tuple)): model, fields = maybe_field inst = getattr(data, model, None) - + if inst: if hasattr(inst, 'all'): ret[model] = _related(inst, fields) @@ -192,14 +200,14 @@ def _model(data, fields=()): ret[model] = _any(inst(), fields) else: ret[model] = _model(inst, fields) - + elif maybe_field in met_fields: # Overriding normal field which has a "resource method" # so you can alter the contents of certain fields without # using different names. ret[maybe_field] = _any(met_fields[maybe_field](data)) - - else: + + else: maybe = getattr(data, maybe_field, None) if maybe: if callable(maybe): @@ -209,51 +217,51 @@ def _model(data, fields=()): ret[maybe_field] = _any(maybe) else: ret[maybe_field] = _any(maybe) - + else: - + for f in data._meta.fields: if not f.attname.startswith('_'): ret[f.attname] = _any(getattr(data, f.attname)) - + fields = dir(data.__class__) + ret.keys() add_ons = [k for k in dir(data) if k not in fields] - + for k in add_ons: if not k.__str__().startswith('_'): ret[k] = _any(getattr(data, k)) - + return ret - + def _qs(data, fields=()): """ Querysets. """ - + return [ _any(v, fields) for v in data ] - + def _list(data, fields=()): """ Lists. """ - + return [ _any(v, fields) for v in data ] - + def _dict(data, fields=()): """ Dictionaries. """ - + return dict([ (k, _any(v, fields)) for k, v in data.iteritems() ]) - + # Kickstart the seralizin'. return _any(self.data, self.fields) - + def in_typemapper(self, model, anonymous): for klass, (km, is_anon) in self.typemapper.iteritems(): if model is km and is_anon is anonymous: return klass - + def render(self): """ This super emitter does not implement `render`, @@ -265,12 +273,12 @@ class JSONEmitter(Emitter): """ JSON emitter, understands timestamps. """ - + def render(self): - + indent = 0 if settings.DEBUG: indent = 4 - - seria = simplejson.dumps(self.construct(), cls=DateTimeAwareJSONEncoder, ensure_ascii=False, indent=indent) + + seria = json.dumps(self.construct(), cls=DateTimeAwareJSONEncoder, ensure_ascii=False, indent=indent) return seria diff --git a/dynamicresponse/middleware/dynamicformat.py b/dynamicresponse/middleware/dynamicformat.py index 263efcb..22af6e1 100644 --- a/dynamicresponse/middleware/dynamicformat.py +++ b/dynamicresponse/middleware/dynamicformat.py @@ -1,5 +1,9 @@ +try: + import json +except ImportError: + from django.utils import simplejson as json + from django.http import HttpResponse, QueryDict -from django.utils import simplejson from dynamicresponse.response import DynamicResponse @@ -7,58 +11,58 @@ class DynamicFormatMiddleware: """ Provides support for dynamic content negotiation, both in request and reponse. """ - + def _flatten_dict(self, obj, prefix=''): """ Converts a possibly nested dictionary to a flat dictionary. """ - + encoded_dict = QueryDict('').copy() - + if hasattr(obj, 'items'): for key, value in obj.items(): - + item_key = '%(prefix)s%(key)s' % { 'prefix': prefix, 'key': key } - + # Flatten lists for formsets and model choice fields if isinstance(value, list): for i, item in enumerate(value): - + if isinstance(item, dict): - + # Flatten nested object to work with formsets item_prefix = '%(key)s-%(index)d-' % { 'key': key, 'index': i } encoded_dict.update(self._flatten_dict(item, prefix=item_prefix)) - + # ID for use with model multi choice fields id_value = item.get('id', None) if id_value: encoded_dict.update({ key: id_value }) - + else: - + # Value for use with model multi choice fields encoded_dict.update({ key: item }) - + # ID for use with model choice fields elif isinstance(value, dict): encoded_dict[item_key] = value.get('id', value) - + # Keep JavaScript null as Python None elif value is None: encoded_dict[item_key] = None - + # Other values are used directly else: encoded_dict[item_key] = unicode(value) - + return encoded_dict - + def process_request(self, request): """" Parses the request, decoding JSON payloads to be compatible with forms. """ - + # Does the request contain a JSON payload? content_type = request.META.get('CONTENT_TYPE', '') if content_type != '' and 'application/json' in content_type: @@ -70,19 +74,19 @@ def process_request(self, request): if content_length > 0: try: # Replace request.POST with flattened dictionary from JSON - decoded_dict = simplejson.loads(request.raw_post_data) + decoded_dict = json.loads(request.raw_post_data) request.POST = request.POST.copy() request.POST = self._flatten_dict(decoded_dict) except: return HttpResponse('Invalid JSON', status=400) - + def process_response(self, request, response): """ Handles rendering dynamic responses. """ - + # Cause dynamic responses to be rendered if isinstance(response, DynamicResponse): return response.render_response(request, response) - + return response