您的位置:首页 > 编程语言 > Go语言

Django 1.6 基于类的通用视图

2015-04-22 09:23 801 查看
最初 django 的视图都是用函数实现的,后来开发出一些通用视图函数,以取代某些常见的重复性代码。通用视图就像是一些封装好的处理器,使用它们的时候只须要给出特定的参数集即可,不必关心具体的实现。各种通用视图的参考参见:https://docs.djangoproject.com/en/1.6/ref/class-based-views/

然后函数式视图的缺点——难以扩展和自定义,开始显现出来。于是 1.3 起 django 开始用类视图来实现通用视图。较于函数,类能够更方便的实现继承和 mixins。但类视图并非要取代函数视图,这从现在 URLConf 仍然保留着函数式的调用方式便可以看出来。

因为 URLConf 仍然使用“给一个可调用对象传入 HttpRequest ,并期待其返回一个 HttpResponse”这样的逻辑,所以对于类视图,必须设计一个可调用的接口。这就是类视图的 as_view()
类方法。他接受 request,并实例化类视图,接着调用实例的 dispatch() 方法。这个方法会依据 request 的请求类型再去调用实例的对应同名方法,并把 request 传过去,如果没有对应的方法,就引发一个 HttpResponseNotAllowed 异常。(可以捕捉这个异常用以返回一个 404)值得注意的是,这个(比如 get)方法的返回值和普通的视图函数的返回值没有什么不同,这意味着,http shortcuts(render_to_response之类的)和
TemplateResponse 在类视图里也是有效的。

django 提供了一系列现成的类视图,他们都继承自一个 View 基类(django.views.generic.base.View)。在这个基类里实现了与 URLs 的接口(as_view)、请求方法匹配(dispatch)和一些其他的基本功能。比如 RedirectView 实现了一个简单的 HTTP 重定向,TemplateView 给 View 添加了一个渲染模板的功能。

简单用法:

最简单的通用视图用法就是直接在 URLConf 里创建他们。如果你只需要改几个类视图的属性的话,你可以直接把他们当做关键字参数传入视图的 as_view() 方法里。任何传入 as_view() 的参数都会覆盖类视图实例里的同名属性。

from django.conf.urls import patterns
from django.views.generic import TemplateView

urlpatterns = patterns('',
(r'^about/', TemplateView.as_view(template_name="about.html")),
)


子类化通用视图:

一种稍微高级点的使用通用视图的方法是子类化他们,并重写必要的属性(比如 template_name)和方法(比如 get_context_data)。举个栗子,一个 AboutView,他覆盖了 TemplateView 的模板属性和获取 Context 方法(这是使用 TemplateView 的一般做法):

# some_app/views.py
from django.views.generic import TemplateView

class AboutView(TemplateView):
template_name = "about.html"

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
#alter context
return context


对应的 URLConf:

# urls.py
from django.conf.urls import patterns
from some_app.views import AboutView

urlpatterns = patterns('',
(r'^about/', AboutView.as_view()),
)


这里的 get_context_data(self,**kwargs) 方法只接受关键字参数,是因为这个参数来自于 URLConf 的第三个参数(这例子里没用到),而这个参数就是一个关键字参数。

另外,如果想要对 context 做某些额外的处理,比如增加一些变量,那么就应该把代码写在调用了父类的 get_context_data 之后,return context 之前。

上面这个例子对通用视图的好处体现的还不明显(TemplateView 比 render_to_response 强不到哪去),通用视图的优势常常体现在对数据库的访问上,比如下面这个 显示对象列表的视图,完全省去了对数据库的操作:

# views.py
from django.views.generic import ListView
from books.models import Publisher

class PublisherList(ListView):
model = Publisher


# urls.py
from django.conf.urls import patterns, url
from books.views import PublisherList

urlpatterns = patterns('',
url(r'^publishers/$', PublisherList.as_view()),
)


ListView 会用到的模板:

{% extends "base.html" %}

{% block content %}
<h2>Publishers</h2>
<ul>
{% for publisher in object_list %}
<li>{{ publisher.name }}</li>
{% endfor %}
</ul>
{% endblock %}


template contexts 的变量名匹配:

