Django и Sencha Ext JS в Visual Studio 2015.

Как выглядит Web приложение типа CRUD, написанное с использованием фреймворков Django и Sencha Ext JS разработанное в Visual Studio 2015? Этот вопрос был побудительной причиной работы. Однако подробнее о проекте.
В Visual Studio уже давно появилась возможность создавать приложения на Python и в частности: Django, Flask, Bottle Web проекты. Отмечу, что создание в Visual Studio всей инфроструктуры Django проекта с установкой Python 3.4, устновкой отладочной ORM (объектно-реляционное отображение) СУБД sqlite3, установкой Web сервера происходит без каких либо усилий.
А как дело обстоит с импортом в проект библиотеки Sencha Ext JS 5.1, с помощью которой написан клиент? Здесь тоже все предельно быстро и просто: выполняется обычный импорт папок с файлами в состав проекта. Далее можно писать код. Однако когда приложение написано и отлажено, его потребуется перенести под другую СУБД и другой Web сервер. Но это уже совсем другая история.
Итак, в статье описывается Web приложение, в котором связь между клиентом и сервером выполнена при помощи протокола REST. Реализованно приложение в рамках Django - Sencha ExtJS. В качестве инструмента разработки выбран Visual Studio 2015. Клиент содержит компоненты: Ext.Window, Ext.grid.Panel, Ext.data.Store и другие. Проект можно свободно загрузить.
1. Клиент приложения. Sencha Ext JS.
Как обычно, первой страницей приложения служит файл index.html. Но на этой странице в разделе body нет ни строчки HTML кода, а только ссылки на библиотеку Sencha и приложение app.js, убедитесь сами:
index.html
<head>
    <title>Django Ext JS</title>
    <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">     
    <link rel="stylesheet" type="text/css" href="static/app/resources/ext-theme-neptune-all.css">     
    <script type="text/javascript" src="static/app/scripts/ext-all.js"></script>      
    <script type="text/javascript" src="static/app/scripts/app.js"></script>
    <script type="text/javascript" src="static/app/iconMgr/iconMgr.js"></script>
</head>
<body></body>
</html>

