Sunday, 26 April 2015

Django REST Framework - Going in depth

In the previous post, Django REST Framework - CRUD, we have used some ready made features of Django REST Framework (DRF) to build REST services. These are implemented internally by DRF and do not provide much customization control over the code behavior.

This post deals with writing each method in a class based view to handle user interaction for different HTTP methods. The view classes will inherit APIView. 

APIView is a view class provided by DRF which inherits Django's View class. The advantage is that it wraps Django's HttpRequest  into DRF's request object and then passes it to the handler methods of the view class. Another benefit is that it appropriately handles the response format by setting the correct render. 

The following views.py snap illustrates code for Book model, similarly code for Author model can be constructed.

from django.http import Http404
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from library.models import Book
from library.serializers import BookSerializer


class BookList(APIView):
    """
    List all books, or create a new book.
    """

    def get(self, request, format=None):
        books = Book.objects.all()
        serializer = BookSerializer(books, many=True)
        return Response(serializer.data)

    def post(self, request, format=None):
        serializer = BookSerializer(data=request.DATA)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class BookDetails(APIView):
    """
    Retrieve, update or delete a book
    """

    def get_object(self, pk):
        try:
            return Book.objects.get(pk=pk)
        except Book.DoesNotExist:
            raise Http404

    def get(self, request, pk, format=None):
        book = self.get_object(pk)
        book = BookSerializer(book)
        return Response(book.data)

    def put(self, request, pk, format=None):
        book = self.get_object(pk)
        serializer = BookSerializer(book, data=request.DATA)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_202_ACCEPTED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk, format=None):
        book = self.get_object(pk)
        book.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

Django REST Framework - CRUD

This article is in continuation with Starting with Django REST Framework which just describes how to list that data using generics.ListAPIView. Now we will learn the simplest and easiest way to augment the same application to have a complete CRUD (Create-Retrieve-Update-Delete) functionality implemented in simplest way possible. This posts just covers the changes made to the previously developed application (click here).

Updating view.py

In view.py, we will create a mixin class for each object (book, author) and then used the same to create specific views of respective objects.

E.g. Here we created BookMixin, and then it is used in multiple inheritance fashion with BookList and BookDetails class.

BookList class also inherits generics.ListCreateAPIView which abstracts the logic for listing all the books or creating new book.

Similarly BookDetails class inherits generics.RetrieveUpdateDestroyAPIView which abstracts methods for retrieving a particular book, updating and deleting it.

from django.shortcuts import render
from library.models import Book, Author
from rest_framework import generics
from library.serializers import BookSerializer, AuthorSerializer


class BookMixin:
    serializer_class = BookSerializer
    queryset = Book.objects.all()


class BookList(BookMixin, generics.ListCreateAPIView):
    """
    Returns list of all books or create a new book
    """
    pass


class BookDetails(BookMixin, generics.RetrieveUpdateDestroyAPIView):
    """
    Returns list of all books or create a new book
    """
    pass


class AuthorMixin:
    serializer_class = AuthorSerializer
    queryset = Author.objects.all()


class AuthorList(AuthorMixin, generics.ListCreateAPIView):
    """
    Returns list of all authors or create new authors
    """
    pass


class AuthorDetails(AuthorMixin, generics.RetrieveUpdateDestroyAPIView):
    """
    Returns a specific author, updates it or deletes it.
    """
    pass

Updating urls.py

Following shows building appropriate URLs for the views. The view class inheriting RetrieveUpdateDestroyAPIView  must be passed a parameter pk (primary key).

from django.conf.urls import url
from library.views import BookList, AuthorList, BookDetails, AuthorDetails

urlpatterns = (
    url(r'^books/$', BookList.as_view(), name="book-list"),
    url(r'^books/(?P<pk>[0-9]+)$', BookDetails.as_view(), name="book-details"),
    url(r'^authors/$', AuthorList.as_view(), name="author-list"),
    url(r'^authors/(?P<pk>[0-9]+)$', AuthorDetails.as_view(), name="author-details"),
)

Following are example screen caps.

Get All Authors using GET method

Get Specific Author using GET method and author primary key

Create Author Entry - 1

Create Author Entry - 2 (POST method)
Updating Author Details using PUT method
Deleting Author Entry using DELETE method

Starting with Django REST Framework

REST (REpresentational State Transfer) is a beautiful way for building web APIs and Django REST framework elegantly supports building REST service. To begin with Django you can refer to another post Django - Initial Setup.

Step 0 is to install Django REST Framework (DRF).
>> pip install djangorestframework

Make an entry into INSTALLED_APPS in settings.py of 'rest_framework' and we are good to start.

Create a new app library >> python manage.py startapp library and make its entry into INSTALLED_APPS as 'library'.

In models.py

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)
    country = models.CharField(max_length=50)

