Django 官网最新 Tutorial 渣翻 - Part 3

上一节: Django 官网最新 Tutorial 渣翻 - Part 2

手写第一个Django应用, 第二部分

紧接着Tutorial 2 ,我们继续开发投票这个web应用,并将注意力集中在创建对外访问的“视图”界面上。

概览

视图(view)是Django应用中的一“类”网页,它通常有一个特定的函数以及一个特定的模板。例如,在博客应用中,可能有以下视图:

  • 博客首页 —— 显示最新发表的文章链接。
  • 博客“详细”页面 —— 单篇文章的详细页面。
  • 基于年份的归档页面 —— 显示某给定年份里所有月份发表过的博客。
  • 基于月份的归档页面 —— 显示在给定月份中发表过所有文章。
  • 基于日期的归档页面 —— 显示在给定日期中发表过的所有文章链接。
  • 评论 —— 评论某博客

在我们的投票应用中,将有以下四个视图:

  • Question首页 —— 显示最新发布的几个Question。
  • Question“详细”页面 —— 显示单个Question的具体内容,有一个投票的表单,但没有投票结果。
  • Question“结果”页面 —— 显示某Question的投票结果。
  • 投票功能 —— 可对Question中某个Choice的进行投票。

在Django中,网页的页面和其他内容都是由视图来传递的.每个视图都是由一个简单的Python函数(或者是基于类的视图的方法)。Django通过检查请求的URL(准确地说,是URL里域名之后的那部分)来选择使用哪个视图。

平日你上网时,可能会遇到像 “ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B”这样优美的URL。 你将会愉快地了解到,Django允许我们使用更加优雅的URL模式。

URL模式(URL pattern)就是一个URL的通用形式 —— 例如: /newsarchive/<year>/<month>/.

Django使用叫做‘URLconfs’的配置来为URL匹配视图。 一个URLconf负责将URL模式匹配到视图。

本教程有URLconfs的基本使用方法,你可以在 URL dispatcher看到更详细的信息 。


编写更多的视图

现在让我们给polls/views.py添加一些更多的视图。这些视图和之前的略有不同,因为它们另带了一个参数:

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)

通过下面的path() 调用将这些新的视图和polls.urls模块关联起来:

polls/urls.py

from django.urls import path

from . import views

urlpatterns = [
    # ex: /polls/
    path('', views.index, name='index'),
    # ex: /polls/5/
    path('<int:question_id>/', views.detail, name='detail'),
    # ex: /polls/5/results/
    path('<int:question_id>/results/', views.results, name='results'),
    # ex: /polls/5/vote/
    path('<int:question_id>/vote/', views.vote, name='vote'),
]

看看你的浏览器,输入“/polls/34/”, 它将运行detail()方法并显示你在URL中提供的ID。 再试一下“/polls/34/results/”和“/polls/34/vote/” —— 它们将显示出对应的结果界面和投票界面。

当有人请求你的网站的一个页面时 —— 比如“/polls/34/”,Django 将加载 python 模块 mysite.urls, 因为设置文件中的ROOT_URLCONF 指定了他, 他会找变量名为urlpatterns并按顺序遍历他, 当找到匹配的'polls/'后, 它会截断匹配到的字符串("polls/"), 然后发送剩余的字符串 --- "34/" 给 ‘polls.urls’ 这个URLconf作进一步处理. 这里他仅匹配到了 '<int:question_id>/' , 这样一来他就会像这样一样调用 detail():

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

question_id=34部分来自<int:question_id>, 使用尖括号"捕获"的URL, 并将他(尖括号中的内容)作为关键字参数传入视图函数中, question_id>, 部分的内容当作匹配的标识, <int来决定怎么匹配.

我们不需要添加那些不像url的东西, 如 .html - 除非你想这么做, 这种情况下你可以这样弄:

path('polls/latest.html', views.index),

不过这真是一种**(文明你我他)的行为: )


写点有意义的视图

每个视图都负责一两件事, 返回一个包含整个请求页面内容的HttpResponse对象, 或者抛出一个像 Http404的异常, 这取决于你.

你的视图可以是从数据库读取记录, 或不读取数据库, 你能用Django自带的模板系统, 或第三方的模板系统, 甚至你也可以不用模板, 你还可以动态的生成一个PDF文件, 输出XML文件, 创建一个ZIP文件,使用你想用的Python 库生成任何你想要的。

Django只要求返回一个HttpResponse.或抛出异常.

让我们来用用Tutorial 2学到的数据库API,它是非常方便的。下面是一个新的index()视图,它列出了最近的5个投票Question记录,并用逗号隔开,以发布日期排序:

polls/views.py

from django.http import HttpResponse

from .models import Question


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

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

这里有一个问题:页面的设计被硬编码在视图中。 如果你想更改页面的外观,就得编辑这段Python代码。 因此,让我们使用Django的模板系统,通过创建一个视图能够调用的模板,将页面的html代码从Python中分离出来。

首先,在你的polls目录下创建一个叫做 templates的目录。Django将在这里查找模板。

你项目配置文件中的 TEMPLATES 决定了Django如何加载和渲染模板。默认配置下,Django模板引擎在 APP_DIRS中的设置为True。Django模板引擎会在INSTALLED_APPS里的各个APP目录下查找名为templates的子目录。

在你刚刚创建的templates目录中,创建另外一个目录polls,并在其中创建一个文件index.html。也就是说,你的模板应该位于 polls/templates/polls/index.html。由于app_directories 模板加载器按照上面描述的方式工作,在Django中你可以简单地用polls/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 %}

Now let’s update our index view in polls/views.py to use the template:
现在我们用刚刚的模板来更新polls/views.py的视图函数:

polls/views.py

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

from .models import Question


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

那段代码加载了 polls/index.html模板,并传递了一个context对象,context是一个字典, 将模板的变量和python对象一一对应。

浏览器访问“/polls”,你看到一个列表,包含了我们在Tutorial 2创建的 “What’s up” question,这个链接指向了Question的详细页.

一种快捷方式: render()

加载模板、填充一个context 然后返回一个含有模板渲染结果的HttpResponse对象是非常频繁的。Django为此提供一个快捷方式。下面是重写后的index()视图:

polls/views.py

from .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)

注意,一旦我们在所有的视图上都应用这个快捷函数,我们将不再需要导入loaderHttpResponse(如果你没有改变先前的detail、results和 vote方法,你将需要在导入中保留HttpResponse )。

render()函数将请求对象(request)作为它的第一个参数,模板的名字作为它的第二个参数,一个字典作为它可选的第三个参数。 它返回一个 HttpResponse 对象,含有用给定的context 渲染后的模板。


抛出一个404错误

现在让我们来处理question detail视图——显示某question内容的页面,下面是该视图:

polls/views.py

from .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})

新概念:这个视图抛出了Http404 异常,如果请求的question ID不存在的情况下。

稍后我们将讨论polls/detail.html模板可以写点什么。但是如果你想快速让上面的例子工作,如下就可以:

polls/templates/polls/detail.html

{{ question }}

快捷方法: get_object_or_404

当用 get()时,如果对象不存在抛出 Http404异常是很常用的。Django提供了一个快捷方式,重写后的detail()视图:

polls/views.py

from .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模型作为它的第一个参数,任意数量关键词参数,它将传递给作为模型管理器的 get() 函数,如果对象不存在,它就引发一个 Http404 异常。

哲学
为什么我们要用一个辅助函数 get_object_or_404() 而不是在上层捕获ObjectDoesNotExist异常,或者让模型的API引发 Http404而不是ObjectDoesNotExist
因为那样会将模型层与视图层耦合。Django 最重要的设计目标就是保持松耦合。一些可控的耦合在django.shortcuts有介绍。

还有一个 get_list_or_404()函数, 原理和get_object_or_404()一样——差别在与它用filter() 而不是 get(). 当列表为空时,它抛出 Http404 异常。

使用模板系统

回到我们投票应用的detail()视图。 根据context 变量question,下面是polls/detail.html模板可能的样子:

polls/templates/polls/detail.html

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

模板系统使用点号查找语法来访问变量的属性。在例子 {{ question.question_text }}中, Django首先对question对象做字典查询。如果失败,Django会接着尝试按属性查询 —— 在这个例子中,属性查询会成功。如果属性查询也失败,Django将尝试列表索引查询。

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

查看 template guide 了解更多模板信息.


去除模板中的硬编码

请记住,当我们在polls/index.html 模板中编写一个指向Question的链接时,链接中一部分是硬编码的:

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

这种紧耦合的硬编码有一个问题,就是如果我们想在模板众多的项目中修改URLs,将会变得非常困难。 但是,如果你在polls.urls模块的 path() 函数中定义了name 参数,你可以通过使用 {% url %}模板标签来移除对你的URL配置中定义的特定的URL的依赖:

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

它的工作原理是在 polls.urls 模块里查找指定的URL。你可以看到名为‘detail’的URL的准确定义:

...
#  {% url %} 模板Tag调用了'name' 的值
path('<int:question_id>/', views.detail, name='detail'),
...

如果你想把polls应用中detail视图的URL改成其它样子比如 polls/specifics/12/ ,就可以不必在该模板(或者多个模板)中修改它,只需要修改 polls/urls.py

...
# added the word 'specifics'
path('specifics/<int:question_id>/', views.detail, name='detail'),
...

URL命名空间

教程中的这个项目只有一个应用polls。在真实的Django项目中,可能会有五个、十个、二十个或者更多的应用。 Django如何区分它们URL的名字呢? 例如,polls应用具有一个detail 视图,相同项目中的博客应用可能也有这样一个视图。当使用模板标签{% url %} 时,人们该如何做才能使得Django知道为一个URL创建哪个应用的视图?

答案是在你的主URLconf下添加命名空间。 在 polls/urls.py文件中,增加一个app_name的变量作为命名空间:

polls/urls.py

from . import views

app_name = 'polls'
urlpatterns = [
    path('', views.index, name='index'),
    path('<int:question_id>/', views.detail, name='detail'),
    path('<int:question_id>/results/', views.results, name='results'),
    path('<int:question_id>/vote/', views.vote, name='vote'),
]

现在修改polls/index.html模板:

polls/templates/polls/index.html

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

当你对你写的视图感到满意后,请阅读 part 4 of this tutorial来了解简单的表单处理和通用视图。

下一节: Django 官网最新 Tutorial 渣翻 - Part 3

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容