如何提高本身的開發效率?css
每一個新項目都是本身經作過的項目(經驗所致),在項目開發過程當中不斷總結、封裝屬於本身的組件,html
例如:每一個web項目大部分都涉及增刪改查,分頁顯示,搜素,CRM就是這樣的組件,是一件頗有必要的事情;前端
CURD組件(arya):模仿DjangoAdmin編寫增刪改查插件;python
組件功能:經過在後臺註冊表名配置自定製類,操做、顯示數據庫中的內容;jquery
組件設計目標:把CRM模塊嵌套到不一樣具備增刪改查功能的Django程序上,實現快速開發基於web的後臺管理系統;web
組件配置項:數據庫
list_display=[ '字段',‘函數’] : 定製顯示頁面顯示的列名,和內容;django
action : 定製action,批量操做;json
show_add_btn=True :定製是否顯示添加按鈕bootstrap
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()
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 = option self.data_list = data_list self.param = copy.deepcopy(params) # query_dict對象 print(self.param) # print(self.param) # <QueryDict: {'group': ['1'], 'roles': ['2']}> self.param._mutable = True def __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) #1 text = 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=3 else: 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)
一、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 AppConfig class App01Config(AppConfig): name = 'app01' def ready(self): pass
二、App02Config類中的ready方法,執行 autodiscover_modules('zhanggen'),因此去Django程序的全部app下尋找zhanggen.py並執行;
autodiscover_modules()模塊,在Django程序啓動自動發現並執行一個py文件 (啓動文件)
autodiscover_modules(xx.py)模塊自動去全部的APP下,尋找xx.py文件而且執行
from django.apps import AppConfig class App02Config(AppConfig): name = 'app02' def ready(self): # 1.導入autodiscover_modules 程序啓動執行autodiscover_modules(參數) # 誰導入autodiscover_modules(py文件)並設置了,就會在程序啓動前執行那個pywenj from django.utils.module_loading import autodiscover_modules autodiscover_modules('zhanggen')
三、找到後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_safe class ChangeList(object): #封裝 table 數據傳遞給前端 def __init__(self,data_list,list_display,model_config_obj): self.data_list = data_list self.list_display = list_display self.model_config_obj = model_config_obj def add_html(self): #封裝add 按鈕 app_model = self.model_config_obj.model_class._meta.app_label, self.model_config_obj.model_class._meta.model_name add_url = reverse("zg:%s_%s_add" % app_model) add_html = mark_safe('<a class="btn btn-primary" href="%s">添加</a>' % (add_url,)) return add_html class Modelzg(object): # 註冊的時候沒有使用配置對象,默認使用的配置對象 ''''用戶基礎配置類''' def __init__(self, model_class, site): #model_class=model.userinfo , site=Zgsite對象 self.model_class = model_class self.site = site list_display = [] show_add_btn = True def get_show_add_btn(self): return self.show_add_btn def 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_tag context = { '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 patterns def extra_urls(self): ''''自定義擴展url 預留的鉤子函數''' return [] @property def urls(self): return self.get_urls(),None,None 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對象 ):是配置對象 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名稱 例: app01 table_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 @property def urls(self): return self.ger_urls(),self.name,self.namespace #由於路由系統中的include本質就是返回一個元祖 site=Zgsite()
設計知識點:
文件實現單例模式:
文件導入以後該文件中定義的類被實例化成一個對象,1次執行以後,之後再調用這個文件永遠獲得同一個對象,至關於永遠執行1個對象;
四、zhanggen.py實例化了v1中Zgsite類,獲得單例對象site,site單例對象 2次(2張表) 調用了單例象的register()方法,
v1.site.register(models.UserInfo) v1.site.register(models.UserGroup) #一、導入v1就獲得一個單例對象 site #二、執行site對象的regist()進行註冊
五、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對象 ):是配置對象
六、Django程序的路由系統開始執行,一樣調用了單例對象 v1.py中的單例對象 site,並執行了site單例對象的urls方法;
url(r'^zg/',v1.site.urls),
七、site單例對象的urls方法,調用了site單例對象的ger_urls方法;
def ger_urls(self): patterns=[] patterns+=[ #基本url url(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名稱 例: app01 table_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 @property def urls(self): return self.ger_urls(),self.name,self.namespace #由於路由系統中的include本質就是返回一個元祖
八、ger_urls方法,for循環了單例對象的self._registry屬性,爲每張表生成基礎url映射關係;
^zg/ ^login/
^zg/ ^logout/
^zg/ ^app01/userinfo/
^zg/ ^app01/usergroup/
九、單例對象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 patterns
涉及知識點:
一、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')), ]
二、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:別名)時一點要加上namesp def 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')),
三、model.py中類的_meta方法:
app_name=models.UserInfo._meta.app_label #獲取model中類(表),所在的app table_name=models.UserInfo._meta.model_name #獲取model中類(表),表名 filed_verbose=models.UserInfo._meta.get_field('email').verbose_name #獲取列的verbosename 就是描述信息
四、擴展、和重寫url
1、預留 url擴展和重寫 zhanggen.py #擴展 url def extra_urls(self): patterns = [ url(r'test/$',self.test), ] return patterns #重寫url def get_urls(self): patterns = [ url(r'test/$', self.test), ] return patterns v1.py def 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 patterns def extra_urls(self): ''''自定義擴展url 預留的鉤子函數''' return []
CRM基礎框架已經搭建完成,每一個請求過來都會生成url 視圖映射關係,接下來應該設計 視圖功能了;(day88內容)
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
一、由每張表的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_name else: 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_name context={ 'headers':header(), 'data_list':data_list } return render(request,'test.html',context)
涉及知識點:
三元表達式
i(self,is_header=True) if isinstance(i,FunctionType) else self.model_class._meta.get_field(i).verbose_name
yield生成器: 在後端製做生成器,在前端for循環生成器,提升性能;
yield [i(self, is_header=True, row=obj) if isinstance(i, FunctionType) else getattr(obj, i) for i in self.list_display]
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 in self.list_display]
在配置類自定義列標題 和自定義列內容
def xxx(self,is_header=False,row=None): if is_header==True: #若是調用該函數的時候傳入的參數是is_header==True,函數執行就返回標題 return '列名稱' else: return '自定義列'#若是調用該函數的時候傳入的參數是is_header==Fase,函數執行就返回內容
二、基礎配置對象 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_list self.list_display = list_display self.model_config_obj = model_config_obj def add_html(self): #封裝add 按鈕 app_model = self.model_config_obj.model_class._meta.app_label, self.model_config_obj.model_class._meta.model_name add_url = reverse("zg:%s_%s_add" % app_model) add_html = mark_safe('<a class="btn btn-primary" href="%s">添加</a>' % (add_url,)) return add_html
涉及知識點·:
面向對象封裝
def func(a1,a2,a3,a4): #若是一個函數執行須要不少參數,就能夠吧這個函數改形成面向對象的模式 pass class Foo(): def __init__(self,a1,a2,a3,a4): #優點1:一次傳入,封裝全部值到一個對象; self.a1=a1 self.a2 = a2 self.a3 = a3 self.a4 = a4 def a5(self): #優點2:還能夠經過方法修改封裝的值 pass
三、nb/changelist.html加載 result_list simp_tag,並把後端傳來c1對象傳到{% show_result_list cl%} include_tag
四、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_name
else:
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_name
def 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) for
name in cl.list_display]
return {'headers': headers(), #
'body': body(),}
#就是inclusion_tag(模板)相似simple_tag
涉及知識點:
自定義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=name def show(self): print('show',self.name) obj=Foo('zhangggen') # Foo.show(obj) #類直接調用 類中的函數,須要加self參數 from types import FunctionType,MethodType print(isinstance(Foo.show,FunctionType)) #函數 print(isinstance(obj.show,FunctionType)) #方法
五、前端根據權限 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_class self.site = site list_display = [] show_add_btn = True def get_show_add_btn(self): #默認是True,就是顯示add按鈕,因爲廣度優先繼承PermissionConfig,PermissionConfig類重寫了get_show_add_btn return self.show_add_btn
class PermissionConfig(object): #配置類重寫 get_show_add_btn,控制 add按鈕的顯示 def get_show_add_btn(self): print(self.request) return False
註釋:
因爲配置對象同時繼承了PermissionConfig權限配置類,v1.Modelzg默認配置類,配置對象在默認配置類寫了self.request = request,而配置對象又被封裝到了Cl對象裏,全部前端cl.model_config_obj.get_show_add_btn 能夠判斷是否顯示添加權限;
六、list_display = [ ]配置了字段,就顯示 checkbox選擇和編輯選項,預留權限鉤子
list_display = [] show_add_btn = True def 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 res
預留權限鉤子
def get_list_play(self): return super().get_list_play() # UserInfoConfig對象,先來PermissionConfig,再去v1.Modelzg類找get_list_play方法
七、用戶自定製 批量刪除和初始化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 res
把在基礎配置類裏定義批量刪除 和初始化的函數
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): pass init_action.short_desc = '初始化' # 給函數定義一個屬性
把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)
前端把函數名做爲option的value 函數的short_desc屬性做爲內容顯示
{% for action in cl.actions %} <option value={{ action.0}}>{{ action.1}}</option> {% endfor %}
最後把選擇的checkbox和選擇的函數經過from表單提交到後臺基礎配置類 chang_list_viwe方法
self.request = request if 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()
後端經過反射在後端執獲取函數並執行
def multi_del(self): id_list=self.request.POST.getlist('pk') # self.model_class.objects.fifter(pk__in=id_list).delete()
八、分頁保留原來的搜素條件
當咱們在搜素框輸入了搜素條件,點擊下一頁應當保存搜素條件!
例如:當前頁: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( #自定製分頁器須要傳入的參數 #一、當前訪問頁碼 current_page=model_config_obj.request.GET.get('page'), #二、 數據庫總條目數 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()
修改分頁生成的 頁碼
""" 使用方法: 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_safe class 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 = 1 self.current_page = current_page self.total_item_count = total_item_count self.base_url = base_url self.per_page_count = per_page_count self.show_pager_count = show_pager_count self.request_params=request_params max_pager_num, b = divmod(total_item_count, per_page_count) if b: max_pager_num += 1 self.max_pager_num = max_pager_num @property def start(self): """ :return: """ return (self.current_page-1)* self.per_page_count @property def end(self): """ :return: """ return self.current_page * self.per_page_count def 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-1 prev = ' <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: # 頁碼小於11 pager_start = 1 pager_end = self.max_pager_num + 1 else: if self.current_page <= half_show_pager_count: pager_start = 1 pager_end = self.show_pager_count + 1 else: if self.current_page + half_show_pager_count > self.max_pager_num: pager_start = self.max_pager_num - self.show_pager_count + 1 pager_end = self.max_pager_num + 1 else: pager_start = self.current_page - half_show_pager_count pager_end = self.current_page + half_show_pager_count + 1 for i in range(pager_start, pager_end): self.request_params['page']=i if 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+1 nex = ' <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 onclick="$.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: # 頁碼小於11 pager_start = 1 pager_end = self.max_pager_num + 1 else: if self.current_page <= half_show_pager_count: pager_start = 1 pager_end = self.show_pager_count + 1 else: if self.current_page + half_show_pager_count > self.max_pager_num: pager_start = self.max_pager_num - self.show_pager_count + 1 pager_end = self.max_pager_num + 1 else: pager_start = self.current_page - half_show_pager_count pager_end = self.current_page + half_show_pager_count + 1 for i in range(pager_start, pager_end): if i == self.current_page: tpl = ' <li class="active"><a onclick="$.changePage(%s)" >%s</a></li>' % (i,i,) else: tpl = ' <li><a onclick="$.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 onclick="$.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: # 頁碼小於11 pager_start = 1 pager_end = self.max_pager_num + 1 else: if self.current_page <= half_show_pager_count: pager_start = 1 pager_end = self.show_pager_count + 1 else: if self.current_page + half_show_pager_count > self.max_pager_num: pager_start = self.max_pager_num - self.show_pager_count + 1 pager_end = self.max_pager_num + 1 else: pager_start = self.current_page - half_show_pager_count pager_end = self.current_page + half_show_pager_count + 1 for 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)
設計知識點:
from django.http import QueryDict
當咱們向django發送請求時,Django接收request.get()到的數據是query_dict數據類型
query_dict數據類型默認不能夠修改
request.GET.get()._mutable=True 設置該參數後便可修改
還原成urlencode類型:request.GET.urlencode()
九、單表添加功能
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_name query_dict=QueryDict(mutable=True) query_dict['xxoo']=self.model_config_obj.request.GET.urlencode() #xxoo=p%3D1
一、在add_view中定義ModelForm,讓配置對象調用該方法時,能夠顯示html頁面
self.request=request chang_list_url_params=request.GET.get('xxoo') from django.forms import ModelForm class AddModelForm(ModelForm): class Meta: model=self.model_class #哪一個配置調用本身的add_view方法來,就建立這個表的ModelForm fields='__all__' #在前端顯示的列 if request.method=='GET': form=AddModelForm() context={ 'form':form, } return render(request,'nb/add.html',context)
二、用戶提交數據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_params print(base_url) return redirect(base_url)
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_url
三、自定製ModelForm
經過方法獲取默認配置裏面的ModelForm若是爲空就是使用默認配置,不然使用自定義配置ModelForm
model_form=None def get_model_form_class(self): result=self.model_form if not result: class Defalut_Model_Form(ModelForm): class Meta: model = self.model_class # 哪一個配置調用本身的add_view方法來,就建立這個表的ModelForm fields = '__all__' # 在前端顯示的列 result = Defalut_Model_Form return result
class UserInforModelFrom(ModelForm): www=fields.CharField(widget=widgets.Textarea) class Meta: model=models.UserInfo fields = '__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=UserInforModelFrom
涉及知識點:
一、request.GET.urlencode()
獲取get請求後面攜帶的參數
query_dict['xxoo']=self.model_config_obj.request.GET.urlencode() #xxoo=p%3D1
二、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_params print(base_url) return redirect(base_url) context = { 'form': form, } return render(request,'nb/add.html', context)
在模板中使用
<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>
十、單表編輯/刪除功能
沒時間實現
十一、經過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>
執行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 site register=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']=True flied_class=item.field.queryset.model #字段外鍵對應的表名 app_label=flied_class._meta.app_label #外鍵表所在的app model_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渲染完成頁面
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>
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/title val=obj.pk return render(request,'nb/popuprespose.html',{ 'name':name,'tagId':tagId,'val':val }) return redirect(url)
涉及知識點:
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']=True flied_class=item.field.queryset.model #字段外鍵對應的表名 app_label=flied_class._meta.app_label #外鍵表所在的app model_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
js之popup彈窗 (3個HTML頁面)
一、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>
二、/p2/下的視圖添加數據庫操做,把增長的row對象模板渲染,re1個頁面popuprespose.html
def p2(request):
if request.method=='GET':
return render(request,'p2.html')
elif request.method=='POST':
from app01 import models
city=request.POST.get('city')
obj= models.UserGroup.objects.create(title=city)
return render(request,'popuprespose.html',{'obj':obj})
三、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) }
組合搜素這個功能,其實很簡單卻浪費我大量鬧細胞;
一、把全部在顯示頁面須要的數據封裝進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> {#顯示組合搜素結束#}
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 RelatedField if 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_items
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 = option self.data_list = data_list self.param = copy.deepcopy(params) # query_dict對象 print(self.param) # <QueryDict: {'group': ['1'], 'roles': ['2']}> self.param._mutable = True def __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] = pk url = 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)
RowItems()的__iter__ 方法,每次根據request.get攜帶的參數,yeild a標籤
一、先根據get請求,request.getlist(self.optionname) 獲取current_pk_or_list參數
列表 [5,6]
二、首先self.param.pop(self.option.name)清空group參數本身,留下role參數,生成所有A標籤 ;
三、循環遍歷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思想:
一、用戶首次訪問:http://127.0.0.1:8000/zg/app01/userinfo/
reques.getlist(self.option.name) #獲得 current_pk_or_list爲空 []
二、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) #1 text = 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=3
若是obj.pk不在current_pk_or_list [ ] 中,就把obj.pk添加到tmp,第一次循環進來 tmp=[1 ]
tmp=[]
tmp.extend(current_pk_or_list)
tmp.append(pk)
三、修改 query_dict,<QueryDict: { option.name : [1] }
self.param.setlist(self.option.name,tmp)
四、url endconde編碼 ?group=1
url = self.param.urlencode()
五、遍歷循環結束,根據obj個數生成A標籤的 url
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
一、首次生成url以後,在頁面點擊搜素項,http://127.0.0.1:8000/zg/app01/userinfo/?group=1
reques.getlist(self.option.name) #獲得 current_pk_or_list爲 [1]
二、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]
三、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=None data_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), #自定製函數 ]
總結:根據 request.getlist()獲取的 [ ], 判斷當前循環到的obj.id是否在[],如不不存在 添加到 [ ],修改 query_dict { 'option' : [ ] },urlencode編碼爲url格式做爲A標籤的跳轉連接;
通過漫長的開發週期,模仿DjangoAdmin開發出來的CURD組件終於可使用了,賜名arya;下面是在1個嶄新的Django項目中使用arya組件流程;
一、在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', ]
二、任意APP下建立 arya.py
三、在arya.py中組冊model_class
from . import models from arya.service.sites import site #導入單例模式 site.register(models.Department,)
四、爲已經註冊的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), ]
五、修改母版板樣式layout
diango作模板渲染得時候尋找模板的順序是,從最外層templates 到app裏面的templates
因此若是想修改網頁顯示的樣式,就能夠在最外層templates,定義本身的母版,讓其餘頁面繼承;
六、增、刪、改、查基本操做
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)
七、插件擴展
0、脫離插件
一、擴展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_name patterns = [ url(r'^(.+)/detail/',self.detail_view, name="%s_%s_changelist" % app_model_name), ] return patterns def detail_view(self,request,pk): obj=self.model_class.objects.all().filter(pk=pk).first() print(obj.name,obj.id) return HttpResponse('顯示詳情')
#工單系統 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_name print(app_model_name) p= [ url(r'^worker_order_out/$', self.detail_view, name="%s_%s_t" % app_model_name), ] return p def detail_view(self, request): return HttpResponse('顯示詳情') v1.site.register(models.Worker_order,Worker_orderConfig)
1.2.arya生成url的規則
/arya開頭/app名稱/model表名/擴展url的名/: 例如:/arya/cmdb/worker_order/worker_order_out/
1.3.在RBAC中爲角色增長該URL的訪問權限
在權限管理----》URL權限添加權限標題:測試爲標題
二、自定義添加、顯示、刪除頁面,重寫基礎配置類中的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')
RBAC基於角色的權限管理系統回顧:詳見 Django之權限管理插件
表結構:用戶表 Many to many 角色表 Many to many 權限表 foreign key 菜單表
中間件:判斷當前訪問用戶是否有訪問權限
登陸成功初始化:把當前用戶全部權限初始化放到session中
配置文件: 無需登陸就能使用的權限,session的key
自動生成菜單:css、JavaScript
一、註冊RBAC app
'rbac.apps.RbacConfig',
二、清空rbac插件的migration記錄
三、遷移rbac插件的數據
python manage.py makemigrations
python manage.py migrate
四、在rbac建立arya.py註冊rbac model_class
from arya.service import sites from . import models sites.site.register(models.User) sites.site.register(models.Role) class PermissionConfig(sites.AryaConfig): pass sites.site.register(models.Permission,PermissionConfig) sites.site.register(models.Menu)
五、建立權限信息arya/rbac/permission/
六、完善CURD插件功能;
一、添加權限自動發現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 urlpatterns all_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)
(1)方式2
修改ModelForm組件
from arya.service import sites from . import models from django.shortcuts import render,redirect import json from 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+v result.append((val,val)) else: get_all_url(item.urlconf_name,prev+v) return result sites.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_form class PermissionModelForm(ModelForm): #ModelForm 能夠結合Model把全部數據庫字段在頁面上生成,也能夠增長額外的字段; url=fields.ChoiceField() class Meta: fields = "__all__" model = models.Permission #注意不是models def __init__(self,*args,**kwargs): #重寫父類的 __init__方法,每次實例化實時更新 form中的數據 super(PermissionModelForm,self).__init__(*args,**kwargs) from pro_crm.urls import urlpatterns self.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 = PermissionModelForm
二、經過裝飾器實現每次請求保存request信息
def warpper(self,func): #每次獲取 self.requesr=request @functools.wraps(func) # def inner(request,*args,**kwargs): self.request=request return func(request,*args,**kwargs ) return inner
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), ]
設計知識點:
(1)、裝飾器:
@warpper
def foo()
pass
foo=warpprt(foo)=return的結果
(2)、@functools.wraps(func)
裝飾器函數裏面的文檔介紹 徹底= 原生函數的文檔介紹
(3)、ModelForm 增長數據庫字段以外的field,ModelForm對象實時更新數據
ModelForm 能夠結合Model把全部數據庫字段在頁面上生成,也能夠增長額外的字段;
規則:若是增長的字段和數據裏的filed重名則覆蓋,不重名則新增;
也能夠經過重寫__init__ ,每次實例化1個form對象,實時更新數據;
一、建立名爲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', ]
二、使用RBAC,配置RBAC的表;(只能訪問本身權限的URL,登陸以後獲取本身的權限列表放入request.session)
from arya.service import sites from . import models from django.shortcuts import render,redirect import json from 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+v result.append((val,val)) else: get_all_url(item.urlconf_name,prev+v) return result sites.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 #注意不是models def __init__(self,*args,**kwargs): #重寫父類的 __init__方法,每次實例化實時更新 form中的數據 super(PermissionModelForm,self).__init__(*args,**kwargs) from pro_crm.urls import urlpatterns self.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 = PermissionModelForm sites.site.register(models.User,UserConfig) sites.site.register(models.Permission,PermissionConfig) sites.site.register(models.Role,RoleConfig) sites.site.register(models.Menu)
三、添加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>
四、添加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>
設置crm首頁
{% extends 'arya/layout.html' %} {% block content %} <h1>歡迎登陸</h1> {% endblock %}
五、index視圖執行RBAC插件邏輯
1)登陸成功以後初始化權限信息
initial_permission(request,obj) #初始化用戶權限信息
2)頁面顯示時生成 菜單HTML
3)setings.py配置文件 引入rbac中間件、設置rbac使用的key、simple_tag生成多級菜單
'rbac.middleware.rbac.RbacMiddleware', #引入rbac中間件
# ############################## 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權限相關配置結束 ##############################
六、在權限中間件以前,經過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/')
七、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")
八、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)
自定義函數: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)
小結:
通過CRM業務和Aaya(增、刪、改、查)插件 list_display配置項的結合配置
就能夠 把數據庫字段、外鍵字段、多對多字段、choice字段 都顯示出來了;
九、CRM表結構 配置組合篩選
(1)、改進list_filter
組合搜素顯示:
foreign key
M2M
choice
def __init__(self, option, change_list, data_list, param_dict=None,is_choices=False):
for obj in self.data_list: param_dict = copy.deepcopy(self.param_dict) if self.is_choices: pk=str(obj[0]) text=obj[1]
經過 Q添加篩選條件
sites.FilterOption('consultant',condtion=Q(depart_id=2)),
def __init__(self, field_or_func, is_multi=False, text_func_name=None, val_func_name=None,condtion=None):
@property def get_condtion(self): from django.db.models import Q if self.condtion: return self.condtion con=Q() return con
九、點擊組合篩選,顯示篩選數據;(讀取URL參數,拼接成字典,去數據庫查詢)
@property def get_filter_conditon(self): fields2 = [obj.name for obj in self.model_class._meta._get_fields()] params = self.request.GET con = {} for k in params: if k in fields2: v = self.request.GET.getlist(k) k = '%s__in' % (k) con[k] = v return con
涉及知識點:
一、獲取數據庫中 外鍵字段、choice字段(注意不包含多對多字段)
fields=[ obj.name for obj in self.model_class._meta.fields] #獲取數據中 外鍵字段、choice字段(主語沒有多對多)
二、獲取數據庫中 多對多字段
fields1 = [obj.name for obj in self.model_class._meta.many_to_many] #獲取多對多字段
三、獲取數據庫中全部字段 (包含反向關聯、多對多字段)
# fields2 = [obj.name for obj in self.model_class._meta._get_fields()] # 獲取反向關聯的字段(包含對多多)
四、查詢數據去重 distinct()
data_list = self.model_class.objects.filter(**self.get_filter_conditon).filter(self.get_search_condtion).distinct() #distinct() 多選去重
十、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_list
@property def get_search_condtion(self): con=Q() con.connector='OR' val=self.request.GET.get(self.q) if not val: return con fild_list=self.get_search_list() for field in fild_list: field='%s__contains'%(field) con.children.append((field,val)) return con
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}
模板
{% 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 %}