CURD
# 列表页面定制列
(*≧ω≦) 定制列表页面显示的列!!
在数据库里先手动给迁移的表添加一些数据,便于测试..
# 基本实现
在自定义的handler里添加 list_display 属性, 用于定制列表页面显示的列!!
关键代码如下:
# 默认显示
对list_display属性值为空列表时的处理!! / 未定义list_display字段的页面,默认显示对象.
当我们未自定义handler时,就无法添加list_display属性 默认使用的就是 StarkHandler类中的 空列表 list_display..
或者我们在自定义的handler里 设置list_display属性为空列表..
■ 按照上面的代码, 在模版上展示的效果为空!! 我们不希望如此.
需求: 我们希望若list_display为空,那么表格的表头显示该表名,表格的数据显示 每一行的对象!! (这样更友好Hhh
# ★ 预留钩子方法
这是一个非常重要的扩展点哦!! " !!"
应用场景: 根据登录用户的角色不同, 来定制页面显示的列!!(eg: 高级用户显示的列比较多,普通用户显示的列比较少
# 自定义函数扩展
为页面提供自定义显示的函数, 自定义包括表头和内容
# ◎ 函数和方法
回顾下python知识点, 什么时候称为函数? 什么时候称为方法?!
stark组件中自定义的函数扩展就用到了它!!
类里的func有两种方式进行调用!!调用者的不同,使其可叫作方法亦可叫作函数.
class Foo:
def func(self, name):
print(name)
"""obj1自动传递给了self ★对象调用func,我们通常称func是方法"""
obj1 = Foo()
obj1.func("dc") # dc
print(obj1.func) # <bound method Foo.func of <__main__.Foo object at 0x10d06efd0>>
"""需要将obj2手动传递给了self ★类调用func,我们通常称func是函数"""
obj2 = Foo()
Foo.func(obj2, 'wupeiqi') # wupeiqi
print(Foo.func) # <function Foo.func at 0x10cf53c10>
2
3
4
5
6
7
8
9
10
11
12
13
14
# 编辑和删除按钮
在列表页面 表格里的 编辑和删除按钮!!
脑袋好痒, 快长脑子了(´・Д・)」 !!
# 1:n外键字段
关于外键字段, 更推荐 在list_display中写"depart" + 在model类表中添加
__str__
来达到想要的效果!!
# Choices字段 (闭包)
通过"闭包"来解决model表中有多个chioce字段导致会编写多个自定义函数的问题..
普通版: 很容易想到,这么做.
进阶版: 若model表中有多个choice字段,有几个我们就自定义多少个函数嘛? 可以是可以, 但总感觉很麻烦!!
我们可以通过"闭包"函数来解决这个问题!!
进一步优化: 我们应该将get_choice_text方法放到 stark组件中.
用到时 通过 from apps.stark.service.v1 import site, StarkHandler, get_choice_text
导入即可!!
# ※ 后续: 编辑删除一起
这个是后面在开发crm项目时, 往stark组件里添加的功能, 总结在此处!! 便于翻阅..
我们规定: list_display配置有值, 且没有配置编辑和删除按钮的话!!(单独配置的编辑和删除按钮是分别成一列的)
默认在 表格中 添加"操作"列, 编辑和删除放在同一个格子中!!
stark组件的StarkHandler类添加display_edit_and_del函数和修改get_list_display函数!!
class StarkHandler:
list_display = [] # 用于列表页面表格中字段的显示
def get_list_display(self):
"""为页面显示的列预留的钩子函数"""
# 我们规定, list_display配置有值, 且没有配置编辑和删除按钮的话!!
# 默认在 表格中 添加"操作"列, 编辑和删除放在同一个格子中!!
value = []
if self.list_display:
value.extend(self.list_display)
if StarkHandler.display_del not in self.list_display \
and StarkHandler.display_edit not in self.list_display:
value.append(StarkHandler.display_edit_and_del)
return value
def display_edit_and_del(self, obj=None, is_header=None):
if is_header:
return "操作"
edit_url = self.reverse_edit_url(pk=obj.pk)
del_url = self.reverse_del_url(pk=obj.pk)
return mark_safe(f'''
<div class="layui-btn-group">
<a href={edit_url}>
<button type="button" class="layui-btn layui-btn-sm layui-btn-primary">
<i class="layui-icon layui-icon-edit"></i>
</button>
</a>
<a href={del_url} style="margin-left:10px">
<button type="button" class="layui-btn layui-btn-sm layui-btn-primary layui-border-red">
<i class="layui-icon layui-icon-delete" style="color: #ff5722;"></i>
</button>
</a>
</div>
''')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
后面又对get_list_dispaly方法进行了一点改进!!
class StarkHandler:
def get_list_display(self):
"""为页面显示的列预留的钩子函数"""
value = []
if self.list_display:
value.extend(self.list_display)
if StarkHandler.display_del not in self.list_display \
and StarkHandler.display_edit not in self.list_display:
# value.append(StarkHandler.display_edit_and_del)
# ★★★ type(self)是为了可调用自定义的handler里重写的display_edit_and_del方法
value.append(type(self).display_edit_and_del)
return value
2
3
4
5
6
7
8
9
10
11
12
# ※ 后续: 日期格式的处理
这个是后面在开发crm项目时, 往stark组件里添加的功能, 总结在此处!! 便于翻阅..
代码如下:
def get_datetime_text(title, field, time_format='%Y-%m-%d'):
"""
对于Stark组件中定义列时,定制时间格式的数据
:param title: 表格某一列的表头
:param field: ORM表中的字段名称
:param time_format: 要格式化的时间格式
:return:
"""
def inner(self, obj=None, is_header=None, *args, **kwargs):
if is_header:
return title
datetime_value = getattr(obj, field)
return datetime_value.strftime(time_format)
return inner
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
使用方法
class ClassListHandler(StarkHandler):
list_display = [get_datetime_text('开班日期', 'start_date'),]
2
# ※ 后续: 多对多外键字段
这个是后面在开发crm项目时, 往stark组件里添加的功能, 总结在此处!! 便于翻阅..
代码如下:
def get_m2m_text(title, field):
"""
对于Stark组件中定义列时,显示m2m文本信息
:param title: 表格某一列的表头
:param field: ORM表中的字段名称
:return:
"""
def inner(self, obj=None, is_header=None, *args, **kwargs):
if is_header:
return title
queryset = getattr(obj, field).all()
text_list = [str(row) for row in queryset]
# return ','.join(text_list)
btn_str = ''
for text in text_list:
btn_str += f'<button type="button" class="layui-btn layui-btn-sm layui-bg-blue">{text}</button>'
return mark_safe(btn_str)
return inner
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
使用方法
class ClassListHandler(StarkHandler):
list_display = [get_m2m_text('任课老师', 'tech_teachers'),]
# PS:直接外键字段的话, list_display = ['tech_teachers'], 页面上展示的是 web.UserInfo.None
2
3
4
# ※ 后续: 其他用法
比如:
班级日期的信息结合显示在班级一列中!! (eg: Linux基础 1期
显示学费时前面加前缀"¥"
举个栗子:
class ClassListHandler(StarkHandler):
def display_course(self, obj=None, is_header=None, *args, **kwargs):
if is_header:
return '班级'
return "%s %s期" % (obj.course.name, obj.semester,)
def display_price(self, obj=None, is_header=None, *args, **kwargs):
if is_header:
return '学费'
return f"¥ {obj.price}"
list_display = [
display_course,
display_price,
]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# ※ 后续:自定义插件
(´・Д・)」这谁又能想到呢?!
from django.forms import TextInput
# -- TextInput的源码长这样!! text.html里是什么,页面上就渲染什么!!
#. So,我们可以自己写插件,来满足一些第三方的应用的编写规范!!
#. 比如,在crm示例中的 日期的选择,若用到的是BootStrpt的datetimepicker时间选择器,就会用到该知识点!!
class TextInput(Input):
input_type = 'text'
template_name = 'django/forms/widgets/text.html'
2
3
4
5
6
7
# 模版样式使用
(¯﹃¯)
将rbac组件里的layui.html模版 借鉴过来, 导入那些css,js, 通过模版继承 就能达到截图中的效果!!
# ◆ 分页
目标: 列表页面添加分页的功能!!
分页组件详见 "订单系统"中对该组件的阐述!
先来看看效果, 注意观察 url 的变化!!
你会发现, 每一个分页的url都是携带了原搜索条件的哦~
如何使用? 很简单, 在该示例中, 就添加了 截图中,标注的那些代码!
分页组件全部代码... 拿来即用!!
"""
分页组件的使用,需要以下两个步骤:
-- 视图函数:
def customer_list(request):
queryset = models.Customer.objects.filter(active=1).select_related('level') # 查询所有数据
obj = Pagination(request, queryset) # 使用分页组件对查询出的所有数据进行分页
context = {
"queryset": obj.current_page_queryset,
"pager_string": obj.html,
}
return render(request, 'customer_list.html', context)
# ★ V2版本,在视图函数中传入pager对象,在模版中调用 pager.current_page_queryset 以及pager.html
# pager = Pagination(request, queryset)
# return render(request, 'customer_list.html', {"pager": pager})
-- 前端页面:
{% for row in queryset %} <!--v2版 {% for row in pager.current_page_queryset %} -->
{{row.id}}
{{row.username}}
{% endfor %}
<nav aria-label="Page navigation">
<ul class="pagination">
{{ pager_string }} <!--v2版 {{ pager.html }} -->
</ul>
</nav>
"""
import copy
from django.utils.safestring import mark_safe
class Pagination(object):
""" 分页 """
def __init__(self, request, query_set, per_page_count=10, pager_count=8):
self.query_dict = copy.deepcopy(request.GET)
self.query_dict._mutable = True
self.query_set = query_set # 要进行分页的所有数据(是一个queryset对象)
self.per_page_count = per_page_count # 每页显示的条数
self.pager_count = pager_count # 分页栏中最多显示的页码个数
self.total_count = query_set.count() # 一共多少条数据
self.pager_count_half = pager_count // 2 # 当前页向前向后展示的页码个数
self.total_page, div = divmod(self.total_count, per_page_count)
if div:
self.total_page += 1 # 计算出总共有多少页/最大的页码数 使用内置函数divmod
self.page = self.check_page(request) # 当前页
self.start = (self.page - 1) * per_page_count # 切片的开始位置
self.end = self.page * per_page_count # 切片的结束位置
def check_page(self, request):
"""对url中携带的page参数进行判断!!"""
page = request.GET.get('page')
if not page:
page = 1
else:
if not page.isdecimal():
page = 1
else:
page = int(page)
if page <= 0:
page = 1
else:
if page > self.total_page:
page = self.total_page
return page
def page_nav(self):
"""分页栏中的开始页码和结束页码"""
# 总页码小于11 (分页栏最多显示的页码个数默认是11)
if self.total_page <= self.pager_count:
start_page = 1
end_page = self.total_page
# 总页码大于11
else:
# 当前页如果<=11//2 "左极值 避免开始页码<=0"
if self.page <= self.pager_count_half:
start_page = 1
end_page = self.pager_count
else:
# 当前页如果>11//2 "右极值 避免结束页码>总页码"
if (self.page + self.pager_count_half) > self.total_page:
start_page = self.total_page - self.pager_count + 1
end_page = self.total_page
else:
start_page = self.page - self.pager_count_half
end_page = self.page + self.pager_count_half
return start_page, end_page
@property
def html(self):
"""前端分页栏代码(前端使用的是Bootstrap!)"""
pager_list = []
# 无数据,分页栏不展示
if not self.total_page:
return ""
# start_page、end_page 分页栏开始和结束页码
start_page, end_page = self.page_nav()
# 分页栏首页html
self.query_dict.setlist('page', [1])
pager_list.append('<li><a href="?{}">首页</a></li>'.format(self.query_dict.urlencode()))
# 分页栏上一页html
if self.page == 1:
pager_list.append('<li class="disabled"><a href="#">上一页</a></li>')
if self.page > 1:
self.query_dict.setlist('page', [self.page - 1])
pager_list.append('<li><a href="?{}">上一页</a></li>'.format(self.query_dict.urlencode()))
# 分页栏中间页html
for i in range(start_page, end_page + 1):
self.query_dict.setlist('page', [i])
if i == self.page:
item = '<li class="active"><a href="?{}">{}</a></li>'.format(self.query_dict.urlencode(), i)
else:
item = '<li><a href="?{}">{}</a></li>'.format(self.query_dict.urlencode(), i)
pager_list.append(item)
# 分页栏下一页html
if self.page == self.total_page:
pager_list.append('<li class="disabled"><a href="#">下一页</a></li>')
if self.page < self.total_page:
self.query_dict.setlist('page', [self.page + 1])
pager_list.append('<li><a href="?{}">下一页</a></li>'.format(self.query_dict.urlencode()))
# 分页栏尾页html
self.query_dict.setlist('page', [self.total_page])
pager_list.append('<li><a href="?{}">尾页</a></li>'.format(self.query_dict.urlencode()))
# 分页栏汇总
pager_list.append('<li class="disabled"><a>共{}页/{}条</a></li>'.format(
self.total_page,
self.total_count)
)
pager_string = mark_safe("".join(pager_list))
return pager_string
@property
def current_page_queryset(self):
"""当前页的数据"""
if self.total_count:
return self.query_set[self.start:self.end]
return self.query_set
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# 添加
CURD 之一 添加!!
把rbac组件里的 change.html 拿过来, 用一哈!! 添加和编辑页面都用的它!!Hhh..
# 添加按钮显示
且听我细细道来!!
需要思考解决三个问题:
1> 如何显示添加按钮? - 预留钩子
2> 为添加按钮反向生成url,并保留原搜索条件? - 在rbac组件里涉及过 _filter
3> 每个请求进来时,对应的视图函数都先执行语句self.request = request
- 使用装饰器,其本质就是闭包
关键代码如下:
# 添加功能(两个扩展点)
大致有三个要点, 详见截图!
特别关注两个扩展点: self.model_form_class
、self.save
Ps: 暂且不知道save方法的第二个参数 is_update 有何妙用.. 后面的CRM系统开发过程中应该会揭晓,敬请期待!!
关键代码如下: (细品 截图中的示例!!
# ※ 后续: 配置多个modelform
★ 重写get_model_form_class还可以实现 新增使用1号ModelForm, 编辑使用2号ModelForm.. 后面的CRM系统中回用到, 别急!!
关键代码如下:
# ※ 后续: 样式优化
(*≧ω≦) 样式好看, 心情愉悦!!
1> 应用ModelForm组件时, 改变多对多字段对应的form字段对象使用的默认的插件 xm-select/default_select/choices
2> 隐藏密码显示
3> 日期选择点击出现日历
4> layui的数字输入框
5> 调节文本域的高度
关键代码如下!
后续在实践过程中,还对fromselect组件产生的bug进行了修复!! 补充的关键是这样的!
if mutiple == "xm-select": # -- layui第三方的fromSelects组件
field.widget.attrs['xm-select'] = f"selectId_{name}"
field.widget.attrs['xm-select-search'] = "" # fromSelects组件的搜索
field.widget.attrs['xm-select-search-type'] = "dl" # fromSelects组件搜索显示的位置
# 表单类的实例化会执行init方法. GET请求时,self.data为空;POST请求时,self.data值为request.data
# 因为layui第三方的fromSelects组件 多选后,表单提交到后端的数据值格式是["7,5,2"],我们需要的是["7","5","2"]
# - 还有一点很奇怪,使用default_select和choices时,该多对多字段不填,request.post中是没有该字段的
# 哪怕字段设置了blank=True,即表单可不填.. 使用xm-select也会自动传值,并且其值为[''],
# 于是乎, 后端的字段验证会说 该字段“” is not a valid value.
if name in self.data:
self.data._mutable = True
if not self.data[name]:
self.data.pop(name)
else:
self.data.setlist(name, self.data[name].split(","))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
在自定义的handler中的应用 - 重写__init__
方法!
class UserInfoAddModelForm(StarkModelForm):
def __init__(self, *args, **kwargs):
super().__init__(mutiple="choices", *args, **kwargs)
2
3
# 编辑(★明确两点)
(¯﹃¯)没有啥新的知识点..
大体上需要解决两个问题:
1> 现目前的 编辑和删除 按钮是不具备 保留原搜索条件的..
2> 完成编辑的视图函数的代码. - 它与添加的代码相比, 编辑页面就多了个默认数据!!
★★★ 明确两点:
1. 查看了下源码,Form组件没有save方法! ModelForm组件有save方法!!
2. - 试验了下,ModelForm更新时,使用save,无需所有字段,前端表单中填的字段有一个是ORM表中的就行;
(多的字段是ORM表中没有的字段也无伤大雅
- 试验了下,ModelForm新增时,使用save,除ORM表中允许为空和默认的,其余字段在前端表单中必须都有,少一个都不行!!
但多了却没事Hhh,比如多了个重复密码的额外字段.
★ 无论是新增还是更新,其内部的本质是 将form.instance进行save!!不是将form.cleaned_data来进行save哦!
经过内部处理后,form.instance里面有的字段只会是ORM表中的字段!!
PS:若你头铁,使用 self.model_class.objects.create(**form.cleaned_data) 来进行新增的话,必须确保一个不多一个不少!!
╮( ̄▽ ̄"")╭ 再多说一点,前后端分离的drf组件,使用save新增时,应该是一个不多一个不少!!多了save前pop,少了就给save传参!!
(¯﹃¯)因为其内部是用 校验成功后的数据进行的save!!
2
3
4
5
6
7
8
9
10
11
12
关键代码如下:
# 删除
删除时, 跳转一个页面, 让用户确认.. 在rbac组件里已经实现过啦!!
不过多赘述.. 在现有代码之上添加如下代码即可:
class StarkHandler:
def delete_view(self, request, pk):
"""删除页面"""
queryset = self.model_class.objects.filter(id=pk)
if not queryset:
return render(request, 'stark/500.html', {"msg": "该数据不存在,请联系管理员!"})
origin_url = self.reverse_list_url()
if request.method == 'GET':
return render(request, 'stark/delete.html', {'cancel': origin_url})
queryset.delete()
return redirect(origin_url)
2
3
4
5
6
7
8
9
10
11
12
后续将要进行开发的功能:
■ 排序
■ 模糊搜索
■ 批量操作
■ 组合搜索
# ※ 后续: save方法的改进
让save方法更加的通用!!
request是为了在save时可拿到session里的信息;
*args,**kwargs
是为了拿到路由里的参数; is_update
值为真表明是更新,为假表明是新增!
return response or ..
可自定制save成功后干什么, 默认是返回列表页面! 若是非法操作,即可返回非法操作的提示页面!
关键代码如下:
class StarkHandler:
def save(self, request, form, is_update, *args, **kwargs):
""" 在使用ModelForm保存数据之前预留的钩子方法 """
form.save()
def add_view(self, request, *args, **kwargs):
# ... ...
response = self.save(request, form, False, *args, **kwargs)
return response or redirect(self.reverse_list_url(*args, **kwargs))
# ... ...
def change_view(self, request, pk, *args, **kwargs):
# ... ...
response = self.save(request, form, True, *args, **kwargs)
return response or redirect(self.reverse_list_url(*args, **kwargs))
# ... ...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# ※ 后续:表格展示的优化
框体更好看了(自带打印功能了), 每一列的宽度可以自己把控了, 某些列可以固定了, 某些列可以有排序按钮,
因为后面添加权限粒度到按钮的话, 若没有编辑和删除权限,就不会有操作这一栏啦!!
那么设置的layui_list_width的元素个数就多了一个!! 我们可通过判断, 进行列表分片不要最后一个!!
为了兼容粒度控制到按钮, 修改的代码具体如下:
# ※ 后续:CUD成功提示
在新增、更新、删除成功后, 返回列表页面, 会提示用户 操作成功!! -- 关键在于Django的messages组件!
# ■ 一点思考
若想让新增、更新、删除通过 iframe展示 + ajax发送 请求, 如何实现呢??
■ step1: settings.py中配置
X_FRAME_OPTIONS = 'SAMEORIGIN' # 表示该页面可以在相同域名页面的 frame 中展示, 默认是不允许.
■ step2
- 参照在change.html写了个frame_change.html继承frame_layout.html 该母版中,没有侧边栏、顶部导航等.
- starkhandler中需配置 add_template = "stark/frame_change.html"
■ step3:
1. 我在change_list.html中写了个按钮,测试了下.
<button type="button" class="layui-btn layui-bg-blue" onclick="btnAction('/stark/web/classlist/add/')">
蓝色按钮
</button>
function btnAction(url) {
var index = layer.open({
title: '新增',
type: 2,
maxmin: true, // 是否开启标题栏的最大化和最小化图标。
shadeClose: true, // 点击遮罩区域,关闭弹层
shade: 0.3, // 遮罩透明度
area: ['35%', 'auto'], // iframe框的宽高
offset: '15%', // iframe距离上面的距离
// content: '/stark/web/school/add/',
content: url,
success: function (layero, index, that) {
layer.iframeAuto(index); // 让 iframe 高度自适应
that.offset(); // 重新自适应弹层坐标
}
});
}
2. 在frame_change.html中
// form表单中写了这样一个按钮!如下:
<button type="button" class="layui-btn" lay-submit lay-filter="demo-submit">立即提交</button>
// -- 表单提交
layui.use(function () {
var $ = layui.$;
var form = layui.form;
var layer = layui.layer;
// 提交事件
form.on('submit(demo-submit)', function (data) {
var field = data.field; // 获取表单全部字段值
var elem = data.elem; // 获取当前触发事件的元素 DOM 对象,一般为 button 标签
var elemForm = data.form; // 获取当前表单域的 form 元素对象,若容器为 form 标签才会返回。
// 显示填写结果,仅作演示用
// layer.alert(JSON.stringify(field), {
// title: '当前填写的字段值'
// });
// 此处可执行 Ajax 等操作
// …
// return false; // 阻止默认 form 跳转
});
});
---
看似完成的差不多了,实则差强人意!!
1> 用layui的from提交 ajax请求.
若表单不通过的话 不好在每个表单项后面展示错误信息;
表单通过的话,还挺好操作的,关闭iframe,刷新表格(父/浏览器窗体 当前url刷新),弹出一个成功的信息..
2> 不用ajax请求,那表单所在的frame框如何处理呢?
https://blog.csdn.net/liaoningxinmin/article/details/105810929 form标签的target属性
- form target="_self" -- 就会出现 "redict重定向" 和 "render错误信息" 的响应都在该iframe框中.
- form target="parent"
-- redict响应会在 父/浏览器 框体中展示;
-- 但render相当于在浏览器里重新输入了新增或更新的地址, 展示渲染的表单页面是没有 侧边栏顶部导航这些内容的.
而且没在frame框中显示 不是那么如意.
其实,有个方法曲线救国: form target="_self" 将redict的效果用HttpResponse来实现
def save(self, request, form, is_update, *args, **kwargs):
form.save()
url = self.reverse_list_url(*args, **kwargs)
href = f'''<html><body onLoad="window.top.location.href='{url}'" ></body></html>'''
return HttpResponse(href)
我累了,遇到该需求再说吧!!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# 附录
到目前为止, 可使用的stark组件的功能, 都在下方展示啦! (相当于说明文档了 (*≧ω≦)
apps/app01/models.py
from django.db import models
class Depart(models.Model):
"""部门表"""
title = models.CharField(verbose_name='部门名称', max_length=32)
def __str__(self):
return self.title
class UserInfo(models.Model):
"""用户表"""
gender_choices = (
(1, "男"),
(2, "女"),
(0, "未知"),
)
name = models.CharField(verbose_name='姓名', max_length=32)
age = models.CharField(verbose_name='年龄', max_length=32)
gender = models.IntegerField(verbose_name="性别", choices=gender_choices, default=0)
email = models.CharField(verbose_name='邮箱', max_length=32)
depart = models.ForeignKey(verbose_name='部门', to='Depart', on_delete=models.CASCADE)
def __str__(self):
return self.name
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apps/app01/stark.py
from django.urls import path
from apps.app01 import models
from apps.stark.service.v1 import site, StarkHandler, get_choice_text, StarkModelForm
from django.shortcuts import HttpResponse
from django import forms
class UserInfoModelForm(StarkModelForm):
"""该示例中 新增和更新都用的该ModelForm"""
extra_field = forms.CharField(label="额外字段")
class Meta:
model = models.UserInfo
fields = ["name", "age", "email", "extra_field"] # ★新增页面和更新页面的表单中也就只有这四个字段!!
class UserInfoHandler(StarkHandler):
has_add_btn = True # 若值为False且不重写get_add_btn,页面上新增按钮不显示!!
per_page_count = 6 # 分页,每页显示的条数
model_form_class = UserInfoModelForm # 使用用户指定的modelForm!!
# 自定义函数扩展
def display_depart(self, obj=None, is_header=None):
if is_header:
return "部门(副)"
return obj.depart.title
# 列表页面表格中显示的字段 ★里面要么是ORM表中的字段,要么是函数!!
list_display = ["name", "age", "email",
display_depart,
get_choice_text("性别", 'gender'),
StarkHandler.display_edit,
StarkHandler.display_del]
def get_list_display(self):
"""自定义扩展,eg:根据登录用户的角色不同, 来定制页面显示的列!!"""
return super().get_list_display()
def get_add_btn(self):
"""页面是否显示新增按钮预留的钩子函数 eg:通过权限来判断是否显示"""
return super().get_add_btn()
def get_model_form_class(self):
"""重写model_form_class还可以实现 新增使用1号ModelForm, 编辑使用2号ModelForm.."""
return super().get_model_form_class()
def save(self, form, is_update=False):
"""新增or更新"""
# form.instance.__dict__
# ==> 新增 {'_state':..., 'id': None, 'name': '哈哈哈', 'age': '11', 'gender': 0, 'email': '111', 'depart_id': None}
form.instance.depart_id = 1
"""
- 无论是新增还是更新,form.instance.__dict__的值 里包含的字段只会是ORM表中的字段!!额外字段不会出现在里面!!
==> 新增 {'_state': ..., 'id': None, 'name': '哈哈哈', 'age': '11', 'gender': 0, 'email': '111', 'depart_id': 1}
==> 更新 {'_state': ..., 'id': 291, 'name': '哈哈哈', 'age': '11', 'gender': 0, 'email': '111', 'depart_id': 1}
- form.cleaned_data ==> {'name': '哈哈哈', 'age': '11', 'email': '111', 'extra_field': '123'}
★★★ 综上: 前端模版表单中的字段(由UserInfoModelForm中的fields决定的)
- 新增 得保证save之前,form.instance能满足!! ORM中可为空和默认值的字段前端可不传.
示例中,depart_id的值由None变成了1.. <■ 该示例请细品!!>
- 更新 前端表单中有一个是ORM表中的字段就行,其余的都是额外字段都没事
"""
# print(form.instance.__dict__)
# self.model_class.objects.create(**form.cleaned_data) # 必须一个不多一个不少.
form.save()
def wrapper(self, func):
"""在执行原本的视图函数逻辑之前做点什么.."""
return super().wrapper(func)
def get_urls(self):
"""在原默认的4个url上只使用其中某几个URL"""
patterns = [
path("list/", self.wrapper(self.changelist_view), name=self.get_list_url_name),
path("add/", self.wrapper(self.add_view), name=self.get_add_url_name),
path("edit/<int:pk>/", self.wrapper(self.change_view), name=self.get_change_url_name),
path("del/<int:pk>/", self.wrapper(self.delete_view), name=self.get_delete_url_name),
]
patterns.extend(self.extra_urls())
return patterns
def extra_urls(self):
"""额外的增加url"""
return [
path('detail/<int:pk>/', self.detail_view)
]
def detail_view(self, request, pk):
return HttpResponse("detail_page")
site.register(models.UserInfo, UserInfoHandler)
class DepartHandler(StarkHandler):
list_display = ["id", "title", StarkHandler.display_edit, StarkHandler.display_del]
# site.register(models.Depart) # 使用默认的handler,列表页面表格中显示的是obj
site.register(models.Depart, DepartHandler)
site.register(models.Depart, DepartHandler, prev="xx") # url支持前缀
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102