我想写[Django实战教程]这系列的文章是因为我最近在开发一个数据库脚本发布系统,该系统只限于公司内部使用,它的功能非常简单明了:开发人员通过该系统提交SQL语句,DBA审核后执行,并将结果反馈给开发人员。
当然,开发这种小系统方法很多,我选择的是Django+Bootstrap,Web框架选择Django是因为我对Python语言比较熟,平时经常用它写一些自动化脚本;前端框架选择Bootstrap是因为我很喜欢它那种小清新的界面风格。
这一系列的实战文章会详细记录我开发这个系统的过程,包括设计、源码等,因为我也是第一次使用Django,所以难免会有纰漏和错误,请大家多多指教。
所谓“工欲善其事,必先利其器”,下面介绍如何安装Django+Bootstrap。
Django 安装
在安装Django之前,系统上必须已经安装了Python,至于如何安装Python,这里就不多讲了,有兴趣的可以参考下我之前写的《[Python]安装及环境配置》,我选择的Python版本是2.7.3,这里建议大家不要使用Python 3,因为Python 3做了比较大的改动,目前还不太稳定,最重要的是很多API都改了,可能不兼容旧版本。
参考: Python学习总结—安装与配置环境 http://www.linuxidc.com/Linux/2012-11/73912.htm
安装好Python之后,建议安装easy_install,这样直接使用命令easy_install django即可下载最新版本,非常方便。
当然也可以采用源码安装,首先到Django官网下载tar.gz文件,然后:
tar xzvf Django-1.5.2.tar.gz
cd Django-1.5.2
sudo python setup.py install
django-bootstrap-toolkit 安装
关于Bootstrap的界面风格大家可以去Bootstrap的首页去看下,我个人很喜欢这种小清新的风格,
Bootstrap 的详细介绍:请点这里
Bootstrap 的下载地址:请点这里
相关阅读:
BootStrap入门教程图文详解 http://www.linuxidc.com/Linux/2013-08/88764.htm
dyve大牛开发了开源软件包django-bootstrap-toolkit,大家可以去github下载,链接地址是https://github.com/dyve/django-bootstrap-toolkit
该软件包中最重要的是“bootstrap_toolkit”文件夹,它是一个Django的app,待会儿需要把它拷贝至我们的项目,还有一个demo_project,这是一个demo项目。
通过该软件包,我们可以把Django和Bootstrap无缝结合起来,非常方便。这里顺便吐槽下百度,当我在百度里搜django bootstrap时,死活搜不到,但是用google搜第一个就是它,所以和技术相关的东西大家还是用google吧。
创建Django项目
Django安装成功后,就可以使用django-admin.py创建项目:
# django-admin.py startproject dbrelease
dbrelease是我为这个项目起的名字。
项目创建成功后,我们还要创建应用程序:
# python manage.py startapp dbrelease_app
然后再settings.py的INSTALLED_APPS添加’dbrelease_app’应用程序
现在我们运行一下Web服务器:
# python manage.py runserver 192.168.1.16:8000
在浏览器里输入http://192.168.1.16:8000/就可以看到Django淡蓝色的欢迎界面,因为现在还是一个空项目,除了欢迎界面其它的什么都没有,接下来的全部工作就是丰富页面内容。
为了能在新创建的Django项目中能使用bootstrap,我们需要把文件夹bootstrap_toolkit拷贝到项目的主文件夹下,并在settings.py文件中的INSTALLED_APPS里添加’bootstrap_toolkit’应用程序。
Django 的详细介绍:请点这里
Django 的下载地址:请点这里
推荐阅读:
Ubuntu Server 12.04 安装Nginx+uWSGI+Django环境 http://www.linuxidc.com/Linux/2012-05/60639.htm
更多精彩内容请看下一页: http://www.linuxidc.com/Linux/2013-09/90277p2.htm
在正式开始coding之前,我觉得有必要探讨下Django的MTV模式,理论和实践相结合,才能更好的掌握一门技术。
首先说说Web服务器开发领域里著名的MVC模式,所谓MVC就是把Web应用分为模型(M),控制器(C)和视图(V)三层,他们之间以一种插件式的、松耦合的方式连接在一起,模型负责业务对象与数据库的映射(ORM),视图负责与用户的交互(页面),控制器接受用户的输入调用模型和视图完成用户的请求,其示意图如下所示:
Django的MTV模式本质上和MVC是一样的,也是为了各组件间保持松耦合关系,只是定义上有些许不同,Django的MTV分别是值:
M 代表模型(Model):负责业务对象和数据库的关系映射(ORM)。
T 代表模板 (Template):负责如何把页面展示给用户(html)。
V 代表视图(View):负责业务逻辑,并在适当时候调用Model和Template。
除了以上三层之外,还需要一个URL分发器,它的作用是将一个个URL的页面请求分发给不同的View处理,View再调用相应的Model和Template,MTV的响应模式如下所示:
1,Web服务器(中间件)收到一个http请求
2,Django在URLconf里查找对应的视图(View)函数来处理http请求
3,视图函数调用相应的数据模型来存取数据、调用相应的模板向用户展示页面
4,视图函数处理结束后返回一个http的响应给Web服务器
5,Web服务器将响应发送给客户端
这种设计模式关键的优势在于各种组件都是松耦合的。这样,每个由 Django驱动的Web应用都有着明确的目的,并且可独立更改而不影响到其它的部分。
比如,开发者更改一个应用程序中的 URL 而不用影响到这个程序底层的实现。设计师可以改变 HTML页面的样式而不用接触Python代码。
数据库管理员可以重新命名数据表并且只需更改模型,无需从一大堆文件中进行查找和替换。
落到实处,Django的MTV模式相对应的python文件如下:
前两篇讲述了Django的理论,从这篇开始,我们真正进入Django实战部分,今天先从用户认证开始。
当大家平时打开一个网站时,第一步一般做什么?我猜大部分是先登录吧,所以我们就从用户认证开始。
打开用户认证
Django本身已经提供了用户认证模块,所以我们要做的事很简单,就是在它的基础上添加一些定制化的东西。默认情况下,Django的用户认证模块是打开的,可以通过以下步骤确认用户模块是否打开(在settings.py文件里):
1、确保 MIDDLEWARE_CLASSES 中包含 ‘django.contrib.sessions.middleware.SessionMiddleware’。
2、确认 INSTALLED_APPS 中有 ‘django.contrib.sessions’
3、将 ‘django.contrib.auth’ 放在你的 INSTALLED_APPS 设置中,然后运行 manage.py syncdb以创建对应的数据库表。
4、确认 SessionMiddleware 后面的 MIDDLEWARE_CLASSES 设置中包含 ‘django.contrib.auth.middleware.AuthenticationMiddleware’。
数据库配置
用户认证系统必然离不开数据库,因为用户信息需要保存在数据库里,Django自带的用户认证系统也不例外。在使用它之前,必须配置数据库,Django支持大部分的主流数据库,这里我采用的是Oracle数据库,首先需要安装cx_Oracle模块,Django必须通过它才能访问Oracle数据库。至于如何安装cx_Oracle模块,这里就不讲了,具体查看官方文档。
接着在Oracle中创建一个用户,我们Django项目创建的所有表都建在该用户下,因此需要相应的权限:
SQL> create user dbrelease identified by *** default tablespace dbrelease;
SQL> grant resource,create session,unlimited tablespace to dbrelease;
然后在settings.py中的Databases属性里设置django连接oracle:
DATABASES = {
‘default’: {
‘ENGINE’: ‘django.db.backends.oracle’,
‘NAME’: ‘lxdbtest’,
‘USER’: ‘dbrelease’,
‘PASSWORD’: ‘***’,
‘HOST’: ‘192.168.1.16’,
‘PORT’: ‘1521’,
}
}
注意:上面的NAME是指Oracle的instance_name
好了,下面验证是否可以成功连接Oracle:
进入Django项目的主目录,运行python manage.py shell进入交互命令行,然后输入:
>>> from django.db import connection
>>> cursor = connection.cursor()
如果没有报错,说明已经配置成功。
最后执行syncdb语句,从以下输出中我们可以发现,用户认证系统默认在数据库里创建了以下表:
# python manage.py syncdb
Creating tables …
Creating table auth_permission
Creating table auth_group_permissions
Creating table auth_group
Creating table auth_user_groups
Creating table auth_user_user_permissions
Creating table auth_user
Creating table django_content_type
Creating table django_session
Creating table django_site
上一篇中,我们已经打开了Django自带的用户认证模块,并配置了数据库连接,创建了相应的表,本篇我们将在Django自带的用户认证的基础上,实现自己个性化的用户登录和注销模块。
首先,我们自己定义一个用户登录表单(forms.py):
from django import forms
from django.contrib.auth.models import User
from bootstrap_toolkit.widgets import BootstrapDateInput, BootstrapTextInput, BootstrapUneditableInput
class LoginForm(forms.Form):
username = forms.CharField(
required=True,
label=u”用户名”,
error_messages={‘required’: ‘请输入用户名’},
widget=forms.TextInput(
attrs={
‘placeholder’:u”用户名”,
}
),
)
password = forms.CharField(
required=True,
label=u”密码”,
error_messages={‘required’: u’请输入密码’},
widget=forms.PasswordInput(
attrs={
‘placeholder’:u”密码”,
}
),
)
def clean(self):
if not self.is_valid():
raise forms.ValidationError(u”用户名和密码为必填项”)
else:
cleaned_data = super(LoginForm, self).clean()
我们定义的用户登录表单有两个域username和password,这两个域都为必填项。
接下来,我们定义用户登录视图(views.py),在该视图里实例化之前定义的用户登录表单
from django.shortcuts import render_to_response,render,get_object_or_404
from django.http import HttpResponse, HttpResponseRedirect
from django.contrib.auth.models import User
from django.contrib import auth
from django.contrib import messages
from django.template.context import RequestContext
from django.forms.formsets import formset_factory
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
from bootstrap_toolkit.widgets import BootstrapUneditableInput
from django.contrib.auth.decorators import login_required
from .forms import LoginForm
def login(request):
if request.method == ‘GET’:
form = LoginForm()
return render_to_response(‘login.html’, RequestContext(request, {‘form’: form,}))
else:
form = LoginForm(request.POST)
if form.is_valid():
username = request.POST.get(‘username’, ”)
password = request.POST.get(‘password’, ”)
user = auth.authenticate(username=username, password=password)
if user is not None and user.is_active:
auth.login(request, user)
return render_to_response(‘index.html’, RequestContext(request))
else:
return render_to_response(‘login.html’, RequestContext(request, {‘form’: form,’password_is_wrong’:True}))
else:
return render_to_response(‘login.html’, RequestContext(request, {‘form’: form,}))
该视图实例化了之前定义的LoginForm,它的主要业务逻辑是:
1. 判断必填项用户名和密码是否为空,如果为空,提示”用户名和密码为必填项”的错误信息
2. 判断用户名和密码是否正确,如果错误,提示“用户名或密码错误”的错误信息
3. 登陆成功后,进入主页(index.html)
其中,登录页面的模板(login.html)定义如下:
<!DOCTYPE html>
{% load bootstrap_toolkit %}
{% load url from future %}
<html lang=”en”>
<head>
<meta charset=”utf-8″>
<title>数据库脚本发布系统</title>
<meta name=”description” content=””>
<meta name=”author” content=”朱显杰”>
{% bootstrap_stylesheet_tag %}
{% bootstrap_stylesheet_tag “responsive” %}
<style type=”text/css”>
body {
padding-top: 60px;
}
</style>
<!–[if lt IE 9]>
<script src=”//html5shim.googlecode.com/svn/trunk/html5.js”></script>
<![endif]–>
<script src=”//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js”></script>
{% bootstrap_javascript_tag %}
{% block extra_head %}{% endblock %}
</head>
<body>
{% if password_is_wrong %}
<div class=”alert alert-error”>
<button type=”button” class=”close” data-dismiss=”alert”>×</button>
<h4>错误!</h4>用户名或密码错误
</div>
{% endif %}
<div class=”well”>
<h1>数据库脚本发布系统</h1>
<p> </p>
<form class=”form-horizontal” action=”” method=”post”>
{% csrf_token %}
{{ form|as_bootstrap:”horizontal” }}
<p class=”form-actions”>
<input type=”submit” value=”登录” class=”btn btn-primary”>
<a href=”/contactme/”><input type=”button” value=”忘记密码” class=”btn btn-danger”></a>
<a href=”/contactme/”><input type=”button” value=”新员工?” class=”btn btn-success”></a>
</p>
</form>
</div>
</body>
</html>
最后还需要在urls.py里添加:
(r’^accounts/login/$’, ‘dbrelease_app.views.login’),
最终的效果如下:
1)当在浏览器里输入http://192.168.1.16:8000/accounts/login/,出现如下登陆界面:
2)当用户名或密码为空时,提示”用户名和密码为必填项”,如下所示:
3)当用户名或密码错误时,提示“用户名或密码错误”,如下所示:
4)如果用户名和密码都正确,进入主页(index.html)。
既然有login,当然要有logout,logout比较简单,直接调用Django自带用户认证系统的logout,然后返回登录界面,具体如下(views.py):
@login_required
def logout(request):
auth.logout(request)
return HttpResponseRedirect(“/accounts/login/”)
上面@login_required表示只有用户在登录的情况下才能调用该视图,否则将自动重定向至登录页面。
urls.py里添加:
(r’^accounts/logout/$’, ‘dbrelease_app.views.logout’),
上一篇我们实现了用户的登录和注销模块,本篇我们将实现用户自己修改密码。
同样地,我们首先在forms.py创建修改密码的表单如下:
class ChangepwdForm(forms.Form):
oldpassword = forms.CharField(
required=True,
label=u”原密码”,
error_messages={‘required’: u’请输入原密码’},
widget=forms.PasswordInput(
attrs={
‘placeholder’:u”原密码”,
}
),
)
newpassword1 = forms.CharField(
required=True,
label=u”新密码”,
error_messages={‘required’: u’请输入新密码’},
widget=forms.PasswordInput(
attrs={
‘placeholder’:u”新密码”,
}
),
)
newpassword2 = forms.CharField(
required=True,
label=u”确认密码”,
error_messages={‘required’: u’请再次输入新密码’},
widget=forms.PasswordInput(
attrs={
‘placeholder’:u”确认密码”,
}
),
)
def clean(self):
if not self.is_valid():
raise forms.ValidationError(u”所有项都为必填项”)
elif self.cleaned_data[‘newpassword1’] <> self.cleaned_data[‘newpassword2’]:
raise forms.ValidationError(u”两次输入的新密码不一样”)
else:
cleaned_data = super(ChangepwdForm, self).clean()
return cleaned_data
这个表单有三个选项,分别是:原密码、新密码、确认密码,所有项都为必填项,且两次输入的新密码必须一样。
接着我们在views.py创建修改密码的视图,在该视图中实例化上面定义的表单:
@login_required
def changepwd(request):
if request.method == ‘GET’:
form = ChangepwdForm()
return render_to_response(‘changepwd.html’, RequestContext(request, {‘form’: form,}))
else:
form = ChangepwdForm(request.POST)
if form.is_valid():
username = request.user.username
oldpassword = request.POST.get(‘oldpassword’, ”)
user = auth.authenticate(username=username, password=oldpassword)
if user is not None and user.is_active:
newpassword = request.POST.get(‘newpassword1’, ”)
user.set_password(newpassword)
user.save()
return render_to_response(‘index.html’, RequestContext(request,{‘changepwd_success’:True}))
else:
return render_to_response(‘changepwd.html’, RequestContext(request, {‘form’: form,’oldpassword_is_wrong’:True}))
else:
return render_to_response(‘changepwd.html’, RequestContext(request, {‘form’: form,}))
其中,修改密码的模板changepwd.html的定义如下:
<!DOCTYPE html>
{% load bootstrap_toolkit %}
{% load url from future %}
<html lang=”en”>
<head>
<meta charset=”utf-8″>
<title>数据库脚本发布系统</title>
<meta name=”description” content=””>
<meta name=”author” content=”朱显杰”>
{% bootstrap_stylesheet_tag %}
{% bootstrap_stylesheet_tag “responsive” %}
<style type=”text/css”>
body {
padding-top: 60px;
}
</style>
<!–[if lt IE 9]>
<script src=”//html5shim.googlecode.com/svn/trunk/html5.js”></script>
<![endif]–>
<script src=”//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js”></script>
{% bootstrap_javascript_tag %}
{% block extra_head %}{% endblock %}
</head>
<body>
<h2>修改密码</h2>
{% if oldpassword_is_wrong %}
<div class=”alert alert-error”>
<button type=”button” class=”close” data-dismiss=”alert”>×</button>
<h4>错误!</h4>原密码不正确
</div>
{% endif %}
<div class=”well”>
<form class=”form-horizontal” action=”” method=”post”>
{% csrf_token %}
{{ form|as_bootstrap:”horizontal” }}
<p class=”form-actions”>
<input type=”submit” value=”确认修改” class=”btn btn-primary”>
</p>
</form>
</div>
</body>
</html>
在urls.py添加如下:
(r’^accounts/changepwd/$’, ‘dbrelease_app.views.changepwd’),
最终效果如下:
1)修改密码的登录框如下所示:
2)上述三个域都为必填项,只要有一个为空,就提示“所有项为必填项”的错误信息:
3)如果两次输入的新密码不同,提示“两次输入的新密码不一样”的错误信息:
4)如果原密码错误,提示“原密码错误”的错误信息:
如果所有的信息都填写正确,则修改密码成功,返回主页。
至此,我们的用户认证模块已经实现完毕。有些细心的读者可能已经发现,我们没有实现用户注册,这是因为我的这个系统是个公司内部员工使用的系统,实名绑定,不允许注册。当然,如果你要实现注册,也是非常简单的,这里就不再累述。
前面我们实现了用户认证模块,在该模块里其实已经涉及到模块(ORM),但由于是Django自带的,大部分它都已经帮我们处理好,对于我们来说是透明的,本篇,我们将自己定义模型。
在Web开发中,我习惯从数据库层面开始,一方面是因为我本身是做DBA的,对数据库比较了解;另一部分也是因为相比于模板和视图来说,模型从设计之初基本已确定,不会有大的改动。
我们的模型models.py定义如下:
from django.db import models
from django.contrib.auth.models import User
class Manager(models.Model):
id = models.AutoField(primary_key=True)
username = models.CharField(max_length=30)
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
email = models.EmailField(max_length=70)
def __unicode__(self):
return self.last_name + self.first_name
class Dba(models.Model):
id = models.AutoField(primary_key=True)
username = models.CharField(max_length=30)
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
email = models.EmailField(max_length=70)
def __unicode__(self):
return self.last_name + self.first_name
class State(models.Model):
id = models.AutoField(primary_key=True)
statename = models.CharField(max_length=20)
def __unicode__(self):
return self.statename
class Database(models.Model):
id = models.AutoField(primary_key=True)
databasename = models.CharField(max_length=30)
databaseip = models.CharField(max_length=30)
def __unicode__(self):
return self.databasename + ‘ – ‘ + self.databaseip
class Task(models.Model):
id = models.AutoField(primary_key=True)
creater = models.ForeignKey(User)
manager = models.ForeignKey(Manager)
dba = models.ForeignKey(Dba)
state = models.ForeignKey(State)
databases = models.ManyToManyField(Database)
sql = models.CharField(max_length=2000,blank=True,null=True)
desc = models.CharField(max_length=2000,blank=True, null=True)
createdtime = models.DateTimeField()
lastupdatedtime = models.DateTimeField(blank=True,null=True)
dbacomment = models.CharField(max_length=2000,blank=True,null=True)
attachment = models.FileField(upload_to=’tasks’,blank=True,null=True)
def __unicode__(self):
return str(self.id)
其中最核心的表是最后一张Task,在业务逻辑上代表开发人员提交给DBA的一个数据库更改请求,其它所有表都是围绕这个表创建的:Manager表是Task.manager的外键,Dba表是Task.dba的外键,State表是Task.state的外键,其中比较特殊的是Task.databases和Database表是多对多的关系。还有一点要注意的是,Task.creater是Django自带的用户表auth_user的外键。
以上models.py定义了ORM关系,但当前这些表只存在models.py中,在数据库中还不存在,我们需要通过以下步骤同步数据库:
首先,通过以下语句验证models.py定义模型的正确性:
python manage.py validate
如果上面没有错误,就可以执行如下语句同步数据库了(在同步之前,一定要先确认settings.py中DATABASES属性已正确配置,具体看第3篇):
python manage.py syncdb
如果你想知道syncdb究竟在数据库里做了什么,可以执行如下语句获得DDL:
python manage.py sqlall app_name
至此,模型已经创建成功,之后我们就可以在Django中自由的操纵数据库了。
在上一篇中,我们自己定义了数据库模型(models.py),并已同步至数据库中,那么如何插入、更新、删除表中数据呢?方法有很多,比如直接写SQL语句在数据库层面插入或调用模型的API,但这两种方法都需要写代码实现,可不可以有更直观页面操作呢?答案当然是肯定的,这就是Django自带的Admin模块,本篇主要讲述它。
首先,我们需要通过以下步骤开启Admin模块:
第一步:对settings.py做如下修改:
1)保证INSTALLED_APPS中包含’django.contrib.auth’,’django.contrib.contenttypes’和’django.contrib.sessions’(因为我们之前已经使用了Django自动的用户认证系统,因此这三个包应该就在开启的状态)
2)在INSTALLED_APPS中把’django.contrib.admin’和’django.contrib.admindocs’的注释去掉
3)确保MIDDLEWARE_CLASSES 包含’django.middleware.common.CommonMiddleware’ 、’django.contrib.sessions.middleware.SessionMiddleware’ 和’django.contrib.auth.middleware.AuthenticationMiddleware’
第二步:运行 python manage.py syncdb 。这一步将生成管理界面使用的额外数据库表
第三步:将admin访问配置在URLconf(记住,在urls.py中). 默认情况下,命令django-admin.py startproject生成的文件urls.py是将Django admin的路径注释掉的,你所要做的就是取消注释。如下所示:
# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns(”,
# Examples:
# url(r’^$’, ‘dbrelease.views.home’, name=’home’),
# url(r’^dbrelease/’, include(‘dbrelease.foo.urls’)),
# Uncomment the admin/doc line below to enable admin documentation:
url(r’^admin/doc/’, include(‘django.contrib.admindocs.urls’)),
# Uncomment the next line to enable the admin:
url(r’^admin/’, include(admin.site.urls)),
…
)
第四步:创建admin.py文件,内容如下:
from django.contrib import admin
from .models import *
admin.site.register(Manager)
admin.site.register(Dba)
admin.site.register(State)
admin.site.register(Database)
admin.site.register(Task)
当这一切都配置完毕之后,在浏览器中输入http://192.168.1.16:8000/admin/,登陆成功后就可以使用Admin管理工具了,界面如下:
从以上页面我们发现,除了我们自己定义的模型(Dbrelease_app下的表)外,还有我们之前讲过的Django自带的用户认证模块(Auth)和(Sites)。
当用户登录成功后,首先看到的是他自己之前提交的任务列表,本篇将实现该页面。
视图(views.py)里定义如下:
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
@login_required
def tasklist(request):
username=request.user.username
if len(Dba.objects.filter(username=username)) == 0: #User is not DBA, only shows his/her own tasklist
userid=User.objects.filter(username=username)
lines = Task.objects.filter(creater=userid).order_by(“-id”)
else: #User is DBA, shows all tasklist
lines = Task.objects.order_by(“-id”)
paginator = Paginator(lines, 10)
page = request.GET.get(‘page’)
try:
show_lines = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
show_lines = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
show_lines = paginator.page(paginator.num_pages)
return render_to_response(‘tasklist.html’, RequestContext(request, {‘lines’: show_lines,}))
该视图的业务逻辑如下:
1)判断用户名,如果是普通用户,只显示他自己的任务列表;如果是DBA,则显示所有用户的任务列表。(从Task模型中获取数据)
2)调用Bootstrap的分页器Paginator,每页显示10行。
最终,该视图返回模板tasklist.html,其定义如下:
{% extends “base.html” %}
{% load bootstrap_toolkit %}
{% block content %}
<h1>任务列表</h1>
<table class=”table table-striped table-hover”>
<tr>
<td><strong>ID</strong></td>
<td><strong>提交者</strong></td>
<td><strong>创建时间</strong></td>
<td><strong>最后更新时间</strong></td>
<td><strong>状态</strong></td>
</tr>
{% for line in lines %}
<tr>
<td><a href=”{%%20url%20’dbrelease_app.views.taskdetail’%20%}?taskid={{%20line.id%20}}”>{{ line.id }}</a></td>
<td><a href=”{%%20url%20’dbrelease_app.views.taskdetail’%20%}?taskid={{%20line.id%20}}”>{{ line.creater.last_name }}{{ line.creater.first_name }}</a></td>
<td><a href=”{%%20url%20’dbrelease_app.views.taskdetail’%20%}?taskid={{%20line.id%20}}”>{{ line.createdtime|date:”Y-m-d H:i:s” }}</a></td>
<td><a href=”{%%20url%20’dbrelease_app.views.taskdetail’%20%}?taskid={{%20line.id%20}}”>{{ line.lastupdatedtime|date:”Y-m-d H:i:s” }}</a></td>
<td><a href=”{%%20url%20’dbrelease_app.views.taskdetail’%20%}?taskid={{%20line.id%20}}”>{{ line.state }}</a></td>
</tr>
</a>
{% endfor %}
</table>
{% bootstrap_pagination lines url=”/tasklist?page=1″ align=”center” size=”large” %}
{% endblock %}
上面的模板定义了5列(分别是ID、提交者、创建时间、最后更新时间和状态),每行上都加一个超链接,指向该Task的详细信息(对应的视图taskdetail,我们会在下一章讲述该视图)。
最终效果如下:
本章通过实现一个用户提交任务请求的页面,讲述表单、视图、模型、模板间的交互。
首先,我们需要定义一个表单(forms.py)
class CreatetaskForm(forms.Form):
creater = forms.CharField(
label=u”创建者”,
widget=BootstrapUneditableInput()
)
manager = forms.ModelChoiceField(
queryset=Manager.objects.all(),
required=True,
label=u”项目负责人”,
error_messages={‘required’: u’必选项’},
)
databases = forms.ModelMultipleChoiceField(
queryset=Database.objects.order_by(‘id’),
required=True,
label=u”数据库”,
error_messages={‘required’: u’至少选择一个’},
widget=forms.CheckboxSelectMultiple,
)
sql = forms.CharField(
required=False,
label=u”执行SQL”,
widget=forms.Textarea(
attrs={
‘placeholder’:”请在表名前加上schema,如hospital要写成p95169.hospital”,
‘rows’:5,
‘style’:”width:100%”,
}
),
)
desc = forms.CharField(
required=False,
label=u”描述”,
widget=forms.Textarea(
attrs={
‘placeholder’:”如果不是执行SQL(如数据的导入导出等),一定要在描述里说清楚”,
‘rows’:5,
‘style’:”width:100%”,
}
),
)
attachment = forms.FileField(
required=False,
label=u”附件”,
help_text=u”如果SQL文本过长,超过2000个字符,请上传附件”
)
def clean(self):
if not self.is_valid():
raise forms.ValidationError(u”以下红色标记部分为必选项”)
elif self.cleaned_data[‘sql’] == u” and self.cleaned_data[‘desc’] == u” :
raise forms.ValidationError(u”如果执行SQL为空,描述为必填项”)
else:
cleaned_data = super(CreatetaskForm, self).clean()
return cleaned_data
该表单包含一个多选框和附件上传,然后我们创建一个视图来实例化该表单:
import datetime
@login_required
def createtask(request):
if request.method == ‘GET’:
form = CreatetaskForm(initial={
‘creater’:request.user.last_name + request.user.first_name,
})
return render_to_response(‘createtask.html’, RequestContext(request, {‘form’: form,}))
else:
form = CreatetaskForm(request.POST,request.FILES)
if form.is_valid():
username = request.user.username
t = Task.objects.create(
creater = User.objects.get(username=username),
manager = form.cleaned_data[‘manager’],
dba = Dba.objects.get(id=1),
state = State.objects.get(statename=’Open’),
sql = form.cleaned_data[‘sql’],
desc = form.cleaned_data[‘desc’],
createdtime = datetime.datetime.now(),
lastupdatedtime = datetime.datetime.now(),
attachment = form.cleaned_data[‘attachment’],
)
databaselist = form.cleaned_data[‘databases’]
for db in databaselist:
t.databases.add(db)
t.save()
return render_to_response(‘base.html’, RequestContext(request,{‘createtask_success’:True,}))
else:
return render_to_response(‘createtask.html’, RequestContext(request, {‘form’: form,}))
该视图的业务逻辑是:
1)当用户提交表单后,验证表单的正确性
2)如果表单填写不正确,显示错误提示信息
3)如果表单填写正确,在数据库表Task里插入一条信息
其中,模型createtask.html的定义如下:
{% extends “base.html” %}
{% load bootstrap_toolkit %}
{% block extra_head %}
{{ form.media }}
{% endblock %}
{% block content %}
<h1>创建任务</h1>
<div class=”well”>
<form enctype=”multipart/form-data” class=”form-horizontal” action=”” method=”post”>
{% csrf_token %}
{{ form|as_bootstrap:”horizontal” }}
<p class=”form-actions”>
<input type=”submit” value=”提交” class=”btn btn-primary”>
<a href=”/tasklist/”><input type=”button” value=”返回” class=”btn”></a>
</p>
</form>
</div>
{% endblock %}
最终的效果如下所示:
如果用户没有输入足够的信息,将提示错误信息如下:
如果用户所有的信息都填正确,则提交成功,提示成功信息:
点击OK,返回任务列表,排在第1的就是刚刚创建的任务: