Stark 组件:快速开发神器 —— 页面显示

在这里插入图片描述
说道 Stark 你是不是不会想到他——Tony Stark,超级英雄钢铁侠,这也是我的偶像。

不过我们今天要开发的 Stark 组件,倒是跟他的人工智能助手 JARVIS 有些类似,是帮助我们快速开发数据库增、删、改、查操作、应用各种功能的开发助手。

一、数据

在敲代码之前,通过 admin 来创建一些基本数据好供我们显示。

Role

在这里插入图片描述

Department

在这里插入图片描述

Team

在这里插入图片描述

RbacUserInfo

在这里插入图片描述

二、查

查看信息是第一位,因此我们先来做显示页面,这里要想一个问题,显示信息,并不能吧数据库表的所有字段都显示,那岂不是密码都放到页面上来了,因此,我们需要定制显示的字段,在不同的类中定义 displayList 属性表示显示字段。

在 StarkHandler 类中编写查看视图:

# Stark/main.py
	def 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.py
	def getDisplayList(self, request, *args, **kwargs):
		"""获取页面显示的表格,预留自定义扩展定制显示内容"""
		value = []
		if self.displayList:
			value.extend(self.displayList)
		return value

在 RbacUserHandler 类中定义要显示的字段:

# RBAC/views/rbacUserinfo.py
from 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.py
from 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.py
	def 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.py
def 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.py
def 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 StarkHandler


class 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.py
	def 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.py
	def 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.py
	def 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.py
	def 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 组件的基本增删改查和页面显示也就做完了,后续我们再给它添加更多的功能。

发布了726 篇原创文章 · 获赞 402 · 访问量 31万+

猜你喜欢

转载自blog.csdn.net/weixin_43336281/article/details/105298241