说道 Stark 你是不是不会想到他——Tony Stark,超级英雄钢铁侠,这也是我的偶像。
不过我们今天要开发的 Stark 组件,倒是跟他的人工智能助手 JARVIS 有些类似,是帮助我们快速开发数据库增、删、改、查操作、应用各种功能的开发助手。
Stark 组件:快速开发神器 —— 页面显示
- 一、数据
- Role
- Department
- Team
- RbacUserInfo
- 二、查
- ManyToManyField 处理
- DateTimeField 处理
- 三、增
- IntegerField 处理
- 四、改
- 五、删
一、数据
在敲代码之前,通过 admin 来创建一些基本数据好供我们显示。
Role
Department
Team
RbacUserInfo
二、查
查看信息是第一位,因此我们先来做显示页面,这里要想一个问题,显示信息,并不能吧数据库表的所有字段都显示,那岂不是密码都放到页面上来了,因此,我们需要定制显示的字段,在不同的类中定义 displayList 属性表示显示字段。
在 StarkHandler 类中编写查看视图:
# Stark/main.pydef checkView(self, request, *args, **kwargs):
"""查看功能视图函数"""
# --------------------- 1.显示表格 ---------------------
displayList = self.getDisplayList(request, *args, **kwargs)
# 1.1、处理表格表头
headerList = []
if displayList:
for item in displayList:
verboseName = self.model._meta.get_field(item).verbose_name
headerList.append(verboseName)
else:
headerList.append(self.model._meta.model_name)
# 1.2、处理表格内容
bodyList = []
dataList = self.model.objects.all()
for row in dataList:
rowList = []
if displayList:
for item in displayList:
rowList.append(getattr(row, item))
else:
rowList.append(row)
bodyList.append(rowList)
return render(request, "stark/checkView.html", {
"headerList": headerList,
"bodyList": bodyList,
"dataList": dataList
})
getDisplayList 方法就是用于获取要显示的字段:
# Stark/main.pydef getDisplayList(self, request, *args, **kwargs):
"""获取页面显示的表格,预留自定义扩展定制显示内容"""
value = []
if self.displayList:
value.extend(self.displayList)
return value
在 RbacUserHandler 类中定义要显示的字段:
# RBAC/views/rbacUserinfo.pyfrom Stark.main import StarkHandler
class RbacUserHandler(StarkHandler):
def __init__(self, site, modelClass, prefix):
super().__init__(site, modelClass, prefix)
self.displayList = ["username", "email", "score", "grade", "roles", "team", "department", "dateJoined"]
继承 formwork.html 编写 check 页面:
{% extends 'formwork.html' %}{% block content %}
<div class="container" style="width: 100%; background-color: rgba(245, 245, 245, 0.7)">
<form method="post">
{% csrf_token %}
<table class="table table-hover table-bordered table-striped">
<thead>
<tr class="info">
{% for header in headerList %}
<th>{{ header }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in bodyList %}
<tr class="default">
{% for element in row %}
<td>{{ element }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</form>
</div>
{% endblock %}
此时访问:http://127.0.0.1:7777/stark/RBAC/rbacuserinfo/check/
ManyToManyField 处理
基本的信息已经可以浏览了,但是职位这一栏有点问题,显示的貌似是一个对象,后边还跟了个 None,看一下数据库表结构可以发现,只有 roles 是 ManyToManyField,估计是多对多把 Stark 给整懵了,我们来给它定义一个处理多对多关系的方法,因为这是一个通用的方法,可以直接写到 Stark/main.py 中。
我们先给 roles 字段套上这么一个函数:
# RBAC/views/rbacUserinfo.pyfrom Stark.main import StarkHandler, getM2MText
class RbacUserHandler(StarkHandler):
def __init__(self, site, modelClass, prefix):
super().__init__(site, modelClass, prefix)
self.displayList = ["username", "email", "score", "grade", getM2MText("职务", "roles"), "team", "department",
"dateJoined"]
显示表格的时候是要通过 checkView 统一处理的,因此要对 checkView 做一些更改:
# Stark/main.pydef checkView(self, request, *args, **kwargs):
"""查看功能视图函数"""
# --------------------- 1.显示表格 ---------------------
displayList = self.getDisplayList(request, *args, **kwargs)
# 1.1、处理表格表头
headerList = []
if displayList:
for item in displayList:
verboseName = item(self, obj=None, isHeader=True) if isinstance(item, FunctionType) \
else self.model._meta.get_field(item).verbose_name
headerList.append(verboseName)
else:
headerList.append(self.model._meta.model_name)
# 1.2、处理表格内容
bodyList = []
dataList = self.model.objects.all()
for row in dataList:
rowList = []
if displayList:
for item in displayList:
rowList.append(item(self, obj=row, isHeader=False, *args, **kwargs) if isinstance(item, FunctionType)
else getattr(row, item))
else:
rowList.append(row)
bodyList.append(rowList)
最后编写一个 getM2MText 函数:
# Stark/main.pydef getM2MText(header, field):
"""定义显示 ManyToManyField 字段的函数"""
def inner(self, obj=None, isHeader=None, *args, **kwargs):
return header if isHeader else ",".join([str(item) for item in getattr(obj, field).all()])
return inner
这样职务就能正常显示了:
DateTimeField 处理
但是我现在有觉得那个加入时间显示的比较别扭,想让它按照我指定的格式显示。
处理方法跟 ManyToManyField 类似,也是定义一个函数:
# Stark/main.pydef getDatetime(header, field, timeFormat="%Y-%m-%d"):
"""显示 DateTimeField 字段的函数"""
def inner(self, obj=None, isHeader=None, *args, **kwargs):
return header if isHeader else getattr(obj, field).strftime(timeFormat)
return inner
然后将 displayList 中的"dateJoined" 改为 getDatetime("加入时间", "dateJoined")。
不仅如此,相应的 Team 表和 Department 表也可以显示了:
from Stark.main import StarkHandlerclass TeamHandler(StarkHandler):
def __init__(self, site, modelClass, prefix):
super().__init__(site, modelClass, prefix)
self.displayList = ["name", "introduce"]from Stark.main import StarkHandler
class DepartmentHandler(StarkHandler):
def __init__(self, site, modelClass, prefix):
super().__init__(site, modelClass, prefix)
self.displayList = ["name", "duty"]
通过定义不同数据库表的处理类的 displayList 属性,就可以快速的在页面上显示列表信息。
三、增
增加功能对于项目来说不可或缺,所以我们需要在基类中定制一个通用的添加按钮。
首先要在 checkView 中获取添加按钮,如果能获取到说明此页面允许添加按钮,如果不能获取到说明不需要添加功能,默认都是可以获取到。
# Stark/main.pydef checkView(self, request, *args, **kwargs):
"""查看功能视图函数"""
# --------------------- 1.显示表格 ---------------------
displayList = self.getDisplayList(request, *args, **kwargs)
# 1.1、处理表格表头
headerList = []
if displayList:
for item in displayList:
verboseName = item(self, obj=None, isHeader=True) if isinstance(item, FunctionType) \
else self.model._meta.get_field(item).verbose_name
headerList.append(verboseName)
else:
headerList.append(self.model._meta.model_name)
# 1.2、处理表格内容
bodyList = []
dataList = self.model.objects.all()
for row in dataList:
rowList = []
if displayList:
for item in displayList:
rowList.append(item(self, obj=row, isHeader=False, *args, **kwargs) if isinstance(item, FunctionType)
else getattr(row, item))
else:
rowList.append(row)
bodyList.append(rowList)
# --------------------- 2.添加按钮 ---------------------
addButton = self.getAddButton(request, *args, **kwargs)
return render(request, "stark/checkView.html", {
"headerList": headerList,
"bodyList": bodyList,
"dataList": dataList,
"addButton": addButton,
})
getAddButton 就是用于获取添加按钮的方法,如果 self.hasAddButton 为 True 则返回添加按钮的 HTML 代码,默认就是设置为 True:
# Stark/main.pydef getAddButton(self, request, *args, **kwargs):
"""如果 self.hasAddButton 为 True,在页面显示一个添加按钮"""
return "<a class='btn btn-success' target='_blank' href='%s'>添加</a>" % self.reverseAddUrl(*args, **kwargs) \
if self.hasAddButton else None
self.reverseAddUrl 是生成添加页面的 URL,如果在 checkView 页面带有一些初始搜索条件应该被保留:
# Stark/main.pydef reverseUrl(self, urlName, *args, **kwargs):
"""生成带有原搜索条件的 URL"""
name = "%s:%s" % (self.site.namespace, urlName)
baseUrl = reverse(name, args=args, kwargs=kwargs)
if self.request.GET:
newQueryDict = QueryDict(mutable=True) # mutable 可变类型
newQueryDict["_filter"] = self.request.GET.urlencode()
url = "%s?%s" % (baseUrl, newQueryDict.urlencode())
else:
url = baseUrl
return url
def reverseAddUrl(self, *args, **kwargs):
"""生成带有原搜索条件的添加 URL"""
return self.reverseUrl(self.addUrlName, *args, **kwargs)
现在我们已经在页面生成了一个添加按钮,并且给了它一个跳转链接,接下来就得编写相应的视图函数来处理它:
# Stark/main.pydef addView(self, request, *args, **kwargs):
"""添加功能视图函数"""
form = None
addModelForm = self.getModelForm(isAdd=True, request=request, pk=None, *args, **kwargs)
if request.method == "GET":
form = addModelForm()
elif request.method == "POST":
form = addModelForm(data=request.POST)
if form.is_valid():
return self.save(request=request, form=form, isUpdate=False, *args, **kwargs) \
or redirect(self.reverseListUrl(*args, **kwargs))
return render(request, self.addTemplate or "stark/addOrChange.html", {"form": form})
getModelForm 方法是为了获取数据库表相应页面的 model form,方便地将数据库表的字段显示在页面上:
def getModelForm(self, isAdd, request, pk, *args, **kwargs):"""添加和修改页面的 model form 定制"""
class DynamicModelForm(StarkModelForm):
class Meta:
model = self.model
fields = "__all__"
# 如果有自定义的 self.modelForm 则用自定义的,否则返回通用的
return self.modelForm if self.modelForm else DynamicModelForm
在这里我们也预留一个扩展功能,如果添加页面要显示的字段需要定制,可以通过自定义 self.modelForm 来实现。
而 StarkModelForm 是为了给所有的显示字典添加一个默认样式,看起来更加美观:
class StarkModelForm(forms.ModelForm):"""统一给 ModelForm 生成字段添加样式"""
def __init__(self, *args, **kwargs):
super(StarkModelForm, self).__init__(*args, **kwargs)
for name, field in self.fields.items():
field.widget.attrs['class'] = 'form-control'
self.save 是为了保存添加的数据到数据库中的方法,如果有的页面想在保存到数据库中做一些操作,我们可以吧 save 方法预留出来:
def save(self, request, form, isUpdate, *args, **kwargs):"""在使用 ModelForm 保存数据之前预留扩展方法"""
form.save()
现在对于添加功能老说是万事俱备只欠东风了,也就是还没有编写添加页面,在写添加页面的代码之前,考虑一个问题,添加页面和修改页面的显示效果其实差不多,只不过是添加页面没有默认显示字段而修改页面要显示默认信息,因此我们可以编写一套通用的模板:
{% extends 'formwork.html' %}{% block content %}
<div class="container">
<form class="form-horizontal" method="post" novalidate>
{% csrf_token %}
{% for field in form %}
<div class="form-group">
<label class="col-sm-2 control-label">{{ field.label }}</label>
<div class="col-sm-7">
{{ field }}
<span style="color: red;">{{ field.errors.0 }}</span>
</div>
</div>
{% endfor %}
<div class="form-group">
<div class="col-sm-offset-2 col-sm-8">
<input type="submit" value="保 存" class="btn btn-primary">
</div>
</div>
</form>
</div>
{% endblock %}
这样,我们的添加功能就完成了:
IntegerField 处理
这一跳回来又发现了一个问题,等级那里显示的有问题,按道理来说应该显示的 M1,而不是 1,这个是 IntegerField 字段的选项问题,对于选项我们也得编写一个通用的方法处理。
def getChoice(header, field):"""显示选项字段的中文信息"""
def inner(self, obj=None, isHeader=None, *args, **kwargs):
return header if isHeader else getattr(obj, "get_%s_display" % field)()
return inner
这样选项字段就能够正常显示了:
四、改
如果要修改的话,必须拿到相应的 ID,才能针对具体的某一条记录做修改,因此我们可以在表格的最后一列加上一个修改按钮,一行一个对应其 ID。
首先是仿照 getAddButton 编写一个 getChangeButton:
def getChangeButton(self, obj=None, isHeader=None, *args, **kwargs):"""编辑按钮"""
return "操作" if isHeader else mark_safe(
"<a class='btn btn-warning' target='_blank' href='%s'>编辑</a>" % self.reverseChangeUrl(pk=obj.pk))
然后我们把 getDisplayList 扩展一下:
def getDisplayList(self, request, *args, **kwargs):"""获取页面显示的表格,预留自定义扩展定制显示内容"""
value = []
if self.displayList:
value.extend(self.displayList)
value.append(type(self).getChangeButton)
return value
这样就能在页面上显示一个编辑按钮了:
接下来要编写 changeView 视图函数:
def changeView(self, request, pk, *args, **kwargs):"""修改功能视图函数"""
form = None
currentChangeObject = self.model.objects.filter(pk=pk).first()
if not currentChangeObject:
return render(request, "404.html")
modelForm = self.getModelForm(isAdd=False, request=request, pk=pk, *args, **kwargs)
if request.method == "GET":
form = modelForm(instance=currentChangeObject)
elif request.method == "POST":
form = modelForm(data=request.POST, instance=currentChangeObject)
if form.is_valid():
return self.save(request=request, form=form, isUpdate=True, *args, **kwargs) \
or redirect(self.reverseListUrl(*args, **kwargs))
return render(request, self.changeTemplate or "stark/addOrChange.html", {"form": form})
如果当前请求修改的记录不存在,返回一个404页面,因此我们还得搞一个404:
{% load static %}<!DOCTYPE html>
<html class="fixed" lang="en">
<head>
<meta charset="UTF-8">
<meta name="keywords" content="HTML5 Admin Template"/>
<meta name="description" content="Porto Admin - Responsive HTML5 Template">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<link rel="stylesheet" href="/static/vendor/bootstrap-3.3.7-dist/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/vendor/font-awesome-4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="{% static 'RBAC/css/theme.css' %}">
<link rel="stylesheet" href="{% static 'RBAC/css/default.css' %}">
<title>404 Not Found</title>
</head>
<body>
<section class="body-error error-outside">
<div class="center-error">
<div class="error-header">
<div class="row">
<div class="col-md-12">
<div class="row">
<div class="col-md-8">
<a href="/" class="logo">
<img src="/static/images/MatrixLogo.png" style="height: 54px;" alt="Porto Admin"/>
</a>
</div>
<div class="col-md-4">
<form class="form">
<div class="input-group input-search">
<label for="q"></label>
<input type="text" class="form-control" name="q" id="q" placeholder="Search...">
<span class="input-group-btn">
<button class="btn btn-default" type="submit">
<i class="fa fa-search"></i>
</button>
</span>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-8">
<div class="main-error mb-xlg">
<h2 class="error-code text-dark text-center text-semibold m-none">404 <i class="fa fa-file"></i>
</h2>
<p class="error-explanation text-center">
We're sorry, but the page you were looking for doesn't exist.
</p>
</div>
</div>
<div class="col-md-4">
<h4 class="text">Here are some useful links</h4>
<ul class="nav nav-list primary">
<li>
<a href="{% url 'index' %}"><i class="fa fa-caret-right text-dark"></i> Dashboard </a>
</li>
<li>
<a href="#"><i class="fa fa-caret-right text-dark"></i> User Profile </a>
</li>
<li>
<a href="#"><i class="fa fa-caret-right text-dark"></i> FAQ's </a>
</li>
</ul>
</div>
</div>
</div>
</section>
</body>
</html>
效果如下:
当然这个页面仅供参考,鼓励自己实现。
如果存在的话,通过 getModelForm 获取一个 modelForm,instance放当前请求的对象,这样就能将其原始信息显示出来了。
如果是 POST 请求的话,则是前端发送过来的修改完的数据,验证合法后存储,然后返回列表页面。
这样,我们的修改功能就完成了:
五、删
删除流程就跟修改差不多了,但这里我们就要考虑一个问题了,有些页面可能只需要修改按钮,而有些页面又只需要删除按钮,还有的页面两个按钮都需要,我们除了编写一个单独的删除按钮之外,还要编写一个同时具有修改和删除按钮的方法:
def getDeleteButton(self, obj=None, isHeader=None, *args, **kwargs):"""删除按钮"""
return "操作" if isHeader else mark_safe(
"<a class='btn btn-daner' target='_blank' href='%s'>删除</a>" % self.reverseDeleteUrl(pk=obj.pk))
def getChangeAndDeleteButton(self, obj=None, isHeader=None, *args, **kwargs):
"""编辑和删除按钮"""
return "操作" if isHeader else mark_safe(
"<a class='btn btn-warning' target='_blank' href='%s'>编辑</a> "
"<a class='btn btn-danger' target='_blank' href='%s'>删除</a>" % (
self.reverseChangeUrl(pk=obj.pk), self.reverseDeleteUrl(pk=obj.pk)))
然后对 getDisplayList 再做一些修改:
def getDisplayList(self, request, *args, **kwargs):"""获取页面显示的表格,预留自定义扩展定制显示内容"""
value = []
if self.displayList:
value.extend(self.displayList)
value.append(type(self).getChangeAndDeleteButton)
return value
这样就可以在页面上显示出编辑和删除两个按钮了:
最后编写删除功能的视图函数:
def deleteView(self, request, pk, *args, **kwargs):"""删除功能视图函数"""
baseUrl = self.reverseListUrl(*args, **kwargs)
if request.method == "GET":
return render(request, self.deleteTemplate or "stark/delete.html", {"baseUrl": baseUrl})
response = self.model.objects.filter(pk=pk).delete()
return redirect(baseUrl) or HttpResponse(response)
如此这般,删除功能就实现了:
好了,截止目前,Stark 组件的基本增删改查和页面显示也就做完了,后续我们再给它添加更多的功能。