美文网首页Python 学习笔记
Django视图:动态内容

Django视图:动态内容

作者: 大爷的二舅 | 来源:发表于2018-01-23 22:25 被阅读75次

    第二个视图:动态内容
    “Hello world”视图在演示Django工作原理的基础上很有启发意义,但它不是一个动态网页的例子,因为页面的内容总是相同的。 每当你浏览/hello/,你会看到同样的事情; 它可能是一个静态的HTML文件。

    对于我们的第二个视图,让我们创建一些更动态的东西 - 一个显示当前日期和时间的网页。 这是一个不错且简单的下一步,因为它不涉及数据库或任何用户输入 - 只是服务器内部时钟的输出。 它比“Hello world”更令人兴奋,但它将展示一些新的概念。

    这个新的视图需要做两件事情:计算当前的日期和时间,并返回一个包含该值的HttpResponse。 如果您有使用Python的经验,那么您知道Python包含用于计算日期的日期时间模块。 下面是使用Python交互式解释器的演示:

    C:\Users\nigel>python
    Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 17:54:52) [MSC v.1900 32 bit (Intel)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import datetime
    >>> now = datetime.datetime.now()
    >>> now
    datetime.datetime(2017, 5, 20, 14, 32, 25, 412665)
    >>> print (now)
    2017-05-20 14:32:25.412665
    >>>
    

    这很简单,而且与Django无关。 这只是Python代码。 (我想强调一下,你应该知道什么代码是“只是Python”,而不是Django特有的代码。当你学习Django时,我希望你能够将自己的知识应用到其他Python项目中 一定使用Django。)

    为了使Django视图显示当前的日期和时间,我们只需要将这个datetime.datetime.now()语句关联到一个视图并返回一个HttpResponse。 以下是更新的views.py的样子:

    from django.http import HttpResponse
    import datetime
    
    def hello(request):
        return HttpResponse("Hello world")
    
    def current_datetime(request):
        now = datetime.datetime.now()
        html = "<html><body>It is now %s.</body></html>" % now        
        return HttpResponse(html)
    

    让我们逐步了解我们对views.py所做的更改以适应current_datetime视图。

    • 我们在顶部倒入了一个日期时间的模块,所以我们可以计算日期。
    • 新的current_datetime函数以datetime.datetime对象的形式计算当前日期和时间,并将其作为本地变量进行存储。
    • 视图中的第二行代码使用Python的“格式字符串”功能构建HTML响应。 字符串中的%s是一个占位符,字符串后的百分号表示“用前面的字符串中的%s替换现在的变量的值”。now变量在技术上是一个datetime.datetime对象,而不是一个字符串 ,但%s格式字符将其转换为字符串表示形式,类似于“2017-05-20 14:32:25.412665”。 这将显示一个HTML字符串,如“现在是2017-05-20 14:32:25.412665”。
    • 最后,视图返回一个包含生成响应的HttpResponse对象 - 就像我们在hello中所做的一样。

    在将其添加到views.py之后,将URLpattern添加到urls.py来告诉Django哪个URL应该处理这个视图。 像/time/会有意义的东西:

    from django.conf.urls import url
    from django.contrib import admin
    
    from mysite.views import hello, current_datetime
    
    urlpatterns = [
       url(r'^admin/', admin.site.urls),
       url(r'^hello/$', hello),
       url(r'^time/$', current_datetime),
    ]
    

    我们在这里做了两处修改。 首先,我们在顶部导入current_datetime函数。 其次,更重要的是,我们添加了一个将URL/time/映射到新视图的URL模式。 掌握这个? 随着视图编写完成和URLconf更新,重启开发服务器,并在浏览器中访问http://127.0.0.1:8000/time/。 你应该看到当前的日期和时间。

    URLconf和松耦合

    现在是突出URLconfs和Django背后的关键理念的一个好时机:松散耦合的原理。 简而言之,松耦合是一种软件开发方法,它重视使各部分互换。 如果两段代码是松散耦合的,那么对其中一个代码的改变将对另一个代码产生很小的影响。

    在实践中,Django的URLconf就是这个原理的一个很好的例子。 在Django Web应用程序中,它们所调用的URL定义和视图函数是松散耦合的,即决定给定函数的URL以及函数本身的实现位于两个独立的位置。

    例如,考虑我们的current_datetime视图。 如果我们想要改变应用程序的URL,也就是说将其从/time/移动到/current-time/,我们可以快速改变URLconf,而不必担心视图本身。 同样,如果我们想改变视图函数 - 以某种方式改变其逻辑 - 我们可以做到这一点,而不会影响函数绑定到的URL。 此外,如果我们想要在多个URL中公开当前的日期功能,我们可以轻松地通过编辑URLconf来处理,而不必修改视图代码。

    在这个例子中,我们的current_datetime在两个URL中可用。 这是一个人为的例子,但是这个技巧可以派上用场:

    urlpatterns = [
          url(r'^admin/', admin.site.urls),
          url(r'^hello/$', hello),
          url(r'^time/$', current_datetime),
          url(r'^another-time-page/$', current_datetime),
    ]
    

    URLconf和视图是松耦合的行动。 在本书中,我将继续使用例子来指出这个重要哲学。

    您的第三个视图:动态网址

    在我们的current_datetime视图中,页面的内容(当前日期/时间)是动态的,但URL(/ time /)是静态的。 但是,在大多数动态Web应用程序中,URL包含影响页面输出的参数。 例如,在线书店可能会为每本书提供自己的URL,如/ books/243/和/books/81196/。

    我们来创建第三个视图,将当前日期和时间偏移显示一定的小时数。 我们的目标是制作一个网站,使页面/time/plus/1/显示date/time在未来一小时,页面/time/plus/2/显示date/time在未来两小时,页面/time/plus/3/显示未来三小时的date/time,等等。

    一个新手可能会想为每个小时偏移量编写一个单独的视图函数,这可能会导致如下的URLconf:

    urlpatterns = [
        url(r'^time/$', current_datetime),
        url(r'^time/plus/1/$', one_hour_ahead),
        url(r'^time/plus/2/$', two_hours_ahead),
        url(r'^time/plus/3/$', three_hours_ahead),
    ]
    

    显然,这个思路是有缺陷的。

    这不仅会导致冗余视图功能,而且应用程序基本上仅限于支持预定的小时范围 一个,两个或三个小时。

    如果我们决定创建一个显示未来四小时的页面,那么我们必须为此创建一个单独的视图和URLconf行,从而进一步实现重复。

    那么如何设计我们的应用程序来处理任意的小时偏移呢? 关键是使用通配符的URL模式。 正如我前面提到的,一个URL模式是一个正则表达式; 因此,我们可以使用正则表达式模式\d+匹配一个或多个数字:

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

    (我使用的#...暗示可能有其他的URL模式已经从这个例子修剪。)这个新的URL模式将匹配任何网址,如time/plus/2/,time/plus/25 / ,甚至time/plus/100000000000 /。 来想一想,让我们来限制它,以便允许的最大偏移是合理的。

    在这个例子中,我们将设置最多99个小时,只允许一个或两个数字的数字,并且用正则表达式语法翻译成\d{1,2}:

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

    现在我们已经为URL指定了通配符,我们需要一种将通配符数据传递给view函数的方法,以便我们可以使用单个视图函数来执行任意小时偏移。 我们通过在要保存的URL模式中的数据周围添加括号来实现这一点。 在我们的例子中,我们想要保存在URL中输入的任何数字,所以让我们在\d{1,2}的周围放置括号,就像这样:

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

    如果你熟悉正则表达式,那么在这里你就不会感觉到陌生; 我们使用圆括号从匹配的文本中捕获数据。 最终的URLconf,包括我们以前的两个视图,看起来像这样:

    from django.conf.urls import include, url
    from django.contrib import admin
    from mysite.views import hello, current_datetime, hours_ahead
    
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^hello/$', hello),
        url(r'^time/$', current_datetime),
        url(r'^time/plus/(\d{1,2})/$', hours_ahead),
    ]
    

    如果你在另一个Web开发平台上有经验,你可能会想:“嘿,让我们使用一个查询字符串参数!” - 类似于/ time/plus?hours = 3,其中小时数将由hours参数指定 在URL的查询字符串('?'之后的部分)中,你可以用Django来做到这一点(我将在第7章中告诉你如何做),但Django的核心哲学之一是URL应该很漂亮。
    URL /time/plus/3/更清晰,更简单,更易读,更容易朗读,比查询字符串更漂亮。 漂亮的网址是一个高质量的网络应用程序的特点。 Django的URLconf系统鼓励使用漂亮的网址而不仅仅如此。

    有了这个照顾,让我们写hours_ahead视图。 hours_ahead与我们前面写的current_datetime视图非常相似,主要区别在于:它需要一个额外的参数,偏移小时数。 这是视图代码:

    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)
    

    让我们仔细看看这个代码。

    视图函数hours_ahead有两个参数:request和offset:
    请求是一个HttpRequest对象,就像在hello和current_datetime中一样。 我会再说一遍:每个视图总是以HttpRequest对象作为其第一个参数。

    offset是URLpattern中括号所捕获的字符串。 例如,如果请求的URL是/time/plus/3/,那么offset就是字符串'3'。 如果请求的URL是/ time/plus/21/,那么offset就是字符串'21'。 请注意,即使字符串仅由数字组成,如“21”,捕获的值将始终是Unicode对象,而不是整数。

    我决定调用变量偏移量,但只要它是一个有效的Python标识符,就可以随意调用它。 变量名称不重要; 所有重要的是,这是函数的第二个参数,请求后。 (也可以在URLconf中使用关键字而不是位置参数,我在第7章中已经介绍过)

    我们在函数中做的第一件事是在offset上调用int()。 这将Unicode字符串值转换为整数。

    请注意,如果对不能转换为整数的值(如字符串“foo”)调用int(),则Python将引发ValueError异常。 在这个例子中,如果我们遇到了ValueError,我们引发异常django.http.Http404,你可以想象得到404“Page not found”错误。

    精明的读者会怀疑:我们怎样才能达到ValueError的情况下,无论如何,由于我们的URLpattern - (\ d {1,2})中的正则表达式 - 只捕获数字,因此偏移将只能是由数字? 答案是,我们不会,因为URLpattern提供了一个适度但有用的输入验证级别,但是我们仍然检查ValueError,以防以某种方式调用此视图函数。 实现视图函数是一个很好的做法,这样就不会对其参数做任何假设。 松散的耦合,记得吗?

    在函数的下一行中,我们计算当前date/time并添加适当的小时数。 我们已经从current_datetime视图中看到了datetime.datetime.now()。 这里的新概念是date/time算法可以通过创建一个datetime.timedelta对象并将其添加到一个datetime.datetime对象来执行。 我们的结果存储在变量dt中。

    这一行还显示了我们为什么在offset上调用int() - datetime.timedelta函数要求hours参数是一个整数。

    接下来,我们构造这个视图函数的HTML输出,就像我们在current_datetime中那样。 这一行与上一行的区别在于,它使用了Python的格式化字符串功能,其中有两个值,而不仅仅是一个。 因此,字符串中有两个%符号,并且要插入一个元组值(offset,dt)。

    最后,我们返回一个HTML的HttpResponse。

    使用该视图函数和URLconf写入,启动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/以验证URLconf中的模式只接受一位或两位数字; 在这种情况下,Django应该显示“页面未找到”错误,就像我们之前在“关于404错误的快速注释”一节中看到的一样。

    URL http://127.0.0.1:8000/time/plus/(没有小时指定)也应报404。

    Django的漂亮的错误页面

    花一点时间来欣赏我们迄今为止所做的优秀的网络应用 - 现在让我们来分析一下吧! 让我们通过注释hours_ahead视图中的offset = int(offset)行来故意将Python错误引入到views.py文件中:

    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消息,显示在最上面:“不受支持的timedelta小时组件的类型:str”(图2-3)。 图2-3:Django的错误页面.png

    发生了什么? 那么,datetime.timedelta函数期望hours参数是一个整数,我们注释掉了将偏移量转换为整数的代码位。 这导致了datetime.timedelta引发TypeError。 这是每个程序员在某个时候遇到的一个典型的小错误。

    这个例子的重点是演示Django的错误页面。 花一些时间来探索错误页面,并了解它给你的各种信息。 这里有一些事情要注意:

    • 在页面顶部,您将获得有关异常的关键信息:异常的类型,异常的任何参数(本例中为“不支持的类型”消息),引发异常的文件以及违规d的行号。
    • 在关键的异常信息下,页面显示此异常的完整Python回溯。这与Python的命令行解释器中的标准回溯相似,只是它更具交互性。对于堆栈中的每个级别(“框架”),Django都会显示该文件的名称,函数/方法名称,行号以及该行的源代码。
    • 点击源代码行(深灰色),你会看到错误行前后的几行,给你上下文。单击堆栈中任意帧下的“Local vars”,在该框架中,在引发异常的代码中的确切位置查看所有局部变量及其值的表。这个调试信息可以是一个很大的帮助。
    • 请注意“Traceback”标题下的“切换到复制和粘贴视图”文本。点击这些单词,回溯将切换到可轻松复制和粘贴的替代版本。当你想与其他人分享你的异常回溯来获得技术支持时,可以使用它,比如Django IRC聊天室或Django用户邮件列表中的友善人士。
    • 在下面,“在公共网站上共享这个追踪”按钮只需点击一下就可以完成这项工作。点击它可以将traceback发布到dpaste,在那里你会得到一个独特的URL,你可以与其他人分享。
    • 接下来,“请求信息”部分包含大量有关传入的Web请求的信息,这些信息产生了以下错误:GET和POST信息,Cookie值和元信息(如CGI标头)。附录F有一个请求对象包含的所有信息的完整引用。
    • 在“请求信息”部分的下方,“设置”部分列出了这个特定的Django安装的所有设置。附录D详细介绍了所有可用的设置。

    Django错误页面能够在某些特殊情况下显示更多信息,例如模板语法错误。 稍后我们将讨论Django模板系统。 现在,取消注释offset = int(偏移)行以使视图函数再次正常工作。

    Django错误页面也是非常有用的,如果你是喜欢用仔细的打印语句帮助调试的程序员的类型。

    在您的视图中的任何时间点,临时插入一个断言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项目处于调试模式时才会显示Django错误页面。我将在第13章介绍如何停用调试模式。现在,只要知道每个Django项目在启动时都会自动进入调试模式。 (听起来很熟悉?本章前面描述的“页面未找到”错误的工作原理是一样的。)

    接下来学习什么?
    到目前为止,我们一直在使用HTML直接在Python代码中编写我们的视图函数。我已经这样做了,以便在展示核心概念时保持简单,但在现实世界中,这几乎总是一个坏主意。 Django提供了一个简单但功能强大的模板引擎,允许您将页面设计与底层代码分开。我们将在下一章深入探讨Django的模板引擎。

    相关文章

      网友评论

        本文标题:Django视图:动态内容

        本文链接:https://www.haomeiwen.com/subject/jyssaxtx.html