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 года.
|