python 全栈开发,Day116(可迭代对象,type创建动态类,偏函数,面向对象的封装,获取外键数据,组合搜索,领域驱动设计(DDD))...
?
昨日內容回顧
1. 三個類 ChangeList,封裝列表頁面需要的所有數據。StarkConfig,生成URL和視圖對應關系 + 默認配置 AdminSite,用于保存 數據庫類 和 處理該類的對象 的對應關系 + 路由分發_registry = {} 2. 知識點 inclusion_tagyieldurlencode_meta.model_name_meta.app_label深淺拷貝 QueryDict對象默認不可改 _mutable=True 生成器 路由分發:- include- ([],None,None)函數和方法的區別?Q的作用?構造復雜的查詢條件models.User.object.filter(name__contains='李')models.User.object.filter(name__contains='李',email__contains='李')構造 or c1 = Q()c1.connector = 'OR'c1.children.append( ('name__contains','李') )c1.children.append( ('email__contains','李') )c2 = Q()c2.connector = 'ADN'c2.children.append( ('id__gt',2) )c3.children.append( ('age__lte',5) )c3 = Q()c3.connector = 'ADN'c3.add(c1,"ADN")c3.add(c2,"ADN")等同于(name=li or email = li) AND ( id>2 and age<=5)models.User.object.filter(con)反射 有2個地方用到了反射list_display: # 顯示指定的列 row.name getattr(row,'name')# 批量操作response = getattr(self, action_name)(request)pass繼承 class RoleConfig(StarkConfig):pass self到底是誰?反向生成URLreverse('xxx')reverse('namespace:xxx')多個namespace,也是以冒號分隔分頁(保留原搜索條件) 跳轉的url中保留_filter=原搜索條件ModelForm組件添加和修改functools- wraps,用于保留原函數的元信息(函數名/函數注釋等信息)- partial,偏函數為函數默認傳參。import functoolsdef func(a1,a2):print(a1+a2)new_func = functools.partial(func,8)new_func(7)new_func(2)new_func(8)預留可擴展位置request.GETrequest.GET.get('x')request.GET['x']request.GET.getlist('xxx')request.GET._mutable = True request.GET.copy()request.GET.urlencode()mark_safe xss攻擊是什么?跨站腳本攻擊一般用js代碼,進行攻擊。獲取cookie,模擬登錄,做非法操作!單例模式- 多個模塊之間導入- 共享同一個數據時獲取函數名__name__autodiscover_module自動發現模塊裝飾器 添加request參數,方便非視圖函數調用requestorder_by排序展示__str__在models.py中使用3. QueryDict對象 params = request.GET.copy() # 使用深copyparams._mutable = True # 允許修改 params['k1'] = 'v1' # 添加單個params.setlist('k2',[11,22]) # 添加多個 View Code?
一、可迭代對象
什么是可迭代對象
在之前學過的容器中,許多容器都是可迭代對象,可以直接用于for…in…循環的而對象都是可迭代對象,比如:list,tuple,dict,set,str等等。
可迭代對象滿足條件:實現了__iter__方法
?
注意:__iter__方法必須返回一個迭代器(iter)
?
可迭代對象并不是一種具體的數據類型,比如list是可迭代對象,dict也是可迭代對象。
如何判斷一個對象是否是可迭代對象? 使用isinstance()函數
from collections import Iterablea = isinstance("123",Iterable) b = isinstance(1,Iterable) print(a) #字符串是不是可迭代對象 返回True print(b) #數字是不是可迭代對象 返回False?
舉例1
看下面一段代碼
class Foo(object):passobj = Foo() for item in obj:print(item)執行報錯:
TypeError: 'Foo' object is not iterable提示不可迭代,怎么讓它可以迭代?
先來看可迭代對象滿足條件:實現了__iter__方法
?
那么在類中,定義一個__iter__方法,返回迭代器就可以了!
class Foo(object):def __iter__(self):# return iter([11,22,33,44]) # 返回迭代器yield 11 # 返回生成器(迭代器的一種)obj = Foo() for item in obj:print(item)執行輸出: 11
?
舉例2
這是汽車之家的篩選頁面,每一個行的數據是循環展示的!如何構造這些數據?
方法一:使用字典
data_list= [['1.0以下','1.1-1.6'],['汽油','柴油','混合動力','電動'], ]for row in data_list:for field in row:print(field)執行輸出:
1.0以下 1.1-1.6 汽油 柴油 混合動力 電動 View Code?
方法二:使用對象+yield
class Row(object):def __init__(self,data):self.data = datadef __iter__(self):for item in self.data:yield itemdata_list= [Row(['1.0以下','1.1-1.6']),Row(['汽油','柴油','混合動力','電動']), ]for row in data_list:for field in row:print(field) View Code執行輸出,效果同上!
?
為什么要用對象構造數據?因為要構造更復雜的數據結構!
class Row(object):def __init__(self,data):self.data = datadef __iter__(self):yield "<div>"yield '全部'for item in self.data:yield "<a href='/index/?p1=1.0'>%s</a>" %itemyield "</div>"data_list= [Row(['1.0以下','1.1-1.6']),Row(['汽油','柴油','混合動力','電動']), ]for row in data_list:for field in row:print(field) View Code執行輸出:
<div> 全部 <a href='/index/?p1=1.0'>1.0以下</a> <a href='/index/?p1=1.0'>1.1-1.6</a> </div> <div> 全部 <a href='/index/?p1=1.0'>汽油</a> <a href='/index/?p1=1.0'>柴油</a> <a href='/index/?p1=1.0'>混合動力</a> <a href='/index/?p1=1.0'>電動</a> </div> View Code可以看出,這段數據,比上面的復雜!
如果用列表,那么有很多if判斷!使用類,2層for循環就出來了!
為什么要輸出a標簽?因為前端渲染麻煩,后端生成a標簽。前端容易展示,還可以做一些復雜的需求!
?
二、type創建動態類
前戲
舉例1
def gen_cls():class Foo(object):passreturn Foocls = gen_cls()print(cls) View Code執行輸出:
<class '__main__.gen_cls.<locals>.Foo'>對象是由類創建的。那么類是由誰創建的?先帶著這個疑問
舉例2
name = "Foo" country = "中國" detail = lambda self, x: x + 1根據以上三個參數創建一個類,類中有兩個成員。實現的效果如下:
class Foo(object):country = '中國'def detail(self,x):return x + 1如何用代碼實現呢?
不能這么寫
class name(object):country = '中國'...那么class名就是name了,需要使用type
?
type創建動態類
type還有一種完全不同的功能,動態的創建類。
type可以接受一個類的描述作為參數,然后返回一個類。(要知道,根據傳入參數的不同,同一個函數擁有兩種完全不同的用法是一件很傻的事情,但這在Python中是為了保持向后兼容性)
?
type可以像這樣工作:
type(類名,由父類名稱組成的元組(針對繼承的情況,可以為空),包含屬性的字典(名稱和值))?
根據上面的3個條件,使用type創建一個類
name = "Foo" country = "中國" detail = lambda self, x: x + 1 # 使用type創建動態類 cls = type(name, (object,), {'country': '中國', 'detail': lambda self, x: x + 1})obj = cls() print(obj) print(obj.country) print(obj.detail(100)) View Code執行輸出:
<__main__.Foo object at 0x000001E0FBF2DA20> 中國 101根據條件,由type動態創建了一個類。類的基本操作,比如實例化,獲取靜態字段,傳參,都沒有問題!
?
函數type實際上是一個元類。type就是Python在背后用來創建所有類的元類。type就是Python的內建元類
總結:對象是由類創建的。那么類默認是由type創建的
?
三、偏函數
什么是偏函數
偏函數是2.5版本以后引進來的東西。屬于函數式編程的一部分,使用偏函數可以通過有效地"凍結"那些預先確定的參數,來緩存函數參數,然后在運行時,當獲得需要的剩余參數后,可以將他們解凍,傳遞到最終的參數中,從而使用最終確定的所有參數去調用函數。
舉例
示例函數
def func(a1,a2):return a1+a2res = func(1,3) print(res)執行輸出:4
使用偏函數
import functools def func(a1,a2):return a1+a2# 相當于給第一個參數,設置默認參數8 new_func = functools.partial(func,8) # 傳入第二個參數 res = new_func(7) print(res)執行輸出:15
?
func有2個參數,怎么確定是給第一個參數,設置了默認參數?
import functools def func(a1,a2=22):return a1+a2# 相當于給第一個參數,設置默認參數8 new_func = functools.partial(func,8) # 傳入第二個參數 res = new_func(7) print(res)執行輸出:15
注意:a1直接設置默認參數,是會報錯的!默認參數必須在位置參數的后面!
在flask中,會用到偏函數
?
四、面向對象的封裝
看下面一段數據
list_filter = ['全智賢','高圓圓','胡歌',]如何用代碼區分性別?
?
第一種方法:使用字典
list_filter = [{'name':'全智賢','sex':'女'},{'name':'高圓圓','sex':'女'},{'name':'胡歌','sex':'男'}, ]再區分衣服的顏色呢?
list_filter = [{'name':'全智賢','sex':'女','color':'pink'},{'name':'高圓圓','sex':'女','color':'red'},{'name':'胡歌','sex':'男','color':'black'}, ]如果還有其它屬性呢?繼續加?
使用字典也是一個封裝思想
?
第二種方法:使用類(推薦)
class Option(object):def __init__(self,name,sex,color):self.name = nameself.sex = sexself.color = colorlist_filter = [# 字典對象做封裝Option(name='全智賢',sex='女',color = 'pink'),Option(name='高圓圓',sex='女',color = 'red'),Option(name='胡歌',sex='男',color = 'black'), ]for item in list_filter:print(item.__dict__) View Code執行輸出:
{'name': '全智賢', 'color': 'pink', 'sex': '女'} {'name': '高圓圓', 'color': 'red', 'sex': '女'} {'name': '胡歌', 'color': 'black', 'sex': '男'}通過這2種方法對比,使用類更好一點!
如果使用字典,那么第一條數據,加了一個參數。而另外2條數據,卻沒有加!
那么頁面展示就會錯亂!
而使用類,則不會這個情況!另外一點,使用類,可以構造更加復雜的數據結構!
使用類,是新的 封裝思想。可擴展功能比較方便
?
五、獲取外鍵數據
外鍵分為3種:?FK/M2M/O2O,分別對應一對多,多對多,一對一
給定2個參數
model_class = Depart # 模型表類 _field = "user" # 外鍵字段要獲取外鍵字段對應的所有數據,如何獲取?
?
新建項目untitled4,注意:django版本為1.11
?
修改models.py,新建2個表
from django.db import models# Create your models here. class UserInfo(models.Model):title = models.CharField(verbose_name='標題',max_length=32)def __str__(self):return self.titleclass Depart(models.Model):name = models.CharField(verbose_name='部門名稱',max_length=32)tel = models.CharField(verbose_name='聯系電話',max_length=32)user = models.ForeignKey(verbose_name='負責人',to='UserInfo')def __str__(self):return self.name View Code使用2個命令生成表
python manage.py makemigrations python manage.py migrate?
增加數據,使用Navicat打開sqlite3數據庫,執行sql語句
INSERT INTO "app01_depart" ("name", "tel", "user_id") VALUES ('總經理', 23456342, 1); INSERT INTO "app01_depart" ("name", "tel", "user_id") VALUES ('技術部', 34565422, 1); INSERT INTO "app01_depart" ("name", "tel", "user_id") VALUES ('運營部', 34344523, 2);INSERT INTO "app01_userinfo" ("title") VALUES ('xiao'); INSERT INTO "app01_userinfo" ("title") VALUES ('zhang'); View Code?
修改urls.py,增加路徑
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [url(r'^admin/', admin.site.urls),url(r'^index/', views.index), ] View Code?
修改views.py,增加視圖函數
from django.shortcuts import render,HttpResponse from app01 import models # Create your views here. def index(request):# 外鍵對象,通過get_field獲取fk_obj = models.Depart._meta.get_field("user")print(fk_obj,type(fk_obj))# .all表示獲取所有數據user_info_queryset = fk_obj.rel.model.objects.all()print(user_info_queryset)for i in user_info_queryset:print('row:',i) # 打印每一個行的數據return HttpResponse('...') View Code啟動django項目,訪問首頁
查看Pycharm控制臺輸出:
app01.Depart.user <class 'django.db.models.fields.related.ForeignKey'> <QuerySet [<UserInfo: xiao>, <UserInfo: zhang>]> row: xiao row: zhang注意:FK/M2M/O2O都是通過get_field來獲取的
?
六、組合搜索
展示頁面
務必下載github代碼:
https://github.com/987334176/luffy_stark/archive/v1.2.zip
因為下面的內容,都是這份代碼來修改的!
?
修改?stark-->templates-->stark-->changelist.html
{% extends 'stark/layout.html' %} {% load stark %}{% block css %}<style>.comb-search {padding: 5px 20px;}.comb-search .row .whole {width: 60px;float: left;}.comb-search .row .others {padding-left: 60px;}.comb-search .row a {display: inline-block;padding: 5px 8px;margin: 3px;border: 1px solid #d4d4d4; }.comb-search .row a {display: inline-block;padding: 5px 8px;margin: 3px;border: 1px solid #d4d4d4; }.comb-search a.active {color: #fff;background-color: #337ab7;border-color: #2e6da4; }</style> {% endblock %} {% block content %}<div>{#組合搜索#}<div class="comb-search"><div class="row"><div class="whole"><a href="#">全部</a></div><div class="others"><a href="#">條件1</a><a href="#">條件2</a><a href="#">條件3</a></div></div></div>{#添加按鈕#}{% if cl.add_btn %}<div style="margin: 5px 0;">{{ cl.add_btn }}</div>{% endif %}{#搜索框#}{% if cl.search_list %}<div style="float: right;"><form method="GET" class="form-inline"><div class="form-group"><input class="form-control" type="text" name="q" value="{{ cl.q }}" placeholder="關鍵字搜索"><button class="btn btn-primary" type="submit"><i class="fa fa-search" aria-hidden="true"></i></button></div></form></div>{% endif %}<form class="form-inline" method="post">{% csrf_token %}{#批量操作#}{% if cl.action_list %}<div class="form-group"><select name="action" class="form-control" style="min-width: 200px;"><option>請選擇功能</option>{% for item in cl.action_list %}<option value="{{ item.name }}">{{ item.text }}</option>{% endfor %}</select><input class="btn btn-primary" type="submit" value="執行"></div>{% endif %}{#使用table展示數據#}{% table cl %}{#分頁展示#}<nav aria-label="Page navigation"><ul class="pagination">{{ cl.page.page_html|safe }}</ul></nav></form></div>{% endblock %} View Code訪問url:?http://127.0.0.1:8000/stark/app01/depart/list/
效果如下:
?
修改 stark-->server-->stark.py,增加變量 list_filter,增加一個鉤子函數?get_list_filter
使用get_field獲取字段
import functools from django.conf.urls import url from django.shortcuts import HttpResponse,render,redirect from types import FunctionType from django.utils.safestring import mark_safe from django.urls import reverse from django import forms from django.db.models import Q from django.http import QueryDictclass ChangeList(object):"""封裝列表頁面需要的所有功能"""def __init__(self,config,queryset,q,search_list,page):### 處理搜索 ###self.q = q # 搜索條件self.search_list = search_list # 搜索字段self.page = page # 分頁# 配置參數self.config = config# 批量操作self.action_list = [{'name': func.__name__, 'text': func.text} for func in config.get_action_list()]# 添加按鈕self.add_btn = config.get_add_btn()# ORM執行結果self.queryset = queryset# 顯示的列self.list_display = config.get_list_display()class StarkConfig(object):def __init__(self,model_class,site):self.model_class = model_classself.site = site# 定義request變量,用于非視圖函數使用。# 在wrapper裝飾器中,對這個值重新賦值!self.request = None# url中的搜索條件,存在字典中。key為_filterself.back_condition_key = "_filter"def display_checkbox(self,row=None,header=False): # 顯示復選框if header:# 輸出中文return "選擇"# 注意:這里要寫row.pk,不能寫row.id。你不能保證每一個表的主鍵都是idreturn mark_safe("<input type='checkbox' name='pk' value='%s' />" % row.pk)def display_edit(self, row=None, header=False):if header:return "編輯"return mark_safe('<a href="%s"><i class="fa fa-edit" aria-hidden="true"></i></a></a>' % self.reverse_edit_url(row))def display_del(self, row=None, header=False):if header:return "刪除"return mark_safe('<a href="%s"><i class="fa fa-trash-o" aria-hidden="true"></i></a>' % self.reverse_del_url(row))def display_edit_del(self, row=None, header=False):if header:return "操作"tpl = """<a href="%s"><i class="fa fa-edit" aria-hidden="true"></i></a></a> |<a href="%s"><i class="fa fa-trash-o" aria-hidden="true"></i></a>""" % (self.reverse_edit_url(row), self.reverse_del_url(row),)return mark_safe(tpl)def multi_delete(self, request): # 批量刪除"""批量刪除的action:param request::return:"""pk_list = request.POST.getlist('pk')self.model_class.objects.filter(pk__in=pk_list).delete()# return HttpResponse('刪除成功') multi_delete.text = "批量刪除" # 添加自定義屬性textdef multi_init(self,request): # 批量初始化print('批量初始化')multi_init.text = "批量初始化" # 添加自定義屬性text order_by = [] # 需要排序的字段,由用戶自定義list_display = [] # 定義顯示的列,由用戶自定義model_form_class = None # form組件需要的model_classaction_list = [] # 批量操作方法# 搜索字段,如果是跨表字段,要按照ORM語法來search_list = []list_filter = [] # 組合搜索def get_order_by(self): # 獲取排序列表return self.order_bydef get_list_display(self): # 獲取顯示的列return self.list_displaydef get_add_btn(self): # 顯示添加按鈕return mark_safe('<a href="%s" class="btn btn-success">添加</a>' % self.reverse_add_url())def get_model_form_class(self):"""獲取ModelForm類:return:"""if self.model_form_class:return self.model_form_classclass AddModelForm(forms.ModelForm):class Meta:model = self.model_classfields = "__all__"return AddModelFormdef get_action_list(self): # 獲取批量操作方法val = [] # 空列表# 擴展列表的元素 val.extend(self.action_list)return valdef get_action_dict(self): # 獲取匹配操作字典val = {}for item in self.action_list:# 以方法名為keyval[item.__name__] = itemreturn valdef get_search_list(self): # 獲取搜索字段val = []val.extend(self.search_list)return valdef get_search_condition(self, request): # 根據關鍵字,組合ORM查詢語句search_list = self.get_search_list() # ['name','tel']q = request.GET.get('q', "") # 搜索條件con = Q()con.connector = "OR" # 以OR作為連接符if q: # 判斷條件不為空for field in search_list:# 合并條件進行查詢, __contains表示使用like查詢con.children.append(('%s__contains' % field, q))return search_list, q, condef get_list_filter(self): # 獲取組合搜索條件val = []val.extend(self.list_filter)return valdef changelist_view(self, request):"""所有URL查看列表頁面:param request::return:"""if request.method == 'POST':action_name = request.POST.get('action')action_dict = self.get_action_dict()if action_name not in action_dict:return HttpResponse('非法請求')response = getattr(self, action_name)(request)if response:return response### 處理搜索 ###search_list, q, con = self.get_search_condition(request)# ##### 處理分頁 #####from stark.utils.pagination import Pagination# 總條數total_count = self.model_class.objects.filter(con).count()# 復制GET參數query_params = request.GET.copy()# 允許編輯query_params._mutable = True# 使用分頁類Pagination,傳入參數。每頁顯示3條page = Pagination(request.GET.get('page'), total_count, request.path_info, query_params, per_page=3)# 根據排序列表進行排序,以及分頁功能queryset = self.model_class.objects.filter(con).order_by(*self.get_order_by())[page.start:page.end]cl = ChangeList(self, queryset, q, search_list, page)# ######## 組合搜索 ########## list_filter = ['name','user']list_filter = self.get_list_filter()for field in list_filter:# 如果field = "name" --> 查Depart所有數據# 如果field = "user" --> 查UserInfo所有數據_field = self.model_class._meta.get_field(field)print(_field,type(_field)) # 打印字段類型 context = {'cl': cl}# 注意:要傳入參數return render(request,'stark/changelist.html',context)def add_view(self, request):"""所有的添加頁面,都在此方法處理使用ModelForm實現:param request::return:"""# 添加數據,使用ModelFormAddModelForm = self.get_model_form_class()if request.method == "GET":form = AddModelForm()return render(request,'stark/change.html',{'form':form})form = AddModelForm(request.POST) # 接收POST數據if form.is_valid(): # 驗證數據form.save() # 自動保存數據# 反向生成url,跳轉到列表頁面return redirect(self.reverse_list_url())# 渲染頁面,此時會保存表單數據return render(request, 'stark/change.html', {'form': form})def change_view(self, request, pk):"""所有編輯頁面:param request::param pk::return:"""# 查看單條數據obj = self.model_class.objects.filter(pk=pk).first()if not obj:return HttpResponse('數據不存在')# 獲取model_form類ModelFormClass = self.get_model_form_class()if request.method == 'GET':# instance表示生成默認值form = ModelFormClass(instance=obj)# 渲染頁面,添加和修改可以共用一個一個模板文件return render(request, 'stark/change.html', {'form': form})# instance = obj 表示指定給誰做修改form = ModelFormClass(data=request.POST, instance=obj)if form.is_valid():form.save() # 修改數據# 跳轉到列表頁面return redirect(self.reverse_list_url())return render(request, 'stark/change.html', {'form': form})def delete_view(self, request, pk):"""所有刪除頁面:param request::param pk::return:"""if request.method == "GET":# cancel_url表示用戶點擊取消時,跳轉到列表頁面return render(request, 'stark/delete.html', {'cancel_url': self.reverse_list_url()})# 定位單條數據,并刪除!self.model_class.objects.filter(pk=pk).delete()return redirect(self.reverse_list_url())def wrapper(self, func):@functools.wraps(func)def inner(request, *args, **kwargs):self.request = requestreturn func(request, *args, **kwargs)return innerdef get_urls(self):info = self.model_class._meta.app_label, self.model_class._meta.model_nameurlpatterns = [url(r'^list/$', self.wrapper(self.changelist_view), name='%s_%s_changelist' % info),url(r'^add/$', self.wrapper(self.add_view), name='%s_%s_add' % info),url(r'^(?P<pk>\d+)/change/', self.wrapper(self.change_view), name='%s_%s_change' % info),url(r'^(?P<pk>\d+)/del/', self.wrapper(self.delete_view), name='%s_%s_del' % info),]extra = self.extra_url()if extra: # 判斷變量不為空# 擴展路由 urlpatterns.extend(extra)# print(urlpatterns)return urlpatternsdef extra_url(self): # 額外的路由,由調用者重構passdef reverse_list_url(self): # 反向生成訪問列表的urlapp_label = self.model_class._meta.app_labelmodel_name = self.model_class._meta.model_namenamespace = self.site.namespacename = '%s:%s_%s_changelist' % (namespace, app_label, model_name)list_url = reverse(name)# 獲取當前請求的_filter參數,也就是跳轉之前的搜索條件origin_condition = self.request.GET.get(self.back_condition_key)if not origin_condition: # 如果沒有獲取到return list_url # 返回列表頁面# 列表地址和搜索條件拼接list_url = "%s?%s" % (list_url, origin_condition,)return list_urldef reverse_add_url(self): # 反向生成添加urlapp_label = self.model_class._meta.app_labelmodel_name = self.model_class._meta.model_namenamespace = self.site.namespacename = '%s:%s_%s_add' % (namespace, app_label, model_name)add_url = reverse(name)if not self.request.GET: # 判斷get參數為空return add_url # 返回原url# request.GET的數據類型為QueryDict# 對QueryDict做urlencode編碼param_str = self.request.GET.urlencode() # 比如q=xiao&age=20# 允許對QueryDict做修改new_query_dict = QueryDict(mutable=True)# 添加鍵值對. _filter = param_strnew_query_dict[self.back_condition_key] = param_str# 添加url和搜索條件做拼接add_url = "%s?%s" % (add_url, new_query_dict.urlencode(),)# 返回最終urlreturn add_urldef reverse_edit_url(self, row): # 反向生成編輯行內容的urlapp_label = self.model_class._meta.app_label # app名model_name = self.model_class._meta.model_name # 表名namespace = self.site.namespace # 命名空間# 拼接字符串,這里為changename = '%s:%s_%s_change' % (namespace, app_label, model_name)# 反向生成url,傳入參數pk=row.pkedit_url = reverse(name, kwargs={'pk': row.pk})if not self.request.GET:return edit_urlparam_str = self.request.GET.urlencode()new_query_dict = QueryDict(mutable=True)new_query_dict[self.back_condition_key] = param_stredit_url = "%s?%s" % (edit_url, new_query_dict.urlencode(),)return edit_urldef reverse_del_url(self, row): # 反向生成刪除行內容的urlapp_label = self.model_class._meta.app_labelmodel_name = self.model_class._meta.model_namenamespace = self.site.namespace# 注意:這里為delname = '%s:%s_%s_del' % (namespace, app_label, model_name)del_url = reverse(name, kwargs={'pk': row.pk})if not self.request.GET:return del_urlparam_str = self.request.GET.urlencode()new_query_dict = QueryDict(mutable=True)new_query_dict[self.back_condition_key] = param_strdel_url = "%s?%s" % (del_url, new_query_dict.urlencode(),)return del_url@propertydef urls(self):return self.get_urls()class AdminSite(object):def __init__(self):self._registry = {}self.app_name = 'stark'self.namespace = 'stark'def register(self,model_class,stark_config=None):# not None的結果為Tureif not stark_config:# 也就是說,當其他應用調用register時,如果不指定stark_config參數# 那么必然執行下面這段代碼!# stark_config和StarkConfig是等值的!都能實例化stark_config = StarkConfig# 添加鍵值對,實例化類StarkConfig,傳入參數model_class# self指的是AdminSite類self._registry[model_class] = stark_config(model_class,self)# print(self._registry) # 打印字典"""{app01.models.UserInfo:StarkConfig(app01.models.UserInfo)app02.models.Role:RoleConfig(app02.models.Role)}"""# for k, v in self._registry.items():# print(k,v)def get_urls(self):urlpatterns = []for k, v in self._registry.items():# k=modes.UserInfo,v=StarkConfig(models.UserInfo), # 封裝:model_class=UserInfo,site=site對象# k=modes.Role,v=RoleConfig(models.Role) # 封裝:model_class=Role,site=site對象app_label = k._meta.app_labelmodel_name = k._meta.model_nameurlpatterns.append(url(r'^%s/%s/' % (app_label, model_name,), (v.urls, None, None)))return urlpatterns@propertydef urls(self):# 調用get_urls方法# self.app_name和self.namespace值是一樣的,都是starkreturn self.get_urls(), self.app_name, self.namespacesite = AdminSite() # 實例化類 View Code?
修改 app01-->stark.py,增加list_filter屬性
from stark.server.stark import site, StarkConfig from app01 import models from django import forms from django.shortcuts import render from django.conf.urls import urlclass UserInfoConfig(StarkConfig):list_display = ['id', 'username']class DepartModelForm(forms.ModelForm):class Meta:model = models.Departfields = "__all__"def clean_name(self): # 定義鉤子# print(self.cleaned_data['name'])return self.cleaned_data['name']class DepartConfig(StarkConfig):list_display = [StarkConfig.display_checkbox,'id','name', 'tel', 'user',StarkConfig.display_edit_del]# model_form_class = DepartModelForm# 批量操作action_list = [StarkConfig.multi_delete,StarkConfig.multi_init]# 搜索字段,如果是跨表字段,要按照ORM語法來search_list = ['name', 'tel', 'user__username']list_filter = ["name","user"] # 組合搜索# def get_add_btn(self): # 返回None,表示不顯示添加按鈕# pass# def changelist_view(self, request): # 重寫changelist_view方法# # 渲染自定義的列表頁面# return render(request,'stark/custom_list.html')# def get_urls(self): # 自定義路由# info = self.model_class._meta.app_label, self.model_class._meta.model_name# # urlpatterns = [# url(r'^list/$', self.changelist_view, name='%s_%s_changelist' % info),# ]# return urlpatterns site.register(models.UserInfo, UserInfoConfig) site.register(models.Depart, DepartConfig) View Code刷新頁面,查看Pycharm控制臺輸出:
app01.Depart.name <class 'django.db.models.fields.CharField'> app01.Depart.user <class 'django.db.models.fields.related.ForeignKey'>ForeignKey表示一對多
?
修改?stark-->server-->stark.py,導入模塊ForeignKey,判斷類型。如果是FK,就跨表查詢。
添加list_filter_rows列表,并傳給模板
import functools from django.conf.urls import url from django.shortcuts import HttpResponse,render,redirect from types import FunctionType from django.utils.safestring import mark_safe from django.urls import reverse from django import forms from django.db.models import Q from django.http import QueryDict from django.db.models.fields.related import ForeignKeyclass ChangeList(object):"""封裝列表頁面需要的所有功能"""def __init__(self,config,queryset,q,search_list,page):### 處理搜索 ###self.q = q # 搜索條件self.search_list = search_list # 搜索字段self.page = page # 分頁# 配置參數self.config = config# 批量操作self.action_list = [{'name': func.__name__, 'text': func.text} for func in config.get_action_list()]# 添加按鈕self.add_btn = config.get_add_btn()# ORM執行結果self.queryset = queryset# 顯示的列self.list_display = config.get_list_display()class StarkConfig(object):def __init__(self,model_class,site):self.model_class = model_classself.site = site# 定義request變量,用于非視圖函數使用。# 在wrapper裝飾器中,對這個值重新賦值!self.request = None# url中的搜索條件,存在字典中。key為_filterself.back_condition_key = "_filter"def display_checkbox(self,row=None,header=False): # 顯示復選框if header:# 輸出中文return "選擇"# 注意:這里要寫row.pk,不能寫row.id。你不能保證每一個表的主鍵都是idreturn mark_safe("<input type='checkbox' name='pk' value='%s' />" % row.pk)def display_edit(self, row=None, header=False):if header:return "編輯"return mark_safe('<a href="%s"><i class="fa fa-edit" aria-hidden="true"></i></a></a>' % self.reverse_edit_url(row))def display_del(self, row=None, header=False):if header:return "刪除"return mark_safe('<a href="%s"><i class="fa fa-trash-o" aria-hidden="true"></i></a>' % self.reverse_del_url(row))def display_edit_del(self, row=None, header=False):if header:return "操作"tpl = """<a href="%s"><i class="fa fa-edit" aria-hidden="true"></i></a></a> |<a href="%s"><i class="fa fa-trash-o" aria-hidden="true"></i></a>""" % (self.reverse_edit_url(row), self.reverse_del_url(row),)return mark_safe(tpl)def multi_delete(self, request): # 批量刪除"""批量刪除的action:param request::return:"""pk_list = request.POST.getlist('pk')self.model_class.objects.filter(pk__in=pk_list).delete()# return HttpResponse('刪除成功') multi_delete.text = "批量刪除" # 添加自定義屬性textdef multi_init(self,request): # 批量初始化print('批量初始化')multi_init.text = "批量初始化" # 添加自定義屬性text order_by = [] # 需要排序的字段,由用戶自定義list_display = [] # 定義顯示的列,由用戶自定義model_form_class = None # form組件需要的model_classaction_list = [] # 批量操作方法# 搜索字段,如果是跨表字段,要按照ORM語法來search_list = []list_filter = [] # 組合搜索def get_order_by(self): # 獲取排序列表return self.order_bydef get_list_display(self): # 獲取顯示的列return self.list_displaydef get_add_btn(self): # 顯示添加按鈕return mark_safe('<a href="%s" class="btn btn-success">添加</a>' % self.reverse_add_url())def get_model_form_class(self):"""獲取ModelForm類:return:"""if self.model_form_class:return self.model_form_classclass AddModelForm(forms.ModelForm):class Meta:model = self.model_classfields = "__all__"return AddModelFormdef get_action_list(self): # 獲取批量操作方法val = [] # 空列表# 擴展列表的元素 val.extend(self.action_list)return valdef get_action_dict(self): # 獲取匹配操作字典val = {}for item in self.action_list:# 以方法名為keyval[item.__name__] = itemreturn valdef get_search_list(self): # 獲取搜索字段val = []val.extend(self.search_list)return valdef get_search_condition(self, request): # 根據關鍵字,組合ORM查詢語句search_list = self.get_search_list() # ['name','tel']q = request.GET.get('q', "") # 搜索條件con = Q()con.connector = "OR" # 以OR作為連接符if q: # 判斷條件不為空for field in search_list:# 合并條件進行查詢, __contains表示使用like查詢con.children.append(('%s__contains' % field, q))return search_list, q, condef get_list_filter(self): # 獲取組合搜索條件val = []val.extend(self.list_filter)return valdef changelist_view(self, request):"""所有URL查看列表頁面:param request::return:"""if request.method == 'POST':action_name = request.POST.get('action')action_dict = self.get_action_dict()if action_name not in action_dict:return HttpResponse('非法請求')response = getattr(self, action_name)(request)if response:return response### 處理搜索 ###search_list, q, con = self.get_search_condition(request)# ##### 處理分頁 #####from stark.utils.pagination import Pagination# 總條數total_count = self.model_class.objects.filter(con).count()# 復制GET參數query_params = request.GET.copy()# 允許編輯query_params._mutable = True# 使用分頁類Pagination,傳入參數。每頁顯示3條page = Pagination(request.GET.get('page'), total_count, request.path_info, query_params, per_page=3)# 根據排序列表進行排序,以及分頁功能queryset = self.model_class.objects.filter(con).order_by(*self.get_order_by())[page.start:page.end]cl = ChangeList(self, queryset, q, search_list, page)# ######## 組合搜索 ########## list_filter = ['name','user']list_filter = self.get_list_filter()list_filter_rows = []for field in list_filter:# 如果field = "name" --> 查Depart所有數據# 如果field = "user" --> 查UserInfo所有數據_field = self.model_class._meta.get_field(field)# print(_field,type(_field)) # 打印字段類型if isinstance(_field,ForeignKey):row = _field.rel.model.objects.all()else:row = self.model_class.objects.all()list_filter_rows.append(row)context = {'cl': cl,'list_filter_rows':list_filter_rows}# 注意:要傳入參數return render(request,'stark/changelist.html',context)def add_view(self, request):"""所有的添加頁面,都在此方法處理使用ModelForm實現:param request::return:"""# 添加數據,使用ModelFormAddModelForm = self.get_model_form_class()if request.method == "GET":form = AddModelForm()return render(request,'stark/change.html',{'form':form})form = AddModelForm(request.POST) # 接收POST數據if form.is_valid(): # 驗證數據form.save() # 自動保存數據# 反向生成url,跳轉到列表頁面return redirect(self.reverse_list_url())# 渲染頁面,此時會保存表單數據return render(request, 'stark/change.html', {'form': form})def change_view(self, request, pk):"""所有編輯頁面:param request::param pk::return:"""# 查看單條數據obj = self.model_class.objects.filter(pk=pk).first()if not obj:return HttpResponse('數據不存在')# 獲取model_form類ModelFormClass = self.get_model_form_class()if request.method == 'GET':# instance表示生成默認值form = ModelFormClass(instance=obj)# 渲染頁面,添加和修改可以共用一個一個模板文件return render(request, 'stark/change.html', {'form': form})# instance = obj 表示指定給誰做修改form = ModelFormClass(data=request.POST, instance=obj)if form.is_valid():form.save() # 修改數據# 跳轉到列表頁面return redirect(self.reverse_list_url())return render(request, 'stark/change.html', {'form': form})def delete_view(self, request, pk):"""所有刪除頁面:param request::param pk::return:"""if request.method == "GET":# cancel_url表示用戶點擊取消時,跳轉到列表頁面return render(request, 'stark/delete.html', {'cancel_url': self.reverse_list_url()})# 定位單條數據,并刪除!self.model_class.objects.filter(pk=pk).delete()return redirect(self.reverse_list_url())def wrapper(self, func):@functools.wraps(func)def inner(request, *args, **kwargs):self.request = requestreturn func(request, *args, **kwargs)return innerdef get_urls(self):info = self.model_class._meta.app_label, self.model_class._meta.model_nameurlpatterns = [url(r'^list/$', self.wrapper(self.changelist_view), name='%s_%s_changelist' % info),url(r'^add/$', self.wrapper(self.add_view), name='%s_%s_add' % info),url(r'^(?P<pk>\d+)/change/', self.wrapper(self.change_view), name='%s_%s_change' % info),url(r'^(?P<pk>\d+)/del/', self.wrapper(self.delete_view), name='%s_%s_del' % info),]extra = self.extra_url()if extra: # 判斷變量不為空# 擴展路由 urlpatterns.extend(extra)# print(urlpatterns)return urlpatternsdef extra_url(self): # 額外的路由,由調用者重構passdef reverse_list_url(self): # 反向生成訪問列表的urlapp_label = self.model_class._meta.app_labelmodel_name = self.model_class._meta.model_namenamespace = self.site.namespacename = '%s:%s_%s_changelist' % (namespace, app_label, model_name)list_url = reverse(name)# 獲取當前請求的_filter參數,也就是跳轉之前的搜索條件origin_condition = self.request.GET.get(self.back_condition_key)if not origin_condition: # 如果沒有獲取到return list_url # 返回列表頁面# 列表地址和搜索條件拼接list_url = "%s?%s" % (list_url, origin_condition,)return list_urldef reverse_add_url(self): # 反向生成添加urlapp_label = self.model_class._meta.app_labelmodel_name = self.model_class._meta.model_namenamespace = self.site.namespacename = '%s:%s_%s_add' % (namespace, app_label, model_name)add_url = reverse(name)if not self.request.GET: # 判斷get參數為空return add_url # 返回原url# request.GET的數據類型為QueryDict# 對QueryDict做urlencode編碼param_str = self.request.GET.urlencode() # 比如q=xiao&age=20# 允許對QueryDict做修改new_query_dict = QueryDict(mutable=True)# 添加鍵值對. _filter = param_strnew_query_dict[self.back_condition_key] = param_str# 添加url和搜索條件做拼接add_url = "%s?%s" % (add_url, new_query_dict.urlencode(),)# 返回最終urlreturn add_urldef reverse_edit_url(self, row): # 反向生成編輯行內容的urlapp_label = self.model_class._meta.app_label # app名model_name = self.model_class._meta.model_name # 表名namespace = self.site.namespace # 命名空間# 拼接字符串,這里為changename = '%s:%s_%s_change' % (namespace, app_label, model_name)# 反向生成url,傳入參數pk=row.pkedit_url = reverse(name, kwargs={'pk': row.pk})if not self.request.GET:return edit_urlparam_str = self.request.GET.urlencode()new_query_dict = QueryDict(mutable=True)new_query_dict[self.back_condition_key] = param_stredit_url = "%s?%s" % (edit_url, new_query_dict.urlencode(),)return edit_urldef reverse_del_url(self, row): # 反向生成刪除行內容的urlapp_label = self.model_class._meta.app_labelmodel_name = self.model_class._meta.model_namenamespace = self.site.namespace# 注意:這里為delname = '%s:%s_%s_del' % (namespace, app_label, model_name)del_url = reverse(name, kwargs={'pk': row.pk})if not self.request.GET:return del_urlparam_str = self.request.GET.urlencode()new_query_dict = QueryDict(mutable=True)new_query_dict[self.back_condition_key] = param_strdel_url = "%s?%s" % (del_url, new_query_dict.urlencode(),)return del_url@propertydef urls(self):return self.get_urls()class AdminSite(object):def __init__(self):self._registry = {}self.app_name = 'stark'self.namespace = 'stark'def register(self,model_class,stark_config=None):# not None的結果為Tureif not stark_config:# 也就是說,當其他應用調用register時,如果不指定stark_config參數# 那么必然執行下面這段代碼!# stark_config和StarkConfig是等值的!都能實例化stark_config = StarkConfig# 添加鍵值對,實例化類StarkConfig,傳入參數model_class# self指的是AdminSite類self._registry[model_class] = stark_config(model_class,self)# print(self._registry) # 打印字典"""{app01.models.UserInfo:StarkConfig(app01.models.UserInfo)app02.models.Role:RoleConfig(app02.models.Role)}"""# for k, v in self._registry.items():# print(k,v)def get_urls(self):urlpatterns = []for k, v in self._registry.items():# k=modes.UserInfo,v=StarkConfig(models.UserInfo), # 封裝:model_class=UserInfo,site=site對象# k=modes.Role,v=RoleConfig(models.Role) # 封裝:model_class=Role,site=site對象app_label = k._meta.app_labelmodel_name = k._meta.model_nameurlpatterns.append(url(r'^%s/%s/' % (app_label, model_name,), (v.urls, None, None)))return urlpatterns@propertydef urls(self):# 調用get_urls方法# self.app_name和self.namespace值是一樣的,都是starkreturn self.get_urls(), self.app_name, self.namespacesite = AdminSite() # 實例化類 View Code修改?stark-->templates-->stark-->changelist.html,for循環list_filter_rows列表
{% extends 'stark/layout.html' %} {% load stark %}{% block css %}<style>.comb-search {padding: 5px 20px;}.comb-search .row .whole {width: 60px;float: left;}.comb-search .row .others {padding-left: 60px;}.comb-search .row a {display: inline-block;padding: 5px 8px;margin: 3px;border: 1px solid #d4d4d4; }.comb-search .row a {display: inline-block;padding: 5px 8px;margin: 3px;border: 1px solid #d4d4d4; }.comb-search a.active {color: #fff;background-color: #337ab7;border-color: #2e6da4; }</style> {% endblock %} {% block content %}<div>{#組合搜索#}<div class="comb-search">{% for row in list_filter_rows %}<div class="row"><div class="whole"><a href="#">全部</a></div><div class="others">{% for obj in row %}<a href="#">{{ obj }}</a>{% endfor %}</div></div>{% endfor %}</div>{#添加按鈕#}{% if cl.add_btn %}<div style="margin: 5px 0;">{{ cl.add_btn }}</div>{% endif %}{#搜索框#}{% if cl.search_list %}<div style="float: right;"><form method="GET" class="form-inline"><div class="form-group"><input class="form-control" type="text" name="q" value="{{ cl.q }}" placeholder="關鍵字搜索"><button class="btn btn-primary" type="submit"><i class="fa fa-search" aria-hidden="true"></i></button></div></form></div>{% endif %}<form class="form-inline" method="post">{% csrf_token %}{#批量操作#}{% if cl.action_list %}<div class="form-group"><select name="action" class="form-control" style="min-width: 200px;"><option>請選擇功能</option>{% for item in cl.action_list %}<option value="{{ item.name }}">{{ item.text }}</option>{% endfor %}</select><input class="btn btn-primary" type="submit" value="執行"></div>{% endif %}{#使用table展示數據#}{% table cl %}{#分頁展示#}<nav aria-label="Page navigation"><ul class="pagination">{{ cl.page.page_html|safe }}</ul></nav></form></div>{% endblock %} View Code刷新頁面,效果如下:
?
?
?
?
?
?
后臺搜索
?
關于后面的詳細步驟,沒有時間寫了。附上完整代碼:
鏈接:https://pan.baidu.com/s/1fLOGH_3G7hPTvCYKX84UdQ 密碼:m8rh
?
七、領域驅動設計(DDD)
什么是領域驅動設計(DDD)
2004年著名建模專家Eric Evans發表了他最具影響力的書籍:《Domain-Driven Design –Tackling Complexity in the Heart of Software》(中文譯名:領域驅動設計—軟件核心復雜性應對之道),書中提出了“領域驅動設計(簡稱 DDD)”的概念。
領域驅動設計事實上是針對OOAD的一個擴展和延伸,DDD基于面向對象分析與設計技術,對技術架構進行了分層規劃,同時對每個類進行了策略和類型的劃分。
領域模型是領域驅動的核心。采用DDD的設計思想,業務邏輯不再集中在幾個大型的類上,而是由大量相對小的領域對象(類)組成,這些類具備自己的狀態和行為,每個類是相對完整的獨立體,并與現實領域的業務對象映射。領域模型就是由這樣許多的細粒度的類組成。基于領域驅動的設計,保證了系統的可維護性、擴展性和復用性,在處理復雜業務邏輯方面有著先天的優勢。
領域驅動設計的特點
領域驅動的核心應用場景就是解決復雜業務的設計問題,其特點與這一核心主題息息相關:
?
舉例:商品價格
一個商品,有商品名(name),原價(price),折扣價(discount)。如何用類來表示呢?
常規類
class Goods(object):def __init__(self,name,price,discount):self.name = nameself.price = priceself.discount = discount如果要增加優惠券(滿減,立減,折扣),怎么辦?
領域驅動設計
class BaseCoupon(object):"""優惠券基礎類"""passclass Coupon1(BaseCoupon):"""滿減"""passclass Coupon2(BaseCoupon):"""立減"""passclass Coupon3(BaseCoupon):"""折扣"""passclass Price(object):"""商品價格"""def __init__(self,price,discount):self.price = priceself.discount = discountdef pay(self): # 交易價格passclass Goods(object):def __init__(self,name):self.name = name View Code重點就是建模
一般做3年開發,就可以領悟 領域驅動設計。具體還得看個人的領悟能力!
?
其他更多信息,請參考鏈接:
https://www.cnblogs.com/yihaha/p/3977496.html
?
關于python方面領域驅動設計的相關書籍,暫時還沒有。
主流的是JAVA,C#,PHP
?
領域驅動設計它是一種編程思想,重點就是建模!對于開發一個大型項目,尤為重要!
在python源代碼中,就利用這種思想。類中層層嵌套類!
對于個人編程能力的提升,可以看一下相關書籍!
?
未完待續...
轉載于:https://www.cnblogs.com/xiao987334176/p/9575783.html
總結
以上是生活随笔為你收集整理的python 全栈开发,Day116(可迭代对象,type创建动态类,偏函数,面向对象的封装,获取外键数据,组合搜索,领域驱动设计(DDD))...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 移植madplay到jz2440【学习笔
- 下一篇: vscode Python 运行环境配置