stark

Posted leiyiming

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了stark相关的知识,希望对你有一定的参考价值。

stark组件

介绍:

? stark组件,是一个帮助开发者快速实现数据库表的增删改查。

目标:

? 10s 钟完成一张表的增删改查

前戏

项目启动前加载指定文件
  • Django 启动时,在执行 url.py之前执行一个指定程序

    # 在已注册的APP中 app.py 里面
    # django 内部有两个线程在运行,一个是运行Django,一个是检测代码是否发生变化
    #runserver   --norload  不启动检测线程
    class appconfig(appconfig):
        name = ‘app‘
        def ready(self):
            # 路由加载之前 在已注册的所有APP中自动寻找xxxx.py文件,并导入
            # 如果执行两次,是因为Django内部自动重启导致
            autodiscover_modules("xxxx")
    
    # 提示:
    	如果xxxx.py 执行的代码向“某个神奇的地方”放入了一些值,之后的路由加载时,可以去‘某个神奇的地方‘读取原理的值
    
单例模式
  • 一个实例,一个对象

    # 多实例
    class foo(object):
        pass
    obj1 = foo()
    print(obj1)       #<__main__.foo object at 0x02B9E670>
    obj2 = foo()
    print(obj2)       #<__main__.foo object at 0x02B9E538>   这是两个实例
    
  • 通过python 模块导入的方式,实现单例模式

    xx.py
    class AdminSite(object):
        pass
    
    site = AdminSite() # 为AdminSite类创建了一个对象(实例)
    
    
    app.py
    import utils
    print(utils.site)
    import utils
    print(utils.site)
    
    """
    在Python中,如果已经导入过的文件再次被重新导入时候,python不会再重新解释一遍,而是选择从内存中直接将原来导入的值拿来用。
    """
    
  • 补充

    • 如果以后存在一个单例模式的对象,可以先在此对象中放入一个值,然后在其他的文件中导入该对象,通过对象再次将值获取到

?

Dango路由分发的本质,include
  • 方式一

    from django.conf.urls import url,include
    
    urlpatterns = [
    	url(url(r‘^web/‘, include(‘app01.urls‘)),   
        # include 返回一个元组 (urlconf_module, app_name, namespace))
    ]
    
  • 方式二

    # include 函数主要返回有三个元素的元组
    from django.conf.urls import url,include
    from app01 import urls
    urlpatterns = [
    	url(r‘^web/‘, (urls, app_name, namespace)),
    ]
    

开始:

1.创建 Django project

2.创建基础业务表

  1. app01/models.py
    部门表
    用户表

  2. app02/models.py
    主机表

3.对以上的三张表做增删改查

a.分析
  1. 为每张表创建4个url

  2. 为每张表创建4个视图函数

    # 避免url重复,默认以应用名为开头
    app01/models.py
        Depart
            /app01/depart/list/
            /app01/depart/add/
            /app01/depart/edit/(d+)/
            /app01/depart/del/(d+)/
    
        UserInfo
            /app01/userinfo/list/
            /app01/userinfo/add/
            /app01/userinfo/edit/(d+)/
            /app01/userinfo/del/(d+)/
    app02/models.py
        Host
            /app02/host/list/
            /app02/host/add/
            /app02/host/edit/(d+)/
            /app02/host/del/(d+)/
    
    
b.为app中的每个model类自动创建URL以及相关视图函数
  1. 将视图提取到基类
    # v1.py
    class StarkSite(object):
        def __init__(self):
            self._registry = []
            self.app_name = ‘stark‘
            self.namespace = ‘stark‘
    
        def register(self, model_class, handler_class):
            """
            :param model_class: 是models中的数据库表对应的类。 models.UserInfo
            :param handler_class: 处理请求的视图函数所在的类
            :return:
            """
            self._registry.append({‘model_class‘: model_class, ‘handler‘: handler_class(model_class)})
    
        def get_urls(self):
            patterns = []
            for item in self._registry:
                model_class = item[‘model_class‘]
                handler = item[‘handler‘]
                app_label, model_name = model_class._meta.app_label, model_class._meta.model_name
                # model_class._meta.app_label 获取models类所在的app名称
                # model_class._meta.model_name 获取models类的名称
                # patterns.append(url(r‘x1/‘,lambda request:HttpResponse("1")),)
                patterns.append(url(r‘%s/%s/list/$‘ % (app_label, model_name,), handler.changelist_view))
                patterns.append(url(r‘%s/%s/add/$‘ % (app_label, model_name,), handler.add_view))
                patterns.append(url(r‘%s/%s/change/(d+)/$‘ % (app_label, model_name,), handler.change_view))
                patterns.append(url(r‘%s/%s/del/(d+)/$‘ % (app_label, model_name,), handler.change_view))
    
            return patterns
    
        @property
        def urls(self):
            return self.get_urls(), self.app_name, self.namespace
    site = StarkSite()
    
    #url.py
    from django.contrib import admin
    from django.urls import path
    from stark.service.v1 import site
    print(site._registry)
    urlpatterns = [
        path(‘admin/‘, admin.site.urls),
        path(‘^stark/‘, site.urls),  #(site.get_urls(), site.app_name, site.namespace)
    ]
    
  2. URL分发扩展&后缀

  3. 为url设置别名

  4. url的别名进行重新生成

c.定制页面显示的列
  1. 基本列表页面的定制

  2. 未定义list_display字段的页面,默认显示对象

    class StarkHandler(object):
        list_display = []
        def changelist_view(self, request):
            if list_display:
                for i in list_display:
                pass
            else:
                pass
    
  3. 为页面显示的列预留一个钩子函数

    class StarkHandler(object):
        list_display = []
        def get_list_display(self):
            value = []
            value.extend(self.list_display)
            return value
        def changelist_view(self, request):
            list_display = self.get_list_display()
            if list_display:
                for i in list_display:
                pass
            else:
                pass
    
  4. 为页面提供自定义显示的函数
    v1.py

    class StarkHandler(object):
            def display_edit(self, obj=None, is_header=None):
            """
            自定义页面显示的列(表头和内容)
            :param obj:
            :param is_header:
            :return:
            """
            if is_header:
                return "编辑"
            name = "%s:%s" % (self.site.namespace, self.get_change_url_name,)
            return mark_safe(‘<a href="%s">编辑</a>‘ % reverse(name, args=(obj.pk,)))
        # 根据名称空间,别名,反向生成url
    
        def display_del(self, obj=None, is_header=None):
            if is_header:
                return "删除"
            name = "%s:%s" % (self.site.namespace, self.get_delete_url_name,)
            return mark_safe(‘<a href="%s">删除</a>‘ % reverse(name, args=(obj.pk,)))
        def changelist_view(self, request):
                    if list_display:
                for key_or_func in list_display:
                    if isinstance(key_or_func, FunctionType):
    				# key_or_func虽然本来是hander中的方法,
    				#但在传入时是以函数传入的,所以要把self也传入
                        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)
    

    stark.py

    from stark.service.v1 import site, StarkHandler,get_choice_text
    class UserInfoHandler(StarkHandler):
        list_display = [‘name‘,
                        ‘age‘, ‘email‘, ‘depart‘,
                        StarkHandler.display_edit,
                        StarkHandler.display_del]
    site.register(models.UserInfo,UserInfoHandler)
    
  5. 应用