class Book(models.Model):
    title = models.CharField(max_length=100)
    publisher = models.CharField(max_length=50)
    price = models.FloatField()
    author = models.ForeignKey("Author")

So here we are creating a small relation of the following type.

Execute following commands to get these models created as tables in SQLite database.
>> python manage.py makemigrations
>> python manage.py syncdb

I have created one entry into each table (library_Book and library_Author) using SQLite Browser for demonstration.

These records along with certain calculated fields need be exposed as JSON to the clients. For which create serializers.py inside library app and add the following code.

from library.models import Book, Author
from rest_framework import serializers

class AuthorSerializer(serializers.ModelSerializer):
    """
    Serializing all authors
    """
    class Meta:
        model = Author
        fields = ('name', 'country')


class BookSerializer(serializers.ModelSerializer):
    """
    Serializing all books
    """
    author = AuthorSerializer(many=False)
    search_url = serializers.SerializerMethodField()
    pack_of_10_cost = serializers.SerializerMethodField()


    class Meta:
        model = Book
        fields = ('id', 'title', 'publisher', 'price', 'pack_of_10_cost', 'search_url', 'author')

    def get_search_url(self, obj):
        return "http://www.isbnsearch.org/isbn/{}".format(obj.id)

    def get_pack_of_10_cost(self, obj):
        return (obj.price*10*0.5)

We have created AuthorSerializer and BookSerializer classes. Inside that defined Meta class providing it model name and fields to send as JSON data.

In the BookSerializer class, we want to add 3 extra fields.
  • author - it should show data of the book's Author which is achieved by providing AuthorSerializer class.
  • search_url, pack_of_10_cost - These are calculated fields, DRF automatically checks for methods prefixed with get_ in the same class to get the values of these fields.
Write following code in views.py file.

from django.shortcuts import render
from library.models import Book, Author
from rest_framework import generics
from library.serializers import BookSerializer, AuthorSerializer


class BookList(generics.ListAPIView):
    """
    Returns list of all books
    """
    serializer_class = BookSerializer
    queryset = Book.objects.all()


class AuthorList(generics.ListAPIView):
    """
    Returns list of all authors
    """
    serializer_class = AuthorSerializer
    queryset = Author.objects.all()

We are using class-based views, which inherit generics.ListAPIView. In this class we need to specify
model's serializer_class and the queryset.

Create urls.py class in the library app, and add the following url mappings.

from django.conf.urls import url
from library.views import BookList, AuthorList

urlpatterns = (
    url(r'^books/$', BookList.as_view(), name="book-list"),
    url(r'^authors/$', AuthorList.as_view(), name="author-list")
)

Make the following url mapping entry in urls.py of the project.

url(r'^library/', include('library.urls'))

We are all set. Now start the server and hit the URLs to see the results.


We have seen how we can list the records and send them as JSON over the wire. In the next post we will learn how to perform CRUD (create-retrieve-update-delete) operations using DRF.

Sunday, 12 April 2015

Django - Working with URLs

In this post I'll discuss 
  • how to handle URL requests?
  • how to access parameters from query string?
  • how to design RESTful URLs?
Handling URL requests

In Django, a project contains different applications. The project contains a file urls.py and each application also contains a file urls.py.

The project runs on the server on a port (e.g. 8080). When this port is hit, then the server looks into urls.py file to find further matching URL and then it invokes respective view.

The projects urls.py file contains a variable urlpatterns. This variable references a call to patterns functions which takes calls to url functions as arguments. Each call to url will specify a patterns to match and then redirects to corresponding applications's urls.py file where further pattern matching is performed.

E.g. consider a URL http://localhost:8080/com/departments

Following is the project's urls.py file.
from django.conf.urls import patterns, include, url
from django.contrib import admin

admin.autodiscover()

urlpatterns = patterns('',    
    url(r'^admin/', include(admin.site.urls)),
    url(r'^polls/', include('polls.urls')),
    url(r'^com/', include('company.urls'))
)

As the server running on localhost port 8080 will receive the URL request, it will see com/  in http://localhost:8080/com/departments and will go to urls.py file in company application.
from django.conf.urls import url
from company import views

urlpatterns = [
    url(r'^departments$', views.department_list, name="department")
]

Here it will match the next part http://localhost:8080/com/departments and will invoke function in the view department_list passing it request object. The views.py file in company application is as follows.
from django.http import HttpResponse

def department_list(request):
    return HttpResponse("Received request to show departments.")

The department_list will just return an HttpResponse. The output is as follows.


Accessing params from QueryString

E.g. consider a URL http://localhost:8080/com/departments?name=HR

Now we need to modify view department_list to read the value of query parameter name. Therefore,
from django.http import HttpResponse


def department_list(request):
    if 'name' in request.GET and request.GET['name']:
        dept = request.GET['name']
        return HttpResponse("Received your request for {}".format(dept))

    return HttpResponse("Received request to show departments.")

