CURD插件(仿Django
前言
如何提升自己的开发效率?
每个新项目都是自己经做过的项目(经验所致),在项目开发过程中不断总结、封装属于自己的组件,
例如:每个web项目大部分都涉及增删改查,分页显示,搜素,CRM就是这样的组件,是一件很有必要的事情;
CURD组件(arya):模仿DjangoAdmin编写增删改查插件;
组件功能:通过在后台注册表名配置自定制类,操作、显示数据库中的内容;
组件设计目标:把CRM模块嵌套到不同具有增删改查功能的Django程序上,实现快速开发基于web的后台管理系统;
组件配置项:
list_display=[ '字段',‘函数’] : 定制显示页面显示的列名,和内容;
action : 定制action,批量操作;
show_add_btn=True :定制是否显示添加按钮
model_form=None :定制model_form 提供编辑、添加、删除页面,通过popup操作关联表数据
lister_filter :组合筛选项
search_list = ['name', 'qq'] :模糊搜素
组件说明:
Site类:注册和生成基础url
---方法:
url(路由调用)
get_usrl(url方法调用)
register(注册调用)
login(登录视图)
logout(登出视图)
---字段:
namespace(url所需名称空间)
self. _registry={ }(注册生成
# {
# model.表名1,配置对象1
# model.表名2,配置对象2
# }
)
self.name app名称
Config类:生成基础配置,生成增、删、改、查url,处理request请求;
---方法
warpper() 通过装饰器实现每次请求保存request信息
add_view() 增、删、改、查视图
changlist_view()
delete_view()
changge_view()
urls() 增、删、改、查url
extra_urls()url扩展
类字段:
list_dispaly=[字段、函数 ] 配置list显示页面需要列 和内容
get_listdisplay()
actions = [函数] 配置选中chebox可执行的操作
get_actions()
add_btn=True list显示页面是否显示添加按钮
get_ show_add_btn()
model_form = None 类 配置 增、删、改。查操作的model_form
get_model_form_class()
list_filter = [字段、函数 ] 配置组合搜素内容
get_list_filter()
对象字段
self.model_class (models.UserInfo)
self.request (每个对象的request请求信息)
self.site (注册类 )
ChangList类:封装list显示页面所有数据,通过对象的方式传到前端模板;
方法:
add_html(self),gen_list_filter 生成添加按钮 、生成组合搜素 需要的数据比较多不适合在前端模板生成,所以选择后端;
类字段:
对象字段:
self.model_config_obj (因为list显示页面,需要list_display,action,show_add。。。很多配置类的属性,所有干脆把 配置对象封装到ChangList类中 )
1 class ChangeList(object): # 由于使用inclusion_tag,需要传很多值,就把这个些值封装到类里,一对象的形式递给前端 2 def __init__(self, data_list, model_config_obj): 3 self.list_display = model_config_obj.get_list_play() 4 self.actions = model_config_obj.get_actions() 5 self.model_config_obj = model_config_obj 6 self.list_filter = model_config_obj.get_list_filter()View Code
self.request = request 、self.datalist 用于分页
-----------------------------------------------------------------------------------------------------------------
option类:组合搜素 配置项
list_filter= [v1.OptionConfig('group',True), v1.OptionConfig('roles',False), ]
方法:
is_func(self): 判断是否是函数
name(self): 生成函数名或者字段名
对象字段
"""
:param field: 字段名称或函数
:param is_multi: 是否支持多选
:param text_func_name: 在Model中定义函数,显示文本名称,默认使用 str(对象)
:param val_func_name: 在Model中定义函数,显示文本名称,默认使用 对象.pk
RowItems类:生成组合搜素需要的A标签
class RowItems():def __init__(self, option, data_list, params):self.option = optionself.data_list = data_listself.param = copy.deepcopy(params) # query_dict对象print(self.param)# print(self.param) # <QueryDict: {'group': ['1'], 'roles': ['2']}>self.param._mutable = Truedef __iter__(self):if self.option.is_muti: #如果配置了多选current_pk_or_list = self.param.getlist(self.option.name) #[1,2]print(current_pk_or_list)else:current_pk_or_list = self.param.get(self.option.name) # [1]if self.param.get(self.option.name): #如果<QueryDict: {'roles': ['2'], 'group': ['4', '5', '3', '2', '1']}> 能获取到 group roles# 生成全部self.param.pop(self.option.name) # 全部就pop自己all_url = self.param.urlencode()tmp = '<a href="?%s">全部</a>' % (all_url)yield mark_safe(tmp)else:all_url = self.param.urlencode() #如果访问的url 没有携带参数tmp = '<a class="active" href="?%s">全部</a>' % (all_url)yield mark_safe(tmp)#生成全部右侧 obj data_list=[obj,obj,obj ]for obj in self.data_list:#每个obj 生成 每个 A标签pk = str(obj.pk) #1text = str(obj) #阿斯蒂芬if self.option.is_muti: #如果配置了多选if pk not in current_pk_or_list: #如果obj.pk不在【1.2】tmp=[]tmp.extend(current_pk_or_list)tmp.append(pk)self.param.setlist(self.option.name,tmp) #注意在原来不变的基础上多加1个?group=2&group=3else:self.param[self.option.name]=pk# print(self.param) #<QueryDict: {'group': ['2', '3'], 'roles': ['1']}>url = self.param.urlencode()if not self.option.is_muti: #单选生成A标签if current_pk_or_list == pk: #url中传来的 get参数tmp = '<a class="active" href="?%s">%s</a>' % (url, text) # 设置选择标签颜色else:tmp = '<a href="?%s">%s</a>' % (url, text)yield mark_safe(tmp)else:if pk in current_pk_or_list: # url中传来的 get参数tmp = '<a class="active" href="?%s">%s</a>' % (url, text) # 设置选择标签颜色else:tmp = '<a href="?%s">%s</a>' % (url,text)yield mark_safe(tmp)View Code
一、CURD组件开发
1、CRM程序入口 apps.App02Config,执行app02下的app.py文件中App02Config类的ready方法;
CRM要从Django的setings.py配置文件的apps.App02Config类说起,apps.App02Config类在每个APP的apps.py文件中,该类的ready()方法会在ROOT_URLCONF = 'CRM.urls'配置未执行之前执行,实现帮我们把表和自定义配置类注册;
from django.apps import AppConfigclass App01Config(AppConfig):name = 'app01'def ready(self):passView Code
2、App02Config类中的ready方法,执行 autodiscover_modules('zhanggen'),所以去Django程序的所有app下寻找zhanggen.py并执行;
autodiscover_modules()模块,在Django程序启动自动发现并执行一个py文件 (启动文件)
autodiscover_modules(xx.py)模块自动去所有的APP下,寻找xx.py文件并且执行
from django.apps import AppConfigclass App02Config(AppConfig):name = 'app02'def ready(self):# 1.导入autodiscover_modules 程序启动执行autodiscover_modules(参数)# 谁导入autodiscover_modules(py文件)并设置了,就会在程序启动前执行那个pywenjfrom django.utils.module_loading import autodiscover_modulesautodiscover_modules('zhanggen')View Code
3、找到后zhanggen.py from app02.service import v1
先写好注册功能,等待调用。。(注意site是用文件实现的单例模式)
from django.shortcuts import HttpResponse,render from django.conf.urls import url from types import FunctionType from django.urls import reverse from django.utils.safestring import mark_safeclass ChangeList(object): #封装 table 数据传递给前端def __init__(self,data_list,list_display,model_config_obj):self.data_list = data_listself.list_display = list_displayself.model_config_obj = model_config_objdef add_html(self): #封装add 按钮app_model = self.model_config_obj.model_class._meta.app_label, self.model_config_obj.model_class._meta.model_nameadd_url = reverse("zg:%s_%s_add" % app_model)add_html = mark_safe('<a class="btn btn-primary" href="%s">添加</a>' % (add_url,))return add_htmlclass Modelzg(object): # 注册的时候没有使用配置对象,默认使用的配置对象''''用户基础配置类'''def __init__(self, model_class, site): #model_class=model.userinfo , site=Zgsite对象self.model_class = model_classself.site = sitelist_display = []show_add_btn = Truedef get_show_add_btn(self):return self.show_add_btndef changelist_view(self, request, *args, **kwargs):#由于组册类调用本类的时候传入了表名 self._registry[model]=model_zg(model,self)#表名 model_class=models.UserInfo ,所有就可以根据 组册表名获取数据了data_list=self.model_class.objects.all()self.request = request# def headers():# if not self.list_display:#如果用户没有自定义配置就返回表名# yield self.model_class._meta.model_name# else:# for v in self.list_display:# # if isinstance(v,FunctionType):# # yield v(True)# # else:# # #获取中文 列名称# # # models.UserInfo._meta.get_field(email/name).verbose_name# # verbose_name= self.model_class._meta.get_field(v).verbose_name# # yield verbose_name# #三元运算# yield v(self, is_header=True) if isinstance(v, FunctionType) else self.model_class._meta.get_field(# v).verbose_name# def body():# for row in data_list:# #row 是数据库中的一行数据,对象# row_data=[]# for name in self.list_display:# #list_display = ['id', 'name', 'email', xxxxx]# if isinstance(name,FunctionType):# row_data.append(name(self,row))# else:# row_data.append(getattr(row,name))# yield row_data#三元表达式 列表生成式# if not self.list_display:# yield [str(row), ]# else:# yield [name(self,obj=row) if isinstance(name, FunctionType) else getattr(row, name) for name in# self.list_display]# # context = {'data_list': data_list,# 'list_display': self.list_display,# 'headers':headers(),# 'body':body(),# }cl = ChangeList(data_list,self.list_display,self) #传到simp_tagcontext = {'cl': cl}return render(request,'nb/changelist.html', context)def add_view(self, request, *args, **kwargs):return HttpResponse('添加页面')def delete_view(self, request, *args, **kwargs):return HttpResponse('删除页面')def change_view(self, request, *args, **kwargs):return HttpResponse('修改页面')def get_urls(self):app_model_name = self.model_class._meta.app_label, self.model_class._meta.model_name #(app01,表名)'''生成url:/zg/app01/userinfo/ 显示 /zg/app01/userinfo/add 添加/zg/app01/userinfo/ 删除/zg/app01/userinfo/ 编辑 '''patterns = [url(r'^$', self.changelist_view,name="%s_%s_changelist" %app_model_name),url(r'^add/$', self.add_view,name="%s_%s_add" %app_model_name),url(r'^(.+)/delete/$', self.delete_view,name="%s_%s_delete" %app_model_name),url(r'^(.+)/change/$', self.change_view,name="%s_%s_change" %app_model_name),]patterns+=self.extra_urls()return patternsdef extra_urls(self):''''自定义扩展url 预留的钩子函数'''return []@propertydef urls(self):return self.get_urls(),None,Noneclass Zgsite(object):def __init__(self):self._registry={}self.name='zg'self.namespace='zg'def register(self,model,model_zg=None):if not model_zg: #如果在register()注册的时候,没有传(配置类)model_zg=Modelzg #就使用Modelzg自己定义的类self._registry[model]=model_zg(model,self) #注意 model:表名 model_zg(model表名,self =Zgsite对象 ):是配置对象def login(self,request):return HttpResponse('登录页面')def logout(self,request):return HttpResponse('注销页面')def ger_urls(self):patterns=[]patterns+=[url(r'^login/',self.login),url(r'^logout/', self.logout),]for table_name,config_obj in self._registry.items():'''table_name._meta.app_label 注册表名所在的app名称 例: app01table_name._meta.model_name 注册表名所在的 表名小写 例:userinfo / usergroup'''patterns += [ #'/zg/app01/userinfo/'# url(r'^app01/userinfo/',config_obj.urls)# url(r'^app01/usergroup/',config_obj.urls继续构造([],'xx','xxx'))# config_obj=Modelzg() 默认对象#config_obj.urls 调用默认对象的 urls,继续构造([],'xx','xxx'))url(r'^%s/%s/'%(table_name._meta.app_label,table_name._meta.model_name,),(config_obj.urls))]return patterns@propertydef urls(self):return self.ger_urls(),self.name,self.namespace #因为路由系统中的include本质就是返回一个元祖 site=Zgsite()View Code
设计知识点:
文件实现单例模式:
文件导入之后该文件中定义的类被实例化成一个对象,1次执行之后,以后再调用这个文件永远得到同一个对象,相当于永远执行1个对象;
4、zhanggen.py实例化了v1中Zgsite类,得到单例对象site,site单例对象 2次(2张表) 调用了单例象的register()方法,
v1.site.register(models.UserInfo) v1.site.register(models.UserGroup)#1、导入v1就得到一个单例对象 site #2、执行site对象的regist()进行注册View Code
5、site单例对象的register()方法把site对象的self._registry属性,填充成
{
models.UserInfo:UserInfoConfig(models.UserInfo,self,) #sel也就是,site对象由于是单例模式userindo的site对象=slef=UserGroup的site对象
models.UserGroup:UserGroupConfig(models.UserGroup,self,)
}
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
{
models.UserInfo,UserInfo配置对象(models.UserInfo,site对象), #配置对象是相互无关系的
models.UserGroup,UserGrou配置对(models.UserInfo,对象)
}
class Zgsite(object):def __init__(self):self._registry={}self.name='zg'self.namespace='zg'def register(self,model,model_zg=None):if not model_zg: #如果在register()注册的时候,没有传(配置类)model_zg=Modelzg #就使用Modelzg自己定义的类self._registry[model]=model_zg(model,self) #注意 model:表名 model_zg(model表名,self =Zgsite对象 ):是配置对象View Code
6、Django程序的路由系统开始执行,同样调用了单例对象 v1.py中的单例对象 site,并执行了site单例对象的urls方法;
url(r'^zg/',v1.site.urls),
7、site单例对象的urls方法,调用了site单例对象的ger_urls方法;
def ger_urls(self):patterns=[]patterns+=[ #基本urlurl(r'^login/',self.login), #/app01/login/url(r'^logout/', self.logout),#/app01/logout/ ]for table_name,config_obj in self._registry.items():# print(config_obj.model_class)'''table_name._meta.app_label 注册表名所在的app名称 例: app01table_name._meta.model_name 注册表名所在的 表名小写 例:userinfo / usergroup'''patterns += [ #'/zg/app01/userinfo/'# url(r'^app01/userinfo/',config_obj.urls)# url(r'^app01/usergroup/',config_obj.urls继续构造([],'xx','xxx'))# config_obj=Modelzg() 默认对象#config_obj.urls 调用默认对象的 urls,继续构造([],'xx','xxx'))url(r'^%s/%s/'%(table_name._meta.app_label,table_name._meta.model_name,),(config_obj.urls)) #site类注意转接给你了基础配置类 ]return patterns@propertydef urls(self):return self.ger_urls(),self.name,self.namespace #因为路由系统中的include本质就是返回一个元祖View Code
8、ger_urls方法,for循环了单例对象的self._registry属性,为每张表生成基础url映射关系;
^zg/ ^login/
^zg/ ^logout/
^zg/ ^app01/userinfo/
^zg/ ^app01/usergroup/
9、单例对象site的ger_urls方法,并每个调用配置对象中的get_urls方法,为所有注册的表生成增删改查路由映射;
def get_urls(self):app_model_name = self.model_class._meta.app_label, self.model_class._meta.model_name #(app01,表名)'''生成url:/zg/app01/userinfo/ 显示 /zg/app01/userinfo/add 添加/zg/app01/userinfo/ 删除/zg/app01/userinfo/ 编辑 '''patterns = [#每个配置对象执行自己的changelist_view,所有他们执行视图虽然都叫changelist_view()但是不一样url(r'^$', self.changelist_view,name="%s_%s_changelist" %app_model_name),url(r'^add/$', self.add_view,name="%s_%s_add" %app_model_name),url(r'^(.+)/delete/$', self.delete_view,name="%s_%s_delete" %app_model_name),url(r'^(.+)/change/$', self.change_view,name="%s_%s_change" %app_model_name),]patterns+=self.extra_urls()return patternsView Code
涉及知识点:
1、Django路由系统 includ()路由分发的本质
本质就是就是return一个元祖,([],app名称,namespace名称空间)或者(py文件,app名称,namespace名称空间)切支持一直往下嵌套;
urlpatterns = [# url(r'^admin/', admin.site.urls),url(r'^zg/',v1.site.urls),url(r'^app666/', ([url(r'^login/', ( [url(r'^login1/',([url(r'^login2/',index)],'xxx','xxx')) ],'xx','xx')),# url(r'^logout/', self.logout),# url(r'^app01/userinfo/', self.logout),# url(r'^app01/usergroup/', self.logout),],'nb','nb')),]View Code
2、namespace 反向生成url
2.反向生成url视图def index(request):urls=reverse('tex',args=(8,))print(urls)return redirect(urls)路由系统 url(r'^test/(\d+)/',test,name='tex'),视图 def index(request):urls=reverse('tex',kwargs={'a1':9})print(urls)return redirect(urls)路由系统url(r'^test/(?P<a1>\d+)/',test,name='tex'),namespace:区分urlpatterns中同名的path如果url()中包含namespace,在 reverse(namespace:别名)时一点要加上namespdef index(request):urls=reverse('xx:tex')print(urls)return redirect(urls)url(r'^app01/',([url(r'^index/',index,name='inde'),url(r'^test/',test,name='tex'),],'xx','xx')),View Code
3、model.py中类的_meta方法:
app_name=models.UserInfo._meta.app_label #获取model中类(表),所在的apptable_name=models.UserInfo._meta.model_name #获取model中类(表),表名filed_verbose=models.UserInfo._meta.get_field('email').verbose_name #获取列的verbosename 就是描述信息View Code
4、扩展、和重写url
1、预留 url扩展和重写zhanggen.py #扩展 urldef extra_urls(self):patterns = [url(r'test/$',self.test),]return patterns#重写urldef get_urls(self):patterns = [url(r'test/$', self.test),]return patternsv1.pydef get_urls(self):patterns = [url(r'^$', self.changelist_view),url(r'^add/$', self.add_view),url(r'^(.+)/delete/$', self.delete_view),url(r'^(.+)/change/$', self.change_view),]patterns+=self.extra_urls()return patternsdef extra_urls(self):''''自定义扩展url 预留的钩子函数'''return []View Code
CRM基础框架已经搭建完成,每个请求过来都会生成url 视图映射关系,接下来应该设计 视图功能了;(day88内容)
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1、由每张表的url路由关系,的视图函数设置的都是配置对象的 changelist_view方法;如何区分?别忘了
{
models.UserInfo,UserInfo配置对象(models.UserInfo,site对象), #配置对象是相互无关系的
models.UserGroup,UserGrou配置对(models.UserInfo,对象)
}
显示中文标题
通过获取字段的verbose_name,显示中文标题
def changelist_view(self, request, *args, **kwargs):# 由于在注册的时候,实例化配置对象中传入了 model_class,site,所有可以通过self.model_class区分不同的表data_list=self.model_class.objects.all()def header():if not self.list_display:yield self.model_class._meta.model_nameelse:for i in self.list_display:yield i(self,is_header=True) if isinstance(i,FunctionType) else self.model_class._meta.get_field(i).verbose_namecontext={'headers':header(),'data_list':data_list}return render(request,'test.html',context)View Code
涉及知识点:
三元表达式
i(self,is_header=True) if isinstance(i,FunctionType) else self.model_class._meta.get_field(i).verbose_nameView Code
yield生成器: 在后端制作生成器,在前端for循环生成器,提高性能;
yield [i(self, is_header=True, row=obj) if isinstance(i, FunctionType) else getattr(obj, i) for i inself.list_display]View Code
yileld应用场景:
应用1:xrang(100),通过yield做生成器,惰性生成,不要在内存中全部创建;
应用2:在Django如果后台的数据需要做一些处理之后,再在模板中循环显示出来,就可以在后端使用yield生成;省去了后端的循环;
显示内容
涉及知识点:
列表生成式
def body():for obj in data_list:yield [i(self, is_header=True, row=obj) if isinstance(i, FunctionType) else getattr(obj, i) for i inself.list_display]View Code
在配置类自定义列标题 和自定义列内容
def xxx(self,is_header=False,row=None):if is_header==True: #如果调用该函数的时候传入的参数是is_header==True,函数执行就返回标题return '列名称'else:return '自定义列'#如果调用该函数的时候传入的参数是is_header==Fase,函数执行就返回内容View Code
2、基础配置对象 changelist_view方法,根据每张表的list_display = []配置,把生成thead 和tbody内容,封装到ChangeList类中cl对象中返回给nb/changelist.html
class ChangeList(object): #由于使用inclusion_tag,需要传很多值,就把这个些值封装到类里,一对象的形式递给前端def __init__(self,data_list,list_display,model_config_obj):self.data_list = data_listself.list_display = list_displayself.model_config_obj = model_config_objdef add_html(self): #封装add 按钮app_model = self.model_config_obj.model_class._meta.app_label, self.model_config_obj.model_class._meta.model_nameadd_url = reverse("zg:%s_%s_add" % app_model)add_html = mark_safe('<a class="btn btn-primary" href="%s">添加</a>' % (add_url,))return add_htmlView Code
涉及知识点·:
面向对象封装
def func(a1,a2,a3,a4): #如果一个函数执行需要很多参数,就可以吧这个函数改造成面向对象的模式passclass Foo():def __init__(self,a1,a2,a3,a4): #优势1:一次传入,封装所有值到一个对象;self.a1=a1self.a2 = a2self.a3 = a3self.a4 = a4def a5(self): #优势2:还可以通过方法修改封装的值passView Code
3、nb/changelist.html加载 result_list simp_tag,并把后端传来c1对象传到{% show_result_list cl%} include_tag
4、show_result_list include_tag,把c1对象中的数据,和change_list_table.html进行渲染,后nb/changelist.html显示了后端所有内容
from django.template.library import Library from types import FunctionType register=Library()@register.inclusion_tag('nb/change_list_table.html') #inclusion包含_tag自动去找nb/change_list_table.html #inclusion_tag返回什么值,就会把这些值,渲染到nb/change_list_table.html #其他页面调用show_result_list,就会得到 nb/change_list_table.html模板和show_result_list返回的结果的渲染字符串 def show_result_list(cl):def headers():if not cl.list_display:yield cl.model_config_obj.model_class._meta.model_nameelse:for v in cl.list_display:yield v(cl.model_config_obj, is_header=True) if isinstance(v,FunctionType) else cl.model_config_obj.model_class._meta.get_field(v).verbose_namedef body():for row in cl.data_list:if not cl.list_display:yield [str(row), ]else:#obj=row,在Django admin定义 def xx(self,obj): #obj =当前行的对象,方便我们显示 多列数据组合yield [name(cl.model_config_obj, obj=row) if isinstance(name, FunctionType) else getattr(row, name) forname in cl.list_display]return {'headers': headers(), #'body': body(),}#就是inclusion_tag(模板)类似simple_tagView Code
涉及知识点:
自定义simp_tag
当Django模板语言提供的simp_tag无法满足模板渲染需求时,可以利用python代码自定义自己的simp_tag
include_tag:
@register.inclusion_tag('nb/change_list_table.html')
inclusion包含_tag自动去找nb/change_list_table.html
inclusion_tag返回什么值,就会把这些值,渲染到nb/change_list_table.html
其他页面调用show_result_list,就会得到 nb/change_list_table.html模板和show_result_list返回的结果的渲染字符串
方法和函数
类执行类里的函数需要加self参数,传入一个实例对象。
对象执行类里的函数叫方法,会自动把自己作为参数传递进去,不需要加self参数。
#函数和方法class Foo(object):def __init__(self,name):self.name=namedef show(self):print('show',self.name)obj=Foo('zhangggen') # Foo.show(obj) #类直接调用 类中的函数,需要加self参数from types import FunctionType,MethodTypeprint(isinstance(Foo.show,FunctionType)) #函数print(isinstance(obj.show,FunctionType)) #方法View Code
5、前端根据权限 get_show_add_btn决定是否显示添加按钮,PermissionConfig()类。权限表钩子
由于配置类继承了PermissionConfig也继承了基础配置类,权限类优先继承,所有可以重写def get_show_add_btn(self):方法
class Modelzg(object): # 注册的时候没有使用配置对象,默认使用的配置对象''''用户基础配置类'''def __init__(self, model_class, site): #model_class=model.userinfo , site=Zgsite对象self.model_class = model_classself.site = sitelist_display = []show_add_btn = Truedef get_show_add_btn(self): #默认是True,就是显示add按钮,由于广度优先继承PermissionConfig,PermissionConfig类重写了get_show_add_btnreturn self.show_add_btnView Code
class PermissionConfig(object): #配置类重写 get_show_add_btn,控制 add按钮的显示def get_show_add_btn(self):print(self.request)return FalseView Code
注释:
由于配置对象同时继承了PermissionConfig权限配置类,v1.Modelzg默认配置类,配置对象在默认配置类写了self.request = request,而配置对象又被封装到了Cl对象里,所有前端cl.model_config_obj.get_show_add_btn 可以判断是否显示添加权限;
6、list_display = [ ]配置了字段,就显示 checkbox选择和编辑选项,预留权限钩子
list_display = []show_add_btn = Truedef zhinan(self, obj=None, is_header=False):if is_header:return '选择'else:tpl = "<input type='checkbox' value='%s' />" % (obj.pk,)return mark_safe(tpl)def option(self, obj=None, is_header=False):if is_header:return '选项'else:# edit_url = reverse('zg:app01_userinfo_change',args=(obj.pk,))edit_url = reverse('zg:%s_%s_change' % (self.model_class._meta.app_label, self.model_class._meta.model_name,),args=(obj.pk,))tpl = "<a href='%s'>编辑</a>|<a>删除</a>" % (edit_url,)return mark_safe(tpl)def get_list_play(self):res=[] #不能直接操作self.listplay 因为刷新又会从新添加一次if self.list_display: #如果用户设置显示的列 和数据 res.extend(self.list_display)res.insert(0,Modelzg.zhinan)res.append(Modelzg.option)return resView Code
预留权限钩子
def get_list_play(self):return super().get_list_play() # UserInfoConfig对象,先来PermissionConfig,再去v1.Modelzg类找get_list_play方法View Code
7、用户自定制 批量删除和初始化actions
定义一个actions = []配置项,通过基础配置类的 get_actions(self) 去获取
actions = []def get_actions(self):res=[]res.extend(self.actions)res.append(Modelzg.multi_del)res.append(Modelzg.init_action)return resView Code
把在基础配置类里定义批量删除 和初始化的函数
def multi_del(self):id_list=self.request.POST.getlist('pk')# self.model_class.objects.fifter(pk__in=id_list).delete() multi_del.short_desc='批量删除' #给函数定义一个属性def init_action(self):passinit_action.short_desc = '初始化' # 给函数定义一个属性View Code
把actions = []里定义函数名封装到 cl对象传给前端
def xxxff(model_config_obj):for i in model_config_obj.get_actions():yield (i.__name__,i.short_desc)self.actions = xxxff(model_config_obj)View Code
前端把函数名作为option的value 函数的short_desc属性作为内容显示
{% for action in cl.actions %}<option value={{ action.0}}>{{ action.1}}</option>{% endfor %}View Code
最后把选择的checkbox和选择的函数通过from表单提交到后台基础配置类 chang_list_viwe方法
self.request = requestif request.method=='POST':func = request.POST.get('action')print(func)method = getattr(self,func,None)# print(isinstance(action_func,FunctionType))# 注意从对象中获取的是方法if method:method() # 执行 actions中的函数data_list = self.model_class.objects.all()View Code
后端通过反射在后端执获取函数并执行
def multi_del(self):id_list=self.request.POST.getlist('pk')# self.model_class.objects.fifter(pk__in=id_list).delete()View Code
8、分页保留原来的搜素条件
当我们在搜素框输入了搜素条件,点击下一页应当保存搜素条件!
例如:当前页:http://127.0.0.1:8000/zg/app01/userinfo/?p=1&id=2&page=2/
点击下一页如和保存p=1&id=2参数
request_get._mutable=True #设置可编辑参数 page=Pagination(#自定制分页器需要传入的参数#1、当前访问页码current_page=model_config_obj.request.GET.get('page'),#2、 数据库总条目数total_item_count=data_list.count(),#base_url 信息base_url=model_config_obj.request.path_info,request_params=request_get)#page对象产出 开始也 和结束页self.data_list=data_list[page.start:page.end]#分页HTML代码self.page_html=page.page_html()View Code
修改分页生成的 页码
""" 使用方法:from utils.page import Pagination def users(request):current_page = int(request.GET.get('page',1))total_item_count = models.UserInfo.objects.all().count()page_obj = Pagination(current_page,total_item_count,'/users.html')user_list = models.UserInfo.objects.all()[page_obj.start:page_obj.end]return render(request,'users.html',{'user_list':user_list,'page_html':page_obj.page_html()})"""from django.utils.safestring import mark_safeclass Pagination(object):def __init__(self,current_page,total_item_count,base_url=None,per_page_count=10,show_pager_count=11,request_params=None):""":param current_page: 当前页:param total_item_count: 数据库数据总条数:param base_url: 分页前缀URL:param per_page_count: 每页显示数据条数:param show_pager_count: 对多显示的页码"""try:current_page = int(current_page)except Exception as e:current_page = 1self.current_page = current_pageself.total_item_count = total_item_countself.base_url = base_urlself.per_page_count = per_page_countself.show_pager_count = show_pager_countself.request_params=request_paramsmax_pager_num, b = divmod(total_item_count, per_page_count)if b:max_pager_num += 1self.max_pager_num = max_pager_num@propertydef start(self):""":return:"""return (self.current_page-1)* self.per_page_count@propertydef end(self):""":return:"""return self.current_page * self.per_page_countdef page_html(self):""":return:"""page_list = []if self.current_page == 1:prev = ' <li><a href="#">上一页</a></li>'else:self.request_params['page']=self.current_page-1prev = ' <li><a href="%s?%s">上一页</a></li>' % (self.base_url,self.request_params.urlencode(),)page_list.append(prev)half_show_pager_count = int(self.show_pager_count / 2)# 数据特别少,15条数据=2页if self.max_pager_num < self.show_pager_count:# 页码小于11pager_start = 1pager_end = self.max_pager_num + 1else:if self.current_page <= half_show_pager_count:pager_start = 1pager_end = self.show_pager_count + 1else:if self.current_page + half_show_pager_count > self.max_pager_num:pager_start = self.max_pager_num - self.show_pager_count + 1pager_end = self.max_pager_num + 1else:pager_start = self.current_page - half_show_pager_countpager_end = self.current_page + half_show_pager_count + 1for i in range(pager_start, pager_end):self.request_params['page']=iif i == self.current_page:tpl = ' <li class="active"><a href="%s?%s">%s</a></li>' % (self.base_url,self.request_params.urlencode(), i,)else:tpl = ' <li><a href="%s?%s">%s</a></li>' % (self.base_url,self.request_params.urlencode(), i,)page_list.append(tpl)if self.current_page == self.max_pager_num:nex = ' <li><a href="#">下一页</a></li>'else:self.request_params['page']=self.request_params+1nex = ' <li><a href="%s?%s">下一页</a></li>' % (self.base_url,self.request_params.urlencode(),)page_list.append(nex)return mark_safe(''.join(page_list))def page_html_js(self):page_list = []if self.current_page == 1:prev = ' <li><a href="#">上一页</a></li>'else:prev = ' <li><a οnclick="$.changePage(%s)">上一页</a></li>' %(self.current_page-1,)page_list.append(prev)half_show_pager_count = int(self.show_pager_count / 2)# 数据特别少,15条数据=2页if self.max_pager_num < self.show_pager_count:# 页码小于11pager_start = 1pager_end = self.max_pager_num + 1else:if self.current_page <= half_show_pager_count:pager_start = 1pager_end = self.show_pager_count + 1else:if self.current_page + half_show_pager_count > self.max_pager_num:pager_start = self.max_pager_num - self.show_pager_count + 1pager_end = self.max_pager_num + 1else:pager_start = self.current_page - half_show_pager_countpager_end = self.current_page + half_show_pager_count + 1for i in range(pager_start, pager_end):if i == self.current_page:tpl = ' <li class="active"><a οnclick="$.changePage(%s)" >%s</a></li>' % (i,i,)else:tpl = ' <li><a οnclick="$.changePage(%s)" >%s</a></li>' % (i, i,)page_list.append(tpl)if self.current_page == self.max_pager_num:nex = ' <li><a href="#">下一页</a></li>'else:nex = ' <li><a οnclick="$.changePage(%s)" >下一页</a></li>' %(self.current_page+1,)page_list.append(nex)return ''.join(page_list)def page_html_test(self):page_list = []if self.current_page == 1:prev = ' <li><a href="#">上一页</a></li>'else:prev = ' <li><a num="%s">上一页</a></li>' %(self.current_page-1,)page_list.append(prev)half_show_pager_count = int(self.show_pager_count / 2)# 数据特别少,15条数据=2页if self.max_pager_num < self.show_pager_count:# 页码小于11pager_start = 1pager_end = self.max_pager_num + 1else:if self.current_page <= half_show_pager_count:pager_start = 1pager_end = self.show_pager_count + 1else:if self.current_page + half_show_pager_count > self.max_pager_num:pager_start = self.max_pager_num - self.show_pager_count + 1pager_end = self.max_pager_num + 1else:pager_start = self.current_page - half_show_pager_countpager_end = self.current_page + half_show_pager_count + 1for i in range(pager_start, pager_end):if i == self.current_page:tpl = ' <li class="active"><a num="%s" >%s</a></li>' % (i,i,)else:tpl = ' <li><a num="%s" >%s</a></li>' % (i, i,)page_list.append(tpl)if self.current_page == self.max_pager_num:nex = ' <li><a href="#">下一页</a></li>'else:nex = ' <li><a num="%s">下一页</a></li>' %(self.current_page+1,)page_list.append(nex)return ''.join(page_list)View Code
设计知识点:
from django.http import QueryDict
当我们向django发送请求时,Django接收request.get()到的数据是query_dict数据类型
query_dict数据类型默认不可以修改
request.GET.get()._mutable=True 设置该参数后即可修改
还原成urlencode类型:request.GET.urlencode()
9、单表添加功能
0、设置list显示页面add按钮的跳转的url ,保存url后面的的get参数
def add_html(self): #封装add 按钮app_model = self.model_config_obj.model_class._meta.app_label, self.model_config_obj.model_class._meta.model_namequery_dict=QueryDict(mutable=True)query_dict['xxoo']=self.model_config_obj.request.GET.urlencode() #xxoo=p%3D1View Code
1、在add_view中定义ModelForm,让配置对象调用该方法时,可以显示html页面
self.request=requestchang_list_url_params=request.GET.get('xxoo')from django.forms import ModelFormclass AddModelForm(ModelForm):class Meta:model=self.model_class #哪个配置调用自己的add_view方法来,就创建这个表的ModelFormfields='__all__' #在前端显示的列if request.method=='GET':form=AddModelForm()context={'form':form,}return render(request,'nb/add.html',context)View Code
2、用户提交数据form验证通过,携带list页面的参数,跳转会list页面
elif request.method=='POST':form = AddModelForm(data=request.POST)if form.is_valid():form.save()base_url=self.revesrs_chang_list_url()+chang_list_url_paramsprint(base_url)return redirect(base_url)View Code
def revesrs_chang_list_url(self): #为add_viwe生成 跳转会list页面的标签list_url=reverse("%s:%s_%s_changelist" % (self.site.namespace,self.app_lable,self.model_name,) )return list_urlView Code
3、自定制ModelForm
通过方法获取默认配置里面的ModelForm如果为空就是使用默认配置,否则使用自定义配置ModelForm
model_form=Nonedef get_model_form_class(self):result=self.model_formif not result:class Defalut_Model_Form(ModelForm):class Meta:model = self.model_class # 哪个配置调用自己的add_view方法来,就创建这个表的ModelFormfields = '__all__' # 在前端显示的列 result = Defalut_Model_Formreturn resultView Code
class UserInforModelFrom(ModelForm):www=fields.CharField(widget=widgets.Textarea)class Meta:model=models.UserInfofields = '__all__'class UserInfoConfig(PermissionConfig,v1.Modelzg): #多继承 既继承v1.Modelzg又继承PermissionConfig 优先def xxx(self, obj=None, is_header=False):if is_header:return '列名称'return obj.name + obj.email# list_display = [zhinan, 'id', 'name', 'nickname', 'email', xxx, option]model_form=UserInforModelFromView Code
涉及知识点:
1、request.GET.urlencode()
获取get请求后面携带的参数
query_dict['xxoo']=self.model_config_obj.request.GET.urlencode() #xxoo=p%3D1View Code
2、ModelForm的使用
if request.method=='GET':form=AddModelForm()context={'form':form,}return render(request,'nb/add.html',context)elif request.method=='POST':form = AddModelForm(data=request.POST)if form.is_valid():form.save()base_url=self.revesrs_chang_list_url()+chang_list_url_paramsprint(base_url)return redirect(base_url)context = {'form': form,}return render(request,'nb/add.html', context)View Code
在模板中使用
<form action="" method="post">{% csrf_token %} {# {{ form.as_p }}#}{% for item in form %}<p>{{ item.label }}:{{ item }}{{ item.errors.0 }} </p>{% endfor %}<input type="submit" value="提交"> </form>View Code
10、单表编辑/删除功能
没时间实现
11、通过popup实现在userinfo表联动添加外键表usergroup的信息
userinfo添加页面显示
<!DOCTYPE html> {% load result_form %} <html lang="en"> <head><meta charset="UTF-8"><title>Title</title><link rel="stylesheet" href="/static/plugins/bootstrap/css/bootstrap.css"><style>.form-horizontal input[type='text'],input[type='checkbox'],input[type='radio'],input[type='email'], select, textarea {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;}</style> </head> <body> <h1>添加页面</h1> {% show_form form %} <h1></h1> </body> </html>View Code
执行inclusion_tag,完成对外键表联动添
from django.template.library import Library from types import FunctionType from django.forms.models import ModelMultipleChoiceField from django.urls import reverse from django.forms.models import ModelChoiceField from django.db.models.query import QuerySet from app02.service.v1 import siteregister=Library()def xxxx(model_form_obj):for item in model_form_obj :tpl={'has_popup':False,'item':item,'popup_url':None}# 判断userinfo表的列中是否含外键字段,并且外键的表也得已经注册if isinstance(item.field,ModelChoiceField) and item.field.queryset.model in site._registry:tpl['has_popup']=Trueflied_class=item.field.queryset.model #字段外键对应的表名app_label=flied_class._meta.app_label #外键表所在的appmodel_name=flied_class._meta.model_name#外键表的表名url= reverse('{0}:{1}_{2}_add'.format(site.namespace,app_label,model_name ))tpl['popup_url']=url# print(item.field) #字段类型yield tpl@register.inclusion_tag('nb/change_form.html') def show_form(model_form_obj):return {'form':xxxx(model_form_obj)}#inclusion_tag returm什么数据,就在changelist.html里面渲染什么数据 #最后谁调用 show_form simpletag就包含了 changelist.html渲染完成页面View Code
inclusion_tag使用的页面
<form class="form-horizontal" method="POST" novalidate>{% csrf_token %}{% for col in form %}<div class="form-group col-sm-6"><label class="col-sm-3 control-label">{{ col.item.label }}</label><div class="col-sm-9" style="position: relative">{{ col.item }}{{ col.item.errors.0 }}{% if col.has_popup %}<a onclick="popup('{{ col.popup_url }}')" >添加</a>{% endif %}</div></div>{% endfor %}<div class="form-group"><div class="col-sm-offset-10 col-sm-2"><input type="submit" class="btn btn-primary" value="确认添加"/></div></div> </form>View Code
elif request.method=='POST':form = self.get_model_form_class()(data=request.POST)if form.is_valid():obj=form.save()base_url=self.revesrs_chang_list_url()url="%s?%s" %(base_url,chang_list_url_params)tagId = request.GET.get('_popup', None)if tagId:name=str(obj) #获取 name/titleval=obj.pkreturn render(request,'nb/popuprespose.html',{ 'name':name,'tagId':tagId,'val':val })return redirect(url)View Code
涉及知识点:
name=str(obj) 执行在model.py中定义的 __str__方法
查看form对象中包含列的相关属性
def xxxx(model_form_obj):for item in model_form_obj :tpl={'has_popup':False,'item':item,'popup_url':None}# 判断userinfo表的列中是否含外键字段,并且外键的表也得已经注册if isinstance(item.field,ModelChoiceField) and item.field.queryset.model in site._registry:tpl['has_popup']=Trueflied_class=item.field.queryset.model #字段外键对应的表名app_label=flied_class._meta.app_label #外键表所在的appmodel_name=flied_class._meta.model_name#外键表的表名url= reverse('{0}:{1}_{2}_add'.format(site.namespace,app_label,model_name ))tpl['popup_url']=url# print(item.field) #字段类型yield tplView Code
js之popup弹窗 (3个HTML页面)
1、p1页面定义1个popup回调函数,p1页面绑定1个popup弹窗时间 到 /p2/
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Title</title> </head> <body> <h1>p1页面</h1> <input type="button" value="按钮" onclick="popfunc();"></body> <script>function popfunc() {window.open('/p2/','别名',"status=1, height:500, width:600, toolbar=0, resizeable=0"); {# 点击按钮 出现弹窗 到p2 #}}function xxx(name) {alert(name)} </script> </html>View Code
2、/p2/下的视图添加数据库操作,把增加的row对象模板渲染,re1个页面popuprespose.html
def p2(request):if request.method=='GET':return render(request,'p2.html')elif request.method=='POST':from app01 import modelscity=request.POST.get('city')obj= models.UserGroup.objects.create(title=city)return render(request,'popuprespose.html',{'obj':obj})View Code
3、popuprespose.html页面 自执行函数执行popup发起页面(p1)的回调函数
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Title</title> </head> <body> <h1>p1页面</h1> <input type="button" value="按钮" onclick="popfunc();"></body> <script>function popfunc() {window.open('/p2/','别名',"status=1, height:500, width:600, toolbar=0, resizeable=0"); {# 点击按钮 出现弹窗 到p2 #}}//回调函数function xxx(name) {alert(name)}View Code
12、组合搜素逻辑
组合搜素这个功能,其实很简单却浪费我大量闹细胞;
1、把所有在显示页面需要的数据封装进ChangeList类里,实例化cl对象传到模板语言通过simple-tag循环、调用生成标签;这是前提;
显示页面模板中的2层for循环是切入点:
div class="container"><h1>数据列表</h1>{#显示组合搜素开始#}<div class="row">{% if cl.list_filter %}<!-- 遍历chang_list的gen_list_filter 方法,也就是Userinfo表中的 query_aet) -->{% for row_items in cl.gen_list_filter %}<div class="filter-item"><!-- 遍历自自定义的,具有__iter__方法的个可迭代对象 -->{% for item in row_items %}{{ item }}{% endfor %}</div>{% endfor %}<!-- 遍历自自定义的,具有__iter__方法的个可迭代对象 结束 -->{% endif %}<!-- 遍历chang_list中的gen_list_filter 方法结束 --></div>{#显示组合搜素结束#}View Code
1层for循环: yelid row_items = RowItems(option, data_list, params)
其中ChangeLis中定义的gen_list_filter方法是个生成器,获取list_filter = [‘group’,'roles ']配置项对应表的数据,for循环gen_list_filter得到一个RowItems对象(也是个生成器);
def gen_list_filter(self):model_class = self.model_config_obj.model_class # userinfo 类params = self.model_config_obj.request.GET # 从配置对象中获取request参数,传入到 RowItems类for option in self.list_filter: # list_filter=['group对象','name对象','roles对象']fild_obj = model_class._meta.get_field(option.name) # 获取到列对象from django.db.models.fields.related import RelatedFieldif isinstance(fild_obj, RelatedField): # 判断是否为外键字段field_related_class = fild_obj.rel.to # 获取到列对应的外键表data_list = field_related_class.objects.all()row_items = RowItems(option, data_list, params) # 实例化一个RowItems对象else: # 判断如果是普通字段直接取值data_list = model_class.objects.all()row_items = RowItems(option, data_list, params) # 实例化一个RowItems对象yield row_itemsView Code
2层for循环: yield mark_safe( tmp = '<a href="?%s">%s</a>' % (url, text))
遍历data_list,query_set[obj,objobj ],所以data_list中有几个对象就行数据,就得到是1个带有herf链接url的A标签;
class RowItems():def __init__(self, option, data_list, params):self.option = optionself.data_list = data_listself.param = copy.deepcopy(params) # query_dict对象print(self.param) # <QueryDict: {'group': ['1'], 'roles': ['2']}>self.param._mutable = Truedef __iter__(self):current_pk = self.param.get(self.option.name) # str类型 self.param.pop(self.option.name)all_url = self.param.urlencode()tmp = '<a href="?%s">全部</a>' % (all_url)yield mark_safe(tmp)for obj in self.data_list:pk = str(obj.pk)text = str(obj)self.param[self.option.name] = pkurl = self.param.urlencode()if current_pk == pk:tmp = '<a class="active" href="?%s">%s</a>' % (url, text) # 设置选择标签颜色else:tmp = '<a href="?%s">%s</a>' % (url, text)yield mark_safe(tmp)View Code
RowItems()的__iter__ 方法,每次根据request.get携带的参数,yeild a标签
1、先根据get请求,request.getlist(self.optionname) 获取current_pk_or_list参数
列表 [5,6]
2、首先self.param.pop(self.option.name)清空group参数自己,留下role参数,生成全部A标签 ;
3、循环遍历data_list数据,根据query_set[obj,obj,obj]中有几个obj,生成几个A标签;
3.1先得到每个obj的id 和 str(obj)中文显示内容
3.2判断是否是多选;
3.3 判断当前for 循环的obj对象的pk 是否在current_pk_or_list参数列表 [5,6]?
3.4 如果不在就把当前 obj的的id 添加到current_pk_or_list [1,5,6]
3.5 重置<QueryDict: {}>,为<QueryDict: {'group': ['1', '5', '6']}>
3.6 urlendcodeQueryDict,每次赋值A标签的herf属性
组合搜素 A标签生成href url思想:
1、用户首次访问:http://127.0.0.1:8000/zg/app01/userinfo/
reques.getlist(self.option.name) #得到 current_pk_or_list为空 []
2、RowItems类的__iter__方法遍历 data_list,query_set [ obj ,obj ]
#生成全部右侧 obj data_list=[obj,obj,obj ]for obj in self.data_list:#每个obj 生成 每个 A标签pk = str(obj.pk) #1text = str(obj) #阿斯蒂芬if self.option.is_muti: #如果配置了多选if pk not in current_pk_or_list: #如果obj.pk不在【1.2】tmp=[]tmp.extend(current_pk_or_list)tmp.append(pk)self.param.setlist(self.option.name,tmp) #注意在原来不变的基础上多加1个?group=2&group=3View Code
如果obj.pk不在current_pk_or_list [ ] 中,就把obj.pk添加到tmp,第一次循环进来 tmp=[1 ]
tmp=[]tmp.extend(current_pk_or_list)tmp.append(pk)
3、修改 query_dict,<QueryDict: { option.name : [1] }
self.param.setlist(self.option.name,tmp)
4、url endconde编码 ?group=1
url = self.param.urlencode()
5、遍历循环结束,根据obj个数生成A标签的 url
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1、首次生成url之后,在页面点击搜素项,http://127.0.0.1:8000/zg/app01/userinfo/?group=1
reques.getlist(self.option.name) #得到 current_pk_or_list为 [1]
2、RowItems类的__iter__方法遍历 data_list,query_set [ obj ,obj, ]
如果obj.pk不在current_pk_or_list [ ] 中,就把obj.pk添加到tmp,第一次循环进来 tmp=[1,2 ] / tmp=[1,3 ] / tmp=[1,4]
3、url endconde编码,生成A标签的url
补充 list_filter 配置 函数和‘数据库字段’
class UserInfoConfig(sites.AryaConfig):list_display = ['name', ]def func(self,change_list,option):#option,change_list,data_list,param_dict=Nonedata_list=models.UserInfo.objects.filter(id__gt=2)return sites.FilterRow(option,change_list,data_list,self.request.GET)list_filter = [ #文本显示的内容 url参数sites.FilterOption('group', True, lambda x: x.title, lambda x: x.id),sites.FilterOption('name', False, lambda x: x.name, lambda x: x.name),# sites.FilterOption(func, False, lambda x: x.name, lambda x: x.name), #自定制函数]View Code
总结:根据 request.getlist()获取的 [ ], 判断当前循环到的obj.id是否在[],如不不存在 添加到 [ ],修改 query_dict { 'option' : [ ] },urlencode编码为url格式作为A标签的跳转链接;
二、CURD组件使用
经过漫长的开发周期,模仿DjangoAdmin开发出来的CURD组件终于可以使用了,赐名arya;下面是在1个崭新的Django项目中使用arya组件流程;
1、在settings.py组册arya为本程序app
INSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','app01.apps.App01Config','arya.apps.AryaConfig',]View Code
2、任意APP下创建 arya.py
3、在arya.py中组册model_class
from . import models from arya.service.sites import site #导入单例模式 site.register(models.Department,)View Code
4、为已经注册的model_class,生成路由映射关系;
from django.conf.urls import url from django.contrib import admin from app01 import views from arya.service.sites import site urlpatterns = [# url(r'^admin/', admin.site.urls),url(r'^index/',views.index),url(r'^arya/',site.urls), ]View Code
5、修改母版板样式layout
diango做模板渲染得时候寻找模板的顺序是,从最外层templates 到app里面的templates
所以如果想修改网页显示的样式,就可以在最外层templates,定义自己的母版,让其他页面继承;
6、增、删、改、查基本操作
from . import models from arya.service import sites #导入单例模式class DepartmentConfig(sites.AryaConfig): #注册部门表list_display = ['title']sites.site.register(models.Department,DepartmentConfig)class UserinfoConfig(sites.AryaConfig): #注册用户表list_display = ['name','email']sites.site.register(models.UserInfo, UserinfoConfig)class CoursetConfig(sites.AryaConfig): #注册 课程表list_display = ['name']sites.site.register(models.Course, CoursetConfig)class SchoolConfig(sites.AryaConfig): #注册部门表list_display = ['title']sites.site.register(models.School,SchoolConfig)View Code
7、插件扩展
0、脱离插件
1、扩展URL,extra_url方法,重写URL get_urls方法,添加RBAC权限
1.0.添加url
class UserinfoConfig(sites.AryaConfig): #注册用户表list_display = ['name','email']def extra_urls(self):app_model_name = self.model_class._meta.app_label, self.model_class._meta.model_namepatterns = [url(r'^(.+)/detail/',self.detail_view, name="%s_%s_changelist" % app_model_name),]return patternsdef detail_view(self,request,pk):obj=self.model_class.objects.all().filter(pk=pk).first()print(obj.name,obj.id)return HttpResponse('显示详情')View Code
#工单系统class Worker_orderConfig(PermissionConfig,v1.AryaConfig):def row_graph(self, row=None, is_header=None):if is_header:return "工单状态"button_list=["<button class='btn btn-danger btn-sm button' type='button'>未理中</button>","<button class='btn btn-success btn-sm button' type='button'>处理中</button>","<button class='btn disabled btn-sm button' type='button'>已关闭</button>"]return mark_safe(button_list[row.status] )def details(self, row=None, is_header=None):if is_header:return "工单流程"return mark_safe("<a href="">详细</a>" )def fenpei(self, row=None, is_header=None):if is_header:return "分配"return mark_safe("<a href='/allocation/?id=%s'>分配</a>"%(row.id) )list_display = ['alarm_time', 'initiator', 'title', 'agent','desc',row_graph,fenpei,details,]def extra_url(self):app_model_name = self.model_class._meta.app_label, self.model_class._meta.model_nameprint(app_model_name)p= [url(r'^worker_order_out/$', self.detail_view, name="%s_%s_t" % app_model_name),]return pdef detail_view(self, request):return HttpResponse('显示详情')v1.site.register(models.Worker_order,Worker_orderConfig)扩展URL
1.2.arya生成url的规则
/arya开头/app名称/model表名/扩展url的名/: 例如:/arya/cmdb/worker_order/worker_order_out/
1.3.在RBAC中为角色增加该URL的访问权限
在权限管理----》URL权限添加权限标题:测试为标题
2、自定义添加、显示、删除页面,重写基础配置类中的add_view 、 changelist_view 、delete_view
注意每一个方法,都要执行self.request=request
class UserinfoConfig(sites.AryaConfig): #注册用户表list_display = ['name','email']def add_view(self,request):return render(request,'test.html')View Code
三、RBAC权限录入
RBAC基于角色的权限管理系统回顾:详见 Django之权限管理插件
表结构:用户表 Many to many 角色表 Many to many 权限表 foreign key 菜单表
中间件:判断当前访问用户是否有访问权限
登录成功初始化:把当前用户所有权限初始化放到session中
配置文件: 无需登录就能使用的权限,session的key
自动生成菜单:css、JavaScript
1、注册RBAC app
'rbac.apps.RbacConfig',View Code
2、清空rbac插件的migration记录
3、迁移rbac插件的数据
python manage.py makemigrationspython manage.py migrate
4、在rbac创建arya.py注册rbac model_class
from arya.service import sites from . import modelssites.site.register(models.User) sites.site.register(models.Role)class PermissionConfig(sites.AryaConfig):passsites.site.register(models.Permission,PermissionConfig) sites.site.register(models.Menu)View Code
5、创建权限信息arya/rbac/permission/
6、完善CURD插件功能;
1、添加权限自动发现URL;
(1)方式1
定制list_play=[]
重写add_view
lass PermissionConfig(sites.AryaConfig):def dabo(self, obj=None, is_header=False):if is_header:return '其他'return obj.caption+'大波'list_display = ['caption','url','menu',dabo]def add_view(self, request, *args, **kwargs):from pro_crm.urls import urlpatternsall_url_list=get_all_url(urlpatterns,prev='/',is_first=True,)model_form_cls = self.get_model_form_class()popup_id = request.GET.get(self.popup_key)if request.method == 'GET':form = model_form_cls()return render(request, "permission_add_popup.html" if popup_id else "permission_add.html",{'form': form,'url_list':all_url_list})elif request.method == "POST":form = model_form_cls(data=request.POST, files=request.FILES)if form.is_valid():obj = self.save(form, True)if obj:if popup_id:context = {'pk': obj.pk, 'value': str(obj), 'popup_id': popup_id}return render(request, 'arya/popup_response.html',{"popup_response_data": json.dumps(context)})else:return redirect(self.changelist_url_params)return render(request,"permission_add_popup.html" if popup_id else "permission_add.html", {'form': form,'url_list':all_url_list})sites.site.register(models.Permission,PermissionConfig)View Code
(1)方式2
修改ModelForm组件
from arya.service import sites from . import models from django.shortcuts import render,redirect import jsonfrom django.urls.resolvers import RegexURLPattern def get_all_url(patterns,prev,is_first=False,result=[],):if is_first:result.clear()for item in patterns:v=item._regex.strip('^$')if isinstance(item,RegexURLPattern):val=prev+vresult.append((val,val))else:get_all_url(item.urlconf_name,prev+v)return resultsites.site.register(models.User) sites.site.register(models.Role)from django.forms import ModelForm from django.forms import fields from django.forms import widgets#自定义model_formclass PermissionModelForm(ModelForm):#ModelForm 可以结合Model把所有数据库字段在页面上生成,也可以增加额外的字段;url=fields.ChoiceField()class Meta:fields = "__all__"model = models.Permission #注意不是modelsdef __init__(self,*args,**kwargs): #重写父类的 __init__方法,每次实例化实时更新 form中的数据super(PermissionModelForm,self).__init__(*args,**kwargs)from pro_crm.urls import urlpatternsself.fields['url'].choices=get_all_url(urlpatterns,'/', True)class PermissionConfig(sites.AryaConfig):def dabo(self, obj=None, is_header=False):if is_header:return '其他'return obj.caption+'大波'list_display = ['caption','url','menu',dabo]model_form = PermissionModelFormView Code
2、通过装饰器实现每次请求保存request信息
def warpper(self,func): #每次获取 self.requesr=request@functools.wraps(func) # def inner(request,*args,**kwargs):self.request=requestreturn func(request,*args,**kwargs )return innerView Code
patterns = [url(r'^$',self.warpper(self.changelist_view), name="%s_%s_changelist" % app_model_name),url(r'^add/$',self.warpper(self.add_view), name="%s_%s_add" % app_model_name),url(r'^(.+)/delete/$',self.warpper(self.delete_view), name="%s_%s_delete" % app_model_name),url(r'^(.+)/change/$',self.warpper(self.change_view), name="%s_%s_change" % app_model_name),]View Code
设计知识点:
(1)、装饰器:
@warpper
def foo()
pass
foo=warpprt(foo)=return的结果
(2)、@functools.wraps(func)
装饰器函数里面的文档介绍 完全= 原生函数的文档介绍
(3)、ModelForm 增加数据库字段之外的field,ModelForm对象实时更新数据
Django之Form、ModelForm 组件
ModelForm 可以结合Model把所有数据库字段在页面上生成,也可以增加额外的字段;
规则:如果增加的字段和数据里的filed重名则覆盖,不重名则新增;
也可以通过重写__init__ ,每次实例化1个form对象,实时更新数据;
View Code
四、Arya整合RBAC整合CRM
1、创建名为crm的app,并且在setings.py里面注册
INSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','arya.apps.AryaConfig','rbac.apps.RbacConfig','crm.apps.CrmConfig',]View Code
2、使用RBAC,配置RBAC的表;(只能访问自己权限的URL,登录之后获取自己的权限列表放入request.session)
from arya.service import sites from . import models from django.shortcuts import render,redirect import jsonfrom django.urls.resolvers import RegexURLPattern def get_all_url(patterns,prev,is_first=False,result=[],):if is_first:result.clear()for item in patterns:v=item._regex.strip('^$')if isinstance(item,RegexURLPattern):val=prev+vresult.append((val,val))else:get_all_url(item.urlconf_name,prev+v)return resultsites.site.register(models.User) sites.site.register(models.Role)from django.forms import ModelForm from django.forms import fields from django.forms import widgets#-------------------用户表相关配置 class UserConfig(sites.AryaConfig):list_display = ['username','email' ] #--------------------角色相关配置 class RoleConfig(sites.AryaConfig):list_display = ['caption',]#----------------------权限相关配置 #自定义model_form class PermissionModelForm(ModelForm):#ModelForm 可以结合Model把所有数据库字段在页面上生成,也可以增加额外的字段;url=fields.ChoiceField()class Meta:fields = "__all__"model = models.Permission #注意不是modelsdef __init__(self,*args,**kwargs): #重写父类的 __init__方法,每次实例化实时更新 form中的数据super(PermissionModelForm,self).__init__(*args,**kwargs)from pro_crm.urls import urlpatternsself.fields['url'].choices=get_all_url(urlpatterns,'/', True) class PermissionConfig(sites.AryaConfig):def dabo(self, obj=None, is_header=False):if is_header:return '其他'return obj.caption+'大波'list_display = ['caption','url','menu',dabo]model_form = PermissionModelFormsites.site.register(models.User,UserConfig) sites.site.register(models.Permission,PermissionConfig) sites.site.register(models.Role,RoleConfig) sites.site.register(models.Menu)View Code
3、添加crm登录页面
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>CRM系统登录</title> </head> <body> <form action="" method="post">{% csrf_token %}<p>用户名:<input type="text" name="username"></p><p>密码:<input type="submit" name="pwd"></p><input type="submit" value="提交"> </form></body> </html>View Code
4、添加crm首页
设置母版layout
<!--- 加载rbac插件中的simp-tag-->
<!--- 加载rbac插件中的css-->
//加载rbac插件中的js
{% load static %} {% load rbac %} <!--- 加载rbac插件中的simp-tag--> <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>{{ title }}</title><link rel="stylesheet" href="{% static 'arya/plugins/bootstrap/css/bootstrap.css' %}"/><link rel="stylesheet" href="{% static 'arya/css/commons.css' %}"/><sytyle><!--- 加载rbac插件中的css-->{% rbac_css %}</sytyle>{% block css %}{% endblock %} </head> <body> <div class="pg-header"><div class="logo left" style="text-align: center;background-color: #1c5a9c;"><a href="#" style="color: #ffffff;font-size:22px;font-weight: bold;text-decoration: none">CRM系统</a></div><div class="left-menu left"><a class="menu-item" href="#">平台首页</a><a class="menu-item" href="#">资产首页</a></div><div class="right-menu right clearfix"><div class="user-info right"><a href="#" class="avatar"><img class="img-circle" src="{% static 'arya/img/default_avatar.png' %}"></a><div class="more-info"><a href="#" class="more-item">个人信息</a><a href="/logout.html" class="more-item">注销</a></div></div><a class="user-menu right">消息<i class="fa fa-commenting-o" aria-hidden="true"></i><span class="badge bg-success">2</span></a><a class="user-menu right">通知<i class="fa fa-envelope-o" aria-hidden="true"></i><span class="badge bg-success">2</span></a><a class="user-menu right">任务<i class="fa fa-bell-o" aria-hidden="true"></i><span class="badge bg-danger">4</span></a></div></div><div class="pg-body"><div class="menu">{% rbac_menu request %}</div><div class="content">{% block breadcrumb %} {% endblock %}{% block content %} {% endblock %}</div> </div><script src="{% static 'arya/js/jquery-1.12.4.js' %}"></script> <script src="{% static 'arya/plugins/bootstrap/js/bootstrap.js' %}"></script><script>{% rbac_js %} //加载rbac插件中的js </script> {% block js %} {% endblock %} </body> </html>View Code
设置crm首页
{% extends 'arya/layout.html' %}{% block content %}<h1>欢迎登录</h1>{% endblock %}View Code
5、index视图执行RBAC插件逻辑
1)登录成功之后初始化权限信息
initial_permission(request,obj) #初始化用户权限信息
2)页面显示时生成 菜单HTML
3)setings.py配置文件 引入rbac中间件、设置rbac使用的key、simple_tag生成多级菜单
'rbac.middleware.rbac.RbacMiddleware', #引入rbac中间件View Code
# ############################## RBAC权限相关配置开始 ############################## # session中保存权限信息的Key RBAC_PERMISSION_URL_SESSION_KEY = "rbac_permission_url_session_key"# Session中保存菜单和权限信息的Key RBAC_MENU_PERMISSION_SESSION_KEY = "rbac_menu_permission_session_key" RBAC_MENU_KEY = "rbac_menu_key" RBAC_MENU_PERMISSION_KEY = "rbac_menu_permission_key"# 匹配URL时指定规则 RBAC_MATCH_PARTTERN = "^{0}$"# 无需权限控制的URL RBAC_NO_AUTH_URL = ['/login/',"/index/", ]# 无权访问时,页面提示信息 RBAC_PERMISSION_MSG = "无权限访问"# 菜单主题 RBAC_THEME = "default" # ############################## RBAC权限相关配置结束 ##############################View Code
6、在权限中间件之前,通过UserAuthMiddleware判断用户是否已经登录,登录之后才有必要设置权限,否则直接返回登录页面;
权限中间件没有必要,每次每个用户都要执行一次,所有在权限中间件之前新增一个中间件,先判断用户是否已经登录,没有登录直截了当让他先登录;
if obj:request.session['user_info'] = {'nid': obj.id} #用户登录后 设置登录标记initial_permission(request,obj) #初始化用户权限信息return redirect('/index/') #跳转到首页
class UserAuthMiddleware(MiddlewareMixin):def process_request(self,request):# 如果是/login/可以继续执行if request.path_info == '/login/':return None# 设置权限之前,判断用户是否已经登录?登录之后才判断是否有权限user_info = request.session.get('user_info')if not user_info:return redirect('/login/')View Code
7、CRM的用户表和RBAC的用户表做One to One关联
from rbac.models import User class UserInfo(models.Model):"""员工表"""user = models.OneToOneField(verbose_name='用户账号',to=User)name=models.CharField(verbose_name='员工姓名',max_length=16)phone=models.CharField(verbose_name='员工电话',max_length=16)depart = models.ForeignKey(verbose_name='部门', to="Department")View Code
8、CRM表结构 配置与页面显示
自定义函数:list_display显示多对多关系字段
aryaCURD插件: list_display 定义函数,自定义函数返回什么list页面显示什么,外键关系连表查询
from . import models from arya.service import sites from django.utils.safestring import mark_safe class DepartmentConfig(sites.AryaConfig):list_display = ['title']sites.site.register(models.Department,DepartmentConfig)class UserInfoConfig(sites.AryaConfig):list_display = ['name','phone']sites.site.register(models.UserInfo,UserInfoConfig)class CourseConfig(sites.AryaConfig):list_display = ['name']sites.site.register(models.Course,CourseConfig)class SchoolConfig(sites.AryaConfig):list_display = ['title'] sites.site.register(models.School,SchoolConfig)class ClassListConfig(sites.AryaConfig):def course_dispaly(self,obj=None,is_header=False):if is_header:return '班级'return '%s(%s)期'% (obj.course.name,obj.semester)def zhanggen(self,obj=None,is_header=False):if is_header:return '任课老师'teachers=[]for obj in obj.teachers.all():tpl='<span style="dispaly:inline-block;padding:3px;margin:2px;boder:1px solid #add;" >%s</span>'% (obj.name)teachers.append(tpl)return mark_safe(''.join(teachers))list_display = ['school',course_dispaly,'price','tutor',zhanggen]sites.site.register(models.ClassList,ClassListConfig)View Code
自定义函数:list_display显示choices字段
利用obj的get_gender_display()特性,显示choice字段对应的中文名称
1 def gender_display(self,obj=None,is_header=False): #显示 choice 字段 2 if is_header: 3 return '性别' 4 return obj.get_gender_display() 5 list_display = ['qq', 'name', 'consultant',zhanggen,gender_display,] 6 sites.site.register(models.Customer,CustomerConfig)View Code
小结:
经过CRM业务和Aaya(增、删、改、查)插件 list_display配置项的结合配置
就可以 把数据库字段、外键字段、多对多字段、choice字段 都显示出来了;
9、CRM表结构 配置组合筛选
(1)、改进list_filter
组合搜素显示:
foreign key
M2M
choice
def __init__(self, option, change_list, data_list, param_dict=None,is_choices=False):View Code
for obj in self.data_list:param_dict = copy.deepcopy(self.param_dict)if self.is_choices:pk=str(obj[0])text=obj[1]View Code
通过 Q添加筛选条件
sites.FilterOption('consultant',condtion=Q(depart_id=2)),View Code
def __init__(self, field_or_func, is_multi=False, text_func_name=None, val_func_name=None,condtion=None):View Code
@propertydef get_condtion(self):from django.db.models import Qif self.condtion:return self.condtioncon=Q()return conView Code
9、点击组合筛选,显示筛选数据;(读取URL参数,拼接成字典,去数据库查询)
@propertydef get_filter_conditon(self):fields2 = [obj.name for obj in self.model_class._meta._get_fields()]params = self.request.GETcon = {}for k in params:if k in fields2:v = self.request.GET.getlist(k)k = '%s__in' % (k)con[k] = vreturn conView Code
涉及知识点:
1、获取数据库中 外键字段、choice字段(注意不包含多对多字段)
fields=[ obj.name for obj in self.model_class._meta.fields] #获取数据中 外键字段、choice字段(主语没有多对多)
2、获取数据库中 多对多字段
fields1 = [obj.name for obj in self.model_class._meta.many_to_many] #获取多对多字段
3、获取数据库中所有字段 (包含反向关联、多对多字段)
# fields2 = [obj.name for obj in self.model_class._meta._get_fields()] # 获取反向关联的字段(包含对多多)
4、查询数据去重 distinct()
data_list = self.model_class.objects.filter(**self.get_filter_conditon).filter(self.get_search_condtion).distinct() #distinct() 多选去重
10、CRM客户部 配置模糊搜素功能 search_list = ['name', 'qq'],;
search_list = ['name', 'qq']
基础配置类
search_list = []def get_search_list(self):search_list = []search_list.extend(self.search_list)return search_listView Code
@propertydef get_search_condtion(self):con=Q()con.connector='OR'val=self.request.GET.get(self.q)if not val:return confild_list=self.get_search_list()for field in fild_list:field='%s__contains'%(field)con.children.append((field,val))return conView Code
ChangeList类
self.search_list = model_config.get_search_list()
def seach_attr(self):val=self.model_config.request.GET.get(self.model_config.q)print(val)return {'value':val,'name':self.model_config.q}View Code
模板
{% if cl.list_filter %}<div class="comb-search">{% for row in cl.gen_list_filter %}<div class="row">{% for col in row %}{{ col }}{% endfor %}</div>{% endfor %}</div>{% endif %}{% if cl.search_list %}<div class="row"><form method="get"><input value="{{cl.seach_attr.value}}" class="form-control" name="{{cl.seach_attr.name}}" style="width: 250px;display: inline-block" type="text" placeholder="请输入关键字"><button class="btn btn-primary"><span class="glyphicon glyphicon-search"></span></button></form></div>{% endif %}View Code
转载于:.html
CURD插件(仿Django
前言
如何提升自己的开发效率?
每个新项目都是自己经做过的项目(经验所致),在项目开发过程中不断总结、封装属于自己的组件,
例如:每个web项目大部分都涉及增删改查,分页显示,搜素,CRM就是这样的组件,是一件很有必要的事情;
CURD组件(arya):模仿DjangoAdmin编写增删改查插件;
组件功能:通过在后台注册表名配置自定制类,操作、显示数据库中的内容;
组件设计目标:把CRM模块嵌套到不同具有增删改查功能的Django程序上,实现快速开发基于web的后台管理系统;
组件配置项:
list_display=[ '字段',‘函数’] : 定制显示页面显示的列名,和内容;
action : 定制action,批量操作;
show_add_btn=True :定制是否显示添加按钮
model_form=None :定制model_form 提供编辑、添加、删除页面,通过popup操作关联表数据
lister_filter :组合筛选项
search_list = ['name', 'qq'] :模糊搜素
组件说明:
Site类:注册和生成基础url
---方法:
url(路由调用)
get_usrl(url方法调用)
register(注册调用)
login(登录视图)
logout(登出视图)
---字段:
namespace(url所需名称空间)
self. _registry={ }(注册生成
# {
# model.表名1,配置对象1
# model.表名2,配置对象2
# }
)
self.name app名称
Config类:生成基础配置,生成增、删、改、查url,处理request请求;
---方法
warpper() 通过装饰器实现每次请求保存request信息
add_view() 增、删、改、查视图
changlist_view()
delete_view()
changge_view()
urls() 增、删、改、查url
extra_urls()url扩展
类字段:
list_dispaly=[字段、函数 ] 配置list显示页面需要列 和内容
get_listdisplay()
actions = [函数] 配置选中chebox可执行的操作
get_actions()
add_btn=True list显示页面是否显示添加按钮
get_ show_add_btn()
model_form = None 类 配置 增、删、改。查操作的model_form
get_model_form_class()
list_filter = [字段、函数 ] 配置组合搜素内容
get_list_filter()
对象字段
self.model_class (models.UserInfo)
self.request (每个对象的request请求信息)
self.site (注册类 )
ChangList类:封装list显示页面所有数据,通过对象的方式传到前端模板;
方法:
add_html(self),gen_list_filter 生成添加按钮 、生成组合搜素 需要的数据比较多不适合在前端模板生成,所以选择后端;
类字段:
对象字段:
self.model_config_obj (因为list显示页面,需要list_display,action,show_add。。。很多配置类的属性,所有干脆把 配置对象封装到ChangList类中 )
1 class ChangeList(object): # 由于使用inclusion_tag,需要传很多值,就把这个些值封装到类里,一对象的形式递给前端 2 def __init__(self, data_list, model_config_obj): 3 self.list_display = model_config_obj.get_list_play() 4 self.actions = model_config_obj.get_actions() 5 self.model_config_obj = model_config_obj 6 self.list_filter = model_config_obj.get_list_filter()View Code
self.request = request 、self.datalist 用于分页
-----------------------------------------------------------------------------------------------------------------
option类:组合搜素 配置项
list_filter= [v1.OptionConfig('group',True), v1.OptionConfig('roles',False), ]
方法:
is_func(self): 判断是否是函数
name(self): 生成函数名或者字段名
对象字段
"""
:param field: 字段名称或函数
:param is_multi: 是否支持多选
:param text_func_name: 在Model中定义函数,显示文本名称,默认使用 str(对象)
:param val_func_name: 在Model中定义函数,显示文本名称,默认使用 对象.pk
RowItems类:生成组合搜素需要的A标签
class RowItems():def __init__(self, option, data_list, params):self.option = optionself.data_list = data_listself.param = copy.deepcopy(params) # query_dict对象print(self.param)# print(self.param) # <QueryDict: {'group': ['1'], 'roles': ['2']}>self.param._mutable = Truedef __iter__(self):if self.option.is_muti: #如果配置了多选current_pk_or_list = self.param.getlist(self.option.name) #[1,2]print(current_pk_or_list)else:current_pk_or_list = self.param.get(self.option.name) # [1]if self.param.get(self.option.name): #如果<QueryDict: {'roles': ['2'], 'group': ['4', '5', '3', '2', '1']}> 能获取到 group roles# 生成全部self.param.pop(self.option.name) # 全部就pop自己all_url = self.param.urlencode()tmp = '<a href="?%s">全部</a>' % (all_url)yield mark_safe(tmp)else:all_url = self.param.urlencode() #如果访问的url 没有携带参数tmp = '<a class="active" href="?%s">全部</a>' % (all_url)yield mark_safe(tmp)#生成全部右侧 obj data_list=[obj,obj,obj ]for obj in self.data_list:#每个obj 生成 每个 A标签pk = str(obj.pk) #1text = str(obj) #阿斯蒂芬if self.option.is_muti: #如果配置了多选if pk not in current_pk_or_list: #如果obj.pk不在【1.2】tmp=[]tmp.extend(current_pk_or_list)tmp.append(pk)self.param.setlist(self.option.name,tmp) #注意在原来不变的基础上多加1个?group=2&group=3else:self.param[self.option.name]=pk# print(self.param) #<QueryDict: {'group': ['2', '3'], 'roles': ['1']}>url = self.param.urlencode()if not self.option.is_muti: #单选生成A标签if current_pk_or_list == pk: #url中传来的 get参数tmp = '<a class="active" href="?%s">%s</a>' % (url, text) # 设置选择标签颜色else:tmp = '<a href="?%s">%s</a>' % (url, text)yield mark_safe(tmp)else:if pk in current_pk_or_list: # url中传来的 get参数tmp = '<a class="active" href="?%s">%s</a>' % (url, text) # 设置选择标签颜色else:tmp = '<a href="?%s">%s</a>' % (url,text)yield mark_safe(tmp)View Code
一、CURD组件开发
1、CRM程序入口 apps.App02Config,执行app02下的app.py文件中App02Config类的ready方法;
CRM要从Django的setings.py配置文件的apps.App02Config类说起,apps.App02Config类在每个APP的apps.py文件中,该类的ready()方法会在ROOT_URLCONF = 'CRM.urls'配置未执行之前执行,实现帮我们把表和自定义配置类注册;
from django.apps import AppConfigclass App01Config(AppConfig):name = 'app01'def ready(self):passView Code
2、App02Config类中的ready方法,执行 autodiscover_modules('zhanggen'),所以去Django程序的所有app下寻找zhanggen.py并执行;
autodiscover_modules()模块,在Django程序启动自动发现并执行一个py文件 (启动文件)
autodiscover_modules(xx.py)模块自动去所有的APP下,寻找xx.py文件并且执行
from django.apps import AppConfigclass App02Config(AppConfig):name = 'app02'def ready(self):# 1.导入autodiscover_modules 程序启动执行autodiscover_modules(参数)# 谁导入autodiscover_modules(py文件)并设置了,就会在程序启动前执行那个pywenjfrom django.utils.module_loading import autodiscover_modulesautodiscover_modules('zhanggen')View Code
3、找到后zhanggen.py from app02.service import v1
先写好注册功能,等待调用。。(注意site是用文件实现的单例模式)
from django.shortcuts import HttpResponse,render from django.conf.urls import url from types import FunctionType from django.urls import reverse from django.utils.safestring import mark_safeclass ChangeList(object): #封装 table 数据传递给前端def __init__(self,data_list,list_display,model_config_obj):self.data_list = data_listself.list_display = list_displayself.model_config_obj = model_config_objdef add_html(self): #封装add 按钮app_model = self.model_config_obj.model_class._meta.app_label, self.model_config_obj.model_class._meta.model_nameadd_url = reverse("zg:%s_%s_add" % app_model)add_html = mark_safe('<a class="btn btn-primary" href="%s">添加</a>' % (add_url,))return add_htmlclass Modelzg(object): # 注册的时候没有使用配置对象,默认使用的配置对象''''用户基础配置类'''def __init__(self, model_class, site): #model_class=model.userinfo , site=Zgsite对象self.model_class = model_classself.site = sitelist_display = []show_add_btn = Truedef get_show_add_btn(self):return self.show_add_btndef changelist_view(self, request, *args, **kwargs):#由于组册类调用本类的时候传入了表名 self._registry[model]=model_zg(model,self)#表名 model_class=models.UserInfo ,所有就可以根据 组册表名获取数据了data_list=self.model_class.objects.all()self.request = request# def headers():# if not self.list_display:#如果用户没有自定义配置就返回表名# yield self.model_class._meta.model_name# else:# for v in self.list_display:# # if isinstance(v,FunctionType):# # yield v(True)# # else:# # #获取中文 列名称# # # models.UserInfo._meta.get_field(email/name).verbose_name# # verbose_name= self.model_class._meta.get_field(v).verbose_name# # yield verbose_name# #三元运算# yield v(self, is_header=True) if isinstance(v, FunctionType) else self.model_class._meta.get_field(# v).verbose_name# def body():# for row in data_list:# #row 是数据库中的一行数据,对象# row_data=[]# for name in self.list_display:# #list_display = ['id', 'name', 'email', xxxxx]# if isinstance(name,FunctionType):# row_data.append(name(self,row))# else:# row_data.append(getattr(row,name))# yield row_data#三元表达式 列表生成式# if not self.list_display:# yield [str(row), ]# else:# yield [name(self,obj=row) if isinstance(name, FunctionType) else getattr(row, name) for name in# self.list_display]# # context = {'data_list': data_list,# 'list_display': self.list_display,# 'headers':headers(),# 'body':body(),# }cl = ChangeList(data_list,self.list_display,self) #传到simp_tagcontext = {'cl': cl}return render(request,'nb/changelist.html', context)def add_view(self, request, *args, **kwargs):return HttpResponse('添加页面')def delete_view(self, request, *args, **kwargs):return HttpResponse('删除页面')def change_view(self, request, *args, **kwargs):return HttpResponse('修改页面')def get_urls(self):app_model_name = self.model_class._meta.app_label, self.model_class._meta.model_name #(app01,表名)'''生成url:/zg/app01/userinfo/ 显示 /zg/app01/userinfo/add 添加/zg/app01/userinfo/ 删除/zg/app01/userinfo/ 编辑 '''patterns = [url(r'^$', self.changelist_view,name="%s_%s_changelist" %app_model_name),url(r'^add/$', self.add_view,name="%s_%s_add" %app_model_name),url(r'^(.+)/delete/$', self.delete_view,name="%s_%s_delete" %app_model_name),url(r'^(.+)/change/$', self.change_view,name="%s_%s_change" %app_model_name),]patterns+=self.extra_urls()return patternsdef extra_urls(self):''''自定义扩展url 预留的钩子函数'''return []@propertydef urls(self):return self.get_urls(),None,Noneclass Zgsite(object):def __init__(self):self._registry={}self.name='zg'self.namespace='zg'def register(self,model,model_zg=None):if not model_zg: #如果在register()注册的时候,没有传(配置类)model_zg=Modelzg #就使用Modelzg自己定义的类self._registry[model]=model_zg(model,self) #注意 model:表名 model_zg(model表名,self =Zgsite对象 ):是配置对象def login(self,request):return HttpResponse('登录页面')def logout(self,request):return HttpResponse('注销页面')def ger_urls(self):patterns=[]patterns+=[url(r'^login/',self.login),url(r'^logout/', self.logout),]for table_name,config_obj in self._registry.items():'''table_name._meta.app_label 注册表名所在的app名称 例: app01table_name._meta.model_name 注册表名所在的 表名小写 例:userinfo / usergroup'''patterns += [ #'/zg/app01/userinfo/'# url(r'^app01/userinfo/',config_obj.urls)# url(r'^app01/usergroup/',config_obj.urls继续构造([],'xx','xxx'))# config_obj=Modelzg() 默认对象#config_obj.urls 调用默认对象的 urls,继续构造([],'xx','xxx'))url(r'^%s/%s/'%(table_name._meta.app_label,table_name._meta.model_name,),(config_obj.urls))]return patterns@propertydef urls(self):return self.ger_urls(),self.name,self.namespace #因为路由系统中的include本质就是返回一个元祖 site=Zgsite()View Code
设计知识点:
文件实现单例模式:
文件导入之后该文件中定义的类被实例化成一个对象,1次执行之后,以后再调用这个文件永远得到同一个对象,相当于永远执行1个对象;
4、zhanggen.py实例化了v1中Zgsite类,得到单例对象site,site单例对象 2次(2张表) 调用了单例象的register()方法,
v1.site.register(models.UserInfo) v1.site.register(models.UserGroup)#1、导入v1就得到一个单例对象 site #2、执行site对象的regist()进行注册View Code
5、site单例对象的register()方法把site对象的self._registry属性,填充成
{
models.UserInfo:UserInfoConfig(models.UserInfo,self,) #sel也就是,site对象由于是单例模式userindo的site对象=slef=UserGroup的site对象
models.UserGroup:UserGroupConfig(models.UserGroup,self,)
}
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
{
models.UserInfo,UserInfo配置对象(models.UserInfo,site对象), #配置对象是相互无关系的
models.UserGroup,UserGrou配置对(models.UserInfo,对象)
}
class Zgsite(object):def __init__(self):self._registry={}self.name='zg'self.namespace='zg'def register(self,model,model_zg=None):if not model_zg: #如果在register()注册的时候,没有传(配置类)model_zg=Modelzg #就使用Modelzg自己定义的类self._registry[model]=model_zg(model,self) #注意 model:表名 model_zg(model表名,self =Zgsite对象 ):是配置对象View Code
6、Django程序的路由系统开始执行,同样调用了单例对象 v1.py中的单例对象 site,并执行了site单例对象的urls方法;
url(r'^zg/',v1.site.urls),
7、site单例对象的urls方法,调用了site单例对象的ger_urls方法;
def ger_urls(self):patterns=[]patterns+=[ #基本urlurl(r'^login/',self.login), #/app01/login/url(r'^logout/', self.logout),#/app01/logout/ ]for table_name,config_obj in self._registry.items():# print(config_obj.model_class)'''table_name._meta.app_label 注册表名所在的app名称 例: app01table_name._meta.model_name 注册表名所在的 表名小写 例:userinfo / usergroup'''patterns += [ #'/zg/app01/userinfo/'# url(r'^app01/userinfo/',config_obj.urls)# url(r'^app01/usergroup/',config_obj.urls继续构造([],'xx','xxx'))# config_obj=Modelzg() 默认对象#config_obj.urls 调用默认对象的 urls,继续构造([],'xx','xxx'))url(r'^%s/%s/'%(table_name._meta.app_label,table_name._meta.model_name,),(config_obj.urls)) #site类注意转接给你了基础配置类 ]return patterns@propertydef urls(self):return self.ger_urls(),self.name,self.namespace #因为路由系统中的include本质就是返回一个元祖View Code
8、ger_urls方法,for循环了单例对象的self._registry属性,为每张表生成基础url映射关系;
^zg/ ^login/
^zg/ ^logout/
^zg/ ^app01/userinfo/
^zg/ ^app01/usergroup/
9、单例对象site的ger_urls方法,并每个调用配置对象中的get_urls方法,为所有注册的表生成增删改查路由映射;
def get_urls(self):app_model_name = self.model_class._meta.app_label, self.model_class._meta.model_name #(app01,表名)'''生成url:/zg/app01/userinfo/ 显示 /zg/app01/userinfo/add 添加/zg/app01/userinfo/ 删除/zg/app01/userinfo/ 编辑 '''patterns = [#每个配置对象执行自己的changelist_view,所有他们执行视图虽然都叫changelist_view()但是不一样url(r'^$', self.changelist_view,name="%s_%s_changelist" %app_model_name),url(r'^add/$', self.add_view,name="%s_%s_add" %app_model_name),url(r'^(.+)/delete/$', self.delete_view,name="%s_%s_delete" %app_model_name),url(r'^(.+)/change/$', self.change_view,name="%s_%s_change" %app_model_name),]patterns+=self.extra_urls()return patternsView Code
涉及知识点:
1、Django路由系统 includ()路由分发的本质
本质就是就是return一个元祖,([],app名称,namespace名称空间)或者(py文件,app名称,namespace名称空间)切支持一直往下嵌套;
urlpatterns = [# url(r'^admin/', admin.site.urls),url(r'^zg/',v1.site.urls),url(r'^app666/', ([url(r'^login/', ( [url(r'^login1/',([url(r'^login2/',index)],'xxx','xxx')) ],'xx','xx')),# url(r'^logout/', self.logout),# url(r'^app01/userinfo/', self.logout),# url(r'^app01/usergroup/', self.logout),],'nb','nb')),]View Code
2、namespace 反向生成url
2.反向生成url视图def index(request):urls=reverse('tex',args=(8,))print(urls)return redirect(urls)路由系统 url(r'^test/(\d+)/',test,name='tex'),视图 def index(request):urls=reverse('tex',kwargs={'a1':9})print(urls)return redirect(urls)路由系统url(r'^test/(?P<a1>\d+)/',test,name='tex'),namespace:区分urlpatterns中同名的path如果url()中包含namespace,在 reverse(namespace:别名)时一点要加上namespdef index(request):urls=reverse('xx:tex')print(urls)return redirect(urls)url(r'^app01/',([url(r'^index/',index,name='inde'),url(r'^test/',test,name='tex'),],'xx','xx')),View Code
3、model.py中类的_meta方法:
app_name=models.UserInfo._meta.app_label #获取model中类(表),所在的apptable_name=models.UserInfo._meta.model_name #获取model中类(表),表名filed_verbose=models.UserInfo._meta.get_field('email').verbose_name #获取列的verbosename 就是描述信息View Code
4、扩展、和重写url
1、预留 url扩展和重写zhanggen.py #扩展 urldef extra_urls(self):patterns = [url(r'test/$',self.test),]return patterns#重写urldef get_urls(self):patterns = [url(r'test/$', self.test),]return patternsv1.pydef get_urls(self):patterns = [url(r'^$', self.changelist_view),url(r'^add/$', self.add_view),url(r'^(.+)/delete/$', self.delete_view),url(r'^(.+)/change/$', self.change_view),]patterns+=self.extra_urls()return patternsdef extra_urls(self):''''自定义扩展url 预留的钩子函数'''return []View Code
CRM基础框架已经搭建完成,每个请求过来都会生成url 视图映射关系,接下来应该设计 视图功能了;(day88内容)
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1、由每张表的url路由关系,的视图函数设置的都是配置对象的 changelist_view方法;如何区分?别忘了
{
models.UserInfo,UserInfo配置对象(models.UserInfo,site对象), #配置对象是相互无关系的
models.UserGroup,UserGrou配置对(models.UserInfo,对象)
}
显示中文标题
通过获取字段的verbose_name,显示中文标题
def changelist_view(self, request, *args, **kwargs):# 由于在注册的时候,实例化配置对象中传入了 model_class,site,所有可以通过self.model_class区分不同的表data_list=self.model_class.objects.all()def header():if not self.list_display:yield self.model_class._meta.model_nameelse:for i in self.list_display:yield i(self,is_header=True) if isinstance(i,FunctionType) else self.model_class._meta.get_field(i).verbose_namecontext={'headers':header(),'data_list':data_list}return render(request,'test.html',context)View Code
涉及知识点:
三元表达式
i(self,is_header=True) if isinstance(i,FunctionType) else self.model_class._meta.get_field(i).verbose_nameView Code
yield生成器: 在后端制作生成器,在前端for循环生成器,提高性能;
yield [i(self, is_header=True, row=obj) if isinstance(i, FunctionType) else getattr(obj, i) for i inself.list_display]View Code
yileld应用场景:
应用1:xrang(100),通过yield做生成器,惰性生成,不要在内存中全部创建;
应用2:在Django如果后台的数据需要做一些处理之后,再在模板中循环显示出来,就可以在后端使用yield生成;省去了后端的循环;
显示内容
涉及知识点:
列表生成式
def body():for obj in data_list:yield [i(self, is_header=True, row=obj) if isinstance(i, FunctionType) else getattr(obj, i) for i inself.list_display]View Code
在配置类自定义列标题 和自定义列内容
def xxx(self,is_header=False,row=None):if is_header==True: #如果调用该函数的时候传入的参数是is_header==True,函数执行就返回标题return '列名称'else:return '自定义列'#如果调用该函数的时候传入的参数是is_header==Fase,函数执行就返回内容View Code
2、基础配置对象 changelist_view方法,根据每张表的list_display = []配置,把生成thead 和tbody内容,封装到ChangeList类中cl对象中返回给nb/changelist.html
class ChangeList(object): #由于使用inclusion_tag,需要传很多值,就把这个些值封装到类里,一对象的形式递给前端def __init__(self,data_list,list_display,model_config_obj):self.data_list = data_listself.list_display = list_displayself.model_config_obj = model_config_objdef add_html(self): #封装add 按钮app_model = self.model_config_obj.model_class._meta.app_label, self.model_config_obj.model_class._meta.model_nameadd_url = reverse("zg:%s_%s_add" % app_model)add_html = mark_safe('<a class="btn btn-primary" href="%s">添加</a>' % (add_url,))return add_htmlView Code
涉及知识点·:
面向对象封装
def func(a1,a2,a3,a4): #如果一个函数执行需要很多参数,就可以吧这个函数改造成面向对象的模式passclass Foo():def __init__(self,a1,a2,a3,a4): #优势1:一次传入,封装所有值到一个对象;self.a1=a1self.a2 = a2self.a3 = a3self.a4 = a4def a5(self): #优势2:还可以通过方法修改封装的值passView Code
3、nb/changelist.html加载 result_list simp_tag,并把后端传来c1对象传到{% show_result_list cl%} include_tag
4、show_result_list include_tag,把c1对象中的数据,和change_list_table.html进行渲染,后nb/changelist.html显示了后端所有内容
from django.template.library import Library from types import FunctionType register=Library()@register.inclusion_tag('nb/change_list_table.html') #inclusion包含_tag自动去找nb/change_list_table.html #inclusion_tag返回什么值,就会把这些值,渲染到nb/change_list_table.html #其他页面调用show_result_list,就会得到 nb/change_list_table.html模板和show_result_list返回的结果的渲染字符串 def show_result_list(cl):def headers():if not cl.list_display:yield cl.model_config_obj.model_class._meta.model_nameelse:for v in cl.list_display:yield v(cl.model_config_obj, is_header=True) if isinstance(v,FunctionType) else cl.model_config_obj.model_class._meta.get_field(v).verbose_namedef body():for row in cl.data_list:if not cl.list_display:yield [str(row), ]else:#obj=row,在Django admin定义 def xx(self,obj): #obj =当前行的对象,方便我们显示 多列数据组合yield [name(cl.model_config_obj, obj=row) if isinstance(name, FunctionType) else getattr(row, name) forname in cl.list_display]return {'headers': headers(), #'body': body(),}#就是inclusion_tag(模板)类似simple_tagView Code
涉及知识点:
自定义simp_tag
当Django模板语言提供的simp_tag无法满足模板渲染需求时,可以利用python代码自定义自己的simp_tag
include_tag:
@register.inclusion_tag('nb/change_list_table.html')
inclusion包含_tag自动去找nb/change_list_table.html
inclusion_tag返回什么值,就会把这些值,渲染到nb/change_list_table.html
其他页面调用show_result_list,就会得到 nb/change_list_table.html模板和show_result_list返回的结果的渲染字符串
方法和函数
类执行类里的函数需要加self参数,传入一个实例对象。
对象执行类里的函数叫方法,会自动把自己作为参数传递进去,不需要加self参数。
#函数和方法class Foo(object):def __init__(self,name):self.name=namedef show(self):print('show',self.name)obj=Foo('zhangggen') # Foo.show(obj) #类直接调用 类中的函数,需要加self参数from types import FunctionType,MethodTypeprint(isinstance(Foo.show,FunctionType)) #函数print(isinstance(obj.show,FunctionType)) #方法View Code
5、前端根据权限 get_show_add_btn决定是否显示添加按钮,PermissionConfig()类。权限表钩子
由于配置类继承了PermissionConfig也继承了基础配置类,权限类优先继承,所有可以重写def get_show_add_btn(self):方法
class Modelzg(object): # 注册的时候没有使用配置对象,默认使用的配置对象''''用户基础配置类'''def __init__(self, model_class, site): #model_class=model.userinfo , site=Zgsite对象self.model_class = model_classself.site = sitelist_display = []show_add_btn = Truedef get_show_add_btn(self): #默认是True,就是显示add按钮,由于广度优先继承PermissionConfig,PermissionConfig类重写了get_show_add_btnreturn self.show_add_btnView Code
class PermissionConfig(object): #配置类重写 get_show_add_btn,控制 add按钮的显示def get_show_add_btn(self):print(self.request)return FalseView Code
注释:
由于配置对象同时继承了PermissionConfig权限配置类,v1.Modelzg默认配置类,配置对象在默认配置类写了self.request = request,而配置对象又被封装到了Cl对象里,所有前端cl.model_config_obj.get_show_add_btn 可以判断是否显示添加权限;
6、list_display = [ ]配置了字段,就显示 checkbox选择和编辑选项,预留权限钩子
list_display = []show_add_btn = Truedef zhinan(self, obj=None, is_header=False):if is_header:return '选择'else:tpl = "<input type='checkbox' value='%s' />" % (obj.pk,)return mark_safe(tpl)def option(self, obj=None, is_header=False):if is_header:return '选项'else:# edit_url = reverse('zg:app01_userinfo_change',args=(obj.pk,))edit_url = reverse('zg:%s_%s_change' % (self.model_class._meta.app_label, self.model_class._meta.model_name,),args=(obj.pk,))tpl = "<a href='%s'>编辑</a>|<a>删除</a>" % (edit_url,)return mark_safe(tpl)def get_list_play(self):res=[] #不能直接操作self.listplay 因为刷新又会从新添加一次if self.list_display: #如果用户设置显示的列 和数据 res.extend(self.list_display)res.insert(0,Modelzg.zhinan)res.append(Modelzg.option)return resView Code
预留权限钩子
def get_list_play(self):return super().get_list_play() # UserInfoConfig对象,先来PermissionConfig,再去v1.Modelzg类找get_list_play方法View Code
7、用户自定制 批量删除和初始化actions
定义一个actions = []配置项,通过基础配置类的 get_actions(self) 去获取
actions = []def get_actions(self):res=[]res.extend(self.actions)res.append(Modelzg.multi_del)res.append(Modelzg.init_action)return resView Code
把在基础配置类里定义批量删除 和初始化的函数
def multi_del(self):id_list=self.request.POST.getlist('pk')# self.model_class.objects.fifter(pk__in=id_list).delete() multi_del.short_desc='批量删除' #给函数定义一个属性def init_action(self):passinit_action.short_desc = '初始化' # 给函数定义一个属性View Code
把actions = []里定义函数名封装到 cl对象传给前端
def xxxff(model_config_obj):for i in model_config_obj.get_actions():yield (i.__name__,i.short_desc)self.actions = xxxff(model_config_obj)View Code
前端把函数名作为option的value 函数的short_desc属性作为内容显示
{% for action in cl.actions %}<option value={{ action.0}}>{{ action.1}}</option>{% endfor %}View Code
最后把选择的checkbox和选择的函数通过from表单提交到后台基础配置类 chang_list_viwe方法
self.request = requestif request.method=='POST':func = request.POST.get('action')print(func)method = getattr(self,func,None)# print(isinstance(action_func,FunctionType))# 注意从对象中获取的是方法if method:method() # 执行 actions中的函数data_list = self.model_class.objects.all()View Code
后端通过反射在后端执获取函数并执行
def multi_del(self):id_list=self.request.POST.getlist('pk')# self.model_class.objects.fifter(pk__in=id_list).delete()View Code
8、分页保留原来的搜素条件
当我们在搜素框输入了搜素条件,点击下一页应当保存搜素条件!
例如:当前页:http://127.0.0.1:8000/zg/app01/userinfo/?p=1&id=2&page=2/
点击下一页如和保存p=1&id=2参数
request_get._mutable=True #设置可编辑参数 page=Pagination(#自定制分页器需要传入的参数#1、当前访问页码current_page=model_config_obj.request.GET.get('page'),#2、 数据库总条目数total_item_count=data_list.count(),#base_url 信息base_url=model_config_obj.request.path_info,request_params=request_get)#page对象产出 开始也 和结束页self.data_list=data_list[page.start:page.end]#分页HTML代码self.page_html=page.page_html()View Code
修改分页生成的 页码
""" 使用方法:from utils.page import Pagination def users(request):current_page = int(request.GET.get('page',1))total_item_count = models.UserInfo.objects.all().count()page_obj = Pagination(current_page,total_item_count,'/users.html')user_list = models.UserInfo.objects.all()[page_obj.start:page_obj.end]return render(request,'users.html',{'user_list':user_list,'page_html':page_obj.page_html()})"""from django.utils.safestring import mark_safeclass Pagination(object):def __init__(self,current_page,total_item_count,base_url=None,per_page_count=10,show_pager_count=11,request_params=None):""":param current_page: 当前页:param total_item_count: 数据库数据总条数:param base_url: 分页前缀URL:param per_page_count: 每页显示数据条数:param show_pager_count: 对多显示的页码"""try:current_page = int(current_page)except Exception as e:current_page = 1self.current_page = current_pageself.total_item_count = total_item_countself.base_url = base_urlself.per_page_count = per_page_countself.show_pager_count = show_pager_countself.request_params=request_paramsmax_pager_num, b = divmod(total_item_count, per_page_count)if b:max_pager_num += 1self.max_pager_num = max_pager_num@propertydef start(self):""":return:"""return (self.current_page-1)* self.per_page_count@propertydef end(self):""":return:"""return self.current_page * self.per_page_countdef page_html(self):""":return:"""page_list = []if self.current_page == 1:prev = ' <li><a href="#">上一页</a></li>'else:self.request_params['page']=self.current_page-1prev = ' <li><a href="%s?%s">上一页</a></li>' % (self.base_url,self.request_params.urlencode(),)page_list.append(prev)half_show_pager_count = int(self.show_pager_count / 2)# 数据特别少,15条数据=2页if self.max_pager_num < self.show_pager_count:# 页码小于11pager_start = 1pager_end = self.max_pager_num + 1else:if self.current_page <= half_show_pager_count:pager_start = 1pager_end = self.show_pager_count + 1else:if self.current_page + half_show_pager_count > self.max_pager_num:pager_start = self.max_pager_num - self.show_pager_count + 1pager_end = self.max_pager_num + 1else:pager_start = self.current_page - half_show_pager_countpager_end = self.current_page + half_show_pager_count + 1for i in range(pager_start, pager_end):self.request_params['page']=iif i == self.current_page:tpl = ' <li class="active"><a href="%s?%s">%s</a></li>' % (self.base_url,self.request_params.urlencode(), i,)else:tpl = ' <li><a href="%s?%s">%s</a></li>' % (self.base_url,self.request_params.urlencode(), i,)page_list.append(tpl)if self.current_page == self.max_pager_num:nex = ' <li><a href="#">下一页</a></li>'else:self.request_params['page']=self.request_params+1nex = ' <li><a href="%s?%s">下一页</a></li>' % (self.base_url,self.request_params.urlencode(),)page_list.append(nex)return mark_safe(''.join(page_list))def page_html_js(self):page_list = []if self.current_page == 1:prev = ' <li><a href="#">上一页</a></li>'else:prev = ' <li><a οnclick="$.changePage(%s)">上一页</a></li>' %(self.current_page-1,)page_list.append(prev)half_show_pager_count = int(self.show_pager_count / 2)# 数据特别少,15条数据=2页if self.max_pager_num < self.show_pager_count:# 页码小于11pager_start = 1pager_end = self.max_pager_num + 1else:if self.current_page <= half_show_pager_count:pager_start = 1pager_end = self.show_pager_count + 1else:if self.current_page + half_show_pager_count > self.max_pager_num:pager_start = self.max_pager_num - self.show_pager_count + 1pager_end = self.max_pager_num + 1else:pager_start = self.current_page - half_show_pager_countpager_end = self.current_page + half_show_pager_count + 1for i in range(pager_start, pager_end):if i == self.current_page:tpl = ' <li class="active"><a οnclick="$.changePage(%s)" >%s</a></li>' % (i,i,)else:tpl = ' <li><a οnclick="$.changePage(%s)" >%s</a></li>' % (i, i,)page_list.append(tpl)if self.current_page == self.max_pager_num:nex = ' <li><a href="#">下一页</a></li>'else:nex = ' <li><a οnclick="$.changePage(%s)" >下一页</a></li>' %(self.current_page+1,)page_list.append(nex)return ''.join(page_list)def page_html_test(self):page_list = []if self.current_page == 1:prev = ' <li><a href="#">上一页</a></li>'else:prev = ' <li><a num="%s">上一页</a></li>' %(self.current_page-1,)page_list.append(prev)half_show_pager_count = int(self.show_pager_count / 2)# 数据特别少,15条数据=2页if self.max_pager_num < self.show_pager_count:# 页码小于11pager_start = 1pager_end = self.max_pager_num + 1else:if self.current_page <= half_show_pager_count:pager_start = 1pager_end = self.show_pager_count + 1else:if self.current_page + half_show_pager_count > self.max_pager_num:pager_start = self.max_pager_num - self.show_pager_count + 1pager_end = self.max_pager_num + 1else:pager_start = self.current_page - half_show_pager_countpager_end = self.current_page + half_show_pager_count + 1for i in range(pager_start, pager_end):if i == self.current_page:tpl = ' <li class="active"><a num="%s" >%s</a></li>' % (i,i,)else:tpl = ' <li><a num="%s" >%s</a></li>' % (i, i,)page_list.append(tpl)if self.current_page == self.max_pager_num:nex = ' <li><a href="#">下一页</a></li>'else:nex = ' <li><a num="%s">下一页</a></li>' %(self.current_page+1,)page_list.append(nex)return ''.join(page_list)View Code
设计知识点:
from django.http import QueryDict
当我们向django发送请求时,Django接收request.get()到的数据是query_dict数据类型
query_dict数据类型默认不可以修改
request.GET.get()._mutable=True 设置该参数后即可修改
还原成urlencode类型:request.GET.urlencode()
9、单表添加功能
0、设置list显示页面add按钮的跳转的url ,保存url后面的的get参数
def add_html(self): #封装add 按钮app_model = self.model_config_obj.model_class._meta.app_label, self.model_config_obj.model_class._meta.model_namequery_dict=QueryDict(mutable=True)query_dict['xxoo']=self.model_config_obj.request.GET.urlencode() #xxoo=p%3D1View Code
1、在add_view中定义ModelForm,让配置对象调用该方法时,可以显示html页面
self.request=requestchang_list_url_params=request.GET.get('xxoo')from django.forms import ModelFormclass AddModelForm(ModelForm):class Meta:model=self.model_class #哪个配置调用自己的add_view方法来,就创建这个表的ModelFormfields='__all__' #在前端显示的列if request.method=='GET':form=AddModelForm()context={'form':form,}return render(request,'nb/add.html',context)View Code
2、用户提交数据form验证通过,携带list页面的参数,跳转会list页面
elif request.method=='POST':form = AddModelForm(data=request.POST)if form.is_valid():form.save()base_url=self.revesrs_chang_list_url()+chang_list_url_paramsprint(base_url)return redirect(base_url)View Code
def revesrs_chang_list_url(self): #为add_viwe生成 跳转会list页面的标签list_url=reverse("%s:%s_%s_changelist" % (self.site.namespace,self.app_lable,self.model_name,) )return list_urlView Code
3、自定制ModelForm
通过方法获取默认配置里面的ModelForm如果为空就是使用默认配置,否则使用自定义配置ModelForm
model_form=Nonedef get_model_form_class(self):result=self.model_formif not result:class Defalut_Model_Form(ModelForm):class Meta:model = self.model_class # 哪个配置调用自己的add_view方法来,就创建这个表的ModelFormfields = '__all__' # 在前端显示的列 result = Defalut_Model_Formreturn resultView Code
class UserInforModelFrom(ModelForm):www=fields.CharField(widget=widgets.Textarea)class Meta:model=models.UserInfofields = '__all__'class UserInfoConfig(PermissionConfig,v1.Modelzg): #多继承 既继承v1.Modelzg又继承PermissionConfig 优先def xxx(self, obj=None, is_header=False):if is_header:return '列名称'return obj.name + obj.email# list_display = [zhinan, 'id', 'name', 'nickname', 'email', xxx, option]model_form=UserInforModelFromView Code
涉及知识点:
1、request.GET.urlencode()
获取get请求后面携带的参数
query_dict['xxoo']=self.model_config_obj.request.GET.urlencode() #xxoo=p%3D1View Code
2、ModelForm的使用
if request.method=='GET':form=AddModelForm()context={'form':form,}return render(request,'nb/add.html',context)elif request.method=='POST':form = AddModelForm(data=request.POST)if form.is_valid():form.save()base_url=self.revesrs_chang_list_url()+chang_list_url_paramsprint(base_url)return redirect(base_url)context = {'form': form,}return render(request,'nb/add.html', context)View Code
在模板中使用
<form action="" method="post">{% csrf_token %} {# {{ form.as_p }}#}{% for item in form %}<p>{{ item.label }}:{{ item }}{{ item.errors.0 }} </p>{% endfor %}<input type="submit" value="提交"> </form>View Code
10、单表编辑/删除功能
没时间实现
11、通过popup实现在userinfo表联动添加外键表usergroup的信息
userinfo添加页面显示
<!DOCTYPE html> {% load result_form %} <html lang="en"> <head><meta charset="UTF-8"><title>Title</title><link rel="stylesheet" href="/static/plugins/bootstrap/css/bootstrap.css"><style>.form-horizontal input[type='text'],input[type='checkbox'],input[type='radio'],input[type='email'], select, textarea {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;}</style> </head> <body> <h1>添加页面</h1> {% show_form form %} <h1></h1> </body> </html>View Code
执行inclusion_tag,完成对外键表联动添
from django.template.library import Library from types import FunctionType from django.forms.models import ModelMultipleChoiceField from django.urls import reverse from django.forms.models import ModelChoiceField from django.db.models.query import QuerySet from app02.service.v1 import siteregister=Library()def xxxx(model_form_obj):for item in model_form_obj :tpl={'has_popup':False,'item':item,'popup_url':None}# 判断userinfo表的列中是否含外键字段,并且外键的表也得已经注册if isinstance(item.field,ModelChoiceField) and item.field.queryset.model in site._registry:tpl['has_popup']=Trueflied_class=item.field.queryset.model #字段外键对应的表名app_label=flied_class._meta.app_label #外键表所在的appmodel_name=flied_class._meta.model_name#外键表的表名url= reverse('{0}:{1}_{2}_add'.format(site.namespace,app_label,model_name ))tpl['popup_url']=url# print(item.field) #字段类型yield tpl@register.inclusion_tag('nb/change_form.html') def show_form(model_form_obj):return {'form':xxxx(model_form_obj)}#inclusion_tag returm什么数据,就在changelist.html里面渲染什么数据 #最后谁调用 show_form simpletag就包含了 changelist.html渲染完成页面View Code
inclusion_tag使用的页面
<form class="form-horizontal" method="POST" novalidate>{% csrf_token %}{% for col in form %}<div class="form-group col-sm-6"><label class="col-sm-3 control-label">{{ col.item.label }}</label><div class="col-sm-9" style="position: relative">{{ col.item }}{{ col.item.errors.0 }}{% if col.has_popup %}<a onclick="popup('{{ col.popup_url }}')" >添加</a>{% endif %}</div></div>{% endfor %}<div class="form-group"><div class="col-sm-offset-10 col-sm-2"><input type="submit" class="btn btn-primary" value="确认添加"/></div></div> </form>View Code
elif request.method=='POST':form = self.get_model_form_class()(data=request.POST)if form.is_valid():obj=form.save()base_url=self.revesrs_chang_list_url()url="%s?%s" %(base_url,chang_list_url_params)tagId = request.GET.get('_popup', None)if tagId:name=str(obj) #获取 name/titleval=obj.pkreturn render(request,'nb/popuprespose.html',{ 'name':name,'tagId':tagId,'val':val })return redirect(url)View Code
涉及知识点:
name=str(obj) 执行在model.py中定义的 __str__方法
查看form对象中包含列的相关属性
def xxxx(model_form_obj):for item in model_form_obj :tpl={'has_popup':False,'item':item,'popup_url':None}# 判断userinfo表的列中是否含外键字段,并且外键的表也得已经注册if isinstance(item.field,ModelChoiceField) and item.field.queryset.model in site._registry:tpl['has_popup']=Trueflied_class=item.field.queryset.model #字段外键对应的表名app_label=flied_class._meta.app_label #外键表所在的appmodel_name=flied_class._meta.model_name#外键表的表名url= reverse('{0}:{1}_{2}_add'.format(site.namespace,app_label,model_name ))tpl['popup_url']=url# print(item.field) #字段类型yield tplView Code
js之popup弹窗 (3个HTML页面)
1、p1页面定义1个popup回调函数,p1页面绑定1个popup弹窗时间 到 /p2/
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Title</title> </head> <body> <h1>p1页面</h1> <input type="button" value="按钮" onclick="popfunc();"></body> <script>function popfunc() {window.open('/p2/','别名',"status=1, height:500, width:600, toolbar=0, resizeable=0"); {# 点击按钮 出现弹窗 到p2 #}}function xxx(name) {alert(name)} </script> </html>View Code
2、/p2/下的视图添加数据库操作,把增加的row对象模板渲染,re1个页面popuprespose.html
def p2(request):if request.method=='GET':return render(request,'p2.html')elif request.method=='POST':from app01 import modelscity=request.POST.get('city')obj= models.UserGroup.objects.create(title=city)return render(request,'popuprespose.html',{'obj':obj})View Code
3、popuprespose.html页面 自执行函数执行popup发起页面(p1)的回调函数
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Title</title> </head> <body> <h1>p1页面</h1> <input type="button" value="按钮" onclick="popfunc();"></body> <script>function popfunc() {window.open('/p2/','别名',"status=1, height:500, width:600, toolbar=0, resizeable=0"); {# 点击按钮 出现弹窗 到p2 #}}//回调函数function xxx(name) {alert(name)}View Code
12、组合搜素逻辑
组合搜素这个功能,其实很简单却浪费我大量闹细胞;
1、把所有在显示页面需要的数据封装进ChangeList类里,实例化cl对象传到模板语言通过simple-tag循环、调用生成标签;这是前提;
显示页面模板中的2层for循环是切入点:
div class="container"><h1>数据列表</h1>{#显示组合搜素开始#}<div class="row">{% if cl.list_filter %}<!-- 遍历chang_list的gen_list_filter 方法,也就是Userinfo表中的 query_aet) -->{% for row_items in cl.gen_list_filter %}<div class="filter-item"><!-- 遍历自自定义的,具有__iter__方法的个可迭代对象 -->{% for item in row_items %}{{ item }}{% endfor %}</div>{% endfor %}<!-- 遍历自自定义的,具有__iter__方法的个可迭代对象 结束 -->{% endif %}<!-- 遍历chang_list中的gen_list_filter 方法结束 --></div>{#显示组合搜素结束#}View Code
1层for循环: yelid row_items = RowItems(option, data_list, params)
其中ChangeLis中定义的gen_list_filter方法是个生成器,获取list_filter = [‘group’,'roles ']配置项对应表的数据,for循环gen_list_filter得到一个RowItems对象(也是个生成器);
def gen_list_filter(self):model_class = self.model_config_obj.model_class # userinfo 类params = self.model_config_obj.request.GET # 从配置对象中获取request参数,传入到 RowItems类for option in self.list_filter: # list_filter=['group对象','name对象','roles对象']fild_obj = model_class._meta.get_field(option.name) # 获取到列对象from django.db.models.fields.related import RelatedFieldif isinstance(fild_obj, RelatedField): # 判断是否为外键字段field_related_class = fild_obj.rel.to # 获取到列对应的外键表data_list = field_related_class.objects.all()row_items = RowItems(option, data_list, params) # 实例化一个RowItems对象else: # 判断如果是普通字段直接取值data_list = model_class.objects.all()row_items = RowItems(option, data_list, params) # 实例化一个RowItems对象yield row_itemsView Code
2层for循环: yield mark_safe( tmp = '<a href="?%s">%s</a>' % (url, text))
遍历data_list,query_set[obj,objobj ],所以data_list中有几个对象就行数据,就得到是1个带有herf链接url的A标签;
class RowItems():def __init__(self, option, data_list, params):self.option = optionself.data_list = data_listself.param = copy.deepcopy(params) # query_dict对象print(self.param) # <QueryDict: {'group': ['1'], 'roles': ['2']}>self.param._mutable = Truedef __iter__(self):current_pk = self.param.get(self.option.name) # str类型 self.param.pop(self.option.name)all_url = self.param.urlencode()tmp = '<a href="?%s">全部</a>' % (all_url)yield mark_safe(tmp)for obj in self.data_list:pk = str(obj.pk)text = str(obj)self.param[self.option.name] = pkurl = self.param.urlencode()if current_pk == pk:tmp = '<a class="active" href="?%s">%s</a>' % (url, text) # 设置选择标签颜色else:tmp = '<a href="?%s">%s</a>' % (url, text)yield mark_safe(tmp)View Code
RowItems()的__iter__ 方法,每次根据request.get携带的参数,yeild a标签
1、先根据get请求,request.getlist(self.optionname) 获取current_pk_or_list参数
列表 [5,6]
2、首先self.param.pop(self.option.name)清空group参数自己,留下role参数,生成全部A标签 ;
3、循环遍历data_list数据,根据query_set[obj,obj,obj]中有几个obj,生成几个A标签;
3.1先得到每个obj的id 和 str(obj)中文显示内容
3.2判断是否是多选;
3.3 判断当前for 循环的obj对象的pk 是否在current_pk_or_list参数列表 [5,6]?
3.4 如果不在就把当前 obj的的id 添加到current_pk_or_list [1,5,6]
3.5 重置<QueryDict: {}>,为<QueryDict: {'group': ['1', '5', '6']}>
3.6 urlendcodeQueryDict,每次赋值A标签的herf属性
组合搜素 A标签生成href url思想:
1、用户首次访问:http://127.0.0.1:8000/zg/app01/userinfo/
reques.getlist(self.option.name) #得到 current_pk_or_list为空 []
2、RowItems类的__iter__方法遍历 data_list,query_set [ obj ,obj ]
#生成全部右侧 obj data_list=[obj,obj,obj ]for obj in self.data_list:#每个obj 生成 每个 A标签pk = str(obj.pk) #1text = str(obj) #阿斯蒂芬if self.option.is_muti: #如果配置了多选if pk not in current_pk_or_list: #如果obj.pk不在【1.2】tmp=[]tmp.extend(current_pk_or_list)tmp.append(pk)self.param.setlist(self.option.name,tmp) #注意在原来不变的基础上多加1个?group=2&group=3View Code
如果obj.pk不在current_pk_or_list [ ] 中,就把obj.pk添加到tmp,第一次循环进来 tmp=[1 ]
tmp=[]tmp.extend(current_pk_or_list)tmp.append(pk)
3、修改 query_dict,<QueryDict: { option.name : [1] }
self.param.setlist(self.option.name,tmp)
4、url endconde编码 ?group=1
url = self.param.urlencode()
5、遍历循环结束,根据obj个数生成A标签的 url
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1、首次生成url之后,在页面点击搜素项,http://127.0.0.1:8000/zg/app01/userinfo/?group=1
reques.getlist(self.option.name) #得到 current_pk_or_list为 [1]
2、RowItems类的__iter__方法遍历 data_list,query_set [ obj ,obj, ]
如果obj.pk不在current_pk_or_list [ ] 中,就把obj.pk添加到tmp,第一次循环进来 tmp=[1,2 ] / tmp=[1,3 ] / tmp=[1,4]
3、url endconde编码,生成A标签的url
补充 list_filter 配置 函数和‘数据库字段’
class UserInfoConfig(sites.AryaConfig):list_display = ['name', ]def func(self,change_list,option):#option,change_list,data_list,param_dict=Nonedata_list=models.UserInfo.objects.filter(id__gt=2)return sites.FilterRow(option,change_list,data_list,self.request.GET)list_filter = [ #文本显示的内容 url参数sites.FilterOption('group', True, lambda x: x.title, lambda x: x.id),sites.FilterOption('name', False, lambda x: x.name, lambda x: x.name),# sites.FilterOption(func, False, lambda x: x.name, lambda x: x.name), #自定制函数]View Code
总结:根据 request.getlist()获取的 [ ], 判断当前循环到的obj.id是否在[],如不不存在 添加到 [ ],修改 query_dict { 'option' : [ ] },urlencode编码为url格式作为A标签的跳转链接;
二、CURD组件使用
经过漫长的开发周期,模仿DjangoAdmin开发出来的CURD组件终于可以使用了,赐名arya;下面是在1个崭新的Django项目中使用arya组件流程;
1、在settings.py组册arya为本程序app
INSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','app01.apps.App01Config','arya.apps.AryaConfig',]View Code
2、任意APP下创建 arya.py
3、在arya.py中组册model_class
from . import models from arya.service.sites import site #导入单例模式 site.register(models.Department,)View Code
4、为已经注册的model_class,生成路由映射关系;
from django.conf.urls import url from django.contrib import admin from app01 import views from arya.service.sites import site urlpatterns = [# url(r'^admin/', admin.site.urls),url(r'^index/',views.index),url(r'^arya/',site.urls), ]View Code
5、修改母版板样式layout
diango做模板渲染得时候寻找模板的顺序是,从最外层templates 到app里面的templates
所以如果想修改网页显示的样式,就可以在最外层templates,定义自己的母版,让其他页面继承;
6、增、删、改、查基本操作
from . import models from arya.service import sites #导入单例模式class DepartmentConfig(sites.AryaConfig): #注册部门表list_display = ['title']sites.site.register(models.Department,DepartmentConfig)class UserinfoConfig(sites.AryaConfig): #注册用户表list_display = ['name','email']sites.site.register(models.UserInfo, UserinfoConfig)class CoursetConfig(sites.AryaConfig): #注册 课程表list_display = ['name']sites.site.register(models.Course, CoursetConfig)class SchoolConfig(sites.AryaConfig): #注册部门表list_display = ['title']sites.site.register(models.School,SchoolConfig)View Code
7、插件扩展
0、脱离插件
1、扩展URL,extra_url方法,重写URL get_urls方法,添加RBAC权限
1.0.添加url
class UserinfoConfig(sites.AryaConfig): #注册用户表list_display = ['name','email']def extra_urls(self):app_model_name = self.model_class._meta.app_label, self.model_class._meta.model_namepatterns = [url(r'^(.+)/detail/',self.detail_view, name="%s_%s_changelist" % app_model_name),]return patternsdef detail_view(self,request,pk):obj=self.model_class.objects.all().filter(pk=pk).first()print(obj.name,obj.id)return HttpResponse('显示详情')View Code
#工单系统class Worker_orderConfig(PermissionConfig,v1.AryaConfig):def row_graph(self, row=None, is_header=None):if is_header:return "工单状态"button_list=["<button class='btn btn-danger btn-sm button' type='button'>未理中</button>","<button class='btn btn-success btn-sm button' type='button'>处理中</button>","<button class='btn disabled btn-sm button' type='button'>已关闭</button>"]return mark_safe(button_list[row.status] )def details(self, row=None, is_header=None):if is_header:return "工单流程"return mark_safe("<a href="">详细</a>" )def fenpei(self, row=None, is_header=None):if is_header:return "分配"return mark_safe("<a href='/allocation/?id=%s'>分配</a>"%(row.id) )list_display = ['alarm_time', 'initiator', 'title', 'agent','desc',row_graph,fenpei,details,]def extra_url(self):app_model_name = self.model_class._meta.app_label, self.model_class._meta.model_nameprint(app_model_name)p= [url(r'^worker_order_out/$', self.detail_view, name="%s_%s_t" % app_model_name),]return pdef detail_view(self, request):return HttpResponse('显示详情')v1.site.register(models.Worker_order,Worker_orderConfig)扩展URL
1.2.arya生成url的规则
/arya开头/app名称/model表名/扩展url的名/: 例如:/arya/cmdb/worker_order/worker_order_out/
1.3.在RBAC中为角色增加该URL的访问权限
在权限管理----》URL权限添加权限标题:测试为标题
2、自定义添加、显示、删除页面,重写基础配置类中的add_view 、 changelist_view 、delete_view
注意每一个方法,都要执行self.request=request
class UserinfoConfig(sites.AryaConfig): #注册用户表list_display = ['name','email']def add_view(self,request):return render(request,'test.html')View Code
三、RBAC权限录入
RBAC基于角色的权限管理系统回顾:详见 Django之权限管理插件
表结构:用户表 Many to many 角色表 Many to many 权限表 foreign key 菜单表
中间件:判断当前访问用户是否有访问权限
登录成功初始化:把当前用户所有权限初始化放到session中
配置文件: 无需登录就能使用的权限,session的key
自动生成菜单:css、JavaScript
1、注册RBAC app
'rbac.apps.RbacConfig',View Code
2、清空rbac插件的migration记录
3、迁移rbac插件的数据
python manage.py makemigrationspython manage.py migrate
4、在rbac创建arya.py注册rbac model_class
from arya.service import sites from . import modelssites.site.register(models.User) sites.site.register(models.Role)class PermissionConfig(sites.AryaConfig):passsites.site.register(models.Permission,PermissionConfig) sites.site.register(models.Menu)View Code
5、创建权限信息arya/rbac/permission/
6、完善CURD插件功能;
1、添加权限自动发现URL;
(1)方式1
定制list_play=[]
重写add_view
lass PermissionConfig(sites.AryaConfig):def dabo(self, obj=None, is_header=False):if is_header:return '其他'return obj.caption+'大波'list_display = ['caption','url','menu',dabo]def add_view(self, request, *args, **kwargs):from pro_crm.urls import urlpatternsall_url_list=get_all_url(urlpatterns,prev='/',is_first=True,)model_form_cls = self.get_model_form_class()popup_id = request.GET.get(self.popup_key)if request.method == 'GET':form = model_form_cls()return render(request, "permission_add_popup.html" if popup_id else "permission_add.html",{'form': form,'url_list':all_url_list})elif request.method == "POST":form = model_form_cls(data=request.POST, files=request.FILES)if form.is_valid():obj = self.save(form, True)if obj:if popup_id:context = {'pk': obj.pk, 'value': str(obj), 'popup_id': popup_id}return render(request, 'arya/popup_response.html',{"popup_response_data": json.dumps(context)})else:return redirect(self.changelist_url_params)return render(request,"permission_add_popup.html" if popup_id else "permission_add.html", {'form': form,'url_list':all_url_list})sites.site.register(models.Permission,PermissionConfig)View Code
(1)方式2
修改ModelForm组件
from arya.service import sites from . import models from django.shortcuts import render,redirect import jsonfrom django.urls.resolvers import RegexURLPattern def get_all_url(patterns,prev,is_first=False,result=[],):if is_first:result.clear()for item in patterns:v=item._regex.strip('^$')if isinstance(item,RegexURLPattern):val=prev+vresult.append((val,val))else:get_all_url(item.urlconf_name,prev+v)return resultsites.site.register(models.User) sites.site.register(models.Role)from django.forms import ModelForm from django.forms import fields from django.forms import widgets#自定义model_formclass PermissionModelForm(ModelForm):#ModelForm 可以结合Model把所有数据库字段在页面上生成,也可以增加额外的字段;url=fields.ChoiceField()class Meta:fields = "__all__"model = models.Permission #注意不是modelsdef __init__(self,*args,**kwargs): #重写父类的 __init__方法,每次实例化实时更新 form中的数据super(PermissionModelForm,self).__init__(*args,**kwargs)from pro_crm.urls import urlpatternsself.fields['url'].choices=get_all_url(urlpatterns,'/', True)class PermissionConfig(sites.AryaConfig):def dabo(self, obj=None, is_header=False):if is_header:return '其他'return obj.caption+'大波'list_display = ['caption','url','menu',dabo]model_form = PermissionModelFormView Code
2、通过装饰器实现每次请求保存request信息
def warpper(self,func): #每次获取 self.requesr=request@functools.wraps(func) # def inner(request,*args,**kwargs):self.request=requestreturn func(request,*args,**kwargs )return innerView Code
patterns = [url(r'^$',self.warpper(self.changelist_view), name="%s_%s_changelist" % app_model_name),url(r'^add/$',self.warpper(self.add_view), name="%s_%s_add" % app_model_name),url(r'^(.+)/delete/$',self.warpper(self.delete_view), name="%s_%s_delete" % app_model_name),url(r'^(.+)/change/$',self.warpper(self.change_view), name="%s_%s_change" % app_model_name),]View Code
设计知识点:
(1)、装饰器:
@warpper
def foo()
pass
foo=warpprt(foo)=return的结果
(2)、@functools.wraps(func)
装饰器函数里面的文档介绍 完全= 原生函数的文档介绍
(3)、ModelForm 增加数据库字段之外的field,ModelForm对象实时更新数据
Django之Form、ModelForm 组件
ModelForm 可以结合Model把所有数据库字段在页面上生成,也可以增加额外的字段;
规则:如果增加的字段和数据里的filed重名则覆盖,不重名则新增;
也可以通过重写__init__ ,每次实例化1个form对象,实时更新数据;
View Code
四、Arya整合RBAC整合CRM
1、创建名为crm的app,并且在setings.py里面注册
INSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','arya.apps.AryaConfig','rbac.apps.RbacConfig','crm.apps.CrmConfig',]View Code
2、使用RBAC,配置RBAC的表;(只能访问自己权限的URL,登录之后获取自己的权限列表放入request.session)
from arya.service import sites from . import models from django.shortcuts import render,redirect import jsonfrom django.urls.resolvers import RegexURLPattern def get_all_url(patterns,prev,is_first=False,result=[],):if is_first:result.clear()for item in patterns:v=item._regex.strip('^$')if isinstance(item,RegexURLPattern):val=prev+vresult.append((val,val))else:get_all_url(item.urlconf_name,prev+v)return resultsites.site.register(models.User) sites.site.register(models.Role)from django.forms import ModelForm from django.forms import fields from django.forms import widgets#-------------------用户表相关配置 class UserConfig(sites.AryaConfig):list_display = ['username','email' ] #--------------------角色相关配置 class RoleConfig(sites.AryaConfig):list_display = ['caption',]#----------------------权限相关配置 #自定义model_form class PermissionModelForm(ModelForm):#ModelForm 可以结合Model把所有数据库字段在页面上生成,也可以增加额外的字段;url=fields.ChoiceField()class Meta:fields = "__all__"model = models.Permission #注意不是modelsdef __init__(self,*args,**kwargs): #重写父类的 __init__方法,每次实例化实时更新 form中的数据super(PermissionModelForm,self).__init__(*args,**kwargs)from pro_crm.urls import urlpatternsself.fields['url'].choices=get_all_url(urlpatterns,'/', True) class PermissionConfig(sites.AryaConfig):def dabo(self, obj=None, is_header=False):if is_header:return '其他'return obj.caption+'大波'list_display = ['caption','url','menu',dabo]model_form = PermissionModelFormsites.site.register(models.User,UserConfig) sites.site.register(models.Permission,PermissionConfig) sites.site.register(models.Role,RoleConfig) sites.site.register(models.Menu)View Code
3、添加crm登录页面
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>CRM系统登录</title> </head> <body> <form action="" method="post">{% csrf_token %}<p>用户名:<input type="text" name="username"></p><p>密码:<input type="submit" name="pwd"></p><input type="submit" value="提交"> </form></body> </html>View Code
4、添加crm首页
设置母版layout
<!--- 加载rbac插件中的simp-tag-->
<!--- 加载rbac插件中的css-->
//加载rbac插件中的js
{% load static %} {% load rbac %} <!--- 加载rbac插件中的simp-tag--> <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>{{ title }}</title><link rel="stylesheet" href="{% static 'arya/plugins/bootstrap/css/bootstrap.css' %}"/><link rel="stylesheet" href="{% static 'arya/css/commons.css' %}"/><sytyle><!--- 加载rbac插件中的css-->{% rbac_css %}</sytyle>{% block css %}{% endblock %} </head> <body> <div class="pg-header"><div class="logo left" style="text-align: center;background-color: #1c5a9c;"><a href="#" style="color: #ffffff;font-size:22px;font-weight: bold;text-decoration: none">CRM系统</a></div><div class="left-menu left"><a class="menu-item" href="#">平台首页</a><a class="menu-item" href="#">资产首页</a></div><div class="right-menu right clearfix"><div class="user-info right"><a href="#" class="avatar"><img class="img-circle" src="{% static 'arya/img/default_avatar.png' %}"></a><div class="more-info"><a href="#" class="more-item">个人信息</a><a href="/logout.html" class="more-item">注销</a></div></div><a class="user-menu right">消息<i class="fa fa-commenting-o" aria-hidden="true"></i><span class="badge bg-success">2</span></a><a class="user-menu right">通知<i class="fa fa-envelope-o" aria-hidden="true"></i><span class="badge bg-success">2</span></a><a class="user-menu right">任务<i class="fa fa-bell-o" aria-hidden="true"></i><span class="badge bg-danger">4</span></a></div></div><div class="pg-body"><div class="menu">{% rbac_menu request %}</div><div class="content">{% block breadcrumb %} {% endblock %}{% block content %} {% endblock %}</div> </div><script src="{% static 'arya/js/jquery-1.12.4.js' %}"></script> <script src="{% static 'arya/plugins/bootstrap/js/bootstrap.js' %}"></script><script>{% rbac_js %} //加载rbac插件中的js </script> {% block js %} {% endblock %} </body> </html>View Code
设置crm首页
{% extends 'arya/layout.html' %}{% block content %}<h1>欢迎登录</h1>{% endblock %}View Code
5、index视图执行RBAC插件逻辑
1)登录成功之后初始化权限信息
initial_permission(request,obj) #初始化用户权限信息
2)页面显示时生成 菜单HTML
3)setings.py配置文件 引入rbac中间件、设置rbac使用的key、simple_tag生成多级菜单
'rbac.middleware.rbac.RbacMiddleware', #引入rbac中间件View Code
# ############################## RBAC权限相关配置开始 ############################## # session中保存权限信息的Key RBAC_PERMISSION_URL_SESSION_KEY = "rbac_permission_url_session_key"# Session中保存菜单和权限信息的Key RBAC_MENU_PERMISSION_SESSION_KEY = "rbac_menu_permission_session_key" RBAC_MENU_KEY = "rbac_menu_key" RBAC_MENU_PERMISSION_KEY = "rbac_menu_permission_key"# 匹配URL时指定规则 RBAC_MATCH_PARTTERN = "^{0}$"# 无需权限控制的URL RBAC_NO_AUTH_URL = ['/login/',"/index/", ]# 无权访问时,页面提示信息 RBAC_PERMISSION_MSG = "无权限访问"# 菜单主题 RBAC_THEME = "default" # ############################## RBAC权限相关配置结束 ##############################View Code
6、在权限中间件之前,通过UserAuthMiddleware判断用户是否已经登录,登录之后才有必要设置权限,否则直接返回登录页面;
权限中间件没有必要,每次每个用户都要执行一次,所有在权限中间件之前新增一个中间件,先判断用户是否已经登录,没有登录直截了当让他先登录;
if obj:request.session['user_info'] = {'nid': obj.id} #用户登录后 设置登录标记initial_permission(request,obj) #初始化用户权限信息return redirect('/index/') #跳转到首页
class UserAuthMiddleware(MiddlewareMixin):def process_request(self,request):# 如果是/login/可以继续执行if request.path_info == '/login/':return None# 设置权限之前,判断用户是否已经登录?登录之后才判断是否有权限user_info = request.session.get('user_info')if not user_info:return redirect('/login/')View Code
7、CRM的用户表和RBAC的用户表做One to One关联
from rbac.models import User class UserInfo(models.Model):"""员工表"""user = models.OneToOneField(verbose_name='用户账号',to=User)name=models.CharField(verbose_name='员工姓名',max_length=16)phone=models.CharField(verbose_name='员工电话',max_length=16)depart = models.ForeignKey(verbose_name='部门', to="Department")View Code
8、CRM表结构 配置与页面显示
自定义函数:list_display显示多对多关系字段
aryaCURD插件: list_display 定义函数,自定义函数返回什么list页面显示什么,外键关系连表查询
from . import models from arya.service import sites from django.utils.safestring import mark_safe class DepartmentConfig(sites.AryaConfig):list_display = ['title']sites.site.register(models.Department,DepartmentConfig)class UserInfoConfig(sites.AryaConfig):list_display = ['name','phone']sites.site.register(models.UserInfo,UserInfoConfig)class CourseConfig(sites.AryaConfig):list_display = ['name']sites.site.register(models.Course,CourseConfig)class SchoolConfig(sites.AryaConfig):list_display = ['title'] sites.site.register(models.School,SchoolConfig)class ClassListConfig(sites.AryaConfig):def course_dispaly(self,obj=None,is_header=False):if is_header:return '班级'return '%s(%s)期'% (obj.course.name,obj.semester)def zhanggen(self,obj=None,is_header=False):if is_header:return '任课老师'teachers=[]for obj in obj.teachers.all():tpl='<span style="dispaly:inline-block;padding:3px;margin:2px;boder:1px solid #add;" >%s</span>'% (obj.name)teachers.append(tpl)return mark_safe(''.join(teachers))list_display = ['school',course_dispaly,'price','tutor',zhanggen]sites.site.register(models.ClassList,ClassListConfig)View Code
自定义函数:list_display显示choices字段
利用obj的get_gender_display()特性,显示choice字段对应的中文名称
1 def gender_display(self,obj=None,is_header=False): #显示 choice 字段 2 if is_header: 3 return '性别' 4 return obj.get_gender_display() 5 list_display = ['qq', 'name', 'consultant',zhanggen,gender_display,] 6 sites.site.register(models.Customer,CustomerConfig)View Code
小结:
经过CRM业务和Aaya(增、删、改、查)插件 list_display配置项的结合配置
就可以 把数据库字段、外键字段、多对多字段、choice字段 都显示出来了;
9、CRM表结构 配置组合筛选
(1)、改进list_filter
组合搜素显示:
foreign key
M2M
choice
def __init__(self, option, change_list, data_list, param_dict=None,is_choices=False):View Code
for obj in self.data_list:param_dict = copy.deepcopy(self.param_dict)if self.is_choices:pk=str(obj[0])text=obj[1]View Code
通过 Q添加筛选条件
sites.FilterOption('consultant',condtion=Q(depart_id=2)),View Code
def __init__(self, field_or_func, is_multi=False, text_func_name=None, val_func_name=None,condtion=None):View Code
@propertydef get_condtion(self):from django.db.models import Qif self.condtion:return self.condtioncon=Q()return conView Code
9、点击组合筛选,显示筛选数据;(读取URL参数,拼接成字典,去数据库查询)
@propertydef get_filter_conditon(self):fields2 = [obj.name for obj in self.model_class._meta._get_fields()]params = self.request.GETcon = {}for k in params:if k in fields2:v = self.request.GET.getlist(k)k = '%s__in' % (k)con[k] = vreturn conView Code
涉及知识点:
1、获取数据库中 外键字段、choice字段(注意不包含多对多字段)
fields=[ obj.name for obj in self.model_class._meta.fields] #获取数据中 外键字段、choice字段(主语没有多对多)
2、获取数据库中 多对多字段
fields1 = [obj.name for obj in self.model_class._meta.many_to_many] #获取多对多字段
3、获取数据库中所有字段 (包含反向关联、多对多字段)
# fields2 = [obj.name for obj in self.model_class._meta._get_fields()] # 获取反向关联的字段(包含对多多)
4、查询数据去重 distinct()
data_list = self.model_class.objects.filter(**self.get_filter_conditon).filter(self.get_search_condtion).distinct() #distinct() 多选去重
10、CRM客户部 配置模糊搜素功能 search_list = ['name', 'qq'],;
search_list = ['name', 'qq']
基础配置类
search_list = []def get_search_list(self):search_list = []search_list.extend(self.search_list)return search_listView Code
@propertydef get_search_condtion(self):con=Q()con.connector='OR'val=self.request.GET.get(self.q)if not val:return confild_list=self.get_search_list()for field in fild_list:field='%s__contains'%(field)con.children.append((field,val))return conView Code
ChangeList类
self.search_list = model_config.get_search_list()
def seach_attr(self):val=self.model_config.request.GET.get(self.model_config.q)print(val)return {'value':val,'name':self.model_config.q}View Code
模板
{% if cl.list_filter %}<div class="comb-search">{% for row in cl.gen_list_filter %}<div class="row">{% for col in row %}{{ col }}{% endfor %}</div>{% endfor %}</div>{% endif %}{% if cl.search_list %}<div class="row"><form method="get"><input value="{{cl.seach_attr.value}}" class="form-control" name="{{cl.seach_attr.name}}" style="width: 250px;display: inline-block" type="text" placeholder="请输入关键字"><button class="btn btn-primary"><span class="glyphicon glyphicon-search"></span></button></form></div>{% endif %}View Code
转载于:.html
发布评论