stark组件开发之关键搜索

时间:2019-04-20
本文章向大家介绍stark组件开发之关键搜索,主要包括stark组件开发之关键搜索使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

- 模糊搜索:
  在页面生成一个表单。  以get 方式, 将数据提交到。当前查看页面。 后台接收数据,然后进行筛选过滤。

着个也需要,用户自定制!
   定义一个  search_list  这个值,默认为空。 页面进行判断,为空就不显示。 搜索框!
如果,用户 定义了这个列表, 那么就显示!

SharkHandler:   
search_list = [] # 方便,用户自己定制 def get_search_list(self): return self.search_lis

子类中, 由用户自己定制:

class UserInfoHandler(StartHandler):
    list_display = ["name", "age", "depart", get_choice_txt("性别", "gender"), StartHandler.display_edit,
                    StartHandler.display_del]
    per_page = 10  # 重订 每页显示 多少 数据
    has_add_btn = True

    ordered_list = ["-id"]  # 排序的规则! 默认是 id  自己制定 -id 表示。反向 id 排序
    # 模糊查询使用,包含。  "name_contains" 表示name字段包含。。。  filter(name_contains="alex")  过滤名字中包含alex的名字
    search_list = ["name_contains"]  # 如果这里不设置的话, 就不显示搜索框

接下来就是一个 ,模糊搜索了: 先看一了 例子!:

        from django.db.models import Q, F  # 用于构造复杂的 搜索条件
        conn = Q()
        conn.connector = "OR"  # 让添加进来的条件, 做 or 判断
        conn.children.append(("name__contains", "lijie"))
        conn.children.append(("email", "lijie"))
        conn.children.append(("id__gt", 5))
        self.model_class.objects.filter(conn)  # 这样就可以根据添加进 Q 的条件, 按照 or 的方式。在数据库查询!

ORM  无法进行,复杂的搜索条件。 所以使用 django 内置的  Q 方法来做这件事!

所以重点还是  子类中需要自定制的 search_list = ["name_contains"]  这里才是决定了。 前端更够查询到的东西。

但是,这里还是, 定死了的。  如果想要更加的灵活。 以后再说:
先看看。 整体代码吧!

#! /usr/bin/env python
# -*- coding: utf-8 -*-
# __author__ = "nothing"
# Date: 2019/4/18
import functools
from types import FunctionType
from django.urls import path, re_path, reverse
from django.http import HttpResponse, JsonResponse, QueryDict
from django.shortcuts import render, redirect, reverse
from django.utils.safestring import mark_safe
from django import forms
from app01 import models
from stark.utils.pagination import Pagination
from django.db.models import Q, F  # 用于构造复杂的 搜索条件


class StarkModelForm(forms.ModelForm):
    '''
    因为,太多的地方需要使用, __init__ 初始化方式。来对每个标签添加 class="form-control" 所以搞个基类让
    要进行, 这部操作的 类去继承,
    '''

    def __init__(self, *args, **kwargs):
        super(StarkModelForm, self).__init__(*args, **kwargs)
        for name, field in self.fields.items():
            field.widget.attrs["class"] = "form-control"


class StartHandler(object):
    list_display = []

    def __init__(self, site, model_class, prve):
        self.site = site
        self.model_class = model_class
        self.prev = prve
        self.request = None

    has_add_btn = True  # 指定配置,默认显示。 用户在子类中,自定制是否显示,添加按钮

    def get_add_btn(self):
        '''预留钩子,子类中重写该方法。 根据权限的判断是否显示添加按钮'''
        if self.has_add_btn:
            # 根据别名反向生成, URL
            add_url = self.memory_url(self.get_add_url_name)
            return "<a class='btn btn-primary' href='%s'>添加</a>" % add_url
        return None

    # 用于在 a 标签。 携带本次GET 请求参数
    def memory_url(self, get_url_name, *args, **kwargs):
        '''用于反向生成url, 并且携带,get请求的参数,跳转到下一个网页'''
        name = "%s:%s" % (self.site.namespace, get_url_name)
        base_url = reverse(name, args=args, kwargs=kwargs)
        # 记录原搜索条件
        if not self.request.GET:
            add_url = base_url
        else:
            param = self.request.GET.urlencode()  # 获取到GET请求的,所有的参数。 ?page=1&age=20
            new_query_dict = QueryDict(mutable=True)
            new_query_dict["_filter"] = param
            add_url = "%s?%s" % (base_url, new_query_dict.urlencode())
        return add_url

    # 用于 跳转会原页面时,解析出 GET 请求的参数。并拼接
    def memory_reverse(self, get_url_name, *args, **kwargs):
        name = "%s:%s" % (self.site.namespace, get_url_name)
        url = reverse(name, args=args, kwargs=kwargs)
        origin_params = self.request.GET.get("_filter")
        if origin_params:
            url = "%s?%s" % (url, origin_params)
        return url

    # 用于用户自定制, 是否显示编辑按钮, 和显示的样式
    def display_edit(self, obj=None, is_header=None):
        '''
        自定义页面,显示的列,(表头和内容)
        :param obj:   数据库中每一行记录的 model对象
        :param is_header:  判断是否为表头
        :return:
        '''
        if is_header:
            return "编辑表头"
        # name = "%s:%s" % (self.site.namespace, self.get_edit_url_name)  # 拼接 stark:app01_userinfo_change
        return mark_safe("<a href='%s'>编辑</a>" %
                         self.memory_url(get_url_name=self.get_edit_url_name, pk=obj.pk))

    # 用于用户自定制, 是否显示删除按钮
    def display_del(self, obj=None, is_header=None):
        if is_header:
            return "删除表头"
        # name = "%s:%s" % (self.site.namespace, self.get_del_url_name)
        return mark_safe("<a href='%s'>删除</a>" %
                         self.memory_url(get_url_name=self.get_del_url_name, pk=obj.pk))

    def get_list_display(self):
        '''
        获取不同用户登录时, 页面应该显示的列. 使用时在子类中,重写该方法,指定list_display 要包含哪些值
        :return:
        '''
        value = []
        value.extend(self.list_display)
        return value

    ordered_list = []  # 排序规则由 用户指定。

    def get_ordered_list(self):
        return self.ordered_list or ["id", ]  # 默认使用 id 进行排序

    search_list = []  # 方便,用户自己定制

    def get_search_list(self):
        return self.search_list

    per_page = 10  # 默认每页显示,多少数据。 也可在子类中,自行定制

    def check_list_view(self, request):
        '''
        列表查看页面
        :param request:
        :return:
        '''
        # self.request = request  # 进入查看页面,为request赋值! 使其他地方可以用到!
        list_display = self.get_list_display()
        # 页面要显示的列 self.list_display  示例:['name', 'age', 'depart']

        # 1. 制作表头, 就是每张表中,每个字段写的 verbose_name.。 如何获取到这个值呢?
        # self.model_class._meta.get_field('name').verbose_name
        header_list = []  # 表头
        if list_display:
            for key_or_func in list_display:
                if isinstance(key_or_func, FunctionType):  # 判断当前参数, 是一个字符串还是一个函数。
                    verbose_name = key_or_func(self, obj=None, is_header=True)
                else:
                    verbose_name = self.model_class._meta.get_field(key_or_func).verbose_name
                header_list.append(verbose_name)
        else:
            header_list.append(self.model_class._meta.model_name)

        # ##################获取排序######################
        order_list = self.get_ordered_list()
        # ##################搜索条件######################
        search_list = self.get_search_list()  # 搜索的条件 ["name_contains", "email"]
        '''
        1. 如果 search_list 为空, 则不显示 搜索框
        2. 获取用户输入的 关键字
        3. 构造搜索条件
        '''
        search_value = self.request.GET.get("q", None)  # 获取用户发送过来的关键字,如果没有 q 这个参数。 就返回 None
        conn = Q()
        conn.connector = "OR"  # 让添加进来的条件, 做 or 判断
        if search_value:  # 接收到了, 用户的搜索才, 进行 模糊查询。 否则 啥都不干
            for item in search_list:
                conn.children.append((item, search_value))

        # 2. 处理 从数据库 取到的数据   # 用户访问的表  self.model_class
        query_set = self.model_class.objects.filter(conn).order_by(*order_list)  # 计算总数量,和 表格显示内容时,都需要,就提取出来了
        #   2.1 ###############处理分页#################
        '''1.根据用户访问页面,计算出索引的位置, 比如 page=3
            2. 生成html页码
        '''
        all_count = query_set.count()
        query_params = request.GET.copy()  # page=1&level=2
        query_params._mutable = True  # request.get中的值默认是不能被修改的。加上这句代码就可以修改了

        pager = Pagination(
            current_page=request.GET.get("page"),  # 用户访问的当前叶
            all_count=all_count,  # 数据库一共有多少数据
            base_url=request.path_info,  # 所在的url 就是 ?page=1 之前的URL
            # 用于保留,用户的请求信息,比如 level=2 被用户先选中。 那么分页后。因为查询的东西少了,分页也应该想要的减少,
            # 但是level=2这个, 请求的信息!不能因为。分页的原因。而减少。
            query_params=query_params,
            per_page=self.per_page,  # 每页显示多少数据。
        )

        #  2.1  ###############处理表格#################
        data_list = query_set[pager.start:pager.end]

        body_list = []
        for row in data_list:
            row_list = []
            if list_display:
                for key_or_func in list_display:
                    if isinstance(key_or_func, FunctionType):
                        # 这里is_header=False  obj=row(数据库中循环的每一行的对象)
                        row_list.append(key_or_func(self, obj=row, is_header=False))
                    else:
                        row_list.append(getattr(row, key_or_func))
            else:
                row_list.append(row)
            body_list.append(row_list)
        # 3 ############# 处理添加按钮####################
        add_btn = self.get_add_btn()

        return render(request, "stark/changelist.html",
                      {"header_list": header_list, "data_list": data_list,
                       "body_list": body_list,
                       "pager": pager,
                       "add_btn": add_btn,
                       "search_list": search_list,
                       "search_value": search_value})

    model_form_class = None  # 预留自定义form对象的接口

    def get_model_form_class(self):
        if self.model_form_class:
            return self.model_form_class

        class DynamicModelForm(StarkModelForm):
            class Meta:
                model = self.model_class
                fields = "__all__"

        return DynamicModelForm

    # 预留的 form 保存。自定制接口
    def save(self, form, is_update=False):
        '''
        在使用 ModelForm 保存数据之前,预留的钩子方法
        :param form:   每一个 model_class 自己的 form 对象
        :param is_update:
        :return:
        '''
        form.save()

    def add_view(self, request):
        '''
        添加页面
        :param request:
        :return:
        '''

        model_form_class = self.get_model_form_class()
        if request.method == "GET":
            form = model_form_class()
            return render(request, "stark/change.html", {"form": form})
        form = model_form_class(data=request.POST)
        if request.method == "POST":
            if form.is_valid():
                self.save(form, is_update=False)
                return redirect(self.memory_reverse(self.get_list_url_name))
            return render(request, "stark/change.html", {"form": form})

    def change_view(self, request, pk):
        '''
        编辑页面
        :param request:
        :return:
        '''
        current_change_obj = self.model_class.objects.filter(pk=pk).first()
        if not current_change_obj:
            return HttpResponse("要修改的页面不存在,请重新选择")

        model_form_class = self.get_model_form_class()
        if request.method == "GET":
            form = model_form_class(instance=current_change_obj)
            return render(request, "stark/change.html", {"form": form})
        form = model_form_class(data=request.POST, instance=current_change_obj)
        if request.method == "POST":
            if form.is_valid():
                self.save(form, is_update=False)
                return redirect(self.memory_reverse(self.get_list_url_name))
            return render(request, "stark/change.html", {"form": form})
        return HttpResponse("编辑页面")

    def delete_view(self, request, pk):
        '''
        删除页面
        :param request:
        :return:
        '''
        origin_url = self.memory_reverse(get_url_name=self.get_list_url_name)
        current_model_obj = self.model_class.objects.filter(pk=pk)
        if not current_model_obj:
            return HttpResponse("要修改的记录不存在,请重新选择")
        if request.method == "POST":
            current_model_obj.delete()
            return redirect(origin_url)
        return render(request, "stark/delete.html", {"cancel": origin_url})

    def get_url_name(self, param):
        '''
        判断是否有后缀  prev。 进行拼接URL的别名
        :param param: 页面后缀(list, add, change, del)
        :return:
        '''
        # 获取每个model_class类。所在的app_name 和 他自己的 表名称model_name
        app_label, model_name = self.model_class._meta.app_label, self.model_class._meta.model_name
        if self.prev:
            return "%s_%s_%s_%s" % (app_label, model_name, self.prev, param)  # app01/userinfo/prev/list/
        return "%s_%s_%s" % (app_label, model_name, param)  # app01/userinfo/list/

    @property
    def get_list_url_name(self):
        '''获取列表页面URL 的name'''
        return self.get_url_name("list")

    @property
    def get_add_url_name(self):
        '''获取添加页面URL 的name'''
        return self.get_url_name("add")

    @property
    def get_edit_url_name(self):
        '''获取修改页面URL 的name'''
        return self.get_url_name("change")  # app01_userinfo_change

    @property
    def get_del_url_name(self):
        '''获取删除页面URL 的name'''
        return self.get_url_name("del")

    def wrapper(self, func):
        @functools.wraps(func)  # 保留原函数的 原信息
        def inner(request, *args, **kwargs):  # 这个inner 就是,我的每一个视图函数了!
            self.request = request
            return func(request, *args, **kwargs)

        return inner

    def get_urls(self):
        partterns = [
            re_path(r"list/$", self.wrapper(self.check_list_view), name=self.get_list_url_name),
            re_path(r"add/$", self.wrapper(self.add_view), name=self.get_add_url_name),
            re_path(r"change/(?P<pk>\d+)/$", self.wrapper(self.change_view), name=self.get_edit_url_name),
            re_path(r"del/(?P<pk>\d+)/$", self.wrapper(self.delete_view), name=self.get_del_url_name),
        ]

        partterns.extend(self.extra_url())
        return partterns

    def extra_url(self):
        return []


class StartSite(object):
    def __init__(self):
        self._registry = []
        self.app_name = "stark"
        self.namespace = "stark"

    def register(self, model_class, handler_class=None, prev=None):
        '''

        :param model_class:  是model中数据库相关类。 接受一个类而不是对象
        :param handler_class: 处理请求的视图函数,所在的类
        :param prev:  生成url 的前缀
        :return:
        '''

        if handler_class is None:
            handler_class = StartHandler  # 做个默认的Handler

        self._registry.append(
            {'model_class': model_class, "handler": handler_class(self, model_class, prev), "prev": prev})
        '''
        [
            {'model_class':models.Depart, "handler":DepartHandler(models.Depart, prev),"prev": prev},
            {'model_class':models.UserInfo, "handler":UserInfoHandler(models.UserInfo, prev),"prev": prev},
            {'model_class':models.Host, "handler":HostHandler(models.Host, prev),"prev": prev},
        ]
        '''

    def get_urls(self):
        partterns = []
        for item in self._registry:
            model_class = item["model_class"]
            handler = item["handler"]
            prev = item["prev"]
            # 获取当前model_class所在的app名字 # 获取当前model_class的类名,小写
            app_label, model_name = model_class._meta.app_label, model_class._meta.model_name
            if prev:
                partterns.append(
                    re_path(r"%s/%s/%s/" % (app_label, model_name, prev), (handler.get_urls(), None, None)))
            else:
                partterns.append(re_path(r"%s/%s/" % (app_label, model_name), (handler.get_urls(), None, None)))
        return partterns

    @property
    def urls(self):
        '''模拟include的返回值'''
        return (self.get_urls(), self.app_name, self.namespace)