А вот так выглядит JavaScript код клиента.
app.js
/* 20.07.2015.
Клиент на Ext JS 5.1 для серверного приложения Django под Visual Studio 2015.
Написан по мотивам книги Ext JS in Action, Second Edition авторов:
JESUS GARCIA, GRGUR GRISOGONO, JACOB K. ANDRESEN. Отдельные фрагменты кода взяты из книги. Cпасибо им.
http://www.manning.com/garcia3/. Документация: http://www.sencha.com/.
*/
Ext.onReady(function () {
    // Придумаем простую модель данных
    Ext.define('Registr', {
        extend: 'Ext.data.Model',
        idProperty: 'id',
        fields: [
            { name: 'id', type: 'int' },
            'fam',
            'im',
            'otch',
            'city',
            'street',
            'code'
        ]
    });

    var urlRoot = 'data?model=Registr&method=';
    // Хранилище для данных таблицы Registr
    var registrStore = Ext.create('Ext.data.Store', {
        model: 'Registr',
        pageSize: 10,
        proxy: {
             type: 'jsonp',
            noCache: false,          
            api: {
                create:     urlRoot + 'Create',
                read:       urlRoot + 'Read',
                update:     urlRoot + 'Update',
                destroy:    urlRoot + 'Destroy'
            },
            reader: {
                type: 'json',
                metaProperty: 'meta',
                root: 'data',
                idProperty: 'id',
                totalProperty: 'meta.total',
                successProperty: 'meta.success'       
            },           
            writer: {
                type: 'json',
                encode: true,
                writeAllFields: true,
                root: 'data',
                allowSingle: false,
            }
        }
    });

    // Ввод и редктирование строки, определение парметров для Get запроса
    var rowEditing = Ext.create('Ext.grid.plugin.RowEditing', {
        clicksToEdit: 2,
        autoCancel: false,
        listeners: {
            edit: function (editor, context) {
                var emp = registrStore.getProxy();
                var con = context.record;
                emp.setExtraParam("id",         con.data['id']);
                emp.setExtraParam("fam",        con.data['fam']);
                emp.setExtraParam("im",         con.data['im']);
                emp.setExtraParam("otch",       con.data['otch']);
                emp.setExtraParam("street",     con.data['street']);
                emp.setExtraParam("city",       con.data['city']);
                emp.setExtraParam("code",       con.data['code']);
            }
        }
    });
    
    // Настройка типа полей для столбцов
    var textField = {
        xtype: 'textfield'
    };
    
    // Определение столбцов
    var columns = [
        {
            header: 'ID',
            dataIndex: 'id',
            sortable: true,
            width: 35
        },
        {
            header: 'Фамилия',
            dataIndex: 'fam',
            sortable: true,
            editor: textField
        },
        {
             header: 'Имя',
             dataIndex: 'im',
             sortable: true,
             editor: textField
        },
        {
            header: 'Отчество',
            dataIndex: 'otch',
            sortable: true,
            editor: textField
        },
        {
            header: 'Город',
            dataIndex: 'city',
            sortable: true,
            editor: textField
        },
        {
            header: 'Улица',
            dataIndex: 'street',
            flex: 1,
            sortable: true,
            editor: textField
        },
        
        {
            header: 'Индекс',
            dataIndex: 'code',
            sortable: true,
            editor: textField
        }
    ];
    // Определение нижней панели управления
    var pagingToolbar = {
        xtype: 'pagingtoolbar',
        store: registrStore,
        displayInfo: true,
        items: [
            '-',
            {
                text: 'Save Changes',
                handler: function () {  
                registrStore.sync();
                }
            },
            '-',
            {
                text: 'Reject Changes',
                handler: function () {
                    // Отмена изменений в stoe
                    registrStore.rejectChanges();
                }
            },
            '-'
        ]
    };
    // Удаление записей в grid
    var onDelete = function () {
        var selected = grid.selModel.getSelection();
        Ext.MessageBox.confirm(
                'Confirm delete',
                'Are you sure?',
                function (btn) {
                    if (btn == 'yes') {
                        var nn = selected[0].get('id')
                        var emp = registrStore.getProxy();
                        emp.setExtraParam("id", nn)
                        grid.store.remove(selected);                      
                        grid.store.sync();
                    }
                }
        );
    };

    // Вставка записей в Grid
    var onInsertRecord = function () {
        var selected = grid.selModel.getSelection();
        rowEditing.cancelEdit();
        var newRegistr = Ext.create("Registr");
        registrStore.insert(selected[0].index, newRegistr);
        rowEditing.startEdit(selected[0].index, 0);
    };
    // Контекстное меню
    var doRowCtxMenu = function (view, record, item, index, e) {
        e.stopEvent();
        if (!grid.rowCtxMenu) {
            grid.rowCtxMenu = new Ext.menu.Menu({
                items: [
                    {
                        text: 'Insert Record',
                        handler: onInsertRecord
                        
                    },
                    {
                        text: 'Delete Record',
                        handler: onDelete
                    }
                ]
            });
        }
        grid.selModel.select(record);
        grid.rowCtxMenu.showAt(e.getXY());
    };

    // Grid панель
    var grid = Ext.create('Ext.grid.Panel', {
        columns: columns,
        store: registrStore,
        loadMask: true,
        bbar: pagingToolbar,
        plugins: [rowEditing],
        stripeRows: true,
        selType: 'rowmodel',
        viewConfig: {
            forceFit: true
        },
        listeners: {
            itemcontextmenu: doRowCtxMenu,
            destroy: function (thisGrid) {
                if (thisGrid.rowCtxMenu) {
                    thisGrid.rowCtxMenu.destroy();
                }
            }
        }
    });

    // Основное окно
    Ext.create('Ext.Window', {
        title: 'Клиент Django',
       // icon: '/static/app/iconMgr/icons/grid.png',
        height: 350,
        width: 800,
        border: false,
        layout: 'fit',
        items: grid,
        closable: true,
        maximizable: true,      
    }).show();

    // Старт!
    registrStore.load();
});

Описывать исходный текст приложения я не буду - он достаточно хорошо комментирован. А так выглядит клиентское приложение в работе.

2. Серверная часть приложения. Django Python 3.4.
Текст серверной части компактен, вся работа с протоколом HTTP и базой данных (CRUD) укладывается в 130 строк. Код написан 'с нуля' и предлагается 'как есть'.
kод Python
# 20.07.2015.
# Пример простой реаализации на Django стандартных операций CRUD в базе данных.
# 1. Решение реализовано в Visual Studio 2015 как проект типа DjangoWebProject.
# 2. В качестве клиента используется приложение написанное на Ext JS версии 5.1,
#    для чего в проект импортированы необходимые файлы framework Ext JS.
# 3. В качестве базы данных использована Sqlite3, которая создается автоматически.
# 4. Для запуска проекта достаточно распаковать архив и запустить приложения под Visual Studio.

from django.shortcuts import render
from django.http import HttpRequest
from django.template import RequestContext
from datetime import datetime
from django.http import HttpRequest, HttpResponse
from app.models import Registr
from django.core import serializers
import json

# Вызов стартовой страницы
def home(request):
    assert isinstance(request, HttpRequest)
    return render(
        request,
        'app/index.html',
         context_instance = RequestContext(request,
        {})           
    )