d.后台应用模板样式
e.列表页面添加分页的功能
f.添加按钮
  • 如何显示添加按钮

  • 如何添加按钮的url

    from django.http import QueryDict
    import functools
    class StarkHandler(object):
        list_display = []
        def __init__(self, site, model_class, prev):
            self.site = site
            self.model_class = model_class
            self.prev = prev
            self.request = None
            self.has_add_btn = True # 默认要显示添加按钮
          
        def reverse_add_url(self):
            """
            生成带有原搜索条件的添加URL
            :return:
            """
            name = "%s:%s" % (self.site.namespace, self.get_add_url_name,)
            base_url = reverse(name)
            if not self.request.GET:
                add_url = base_url
            else:
                param = self.request.GET.urlencode()
                new_query_dict = QueryDict(mutable=True)
                new_query_dict[‘_filter‘] = param
                add_url = "%s?%s" % (base_url, new_query_dict.urlencode())
            return add_url
        
        def get_add_btn(self):
            # 返回添加按钮,href 通过 reverse_add_url()获得反向生成的url
            if self.has_add_btn:
                # 判断是否要显示添加按钮
                return "<a class=‘btn btn-primary‘ href=‘%s‘>添加</a>" 
            					% self.reverse_add_url()
            return None
    
        def changelist_view(self, request):
    		add_btn = self.get_add_btn()
            return render(request,‘stark/changelist.html‘,
                {‘add_btn‘: add_btn,})
    
        def wrapper(self, func):
            @functools.wraps(func)
            # @functools.wraps 保留原函数的参数信息
            def inner(request, *args, **kwargs):
                # 通过装饰器,使每一个请求进入视图函数后都能保存自己的request
                self.request = request
                return func(request, *args, **kwargs)
            return inner
        
        def get_urls(self):
            patterns = [
                url(r‘^list/$‘, self.wrapper(self.changelist_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
    
    
  • 添加页面进行添加数据
    可以添加显示的字段,接口供用户扩展
    需求 :少显示字段,保存时,减少的字段也得设置,否则存储出错

    from django import forms
    
    class StarkModelForm(forms.ModelForm):
        def __init__(self, *args, **kwargs):
            super(StarkModelForm, self).__init__(*args, **kwargs)
            # 统一给ModelForm生成字段添加样式
            for name, field in self.fields.items():
                field.widget.attrs[‘class‘] = ‘form-control‘
    
    class StarkHandler(object):
        
        
        def save(self, form, is_update=False):
            """
            在使用ModelForm保存数据之前预留的钩子方法
            """
            form.save()
            
        model_form_class = None
        def get_model_form_class(self):
            if self.model_form_class:
                # 如果自定义了model_form_class,及用自定义的,如果没有,用默认的
                return self.model_form_class
    
            class DynamicModelForm(StarkModelForm):  #继承StarkModelForm,来继承bootstrap样式
                class Meta:
                    model = self.model_class  # 根据当前的model,来显示
                    fields = "__all__"
    
            return DynamicModelForm
        
        def reverse_list_url(self):
            name = "%s:%s" % (self.site.namespace, self.get_list_url_name,)
            base_url = reverse(name)
            param = self.request.GET.get(‘_filter‘)
            if not param:
                return base_url
            return "%s?%s" % (base_url, param,)  
    
        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)
            # 验证传回的form表单信息
            if form.is_valid():
                self.save(form, is_update=False)
                # 在数据库保存成功后,跳转回列表页面(携带原来的参数)。
                return redirect(self.reverse_list_url())
            return render(request, ‘stark/change.html‘, {‘form‘: form})
    
    

    stark.py

    class UserInfoModelForm(StarkModelForm):
        class Meta:
            model = models.UserInfo
            fields = [‘name‘, ‘gender‘, ‘classes‘, ‘age‘, ‘email‘]
            
    class UserInfoHandler(StarkHandler):
        model_form_class = UserInfoModelForm
        # 自定义要渲染的form表单,由于在显示的时候可以
    site.register(models.UserInfo,UserInfoHandler) 
    
g.编辑功能实现

? 根据pk值,来展示编辑页面,所有要传入pk
? 看到的东西有默认值

class StarkHandler(object):
    list_display = []

    def display_edit(self, obj=None, is_header=None):
        """
        自定义页面显示的列(表头和内容)
        """
        if is_header:
            return "编辑"
        return mark_safe(‘<a href="%s">编辑</a>‘ % self.reverse_change_url(pk=obj.pk))    
    def reverse_change_url(self, *args, **kwargs):
        """
        # 在原基础上, *args, **kwargs 
        生成带有原搜索条件的编辑URL
        :param args:
        :param kwargs:
        :return:
        """
        name = "%s:%s" % (self.site.namespace, self.get_change_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()
            new_query_dict = QueryDict(mutable=True)
            new_query_dict[‘_filter‘] = param
            add_url = "%s?%s" % (base_url, new_query_dict.urlencode())
        return add_url    
    
    # 在最开始的时候要获取到pk值
    def get_urls(self):
        # 正则获取pk值
        patterns = [
            url(r‘^list/$‘, self.wrapper(self.changelist_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
    
    
    def change_view(self, request, pk):
        """
        编辑页面
        :param request:
        :param pk:
        :return:
        """
        current_change_object = self.model_class.objects.filter(pk=pk).first()
        if not current_change_object:
            return HttpResponse(‘要修改的数据不存在,请重新选择!‘)

        model_form_class = self.get_model_form_class()
        if request.method == ‘GET‘:
            form = model_form_class(instance=current_change_object)
            # 获取当前models的form组件来渲染form表单
            return render(request, ‘stark/change.html‘, {‘form‘: form})
        form = model_form_class(data=request.POST, instance=current_change_object)
        if form.is_valid():
            self.save(form, is_update=False)
            # 在数据库保存成功后,跳转回列表页面(携带原来的参数)。
            return redirect(self.reverse_list_url())
        return render(request, ‘stark/change.html‘, {‘form‘: form})
h.删除
class StarkHandler(object):    
    def display_del(self, obj=None, is_header=None):
        if is_header:
            return "删除"
        return mark_safe(‘<a href="%s">删除</a>‘ % self.reverse_delete_url(pk=obj.pk))    
    def reverse_delete_url(self, *args, **kwargs):
        """
        生成带有原搜索条件的删除URL
        :param args:
        :param kwargs:
        :return:
        """
        name = "%s:%s" % (self.site.namespace, self.get_delete_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()
            new_query_dict = QueryDict(mutable=True)
            new_query_dict[‘_filter‘] = param
            add_url = "%s?%s" % (base_url, new_query_dict.urlencode())
        return add_url
    def delete_view(self, request, pk):
        """
        删除页面
        :param request:
        :param pk:
        :return:
        """
        origin_list_url = self.reverse_list_url()
        if request.method == ‘GET‘:
            return render(request, ‘stark/delete.html‘, {‘cancel‘: origin_list_url})

        self.model_class.objects.filter(pk=pk).delete()
        return redirect(origin_list_url)


4.其它功能

a.排序
    order_list = []

    def get_order_list(self):
        return self.order_list or [‘-id‘, ]

    def changelist_view(self, request):
        """
        列表页面
        :param request:
        :return:
        """

        # ########## 1. 获取排序 ##########
        order_list = self.get_order_list()
        queryset = self.model_class.objects.all().order_by(*order_list)    
        # 以下的数据都经过 排序,以下都用queryset
b.模糊搜索

实现思路:
在页面设置form表单,搜索:以get形式提交到后台,后台获取数据然后进行筛选

? 在后端获取关键字后,根据

from django.db.models import Q    
    search_list = [‘name__contains‘,] # 如果不加__contains就算精确查找

    def get_search_list(self):
        return self.search_list
    def changelist_view(self, request):
        """
        列表页面
        :param request:
        :return:
        """
         # ########## 0. 模糊查询 ##########
        #1,如果search_list 中没有值,则不显示搜索框
        #2.获取用户提交的关键字
        # 3.构造条件
        # Django Q 对象,用户构造复杂的orm查询条件
        search_list = self.get_search_list()
        search_value = request.GET.get(‘q‘, ‘‘)
        conn = Q()
        conn.connector = ‘OR‘
        if search_value:
            # 如果用户没有搜索,就不构造
            for item in search_list:
                conn.children.append((item, search_value))

        # ########## 1. 获取排序 ##########
        order_list = self.get_order_list()
        queryset = self.model_class.objects.filter(conn).order_by(*order_list)
  # filter.all()变成了filter(conn)
c.批量操作
  • 添加checkbox列

  • 生成批量操作的按钮

  • 在display_list中添加一列

  • 将函数传入 html,中,函数会自动执行

  • 将提交的函数名,通过反射来找函数执行

  • 扩展,handler的函数预留参数接口 *args, **kwargs

  • 扩展,点击批量操作的执行后,能够跳转某个页面去查看某些内容

    change_listhtml.

    {% if action_dict %}
    // 如果下拉框有值才显示,
        <div style="float: left;margin: 5px 10px 5px 0;">
            <div class="form-inline">
                <div class="form-group">
                    <select class="form-control" name="action">
                        <option value="">请选择操作</option>
                        {% for func_name,func_text in action_dict.items %}
                            <option value="{{ func_name }}">{{ func_text }}</option>
                        {% endfor %}
                    </select>
                    <input class="btn btn-primary" type="submit" value="执行"/>
                </div>
            </div>
        </div>
    {% endif %}
    
        def display_checkbox(self, obj=None, is_header=None):
            """
            :param obj:
            :param is_header:
            :return:
            """
            if is_header:
                return "选择"
            return mark_safe(‘<input type="checkbox" name="pk" value="%s" />‘ % obj.pk)
        
        
        action_list = []  #在里面放上函数
        def get_action_list(self):
            return self.action_list
        
        def action_multi_delete(self, request, *args, **kwargs):
            """
            批量删除(如果想要定制执行成功后的返回值,那么就为action函数设置返回值即可。)    return redirect(‘html‘)
            :return:
            """
            pk_list = request.POST.getlist(‘pk‘)
            self.model_class.objects.filter(id__in=pk_list).delete()
    
        action_multi_delete.text = "批量删除"        
    
        def changelist_view(self, request, *args, **kwargs):
            """
            列表页面
            :param request:
            :return:
            """
            # ########## 1. 处理Action ##########
            action_list = self.get_action_list()
            # 将函数传入 html,中,函数会自动执行,所以将函数改为字典
            action_dict = {func.__name__: func.text for func in action_list}  		  # {‘multi_delete‘:‘批量删除‘,‘multi_init‘:‘批量初始化‘}
    
            if request.method == ‘POST‘:
                action_func_name = request.POST.get(‘action‘)
                if action_func_name and action_func_name in action_dict:
                    # 用户提交的函数必须是我们定义过的,避免用户修改html代码来破坏
                    action_response = getattr(self, action_func_name)(request, *args, **kwargs)
                    if action_response:
                        # 如果函数有返回值,就证明要跳转到某个页面
                        return action_response
    
    
d.组合搜索
  • 什么是组合搜索

  • 如何实现组合搜索

    • 实现思路

      • 第一步:根据字段找到其关联的数据,choice,FK,M2M

      • 第二步:根据配置获取关联数据,不写,就不显示

      • 第三步:根据配置获取相关数据(含条件)

      • 第四步:

      • 第五步:为组合

        • 生成url时,不影响其他组影响、

          1. 手动拼接,"/stark/app01/userinfo/list" + ‘?depart=1‘
          2. 根据request.get来生成,每点击一次,都能生成根据这次点击,而形成的url
                      query_dict = self.rquests.copy()
                      query_dict._mutable = True
                      query_dict[‘depart‘] = 1
          
          

      ?

      class SearchGroupRow(object):
          def __init__(self, title, queryset_or_tuple, option):
              """
      		# 将querysert,或元组,组装成一个统一的可迭代对象
              :param title: 组合搜索的列名称
              :param queryset_or_tuple: 组合搜索关联获取到的数据
              :param option: 配置
              """
              self.title = title
              self.queryset_or_tuple = queryset_or_tuple
              self.option = option
      
          def __iter__(self):
              yield ‘<div class="whole">‘
              yield self.title
              yield ‘</div>‘
      
              yield ‘<div class="others">‘
              yield "<a>全部</a>"
              for item in self.queryset_or_tuple:
                  text = self.option.get_text(item)
                  yield "<a href=‘#‘>%s</a>" % text 
                  #此处写死,不易扩展,比如要对字符串进行加工,所以取一个函数返回值
      
              yield ‘</div>‘
      
      
      
      class Option(object):
          def __init__(self, field, db_condition=None, text_func=None):
              """
              :param field: 组合搜索关联的字段
              :param db_condition: 数据库关联查询时的条件
              :param text_func: 此函数用于显示组合搜索按钮页面文本
              """
              self.field = field
              if not db_condition:
                  db_condition = {}
              self.db_condition = db_condition
              self.text_func = text_func
      
              self.is_choice = False
      
          def get_db_condition(self, request, *args, **kwargs):
              return self.db_condition
      
          def get_queryset_or_tuple(self, model_class, request, *args, **kwargs):
              """
              根据字段去获取数据库关联的数据
              :return:
              """
              # 根据gender或depart字符串,去自己对应的Model类中找到 字段对象
              field_object = model_class._meta.get_field(self.field)
              title = field_object.verbose_name
              # 获取关联数据
              if isinstance(field_object, ForeignKey) or isinstance(field_object, ManyToManyField):
                  # FK和M2M,应该去获取其关联表中的数据: QuerySet
                  db_condition = self.get_db_condition(request, *args, **kwargs)
                  return SearchGroupRow(title, field_object.rel.model.objects.filter(**db_condition), self)
              else:
                  # 获取choice中的数据:元组
                  self.is_choice = True
                  return SearchGroupRow(title, field_object.choices, self)
      
          def get_text(self, field_object):
              """
              获取文本函数,
              :param field_object:
              :return:
              """
              if self.text_func:
                  return self.text_func(field_object)
      
              if self.is_choice:
                  return field_object[1]
      
              return str(field_object)
      
      
                  
                  
      class StarkHandler(object): 
          search_group = []
          def get_search_group(self):
              return self.search_group
          
          def changelist_view(self, request, *args, **kwargs):
              """
              列表页面
              :param request:
              :return:
              """
              search_group_row_list = []
              search_group = self.get_search_group()  
              # [‘gender‘, ‘depart‘]
              for option_object in search_group:
                  # 根据gender或depart 字符串,去自己对应的model类中找到字段对象
                  option_object.get_queryset_or_tuple(self.model_class, request, *args, **kwargs)    
                  search_group_row_list.append(row)
      

      stark.py

      class UserInfoHandler(StarkHandler):
          search_group = [
              Option(‘gender‘),
              MyOption(‘depart‘, {‘id__gt‘: 2}),
          ]
      
      site.register(models.UserInfo, UserInfoHandler)
      

总结:

列表页面

添加页面

编辑页面

删除页面

关键字搜索

批量操作

组合搜索

保留原函数的信息

后台make_safe,和前端管道符safe效果一样

以上是关于stark的主要内容,如果未能解决你的问题,请参考以下文章

Rescue-Prime hash STARK 代码解析

STARK Arithmetization

Django——stark组件

STARK Low Degree Testing——FRI

Polygon zkEVM的pil-stark Fibonacci状态机代码解析

crm——stark组件核心原理