def get_choice_txt(title, field):
    '''
    对于 Stark组件中定义列时, choice如果想要显示中文信息,调用此方法即可。
    :param title:   希望页面上显示的表头
    :param field:  字段名称
    :return:
    '''

    def inner(self, obj=None, is_header=None):
        '''
        :param self:
        :param obj:  StarkHandler 里面列表视图函数 中 循环出的每一个 model对象
        :param is_header:
        :return:
        '''
        if is_header:
            return title
        method = "get_%s_display" % field
        return getattr(obj, method)()  # 从model对象中,根据这个字符串。找到这个方法。 并执行。 拿到中文结果后, 返回

    return inner


site = StartSite()
\stark\servers\start_v1.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# __author__ = "nothing"
# Date: 2019/4/18
from django.urls import re_path
from stark.servers.start_v1 import site, StartHandler, get_choice_txt, StarkModelForm
from django.http import HttpResponse, JsonResponse
from django.utils.safestring import mark_safe
from django.urls import reverse
from app01 import models
from django import forms


class DepartHandler(StartHandler):
    '''定义增删改查'''
    list_display = ["id", "title", StartHandler.display_edit, StartHandler.display_del]
    # def extra_url(self):  # 自定制,新增URL
    #     return [
    #         re_path("detail/(\d+)/$", self.detail_view)
    #     ]
    #
    # def detail_view(self):
    #     return HttpResponse("详情页")


