# Django Generic JSON Views

[![Stories in Ready](https://badge.waffle.io/bbengfort/django-generic-json-views.png?label=ready&title=Ready)](https://waffle.io/bbengfort/django-generic-json-views)
 [![Build Status](https://travis-ci.org/bbengfort/django-generic-json-views.svg)](https://travis-ci.org/bbengfort/django-generic-json-views) [![PyPi version](https://pypip.in/v/django-generic-json-views/badge.png)](https://crate.io/packages/django-generic-json-views/)
[![PyPi downloads](https://pypip.in/d/django-generic-json-views/badge.png)](https://crate.io/packages/django-generic-json-views/)

**Class based generic views that render JSON data.**

The `django.views.generic` package has several generic class based views
that render HTML for a model or a set of applications and provide an API
to use those views and modify querysets and context data. However, Django
recommends that to specify the output of JSON data, you use a mixin or
another approach to `render_to_response` the JSON.

Many AJAX applications, however, have the need for quick views like the
ones specified the in the `generic` package to either serialize a model
or to provide other context data in the View logic. These generic views
allow that, and correctly render JSON data with the correct HTTP Headers.

More documentation can be found at Read the Docs: [Django Generic JSON Views Documentation](http://django-generic-json-views.readthedocs.org/en/latest/)

## Installation

The easiest way to install this library is to use `pip` as follows:

```bash
$ pip install django-generic-json-views
```

Alternatively you can download the source code and run the following:

```bash
$ python setup.py install
```

Otherwise copy the views.py file to a utils directory in your Django Project and import it correctly from there.

## Basic Usage

There are several provided classes that work right off the bat:

* `JSONDataView`
* `JSONDetailView`
* `JSONListView`
* `PaginatedJSONListView`
* `JSONFormView`

These classes all provide the following functionality:

1. Render the output of `get_context_data` as a JSON object
2. Serialize Django Models and Querysets with a Lazy Encoder
3. Correctly add the application/json HTTP header
4. Remove duplicate objects and middleware context built into Django

The most simple example is the `JSONDataView` which will allow you to output generic JSON data as needed:

```python
class JSONTime(JSONDataView):

    def get_context_data(self, **kwargs):
        context = super(JSONTime, self).get_context_data(**kwargs)
        context['current_time'] = datetime.now.strftime("%c")
        return context
```

In order to render the detail view of a model as a JSON serialization,
use the `JSONDetailView` -- note that this view can be used for both
serialization and deserialization as noted in the section below.

```python
class CustomerJSON(JSONDetailView):

    model = Customer
```

Many JSON views are centered around lists, hence the `JSONListView` and
the `PaginatedJSONListView`.

```python
class ProductsJSON(JSONListView):

    model = Product
```

The `PaginatedJSONListView` looks up the special parameter `page=n` to
decide which part of the list to return. It also removes the Django paginator
object and returns the following data:

```python
{
    'pages': Number,
    'count': Number,
    'is_paginated': Bool,
    'per_page': Number,
    'current': Number,
    'object_list': List
}
```

An example of using this view is:

```python
class PaginatedProducts(PaginatedJSONListView):

    paginate_by = 10
    count_query = 'count'
    count_only  = False

    def get_querset(self):
        return Product.objects.filter(instores=True)
```

`count_query` specifies the name of the property to pass in as kwargs in
a GET request that will force the view to only return the total number
of expected items, and not fetch them from the db. E.g. if you use
`/?count=true` in your url, the view will respond only with the total number.

Finally the JSONFormView accepts incoming JSON data to populate a form
rather than waiting for HTTP POST data, this is how you would do AJAX login
forms. Note that GET requests are not allowed.

```python
class LoginForm(JSONFormView):

    form = LoginForm
```

In the form view, a parameter `success` is passed back, as either True or
False. This can be customized by overriding `get_context_data`.

## Serialization & Deserialization

The lazy encoder attempts to serialze the model into a dictionary or other
compatible JSON-Python type, and does so in a recursive fashion,
continuing to serialize objects, the default serialization is this:

1. If the object is an iterable- construct a list
2. If the object extends `ModelBase` attempt to call a `serialize` method
3. Utilize `force_unicode` to force the object to unicode data.

Therefore if your `Models` provide a `serialize` method that returns a
dictionary, you can very easily control what fields and what format the
model is serialized to.

Additionally you can specify a `@classmethod` `deserialize` that accepts
as input a dictionary and returns an instance of the `Model`.

Here is an example of the serialization of a UserProfile, as an extension
of the `django.contrib.auth` package.

```python
class UserProfile(models.Model):

    user  = models.OneToOneField( 'django.contrib.auth.User', editable=False, related_name='profile' )
    bio   = models.CharField( max_length=255, null=True, blank=True, default=None )
    birth = models.DateField( )

    def serialize(self):
        return {
            'username': self.user.username,
            'email': self.user.email,
            'name': " ".join((self.user.first_name, self.user.last_name)),
            'birthday': self.birth.strftime("%d %M, %Y"),
        }

    @classmethod
    def deserialize(klass, data):
        username = data.get('username', None)
        if username:
            try:
                return klass.objects.get(username=username)
            except klass.DoesNotExist:
                pass
        return klass(**data)
```

## Pushing to PyPi

A couple of notes on pushing this software to PyPi:

1. Version Bump    
    1. Change the version in `__init__`.py
    2. Change the version in tests
    3. Change the download URL in setup.py 
2. Tag the release `git tag -a v0.x.x`
3. Push to Github
4. Register `python setup.py register -r pypi`
5. Upload `python setup.py sdist upload -r pypi`  
