Source

django-rest-api / rest_api / serializers / json.py

The default branch has multiple heads

Full commit
"""
At this stage, the only supported serializer is JSON.

Implemented a custom serializer, as the django one didn't quite do what I
needed it to do, and using templates wasn't that useful either.

It uses a subclass of DjangoJSONEncoder, however.
"""

from django.core.serializers.json import simplejson, DjangoJSONEncoder
from django.core.exceptions import FieldError, ObjectDoesNotExist
from django.db.models import FieldDoesNotExist
from django.db import models
from datetime import date, time, datetime, timedelta
from decimal import Decimal

class JSONEncoder(DjangoJSONEncoder):
    def default(self, o):
        if isinstance(o, datetime):
            return super(JSONEncoder, self).default(o).replace('T', ' ')
        return super(JSONEncoder, self).default(o)
    
def serialize(queryset, fields=None, exclude=None, **kwargs):
    """
    Serialize an object or list or objects.
    """
    # We cannot just set the default fields to a list, as then it would
    # be stored between calls.
    if not exclude:
        exclude = []
    
    if hasattr(queryset, 'model'):
        model = queryset.model
    elif isinstance(queryset, models.Model):
        model = queryset.__class__
    else:
        model = None
        
    if model:
        from rest_api import sites
        api_model = sites.site.get_registered(model)
        if fields is None:
            # First, see if this model is already registered, and use the
            # fields it is registered with.
            if api_model:
                fields = list(api_model.fields)
            else:
                fields = [x.name for x in model._meta.fields]
        else:
            fields = list(fields)
        if api_model:
            fields += list(api_model.extra_fields)
            fields = [f for f in fields if 
                f not in api_model.exclude
                and f not in exclude
            ]
    
        if not fields:
           fields = []
    
        data = []
        if not hasattr(queryset, '__iter__'):
            queryset = [queryset]
            single = True
        else:
            single = False
        
        for obj in queryset:
            this = {
                # 'type':obj.__class__.__name__
            }
            for field in fields:
                try:
                    if api_model and getattr(api_model, field, None):
                        temp = getattr(api_model, field)(obj)
                    else:
                        temp = getattr(obj, field, None)
                except ObjectDoesNotExist:
                    temp = None
                if api_model and field in api_model.embed_objects:
                    # Need to serialize one object
                    new_kwargs = kwargs.copy()
                    new_kwargs['python'] = True
                    if hasattr(temp, 'all'):
                        temp = serialize(temp.all(), **new_kwargs)
                    else:
                        temp = serialize(temp, **new_kwargs)
                elif hasattr(temp, 'pk'):
                    temp = temp.pk
                elif hasattr(temp, 'all'):
                    temp = list(temp.values_list('pk', flat=True))
                elif not isinstance(temp, (str, unicode, int, float, type(None), bool, datetime, date, time)):
                    # We have an object that json won't be able to serialize,
                    # get the field to do it itself.
                    try:
                        temp = obj._meta.get_field(field).value_to_string(obj)
                    except FieldDoesNotExist:
                        # See if it is callable
                        if callable(temp):
                            temp = temp()
                # Not sure if this test should be here. Do we want to
                # exclude extra fields if they are null?
                if temp is not None or (api_model and field not in api_model.extra_fields):
                    if field[0] == "_":
                        field = field[1:]
                    this[field] = temp
            data.append(this)
        if single:
            data = data[0]
    else:
        # This is a python structure, not a queryset/object.
        data = queryset
    
    if kwargs.get('python'):
        return data
    
    # We want to make null DecimalFields into "" ?
    return JSONEncoder(**kwargs).encode(data), 'application/json'


def deserialize(stream):
    """
    We need to ensure any dictionary keys are strings, not unicode strings.
    This appears to only happen with some versions of simplejson.
    
    We also want to use Decimal instead of floats, just about everywhere.
    """
    return simplejson.loads(stream, 
        parse_float=Decimal,
        object_hook=lambda dictionary: dict([(str(k),v) for k,v in dictionary.items()]))