Wednesday, May 11, 2016

Python : django 1.8 + Restful api + ExtJs 6

This article follows the basic steps to quickly create an application server with django and a PostgreSQL database. It will explain how to integrate ExtJs into a django project and will show some examples of templating.

Install django dependencies

sudo pip install django-filter djangorestframework django-rest-auth django-registration

Generate views and routes to handle the following HTTP requests :

  • GET /api/deviceproxy (get a list of all device proxies)
  • POST /api/deviceproxy (add a new device proxy)
  • OPTIONS /api/deviceproxy (get meta-information of the DeviceProxy model)
 Edit poc_supervisor/settings.py  and add the following lines to INSTALLED_APPS :

...
'rest_framework',
'rest_framework.authtoken',
'rest_auth',
...


 and add the Rest configuration :

REST_FRAMEWORK = {
    # Use hyperlinked styles by default.
    # Only used if the `serializer_class` attribute is not set on a view.
    'DEFAULT_MODEL_SERIALIZER_CLASS':
    'rest_framework.serializers.HyperlinkedModelSerializer',
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication',
    ),

    # Use Django's standard `django.contrib.auth` permissions,
    # or allow read-only access for unauthenticated users.
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.DjangoModelPermissions',
    ],

    'PAGINATE_BY_PARAM': 'limit',
    'ORDERING_PARAM': 'sort',
    'PAGINATE_BY': 10,
    'MAX_PAGINATE_BY': 100,
    'DEFAULT_FILTER_BACKENDS': [
        'rest_framework.filters.DjangoFilterBackend',
        'rest_framework.filters.OrderingFilter',
    ],
}

LOGIN_REDIRECT_URL = '/api/' 

Update your django app

When we followed the django tutorial, we created a basic view in views.py and a url conf in urls.py. Remove both files and add a rest.py file instead with the following code :

from rest_framework import viewsets, routers, serializers
from supervisor.models import DeviceProxy, SystemData, SystemDataAcquisition

#
# This file holds the REST related code
#

#################################################################################
#
#
#  SERIALIZERS : The serializers specify how the records are serialized in the
#                list or detail-views
#
#
#################################################################################
class DeviceProxySerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = DeviceProxy
        fields = ('name', 'description', 'ip_address', 'subnet_mask', 'port',)

class SystemDataSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = SystemData
        fields = ('identifier', 'description', 'resolution', 'unit',)

class SystemDataAcquisitionSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = SystemData
        fields = ('device_proxy', 'identifier', 'timestamp', 'status', 'value',)

#################################################################################
#
#
#  FILTERS : Use filters to retsrict items returned by querysets (RFU)
#
#
#################################################################################


#################################################################################
#
#
#  VIEWSETS : Controllers for incoming requests. They access the model and return
#             the proper output
#
#
#################################################################################
class DeviceProxyViewSet(viewsets.ModelViewSet):
    queryset = DeviceProxy.objects.all()
    serializer_class = DeviceProxySerializer
    # filter_class =

   

class SystemDataViewSet(viewsets.ModelViewSet):
    queryset = SystemData.objects.all()
    serializer_class = SystemDataSerializer
    # filter_class =

class SystemDataAcquisitionViewSet(viewsets.ModelViewSet):
    queryset = SystemDataAcquisition.objects.all()
    serializer_class = SystemDataAcquisitionSerializer
    # filter_class =

#################################################################################
#
# Register viewsets to REST router
#
#################################################################################
def register(restrouter):
    restrouter.register(r'deviceproxy', DeviceProxyViewSet)
    restrouter.register(r'systemdata', SystemDataViewSet)
    restrouter.register(r'SystemDataAcquisition', SystemDataAcquisitionViewSet) 


Update your project url settings (urls.py)
Replace the content with the following one :

from django.conf.urls import patterns, include, url
from django.contrib import admin
from rest_framework import viewsets, routers
from supervisor import rest as supervisorAPI

#
# This url pattern definition is the REST-specific one
#
# admin ui
admin.autodiscover()

# routers for the rest-api
restrouter = routers.DefaultRouter()

# add api-router for the supervisor application
supervisorAPI.register(restrouter)

# root-level url patterns
urlpatterns = patterns('',
    # frontpage
    #url(r'^', include('frontend.urls')),
    # admin-ui
    url(r'^admin/', include(admin.site.urls)),
    # provide a login-link in the browsable api
    url(r'^api/ui-auth/', include('rest_framework.urls', namespace='rest_framework')),
    # login for rest-api 
    #url(r'^api/auth/', include('rest_auth.urls')),
    # login for rest-api
    #url(r'^api/', include('frontend.apiurls')),
    # base for the browsable rest-api
    url(r'^api/', include(restrouter.urls)),
)

Note 1: Do not mind the commented code for the moment, we will see that later
Note 2: Beware of the urlpatterns definition : the function named patterns is now called. Do not forget it or you will have this error : AttributeError: 'str' object has no attribute 'resolve'
Note 3: Do not forget the trailing slash in regex definitions or you will encounter url resolution errors