上例中,在 publisher_list.html 模板中使用的 context 变量名为“object_list”。这是一个通用名,即 ListView 视图总会使用的一个名字,另外当你访问的是一个数据库模型的时候,比如上例中的 Publisher,视图还会自动用小写的模型名+_list 的格式命名一个相同的 context 去渲染模板,即“publisher_list”。所以上例中和其他对模型的访问都可以用这种格式的名字来编写模板。如果对此种匹配方式仍不放心,还可以在通用视图中显式指定 context
的名字,就像这样(建议总是显示指定):

# views.py
from django.views.generic import ListView
from books.models import Publisher

class PublisherList(ListView):
model = Publisher
context_object_name = 'my_favourite_publishers'


对数据模型进行筛选:

通过 model = Publisher 这样的方式可以指定通用视图使用的模型,而当想要对模型的 objects 管理器应用 filter 的时候,可以通过 query_set 变量来指定:

from django.views.generic import ListView
from books.models import Book

class BookList(ListView):
queryset = Book.objects.order_by('-publication_date')
context_object_name = 'book_list'


而如果 filter 的参数来自于 url 匹配出的子组,那么这个参数将被传入类视图的 self.args 和 self.kwargs 属性内。get_queryset() 方法也可以用来做逻辑性更强的定制:

# urls.py
from django.conf.urls import patterns
from books.views import PublisherBookList

urlpatterns = patterns('',
(r'^books/([\w-]+)/$', PublisherBookList.as_view()),
)


# views.py
from django.shortcuts import get_object_or_404
from django.views.generic import ListView
from books.models import Book, Publisher

class PublisherBookList(ListView):

template_name = 'books/books_by_publisher.html'

def get_queryset(self):
self.publisher = get_object_or_404(Publisher, name=self.args[0])
return Book.objects.filter(publisher=self.publisher)


实现模型访问时的额外小动作:

如果想要在对数据库进行访问之前(或后)实现一些额外的操作,那么可以通过封装 类视图的 get_object() 方法来实现。

例:假设 Author 模型有一个 DateTimeField 类型的字段,名为 last_accessed,用以保存最后一次被访问的时间。

# models.py
from django.db import models

class Author(models.Model):
salutation = models.CharField(max_length=10)
name = models.CharField(max_length=200)
email = models.EmailField()
headshot = models.ImageField(upload_to='author_headshots')
last_accessed = models.DateTimeField()


#URLConf

from django.conf.urls import patterns, url
from books.views import AuthorDetailView

urlpatterns = patterns('',
#...
url(r'^authors/(?P<pk>\d+)/$', AuthorDetailView.as_view(), name='author-detail'),
)


#View

from django.views.generic import DetailView
from django.utils import timezone
from books.models import Author

class AuthorDetailView(DetailView):

queryset = Author.objects.all()

def get_object(self):
# Call the superclass
object = super(AuthorDetailView, self).get_object()
# Record the last accessed date
object.last_accessed = timezone.now()
object.save()
# Return the object
return object


此处 URL 里的 pk 是 DetailView 视图默认使用的某个名字,详情可参考视图的细节页面。

对 HTTP require method 的支持:

为不同的请求写一个同名的方法就可以了,调用工作会有 dispatch() 方法来做。比如:

# urls.py
from django.conf.urls import patterns
from myapp.views import MyView

urlpatterns = patterns('',
(r'^about/', MyView.as_view()),
)


from django.http import HttpResponse
from django.views.generic import View

class MyView(View):
def get(self, request):
# <view logic>
return HttpResponse('result')


还可以定义一些 head() , post() 之类的方法。这种方式取代了视图函数里的 if 分支逻辑。

处理 Form:

基本 Forms:

一个简单的 contact form

# forms.py
from django import forms

class ContactForm(forms.Form):
name = forms.CharField()
message = forms.CharField(widget=forms.Textarea)

def send_email(self):
# 使用 self.cleaned_data 字典来发送一封邮件
pass


对应的视图可以使用 FormView 构建:

# views.py
from myapp.forms import ContactForm
from django.views.generic.edit import FormView

class ContactView(FormView):
template_name = 'contact.html'
form_class = ContactForm
success_url = '/thanks/'

def form_valid(self, form):
# 当有效的数据被 POST 进来以后,本方法就会被调用
# 本方法应当返回一个 HttpResponse.
form.send_email()
return super(ContactView, self).form_valid(form)


注意:

FormView 继承自 TemplateResponseMixin,所以 template_name 属性可用
form_valid() 的默认实现仅仅是将页面重定向至 success_url
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: