zoukankan      html  css  js  c++  java
  • 自定义 Django admin 组件

     

     摘要:学习 Django admin 组件,仿照源码的逻辑,自定义了一个简易的 stark 组件,实现类似 admin 的功能。

        可自动生成 url 路由,对于model 有与之相应的配置类对象,可进行增、删、改、查的操作。

        通过继承 ModelStark 配置类,重写类参数和方法,可自定义查找显示信息,模糊查询字段,批量操作方法等


    那么让我们开始吧~

    思路:

      在启动时------>  1、遍历所有app的stark.py文件,注册model

              2、自动生成,注册的model对应的url

      执行时 ------->  1、url路由分发,调用配置类中相应的方法

             2、在 ModelStark 配置类中处理数据、渲染页面 

             3、返回 Responce 对象


    具体实现:

        一、先创建一个组件 stark

                  

      二、并在组件的 app.py 文件中,写下遍历的其他APP的方法

    from django.apps import AppConfig
    from django.utils.module_loading import autodiscover_modules
    
    
    class StarkConfig(AppConfig):
        name = 'stark'
    
        def ready(self):
            #这句表示 自动遍历所有app 的 stark.py 文件
            autodiscover_modules('stark')    

       注意:在settings.py 文件中 ,注册 stark 组件时,一定要写上,只写 ‘stark’ 的话,不会执行ready的

    INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config',
    "stark.apps.StarkConfig"
    ]

        三、 在 组件的 stark.py文件中

        定义一个 单例的 全局类 StrakSite ,该类控制以及路由的生成,并调用配置类,生成第二级的路由

    class StrakSite:
        def __init__(self, name='stark'):
            self.name = name
            # 注册了的所有表与表的配置类对象
            self._registry = {}
    
        def register(self, model, model_config=None):
            if not model_config:
                #如果没有自定义配置类,就用父类的配置类
                model_config = ModelStark
    
            self._registry[model] = model_config(model)
    
        #  URL分发 第一级
        def get_urls(self):
            temp = []
            for model, model_config in self._registry.items():
                app_label = model._meta.app_label
                model_name = model._meta.model_name
                temp.append(url(r'^%s/%s/' % (app_label, model_name), model_config.urls))
            return temp
    
        @property
        def urls(self):
                        #路由分发
            return self.get_urls(), None, None



    # 单例模式 所有的model共享同一个StrakSite对象
    site = StrakSite()

         配置类,在python代码里生成前端HTML标签:    

    class ModelStark:
        list_display = ['__str__']  # 列表时,定制显示的列。
        modelfoem_class = []  # 自定义配置类
        list_display_links = []  # list页面点击可以跳转到编辑页面的字段
        search_fields = []  # 搜索框,自定义的搜索字段
        actions = []  # 自定义批处理函数列表
        list_filter = []  # 自定义 右侧筛选字段
    
        def __init__(self, model):
            self.model = model
            self.app_label = model._meta.app_label
            self.model_name = model._meta.model_name
    
            meta= model._meta
            print(meta)
    
        #  生成复选框
        def checkbox_col(self, obj=None, is_header=False):
            '''
            生成复选框
            :param is_hander:
            :return:
            '''
            if is_header:
                return '选择'
            # _url=
            return mark_safe('<input type="checkbox" name="selected_action" value="%s">' % obj.pk)
    
        # 反向解析,获取URL
        def get_url(self, type, id=None):
            '''
            反向解析,获取URL
            :param type:
            :param id:
            :return:
            '''
            name = '%s_%s_%s' % (self.app_label, self.model_name, type)
            if id:
                return reverse(name, args=(id,))
            else:
                return reverse(name)
    
        # 生成删除按钮
        def del_col(self, obj=None, is_header=False):
            '''
            生成删除按钮
            :param is_hander:
            :return:
            '''
            if is_header:
                return '删除'
            _url = self.get_url('delete', obj.pk)
            return mark_safe('<a id="%s_delete" href="%s">删除</a>' % (self.model_name, _url))
    
        # 生成添加a标签
        def add_col(self):
            '''
            生成添加按钮
            :param is_hander:
            :return:
            '''
            _url = self.get_url('add')
            return mark_safe('<a id="%s_add" href="%s"><span class="glyphicon glyphicon-plus pull-lift"></span></a>'
                             % (self.model_name, _url))
    
        # 生成编辑按钮
        def edit_col(self, obj=None, is_header=False):
            '''
            生成编辑按钮
            :param is_hander:
            :return:
            '''
            if is_header:
                return '编辑'
            _url = self.get_url('edit', obj.pk)
            return mark_safe('<a id="%s_edit" href="%s">编辑</a>' % (self.model_name, _url))
    
        # 按照表格显示需要的列格式,将每个字段按顺序放进列表里
        def get_new_list_display(self):
            new_display = []
            new_display.extend(self.list_display)
            # 这里一定要传 类名.方法名  如果用self.方法名的话
            # 调用的时候 这里传入的方法会自动传self值,而我们自定义的display方法并不会,所以在通用方法处理时会报错
            new_display.insert(0, ModelStark.checkbox_col)
            new_display.extend([ModelStark.edit_col, ModelStark.del_col])
            return new_display
    
        #
        def list(self, request):
    
            self.data_list = self.model.objects.all()
            # 列表展示页面类
            show = ShowList(self, request, self.data_list)
    
            # 如果有自定义查询字段且输入了查询内容 该操作要写在分页之前,因为对data_list进行了修改
            self.search_info = request.GET.get("search")
            if self.search_fields and self.search_info:
                show.get_search(self.search_info)
    
            # 批量操作
            action_list = show.get_actions()
            if request.method == "POST":
                action_info = request.POST.get("action")
                if self.actions and action_info:
                    id_list = request.POST.getlist("selected_action")
                    data = self.model.objects.filter(pk__in=id_list)
                    action = getattr(self, action_info)
                    action(request,data)
    
            # 生成左侧筛选栏  该操作要写在分页之前,因为对data_list进行了修改
            filter_html = None
            if self.list_filter:
                filter_html = show.get_filter()
    
            # 分页HTML
            page_html = show.get_page_html()
            # 表头
            title_list = show.get_hander()
            # 表格内容
            table_list = show.get_body()
            # 生成 添加a标签
            add_opp = self.add_col()
    
            return render(request, 'stark/list.html',
                          {'title_list': title_list,
                           'table_list': table_list,
                           'page_html': page_html,
                           'add_opp': add_opp,
                           'action_list': action_list,
                           'search_about': (self.search_fields, self.search_info),
                           'filter_html': filter_html,
                           })
    
        #
        def add(self, request):
    
            if request.method == 'GET':
                # 生成modelform对象
                form = self.modelform_class()
    
                from django.forms.boundfield import BoundField
                # bfield 是 BoundField类的子类
                for bfield in form:
                    if isinstance(bfield.field, ModelChoiceField):
                        bfield.is_pop = True
                        # 得到该字段的关联表
                        filed_rel_model = self.model._meta.get_field(bfield.name).rel.to
                        app_label = filed_rel_model._meta.app_label
                        model_name = filed_rel_model._meta.model_name
                        name = '%s_%s_add' % (app_label, model_name)
                        _url = reverse(name)
                        bfield.url = _url + "?pop_back_id=" + bfield.auto_id
                return render(request, 'stark/add.html', {'form': form})
            else:
                # 得到带参数的modelform对象
                form = self.modelform_class(request.POST)
    
                from django.forms.boundfield import BoundField
                # bfield 是 BoundField类的子类
                for bfield in form:
                    if isinstance(bfield.field, ModelChoiceField):
                        bfield.is_pop = True
                        # 得到该字段的关联表
                        filed_rel_model = self.model._meta.get_field(bfield.name).rel.to
                        app_label = filed_rel_model._meta.app_label
                        model_name = filed_rel_model._meta.model_name
                        name = '%s_%s_add' % (app_label, model_name)
                        _url = reverse(name)
                        bfield.url = _url + "?pop_back_id=" + bfield.auto_id
    
                        # 校验页面填值
                if form.is_valid():
                    # 保存添加的数据
                    obj = form.save()
    
                    try:
                        pop_back_id = request.GET.get('pop_back_id')
                        if pop_back_id:
                            return render(request, 'stark/close.html',
                                          {'pop_back_id': pop_back_id, 'text': str(obj), 'pk': obj.pk})
                    except Exception:
                        pass
    
                    # 跳转回list页面
                    _url = self.get_url('list')
                    return redirect(_url)
                else:
                    return render(request, 'stark/add.html', {'form': form})
    
        #
        def delete(self, request, id):
            self.model.objects.get(pk=id).delete()
            _url = self.get_url('list')
            return redirect(_url)
    
        #
        def edit(self, request, id):
            model_obj = self.model.objects.get(pk=id)
            if request.method == 'GET':
                # 生成一个带有model对象内容的modelform对象
                form = self.modelform_class(instance=model_obj)
                return render(request, 'stark/edit.html', {'form': form})
            else:
                form = self.modelform_class(request.POST, instance=model_obj)
                if form.is_valid():
                    form.save()
                    _url = self.get_url('list')
                    return redirect(_url)
                else:
                    return render(request, 'stark/edit.html', {'form': form})
    def extra_urls(self):
            return []
    
        #  URL分发 第二级
        def get_urls(self):
            temp = []
    
            #
            temp.append(url(r'add/$', self.add,
                            name='%s_%s_add' % (self.app_label, self.model_name)))
            #
            temp.append(url(r'(?P<id>d+)/delete/$', self.delete,
                            name='%s_%s_delete' % (self.app_label, self.model_name)))
            #
            temp.append(url(r'(?P<id>d+)/edit/$', self.edit,
                            name='%s_%s_edit' % (self.app_label, self.model_name)))
            #
            temp.append(url(r'$', self.list,
                            name='%s_%s_list' % (self.app_label, self.model_name)))
    
            temp.extend(self.extra_urls())
    
            print(temp)
    
            return temp
    
        @property
        def urls(self):
            return self.get_urls(), None, None
    
        # 获取modelform类
        @property
        def modelform_class(self):
            if self.modelfoem_class:
                return self.modelfoem_class
            else:
                class ModelFormClass(forms.ModelForm):
                    class Meta:
                        model = self.model
                        fields = '__all__'
    
                return ModelFormClass

         因为查询页面太大,所以单独抽成一个ShowList类,其中封装了list页面的表头、表格内容、分页、搜索栏、批量操作栏、标签查找栏等

    class ShowList(object):
        def __init__(self, conf_obj, request, data_list):
            self.conf_obj = conf_obj
            self.data_list = data_list
            self.new_list_display = self.conf_obj.get_new_list_display()
            self.request = request
    
        # 获取列表的表头
        def get_hander(self):
            # 表头
            title_list = []
            for field in self.new_list_display:
                if isinstance(field, str):
                    # 如果没有自己传list_display进来,默认的是'__str__',则打印大写的表名
                    if field == '__str__':
                        field = self.conf_obj.model_name.upper()
    
                    else:
                        # 获取字段的对象
                        field_obj = self.conf_obj.model._meta.get_field(field)
                        # 从字段对象中获取字段的verbose_name,在model模型类里写的
                        field = field_obj.verbose_name
                else:
                    # 如果不是表字段的话,执行对应的方法 eg: edit delete
                    field = field(self.conf_obj, is_header=True)
                title_list.append(field)
            return title_list
    
        # 获取表格内容
        def get_body(self):
            table_list = []
            for obj in self.data_list:
                list_info = []
                for field in self.new_list_display:
                    if isinstance(field, str):
                        try:
                            if self.conf_obj.model._meta.get_field(field).choices:
                                field_val = getattr(obj, 'get_%s_display' %field )
                            else:
                                field_val = getattr(obj, field)
                            if field in self.conf_obj.list_display_links:
                                _url = self.conf_obj.get_url('edit', obj.pk)
                                field_val = mark_safe(
                                '<a id="%s_edit" href="%s">%s</a>' % (self.conf_obj.model_name, _url, field_val))
                        except Exception as e:
                            #  __str__ 的字段
                            field_val = getattr(obj, field)
                    else:
                        field_val = field(self.conf_obj, obj=obj)
                    list_info.append(field_val)
                table_list.append(list_info)
            return table_list
    
        # 获取自定义分页的THML
        def get_page_html(self):
            # 来源url
            url_prefix = self.request.get_full_path()
            # 数据总量
            total_num = self.data_list.count()
            # 当前页
            current_page = self.request.GET.get('page')
            # 生成页面对象
            self.page_obj = Page(total_num, current_page, url_prefix, per_page=10, show_page_num=9)
            # 得到当前页展示的数据列表
            self.data_list = self.data_list[self.page_obj.data_start:self.page_obj.data_end]
            # 得到对应的分页HTML
            page_html = self.page_obj.page_html()
            return page_html
    
        # 搜索栏操作
        def get_search(self, search_info):
            search_condition = Q()
            search_condition.connector = "or"
            for field in self.conf_obj.search_fields:
                # 模糊查询
                search_condition.children.append((field + "__icontains", search_info))
            self.data_list = self.conf_obj.model.objects.filter(search_condition)
    
        # 自定义批量操作
        def get_actions(self):
            temp = []
            for action in self.conf_obj.actions:
                temp.append({
                    'name': action.__name__,
                    'desc': action.desc
                })
            return temp
    
        # 右侧筛选栏
        def get_filter(self):
    
            self.params = copy.deepcopy(self.request.GET)
            self.params['page'] = None
            self.params.pop("page")
    
            filter_dic = {}
            filter_condition = Q()
            for k, v in self.params.items():
                if k in self.conf_obj.list_filter and v != 'all':
                    filter_dic[k] = v
                    filter_condition.children.append((k, v))
                    self.data_list = self.data_list.filter(filter_condition)
    
            temp = {}
            for filter in self.conf_obj.list_filter:
                filter_field_obj = self.conf_obj.model._meta.get_field(filter)
                val_html = []
                queryset = filter_field_obj.rel.to.objects.all()
    
                self.params[filter] = 'all'
                val_html.append('<a href="?%s">ALL</a>' % (self.params.urlencode()))
    
                for obj in queryset:
                    self.params[filter] = obj.pk
                    if (filter in filter_dic) and (filter_dic[filter] == str(obj.pk)):
                        val_html.append('<a class="red href="?%s">%s</a>' % (self.params.urlencode(), str(obj)))
                    else:
                        val_html.append('<a href="?%s">%s</a>' % (self.params.urlencode(), str(obj)))
    
                temp[filter] = val_html
            return temp
    ShowList

         

      四、HTML页面和,静态文件,以及工具文件(mypage分页)也都放在组件里,在查找时,最外层的找不到,会直接到组件里找对应的文件,所以可以放在组件里

             

    Django 之 modelForm (edit.html页面的编写)

    自定义分页

     


    使用方法: 

         在app的 stark.py 文件里,  

             1、 注册 model,生成对应的url 
         2、 #自定义配置类
              list_display = ["title", "price", "publish"]     #配置该表显示的字段列
              list_display_links = ["title"]        #配置点击哪些字段可以跳转到编辑页面 
              search_fields = ["price"]      #配置哪些字段可以作为可模糊的字段
              list_filter = ["publish"]         #右侧边分类栏,可分类现实数据

            #自定义 批量操作方法
    from django.shortcuts import HttpResponse, render, redirect
    
    from app01.models import *
    from django.http import JsonResponse
    
    #注册 model,生成对应的url
    site.register(User)
    site.register(Role)
    
    #自定义配置类
    class BookConfig(ModelStark):
        list_display = ["title", "price", "publish"]     #配置该表显示的字段列
        list_display_links = ["title"]        #配置点击哪些字段可以跳转到编辑页面 
        search_fields = ["price"]      #配置哪些字段可以作为可模糊的字段
        list_filter = ["publish"]         #右侧边分类栏,可分类现实数据
    
        #自定义 批量操作方法
        def delete_action(self, queryset):       
            queryset.delete()
        
         #批量操作方法的名称
        delete_action.desc = "批量删除"
        
        def init_price_action(self, queryset):
            queryset.update(price=100.0)
    
        init_price_action.desc = "批量初始化"
    
       #批量操作方法列表
        actions = [delete_action, init_price_action]
    
    #注册该类,将对应的配置类传入
    site.register(models.Book, BookConfig)

         #自定义列  

         #自定义视图

         #自定义url   

    class StudentConfig(ModelStark):
          #自定义列 方法
        def class_display(self, obj=None, is_header=False):
            if is_header:
                return "已报班级"
            temp = []
            for i in obj.class_list.all():
                temp.append(str(i))
            return ",".join(temp)
         
         #自定义列
        def score_display(self, obj=None, is_header=False):
            if is_header:
                return "学习详情"
            return mark_safe("<a href='/stark/app01/student/score/%s'>学习详情</a>" % obj.pk)
        
         #自定义列 方法列表
        list_display = ["customer", class_display, score_display]
        #自定义视图
        def score_view(self, request, sid):
    
            if request.is_ajax():
                sid = request.GET.get("sid")
                cid = request.GET.get("cid")
                # 查询学生sid在班级cid在的所有成绩
                ret = StudentStudyRecord.objects.filter(student=sid, classstudyrecord__class_obj=cid).values_list(
                    "classstudyrecord__day_num", "score")
                print("ret", ret)
    
                data = [["day%s" % i[0], i[1]] for i in ret]
                return JsonResponse(data, safe=False)
    
            student_obj = Student.objects.filter(pk=sid).first()
            class_list = student_obj.class_list.all()
    
            return render(request, "score_view.html", locals())
        
        #自定义url
        def extra_urls(self):
            temp = []
            temp.append(
                url("score/(d+)", self.score_view)
            )
    
            return temp
    
    site.register(Student, StudentConfig)
  • 相关阅读:
    函数
    文件的基本操作
    c语言程序设计案例教程(第2版)笔记(一)—零散、输入输出、最小公倍数、选择排序、冒泡排序
    c语言中的rand()函数用法
    c语言 error C4996: 'strupr': The POSIX name for this item is deprecated. Instead, use the ISO C and C++ conformant name
    Python之列表生成式、生成器
    Python之迭代器
    Python之装饰器
    Linux之线程相关命令及常用命令
    重写、重构、重载区别
  • 原文地址:https://www.cnblogs.com/95lyj/p/9397944.html
Copyright © 2011-2022 走看看