[翻译]编写你的首个Django app, part 3

原创
2015/02/15 18:02
阅读数 106

这篇指南将从Tutorial 2结束的地方继续讲解。我们将继续Web-poll应用,并将集中于创建公共接口——“views。”

哲学

一个view是你的Django应用中一种Web页面的“类型”,通常它服务于一个特定的功能,并具有一个特定的模板。比如,在一个博客应用中,你可能具有如下的views:

  • Blog 主页 – 显示最新的一些entries。
  • Entry “detail”页面 – 一个单独的entry的永久链接页面。
  • 基于年的打包页面 – 显示给定的某一年中以月区分的所有entries。
  • 基于月的打包页面 – 显示给定的月中以日区分的所有entries。
  • 基于日的打包页面 – 显示给定的某一天中所有的entries。
  • 评论行为 – 处理向一个给定的entry下发布的评论。

在我们的poll应用中,我们将具有如下的四个views:

  • Question “index”页面 – 显示最新的一些questions。
  • Question “detail”页面 – 显示一个question文字,没有results,但有一个投票的表单。
  • Question “results”页面 – 显示一个特定的question的results。
  • Vote action – 处理一个特定的question中的一个特定的选择的投票。

在Django中,web页面和其它的内容通过views传递。每个view由一个简单的Python函数表示(或方法,在基于类的views的case中)。Django将通过检查请求的URL来选择一个view(更精确的说,是URL中域名后面的部分)。

你可能遇到过像“ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B”这样的东西。你将会很高兴了解到Djanog允许我们使用比那优雅得多URL 模式

一个URL模式只是一个URL的通用的形式 - 比如:/newsarchive/<year>/<month>/

要从一个URL获取一个view,Django会使用被称为‘URLconfs’的东西。一个URLconf将URL模式(有一个正则表达式描述)映射到views。

这篇指南提供了使用URLconfs的基本的指导,而且你可以参考django.core.urlresolvers来了解更多的信息。

编写你的第一个view

让我们开始编写第一个view。打开文件polls/views.py并在其中放入如下的Python代码:

polls/views.py

from django.http import HttpResponse


def index(request):
    return HttpResponse("Hello, world. You're at the polls index.")

这可能是Django中最简单的view了。要调用这个view,我们需要把它映射为一个URL - 为此我们需要一个URLconf。

要在polls目录下创建一个URLconf,创建一个称为urls.py的文件。你的app目录现在看起来应该是这样的:

polls/
    __init__.py
    admin.py
    models.py
    tests.py
    urls.py
    views.py

polls/urls.py文件中包含如下的代码:

polls/urls.py
from django.conf.urls import patterns, url

from polls import views

urlpatterns = patterns('',
    url(r'^$', views.index, name='index'),
)

下一步是在polls.urls模块指出根URLconf。在mysite/urls.py插入一个include(),看起来像这样:

mysite/urls.py

from django.conf.urls import patterns, include, url
from django.contrib import admin

urlpatterns = patterns('',
    url(r'^polls/', include('polls.urls')),
    url(r'^admin/', include(admin.site.urls)),
)

与你看到的不一致?

如果你在urlpatterns定义的前面看到了admin.autodiscover(),你可能使用了一个与这份指南不匹配的Django本版。你需要切换到更老的指南或使用更新的Django版本。

你已经给URLconf连接了一个index view。在你的浏览器中进入http://localhost:8000/polls/,你应该看到了文本Hello, world. You’re at the polls index.”,这就是你在index view中定义的东西。

url()函数接受4个参数,两个是必须的:regexview,两个是可选的:kwargs和name。此刻,是时候重新回顾一下这些参数是什么了。


url()参数: regex

术语“regex”通常是“正则表达式”的所略形式,“正则表达式”是一种字符串的模式匹配的语法,或者在这个case中,是ur模式。。Django从地一个正则表达式开始,然后以它的方式向下便利列表,逐个比较正则表达式和请求的URL,直到它找到了一个匹配的。

注意,这些正则表达式不搜索GET和POST参数,或者域名。比如,在一个对http://www.example.com/myapp/的请求,URLconf将查找myapp/。在一个对http://www.example.com/myapp/?page=3的请求中,URLconf也是查找myapp/。