site.register(models.Depart, DepartHandler)


class UserInfoModelForm(StarkModelForm):
    class Meta:
        model = models.UserInfo
        fields = ["name", "gender", "age", "depart"]


class UserInfoHandler(StartHandler):
    list_display = ["name", "age", "depart", get_choice_txt("性别", "gender"), StartHandler.display_edit,
                    StartHandler.display_del]
    per_page = 10  # 重订 每页显示 多少 数据
    has_add_btn = True

    ordered_list = ["-id"]
    # 模糊查询使用,包含。  "name_contains" 表示name字段包含。。。  filter(name_contains="alex")  过滤名字中包含alex的名字
    search_list = ["name__contains"]  # 如果这里不设置的话, 就不显示搜索框

    # model_form_class = UserInfoModelForm
    #
    # def save(self, form, is_update=False):
    #     form.instance.pwd = 123
    #     form.save()

    # def get_add_btn(self):
    #     pass

    # def get_list_display(self):
    #     '''预留的自定义扩展。调用父类方法,返回一个包含 模型表字段的 列表。 并且可以根据用户的不同,显示不同的列
    #     此方法,不能够和  list_display  列表共同使用。 此方法会覆盖list_display'''
    #     return ["depart", self.display_edit]


site.register(models.UserInfo, UserInfoHandler)
\app01\stark.py
{% extends "layout.html" %}

{% block content %}
    <div class="luffy-container">
        <h3>数据列表</h3>
        {% if add_btn %}
            <div style="float: left">{{ add_btn|safe }}</div>
        {% endif %}

        {% if search_list %}
            <div style="float: right; margin-bottom: 5px">
                <form method="get" class="form-inline">
                    <div class="form-group">
                        <input type="text" name="q" value="{{ search_value }}" placeholder="关键字搜索" class="form-control">
                        <button type="submit" class="btn-primary btn">
                            <i class="fa fa-search" aria-hidden="true"></i>
                        </button>
                    </div>
                </form>
            </div>
        {% endif %}

        <table class="table table-bordered">
            <thead>
            <tr>
                {% for head in header_list %}
                    <th>{{ head }}</th>
                {% endfor %}
            </tr>
            </thead>
            <tbody>
            {% for row in body_list %}
                <tr>
                    {% for ele in row %}
                        <td>{{ ele }}</td>
                    {% endfor %}
                </tr>
            {% endfor %}
            </tbody>
        </table>
    </div>

    <nav>
        <ul class="pagination">
            {{ pager.page_html|safe }}
        </ul>
    </nav>
{% endblock %}
\stark\changelist.html