Xadmin组件的构建
详情见:
Xadmin组件构建之分页、search查询与action批量操作
-----------------------------------------------------------------------------------------
相关知识: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>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!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>
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>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
""" 自定义分页组件,可以保存搜索条件 """ 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)
Book表
publihs表
页面效果图: