為什麼感覺django裡面class based view很難呢?
代碼明明就在哪裡,可是就是不知道該怎麼用,感覺跟沒有代碼一樣,甚至更糟
拋磚引玉。
我覺得要理解django的class-based-view(以下簡稱cbv),首先要明白django引入cbv的目的是什麼。在django1.3之前,generic view也就是所謂的通用視圖,使用的是function-based-view(fbv),亦即基於函數的視圖。有人認為fbv比cbv更pythonic,竊以為不然。python的一大重要的特性就是面向對象。而cbv更能體現python的面向對象。cbv是通過class的方式來實現視圖方法的。class相對於function,更能利用多態的特定,因此更容易從宏觀層面上將項目內的比較通用的功能抽象出來。關於多態,不多解釋,有興趣的同學自己Google。總之可以理解為一個東西具有多種形態(的特性)。cbv的實現原理通過看django的源碼就很容易明白,大體就是由url路由到這個cbv之後,通過cbv內部的dispatch方法進行分發,將get請求分發給cbv.get方法處理,將post請求分發給cbv.post方法處理,其他方法類似。怎麼利用多態呢?cbv里引入了mixin的概念。Mixin就是寫好了的一些基礎類,然後通過不同的Mixin組合成為最終想要的類。
所以,理解cbv的基礎是,理解Mixin。
我們以1.5為例簡單講解一下Mixin。
在python-path/Lib/site-packages/django/view/generic文件夾下,包含了django自帶的幾個基於類的通用視圖。
base.py:
ContextMixin:
提供get_context_data方法,給cbv提供context_data
View:
cbv的基類,提供視圖分發等功能
TemplateResponseMixin:
提供渲染模板等功能
TemplateView(TemplateResponseMixin, ContextMixin, View):
從類的構造上就可以看出,這個類是由TemplateResponseMixin,ContextMixin,View三個類共同繼承而來的,所以同時具有這三個類的特定,因此,這個類完整的提供了一個cbv應該具有的所有動作(除了處理數據)。
RedirectView(View): 這是View的一個子類,實現的是重定向的功能。
base中已經提供了構成cbv最最基礎的幾個Mixin,以及cbv的基類View。
以下django又提供了detail,list,edit,dates四個模塊,這四個模塊分別用來處理detail數據(比如顯示日誌的某一篇的明細信息),list(比如顯示某user的所有日誌列表),edit(比如為用戶提供新增日誌和修改日誌的功能),dates(比如顯示2014年10月的日誌)。想一下,從數據維度上講,默認的django cbv提供了按照數據維度處理的兩個不同的cbv,分別是detail和list。detail顯示一個數據對象,list顯示數據列表。
下面先分析detail.py:
SimpleObjectMixin(ContextMixin):
這是ContextMixin的一個子類,提供最基礎的取回單個對象的功能。
BaseDetailView(SimpleObjectMixin, View):
提供顯示單個對象的功能。
SimpleObjectTemplateResponseMixin(TemplateResponseMixin):
這是對TemplateResponseMixin的再次封裝,為了實現單個對象的模板顯示。
DetailView(SimpleObjectTemplateResponseMixin, BaseDetailView):
這就是完整的detail view了。
從以上類的繼承上就可以大致猜出,detail模塊中的相關cbv其實是對base中提供的mixin的再度繼承。從而實現更精細複雜的功能。
所以剩下幾個模塊題主完全可以自己分析了。
所以分析完了各個模塊提供的功能就完了嗎?如果到這裡止步,那麼還是不了解cbv的好處。上文說過,cbv的一大好處就是多態。因此可以把通用的功能抽象出來做成mixin給其他cbv用。
比如,想實現restful API。最簡單的,想實現返回json數據。寫一個mixin就好了。
class JSONResponseMixin(object):
"""JSON mixin"""
def render_to_response(self, context):
return self.get_json_response(self.convert_context_to_json(context))
def get_json_response(self, content, **httpresponse_kwargs):
return HttpResponse(content,
content_type=application/json,
**httpresponse_kwargs)
def convert_context_to_json(self, context):
return json.dumps(context)
怎麼用呢?
class CheckRemindUtilView(JSONResponseMixin, ListView):
"""
Check if there is reminder need to be reminded.
This view should be called every minute.
"""
def get_queryset(self):
start = timezone.now()
end = start + datetime.timedelta(minutes=1)
return Reminder.objects.filter(next_t__gte=start,
next_t__lte=end,
is_valid=True)
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
if (self.get_paginate_by(self.object_list) is not None
and hasattr(self.object_list, exists)):
is_empty = not self.object_list.exists()
else:
is_empty = len(self.object_list) == 0
if is_empty:
ret = {code: 42, msg: empty}
else:
for object_ in self.object_list:
code = exec_remind(object_)
object_.previous_t = object_.next_t
update_reminder(object_)
ret = {code: code, msg: reminded.}
return self.render_to_response(ret)
再從另一個方向舉個栗子。比如需要對日誌進行用戶過濾,用戶私有的日誌只能用戶自己看到,其他人看不到。那麼只需要寫一個PrivateObjectMixin,然後其他DetailView,ListView繼承這個就好了。
class PrivateObjectMixin(object):
Filter private object for request.user
def filte_private(self, queryset):
Filte private object for authenticated user.
ordering = getattr(self, ordering, -date_created)
if not hasattr(self, request):
return queryset
if not hasattr(self.request, user):
return queryset
if self.request.user.is_authenticated():
queryset = queryset.filter(Q(is_valid=True), Q(is_private=True)
Q(user__id=self.request.user.id) |
Q(is_private=False))
else:
queryset = queryset.filter(is_valid=True, is_private=False)
try:
result = queryset.order_by(ordering)
except FieldError:
# The model doesnot have an `ordering` field.
return queryset
return result
class NoteListView(PrivateObjectMixin, BaseNoteListView):
Show note list.
def get_queryset(self):
Get notes.
queryset = Note.objects.all()
return self.filte_private(queryset)
上面這兩個例子只是簡單的應用而已,完全可以藉助多態實現更複雜的cbv。
以下是建議部分:
1,建議翻閱django cbv的源碼,自己畫個圖了解cbv的實現原理,繼承流程。
2,自己寫幾個簡單的cbv。
私貨部分:
基於django(django-rest-framework)的個人站:今人不見古時月
django的class-based-view的確設計的不優美,太複雜
充斥著mixin,super,層級結構複雜
而且缺乏很多實際中需求量很大的視圖邏輯,例如CreatePreviewView, UpdatePreviewView,django-formtools中的preview設計的也很糟糕謝謝分享,之前寫項目的時候自己在想django 如何使用cbv,無意中發現django本身有支持,很贊!!自己動手封裝了底層的class,現在我們大部分的項目結構都是按照我之前寫的逐步完善的,個人感覺cbv比fbv好用些,在大項目中cbv的優勢自己去感受:)
推薦閱讀: