Xadmin组件的构建

详情见:

Xadmin组件构建之增删改查 

Xadmin组件构建之分页、search查询与action批量操作

Xadmin组件构建之filter、pop

----------------------------------------------------------------------------------------- 

相关知识:url分发

搭建一个类似admin功能的Xadmin组件

    'Xadmin.apps.XadminConfig',
    'app01.apps.App01Config',
    'app02.apps.App02Config',

 

Xadmin实现流程:

(1) 启动:在Django执行的这一刻把每一个叫Xadmin.py文件都加载一遍 

#Xadmin/apps.py
from django.apps import AppConfig
from django.utils.module_loading import autodiscover_modules

class XadminConfig(AppConfig):
    name = 'Xadmin'

    def ready(self):   #当加载这个类的时候,这个方法自动执行
        autodiscover_modules('Xadmin')  #通过此步设置,Django一启动就是扫描每一个叫Xadmin.py的文件

 (2) 注册、url的设计与构建表单数据 

urls.py

from Xadmin.service.Xadmin import site

urlpatterns = [ url(r'^Xadmin/', site.urls), ]

Xadmin/service/Xadmin.py 

from django.conf.urls import url
from django.shortcuts import HttpResponse, render, redirect
from django.urls import reverse
from django.db.models import Q
from django.utils.safestring import mark_safe
from django.db.models.fields.related import ManyToManyField,ForeignKey

from Xadmin.utils.page import Pagination


# 构建表头数据和构建表单数据本应该放在ModelXadminl类下面list_view视图函数中,但数据太多放在里面会会显得杂乱
# 这里定义一个类专门用来在页面展示数据,把ModelXadminl类中的self以及list_view函数中的data_list和request三个参数传过来
class show_list(object):
    def __init__(self, config, data_list, request):
        self.config = config  # config代表传过来的self, self.config就是ModelXadminl类中的实例化对象self
        self.data_list = data_list
        self.request = request
        # 分页
        data_count = self.data_list.count()
        current_page = int(self.request.GET.get("page", 1))
        base_path = self.request.path

        self.pagination = Pagination(current_page, data_count, base_path, self.request.GET, per_page_num=4, pager_count=11, )
        self.page_data = self.data_list[self.pagination.start:self.pagination.end]

        # actions
        self.actions=self.config.new_actions() # actions里面装的是函数,每个表都有默认的批量删除[patch_delete,]

    def get_filter_linktags(self):
        print("list_filter",self.config.list_filter)    # list_filter ['title','publish', 'authors']
        link_dic={}
        import copy

        for filter_field in self.config.list_filter:  # list_filter ['title',publish', 'authors']
            params = copy.deepcopy(self.request.GET)
            print("111",params)#第一次访问book页面时:<QueryDict: {}>

            cid=self.request.GET.get(filter_field,0) #默认取0,

            print("filter_field:",filter_field)   # filter_field: publish
            filter_field_obj=self.config.model._meta.get_field(filter_field) #取模型表中的字段对象
            print("filter_field_obj:",filter_field_obj)   #filter_field_obj: app01.Book.publish
            print(type(filter_field_obj))  #<class 'django.db.models.fields.related.ForeignKey'>

            #filter_field_obj.rel 打印的是:<ManyToOneRel: app01.book>          取该字段当前的模型表
            #filter_field_obj.rel.to 打印的是:<class 'app01.models.Publish'>   取该字段相关联的模型表
            # print("rel...",filter_field_obj.rel.to.objects.all())              #取该字段相关联的模型表中的所有数据
            #rel... <QuerySet [<Publish: 苹果出版社>, <Publish: 光子出版社>]>
            if isinstance(filter_field_obj,ForeignKey) or isinstance(filter_field_obj,ManyToManyField):
                # data_list=filter_field_obj.rel.to.objects.all() #一对一、多对多字段
                data_list = filter_field_obj.remote_field.model.objects.all()

            else:  #普通字段
                data_list=self.config.model.objects.all().values("pk",filter_field)
                print("data_list:",data_list)  # <QuerySet [{'pk': 1, 'title': '北京折叠'},……]}

            temp=[]

            # 处理 全部标签
            if params.get(filter_field):
                del params[filter_field]
                temp.append("<a href='?%s'>全部</a>"%params.urlencode())
            else:
                temp.append("<a  class='active' href='#'>全部</a>")

            # 处理 数据标签
            for obj in data_list:  #循环字段关联模型表中的数据<QuerySet [<Publish: 苹果出版社>, <Publish: 光子出版社>]>
                if isinstance(filter_field_obj, ForeignKey) or isinstance(filter_field_obj, ManyToManyField):
                    pk=obj.pk
                    text=str(obj)
                    params[filter_field] = pk  # params里面没有filter_field+"__id"键,添加键值,有键,覆盖其值
                else:# data_list= [{"pk":1,"title":"go"},....]
                    print("========")
                    pk=obj.get("pk")
                    text=obj.get(filter_field)
                    params[filter_field] =text

                print(params)  #第一次循环打印结果:<QueryDict: {'publish': [1]}>

                _url=params.urlencode()    #把{'publish': [1]}转成publish=1

                if cid==str(pk) or cid==text:
                    link_tag = "<a class='active' href='?%s'>%s</a>" % (_url, text)  # 路径前没加/,从当前访问的路径开始拼接

                else:
                    link_tag = "<a href='?%s'>%s</a>" % (_url, text)

                temp.append(link_tag)
            link_dic[filter_field]=temp
        print("link_dic:",link_dic)
        #link_dic: {'publish': ["<a  class='active' href='#'>全部</a>",
                 # "<a href='?publish__id=1'>苹果出版社</a>", "<a href='?publish__id=2'>光子出版社</a>"],
                 # 'authors': ["<a  class='active' href='#'>全部</a>",
                 # "<a href='?authors__id=1'>xiaohei</a>", "<a href='?authors__id=2'>xiaobai</a>"]}

        return link_dic


    #定义一个函数,构建数据结构:temp=[ "name": "patch_init","desc":"批量初始化"]
    def get_action_list(self):
        temp=[]
        for action in self.actions:
           temp.append({
               "name":action.__name__,          #取函数的名称:   "patch_init"
               "desc":action.short_description   # 取函数的描述:"批量初始化"
           })

        return temp

    def get_header(self):
        # 构建表头数据
        header_list = []
        print("header", self.config.new_list_display())  # [check,"nid","title","publish","price",edit,delete]

        for field in self.config.new_list_display():
            if isinstance(field, str):
                if field == "__str__":  # 说明是默认的样式,展示大写表名
                    val = self.config.model._meta.model_name.upper()
                else:
                    field_obj = self.config.model._meta.get_field(field)  # 获取表中字段对象
                    val = field_obj.verbose_name  # 获取字段中定义的名称

            else:  # 说明是定义的函数
                val = field(self.config, is_header=True)  # 获取表头,传is_header=True

            header_list.append(val)
        print(header_list)  # ["<input id='choice' type='checkbox'>", ' 编号', '书籍名称', 'publish', 'price', '操作', '操作']
        return header_list

    def get_body(self):
        # 构建表单数据
        new_data_list = []
        for obj in self.page_data:  # data_list:<QuerySet [<Book: 北京折叠>, <Book: 三体>]>
            temp = []
            for field in self.config.new_list_display():  # ["__str__"] [check,"nid","title","publish","price","authors",edit,delete]

                if isinstance(field, str):  # 判断字段是不是str类型
                    try:#为了捕捉__str__,防止报错
                        field_obj = self.config.model._meta.get_field(field) #拿模型表字段对象
                        # 判断是不是多对多字段(把authors取出来)
                        if isinstance(field_obj, ManyToManyField):
                            ret = getattr(obj, field).all()  #取出所有作者 :<QuerySet [<Author: xiaohei>, <Author: xiaobai>……]>
                            t=[]
                            for mobj in ret:  #千万记住多层循环的时候循环名称不要重复这里是obj和mobj
                                t.append(str(mobj))
                            val=",".join(t)
                        else:
                            if field_obj.choices:#如果当前字段对象有choices属性
                                #相当于把((1, '男'), (2, '女'))中的男或女拿出来
                                val = getattr(obj, "get_"+field+"_display")
                            else:
                                val = getattr(obj, field)  # 由字符串找对象的属性,相当于obj.field,也就是把'nid',title',''publish',price'拿出来了
                            if field in self.config.list_display_links:
                                # "app01/userinfo/(\d+)/change"
                                _url = self.config.get_change_url(obj)

                                val = mark_safe("<a href='%s'>%s</a>" % (_url, val))
                    except Exception as e:  # 如果出错误说明是__str__
                        val = getattr(obj, field)

                else:
                    val = field(self.config, obj)  # 如果字段是函数,把当前处理的对象传给当前的函数(这一步为了拿到obj.pk,
                    # 然后拼接路径,比如我们在点击“编辑”的时候跳转到编辑界面,这里就用到了pk),执行后拿到返回结果

                temp.append(val)

            new_data_list.append(temp)

        '''
        new_data_list=[
            [check,1,北京折叠",苹果出版社,<a href=''>编辑</a>,<a href=''>删除</a>],
            [check,2,"三体", 苹果出版社,<a href=''>编辑</a>,<a href=''>删除</a>],
        ]
        '''
        return new_data_list


# 定义每张表的配置类样式
class ModelXadmin(object):
    list_display = ["__str__", ]  # 走默认的样式类的话默认显示"__str__"内容
    list_display_links = []  # 控制点哪个字段进入编辑界面,如果里面添加'publish'则点击publish就能进入编辑界面
    modelform_class = []
    search_fields = []
    actions = []
    list_filter=[]

    # 定制action操作:批量删除--定义到父类中,面对所有的表都有这个默认操作
    def patch_delete(self, request, queryset):
        queryset.delete()
    patch_delete.short_description = "批量删除"


    def __init__(self, model, site):

        self.model = model
        self.site = site  # site为单例对象

    # url路径反向解析
    def get_change_url(self, obj):
        model_name = self.model._meta.model_name
        app_label = self.model._meta.app_label

        _url = reverse("%s_%s_change" % (app_label, model_name), args=(obj.pk,))

        return _url

    def get_delete_url(self, obj):
        model_name = self.model._meta.model_name
        app_label = self.model._meta.app_label

        _url = reverse("%s_%s_delete" % (app_label, model_name), args=(obj.pk,))

        return _url

    def get_add_url(self):

        model_name = self.model._meta.model_name
        app_label = self.model._meta.app_label

        _url = reverse("%s_%s_add" % (app_label, model_name))

        return _url

    def get_list_url(self):

        model_name = self.model._meta.model_name
        app_label = self.model._meta.app_label

        _url = reverse("%s_%s_list" % (app_label, model_name))

        return _url

    # 复选框、删除、编辑,定义在这里是为了在new_list_display函数中进行拼接
    # 在查看每一张表页面时都会出现这三项
    def check(self, obj=None, is_header=False):
        if is_header:
            return mark_safe("<input id='choice' type='checkbox'>")
        # 添加name='selected'_pk value='%s'是为批量操作actions做准备
        return mark_safe("<input class='choice_item' type='checkbox' name='selected_pk' value='%s'>"%obj.pk)

    def edit(self, obj=None, is_header=False):
        if is_header:
            return "操作"
        # 方案1:固定url
        # return mark_safe("<a href='/Xadmin/app01/book/%s/change/'>编辑</a>"%obj.pk)  #这样做把路径写死了

        # 方案2:拼接url
        # return mark_safe("<a href='%s/change/'>编辑</a>"%obj.pk) #比较safe方法,safe用在母版中的
        # %s前面没加/,当点击编辑的时候,按照当前页面的路径在后面把href里面的路径添加进去,这样做为了不把路径写死

        # 方案3:反向解析

        _url = self.get_change_url(obj)  # 反向解析

        return mark_safe("<a href='%s'>编辑</a>" % _url)

    def delete(self, obj=None, is_header=False):

        if is_header:
            return "操作"

        _url = self.get_delete_url(obj)

        return mark_safe("<a href='%s'>删除</a>" % _url)

    #扩展list_display列表,配置每个表默认都有复选框、删除、编辑三个操作,而且固定它们的位置
    def new_list_display(self):
        temp = []
        temp.append(ModelXadmin.check)  # append只能加一个元素
        temp.extend(self.list_display)  # extend扩展一个列表进来
        if not self.list_display_links:
            temp.append(ModelXadmin.edit)
        temp.append(ModelXadmin.delete)

        return temp

    #扩展actions,配置每个表默认都有批量删除操作
    def new_actions(self):
        temp=[]
        temp.append(ModelXadmin.patch_delete)  #批量删除
        temp.extend(self.actions) #如果用户定制了自己的actions就用用户自定义的,没有就添加其父类ModelXadmin下actions空列表

        return temp

    # 获取serach的Q对象的函数
    def get_serach_conditon(self, request):
        key_word = request.GET.get("q", "")  # 第一次访问的时候查询框肯定没有值,用""表示默认为空
        self.key_word = key_word  # 这时候ModelXadmin的实例化对象里面就有了一个self.key_word这个值
        # 可以通过showlist.config.key_word获取

        search_connection = Q()  # Q查询:通过字符串查询
        if key_word:
            # self.search_fields    #["title","price"]
            search_connection.connector = "or"  # 通过这个参数可以将Q对象默认的and关系变成or
            for search_field in self.search_fields:
                search_connection.children.append((search_field + "__contains", key_word))  # 模糊查询
        return search_connection

    def get_filter_condition(self,request):
        filter_condition=Q()  #默认查询方式为且

        for filter_field,val in request.GET.items():   #获取filter查询的键值条件
            if filter_field in self.list_filter: #如果传的键在定义的列表里面,防止把page=1这样的键值对传过来
                filter_condition.children.append((filter_field,val)) #注意里面接收的是一个元组
        return filter_condition  # (AND: ('publish', '2'), ('authors', '1'))

    # 查看视图
    def list_view(self, request):  # 这里注册用哪个样式类(默认、自定义),self就是谁
        print("self.model", self.model)  # 用户访问的是哪张表,self.model就是哪张表,这就是urls跨类定义最大的意义所在
        # 这里首先要弄清楚self是什么,我们一层一层的找,self->list_view->get_urls2->urls2->admin_class_obj,
        # 所以这里self就相当于admin_class_obj,这里就要搞清楚admin_class_obj是默认样式类的实例化对象还是自己定义样式类的实例化对象
        # 以访问Book表为例(这里Book表使用自定义的样式类,以下都是以访问Book表为例),
        # 上面打印内容为,self.model <class 'app01.models.Book'>
        model_name = self.model._meta.model_name  # 获取表名

        if request.method == "POST":  # action
            print("POST:", request.POST)
            #POST: <QueryDict: {'csrfmiddlewaretoken': ['7eY8H0ZW1TS80cB9FPzgPX4AdQayMPqMQoSC0m4fq0dUhctGFyhWWux8Ubqza9i5'],
                              # 'action': ['patch_init'], 'selected_pk': ['1', '5']}>
            action = request.POST.get("action")  # 'action': ['patch_init']
            selected_pk = request.POST.getlist("selected_pk")  #传过来的是一个列表,用getlist取值 'selected_pk': ['1', '5']
            action_func = getattr(self, action) #反射,相当于在自己所在的类(BookConfig)下找patch_init方法,自己类没有去父类找
            queryset = self.model.objects.filter(pk__in=selected_pk)  #查询当前选中的复选框对象 <QuerySet [<Book: 北京折叠>, <Book: 蓦然回首>]>
            ret = action_func(request, queryset)

            # return ret

        # 获取serach的Q对象
        search_connection = self.get_serach_conditon(request)

        # 获取filter构建Q对象
        print("999", request.GET)  # 999 <QueryDict: {'page': ['1'], 'authors': ['1'], 'publish': ['2']}>
        print("777", request.GET.items)
        # 777 <bound method MultiValueDict._iteritems of <QueryDict: {'page': ['1'], 'authors': ['1'], 'publish': ['2']}>>

        filter_condition=self.get_filter_condition(request)
        print("888",filter_condition)  #888 (AND: ('authors', '1'), ('publish', '2')) 处理之后的filter查询条件


        # 筛选获取当前表所有数据
        data_list = self.model.objects.all().filter(search_connection).filter(filter_condition) # <QuerySet [<Book: 北京折叠>, …… ]>
        print("list_display:", self.list_display)  # self.list_display为列表中定义的哪几列, ['nid', 'title', 'publish', 'price']

        # 按照showlist展示数据
        showlist = show_list(self, data_list, request)  # 实例化一个对象showlist,并传三个参数,其中self是当前ModelXadmin的实例化对象
        # 把self传给show_list类后,它里面的__init__进行接收(上面show_list函数用来接收self的参数是config)

        # 构建一个增加url路径
        add_url = self.get_add_url()
        return render(request, 'list_view.html', locals())

    # 增加视图
    def add_view(self, request):
        model_name = self.model._meta.model_name  # 获取表名
        ModelFormDemo = self.get_modelform_class()  # 获取modelform类变量
        form = ModelFormDemo()  # 实例化一个对象form


        # 循环form下每一个字段,如果为一对多或多对多字段为其添加两个属性is_pop=True、
        # url = _url + "?pop_res_id=id_%s" % bfield.name
        #为了在form.html中识别一对多或多对多form字段,为其添加一个+号进行pop操作
        for bfield in form:
            from django.forms.boundfield import BoundField
            print(bfield.field,type(bfield.field))  # 字段对象、字段类型
            print("name",bfield.name)  # 字段名(字符串)


            from django.forms.models import ModelChoiceField
            if isinstance(bfield.field,ModelChoiceField): #如果form字段是一对多或多对多类型
                bfield.is_pop=True   #为form字段加属性,以便之后取出判断是否为一对多或多对多字段

                print("===>",bfield.field.queryset.model)    #一对多或者多对多字段的关联模型表
                                                             #===> <class 'app01.models.Publish'>
                                                             #===> <class 'app01.models.Author'>

                related_model_name = bfield.field.queryset.model._meta.model_name
                related_app_label = bfield.field.queryset.model._meta.app_label

                _url = reverse("%s_%s_add" % (related_app_label, related_model_name))
                bfield.url = _url + "?pop_res_id=id_%s" % bfield.name
                #为bfield加属性 url = _url + "?pop_res_id=id_%s" % bfield.name

        if request.method == "POST":
            form = ModelFormDemo(request.POST)
            if form.is_valid():
                obj=form.save()  #返回的当前插入的那一条记录

                pop_res_id=request.GET.get("pop_res_id")
                if pop_res_id: #说明是子窗口:执行完父窗口函数后自行关闭
                    res = {"pk": obj.pk, "text": str(obj), "pop_res_id": pop_res_id}
                    return render(request, "pop.html", {"res": res})

                else:#正常查看页面:返回展示页面
                    return redirect(self.get_list_url())

        return render(request, 'add_view.html', locals())

    # 获取modelform类:
    def get_modelform_class(self):
        # 如果用户没有定制,就使用默认配置的ModelFormDemo
        if not self.modelform_class:
            from django.forms import ModelForm
            from django.forms import widgets as wid
            class ModelFormDemo(ModelForm):
                class Meta:
                    model = self.model
                    fields = "__all__"

            return ModelFormDemo
        # 如果用户定制了自己的modelform就用用户自己的(app01/Xadmin.py)
        else:
            return self.modelform_class

    # 编辑视图
    def change_view(self, request, id):
        model_name = self.model._meta.model_name  # 获取表名
        ModelFormDemo = self.get_modelform_class()
        edit_obj = self.model.objects.filter(pk=id).first()  # 取出要编辑的对象

        if request.method == "POST":
            form = ModelFormDemo(request.POST, instance=edit_obj)  # 更新当前修改的数据
            if form.is_valid():
                form.save()
                return redirect(self.get_list_url())
            return render(request, 'add_view.html', locals())

        form = ModelFormDemo(instance=edit_obj)  # 传给instance,此时编辑页面就有值了
        return render(request, 'change_view.html', locals())

    # 删除视图
    def delete_view(self, request, id):
        model_name = self.model._meta.model_name  # 获取表名
        url = self.get_list_url()
        if request.method == "POST":
            self.model.objects.filter(pk=id).delete()
            return redirect(self.get_list_url())

        return render(request, "delete_view.html", locals())

    #默认的扩展url为空,用户可以自定义
    def extra_url(self):
        return []

    def get_urls2(self):
        temp = []

        app_label = self.model._meta.app_label
        model_name = self.model._meta.model_name
        # 以上取app名称和model名称是为了给以下路径起别名(因为每一个app下的model名都是不一样的),用于反向解析
        temp.append(url(r"^$", self.list_view, name="%s_%s_list" % (app_label, model_name)))
        temp.append(url(r"^add/$", self.add_view, name="%s_%s_add" % (app_label, model_name)))
        temp.append(url(r"^(\d+)/change/$", self.change_view, name="%s_%s_change" % (app_label, model_name)))
        temp.append(url(r"^(\d+)/delete/$", self.delete_view, name="%s_%s_delete" % (app_label, model_name)))

        # 用户自定义扩展的url
        temp.extend(self.extra_url())

        return temp

    @property
    def urls2(self):  # urls2跨类定义
        return self.get_urls2(), None, None


# 定义一个全局类,用对创建单例对象  在这个类中设计了url以及完成了注册操作
class XadminSite(object):

    def __init__(self, name='admin'):
        self._registry = {}

    def get_urls(self):

        print(self._registry)  # {Book:modelAdmin(Book),.......}

        temp = []
        # 循环在Django一启动时就执行
        for model, admin_class_obj in self._registry.items():
            # 获取当前循环的model的字符串与所在app的字符串,为了拼接路径
            app_name = model._meta.app_label  # "app01"
            model_name = model._meta.model_name  # "book"
            temp.append(url(r'^{0}/{1}/'.format(app_name, model_name), admin_class_obj.urls2), )

            '''
            url(r"app01/book",ModelXadmin(Book,site).urls2)
            url(r"app01/publish",ModelXadmin(Publish,site).urls2)
            url(r"app02/order",ModelXadmin(Order,site).urls2)
            
            '''
        return temp

    @property
    def urls(self):
        return self.get_urls(), None, None

    def register(self, model, admin_class=None, **options):
        if not admin_class:
            admin_class = ModelXadmin  # 使用默认的样式

        self._registry[model] = admin_class(model, self)  # {Book:ModelAdmin(Book),Publish:ModelAdmin(Publish)}


site = XadminSite()  # 创建单例对象 

app01/Xadmin.py

from Xadmin.service.Xadmin import site,ModelXadmin
from django.shortcuts import HttpResponse

from django.forms import ModelForm
from django.forms import widgets as wid

from app01.models import *

#为book表定制modelform
class BookModelForm(ModelForm):
    class Meta:
        model = Book
        fields = "__all__"

        labels={
            "title":"书籍名称",
            "price":"价格",
            "publishDate":"出版日期"
        }  #编辑页面展示效果
        # widgets={  #应用这个然后页面就会显示样式,把浏览器的样式复制到母版中使用
        #     "title":wid.TextInput(attrs={"class":"form-control"})
        # }

#定义我们自己的样式
class BookConfig(ModelXadmin):
    list_display=["nid","title","publish","price","authors"] #定义查看页面有哪些字段
    list_display_links = ["title"]                 #定义点击哪个字段进入编辑页面
    modelform_class=BookModelForm                  #定义编辑页面使用我们自定义的
    search_fields=["title","price"]                #定义搜索框按照这两个字段进行筛选


    # 定制action操作
    def patch_init(self, request, queryset):
        print(queryset)
        # queryset就是我们选中的数据,这里把我们选中的数据全部更新为123
        queryset.update(price=123)
        return HttpResponse("批量初始化OK")

    # 为我们自定义的函数加上中文描述的属性
    patch_init.short_description = "批量初始化"
    # 最后把函数放入actions列表中
    actions = [patch_init]

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

site.register(Book,BookConfig)
site.register(Publish)
site.register(Author)
site.register(AuthorDetail)

app02/Xadmin.py

 

from Xadmin.service.Xadmin import site,ModelXadmin
from app02.models import *

site.register(Order)

class FoodConfig(ModelXadmin):
    list_display = ["id","title"]
site.register(Food,FoodConfig)

print("_registry",site._registry) #6个表 

app01/models.py 

from django.db import models

class Author(models.Model):
    nid = models.AutoField(primary_key=True)
    name=models.CharField( max_length=32)
    age=models.IntegerField()

    # 与AuthorDetail建立一对一的关系
    authorDetail=models.OneToOneField(to="AuthorDetail",on_delete=models.CASCADE)
    def __str__(self):
        return self.name

class AuthorDetail(models.Model):

    nid = models.AutoField(primary_key=True)
    # birthday=models.DateField()
    telephone=models.BigIntegerField()
    addr=models.CharField( max_length=64)

    def __str__(self):
        return str(self.telephone)