如果你想要了解更多关于正则表达式的东西,可以参考Wikipedia’s条目,及re模块的文档。O’Reilly出版的由Jeffrey Friedl所编写的著作“Mastering Regular Expressions”也很不错。然而,实践上,你不需要成为一个正则表达式的专家,你真的只需要知道如何捕捉简单的模式就可以了。实际上,复杂的正则表达式可能具有糟糕的查找性能,因此你可能不应该完全依赖于正则表达式。

最后,一点关于性能的说明:这些正则表达式会在URLconf模块第一次被加载起来时编译。他们超级快(查找没有上述那么复杂)。

url()参数: view

当Django找到了一个匹配的正则表达式,Django调用特定的view函数,并以一个HttpRequest对象作为第一个参数,以从正则表达式中“捕捉到的”任何值作为其他参数。如果regex使用了简单的captures,值被作为位置参数传递;如果它使用了命名captures,值被作为关键字参数传入。稍后我们将会给出一个关于这一点的例子。

url()参数: kwargs

任意的关键字参数可以以一个字典的形式传递给目标view。在这份指南中我们将不会使用Django的这个功能。

url()参数: name

命名你的URL,以使你能够在Django的其它地方,特别是模板中无疑议的引用它。这个强大的功能使你能够只改动一个单独的文件而完成对你的工程的url模式的全局的改动。

编写更多的views

现在,让我们给polls/views.py添加更多的views。这些views只有一点点不同,因为它们都接收一个参数:

polls/views.py

def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)

def results(request, question_id):
    response = "You're looking at the results of question %s."
    return HttpResponse(response % question_id)

def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)

通过添加如下的url()调用来将这些新views加进polls.urls:

polls/urls.py

from django.conf.urls import patterns, url

from polls import views

urlpatterns = patterns('',
    # ex: /polls/
    url(r'^$', views.index, name='index'),
    # ex: /polls/5/
    url(r'^(?P<question_id>\d+)/$', views.detail, name='detail'),
    # ex: /polls/5/results/
    url(r'^(?P<question_id>\d+)/results/$', views.results, name='results'),
    # ex: /polls/5/vote/
    url(r'^(?P<question_id>\d+)/vote/$', views.vote, name='vote'),
)

在你的浏览器中看一下,在“/polls/34/”。它将会执行detail()方法,并显示你在URL中提供的ID。也试试“/polls/34/results/”和“/polls/34/vote/” - 这些将显示占位符结果和投票页面。

当有人请求了一个你的Web站点的页面 - 比如,“/polls/34/”,Django将加载mysite.urls Python模块,它是由ROOT_URLCONF设置指定的。它找到名为urlpatterns的变量,并顺序遍历正则表达式。我们使用的include()函数只是简单地引用其它的URLconfs。注意include()函数的正则表达式不包含一个$(字符串结束匹配字符),而是一个尾斜杠。无论何时Django遇到了include(),它会砍掉那一时刻URL能匹配到的所有部分,并将剩余字符串发给包含的URLconf来做进一步的处理。

include()背后的思想是,使plug-and-play URL变得简单。由于polls是在它们自己的URLconf (polls/urls.py)里的,它们可能被放在“/polls/”下,或“/fun_polls/”下,或“/content/polls/”下,或任何其它路径下,app将依然可以工作。

下面是当一个用户进入系统中的“/polls/34/”时将发生的事情:

  • Django将在'^polls/'查找匹配。

  • 然后,Django将剥去匹配的字符串("polls/"),然后将剩余的字符 - "34/" - 发给‘polls.urls’ URLconf来做进一步的处理,这将匹配r'^(?P<question_id>\d+)/$'并导致一个对于如下的detail() view的调用:

detail(request=<HttpRequest object>, question_id='34')

question_id='34'的部分来自于(?P<question_id>\d+)。用元括号包住一个模式将“捕获”匹配那个模式的文字,并将它作为一个参数传递给view函数;?P<question_id>定义了标识匹配的模式所使用的名字;而\d+是一个匹配一串数字(比如,一个数字)的正则表达式

由于URL模式是正则表达式,因而真的没有限制你到底可以通过它们做什么。也不需要添加诸如.html这样的URL cruft - 除非你想加,你可以做一些像下面这样的事情:

(r'^polls/latest\.html$', 'polls.views.index'),
但是不要那样做。那很愚蠢。

写一些做实际事情的views

每一个view都负责完成两件事情中的一件:返回一个包含了所请求页面的内容的HttpResponse对象,或产生一个诸如Http404这样的异常。其他的就看你了。

你的view可以从一个数据库读取记录,也可以不那么做。它可以使用一个模板系统,比如Django的 - 或一个第三方的Python模板系统 - 或不使用。它可以产生一个PDF文件,输出XML,创建一个ZIP文件,任何你想要的,使用任何你喜欢的Python库。

Django想要的只是一个HttpResponse。或一个异常。

由于它很方便,让我们使用Django自己的数据库API,我们在Tutorial 1中说明过的。这是一个新index() view中的一个stab,它显示系统中最新的5个poll questions,由逗号分割,根据发布日期:

polls/views.py

from django.http import HttpResponse

from polls.models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    output = ', '.join([p.question_text for p in latest_question_list])
    return HttpResponse(output)

# Leave the rest of the views (detail, results, vote) unchanged

这里有一个问题:页面的设计是在view中硬编码的。如果你想修改页面的外观,你将不得不编辑这段Python代码。因此让我们使用Django的模板系统通过创建一个那个view可以使用的模板来将设计从Python代码中分离出来。

首先,在你的polls目录下创建一个称为templates的目录。Django将在那里寻找模板。

Django的TEMPLATE_LOADERS设置项包含一个callables的列表,其知道如何从不同来源导入模板。默认的callables中的一个是django.template.loaders.app_directories.Loader,它在INSTALLED_APPS中的每一个下查找“templates”子目录 - 这也就是Django如何知道要去查找polls templates的,即使我们没有修改TEMPLATE_DIRS,如我们在Tutorial 2中所做的那样。

组织模板

我们可以把我们的所有模板放在一起,在一个大的模板目录下,它将工作的相当好。然而,这个模板属于polls应用,因而,不像在前一篇指南中我们创建的admin 模板,我们将把这个放在应用的模板目录下(polls/templates),而不是项目的(templates)下面。我们将在reusable apps tutorial中更详细地讨论我们为什么要这么做。

在你刚刚创建的模板目录下,创建另一个名为polls的目录,并在其中创建一个称为index.html的文件。换句话说,你的模板应该在polls/templates/polls/index.html。由于app_directories模板加载器以上述的方式工作,你可以参考Django下的aspolls/index.html这个模板。

模板命名空间

现在我们也许能够把我们的模板直接放在polls/templates(而不是创建另一个polls子目录),但它实际是个坏主意。Django将选择它找到的第一个名字匹配的模板,如果你有一个模板具有相同的名字但在一个不同的应用下,Django将无法区分它们。我们需要为Django指定正确的那个,确保这一点的最简单的方法是将它们放在不同的命名空间。即,通过把那些模板放进另一个根据应用本身命名的目录内。

在那个模板中放入如下的代码:

polls/templates/polls/index.html
{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
        <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

现在让我们更新我们polls/views.py中的index view来使用模板:

polls/views.py

from django.http import HttpResponse
from django.template import RequestContext, loader

from polls.models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    template = loader.get_template('polls/index.html')
    context = RequestContext(request, {
        'latest_question_list': latest_question_list,
    })
    return HttpResponse(template.render(context))

那段代码加载称为polls/index.html的模板,并给它传递一个context。context是一个将模板变量名映射到Python对象的字典。

通过将你的浏览器指向“/polls/”,然后你应该看到一个包含了Tutorial 1中的“What’s up” question的无序列表。链接指向question的detail页面。

一个快捷方法: render()

加载一个模板,填充一个context并返回一个包含渲染了模板的结果的HttpResponse对象是一个非常常见的操作。Django提供了一个捷径。这是重写的完整的index() view:

polls/views.py

from django.shortcuts import render

from polls.models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    context = {'latest_question_list': latest_question_list}
    return render(request, 'polls/index.html', context)

注意,一旦我们在所有的views中都做到了这一点,我们就不再需要导入loaderRequestContextHttpResponse了(如果你有一些用于detail,results和vote的stub方法的话,你可能依然想要导入HttpResponse)。

render()函数接收request对象作为它的第一个参数,一个模板名字作为它的第二个参数,及一个字典作为它的可选的第三个参数。它返回一个由给定的context依据给定的模板渲染出来的HttpResponse对象

Raising a 404 error

现在,让我们来处理question detail view - 为一个给定的poll显示question文字的页面。这是该view:

polls/views.py

from django.http import Http404
from django.shortcuts import render

from polls.models import Question
# ...
def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id)
    except Question.DoesNotExist:
        raise Http404("Question does not exist")
    return render(request, 'polls/detail.html', {'question': question})

