《老鸟python 系列》视频上线了,全网稀缺资源,涵盖python人工智能教程,爬虫教程,web教程,数据分析教程以及界面库和服务器教程,以及各个方向的主流实用项目,手把手带你从零开始进阶高手之路!点击 链接 查看详情

第三个视图:动态URL

阅读:475521101    分享到

在我们的 current_datetime 视图范例中,尽管内容是动态的,但是 URL( /time/ )是静态的。在大多数动态 web 应用程序,URL 通常都包含有相关的参数。举个例子,一家在线书店会为每一本书提供一个 URL,如:/books/243/、/books/81196/。

让我们创建第三个视图来显示当前时间和加上时间偏差量的时间,设计是这样的: /time/plus/1/ 显示当前时间 +1 个小时的页面 /time/plus/2/ 显示当前时间 +2 个小时的页面 /time/plus/3/ 显示当前时间 +3 个小时的页面,以此类推。

新手可能会考虑写不同的视图函数来处理每个时间偏差量,URL 配置看起来就象这样:

urlpatterns = [
    path('admin/', admin.site.urls),
    re_path('^time/$', current_datetime),
    re_path('^time/plus/1/$', one_hour_ahead),
    re_path('^time/plus/2/$', two_hours_ahead),
    re_path('^time/plus/3/$', three_hours_ahead),
    re_path('^time/plus/4/$', four_hours_ahead),
]

很明显,这样处理是不太妥当的。不但有很多冗余的视图函数,而且整个应用也被限制了只支持预先定义好的时间段,2 小时,3 小时,或者 4 小时。如果哪天我们要实现 5 小时,我们就不得不再单独创建新的视图函数和配置URL,既重复又混乱。我们需要在这里做一点抽象,提取一些共同的东西出来。

关于漂亮 URL 的一点建议

如果你有其它 web 平台的开发经验(如PHP或Java),你可能会想:嘿!让我们用查询字符串参数吧!就像 /time/plus?hours=3 里面的小时应该在查询字符串中被参数 hours 指定(问号后面的是参数)。

可以 在 Django 里也这样做 (如果你真的想要这样做,我们稍后会告诉你怎么做),但是 Django 的一个核心理念就是 URL 必须看起来漂亮。URL /time/plus/3/ 更加清晰,更简单,也更有可读性,可以很容易的大声念出来,因为它是纯文本,没有查询字符串那么复杂。 漂亮的URL就像是高质量的Web应用的一个标志。

Django 的 URL 配置系统可以使你很容易的设置漂亮的 URL,而尽量不要考虑它的 反面

那么,我们如何设计程序来处理任意数量的时差?答案是:使用通配符(wildcard URLpatterns)。正如我们之前提到过,一个 URL 模式就是一个正则表达式。因此,这里可以使用 d+ 来匹配 1 个以上的数字。

urlpatterns = [
    # ...
    re_path(r'^time/plus/\d+/$', hours_ahead),
    # ...
]

这里使用 # … 来表示省略了其它可能存在的 URL 模式定义。(见上)

这个 URL 模式将匹配类似 /time/plus/2/ , /time/plus/25/ ,甚至 /time/plus/100000000000/ 的任何 URL。更进一步,让我们把它限制在最大允许 99 个小时,这样我们就只允许一个或两个数字,正则表达式的语法就是 \d{1,2} :

re_path(r'^time/plus/\d{1,2}/$', hours_ahead),

备注

在建造 Web 应用的时候,尽可能多考虑可能的数据输入是很重要的,然后决定哪些我们可以接受。在这里我们就设置了 99 个小时的时间段限制。

另外一个重点,正则表达式字符串的开头字母 “r”。它告诉 Python 这是个原始字符串,不需要处理里面的反斜杠(转义字符)。在普通 Python 字符串中,反斜杠用于特殊字符的转义。比如 n 转义成一个换行符。当你用 r 把它标示为一个原始字符串后,Python 不再视其中的反斜杠为转义字符。也就是说,“n” 是两个字符串:“” 和 “n”。由于反斜杠在 Python 代码和正则表达式中有冲突,因此建议你在 Python 定义正则表达式时都使用原始字符串。从现在开始,本教程所有 URL 模式都用原始字符串。

现在我们已经设计了一个带通配符的 URL,我们需要一个方法把它传递到视图函数里去,这样 我们只用一个视图函数就可以处理所有的时间段了。我们使用圆括号把参数在 URL 模式里标识出来。在这个例子中,我们想要把这些数字作为参数,用圆括号把 \d{1,2} 包围起来:

re_path(r'^time/plus/(\d{1,2})/$', hours_ahead),

如果你熟悉正则表达式,那么你应该已经了解,正则表达式也是用圆括号来从文本里 提取 数据的。

最终的 URLconf 包含上面两个视图,如:

from django.contrib import admin
from django.urls import path
from django.urls import re_path
from mysite.views import hours_ahead

urlpatterns = [
    path('admin/', admin.site.urls),
    re_path(r'^time/plus/(\d{1,2})/$', hours_ahead),
]

现在开始写 hours_ahead 视图。

编码次序

这个例子中,我们先写了 URLpattern ,然后是视图,但是在前面的例子中,我们先写了视图,然后是 URLpattern。哪一种方式比较好?

嗯,怎么说呢,每个开发者是不一样的。

如果你是喜欢从总体上来把握事物(注: 或译为“大局观”)类型的人,你应该会想在项目开始的时候就写下所有的URL配置。

如果你从更像是一个自底向上的开发者,你可能更喜欢先写视图,然后把它们挂接到 URL 上。这同样是可以的。

最后,取决与你喜欢哪种技术,两种方法都是可以的。(见上)

hours_ahead 和我们以前写的 current_datetime 很象,关键的区别在于:它多了一个额外参数,时间差。以下是 view 代码:

from django.http import Http404, HttpResponse
import datetime

def hours_ahead(request, offset):
    try:
        offset = int(offset)
    except ValueError:
        raise Http404()
    dt = datetime.datetime.now() + datetime.timedelta(hours=offset)
    html = "<html><body>In %s hour(s), it will be %s.</body></html>" % (offset, dt)
    return HttpResponse(html)

让我们逐行分析一下代码:

视图函数, hoursahead , 有 两个 参数: requestoffset . (见上)
request 是一个 HttpRequest 对象, 就像在 current_datetime 中一样. 再说一次好了: 每一个视图 总是_ 以一个 HttpRequest 对象作为 它的第一个参数。 (见上)offset 是从匹配的URL里提取出来的。 例如:如果请求URL是/time/plus/3/,那么offset将会是3;如果请求URL是/time/plus/21/,那么 offset 将会是 21。请注意:捕获值永远都是字符串(string)类型,而不会是整数(integer)类型,即使这个字符串全由数字构成(如:“21”)。(从技术上来说,捕获值总是 Unicode objects,而不是简单的 Python 字节串,但目前不需要担心这些差别。)在这里我们命名变量为 offset ,你也可以任意命名它,只要符合 Python 的语法。 变量名是无关紧要的,重要的是它的位置,它是这个函数的第二个参数 (在 request 的后面)。你还可以使用关键字来定义它,而不是用位置。
我们在这个函数中要做的第一件事情就是在 offset 上调用 int()。 这会把这个字符串值转换为整数。请留意:如果你在一个不能转换成整数类型的值上调用 int(),Python 将抛出一个 ValueError 异常。如:int(‘foo’)。在这个例子中,如果我们遇到 ValueError 异常,我们将转为抛出 django.http.Http404 异常——正如你想象的那样:最终显示 404 页面(提示信息:页面不存在)。机灵的读者可能会问:我们在 URL 模式中用正则表达式 (d{1,2}) 约束它,仅接受数字怎么样?这样无论如何,offset 都是由数字构成的。答案是:我们不会这么做,因为 URLpattern 提供的是“适度但有用”级别的输入校验。万一这个视图函数被其它方式调用,我们仍需自行检查 ValueError。实践证明,在实现视图函数时,不臆测参数值的做法是比较好的。松散耦合,还记得么?下一行,计算当前日期/时间,然后加上适当的小时数。在 current_datetime 视图中,我们已经见过 datetime.datetime.now()。这里新的概念是执行日期/时间的算术操作。我们需要创建一个 datetime.timedelta 对象和增加一个 datetime.datetime 对象。结果保存在变量 dt 中。这一行还说明了,我们为什么在 offset 上调用 int()——datetime.timedelta 函数要求 hours 参数必须为整数类型。这行和前面的那行的的一个微小差别就是,它使用带有两个值的 Python 的格式化字符串功能,而不仅仅是一个值。因此,在字符串中有两个 %s 符号和一个以进行插入的值的元组:(offset, dt)。最终,返回一个 HTML 的 HttpResponse。如今,这种方式已经过时了。

在完成视图函数和 URL 配置编写后,启动 Django 开发服务器,用浏览器访问 http://127.0.0.1:8000/time/plus/3/ 来确认它工作正常。然后是 http://127.0.0.1:8000/time/plus/5/ 。再然后是 http://127.0.0.1:8000/time/plus/24/。最后,访问 http://127.0.0.1:8000/time/plus/100/,如下是我一个案例图:

来检验 URL 配置里设置的模式是否只接受一个或两个数字;Django 会显示一个 Page not found error 页面, 和以前看到的 404 错误一样。 访问URL http://127.0.0.1:8000/time/plus/ (没有 定义时间差) 也会抛出 404 错误。

