ホームページ  >  記事  >  バックエンド開発  >  Django ユニバーサル ビューの説明

Django ユニバーサル ビューの説明

黄舟
黄舟オリジナル
2017-01-17 14:13:571449ブラウズ

最悪の場合、
Web 開発は退屈で単調です。ここまで、Django がモデルおよびテンプレート レベルでの開発の単調さをどのように軽減するかを紹介しましたが、Web 開発ではビュー レベルでもこの​​退屈な作業が発生します。


Django の一般的なビューは、これらの苦痛を軽減します。ビュー開発で一般的に使用されるいくつかのコードとパターンが抽象化されているため、多くのコードを記述せずに、一般的に使用されるデータ ビューを迅速に作成できます。実際、これまでの章のビューの例はほとんどすべて、汎用ビューを使用して書き直すことができます。


第 8 章では、ビューをより「普遍的」にする方法を簡単に紹介します。振り返ってみると、一連のオブジェクトを表示したり、オブジェクトの内容を表示するコードを作成したりするなど、より一般的なタスクがいくつか見つかります。解決策は、追加のパラメータを URLConf に渡すことです。


Django の組み込みユニバーサル ビューは、次の機能を実現できます:


§ 一般的な単純なタスクを完了します: 別のページにリダイレクトし、指定されたテンプレートをレンダリングします。


§ 特定のオブジェクトのリストと詳細なコンテンツ ページを表示します。第 8 章で説明したevent_list ビューとentry_list ビューは、リスト ビューの例です。単一の
イベント ページは、いわゆる詳細コンテンツ ページです。


§ 現在の日付ベースのデータ年/月/日のアーカイブ ページ、関連詳細ページ、最新ページ。 Django
ウェブログ (http://www.djangoproject.com/weblog/) の年、月、日のアーカイブは、典型的なニュース新聞アーカイブと同様に、ユニバーサル ビュー アーキテクチャを使用しています。


§ 承認の有無にかかわらず、ユーザーがオブジェクトを作成、変更、削除できるようにします。


要約すると、これらのビューは、開発者の日常の開発における一般的なタスクに使いやすいインターフェイスを提供します。


ユニバーサル ビューの使用

ユニバーサル ビューを使用する方法は、URLconf ファイルに構成辞書を作成し、これらの辞書を URLconf タプルの 3 番目のメンバーとして使用することです。


たとえば、静的な About ページをレンダリングする URLconf は次のとおりです:

from django.conf.urls.defaults import *
from django.views.generic.simple import direct_to_template
urlpatterns = patterns('',
('^about/$', direct_to_template, {
'template': 'about.html'
})
)

一見すると、コーディングを必要としないビューなので、少し奇妙に思えるかもしれません。これは第 8 章の例とまったく同じです。direct_to_template ビューは、ビューのレンダリングに必要な関連情報をパラメーターから取得します。


汎用ビューは標準のビュー関数であるため、独自のビューで再利用できます。たとえば、about の例を拡張して、URL を /about/73eb76d467c85f6f564dc45534a3188a/ から静的にレンダリングされた about/73eb76d467c85f6f564dc45534a3188a.html にマップします。まず、URL 設定を新しいビュー関数に変更します:

from django.conf.urls.defaults import *
from django.views.generic.simple import direct_to_template
**from mysite.books.views import about_pages**
urlpatterns = patterns('',
('^about/$', direct_to_template, {
'template': 'about.html'
}),
**('^about/(w+)/$', about_pages),**
)

次に、about_pages ビューのコードを書きます:

from django.http import Http404
from django.template import TemplateDoesNotExist
from django.views.generic.simple import direct_to_template
def about_pages(request, page):
try:
return direct_to_template(request, template="about/%s.html" % page)
except TemplateDoesNotExist:
raise Http404()

ここでは、他の関数と同様に direct_to_template を使用します。 HttpResponse オブジェクトを返すので、単純にそれを返します。テンプレートが見つからない場合の処理​​が少し複雑になります。存在しないテンプレートによってサーバー エラーが発生することは望ましくないので、TempalteDoesNotExist 例外をキャッチして 404 エラーを返します。


ここにセキュリティ上の問題はありますか?


鋭い目の読者は、セキュリティ上の脆弱性の可能性に気づいたかもしれません。クライアントのブラウザからのデータを使用して、テンプレート名 (template="about/%s.html"%page) を直接構築します。一見すると、これは古典的なディレクトリ トラバーサル攻撃のように見えます (詳細については、第 19 章を参照)。真実は本当にそうなのでしょうか?


全然違います。はい、悪意のあるページ値によりディレクトリ スパンが発生する可能性がありますが、要求された URL からページがフェッチされたとしても、すべての値が受け入れられるわけではありません。これが URL 設定の鍵です。正規表現 /w+ を使用して URL からのページと一致させますが、/w は文字と数字のみを受け入れます。したがって、悪意のある文字 (この場合はドット . やスラッシュ / など) は URL の解析時に拒否され、ビュー関数にはまったく渡されません。

オブジェクトの汎用ビュー

direct_to_template は間違いなく非常に便利ですが、Django の汎用ビューはデータベースからデータをレンダリングするときに最も役立ちます。このアプリケーションは非常に一般的であるため、Django にはオブジェクトのリスト ビューと詳細ビューを簡単に生成できるように、多くの組み込みの汎用ビューが付属しています。


一般的なビューの 1 つであるオブジェクト リスト ビューを見てみましょう。例として第 5 章の Publisher を使用します:

class Publisher(models.Model):
name = models.CharField(maxlength=30)
address = models.CharField(maxlength=50)
city = models.CharField(maxlength=60)
state_province = models.CharField(maxlength=30)
country = models.CharField(maxlength=50)
website = models.URLField()
def __str__(self):
return self.name
class Meta:
ordering = ["-name"]
class Admin:
pass

すべての書籍のリスト ページを作成するには、次の URL 構成を使用します:

from django.conf.urls.defaults import *
from django.views.generic import list_detail
from mysite.books.models import Publisher
publisher_info = {
"queryset" : Publisher.objects.all(),
}
urlpatterns = patterns('',
(r'^publishers/$', list_detail.object_list, publisher_info)
)

記述する Python コードはこれですべてです。もちろん、テンプレートを記述する必要もあります。追加のパラメーター ディクショナリに template_name を含めることで、どのテンプレートを使用するかを object_list ビューに明確に指示できますが、テンプレートが指定されていない場合、Django はオブジェクトの名前を使用してテンプレートを推測します。この例では、派生テンプレート名は「books/publisher_list.html」になります。books の部分はこのモデルを定義するアプリの名前で、

publisher の部分はモデル名の小文字バージョンです。


这个模板将按照 context 
中包含的变量object_list来渲染,这个变量包含所有的书籍对象。一个非常简单的模板看起来象下面这样:

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

这就是所有要做的事。要使用通用视图酷酷的特性只需要修改参数字典并传递给通用视图函数。附录D是通用视图的完全参考资料;本章接下来的章节将讲到自定义和扩展通用视图的一些方法。


扩展通用视图

毫无疑问,使用通用视图可以充分加快开发速度。然而,在多数的工程中,也会出现通用视图不能满足需求的情况。实际上,刚接触Django的开发者最常见的问题就是怎样使用通用视图来处理更多的情况。


幸运的是,几乎每种情况都有相应的方法来简单的扩展通用视图来处理它。这时总是使用下面的这些方法。


制作友好的模板Context

你也许已经注意到范例中的出版商列表模板在变量object_list里保存所有的书籍。这个方法工作的很好,只是对编写模板的人不太友好:他们不得不去了解他们现在处理的数据是什么,比方说在这里是书籍。用象publisher_list这样的变量名会更好一点,这样变量的值看起来就很清楚了。


我们可以很容易的象下面这样修改template_object_name参数的名称:

publisher_info = {
"queryset" : Publisher.objects.all(),
**"template_object_name" : "publisher",**
}
urlpatterns = patterns(&#39;&#39;,
(r&#39;^publishers/$&#39;, list_detail.object_list, publisher_info)
)

使用有用的template_object_name总是个好想法。你的设计模板的合作伙伴会感谢你的。


添加额外的Context

你常常需要呈现比通用视图提供的更多的额外信息。例如,考虑一下在每个出版商页面实现所有其他出版商列表。object_detail通用视图提供了出版商到context,但是看起来没有办法在模板中获取所有出版商列表。


这是解决方法:所有的通用视图都有一个额外的可选参数extra_context。这个参数是一个字典数据类型,包含要添加到模板的context中的额外的对象。所以要提供所有的出版商明细给视图,我们就用这样的info字典:

publisher_info = {
"queryset" : Publisher.objects.all(),
"template_object_name" : "publisher",
**"extra_context" : {"book_list" : Book.objects.all()}**
}

这样就把一个{{book_list}}变量放到模板的context中。这个方法可以用来传递任意数据到通用视图模板中去,非常方便。


不过,这里有一个很隐蔽的BUG,不知道你发现了没有?


我们现在来看一下,extra_context里包含数据库查询的问题。因为在这个例子中,我们把Publisher.objects.all()放在URLconf中,它只会执行一次(当URLconf第一次加载的时候)。当你添加或删除出版商,你会发现在重启Web服务器之前,通用视图不会反映出这些修改的(有关QuerySet何时被缓存和赋值的更多信息请参考附录C中“缓存与查询集”一节)。


备注


这个问题不适用于通用视图的queryset参数。因为Django知道有些特别的 QuerySet永远不能被缓存,通用视图在渲染前都做了缓存清除工作。


解决这个问题的办法是在extra_context中用一个回调(callback)来代替使用一个变量。任何可以调用的对象(例如一个函数)在传递给extra_context后都会在每次视图渲染前执行(而不是只执行一次)。你可以象这样定义一个函数:

def get_books():
return Book.objects.all()
publisher_info = {
"queryset" : Publisher.objects.all(),
"template_object_name" : "publisher",
"extra_context" : **{"book_list" : get_books}**
}

或者你可以使用另一个不是那么清晰但是很简短的方法,事实上Publisher.objects.all本身就是可以调用的:

publisher_info = {
"queryset" : Publisher.objects.all(),
"template_object_name" : "publisher",
"extra_context" : **{"book_list" : Book.objects.all}**
}

注意Book.objects.all后面没有括号;这表示这是一个函数的引用,并没有真调用它(通用视图将会在渲染时调用它)。


显示对象的子集

现在让我们来仔细看看这个queryset。大多数通用视图有一个queryset参数,这个参数告诉视图要显示对象的集合(有关QuerySet的解释请看第五章的
“选择对象”章节,详细资料请参看附录C)。


举一个简单的例子,我们打算对书籍列表按出版日期排序,最近的排在最前:

book_info = {
"queryset" : Book.objects.all().order_by("-publication_date"),
}
urlpatterns = patterns(&#39;&#39;,
(r&#39;^publishers/$&#39;, list_detail.object_list, publisher_info),
**(r&#39;^books/$&#39;, list_detail.object_list, book_info),**
)

这是一个相当简单的例子,但是很说明问题。当然,你通常还想做比重新排序更多的事。如果你想要呈现某个特定出版商出版的所有书籍列表,你可以使用同样的技术:

**apress_books = {**
**"queryset": Book.objects.filter(publisher__name="Apress Publishing"),**
**"template_name" : "books/apress_list.html"**
**}**
urlpatterns = patterns(&#39;&#39;,
(r&#39;^publishers/$&#39;, list_detail.object_list, publisher_info),
**(r&#39;^books/apress/$&#39;, list_detail.object_list, apress_books),**
)

注意在使用一个过滤的queryset的同时,我们还使用一个自定义的模板名称。如果我们不这么做,通用视图就会用以前的模板,这可能不是我们想要的结果。


同样要注意的是这并不是一个处理出版商相关书籍的最好方法。如果我们想要添加另一个出版商页面,我们就得在URL配置中写URL配置,如果有很多的出版商,这个方法就不能接受了。在接下来的章节我们将来解决这个问题。


备注


如果你在请求/books/apres/时出现404错误,请检查以确保你的数据库中出版商中有名为Apress
Publishing的记录。通用视图有一个allow_empty参数可以用来处理这个情况,详情请看附录D。


用函数包装来处理复杂的数据过滤

另一个常见的需求是按URL里的关键字来过滤数据对象。在前面我们用在URL配置中硬编码出版商名称的方法来做这个,但是我们想要用一个视图就能显示某个出版商的所有书籍该怎么办呢?我们可以通过对object_list通用视图进行包装来避免写一大堆的手工代码。按惯例,我们先从写URL配置开始:

urlpatterns = patterns(&#39;&#39;,
(r&#39;^publishers/$&#39;, list_detail.object_list, publisher_info),
**(r&#39;^books/(w+)/$&#39;, books_by_publisher),**
)

接下来,我们写books_by_publisher这个视图:(上面的代码中正则表达式有误,在 ‘w’前要加反斜杠)

from django.http import Http404
from django.views.generic import list_detail
from mysite.books.models import Book, Publisher
def books_by_publisher(request, name):
# Look up the publisher (and raise a 404 if it can&#39;t be found).
try:
publisher = Publisher.objects.get(name__iexact=name)
except Publisher.DoesNotExist:
raise Http404
# Use the object_list view for the heavy lifting.
return list_detail.object_list(
request,
queryset = Book.objects.filter(publisher=publisher),
template_name = "books/books_by_publisher.html",
template_object_name = "books",
extra_context = {"publisher" : publisher}
)

这是因为通用视图就是Python函数。和其他的视图函数一样,通用视图也是接受一些参数并返回HttpResponse对象。因此,通过包装通用视图函数可以做更多的事。


注意


注意到在前面这个例子中我们在extra_context传递了当前出版商这个参数。这在包装时通常是一个好注意;它让模板知道当前显示内容的上一层对象。


处理额外工作

我们再来看看最后一个常用模式:在调用通用视图前后做些额外工作。


想象一下我们在Author对象里有一个last_accessed字段,我们用这个字段来更正对author的最近访问时间。当然通用视图object_detail并不能处理这个问题,我们可以很容易的写一个自定义的视图来更新这个字段。


首先,我们需要在URL配置里设置指向到新的自定义视图:

from mysite.books.views import author_detail
urlpatterns = patterns(&#39;&#39;,
#...
**(r&#39;^authors/(?P<author_id>d+)/$&#39;, author_detail),**
)

接下来写包装函数:

import datetime
from mysite.books.models import Author
from django.views.generic import list_detail
from django.shortcuts import get_object_or_404
def author_detail(request, author_id):
# Look up the Author (and raise a 404 if she&#39;s not found)
author = get_object_or_404(Author, pk=author_id)
# Record the last accessed date
author.last_accessed = datetime.datetime.now()
author.save()
# Show the detail page
return list_detail.object_detail(
request,
queryset = Author.objects.all(),
object_id = author_id,
)

注意


除非你添加last_accessed字段到你的Author模型并创建books/author_detail.html模板,否则这段代码不能真正工作。


我们可以用同样的方法修改通用视图的返回值。如果我们想要提供一个供下载用的纯文本版本的author列表,我们可以用下面这个视图:

def author_list_plaintext(request):
response = list_detail.object_list(
request,
queryset = Author.objects.all(),
mimetype = "text/plain",
template_name = "books/author_list.txt"
)
response["Content-Disposition"] = "attachment; filename=authors.txt"
return response

这个方法之所以工作是因为通用视图返回的HttpResponse对象可以象一个字典一样的设置HTTP的头部。随便说一下,这个Content-Disposition的含义是告诉浏览器下载并保存这个页面,而不是在浏览器中显示它。

以上就是Django通用视图讲解的内容,更多相关内容请关注PHP中文网(www.php.cn)!


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。