此处的概念是:如果一个具有所请求ID的question不存在,这个view产生Http404异常。

我们稍后讨论你到底可以向那个polls/detail.html模板中放些什么东西,但如果你想要快速的使上面的代码工作起来,可以先写一个包含如下内容的文件:

polls/templates/polls/detail.html

{{ question }}

将使你现在就可以开始了。

一个快捷方法: get_object_or_404()

对象不存在,而需要使用get()并产生一个Http404的做法非常常见。Django提供了一种快捷方法。这是该detail() view,经过了重写的:

polls/views.py

from django.shortcuts import get_object_or_404, render

from polls.models import Question
# ...
def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/detail.html', {'question': question})

get_object_or_404()接收一个Django model作为它的第一个参数,及一个任意数的关键字参数,其将会被传递给model的manager的get()函数。如果对象不存在,它产生Http404

哲学

为什么我们要使用一个辅助函数get_object_or_404(),而不是在更高层自动地捕捉ObjectDoesNotExist异常,或者使得model API产生Http404,而不是ObjectDoesNotExist

因为那将会把model层和view层合并起来。Django最重要的一个设计目标就是维护松耦合。一些可控的耦合在django.shortcuts模块中引入。

还有一个get_list_or_404()函数,它的工作方式像get_object_or_404()一样 - 除了使用filter()替代了get()。如果列表是空的则它会产生Http404

使用模板系统

回到我们的poll应用的detail() view。给定context变量question,下面是polls/detail.html模板可能的样子:

polls/templates/polls/detail.html

<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

模板系统使用dot-lookup语法来访问变量属性。在{{ question.question_text }}的例子中,Django首先执行一个question上的dictionary查询。如果失败,它会尝试执行一个属性查询——在这个例子中起作用的那个。如果属性查询失败了,它将会尝试一个list-index查询。

方法调用发生在{% for %}循环中:question.choice_set.all 被解释为Python代码question.choice_set.all(),其返回一个Choice对象的迭代器,它适合被用于{% for %}标签中。

请参考模板指南来了解更多关于模板的东西。

移除模板中硬编码的URLs

记住,当我们在polls/index.html模板中向一个question写入链接时,链接像下面这样被部分地硬编码了:

<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

这种硬编码,即紧耦合的方法的问题是,它使得在一个具有大量模板的项目中修改URLs变得很困难。然而,由于你在polls.urls模块中的url()函数中定义了命名参数,你可以过使用{% url %}模板标记消除一个对于定义在你的url配置中的特定的URL路径的依赖:

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

The way this works is by looking up the URL definition as specified in the polls.urls module. You can see exactly where the URL name of ‘detail’ is defined below:

...
# the 'name' value as called by the {% url %} template tag
url(r'^(?P<question_id>\d+)/$', views.detail, name='detail'),
...

如果你想要将polls detail view的URL改为其它什么东西,比如改为polls/specifics/12/,而不是在模板中完成,你可以在polls/urls.py中修改它:

...
# added the word 'specifics'
url(r'^specifics/(?P<question_id>\d+)/$', views.detail, name='detail'),
...

Namespacing URL names

这个tutorial项目只有一个app,polls但在实际的Django项目中,可能会有5个,10个,20个apps或更多。那Django是如何区分它们的URL名字的呢?比如,polls app有一个detail view,相同的项目中可能有一个用于一个blog的app。那么当使用{% url %}模板标记时,又如何让Django知道去创建哪一个app view呢?

答案是给你的根URLconf添加命名空间。在mysite/urls.py文件中,修改它以包含命名空间:

mysite/urls.py

from django.conf.urls import patterns, include, url
from django.contrib import admin

urlpatterns = patterns('',
    url(r'^polls/', include('polls.urls', namespace="polls")),
    url(r'^admin/', include(admin.site.urls)),
)

现在把你的polls/index.html模板从:

polls/templates/polls/index.html

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

修改为指向命名空间的detail view:

polls/templates/polls/index.html

<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

如果你已经熟悉了编写views,则可以阅读part 4 of this tutorial来学习关于简单的表单处理和泛型views了。

Done.

原文链接:

https://docs.djangoproject.com/en/1.7/intro/tutorial03/

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部