Django 漂亮的出错页面

花几分钟时间欣赏一下我们写好的 Web 应用程序,然后我们再来搞点小破坏。我们故意在 views.py 文件中引入一项 Python 错误,注释掉 hours_ahead 视图中的 offset = int(offset) 一行。

def hours_ahead(request, offset):
    # try:
    #     offset = int(offset)
    # except ValueError:
    #     raise Http404()
    dt = datetime.datetime.now() + datetime.timedelta(hours=offset)
    html = "<html><body>In %s hour(s), it will be %s.</body></html>" % (offset, dt)
    return HttpResponse(html)

启动开发服务器,然后访问 /time/plus/3/。你会看到一个包含大量信息的出错页,最上面 的一条 TypeError 信息是: "unsupported type for timedelta hours component: unicode"

怎么回事呢?是的,datetime.timedelta 函数要求 hours 参数必须为整型,而我们注释掉了将 offset 转为整型的代码。这样导致 datetime.timedelta 弹出 TypeError 异常。

这个例子是为了展示 Django 的出错页面。我们来花些时间看一看这个出错页,了解一下其中给出了哪些信息。

以下是值得注意的一些要点:

在页面顶部,你可以得到关键的异常信息:异常数据类型、异常的参数 (如本例中的 "unsupported type")、在哪个文件中引发了异常、出错的行号等等。在关键异常信息下方,该页面显示了对该异常的完整 Python 追踪信息。这类似于你在 Python 命令行解释器中获得的追溯信息,只不过后者更具交互性。 对栈中的每一帧,Django 均显示了其文件名、函数或方法名、行号及该行源代码。点击该行代码 (以深灰色显示),你可以看到出错行的前后几行,从而得知相关上下文情况。点击栈中的任何一帧的“Local vars”可以看到一个所有局部变量的列表,以及在出错那一帧时它们的值。这些调试信息相当有用。注意“Traceback”下面的“Switch to copy-and-paste view”文字。 点击这些字,追溯会 切换另一个视图,它让你很容易地复制和粘贴这些内容。当你想同其他人分享这些异常 追溯以获得技术支持时(比如在 Django 的 IRC 聊天室或邮件列表中),可以使用它。你按一下下面的“Share this traceback on a public Web site”按钮,它将会完成这项工作。点击它以传回追溯信息至 http://www.dpaste.com/,在那里你可以得到一个单独的 URL 并与其他人分享你的追溯信息。接下来的“Request information”部分包含了有关产生错误的 Web 请求的大量信息: GET 和 POST、cookie 值、元数据(象 CGI 头)。在后面章节里给出了 request的对象的完整参考。Request 信息的下面,“Settings”列出了 Django 使用的具体配置信息。(我们已经提及过 ROOT_URLCONF,接下来我们将向你展示各式的 Django 设置。)

Django 的出错页某些情况下有能力显示更多的信息,比如模板语法错误。我们讨论 Django 模板系统时再说它们。现在,取消 offset = int(offset) 这行的注释,让它重新正常工作。

不知道你是不是那种使用小心放置的 print 语句来帮助调试的程序员?你其实可以用 Django 出错页来做这些,而不用 print 语句。在你视图的任何位置,临时插入一个 assert False 来触发出错页。然后,你就可以看到局部变量和程序语句了。这里有个使用 hours_ahead 视图的例子:

def hours_ahead(request, offset):
    try:
        offset = int(offset)
    except ValueError:
        raise Http404()
    dt = datetime.datetime.now() + datetime.timedelta(hours=offset)
    assert False
    html = "<html><body>In %s hour(s), it will be %s.</body></html>" % (offset, dt)
    return HttpResponse(html)

最后,很显然这些信息很多是敏感的,它暴露了你 Python 代码的内部结构以及 Django 配置,在 Internet 上公开这信息是很愚蠢的。不怀好意的人会尝试使用它攻击你的 Web 应用程序,做些下流之事。因此,Django 出错信息仅在 debug 模式下才会显现。我们稍后说明如何禁用 debug 模式。现在,你只要知道 Django 服务器在你开启它时默认运行在 debug 模式就行了。(听起来很熟悉?页面没有发现错误,如前所述,工作正常。)

下一章

目前为止,我们已经写好了视图函数和硬编码的 HTML。在演示核心概念时,我们所作的是为了保持简单。但是在现实世界中,这差不多总是个坏主意。

幸运的是,Django 内建有一个简单有强大的模板处理引擎来让你分离两种工作:下一章,我们将学习模板引擎。


如果以上内容对您有帮助,请老板用微信扫一下赞赏码,赞赏后加微信号 birdpython 领取免费视频。


登录后评论