Finally, migrate your app :

python manage.py migrate


Verify that REST operation are activated


Run the server and go to http://127.0.0.1/api/

Normally, you should see a Django REST framework page. You should be able to log in as well with your predefined admin account.

Add an ExtJS frontend


 ExtJS is a standalone MVC framework which means that, event for the creation of views (no model), we will need to create a new django app to host ExtJS stuff.

python manage.py startapp frontend


Open to frontend/views.py and add this content in it

from django.http import HttpResponse
from django.shortcuts import render
from django.views.decorators.csrf import ensure_csrf_cookie
import json

# Create your views here.

@ensure_csrf_cookie
def index(request):
    return render(request, 'frontend/index.html')

def permissions(request):
    print request.user.user_permissions.all()
    return HttpResponse(json.dumps(
        {
            'is_superuser': request.user.is_superuser,
            'user_permissions': list(map(lambda x:str(x.codename), request.user.user_permissions.all()))
        }

    ), 'applicative/json')


This piece of code is the very basic definition for your index file.
Now open frontend/urls.py and add this piece of code


from django.conf.urls import patterns, url

from frontend import views

urlpatterns = patterns('',
    url(r'^$', views.index, name='index'),
)

Create frontend/apiurls.py file and add the following code in it

from django.conf.urls import patterns, url
from frontend import views

urlpatterns = patterns('',
    url(r'^auth/permissions/$', views.permissions, name='permissions'),
) 



 Ok, we need now to define this app in the projects. Add the name of the app in INSTALLED_APPS in poc_supervisor/settings.py :



...
'supervisor',
'frontend',
...


And uncomment these lines in poc_supervisor/urls.py


...
url(r'^', include('frontend.urls')),
url(r'^api/auth/', include('rest_auth.urls')),
url(r'^api/', include('frontend.apiurls')),
... 


At this moment, you can run your server and try to open http://127.0.0.1/ which will end with a 404 error. Why ? Because the index.html file has not been defined ! This time, instead of creating a basic 'Hello World' html page, we will use django templates to create a default ExtJS model (v6.0.0) that we will inherit in our frontend index page. Huh ?! Yeah yeah, let's go through the following steps :

ExtJs setup

Download ExtJs


https://www.sencha.com/legal/gpl/

Create ExtJs directories

At the root of your project (where manage.py is located) :

mkdir static

mkdir templates

static will contain project-wide static files, like CSS, JavaScript.
templates will store project-wide HTML templates. We will use it to store a generic template with a pre-defined header with extjs inclusion.

Unzip extjs archive to static directory

unzip ext-6.0.0-gpl.zip -d static/

Configure Django


Edit poc_supervisor/settings.py and add the following lines

Add static directory to django project

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static')
    ]


Add templates directory to django project

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
                os.path.join(BASE_DIR, 'templates')
        ],
    ....


Create a base.html file in poc_supervisor/templates and add the following code :

 <!-- Do NOT put any DOCTYPE here unless you want problems in IEs. -->
<html>
    <head>
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <!-- Importing Extjs library -->
        <script type="text/javascript" src="/static/ext-6.0.0/build/ext-all-debug.js"></script>
        <script type="text/javascript" src="/static/ext-6.0.0/build/classic/theme-triton/theme-triton-debug.js"></script>

        <!-- Importing the stylesheet triton -->
        <link rel="stylesheet" type="text/css" href="/static/ext-6.0.0/build/classic/theme-triton/resources/theme-triton-all-debug.css">

        {% block head %}{% endblock %}
    </head>

    <body>
        {% block content %}{% endblock %}
    </body>
</html> 


Now let's create an template named index.html in frontend/templates/frontend/ with the following code in it :

{% extends "base.html" %}

{% block title %}Hello ExtJS Supervisor{% endblock %}

{% block head %}

    <script type='text/javascript' src='/static/frontend/app.js'></script>

{% endblock %}

{% block content %}

{% endblock %}


As you can see, index.html will inherit base.html and call a javascript file 'app.js' which will handle the form creation for us. We will first create a simple window to make sure everything works as expected.

Create frontend/static/frontend/app.js file with the following code
Ext.application({
    name: 'Supervisor',
    launch : function() {
        var win = Ext.create("Ext.window.Window", {
        title: 'My first window',
        width: 300,
        height: 200,
        maximizable: true,
        html: 'this is my first window'
        });
    win.show();
    }
});



Finally, edit the index function that we've created before in supervisor/views.py : 

from django.shortcuts import render_to_response

def index(request):
      return render_to_response('supervisor/index.html')



Ok, now run your server and you should see a nice looking window displayed ! Otherwise, double check your ext-js includes.


The next steps are to customize your view with ExtJS !


Credits


https://github.com/MaZderMind/django-vs-extjs

0 comments:

Post a Comment