CRM项目之stark组件之列表展示页面详细功能实现3

分页

分页器组件代码

"""
分页组件
"""


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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值