Here we check that the dictionary request.GET contains key name and the value isn't None. If the condition evaluates to true the the parameter from query string is read and response is sent to the user. A sample screen cap is as follows.


Designing RESTful URLs

Note: Here we just discuss how to create RESTful looking URLs and not Django Rest Framework, which will be covered in a different post.

A RESTful URL to get department with id=6 will be http://localhost:8080/com/departments/6. For this we need to update urls.py file which will look as follows.
from django.conf.urls import url
from company import views

urlpatterns = [
    url(r'^departments$', views.department_list, name="department"),
    url(r'^departments/(?P<dept_id>[0-9]+)$', views.department_only, name="department_only")
]

The new entry tells that after departments/ match for numbers, make it as a parameter with name dept_id and pass it to view department_only. The first parameter will always be request, all other parameters will be added next to it. Hence the new function in views.py will be as follows.
def department_only(request, dept_id):
    return HttpResponse("Received your request for {}".format(dept_id))

And the output,

As we learned how to build RESTful URLs, we will continue our learning with Django REST Framework and building RESTful applications.

Saturday, 4 April 2015

The first Django project - Activating admin site

This is the continuation of post The first Django project - Initial Setup and woven around the main post is Django docs.

In urls.py, you can find the following mapping for admin.
url(r'^admin/', include(admin.site.urls)),

Therefore, hit URL http://localhost:8080/admin and you will see the following page.

You can login to this admin portal using the credentials set in step 4 of Initial setup post. Once you do login you will see only Groups and Users tables. To add our models to site administration, go to admin.py and add the following lines.
admin.site.register(Poll)
admin.site.register(Choice)

And then do a refresh to see the following page.

I'll leave it to you to explore the free admin functionality. When you click on add in front of Polls,

We can customize admin.py to show four options along with question.
class ChoiceInline(admin.TabularInline):
    model = Choice
    extra = 4


class PollAdmin(admin.ModelAdmin):
    fieldsets = [
        ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
        (None, {'fields': ['question']})
    ]
    inlines = [ChoiceInline]


admin.site.register(Poll, PollAdmin)

Now, adding question screen will look as follows.

The list of questions doesn't look nice - (shown below). We need to customize this screen. Therefore, admin.py will be as follows.
from django.contrib import admin
from polls.models import Poll, Choice


class ChoiceInline(admin.TabularInline):
    model = Choice
    extra = 4


class PollAdmin(admin.ModelAdmin):
    list_display = ('question', 'pub_date')

    fieldsets = [
        ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
        (None, {'fields': ['question']})
    ]

    inlines = [ChoiceInline]

    search_fields = ['question']
    list_filter = ['pub_date']
    admin.site.register(Poll, PollAdmin)

And the screen-cap is -

The first Django project - Initial Setup

In the last article I have completely explained the steps required to do set up of Django on windows. Click here if you haven't done it so far.

This whole exercise is the crux of the post available in django docs.  My approach just augments the theme provided on that link.

We are going to create a polling website in step by step fashion.

1. Create project >> django-admin.py startproject pollsite

2. Start server at port 8080 >> python manage.py runserver 8080

3. (Not required) I have changed the default database name of SQLite, to polling_db


In this file, all the tables required by different applications (INSTALLED_APPS in settings.py) will be created.

4. Each of the INSTALLED_APPS in settings.py has some database table associated. To create that in your project, >> python manage.py syncdb

For auth application, it will ask for username, email and password; provide that.


If you browse polling_db.sqlite3, you will find the following tables and indexes created.


 You will find auth_user table with entry for the user created.


5. Create application polls at same level as manage.py by executing following command.
>> python manage.py startapp polls

This will create the basic directory structure.

6. For informing the project that polls app should be installed, in settings.py, make entry 'polls' in INSTALLED_APPS

7. Now we need to create models for depicting following relationship.

For this write the following code in models.py
class Poll(models.Model):
    question = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

class Choice(models.Model):
    poll = models.ForeignKey(Poll)
    choice = models.CharField(max_length=200)
    votes = models.IntegerField()

To verify that the models created are not having any errors,

>> python manage.py validate

8. The tables for these models need be created in SQLite database. For this,

>> python manage.py makemigrations
>> python manage.py syncdb


You can see the table created, with application name prefixed to the model name.

9. Now to test the code we have done so far, start python shell but via manage.py which will set the required sys.path variables.
>> python manage.py shell

In the application poll, there is a module model which contains Poll class. Therefore,
>>> from polls.models import Poll

Import datetime module and then create the first poll question as follows.
>>> p = Poll(question="Who is the PM of India?", pub_date=datetime.datetime.now())

Now save the object,
>>> p.save()

And check in the database table, you will find record inserted.

We will continue discussion to next post where we will talk about setting up administration site.