页面上展示数据表的表头
我们注册了UserInfo表之后,在视图函数change_list_view中执行data_list = self.model_class.objects.all()
就可以拿到该表中的所有数据,但是页面上还欠缺一个表头信息,通过verbose_name = self.model_class._meta.get_field('name').verbose_name
拿到字段对应的Field对象再拿到我们设置上去的verbose_name的值。
多个模型类下的字段是不相同的,所以要展示那些字段我们交给使用者来决定,在class UserInfoHandle(StarkHandle):
类中给定页面中要展示的字段内容。
代码:
class StarkHandle(object):
"""
处理请求的视图函数所在的类,公共类
"""
display_list = [] # 定义默认的display_list,用户没有继承此类并重写这个值,将默认不会展示任何字段
def __init__(self, model_class, prev):
self.model_class = model_class
self.prev = prev
def change_list_view(self, request):
header_list = []
for key in self.display_list: # 根据继承的特性,用户继承StarkHandle类,重写display_list值,并且注册,此时self的对象就是用户自定义继承StarkHandlr的类,会访问用户定义的display_list值。
verbose_name = self.model_class._meta.get_field(key).verbose_name
header_list.append(verbose_name)
print(header_list)
data_list = self.model_class.objects.all()
print(data_list)
return render(request, 'stark/change_list.html', {'header_list': header_list})
change_list.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<table>
<thead>
<td>
{% for header in header_list %}
<th>{{ header }}</th>
{% endfor %}
</td>
</thead>
</table>
</body>
</html>
注册UserInfo表:
class UserInfoHandle(StarkHandle):
display_list = ["name", "age", "mail", "depart"]
site.register(UserInfo, UserInfoHandle)
效果:
减少UserInfoHandle中的display内容,展示的信息对应减少
class UserInfoHandle(StarkHandle):
display_list = ["name", "age", "mail"]
site.register(UserInfo, UserInfoHandle)
注册Depart表
class DepartHandle(StarkHandle):
display_list = ['id', 'title']
site.register(Depart, DepartHandle)
数据表中数据的展示
通过数据库查找拿到的数据,不能直接在html中使用,因为我们在html中无法使用反射获取字段的数据,所以在后端要构造好数据再传递到html中使用。
代码
class StarkHandle(object):
def change_list_view(self, request):
header_list = []
for key in self.display_list:
verbose_name = self.model_class._meta.get_field(key).verbose_name
header_list.append(verbose_name)
data_list = self.model_class.objects.all()
body_list = [] # 构造出给前端使用的数据结构,包含该表中那个的所有数据
"""
body_list = [
['胡说', '17', 'hushuo@qq.com'], # 数据表中的一行数据
['呼哈', '32', 'huha@qq.com']
]
"""
for item in data_list:
# 构造一行数据
row_list = []
for key in self.display_list:
row_list.append(getattr(item, key)) # 拿到每行数据的字段内容
body_list.append(row_list)
return render(request, 'stark/change_list.html', {'header_list': header_list, 'body_list': body_list})
change_list.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<table border="1">
<thead>
<tr>
{% for header in header_list %}
<th>{{ header }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in body_list %}
<tr>
{% for ele in row %}
<td>{{ ele }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
用户可以通过自定义的类中修改display_list的内容来控制前端展示的字段内容,如不想展示年龄就可以设置为display_list = ["name", "mail"]
列表页面的默认展示
此时我们代码逻辑是,用户必须继承StarkHandle类,并在自定义类下按照表中想展示的字段的值给dislpay_list设置值。我们有些时候就不想重写这个类,就直接使用StarkHandle,就需要添加默认的显示。
默认显示的思路是:表头是当前表的表名,每一行展示的是该表对应模型类的对象。
代码:
class StarkHandle(object):
def change_list_view(self, request):
header_list = []
if self.display_list:
for key in self.display_list:
verbose_name = self.model_class._meta.get_field(key).verbose_name
header_list.append(verbose_name)
else:
# 用户没有继承这个类和重新给display_list赋值,使用当前类中的display_list
# 表头信息为该表名称
header_list.append(self.model_class._meta.model_name)
data_list = self.model_class.objects.all()
body_list = [] # 构造出给前端使用的数据结构,包含该表中那个的所有数据
for item in data_list:
# 构造一行数据
row_list = []
if self.display_list:
for key in self.display_list:
row_list.append(getattr(item, key))
else:
# 展示对象信息到页面
row_list.append(item)
body_list.append(row_list)
return render(request, 'stark/change_list.html', {'header_list': header_list, 'body_list': body_list})
使用默认的StarkHandle类
from stark.service.v1 import site
site.register(Depart)
页面展示:
列表页面预留钩子方法(自定义扩展列显示)重要
继承了StarkHandle类后,可以定义display_list手动修改器值来决定列表页面显示哪些列的内容,如果要根据当前登录的用户来决定展示哪些列,此时代码就满足不了需求了。
思路:在父类中定义一个get_display_list方法,在后续使用到self.dislpay_list获取展示列的地方都修改为调用get_dislpay_list方法,用户动态自定义哪些用户展示哪些列时就可以在继承的子类中重写该方法,来动态的获取展示列的信息(根据继承特性)
代码:
class StarkHandle(object):
"""
处理请求的视图函数所在的类,公共类
"""
display_list = []
def get_display_list(self):
"""
获取页面上应该显示的列,用户集成该类,可以做显示列的自定义扩展: 根据用户的不同显示不同的列
:return:
"""
value = [] # 扩展的显示列的列表
value.extend(self.display_list) # 将原有的display_list添加到扩展列表中
return value # 返回的是 扩展列+用户在display_list中写死的默认展示列
def change_list_view(self, request):
header_list = []
display_list = self.get_display_list() # 通过get_display_list方法拿到要展示的数据,如果用户继承此类并重写了此方法就会调用子类中的该方法,达到扩展的目的
if display_list:
for key in display_list:
verbose_name = self.model_class._meta.get_field(key).verbose_name
header_list.append(verbose_name)
else:
# 用户没有继承这个类和重新给display_list赋值,使用当前类中的display_list
# 表头信息为该表名称
header_list.append(self.model_class._meta.model_name)
data_list = self.model_class.objects.all()
body_list = [] # 构造出给前端使用的数据结构,包含该表中那个的所有数据
for item in data_list:
# 构造一行数据
row_list = []
if display_list:
for key in display_list:
row_list.append(getattr(item, key))
else:
# 展示对象信息到页面
row_list.append(item)
body_list.append(row_list)
return render(request, 'stark/change_list.html', {'header_list': header_list, 'body_list': body_list})
使用:
class UserInfoHandle(StarkHandle):
display_list = ["name", "age", "mail"] # 不重写get_display_list方法时,默认展示的列
def get_display_list(self):
# 动态返回要展示的列
return ['name', 'mail']
site.register(UserInfo, UserInfoHandle)
页面展示:
列表页面自定义函数扩展
在列表页面不仅也可显示数据库表的信息,还可以添加新的一列数据,数据内容由用户来决定,如在每行数据后添加删除的按钮。
新的一列内容是由用户定义的,设计思路是,用户定义一个函数,将函数添加到display_list中,在遍历display_list时,是字符串在数据库表中取值,是函数对象,拿到函数调用后的返回值作为页面内容的展示。用户将想显示的新一列的数据通过函数返回。
代码:
class StarkHandle(object):
"""
处理请求的视图函数所在的类,公共类
"""
display_list = []
def __init__(self, model_class, prev):
self.model_class = model_class
self.prev = prev
def get_display_list(self):
"""
获取页面上应该显示的列,用户集成该类,可以做显示列的自定义扩展: 根据用户的不同显示不同的列
:return:
"""
value = []
value.extend(self.display_list)
return value
def change_list_view(self, request):
header_list = []
display_list = self.get_display_list() # 通过get_display_list方法拿到要展示的数据,如果用户继承此类并重写了此方法就会调用子类中的该方法,达到扩展的目的
if display_list:
for key_or_func in display_list:
if isinstance(key_or_func, FunctionType):
# 是函数对象,调用该函数,将返回值作为表头内容
verbose_name = key_or_func(self, is_header=True)
else:
verbose_name = self.model_class._meta.get_field(key_or_func).verbose_name
header_list.append(verbose_name)
else:
# 用户没有继承这个类和重新给display_list赋值,使用当前类中的display_list
# 表头信息为该表名称
header_list.append(self.model_class._meta.model_name)
data_list = self.model_class.objects.all()
body_list = [] # 构造出给前端使用的数据结构,包含该表中那个的所有数据
for item in data_list:
# 构造一行数据
row_list = []
if display_list:
for key_or_func in display_list:
if isinstance(key_or_func, FunctionType):
# 是函数对象,将函数调用返回值作为该行的一个值
row_list.append(key_or_func(self, obj=item, is_header=False))
else:
row_list.append(getattr(item, key_or_func))
else:
# 展示对象信息到页面
row_list.append(item)
body_list.append(row_list)
return render(request, 'stark/change_list.html', {'header_list': header_list, 'body_list': body_list})
继承StarkHandle类
class UserInfoHandle(StarkHandle):
def display_edit(self, obj=None, is_header=None):
"""
自定义列表展示页的某一列数据,在列表页面展示除数据库表外的数据,如:编辑按钮
:param obj: 要操作的模型类实例化对象,一个模型类实例化对象对应的是数据库中的一行数据
:param is_header: 一列有表头和数据两种,用于判断是返回表格数据还是表头数据
:return:
"""
if is_header:
return "编辑表头"
# return obj.name # 返回想展示到页面的内容,用户自己定制
return mark_safe('<a href="https://www.baidu.com">编辑</a>')
display_list = ["name", "age", "mail", display_edit]
注册
site.register(UserInfo, UserInfoHandle)
页面展示:
新增的一列为编辑表头,用户可以通过自定义函数,并添加到display_list,根据函数的返回值来控制新增一列的内容
我们点击编辑会调转到百度页面,当然这只是一个测试效果,现在就把编辑的链接通过反向获取写入到a标签中。
1.后端的反向获取,先导入reverse函数from django.urls import reverse
2.我们在自动生成别名时,给设定了名称空间,其值在StarkSite类中定义
class StarkSite(object):
def __init__(self):
self._registry = []
self.name = 'stark'
self.namespace = 'stark'
3.在用户自定义的handle类中,我们要是用反向解析可以直接编写代码
class UserInfoHandle(StarkHandle):
def display_edit(self, obj=None, is_header=None):
if is_header:
return "编辑表头"
name = "{}:{}".format('stark', self.get_change_url_name)
return mark_safe('<a href="{}">编辑</a>'.format(reverse(name, args=(obj.pk,))))
display_list = ["name", "age", "mail", display_edit]
4.直接使用’stark’字符串,这种方式功能可以实现,但是是不可取的,当我们名称空间的值修改时,在用户自定义的handle类中‘stark’字符串都需要修改,不符合我们预期,我们希望的是只修改一处StarkSite类中的namesapce值,其他地方不需要改动。所以就要在用户自定义类中拿到StarkSite类中的元素,我们修改StarkSite的代码:
class StarkSite(object):
def __init__(self):
self._registry = []
self.name = 'stark'
self.namespace = 'stark'
def register(self, model_class, handle_class=StarkHandle, prev=None):
self._registry.append({'model_class': model_class, 'handle': handle_class(self, model_class, prev), 'prev': prev})
5.在用户注册时,将self传入到handle_class进行实例化对象,self就是StarkSite的实例化对象,在handle_class的类中修改代码:
class StarkHandle(object):
def __init__(self, site, model_class, prev):
self.site = site # 每个自定义继承此类的类中,就能拿到namespace的值
self.model_class = model_class
self.prev = prev
6.用户自定义handle类中的反向解析:
class UserInfoHandle(StarkHandle):
def display_edit(self, obj=None, is_header=None):
if is_header:
return "编辑表头"
# return obj.name
name = "{}:{}".format(self.site.namespace, self.get_change_url_name)
return mark_safe('<a href="{}">编辑</a>'.format(reverse(name, args=(obj.pk,))))
display_list = ["name", "age", "mail", display_edit]
点击编辑的页面展示:
列表页面内容补充
所有表的列表展示页面均添加修改和删除两列
现在有三张表:
- app01
- Depart
- UserInfo
- app02
- Host
需要为这三张表自定义继承了StarkHandle
的DepartHandle,UserInfoHandle,HostHandle
类,并在每个类下都添加相同的代码:
def display_edit(self, obj=None, is_header=None):
"""
自定义列表展示页的某一列数据,在列表页面展示除数据库表外的数据,如:编辑按钮
:param obj: 要操作的模型类实例化对象,一个模型类实例化对象对应的是数据库中的一行数据
:param is_header: 一列有表头和数据两种,用于判断是返回表格数据还是表头数据
:return:
"""
if is_header:
return "编辑表头"
# return obj.name
name = "{}:{}".format(self.site.namespace, self.get_change_url_name)
return mark_safe('<a href="{}">编辑</a>'.format(reverse(name, args=(obj.pk,))))
def display_del(self, obj=None, is_header=None):
"""
自定义列表展示页的某一列数据,在列表页面展示除数据库表外的数据,如:编辑按钮
:param obj: 要操作的模型类实例化对象,一个模型类实例化对象对应的是数据库中的一行数据
:param is_header: 一列有表头和数据两种,用于判断是返回表格数据还是表头数据
:return:
"""
if is_header:
return "删除表头"
# return obj.name
name = "{}:{}".format(self.site.namespace, self.get_delete_url_name)
return mark_safe('<a href="{}">删除</a>'.format(reverse(name, args=(obj.pk,))))
display_list = [display_edit, display_del] # 在display中添加这两个函数对象
同样的代码我们需要写三份,有代码冗余,对于编辑和删除功能,应该是每个表都应有的,可以将这两个函数写到StarkHandle类中,在用户继承的类中只需要在display_list添加函数对象即可添加这两个功能
class UserInfoHandle(StarkHandle):
display_list = ["name", "age", "mail", StarkHandle.display_edit, StarkHandle.display_del]
class DepartHandle(StarkHandle):
display_list = ['id', 'title', StarkHandle.display_edit, StarkHandle.display_del]
site.register(UserInfo, UserInfoHandle)
site.register(Depart, DepartHandle)
表中choices字段的列表页展示
为UserInfo表添加性别字段:
class UserInfo(models.Model):
name = models.CharField(verbose_name="用户名", max_length=32)
age = models.CharField(verbose_name="年龄", max_length=32)
gender_choices = (
(1, '男'),
(2, '女'),
)
gender = models.IntegerField(verbose_name="性别", choices=gender_choices, default=1)
mail = models.CharField(verbose_name="邮箱", max_length=32)
depart = models.ForeignKey(verbose_name="部门", to='Depart')
def __str__(self):
return self.name
我们用户表用的gender字段是choices类型,在数据库中存储的是数字1,2
,当我们在进行列表页展示加入了gender字段时,现有的处理逻辑是去数据库中拿到该字段对应的值去展示,如果不经过特殊处理,在页面上只能显示出数字,不能展示该数字对应的字符串内容。
针对choices字段的特殊处理:
在模型类实例化对象中有get_字段名_display()
,如果该字段是一个choices类型,就可以通过这个方法拿到对应的字符串内容。
我们最初的想法是在用户自定义的handle类中实现对于choices字段的特殊处理函数,再将函数对象放入display_list列表中进行展示
class UserInfoHandle(StarkHandle):
def display_gender(self, obj=None, is_header=None):
# 对gender字段显示的特殊处理函数
if is_header:
return '性别'
return obj.get_gender_display()
display_list = ["name", "age", display_gender, 'gender', "mail", StarkHandle.display_edit,
StarkHandle.display_del]
此时页面显示的效果:性别成功的显示字符串内容
但是,如果这个表中有三个甚至更多个choices类型的字段,或者其他表中有choices类型字段,有几个这样子的字段,我们就需要添加几个这样子的函数
仔细分析下来,这多个特殊处理函数的内容有红框两处内容不同,其他代码都一致,这两个值是否可以动态改变呢,一个函数的参数是固定不变的,在不修改参数值得情况下,还需要往函数中传入其他的值,可以想到我们可以使用闭包来实现,代码就出来了,我们将代码写在公共的stark组件中,其他app下都可以使用到次函数。
def get_choice_text(title, field):
"""
对于stark组件中,自定义列时,choice字段如果想显示中文信息,调用此方法
:param title: 希望页面显示得表头
:param field: 字段名称
:return:
"""
def inner(self, obj=None, is_header=None):
if is_header:
return title
method = "get_{}_display".format(field)
return getattr(obj, method)()
return inner
在用户自定义的handle类中,动态传入想要展示到页面的choices字段的表头,和字段名,字段名用来组合获取对于字符串的get_xxx_display方法。
from stark.service.v1 import site, StarkHandle, get_choice_text
class UserInfoHandle(StarkHandle):
display_list = ["name", "age", get_choice_text('性别', 'gender'), "gender", "mail", StarkHandle.display_edit, StarkHandle.display_del]
site.register(UserInfo, UserInfoHandle)
页面展示:
不管有多少个choices类型的字段,直接在display列表中添加get_choice_text('表头展示的内容', '模型类对应choices的字段名称')
,来进行展示。
列表展示页面应用样式
应用之前rbac中开发的layout.html模板样式
{% extends 'layout.html' %}
{% block content %}
<div class="luffy-container">
<table border="1" class="table table-bordered">
<thead>
<tr>
{% for header in header_list %}
<th>{{ header }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in body_list %}
<tr>
{% for ele in row %}
<td>{{ ele }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
页面应用样式代码链接:https://download.csdn.net/download/no_name_sky/21130443