WiserTogether / django-remote-forms

A platform independent django form serializer
MIT License
221 stars 113 forks source link

django-remote-forms

A package that allows you to serialize django forms, including fields and widgets into Python dictionary for easy conversion into JSON and expose over API

Please go through my djangocon US 2012 talk to understand the problem sphere, motivations, challenges and implementation of Remote Forms

Sample Implementation

If you don't mind digging around a little bit to learn about different the components that might be necessary for an implementation of django-remote-forms, check out django Remote Admin django-remote-admin

Usage

Minimal Example

from django_remote_forms.forms import RemoteForm

form = LoginForm()
remote_form = RemoteForm(form)
remote_form_dict = remote_form.as_dict()

Upon converting the dictionary into JSON, it looks like this:

{
    "is_bound": false,
    "non_field_errors": [],
    "errors": {},
    "title": "LoginForm",
    "fields": {
        "username": {
            "title": "CharField",
            "required": true,
            "label": "Username",
            "initial": null,
            "help_text": "This is your django username",
            "error_messages": {
                "required": "This field is required.",
                "invalid": "Enter a valid value."
            },
            "widget": {
                "title": "TextInput",
                "is_hidden": false,
                "needs_multipart_form": false,
                "is_localized": false,
                "is_required": true,
                "attrs": {
                    "maxlength": "30"
                },
                "input_type": "text"
            },
            "min_length": 6,
            "max_length": 30
        },
        "password": {
            "title": "CharField",
            "required": true,
            "label": "Password",
            "initial": null,
            "help_text": "",
            "error_messages": {
                "required": "This field is required.",
                "invalid": "Enter a valid value."
            },
            "widget": {
                "title": "PasswordInput",
                "is_hidden": false,
                "needs_multipart_form": false,
                "is_localized": false,
                "is_required": true,
                "attrs": {
                    "maxlength": "128"
                },
                "input_type": "password"
            },
            "min_length": 6,
            "max_length": 128
        }
    },
    "label_suffix": ":",
    "prefix": null,
    "csrfmiddlewaretoken": "2M3MDgfzBmkmBrJ9U0MuYUdy8vgeCCgw",
    "data": {
        "username": null,
        "password": null
    }
}

An API endpoint serving remote forms

from django.core.serializers.json import simplejson as json, DjangoJSONEncoder
from django.http import HttpResponse
from django.middleware.csrf import CsrfViewMiddleware
from django.views.decorators.csrf import csrf_exempt

from django_remote_forms.forms import RemoteForm

from my_awesome_project.forms import MyAwesomeForm

@csrf_exempt
def my_ajax_view(request):
    csrf_middleware = CsrfViewMiddleware()

    response_data = {}
    if request.method == 'GET':
        # Get form definition
        form = MyAwesomeForm()
    elif request.raw_post_data:
        request.POST = json.loads(request.raw_post_data)
        # Process request for CSRF
        csrf_middleware.process_view(request, None, None, None)
        form_data = request.POST.get('data', {})
        form = MyAwesomeForm(form_data)
        if form.is_valid():
            form.save()

    remote_form = RemoteForm(form)
    # Errors in response_data['non_field_errors'] and response_data['errors']
    response_data.update(remote_form.as_dict())

    response = HttpResponse(
        json.dumps(response_data, cls=DjangoJSONEncoder),
        mimetype="application/json"
    )

    # Process response for CSRF
    csrf_middleware.process_response(request, response)
    return response

djangocon Proposal

This is a bit lengthy. But if you want to know more about my motivations behind developing django-remote-forms then read on.

In our quest to modularize the architecture of web applications, we create self-containing backend systems that provide web APIs for programmatic interactions. This gives us the flexibility to separate different system components. A system with multiple backend components e.g. user profile engine, content engine, community engine, analytics engine may have a single frontend application that fetches data from all of these components using respective web APIs.

With the increased availability of powerful JavaScript frameworks, such frontend applications are often purely JS based to decrease application footprint, increase deployment flexibility and separate presentation from data. The separation is very rewarding from a software engineering standpoint but imposes several limitations on system design. Using django to construct the API for arbitrary consumers comes with the limitation of not being able to utilize the powerful django form subsystem to drive forms on these consumers. But is there a way to overcome this restriction?

This is not a trivial problem to solve and there are only a few assumptions we can make about the web API consumer. It can be a native mobile or desktop - application or browser. We advocate that web APIs should provide sufficient information about 'forms' so that they can be faithfully reproduced at the consumer end.

Even in a API backend built using django, forms are essential for accepting, filtering, processing and saving data. The django form subsystem provides many useful features to accomplish these tasks. At the same time it facilitates the process of rendering the form elements in a browser environment. The concepts of form fields combined with widgets can go a long way in streamlining the interface to interact with data.

We propose an architecture to serialize information about django forms (to JSON) in a framework independent fashion so that it can be consumed by any frontend application that renders HTML. Such information includes but is not limited to basic form configurations, security tokens (if necessary), rendering metadata and error handling instructions. We lovingly name this architecture django-remote-forms.

At WiserTogether, we are in the process of building a component based architecture that strictly provides data endpoints for frontend applications to consume. We are working towards developing our frontend application for web browsers using backbone.js as MVC and handlebars as the templating engine. django-remote-forms helps us streamline our data input interface with the django forms living at the API backend.