Writing a django json property serializer


Posted 8 years ago

This blog post is about the django framework, well not exactly the framework itself but a little part that i use daily.

But before the code! A little background showing the why i did this...

Most of my time i use PHP as my main programming language to write web software (not that i like that, but anyway... i have to). Right at the beginning we (means me and my coworkers) start using Zend Framework to write our web apps, and we return JSON strings as part of the response. Simply because we used ExtJs together! Pretty slick at that time, but also pretty heavy (ok, not that much).

Then i learn more and more about python e django to the point that they bacame my main programming language (and framework, off course) used to make my hobbiest projects! :D

So, there came the need of write my own JsonSerializer, because django default serializer don't serialize propertys... for me, propertys are a way to enhance the data viewed by a user inside a grid.

Nowdays i dont use anymore ExtJs (perhaps ill use in future projects), but i build my own jquery grid! (ill put this code on github, after i make some cleanup!) But this little jquery grid plugin still eat up JSON as the main format.

enter image description here

And that's why i change the default JsonSerializer and made this PropertyJsonSerializer!

Anyway... here's the fucking code! And, if you dear reader, find any bugs on this... write that up on the comments or use the gist tool...

JsonPropertySerializer.py
https://gist.github.com/rdenadai/6095815

# -*- coding: utf-8 -*-
#!/usr/bin/env python

import warnings
from django.core.serializers.json import Serializer
from django.utils.encoding import smart_text, is_protected_type
from django.utils import six
from django.utils.html import escape


class JsonPropertySerializer(Serializer):
    def serialize(self, queryset, **options):
        """
        Serialize a queryset.
        """
        self.options = options

        self.stream = options.pop("stream", six.StringIO())
        self.selected_fields = options.pop("fields", None)
        self.use_natural_keys = options.pop("use_natural_keys", False)
        if self.use_natural_keys:
            warnings.warn("``use_natural_keys`` is deprecated; use ``use_natural_foreign_keys`` instead.", PendingDeprecationWarning)
        self.use_natural_foreign_keys = options.pop('use_natural_foreign_keys', False) or self.use_natural_keys
        self.use_natural_primary_keys = options.pop('use_natural_primary_keys', False)

        self.start_serialization()
        self.first = True
        for obj in queryset:
            self.start_object(obj)
            # Use the concrete parent class' _meta instead of the object's _meta
            # This is to avoid local_fields problems for proxy models. Refs #17717.
            concrete_model = obj._meta.concrete_model
            for field in concrete_model._meta.local_fields:
                if field.serialize:
                    if field.rel is None:
                        if self.selected_fields is None or field.attname in self.selected_fields:
                            self.handle_field(obj, field)
                    else:
                        if self.selected_fields is None or field.attname[:-3] in self.selected_fields:
                            self.handle_fk_field(obj, field)
            for field in concrete_model._meta.many_to_many:
                if field.serialize:
                    if self.selected_fields is None or field.attname in self.selected_fields:
                        self.handle_m2m_field(obj, field)
            # Add to make possible to serialize via json property from a object!
            for name, field in obj.__class__.__dict__.iteritems():
                if isinstance(field, property):
                    self.handle_property_field(obj, name)
            self.end_object(obj)
            if self.first:
                self.first = False
        self.end_serialization()
        return self.getvalue()

    def get_dump_object(self, obj):
        current = {key: item if isinstance(item, dict) else escape(item) for key, item in self._current.items()}
        return dict({"pk": smart_text(obj._get_pk_val(), strings_only=True), "model": smart_text(obj._meta)}.items() + current.items())

    def handle_field(self, obj, field):
        value = field._get_val_from_obj(obj)
        # Protected types (i.e., primitives like None, numbers, dates,
        # and Decimals) are passed through as is. All other values are
        # converted to string first.
        if is_protected_type(value):
            self._current[field.name] = value
        else:
            # Changed to make it possible to get the value from a choice field, the LABEL value!!
            value = field.value_to_string(obj)
            if len(field.choices) > 0:
                # Get the first value founded!
                value = filter(None, map(lambda x: x[1] if str(x[0]) == value else None, field.choices))
                if value:
                    value = value[0]
            self._current[field.name] = value

    def handle_property_field(self, obj, name):
        self._current[name] = getattr(obj, name)

thanks a lot!