django创建应用程序_使用Django和jQuery创建电子表格应用程序
django创建应用程序
本文介绍如何使用jQuery,jQuery插件和Django实现基于Web的简单电子表格。 这绝不是完整的尝试,也不是与Google Docs竞争的尝试,而只是证明了当今存在大量jQuery插件和工具的情况下创建“办公室”风格的Web应用程序是多么容易。 我在后端使用了SQLite / Python / Django堆栈,但是您可以轻松地移植到另一个框架,例如Ruby on Rails。
项目依赖
本文使用以下Python技术(请参阅参考资料中的链接):
- Python 2.5以上
- simplejson
- Django 1.2.3
注意: Python 2.5不包含simplejson,但它包含在更高的Python版本中。
如果你不想去通过让所有的jQuery的依赖关系的麻烦,随时免费下载使用的连接器的完整演示相关主题 。 在前端,您需要以下技术:
- jQuery 1.4.3
- jQuery UI 1.8.5
- SlickGrid
- jQuery JSON
所有这些第三方库都可以为您处理大部分工作量,尤其是SlickGrid。 我之所以选择使用SlickGrid是因为它具有突出显示/选择单元格组的功能,以防我想提高单元格的数学运算和解析能力。 它还允许您在滚动时加载数据。 还可以使用其他几种出色的jQuery网格,包括Flexigrid,jQuery Grid,jqGridView和Ingrid。 此外,jQuery项目还宣布了官方jQuery Grid插件的计划。
试算表规格
每个电子表格包含一个工作簿,每个工作簿包含一个或多个数据表。 当输入的第一个字符是等号( = )时,工作表中的每个单元格都应执行算术运算。 否则,输入的文本应保持原样。 数据被加载到JSON对象中,异步发送到后端,然后保存到数据库。 电子表格将处理“打开”,“新建”和“保存”操作,并且工作簿的名称显示在顶部的可编辑文本框中。
单击“ 打开”将打开一个jQuery UI窗口,该窗口显示数据库中的现有工作簿。 选择工作簿后,使用异步JavaScript和XML(Ajax)检索存储的JSON数据并将其呈现到网格中。 保存以异步方式将JSON格式的网格数据发送到后端进行存储。 “新建”操作将删除所有引用并重新加载干净的工作簿。
最后,工作簿的工作表分为不同的jQuery UI选项卡。 与其他任何电子表格一样,这些选项卡显示在底部,并通过单击底部的按钮动态添加。
项目结构
您将所有CSS / JavaScript /图像放置在项目顶层的资源文件夹下。 Django应用程序将包含一个名为index.html的模板,该模板不过是一点点标记,因为您希望保持HTML语义和JavaScript代码非侵入性。 诸如网格之类的组件创建是动态完成的。 电子表格定义包含在一个名为sheetsheet.js的文件中。
创建Django后端
首先,通过发出以下命令来创建Django项目:
django-admin startproject spreadsheet 然后,通过调用以下命令,将Cd放入新创建的项目中以创建应用程序:
django-admin startapp spreadsheet_app 本文使用SQLite3来避免进行额外的数据库工作,但可以随意使用您喜欢的任何关系数据库系统(RDBS)。 修改settings.py文件以使用清单1中的代码。
清单1. Django settings.py文件
import os
APPLICATION_DIR = os.path.dirname( globals()[ '__file__' ] )DATABASES = {'default': {'ENGINE': 'django.db.backends.sqlite3', 'NAME': 'db', 'USER': '', 'PASSWORD': '', 'HOST': '', 'PORT': '', }
}MEDIA_ROOT = os.path.join( APPLICATION_DIR, 'resources' )
MEDIA_URL = 'http://localhost:8000/resources/'ROOT_URLCONF = 'spreadsheet.urls'TEMPLATE_DIRS = (os.path.join( APPLICATION_DIR, 'templates' ),
)INSTALLED_APPS = ('django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.sites','django.contrib.messages','spreadsheet_app',
) 您无需在settings.py文件中修改任何其他变量。 现在,配置URL映射。 在这种情况下,仅需要两个映射:一个用于静态服务的文件,另一个指向索引。 清单2显示了代码。
清单2. urls.py文件
from django.conf.urls.defaults import *
from django.conf import settings
import spreadsheet.spreadsheet_app.views as viewsurlpatterns = patterns('',( r'^resources/(?P.*)$','django.views.static.serve',{ 'document_root': settings.MEDIA_ROOT } ),url( r'^spreadsheet_app/', views.index, name="index" ) ,
) 在项目的顶层创建一个名为资源的目录,并在其子目录中创建名为css,js和images的子目录。 下载的依赖项(例如SlickGrid)以及应用程序的自定义JavaScript代码都存储在此处。 如果您感到懒惰,只需下载演示并复制资源目录即可。
接下来,创建您的域模型(请参见清单3)。 该模型将仅包含三个字段: workbook_name , sheet_name和data 。 Django对象关系映射器(ORM)自动创建一个名为id的键字段。
清单3. models.py文件
# file: spreadsheet_app/models.pyfrom django.db import modelsclass Workbooks(models.Model):workbook_name = models.CharField(max_length=30)sheet_name = models.CharField(max_length=30)data = models.TextField() 如您所知,您没有充分利用Django的全部功能。 您只希望它处理后端工作,而将繁重的工作留给jQuery前端。
最后,创建索引视图。 索引视图处理您对电子表格的创建/读取/更新操作。 在不涉及索引视图的所有细节的情况下,清单4展示了如何处理传入请求的基础。
清单4.视图
# file:spreadsheet_app/views.py
from django.template.context import RequestContext
from spreadsheet.spreadsheet_app.models import Workbooks
import simplejson as json
from django.http import HttpResponsedef index(request):app_action = request.POST.get('app_action')posted_data = request.POST.get('json_data')if posted_data is not None and app_action == 'save':...elif app_action == 'get_sheets':...elif app_action == 'list':... 导入之后,您会看到索引视图接受一个请求对象,该对象包含从客户端发送的发布数据。 您将获得两个参数: app_action和posted_data 。 app_action参数告诉您客户端请求的操作,例如创建新的工作簿。 posted_data参数是从客户端发送的一张工作表的JSON数据。 不同的动作由一个简单的if语句处理; 您可以保存工作表,获取工作簿的所有工作表,或获取数据库中工作簿的列表。
您稍后将返回索引视图以查看详细信息。 现在,在sheetsheets_app目录中添加一个名为模板的目录。 在模板子目录中,添加一个名为index.html的文件,这是项目中唯一的模板。 清单5显示了代码。
清单5.索引模板
Overly Simple Spreadsheet
如您所见,HTML中没有控件逻辑或样式,只是标记。 事实上,身体中甚至没有任何元素。 所有这些都是由JavaScript代码即时生成的。 这个想法是通过方法调用来添加和删除元素。
电子表格.js的鸟瞰图
加载后,电子表格将呈现UI并加载单个选项卡。 清单6显示了render_ui的代码。
清单6.电子表格.js中的render_ui方法
function render_ui(){insert_menu_markup();insert_grid_markup();make_grid_component();add_newtab_button();insert_open_dialog_markup();make_open_dialog();
} 让我们以insert_menu_markup开头, insert_menu_markup每种方法的操作方式。 此方法仅添加了顶部菜单HTML代码,如清单7所示。该菜单由三个按钮(新建,打开和保存)以及用于显示和输入工作簿名称的文本字段组成。 您使用jQuery prepend来确保添加标记时,它将作为正文中的第一个元素插入。
清单7.生成菜单标记的方法
// OK, it's not really a menu...yet :-)
function insert_menu_markup(){$("body").prepend('');$("body").prepend('');$("body").prepend('');$("body").prepend('');
} 图1显示了非常简单的菜单,它实际上只是一些按钮和一个文本框。
图1.菜单
insert_grid_markup方法类似于insert_menu_markup ,但是这次您使用append方法。 jQuery UI需要的列表来生成选项卡小部件:
function insert_grid_markup(){var workbook_widget = '
'; $('body').append(workbook_widget);
} 现在,通过调用方法make_grid_component使选项卡make_grid_component 。 清单8显示了代码。
清单8.将表格div更改为选项卡
function make_grid_component(){$("#tabs").tabs();$(".tabs-bottom .ui-tabs-nav, .tabs-bottom .ui-tabs_nav > *").removeClass("ui-corner-all ui-corner-top").addClass("ui-corner-bottom");
} 您使用jQuery id选择器获取对选项卡的引用,然后调用tabs()方法将转换为选项卡小部件。 删除默认CSS类ui-corner-top ,然后添加ui-corner-bottom类,以便选项卡显示在底部,如图2所示。
图2.工作簿选项卡
该组件是所有数据网格的容器。 现在,在选项卡组件下添加一个按钮,该按钮将在每次单击时动态添加一个选项卡。 您可以使用add_newtab_button方法执行此add_newtab_button :
function add_newtab_button(){$('body').append('');
} 最后一个要创建的可视组件是Open窗口,您可以通过调用清单9中所示的方法insert_open_dialog_markup来创建该窗口。与其他insert标记方法一样,该窗口创建一个包含标记信息的字符串并将其附加到正文中。
清单9.生成对话框标记的方法
function insert_open_dialog_markup(){var dlg = '' +'' +'Select an archive.
';$("body").append(dlg);
} 现在,这个窗口的标记是存在的,你用增加它的功能make_open_dialog清单10的去年方法所示方法render_ui 。 通过调用.dialog()方法并向其传递参数autoOpen:false ,除非明确打开表单,否则该表单不会显示在网页上。 对话框窗体包含一个选择列表,其中包含加载时工作簿名称的列表。
清单10.使“打开”窗口起作用
function make_open_dialog(){$('#dialog_form').dialog({autoOpen: false,modal: true,buttons: {"OK":function(){selected_wb = $('option:selected').attr('value');$(this).dialog('close');// remove grid, existing forms, and recreate$('body').html('');render_ui();// load grids and create forms with invisible inputsload_sheets(selected_wb);// place workbook name in text field$('#workbook_name').val(selected_wb);},"Cancel":function(){$(this).dialog('close');}}});
} 您再次使用jQuery选择器来获取dialog_form的句柄并调用dialog()方法,该方法将html元素转换为jQuery窗口。 “打开”窗口是模式窗口,在页面加载时不会打开。 它还包含两个按钮-确定和取消。 后者只不过是关闭窗口而已。 OK函数在选择列表中找到所选项目并获取其值。 然后,它关闭窗口,删除主体中的所有子元素,重新渲染GUI组件,并加载工作表(或者,如果您更喜欢用术语SlickGrids )。 如前所述,由于标记的生成全部在方法中,因此添加和删除这些窗口小部件组件现在变得无关紧要。
呈现UI基础之后,您现在编写一种用于使用网格打开新选项卡的方法。 继续从上至下的方法,清单11显示了openTab方法,这是此应用程序中的关键功能。
注意:应用程序中的每个工作表都包含一个遵循简单约定的ID:tabs_,后跟工作表中的标签号。
清单11.向工作簿添加新标签的方法
function openTab(sheet_id) {numberOfTabs = $("#tabs").tabs("length");tab_name = "tabs_" + numberOfTabs;$("#tabs").tabs("add","#" + tab_name,"Sheet " + numberOfTabs, numberOfTabs);$("#" + tab_name).css("display","block");$("#tabs").tabs("select",numberOfTabs);$('#'+tab_name ).css('height','80%');$('#'+tab_name ).css('width','95%');$('#'+tab_name ).css('float','left');add_grid(tab_name, numberOfTabs);// add form for saving this tabs dataif(!sheet_id){$('body').append('');} else {$('body').append('');}
} 如果您感兴趣,一种编码标记字符串的好方法是使用某种JavaScript模板,而不是将字符串串联在一起。 唯一的工作表ID被保存到隐藏的input元素中。 您还添加了一个隐藏元素,用于保存JSON数据。 隐藏的输入遵循简单的命名约定-数据后跟制表符编号。 要注意的另一个重要点是,新添加的选项卡在添加实际的SlickGrid之前已对其CSS属性进行了修改。 如果不这样做,则网格将无法正确渲染。
openTab方法调用add_grid方法,该方法执行SlickGrid对象的实际实例化。 清单12显示了该应用程序的繁重工作。 创建两个JavaScript对象- workbook和grid_references 。 Workbook对象包含对当前workbook对象的引用,而grid_references包含对每个SlickGrid对象的引用。 add_grid方法接受两个参数:网格名称和网格编号。
在列定义中,您希望使用SlickGrid示例之一中提供的TextCellEditor作为默认值(从a到p)有16列。 在列定义之后,将参数定义提供给SlickGrid。 单元格是可编辑的,可调整大小的和可选的。 确认asyncEditorLoading选项设置为True很重要,这可以让您在网格上实现Ajax操作。
清单12.将SlickGrids添加到您的应用程序
var workbook = {};
var grid_references = {};function add_grid(grid_name, gridNumber){var grid;var current_cell = null;// column definitionsvar columns = [{id:"row", name:"#", field:"num", cssClass:"cell-selection", width:40, cannotTriggerInsert:true, resizable:false, unselectable:true },{id:"a", name:"a", field:"a", width:70, cssClass:"cell-title", editor:TextCellEditor},{id:"b", name:"b", field:"b", width:70, cssClass:"cell-title", editor:TextCellEditor},{id:"c", name:"c", field:"c", width:70, cssClass:"cell-title", editor:TextCellEditor},{id:"d", name:"d", field:"d", width:70, cssClass:"cell-title", editor:TextCellEditor},{id:"e", name:"e", field:"e", width:70, cssClass:"cell-title", editor:TextCellEditor},{id:"f", name:"f", field:"f", width:70, cssClass:"cell-title", editor:TextCellEditor},{id:"g", name:"g", field:"g", width:70, cssClass:"cell-title", editor:TextCellEditor},{id:"h", name:"h", field:"h", width:70, cssClass:"cell-title", editor:TextCellEditor},{id:"i", name:"i", field:"i", width:70, cssClass:"cell-title", editor:TextCellEditor},{id:"j", name:"j", field:"j", width:70, cssClass:"cell-title", editor:TextCellEditor},{id:"k", name:"k", field:"k", width:70, cssClass:"cell-title", editor:TextCellEditor},{id:"l", name:"l", field:"l", width:70, cssClass:"cell-title", editor:TextCellEditor},{id:"m", name:"m", field:"m", width:70, cssClass:"cell-title", editor:TextCellEditor},{id:"n", name:"n", field:"n", width:70, cssClass:"cell-title", editor:TextCellEditor},{id:"o", name:"o", field:"o", width:70, cssClass:"cell-title", editor:TextCellEditor},{id:"p", name:"p", field:"p", width:70, cssClass:"cell-title", editor:TextCellEditor},];var options = {editable: true,autoEdit: true,enableAddRow: true,enableCellNavigation: true,enableCellRangeSelection : true,asyncEditorLoading: true,multiSelect: true,leaveSpaceForNewRows : true,rerenderOnResize : true};eval("var data" + gridNumber + " = [];");workbook["data" + gridNumber] = [];for( var i=0; i < 100 ; i++ ){var d = (workbook["data"+gridNumber][i] = {});d["num"] = i;d["value"] = "";}grid = new Slick.Grid($("#"+grid_name),workbook["data"+gridNumber], columns, options);
... 您使用eval语句以某种“变态”的方式动态创建变量名。 然后使用空字符串数据加载data变量,并创建一个SlickGrid实例。
现在,向事件添加网格attach ,如清单13所示。当onCurrentCellChanged事件发生时,它将获取网格数据并更新单元格内容。 在单元格编辑完成之前,将调用onBeforeCellEditorDestroy事件。 触发后,您将像以前一样获取单元格数据,但是这次,您将确定第一个字符是否为等号。 如果是这样,请使用JavaScript eval方法评估输入的表达式。
警告:请勿在生产环境中使用此代码。 它将使您的系统容易受到各种讨厌的注入攻击。 始终清理数据。
最后,保存对网格的引用以用于其他方法。
清单13.将事件处理添加到网格
// file: resources/js/spreadsheet.js continued// Eventsgrid.onCurrentCellChanged = function(){d = grid.getData();row = grid.getCurrentCell().row;cell = grid.getCurrentCell().cell;this_cell_data = d[row][grid.getColumns()[cell].field];};grid.onBeforeCellEditorDestroy = function(){d = grid.getData();row = grid.getCurrentCell().row;cell = grid.getCurrentCell().cell;this_cell_data = d[row][grid.getColumns()[cell].field];if(this_cell_data && this_cell_data[0] === "="){// evaluate JavaScript expression, don't use// in production!!!!eval("var result = " + this_cell_data.substring(1));d[row][grid.getColumns()[cell].field] = result;}};grid_references[grid_name] = grid;
}; 添加菜单事件
回到规范,您的电子表格必须创建一个新工作簿以及保存并打开现有工作簿。 让我们实现新的工作簿功能,这是三个任务中最简单的一个。 在这里,您所需要做的就是销毁UI和所有引用,然后重新绘制,如清单14所示。
清单14.创建一个新的工作簿
$('#new').live('click', function(){// delete any existing referencesworkbook = {};grid_references = {};// remove grid, existing forms, and recreate$('body').html('');// recreaterender_ui();openTab();
}); “保存”功能稍微复杂一些。 您使用jQuery选择器获取具有以data开头的name属性的每个元素,然后使用each方法遍历结果集。 要注意的最关键的事情是,使用jquery.json插件将网格数据编码为JSON格式,以便“通过网络”发送。 您可以使用$.post方法异步发送数据。 传递到索引视图的参数是要执行的操作(在本例中,如清单15所示,保存),唯一的工作表ID,工作簿名称以及JSON格式的网格数据。
清单15.保存网格数据
$('#save').live('click',function(){// Do a foreach on all the grids. The ^= operator gets all// the inputs with a name attribute that begins with data$("[name^='data']").each(function(index, value){var data_index = "data"+index;var sheet_id = $('#tabs_'+index+'_form').find('#sheet_id').val();if(sheet_id !== ''){sheet_id = eval(sheet_id);}// convenience variable for readabilityvar data2post = $.JSON.encode(workbook[data_index]);$("#"+data_index).val(data2post);$.post( '{% url index %}', {'app_action':'save', 'sheet_id': sheet_id,'workbook_name':$('#workbook_name').val(),'sheet':data_index, 'json_data':data2post});});
}); 回想一下,打开需要使用load_sheets方法。 现在添加该方法(请参见清单16)。 此方法必须调用后端,以将传递给该工作簿的工作表的所有工作表请求给工作表。 然后将数据加载到相应的SlickGrid对象中。 请注意,在将数据插入对象之前,必须使用decode方法对JSON数据进行反序列化。 然后渲染网格。
清单16.将数据加载到网格中
function load_sheets(workbook_name){$('#workbook_list').load('{% url index %}', {'app_action':'get_sheets','workbook_name':workbook_name}, function(sheets, resp, t){sheets = $.JSON.decode(sheets);workbook = {}; // resetgrid_references = {};$.each(sheets, function(index, value){// add to workbook objectvar sheet_id = value["sheet_id"];openTab(sheet_id);// By calling eval, we translate value from// a string to a JavaScript objectworkbook[index] = eval(value["data"]);// insert data into hidden$("#data"+index).attr('value', workbook[index]);grid_references["tabs_"+index].setData(workbook[index]);grid_references["tabs_"+index].render();});});
} 最后但并非最不重要的一点是,您实现了open函数。 再次,进行异步调用,这次发送list操作,然后再次反序列化以JSON发送的数据。 然后,使用数据库中所有工作簿的名称更新select列表。 然后打开对话框。 清单17显示了代码。
清单17.打开一个现有的工作簿
$('#open').live('click',function(){// load is used for doing asynchronous loading of data$('#workbook_list').load('{% url index %}', {'app_action':'list'}, function(workbooks,success){workbooks = $.JSON.decode(workbooks);$.each(workbooks, function(index, value){$('#workbook_list').append(' 重新查看索引视图
现在,您已经对客户端发布进行了编码,您可以完成索引视图。 清单18显示了完整的索引视图,除了导入。
要(反)序列化JSON代码,请使用simplejson模块,然后命令转储对其进行序列化以及反向加载。 对于“保存”操作,如果为单个工作表指定了ID,则会更新工作表。 否则,将创建一个新工作表。
相反,“获取工作表”操作将创建一个JSON对象,其中包含特定工作簿的所有工作表。 它使用filter()命令检索工作簿的所有工作表( QuerySet对象)。 这等效于select * from spreadsheet_app_workbooks workbook_name = wb_name" select * from spreadsheet_app_workbooks ,其中workbook_name = wb_name" 。检索到集合后,您可以将它们再次转换为JSON格式,然后将其发送回客户端。
List还使用Django ORM,但这一次使用values()方法。 在这里,您告诉Django,“获取workbook_name列”。 通过在QuerySet对象上调用distinct方法,您明确表示您不需要任何重复项。 您再次使用列表推导根据结果创建列表。 对于get_sheets和list ,您必须为jQuery返回HttpResponse对象,以处理您的Ajax响应。
清单18.视图完成
def index(request):template = 'index.html'app_action = request.POST.get('app_action')posted_data = request.POST.get('json_data')if posted_data is not None and app_action == 'save':this_sheet = request.POST.get("sheet")this_workbook = request.POST.get("workbook_name")sheet_id = request.POST.get("sheet_id")posted_data = json.dumps(posted_data)if(sheet_id):wb = Workbooks(id=sheet_id, workbook_name=this_workbook, sheet_name=this_sheet, data=posted_data)else:wb = Workbooks(workbook_name=this_workbook, sheet_name=this_sheet, data=posted_data)wb.save()elif app_action == 'get_sheets':wb_name = request.POST.get('workbook_name')sheets = Workbooks.objects.filter(workbook_name=wb_name)# use list comprehension to create python list which is like a JSON objectsheets = [{ "sheet_id":i.id, "workbook_name": i.workbook_name.encode("utf-8"),"sheet_name": i.sheet_name.encode("utf-8"), "data": json.loads(i.data.encode("utf-8"))} for i in sheets ]# dumps -> serialize to JSONsheets = json.dumps(sheets)return HttpResponse( sheets, mimetype='application/javascript' )elif app_action == 'list':workbooks = Workbooks.objects.values('workbook_name').distinct()# use list comprehension to make a list of just the work books namesworkbooks = [ i['workbook_name'] for i in workbooks ]# encode into json format before sending to pageworkbooks = json.dumps(workbooks)# We need to return an HttpResponse object in order to complete# the ajax callreturn HttpResponse( workbooks, mimetype='application/javascript' )return render_to_response(template, {},context_instance = RequestContext( request )) 完成网格
现在,您已经定义了所有JavaScript方法,可以使用两个方法调用来生成应用程序,如清单19所示。
清单19.加载页面时执行的代码
$(document).ready(function(){render_ui();openTab();
}); 页面加载完成后,将呈现UI,并将新的选项卡插入工作簿。
现在,您可以在命令行中运行python manage syncdb来创建数据库,以查看其电子表格应用程序的全部(缺少)功能。 完成后,执行命令python manage.py runserver并导航到http://localhost:8000/spreadsheet_app 。 您应该看到最终版本,如图3所示。
图3.完整的电子表格应用程序
您可以在这个项目中添加很多东西,但是我不得不施加一些自我约束。 例如,您可以包括:
- 情节
- 一个安全,功能更强大的解析器,用于输入到每个单元格中的公式
- 导出为其他格式,例如Microsoft®Office Excel
- 使用其他jQuery插件的真实菜单
结论
尽管此Web应用程序尚未投入生产,但它演示了如何结合多种技术。 使用不引人注目JavaScript,语义HTML,JSON对象以异步方式从服务器来回传递数据,并且-尤其值得注意的是-当有许多可用的jQuery插件时,不浪费时间,这使您的工作变得更加轻松。
翻译自: https://www.ibm.com/developerworks/web/library/wa-django/index.html
django创建应用程序
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
