分页
分页器组件代码
"""
分页组件
"""
class Pagination(object):
def __init__(self, current_page, all_count, base_url, query_params, per_page=20, pager_page_count=11):
"""
分页初始化
:param current_page: 当前页码
:param per_page: 每页显示数据条数
:param all_count: 数据库中总条数
:param base_url: 基础URL,URL 问号前面的部分,request.path_info可以拿到
:param query_params: QueryDict对象,内部含所有当前URL的原条件(问号后的参数),传递request.GET.copy()
:param pager_page_count: 页面上最多显示的页码数量
:return: 返回页面组成的li标签字符串,可结合bootstrap样式
"""
self.base_url = base_url
try:
self.current_page = int(current_page)
if self.current_page <= 0:
raise Exception()
except Exception as e:
self.current_page = 1
self.query_params = query_params
self.per_page = per_page
self.all_count = all_count
self.pager_page_count = pager_page_count
pager_count, b = divmod(all_count, per_page)
if b != 0:
pager_count += 1
self.pager_count = pager_count
half_pager_page_count = int(pager_page_count / 2)
self.half_pager_page_count = half_pager_page_count
@property
def start(self):
"""
数据获取值起始索引
:return:
"""
return (self.current_page - 1) * self.per_page
@property
def end(self):
"""
数据获取值结束索引
:return:
"""
return self.current_page * self.per_page
def page_html(self):
"""
生成HTML页码
:return:
"""
# 如果数据总页码pager_count<11 pager_page_count
if self.pager_count < self.pager_page_count:
pager_start = 1
pager_end = self.pager_count
else:
# 数据页码已经超过11
# 判断: 如果当前页 <= 5 half_pager_page_count
if self.current_page <= self.half_pager_page_count:
pager_start = 1
pager_end = self.pager_page_count
else:
# 如果: 当前页+5 > 总页码
if (self.current_page + self.half_pager_page_count) > self.pager_count:
pager_end = self.pager_count
pager_start = self.pager_count - self.pager_page_count + 1
else:
pager_start = self.current_page - self.half_pager_page_count
pager_end = self.current_page + self.half_pager_page_count
page_list = []
if self.current_page <= 1:
prev = '<li><a href="#">上一页</a></li>'
else:
self.query_params['page'] = self.current_page - 1
prev = '<li><a href="%s?%s">上一页</a></li>' % (self.base_url, self.query_params.urlencode())
page_list.append(prev)
for i in range(pager_start, pager_end + 1):
self.query_params['page'] = i
if self.current_page == i:
tpl = '<li class="active"><a href="%s?%s">%s</a></li>' % (
self.base_url, self.query_params.urlencode(), i,)
else:
tpl = '<li><a href="%s?%s">%s</a></li>' % (self.base_url, self.query_params.urlencode(), i,)
page_list.append(tpl)
if self.current_page >= self.pager_count:
nex = '<li><a href="#">下一页</a></li>'
else:
self.query_params['page'] = self.current_page + 1
nex = '<li><a href="%s?%s">下一页</a></li>' % (self.base_url, self.query_params.urlencode(),)
page_list.append(nex)
page_str = "".join(page_list)
return page_str
列表展示页应用分页
1.分页代码添加到项目
分页代码拷贝到stark\utils\pagination.py文件中
2.在change_list_view
视图函数中使用
from stark.utils.pagination import Pagination
class StarkHandle(object):
"""
处理请求的视图函数所在的类,公共类
"""
display_list = []
per_page_count = 10 # 每页展示的数据条数,用户在自己的类中写该值可以自定义每页展示条数
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)
# 分页器初始化
all_count = self.model_class.objects.all().count()
query_params = request.GET.copy() # 都是默认不可修改的
query_params._mutable = True # 设置之后,query_params可以修改
pager = Pagination(
current_page=request.GET.get('page'),
all_count=all_count,
base_url=request.path_info,
query_params=query_params,
per_page=self.per_page_count,
)
# 对数据库中的值进行切片,拿到当前页面展示的数据内容
data_list = self.model_class.objects.all()[pager.start:pager.end]
body_list = [] # 构造出给前端使用的数据结构,包含该表中那个的所有数据
"""
body_list = [
['胡说', '17', 'hushuo@qq.com'], # 数据表中的一行数据
['呼哈', '32', 'huha@qq.com']
]
"""
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,
'pager': pager,
}
)
3.在页面代码中展示分页
<nav>
<ul class="pagination">
{{ pager.page_html|safe }}
</ul>
</nav>
4.用户自定义每页展示条数
class UserInfoHandle(StarkHandle):
per_page_count = 1
display_list = ["name", "age", get_choice_text('性别', 'gender'), "gender", "mail", StarkHandle.display_edit, StarkHandle.display_del]
site.register(UserInfo, UserInfoHandle)
页面效果:
添加按钮功能
添加按钮的展示
添加按钮,要根据权限来决定是否显示,所以要预留一个钩子函数,让用户可以根据权限来判断该按钮是否要展示到列表页面。
StarkHandle的修改
增加类属性is_has_btn
,来判断是否展示按钮,增加钩子函数get_add_btn
返回按钮的标签内容。在render中将get_add_btn
方法的返回值传递到前端。
class StarkHandle(object):
"""
处理请求的视图函数所在的类,公共类
"""
display_list = []
per_page_count = 10 # 每页展示的数据条数
is_has_add_btn = True # 用来判断是否有添加按钮
def get_add_btn(self):
"""
预留的获取添加按钮,用户可以自定义来决定按钮的链接和样式,通过is_has_add_btn来决定页面是否展示按钮
:return:
"""
if self.is_has_add_btn:
return '<a href="" class="btn btn-primary">添加</a>'
return None
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)
# 分页器初始化
all_count = self.model_class.objects.all().count()
query_params = request.GET.copy() # 都是默认不可修改的
query_params._mutable = True # 设置之后,query_params可以修改
pager = Pagination(
current_page=request.GET.get('page'),
all_count=all_count,
base_url=request.path_info,
query_params=query_params,
per_page=self.per_page_count,
)
# 对数据库中的值进行切片,拿到当前页面展示的数据内容
data_list = self.model_class.objects.all()[pager.start:pager.end]
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,
'pager': pager,
'add_button': self.get_add_btn(),
}
)
前端代码修改,增加按钮显示
<div style="margin-bottom: 5px;">
{% if add_button %}
{{ add_button|safe }}
{% endif %}
</div>
用户自定义按钮是否展示
class DepartHandle(StarkHandle):
is_has_add_btn = True # 页面展示添加按钮
display_list = ['id', 'title', StarkHandle.display_edit, StarkHandle.display_del]
class UserInfoHandle(StarkHandle):
per_page_count = 1 # 控制页面展示的内容条数
is_has_add_btn = False # 控制页面不展示添加按钮
display_list = ["name", "age", get_choice_text('性别', 'gender'), "gender", "mail", StarkHandle.display_edit, StarkHandle.display_del]
site.register(UserInfo, UserInfoHandle)
site.register(Depart, DepartHandle)
添加按钮的url设置
在StarkHandle类中已经定义好了get_add_url_name
来获取添加按钮url对应的别名,拿到别名可以通过反向解析来获取要跳转的url。
a.reuqets对象的获取
此时需要考虑一个情况:如果我们在列表展示页的第3页,点击添加按钮,完成添加后我们如何跳转回第3页的列表展示页?
代码是通过url后的page参数的值来决定展示哪页的内容,要跳转回之前的页码,就需要保存原url的搜索条件。通过request来拿到原url的搜索条件,在添加按钮点击跳转时,也将这个搜索条件携带。
想在get_add_btn
方法中访问待request,但是整个类中只有在几个视图函数中会接收到request对象,要在其他的方法中访问到request对象,需要在init
方法中定义self.request=None
,在视图函数中首先将self.request=request
,这样其他的方法中通过self.request
可以访问了。
但是我们不想修改这几个视图函数的代码,又想给这个代码在执行前添加self.request=request
的代码,这个功能可以通过装饰器来实现。
在类中定义装饰器:
class StarkHandle(object):
def __init__(self, site, model_class, prev):
self.site = site
self.model_class = model_class
self.prev = prev
self.request = None
def wrapper(self, func):
@functools.wraps(func)
def inner(request, *args, **kwargs):
self.request = request
return func(request, *args, **kwargs)
return inner
装饰器使用,我们在url和视图关系绑定出,直接传入视图函数作为参数直接调用装饰器,装饰器的本质就是函数的调用,不过传递的参数是函数对象。
class StarkHandle(object):
def get_urls(self):
patterns = [
url(r'^list/$', self.wrapper(self.change_list_view), name=self.get_list_url_name),
url(r'^add/$', self.wrapper(self.add_view), name=self.get_add_url_name),
url(r'^change/(?P<pk>\d+)/$', self.wrapper(self.change_view), name=self.get_change_url_name),
url(r'^delete/(?P<pk>\d+)/$', self.wrapper(self.delete_view), name=self.get_delete_url_name),
]
patterns.extend(self.extra_urls())
return patterns
在每次执行了url对应的增删查改四个视图函数,都可以拿到对应的request对象。
b.原搜索条件的保留
def get_add_btn(self):
if self.is_has_add_btn:
name = '{}:{}'.format(self.site.namespace, self.get_add_url_name)
base_url = reverse(name)
if not self.request.GET:
# 没有原搜索条件
add_url = base_url
else:
# 有携带搜索条件
params = self.request.GET.urlencode()
query_dict = QueryDict(mutable=True)
query_dict['_filter'] = params
# 搜索条件拼接
add_url = '{}?{}'.format(base_url, query_dict.urlencode())
return '<a href="{}" class="btn btn-primary">添加</a>'.format(add_url)
return None
页面展示
添加页面、添加数据
a. 构造添加页面,使用forms组件,stark/templates/stark/change.html。
{% extends 'layout.html' %}
{% block css %}
<style>
ul{
list-style: none;
padding: 0;
}
ul li{
list-style: none;
float:left;
padding: 10px;
padding-left:0;
width:80px;
}
ul li i{
font-size: 18px;
margin-left: 5px;
color: #6d6565;
}
</style>
{% endblock %}
{% block content %}
<div class="luffy-container">
<form action="" method="post" class="form-horizontal" 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-8">
{{ 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 %}
b. 构建模型类对应的ModelForm
在StarkHandle类中构建一个DynamicModelForm,根据不同的注册类来决定产生其对应的ModelForm
def get_model_form_class(self):
from stark.forms.base import BootStrapModelForm
class DynamicModelForm(BootStrapModelForm):
class Meta:
model = self.model_class
fields = '__all__' # 默认展示所有字段
return DynamicModelForm
此时所有添加页面都展示的是该表中所有的字段,如果用户要添加一个数据库表中没有的字段如:密码的二次确认输入。我们就要留出一个给用户来扩展的功能,定义一个类属性model_form_class
,用户可以在继承的handle类中,将自己编写的ModelForm类对象赋值给model_form_class
,在get_model_form_class
中检测,如果用户定义了ModelForm类,就使用用户的,没有定义就使用默认的。
model_form_class = None # 支持用户扩展xxx modelform的内容,比如增加一个密码的二次确认输入框
def get_model_form_class(self):
if self.model_form_class:
# 用户在自己定义的hanle类中,自己实现了ModelForm类
return self.model_form_class
from stark.forms.base import BootStrapModelForm
class DynamicModelForm(BootStrapModelForm):
class Meta:
model = self.model_class
fields = '__all__'
return DynamicModelForm
用户想增加一个xx的字段,就可以如下使用:
class UserInfoModelForm(BootStrapModelForm):
# 用户自定义的UserInfo表对应的ModelForm
xx = forms.CharField()
class Meta:
model = UserInfo
fields = '__all__'
class UserInfoHandle(StarkHandle):
per_page_count = 1 # 控制页面展示的内容条数
is_has_add_btn = True # 控制页面不展示添加按钮
model_form_class = UserInfoModelForm # 使用用户自定义的ModelForm
display_list = ["name", "age", get_choice_text('性别', 'gender'), "mail", StarkHandle.display_edit, StarkHandle.display_del]
xx字段展示到页面
c.实现增加的视图函数
在增加页面点击保存只有,应该跳转到之前的页面,如果之前的页面在第3页,即用户点击了其他页码,原页面有搜索条件,就需要拿到原搜索条件拼接之后跳转。
def reverse_list_url(self):
"""
返回解析到原搜索条件的url
:return:
"""
params = self.request.GET.get('_filter') # 拿到原搜索条件
name = "{}:{}".format(self.site.namespace, self.get_list_url_name)
base_url = reverse(name) # 列表展示页面的链接
if not params:
# 原页面没有搜索条件,直接点击的添加
return base_url
return "{}?{}".format(base_url, params) # 原页面的搜索条件拼接
def add_view(self, request):
"""
添加视图函数
"""
model_form_class = self.get_model_form_class()
form = model_form_class()
if request.method == "GET":
return render(request, 'stark/change.html', {'form':form})
form = model_form_class(data=request.POST)
if form.is_valid():
form.save()
return redirect(self.reverse_list_url())
return render(request, 'stark/change.html', {'form': form})
实现了用户点击保存,数据保存到数据库
d.思考一个点,用户在自定义的在UserInfoModelForm中没有展示depart的信息让用户输入,用户输入了展示的信息之后,点击了保存,是会报错的,因为我们没有输入depart的信息,也没有默认的depart信息给我们使用,要提供一个接口出去给用户设定默认的depart值
def save(self, form, is_update=False):
"""
保存前端传递过来的数据, 用户可以通过重写这个方法来在保存前给某些字段设置默认值
:param form:
:param is_update:
:return:
"""
form.save()
def add_view(self, request):
model_form_class = self.get_model_form_class()
form = model_form_class()
if request.method == "GET":
return render(request, 'stark/change.html', {'form':form})
form = model_form_class(data=request.POST)
if form.is_valid():
self.save(form)
return redirect(self.reverse_list_url())
return render(request, 'stark/change.html', {'form': form})
用户通过重写save方法,就可以在保存之前给某些字段添加默认值
class UserInfoModelForm(BootStrapModelForm):
class Meta:
model = UserInfo
fields = ["name", "age", "gender", "mail"]
class UserInfoHandle(StarkHandle):
per_page_count = 1 # 控制页面展示的内容条数
is_has_add_btn = True # 控制页面不展示添加按钮
model_form_class = UserInfoModelForm
def save(self, form, is_update=False):
# 重写save方法,给depart设定默认值
form.instance.depart_id = 1
form.save()
display_list = ["name", "age", get_choice_text('性别', 'gender'), "depart", "mail", StarkHandle.display_edit, StarkHandle.display_del]
页面功能展示:
添加之后
编辑、删除
编辑、删除按钮的url
编辑和删除的url也是需要携带原搜索条件的,实现获取携带原搜索条件的url函数
def reverse_change_url(self, *args, **kwargs):
"""
返回反向解析后的编辑的url,有原搜索条件的话携带
:return:
"""
name = '{}:{}'.format(self.site.namespace, self.get_change_url_name)
base_url = reverse(name, args=args, kwargs=kwargs)
if not self.request.GET:
# 没有原搜索条件,在第一页直接点击的添加按钮
change_url = base_url
else:
# 有携带搜索条件
params = self.request.GET.urlencode()
query_dict = QueryDict(mutable=True)
query_dict['_filter'] = params
change_url = '{}?{}'.format(base_url, query_dict.urlencode())
return change_url
def reverse_del_url(self, *args, **kwargs):
"""
返回反向解析后的删除的url,有原搜索条件的话携带
:return:
"""
name = '{}:{}'.format(self.site.namespace, self.get_delete_url_name)
base_url = reverse(name, args=args, kwargs=kwargs)
if not self.request.GET:
# 没有原搜索条件,在第一页直接点击的添加按钮
delete_url = base_url
else:
# 有携带搜索条件
params = self.request.GET.urlencode()
query_dict = QueryDict(mutable=True)
query_dict['_filter'] = params
delete_url = '{}?{}'.format(base_url, query_dict.urlencode())
return delete_url
删除和编辑的按钮链接
def display_edit(self, obj=None, is_header=None):
if is_header:
return "编辑表头"
return mark_safe('<a href="{}">编辑</a>'.format(self.reverse_change_url(pk=obj.pk)))
def display_del(self, obj=None, is_header=None):
if is_header:
return "删除表头"
return mark_safe('<a href="{}">删除</a>'.format(self.reverse_del_url(pk=obj.pk)))
用户点击编辑或者删除,跳转到的页面都携带有原搜索条件,以便操作完成后返回到原来的列表页面。
编辑界面展示
编辑页面是将数据库已有的数据展示到页面,用户修改之后再进行保存,和新增的相差不大
def change_view(self, request, pk):
# 从数据库拿到用户选中的数据
obj = self.model_class.objects.filter(pk=pk).first()
model_form_class = self.get_model_form_class()
if not obj:
return HttpResponse("信息不存在")
if request.method == "GET":
form = model_form_class(instance=obj)
return render(request, 'stark/change.html', {'form': form})
form = model_form_class(data=request.POST, instance=obj)
if form.is_valid():
# 保存用逇修改
self.save(form)
return redirect(self.reverse_list_url())
return render(request, 'stark/change.html', {'form': form})
删除页面的展示
用户点击确定删除对应数据记录(点击确认是post方式提交,不携带数据),点击取消返回原来的页面,需要将原来页面的url传递到页面,绑定到cancel按钮上。
def delete_view(self, request, pk):
origin_url = self.reverse_list_url()
if request.method == "GET":
return render(request, 'stark/delete.html', {'cancel_url': origin_url})
self.model_class.objects.filter(pk=pk).delete()
return redirect(origin_url)
前端页面
{% extends 'layout.html' %}
{% block content %}
<div class="luffy-container">
<div class="alert alert-danger" role="alert">
<form action="" method="post">
{% csrf_token %}
<p style="font-size: 13px;">
<i class="fa fa-warning" aria-hidden="true"></i>删除后不可恢复,请确认是否进行删除?
<div style="margin-top:20px;">
<a href="{{ cancel_url }}" class="btn btn-default btn-sm">取消</a>
<input type="submit" class="btn btn-danger btn-sm" value="确 认">
</div>
</p>
</form>
</div>
</div>
{% endblock %}
至此stark的增删查改功能完成
代码链接:https://download.csdn.net/download/no_name_sky/21445497