# Данные для стартовой страницы
def data(request):
   # Если GET
    if request.method == 'GET':      
    # Определим переменные
        dict = request.GET
        # Метод работы с базой данных - CRUD
        if 'method' in dict:    method  =  dict['method']        
        # Таблица (модель) с которой будем работать
        if 'model' in dict:     model   =  dict['model']      
        # Число строк на странице в форvате int
        if 'limit' in dict:     limit   =  int(dict['limit'])
        # Номер страницы в форvате int
        if 'page' in dict:      page    =  int(dict['page'])
        # Имя функции обратного вызова
        if 'callback' in dict:  callback=  dict['callback']
        # Стандартные ответы Response
        err = '"meta":{"success":"false","msg":""}})'
        ok  = '"meta":{"success":"true","msg":""}})'
        # Если таблица Registr
        if model =='Registr':   
            # Если method Read
            if method=='Read':
                try:
                    # Узнаем сколько у нас записей
                    count   = Registr.objects.count()
                    # Читаем записи с учетом парметров Page и Limit
                    registr = Registr.objects.all()[(page-1)*limit : page*limit]
                    # Сформируем список для ответа, ручками :)
                    list = []
                    for item  in registr:
                        dict_resp= {}
                        dict_resp['id']         = str(item.id)
                        dict_resp['fam']        = item.fam
                        dict_resp['im']         = item.im
                        dict_resp['otch']       = item.otch
                        dict_resp['city']       = item.city
                        dict_resp['street']     = item.street
                        dict_resp['code']       = item.code
                        list.append(dict_resp)      
                    # Сформируем ответ
                    dict_out= {"data" : list, "meta": { "success": "true", "msg": "", "total": str(count) }}
                    # Переведем его в формат Json
                    jsonFormat = json.dumps(dict_out)
                    # Добавим к ответу callback
                    read_out = dict['callback'] +'(' + jsonFormat + ')'
                    return HttpResponse(read_out)
                except Exception:
                    # Ответ при ошибке чтения
                    error_read=  dict['callback'] +'({"data": "",' + err
                    print('Ошибка при чтении данных ...')
                    return HttpResponse(error_read)
            # Если method Create
            if method=='Create':
                try:
                    # Запишем в базу данных
                    create_obj  = Registr.objects.create(\
                         fam     = dict['fam'],      \
                         im      = dict['im'],       \
                         otch    = dict['otch'],     \
                         city    = dict['city'],     \
                         street  = dict['street'],   \
                         code    = dict['code'])
                    # Cформируем callback
                    create_out = dict['callback'] +'({"data":{"id":' + str(create_obj.id) + '},' + ok  
                    # Вернем номер строки и callback в Ext JS
                    return HttpResponse(create_out);
                except Exception:
                    error_create = dict['callback'] +'({"data":{"id":' + dict['id'] +'},' + err
                    print('Ошибка при добавлении данных ...')
                    return HttpResponse(error_create)
            # Если method Update
            if method=='Update':
                try:
                    # Найдем запись по id
                    update_obj = Registr.objects.get(id = dict['id'])
                    # Изменим данные по полям
                    update_obj.fam    = dict['fam']
                    update_obj.im     = dict['im']
                    update_obj.otch   = dict['otch']
                    update_obj.city   = dict['city']
                    update_obj.street = dict['street']
                    update_obj.code   = dict['code']
                    # Запишем в базу данных
                    update_obj.save()
                    # Вернем ответ
                    update_out = dict['callback'] +'({"data":{"id":' + str(update_obj.id) + '},' + ok   
                    return HttpResponse(update_out)
                except Exception:
                    error_update = dict['callback'] +'({"data":{"id":' + dict['id'] +'},' + err        
                    print('Ошибка при обновлении данных ...')
                    return HttpResponse(error_update)
            # Если метод 'Destroy'
            if method=='Destroy':
                try:
                    # Найдем запись по id
                    delete_obj = Registr.objects.get(id = dict['id'])
                    delete_obj.delete()
                    delete_out = dict['callback'] +'({"data":null,'   + ok               
                    return HttpResponse(delete_out)
                except Exception:
                    print('Ошибка при удалении данных ...')
                    error_delete = dict['callback'] +'({"data":null,' + err  
                    return HttpResponse(error_delete)

Конечно в представленном Python коде работу с базой данных можно было бы выделить в отдельный класс, определить там методы и вызывать их. Работу c HTTP так-же можно выделить в отдельную Activity.

Проект приложения можно свободно загрузить.
Всего доброго, Евгений Вересов.
20.07.2015 года.
© Argument Ltd, 2017 год,  тел: 8-921-215-45-70,   e-mail