zoukankan      html  css  js  c++  java
  • python Stark 组件

    Django Admin 是对model中 对应的数据表进行增删改查的组件,对每个APP下面已注册的model表进行增删改查。而stark组件仿照Admin组件开发。

    Admin的常用配置:

     Filedstes

      使用方法和效果如下图

     

    Action

      使用方法和效果图

       需要在My_AppConfig(admin.ModeAdmin)中编写自定义函数

      需要注意的:(1)该函数在select下拉框中 作为value值

            (2)该函数有两个参数,分别是request、queryset

            (3)前端的复选框,前端整合成一个字典。字典的键:复选框的name,字典的值:列表类型,存储valu

    定义的foo会在前端action的select 下拉框,函数名当作value

    点击go ,根据select下拉框中的value,值,就是刚才定义的函数名发送后端,后端拿到这个值,找到这个函数并执行。

    Admin 的使用两个场景:

      1)admin 配置url

      2)注册model

      注意:每个APP下有个admin模块

    使用流程

    1、当django项目启动时,admin应用首先扫描(加载)每个APP下面的每个admin.py 文件

    自执行函数,autodiscover()执行扫描,扫描

    2、每个admin文件导入admin模块时,生成site对象、

      注意:admin.site.registry(Book),注册时,_registry{}字典保存的键是model 中的类名,值是model的管理类的AdminClass类

    3、使用site对象注册model 中的类名

    4、生成已注册model类的增删改查url

     知识点:
    1、同一模块重复调用,文件只加载一次
    2、基于模块的单例模式,第一次生成对象时,会生成一个内存空间_instance,当第二次生成时,_instance有值,会把该对象的内存指向上一次对象的内存空间

    路由分发

    一条url有正则和相对应的函数组成,路由分发则是把对应的函数转变成一个元组。元组包括一个列表,和两个None值

    仿照admin开发的过程中,目的是生成model对应的url,需要获取该model所在APP的名字和mode的名字.

    使用my_model._meta.model_name 获取

    自定义stark组件仿照admin:注意,在stark apps.py 配置文件中的类中,增加一个函数

        def ready(self):
            autodiscover_modules("stark_admin")

    该函数的意义:——执行文件下的read方法——扫描startk文件

     注意:static 放在项目下面,需配置staticfiles 放在应用下面,无须配置。django扫描每个项目下面的static文件

    request 请求:请求的url ,可以使用request.GET获取数据
    每个APP对应增删改查URL

    admin的两个类

    Starksit ModelStark ,前者负责生成一级路由,后者生成二级路由。而且后者负责增删改查的功能。首先看下stark 组件主要有哪些功能:1)路由分发,涉及到两个类,就是刚才提到的。2)路由分发、action、edit_filter、list_filter、list_display、model_form_class(根据表名生成的表单)、list_display_link、search_fields 、下面详细介绍各个部分。

    路由分发

    StarkSite 部分

    class StarkSite(object):
        def __init__(self):
            self._registry ={}
        def register(self,model,model_config=None):
            if not model_config:
                model_config = ModelStark
            self._registry[model]=model_config(model,self)
    
    
        def get_urls(self):
            tmp = []
            for model,model_config in self._registry.items():
                model_name = model._meta.model_name
                app_name = model._meta.app_label
                u = url('^{model_name}/{app_name}/'.format(model_name=model_name,app_name=app_name),(model_config.urls,None,None)) #设置路由分发
                tmp.append(u)
            return tmp
    
        @property
        def urls(self):
            return self.get_urls(),None,None
    
    site = StarkSite()

    ModelStark部分

    class ModelStark(object):
    #设计二级分发url
        def get_urls(self):
            """
            如何避免model_stark url下面的每个model产生相同的url
            :return: 二级分发的url
            """
            tmp = []
            model_name = self.model._meta.model_name     //获取model的名称
            app_name = self.model._meta.app_label       //获取model所在APP的名字
            app_model = (app_name,model_name)
            tmp.append(url('^$', self.show_list,name="%s_%s_show_list"%app_model))  /避免model_stark url下面的每个model产生相同的url
            tmp.append(url('^(d+)/change/$', self.change_view,name="%s_%s_change"%app_model))
            tmp.append(url('^add/$', self.add_view,name="%s_%s_add"%app_model))
            tmp.append(url('^(d+)/delete/$', self.delete_view,name="%s_%s_delete"%app_model))
            return tmp
    
        @property
        def urls(self):
            return self.get_urls()

    url的注意地方:

    1)StarkSite urls函数返回的是一个元组,包括url列表 和APP的名字  

    2)url的软编码:获取增删改查相对应的软编码,reverse 的使用方法

    reverse("%s_%s_delete" % self.app_model_name, args=(obj.pk,))

    list_display && edit_filter

    list_dispaly 是显示在前端的字段,用户可以自定义,否则,显示默认字段。这样ModelStark需要一个默认字段,list_display=['__str__']。这样在循环用户的list_display列表时,如果用户没有定义,就会遇到“__str__”字段,代表显示一个model的表头名称。由于在ModelStark的show_list 方法中显示前端数据封装至一个ChangeList方法中。ChangeList方法初始化时需要传入三个参数(config,request,queryset)分别代表Model_stark的对象,request请求,queryset是查询的数据,后面其他功能使用到。list_display 中可以添加一个函数名。

    前端显示数据分为两个步骤:

    ModelConfig 的show_list 显示头部、body 部分封装至一个类中,ChangeList中

    1)列表body head的代码如下

    class ChangeList(object):
        """
        封装了查看视图的方法
        """
        def __init__(self,config,request,queryset):
            self.config = config                     #相当于modelstark的本身
            self.request = request                   #每个ChangeList都有一个request
            self.queryset =queryset
            params = self.request.GET
            page_num = self.request.GET.get("page", 1)
            total_count = self.queryset.count()
            base_url = request.path_info
            self.pagination = Pagination(page_num, total_count, base_url, params)
            self.obj_list = self.queryset[self.pagination.start:self.pagination.end]
            self.actions = self.config.get_action()
    
            self.list_filter = self.config.list_filter
    
        def heard(self):                             #获取头部信息
            header_list = []
            for filed in self.config.get_list_display():
                if callable(filed):
                    var = filed(self, is_header=True)  #头部信息,is_hearder设置为True,函数返回函数名称
                    header_list.append(var)
                else:
                    if filed == "__str__":  #如果是__str__字段就获取该model对象的名称
                        header_list.append(self.config.model._meta.model_name.upper())#该字段的名字大写
                    else:
                        filed_obj = self.config.model._meta.get_field(filed)#获取该字段的对象
                        header_list.append(filed_obj.verbose_name)#获取该字段的名字
            return header_list
        def body(self):
            new_data_list = []
            for obj in self.obj_list:
                tem = []
                for filed in self.config.get_list_display():
                    if callable(filed):
                        val = filed(self.config, obj)
                    else:
                        val = getattr(obj, filed)
                        if filed in self.config.list_display_link:
                            val = self.config.get_link_tag(self.request, obj, val)#这里传入的self.request 是ModelConfig实例对象
    
                    tem.append(val)
                new_data_list.append(tem)
            return new_data_list
        #右侧定义过滤字段
        def get_filter_link_tage(self):
            """
            显示遍历用户定义的filter 列表,找到每个filter 字段,然后获取该字段对象,
            然后把该对象字段传入自己创建的类中,获取该字段所有的对象。
            再次for循环通过该字段获取的所有对象,循环过程中,传入外层for循环的字段对象,判断该对象的字段类型,
            然后返回该对象的a标签,该标签的href包含该对象的字段名称和该对象的id
            :return:
            """
            for filter_field_name in self.list_filter:  ##list_filter = ["state","publish","authors"]遍历用户定义的字段
                print("filter_field_name",filter_field_name)
                current_id = int(self.request.GET.get(filter_field_name,0)) #获取前端的url后缀参数
                print("current_id",current_id)
                filter_field_obj = self.config.model._meta.get_field(filter_field_name)#获取该字段对象
                print("filter_field_obj",filter_field_obj)
                filter_field = FilterField(filter_field_name,filter_field_obj)#创建字段对象
                print("*"*120,filter_field)
                def inner(filter_field,current_id):
                    for obj in filter_field.get_data():
                        if isinstance(filter_field.filter_field_obj,ForeignKey) or isinstance(filter_field.filter_field_obj,ManyToManyField):
                            params = copy.deepcopy(self.request.GET)
                            params._mutable = True
                            params[filter_field.filter_field_name] = obj.pk
                            if obj.pk== current_id:
                                yield mark_safe("<a class='active' href='?%s'>%s<a>"%(params.urlencode(),obj))
                            else:
                                yield mark_safe("<a  href='?%s'>%s</a>"%(params.urlencode(),obj))
    
    
                        elif filter_field.filter_field_obj.choices:
    
                            params = copy.deepcopy(self.request.GET)
                            params._mutable = True
                            params[filter_field.filter_field_name] = obj[0]
    
                            if current_id ==obj[0]:
                                yield mark_safe("<a class='active' href='?%s'>%s</a>"%(params.urlencode(),obj[1]))
                            else:
                                yield mark_safe('<a href="?%s">%s</a>'%(params.urlencode(),obj[1]))
                        else:pass
                yield inner(filter_field,current_id)
    View Code

    2)上图用到的get_list_display()方法如下:主要作用是把用户自定义和ModelConfig 中的list_display合并,给所有数据默认添加一个 编辑,删除 字段

     # 封装list_display方法
        def get_list_display(self):
            new_list_display = []
            new_list_display.extend(self.list_display)
            if not self.list_display_link:  # 如果用户没有定义超链接字段,就添加编辑字段。超链接字段和可编辑字段显示一个就可以
                new_list_display.append(ModelStark.edit)
            new_list_display.append(ModelStark.delete)
            new_list_display.insert(0, ModelStark.check_box)
    
            return new_list_display
    get_list_display

    3)用的编辑、删除、添加字段

    #编辑按钮
        def edit(self, obj=None, is_header=False):
            if is_header:
                return "编辑"
            return mark_safe("<a href='%s'>编辑</a>" % self.edit_url(obj))
    
        # 删除按钮
        def delete(self, obj=None, is_header=False):
            if is_header:
                return "删除"
            # 如何反向解析url
            return mark_safe("<a href='%s'>删除</a>" % self.delete_url(obj))
        #添加按钮
        def add(self, obj=None, is_header=False):
            if is_header:
                return "添加"
            return mark_safe("<a href='%s'>添加</a>" % self.add_url())
    View Code

    4)生成表头是用到callable,判断是否为一个函数。下面是编辑、删除按钮的函数,表头显示,传入heard参数。

     #编辑按钮
        def edit(self, obj=None, is_header=False):
            if is_header:
                return "编辑"
            return mark_safe("<a href='%s'>编辑</a>" % self.edit_url(obj))
    
        # 删除按钮
        def delete(self, obj=None, is_header=False):
            if is_header:
                return "删除"
            # 如何反向解析url
            return mark_safe("<a href='%s'>删除</a>" % self.delete_url(obj))
        #添加按钮
        def add(self, obj=None, is_header=False):
            if is_header:
                return "添加"
            return mark_safe("<a href='%s'>添加</a>" % self.add_url())
    View Code

    5)再次判断该字段是否是一个超链接字段

    if filed in self.config.list_display_link:
        val = self.config.get_link_tag(self.request, obj, val)#这里传入的self.request 是ModelConfig实例对象

    超链接字段url后缀(list_filter),在跳转时可以记录上次的url后缀。下面代码设置超链接字段

    #编辑字段设置url后缀
        def get_link_tag(self,request, obj, val):
            """
            :param request:
            :param obj:
            :param val:
            :return:返回一个带有?list_fileter后缀的
            """
            params = request.GET
            import copy
            params = copy.deepcopy(params)
            params._mutable = True
    
            from django.http import QueryDict
    
            query_dict = QueryDict(mutable=True)
    
            query_dict["list_filter"] = params.urlencode()  # qd: {"list_filter":"a%21341%1234b%21322"}
    
            suffix_url = mark_safe("<a href='%s?%s'>%s</a>" % (self.edit_url(obj), query_dict.urlencode(), val))#跳转时保存上次的url
    
            return suffix_url
    get_link_tag

    model_form_class

    添加 编辑用到的modelform

    思路设计:ModeForm 设置一个属性(model_form_class),判断用户是否定义了该属性,如果没有定义使用自身的ModeForm

    #设置ModelForm
        def get_model_form(self):
            from django.forms import ModelForm
            from django.forms import widgets
            class ModelFormClass(ModelForm):
                class Meta:
                    model = self.model
                    fields = "__all__"
            if not self.model_form_class:
                return ModelFormClass
            else:return self.model_form_class
    ModelForm

    URL 对应的添加、编辑视图函数使用到ModeForm,两个视图函数如下。功能:提供表单

    # 编辑视图函数
        def change_view(self,request,id):
            ModelFormClass = self.get_model_form()
            obj = self.model.objects.filter(id=id)[0]
            if request.method=="POST":
                form= ModelFormClass(request.POST,instance=obj)
                if form.is_valid():
                    form.save()
                    params =request.GET.get("list_filter")
                    url = "%s?%s"%(self.show_list_url(),params) #这里传入page,视图函数可以需要根据当前页码来生成数据
                    return redirect(url)
                else:
                    return render(request, "stark/change.html", {"form": form})
            form = ModelFormClass(instance=obj)
            return render(request,"stark/change.html", locals())
    
        # 添加视图函数
        def add_view(self,request):
            ModelFormClass =self.get_model_form()
            if request.method=="POST":
                form= ModelFormClass(request.POST)
    
                if form.is_valid():
                    form.save()
                    return redirect(self.show_list_url())
                else:
                    return render(request, "stark/add.html", locals())
            form = ModelFormClass()
            add_url = self.add_url()
            return render(request,"stark/add.html", locals())
    编辑、添加视图函数

    注意:1)编辑函数,取出url后缀。跳转url需要添加 刚才取到的页码参数,此时传送的page 会在ChangeList类中使用到

    页面状态保留

    跳转时保存上次的url?如:修改一条数据时,跳转到编辑页面,编辑完成后,返回到列表面保存上次的url
    request 请求:请求的url ,可以使用request.GET获取数据

    search_fields

    查询:q的两个条件是“or”的关系

    #查询函数
        def search_fucn(self):
            search_condition = Q()
            if self.search_fields:#如果用户配置了筛选字段
                search_condition.connector = 'or'  #查询:q的两个条件是“or”的关系
                search_keyword = self.request.GET.get("search")
                if search_keyword:
                    for search_field in self.search_fields:
                        search_condition.children.append((search_field+"__contains", search_keyword))
    
            return search_condition  #如果没有查询条件返回空的Q,

    show_list函数:前端提交查询,为GET方式

    前端无法取函数对象的属(前端传的函数名是字符串类型的,不能直接找到该函数),后端处理函数名和函数的描述信息,所以使用getattr的方式获取。

    Action

    action和之前定义的list_display等一样,同样的需要在 ModelConfig 中定义一个默认的action。ModelConfig的默认action中包含一个delete函数

     #前端删除hans
        def patch_delete(self,queryset):
            queryset.delete()
            return True
        patch_delete.desc = "批量删除"  //函数对象设置一个desc属性
    
    
    #用户的action
        def get_action(self):
            temp = []
            temp.extend(self.action)
            temp.append(ModelStark.patch_delete)
            return temp
    ModelStark & action

     上面的代码,把用户自定义和默认的action综合一起,但是在前端不能直接循环取得函数的属性,所以在ChangeList类中重新构建action的数据结构

    class ChangeList(object):
        """
        封装了查看视图的方法
        """
        def __init__(self,config,request,queryset):
          、、、、
        
            self.actions = self.config.get_action()
        、、、、
         #action 数据结构
        def handle_actions(self):
            tmp=[]
            for action_func in self.actions:
                tmp.append({"name":action_func.__name__,"desc":action_func.desc})
            return tmp   #在前端可以去到函数名和函数属性
    View Code

    前端批量选择对象时,触发刚才的action函数,这时show_list 以“POST”方式接受。

     # 显示数据列表
        def show_list(self,request):
            self.request =request  #这里的self.request 是在查询函数中使用,没有这个将会报错
            if request.method == "POST":
                func_name = request.POST.get("action",1)
                pk_list = request.POST.getlist("_selected_action")
                queryset = self.model.objects.filter(pk__in=pk_list)
                func = getattr(self,func_name)
                ret = func(queryset)
                if ret:
                    return HttpResponse("OK")
                else:return HttpResponse("失败")
    View Code

    Filter

    filter 功能,需要用户自定义过滤字段,否则不显示该功能。在ModelStark中 list_filter 默认为空的列表。

    list_filter中存放的使用户想在前端展示的过滤字段如:

    list_filter = ["state","publish","authors"]

    下面介绍展示每个字段经历的过程:

    这里需要用到一个参数两个类。ChangeList下的 get_filter_link_tage_two 函数。FilterField 类 、LinkTagGen类

    get_filter_link_tage_two负责:循环listfilter中的用户定义字段,调用 FilterField、LinkTagGen 类

     # 右侧定义过滤字段--第二版
        def get_filter_link_tage_two(self):
            for filter_field_name in self.list_filter:  #list_filter = ["state","publish","authors"]遍历用户定义的字段
    
                filter_field_obj = self.config.model._meta.get_field(filter_field_name)  # 获取该字段对象
    
                filter_field = FilterField(filter_field_name, filter_field_obj, self.config)  # 创建字段对
                val = LinkTagGen(filter_field.get_data(), filter_field, self.request)#参数分别是:字段对象的所有数据,字段对象,request
                yield val
    View Code

    注意:使用get_field 获取字段对象。然后把字段对象、字段名称传入FilterField, 获得该对象的数据。FilterField产生的数据,放入LinkTagGen 类中。LinkTagGen根据数据类型生成a标签

    FilterField

    class FilterField(object):
        def __init__(self, filter_field_name, filter_field_obj, config):
            self.filter_field_name = filter_field_name                  #字段名称
            self.filter_field_obj = filter_field_obj                    #字段对象
            self.config = config                                        #相当于modelstark的本身
    
        def get_data(self):
            if isinstance(self.filter_field_obj, ForeignKey) or isinstance(self.filter_field_obj, ManyToManyField):
                return self.filter_field_obj.rel.to.objects.all()[:3]
            elif self.filter_field_obj.choices:
                return self.filter_field_obj.choices
            else:
                return self.config.model.objects.values_list("pk", self.filter_field_name)
    获取字段对象的数据
    FilterField,字段对象的所有数据,字段对象,传送给LinkTagGen类

    LinkTagGen

    该类定义了一个iter 方法。该方法的作用:迭代的返回一个数据,节省内存资源。

    iter 生成标签,循环字段对象的所有数据。根据字段的类型判断是否加active,以及把上一次的url后缀保存下来

    class LinkTagGen(object):
        def __init__(self, data, filter_field, request):
            self.data = data
            self.fiter_field = filter_field
            self.request = request
    
        def __iter__(self):
            current_id = self.request.GET.get(self.fiter_field.filter_field_name, '0')
    
            params = copy.deepcopy(self.request.GET)
            params._mutable = True
            if params.get(self.fiter_field.filter_field_name):  # 如果有值说明我是点击其他的按钮,“全部“按钮就没有必要加后缀
    
                del params[self.fiter_field.filter_field_name]
                _url = "%s?%s" % (self.request.path_info, params.urlencode())
                yield mark_safe("<a href='%s'>全部</a>" % _url)
            else:
                yield mark_safe("<a class='active' href='#'>全部</a>")
            for item in self.data:
                pk, obj = None, None
                if isinstance(self.fiter_field.filter_field_obj, ForeignKey) or isinstance(
                        self.fiter_field.filter_field_obj, ManyToManyField):
                    pk, obj = str(item.pk), item
                elif self.fiter_field.filter_field_obj.choices:
                    pk, obj = str(item[0]), item[1]   #注意这里需要转换数据类型
                else:
                    pk, obj =item[1], item[1]
    
                params[self.fiter_field.filter_field_name] = pk
                _url = "%s?%s" % (self.request.path_info, params.urlencode())
                if current_id == pk:
    
                    link_tage = "<a class='active' href='%s'>%s</a>" % (_url, obj)
                else:
                    link_tage = "<a href='%s'>%s</a>" % (_url, obj)
                yield mark_safe(link_tage)
    View Code

    阿萨德发

     

    2、

  • 相关阅读:
    FTP Protocol
    File Operations
    Ubuntu Install Chinese Input Method
    Vim Intro
    ISA Introduction
    Fourier Transform
    Process/Thread Synchronization
    Process Synchronization-Example 2
    leetcode 栈和队列类型题
    leetcode 字符串类型题
  • 原文地址:https://www.cnblogs.com/huyangblog/p/8590682.html
Copyright © 2011-2022 走看看