class Publish(models.Model):
    nid = models.AutoField(primary_key=True)
    name=models.CharField( max_length=32)
    city=models.CharField( max_length=32)
    email=models.EmailField()

    def __str__(self):
        return self.name

class Book(models.Model):

    nid = models.AutoField(primary_key=True,verbose_name=" 编号")  #定义在Xadmin查看页面中显示中文名称“编号”
    title = models.CharField( max_length=32)
    publishDate=models.DateField()
    price=models.DecimalField(max_digits=5,decimal_places=2)

    # 与Publish建立一对多的关系,外键字段建立在多的一方
    publish=models.ForeignKey(to="Publish",to_field="nid",on_delete=models.CASCADE)
    # 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表
    authors=models.ManyToManyField(to='Author',)

    def __str__(self):
        return self.title 

app02/models.py

from django.db import models

class Order(models.Model):
    title=models.CharField(max_length=32)
    def __str__(self):
        return self.title

class Food(models.Model):
    title = models.CharField(max_length=32)

    def __str__(self):
        return self.title

list_view.html 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
    <script src="/static/jquery-3.3.1.js"></script>

    <style>
        .filter a {
            text-decoration: none;
        {# 去除a标签默认样式的下划线 #} color: grey;
        }

        .active {
            color: blue !important;
        }
    </style>
</head>
<body>

<h3>查看{{ model_name }}数据</h3>

<div class="container">
    <div class="row">
        <div class="col-lg-9">
            <!--添加数据-->
            <a href="{{ add_url }}" class="btn btn-primary">添加数据</a>
            <!--搜索数据开始-->
            {% if showlist.config.search_fields %}
                <form action="" class="pull-right">
                    <input type="text" name="q" value="{{ showlist.config.key_word }}">
                    <button>submit</button>
                </form>
            {% endif %}
            <!--搜索数据结束-->

            <!--添加form表单是为了在点击Go时确定发送数据的范围(不包括上面submit里面的内容)-->
            <form action="" method="post">
                {% csrf_token %}
                <!--action操作开始-->
                <select name="action" id="" style="width: 200px;padding: 5px 8px;display: inline-block">
                    <option value="">---------------</option>
                    {% for item in showlist.get_action_list %}
                        <option value="{{ item.name }}">{{ item.desc }}</option>
                    {% endfor %}
                </select>
                <button type="submit" class="btn btn-info">Go</button>
                <!--action操作结束-->

                <!--表格数据开始-->
                <table class="table table-bordered table-striped">
                    <thead>
                    <tr>
                        {% for item in showlist.get_header %}
                            <th>{{ item }}</th>
                        {% endfor %}
                    </tr>
                    </thead>
                    <tbody>
                    {% for data in showlist.get_body %}
                        <tr>
                            {% for item in data %}
                                <td>{{ item }}</td>
                            {% endfor %}
                        </tr>
                    {% endfor %}

                    </tbody>
                </table>
                <!--表格数据结束-->

                <!--分页开始-->
                <nav class="pull-right">
                    <ul class="pagination">
                        {{ showlist.pagination.page_html|safe }}
                    </ul>
                </nav>
                <!--分页结束-->
            </form>
        </div>

        <!--filter开始-->
        <div class="col-md-3">
            <!--如果list_filter有值说明是用户自定义的,展示除来-->
            {% if showlist.config.list_filter %}
                <div class="filter">
                    <h4 style="">Filter</h4>
                    {% for filter_field,linktags in showlist.get_filter_linktags.items %}
                        <div class="well">  <!-- class="well"为加面板-->
                            <p>By {{ filter_field.upper }}</p>
                            {% for link in linktags %}
                                <p>{{ link|safe }}</p>
                            {% endfor %}
                        </div>
                    {% endfor %}
                </div>
            {% endif %}
        </div>
        <!--filter结束-->
    </div>
</div>

<script>

    //给表头复选框加上点击事件(点击表头复选框,下面框全部选中,反之全部取消)
    $("#choice").click(function () {

        if ($(this).prop("checked")) {    //prop() 方法用于设置或返回被选元素的属性和值
            $(".choice_item").prop("checked", true)
        } else {
            $(".choice_item").prop("checked", false)
        }

    })
</script>

</body>
</html> 

form.html

<div class="container">
    <div class="row">
        <div class="col-md-6 col-xs-8 col-md-offset-3">
             <form action="" method="post" novalidate>
                {% csrf_token %}
                {% for field in form %}
                <div style="position: relative">
                    <label for="">{{ field.label }}</label>
                    {{ field }} <span class=" error pull-right">{{ field.errors.0 }}</span>

                    {% if field.is_pop %}
                       <a onclick="pop('{{ field.url }}')" style="position: absolute;right: -30px;top: 20px"><span style="font-size: 28px">+</span></a>
                    {% endif %}
                </div>
                {% endfor %}

                 <button type="submit" class="btn btn-default pull-right">提交</button>
             </form>
        </div>
    </div>
</div>
<script>
    function pop(url) {
        window.open(url,"","width=600,height=400,top=100,left=100")
    }
</script>

add_view.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
    <script src="/static/jquery-3.3.1.js"></script>

    <style>
        {#下面样式从浏览器复制而来,页面的标签都是input/select标签,让它们都有下面的样式#}
        input, select {
            display: block;
            width: 100%;
            height: 34px;
            padding: 6px 12px;
            font-size: 14px;
            line-height: 1.42857143;
            color: #555;
            background-color: #fff;
            background-image: none;
            border: 1px solid #ccc;
            border-radius: 4px;
            -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
            box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
            -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;
            -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
            transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
        }
        .error{
            color:red;
        }

    </style>
</head>
<body>
<h3>添加{{ model_name }}数据</h3>

{% include 'form.html' %}

<script>
    function pop_response(pk,text,id) {

        console.log(pk,text,id);    //23 hui id_publish

        //选择哪一个select标签
        //option的文本值和value值

        var $option=$('<option>');     //创建一个option空标签<option></option>
        $option.html(text);   // 为标签加文本<option>text</option>
        $option.val(pk);             //<option value=pk>text</option>
        $option.attr("selected","selected") ;   //  <option value=111>text</option>
        $("#"+id).append($option)            //把构建的标签放到id=id标签中

    }
</script>

</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
    <script src="/static/jquery-3.3.1.js"></script>

    <style>
        {#下面样式从浏览器复制而来,页面的标签都是input/select标签,让它们都有下面的样式#}
        input, select {
            display: block;
            width: 100%;
            height: 34px;
            padding: 6px 12px;
            font-size: 14px;
            line-height: 1.42857143;
            color: #555;
            background-color: #fff;
            background-image: none;
            border: 1px solid #ccc;
            border-radius: 4px;
            -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
            box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
            -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;
            -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
            transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
        }
        .error{
            color:red;
        }

    </style>
</head>
<body>
<h3>编辑{{ model_name }}数据</h3>

{% include 'form.html' %}
</body>
</html>
change_view.html

delete_view.py

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h3>删除{{ model_name }}数据</h3>

<form action="" method="post">
    {% csrf_token %}
    <button>确认删除?</button>
    <a href="{{ url }}">取消</a>
</form>

</body>
</html>

pop.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>

<script>

    window.opener.pop_response('{{ res.pk }}',"{{ res.text }}",'{{ res.pop_res_id }}')
    window.close()
</script>

</body>
</html> 
"""
自定义分页组件,可以保存搜索条件

"""

class Pagination(object):
    def __init__(self, current_page, all_count, base_url, params, per_page_num=8, pager_count=11, ):
        """
        封装分页相关数据
        :param current_page: 当前页
        :param all_count:    数据库中的数据总条数
        :param per_page_num: 每页显示的数据条数
        :param base_url: 分页中显示的URL前缀
        :param pager_count:  最多显示的页码个数
        """
        try:
            current_page = int(current_page)
        except Exception as e:
            current_page = 1

        if current_page < 1:
            current_page = 1

        self.current_page = current_page

        self.all_count = all_count
        self.per_page_num = per_page_num

        self.base_url = base_url

        # 总页码
        all_pager, tmp = divmod(all_count, per_page_num)
        if tmp:
            all_pager += 1
        self.all_pager = all_pager

        self.pager_count = pager_count  # 最多显示页码数
        self.pager_count_half = int((pager_count - 1) / 2)

        import copy
        params = copy.deepcopy(params)
        params._mutable = True
        self.params = params  # self.params : {"page":77,"title":"python","nid":1}

    @property
    def start(self):
        return (self.current_page - 1) * self.per_page_num

    @property
    def end(self):
        return self.current_page * self.per_page_num

    def page_html(self):
        # 如果总页码 < 11个:
        if self.all_pager <= self.pager_count:
            pager_start = 1
            pager_end = self.all_pager + 1
        # 总页码  > 11
        else:
            # 当前页如果<=页面上最多显示(11-1)/2个页码
            if self.current_page <= self.pager_count_half:
                pager_start = 1
                pager_end = self.pager_count + 1

            # 当前页大于5
            else:
                # 页码翻到最后
                if (self.current_page + self.pager_count_half) > self.all_pager:
                    pager_start = self.all_pager - self.pager_count + 1
                    pager_end = self.all_pager + 1

                else:
                    pager_start = self.current_page - self.pager_count_half
                    pager_end = self.current_page + self.pager_count_half + 1

        page_html_list = []
        self.params["page"] = 1
        first_page = '<li><a href="%s?%s">首页</a></li>' % (self.base_url, self.params.urlencode(),)
        page_html_list.append(first_page)

        if self.current_page <= 1:
            prev_page = '<li class="disabled"><a href="#">上一页</a></li>'
        else:
            self.params["page"] = self.current_page - 1
            prev_page = '<li><a href="%s?%s">上一页</a></li>' % (self.base_url, self.params.urlencode(),)

        page_html_list.append(prev_page)

        # urlencode可以把key-value这样的键值对转换成我们想要的格式,返回的是a=1&b=2这样的字符串

        for i in range(pager_start, pager_end):
            #  self.params  : {"page":77,"title":"python","nid":1}

            self.params["page"] = i  # {"page":72,"title":"python","nid":1}
            if i == self.current_page:
                temp = '<li class="active"><a href="%s?%s">%s</a></li>' % (self.base_url, self.params.urlencode(), i,)
            else:
                temp = '<li><a href="%s?%s">%s</a></li>' % (self.base_url, self.params.urlencode(), i,)
            page_html_list.append(temp)

        if self.current_page >= self.all_pager:
            next_page = '<li class="disabled"><a href="#">下一页</a></li>'
        else:
            self.params["page"] = self.current_page + 1
            next_page = '<li><a href="%s?%s">下一页</a></li>' % (self.base_url, self.params.urlencode(),)
        page_html_list.append(next_page)

        self.params["page"] = self.all_pager
        last_page = '<li><a href="%s?%s">尾页</a></li>' % (self.base_url, self.params.urlencode(),)
        page_html_list.append(last_page)

        return ''.join(page_html_list)

# class Pagination(object):
#
#     def __init__(self, data_num, current_page, url_prefix,params, per_page=10, max_show=3):
#         """
#         进行初始化.
#         :param data_num: 数据总数
#         :param current_page: 当前页
#         :param url_prefix: 生成的页码的链接前缀
#         :param per_page: 每页显示多少条数据
#         :param max_show: 页面最多显示多少个页码
#         """
#         self.data_num = data_num
#         self.per_page = per_page
#         self.max_show = max_show
#         self.url_prefix = url_prefix
#
#         # 计算出总页码数
#         #divmod() 函数把除数和余数运算结果结合起来,返回一个包含商和余数的元组(a // b, a % b)。
#         self.page_num, more = divmod(data_num, per_page)
#         if more:
#             self.page_num += 1
#
#         #对访问的当前页进行监控
#         try:
#             self.current_page = current_page
#         except Exception as e:
#             # 取不到或者页码数不是数字都默认展示第1页
#             self.current_page = 1
#             # 如果URL传过来的页码数是负数
#         if self.current_page <= 0:
#             self.current_page = 1
#             # 如果URL传过来的页码数超过了最大页码数
#         elif self.current_page > self.page_num:
#             self.current_page = self.page_num  # 默认展示最后一页
#
#         # 页码数的一半 算出来
#         self.half_show = max_show // 2
#
#         # 页码最左边显示多少
#         if self.current_page - self.half_show <= 1: # 如果左边越界
#             self.page_start = 1
#             self.page_end = self.max_show
#         elif self.current_page + self.half_show >= self.page_num:  # 如果右边越界
#             self.page_end = self.page_num
#             self.page_start = self.page_num - self.max_show
#         else:
#             #页面上展示的页码从哪儿开始
#             self.page_start = self.current_page - self.half_show
#             # 页面上展示的页码从哪儿结束
#             self.page_end = self.current_page + self.half_show
#
#         import copy
#         self.params=copy.deepcopy(params) # {'page': ['10'], 'name': ['xiaohei']}
#
#     @property
#     def start(self):
#         # 数据从哪儿开始切
#         return (self.current_page - 1) * self.per_page
#
#     @property
#     def end(self):
#         # 数据切片切到哪儿
#         return self.current_page * self.per_page
#
#     #自己拼接分页的HTML代码
#     def page_html(self):
#         # 生成页码
#         l = []
#         # 加一个首页
#         l.append('<li><a href="{}?page=1">首页</a></li>'.format(self.url_prefix))
#         # 加一个上一页
#         if self.current_page == 1:
#             l.append('<li class="disabled" ><a href="#">«</a></li>'.format(self.current_page))
#         else:
#             l.append('<li><a href="{}?page={}">«</a></li>'.format(self.url_prefix, self.current_page - 1))
#
#
#         # {'page': ['10'], 'name': ['xiaohei']}
#
#         # urlencode可以把key-value这样的键值对转换成我们想要的格式,返回的是a=1&b=2这样的字符串
#         print(self.params.urlencode())   # page=10&name=xiaohei
#
#         #控制页面上展示哪些页码(当前页码-最多展示页码的一半,当前页码+最多展示页码的一半)
#         for i in range(self.page_start, self.page_end + 1):
#             self.params["page"]=i # {'page': ['10'], 'name': ['xiaohei']}
#             if i == self.current_page:
#                 tmp = '<li class="active"><a href="{0}?page={1}">{1}</a></li>'.format(self.url_prefix, i)
#             else:
#                 tmp = '<li><a href="{0}?{1}">{2}</a></li>'.format(self.url_prefix, self.params.urlencode(),i)
#             l.append(tmp)
#
#
#         # 加一个下一页
#         if self.current_page == self.page_num:
#             l.append('<li class="disabled"><a href="#">»</a></li>'.format(self.current_page))
#         else:
#             l.append('<li><a href="{}?page={}">»</a></li>'.format(self.url_prefix, self.current_page + 1))
#         # 加一个尾页
#         l.append('<li><a href="{}?page={}">尾页</a></li>'.format(self.url_prefix, self.page_num))
#         return "".join(l)
Xadmin\utils\page.py

 Book表

 

 publihs表

  

 页面效果图:

   

posted @ 2020-05-11 21:57  zh_小猿  阅读(322)  评论(0编辑  收藏  举报