Tuesday, October 20, 2015

REST-ful revelations

I've started using Django REST Framework, and it is simply magic!

Here is a technique I've used to input lists of primitive types and serializers with many=True

from functools import partial
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework import status
from my_app.serializers import MyNestedModelSerializer
...

class MyNestedModelViewSet(viewsets.ViewSet):
    serializer_class = MyNestedModelSerializer

    def create(self, request):
        serializer = self.serializer_class(data=request.data)
        # get the submodel list serializer since it can't render/parse html
        submodel_list_serializer = serializer.fields['submodels']
        # make a partial function by setting the submodel list serializer
        partial_get_value = partial(custom_get_value, submodel_list_serializer)
        # monkey patch submodel_list_serializer.get_value() with partial function
        submodel_list_serializer.get_value = partial_get_value
        if serializer.is_valid():
            simulate_data = serializer.save()
            # do stuff ...
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

The function `custom_get_value()` uses JSON to parse the input:

def custom_get_value(serializer, dictionary):
    if serializer.field_name not in dictionary:
        if getattr(serializer.root, 'partial', False):
            return empty
    # We override the default field access in order to support
    # lists in HTML forms.
    if html.is_html_input(dictionary):
        listval = dictionary.getlist(serializer.field_name)
        if len(listval) == 1 and isinstance(listval[0], basestring):
            # get only item in value list, strip leading/trailing whitespace
            listval = listval[0].strip()
            # add brackets if missing so that it's a JSON list
            if not (listval.startswith('[') and listval.endswith(']')):
                listval = '[' + listval + ']'
            # try to deserialize JSON string
            try:
                listval = json.loads(listval)
            except ValueError as err:
                # return original string and log error
                pass
            # set the field with the new value list
            dictionary.setlist(serializer.field_name, listval)
        val = dictionary.getlist(serializer.field_name, [])
        if len(val) > 0:
            # Support QueryDict lists in HTML input.
            return val
        return html.parse_html_list(dictionary, prefix=serializer.field_name)
    return dictionary.get(serializer.field_name, empty)
Fork me on GitHub