Django 博客首页视图

Django 处理 HTTP 请求

Web 应用的交互过程其实就是 HTTP 请求与响应的过程。无论是在 PC 端还是移动端,我们通常使用浏览器来上网,上网流程大致来说是这样的:

  1. 我们打开浏览器,在地址栏输入想访问的网址,比如 http://zmrenwu.com/(当然你也可能从收藏夹里直接打开网站,但本质上都是一样的)。
  2. 浏览器知道我们想要访问哪个网址后,它在后台帮我们做了很多事情。主要就是把我们的访问意图包装成一个 HTTP 请求,发给我们想要访问的网址所对应的服务器。通俗点说就是浏览器帮我们通知网站的服务器,说有人来访问你啦,访问的请求都写在 HTTP 里了,你按照要求处理后告诉我,我再帮你回应他!
  3. 服务器处理了HTTP 请求,然后生成一段 HTTP 响应给浏览器。浏览器解读这个响应,把相关的内容在浏览器里显示出来,于是我们就看到了网站的内容。比如你访问了我的博客主页 http://zmrenwu.com/,服务器接收到这个请求后就知道用户访问的是首页,首页显示的是全部文章列表,于是它从数据库里把文章数据取出来,生成一个写着这些数据的 HTML 文档,包装到 HTTP 响应里发给浏览器,浏览器解读这个响应,把 HTML 文档显示出来,我们就看到了文章列表的内容。

因此,Django 作为一个 Web 框架,它的使命就是处理流程中的第二步。即接收浏览器发来的 HTTP 请求,返回相应的 HTTP 响应。于是引出这么几个问题:

  1. Django 如何接收 HTTP 请求?
  2. Django 如何处理这个 HTTP 请求?
  3. Django 如何生成 HTTP 响应?

对于如何处理这些问题,Django 有其一套规定的机制。我们按照 Django 的规定,就能开发出所需的功能。

Hello 视图函数

我们先以一个最简单的 Hello World 为例来看看 Django 处理上述问题的机制是怎么样的。

绑定 URL 与视图函数

首先 Django 需要知道当用户访问不同的网址时,应该如何处理这些不同的网址(即所说的路由)。Django 的做法是把不同的网址对应的处理函数写在一个 urls.py 文件里,当用户访问某个网址时,Django 就去会这个文件里找,如果找到这个网址,就会调用和它绑定在一起的处理函数(叫做视图函数)。

下面是具体的做法,首先在 blog 应用的目录下创建一个 urls.py 文件,这时你的目录看起来是这样:

blog\
    __init__.py
    admin.py
    apps.py
    migrations\
        0001_initial.py
        __init__.py
    models.py
    tests.py
    views.py
    urls.py

在 blog\urls.py 中写入这些代码:

blog/urls.py

from django.conf.urls import url

from . import views

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

我们首先从 django.conf.urls 导入了 url 函数,又从当前目录下导入了 views 模块。然后我们把网址和处理函数的关系写在了 urlpatterns 列表里。

绑定关系的写法是把网址和对应的处理函数作为参数传给 url 函数(第一个参数是网址,第二个参数是处理函数),另外我们还传递了另外一个参数 name,这个参数的值将作为处理函数 index 的别名,这在以后会用到。

注意这里我们的网址是用正则表达式写的,Django 会用这个正则表达式去匹配用户实际输入的网址,如果匹配成功,就会调用其后面的视图函数做相应的处理。

比如说我们本地开发服务器的域名是 http://127.0.0.1:8000,那么当用户输入网址 http://127.0.0.1:8000 后,Django 首先会把协议 http、域名 127.0.0.1 和端口号 8000 去掉,此时只剩下一个空字符串,而 r'^$' 的模式正是匹配一个空字符串(这个正则表达式的意思是以空字符串开头且以空字符串结尾),于是二者匹配,Django 便会调用其对应的 views.index 函数。

注意:在项目根目录的 blogproject\ 目录下(即 settings.py 所在的目录),原本就有一个 urls.py 文件,这是整个工程项目的 URL 配置文件。而我们这里新建了一个 urls.py 文件,且位于 blog 应用下。这个文件将用于 blog 应用相关的 URL 配置。不要把两个文件搞混了。

编写视图函数

第二步就是要实际编写我们的 views.index 视图函数了,按照惯例视图函数定义在 views.py 文件里:

blog/views.py

from django.http import HttpResponse

def index(request):
    return HttpResponse("欢迎访问我的博客首页!")

我们前面说过,Web 服务器的作用就是接收来自用户的 HTTP 请求,根据请求内容作出相应的处理,并把处理结果包装成 HTTP 响应返回给用户。

这个两行的函数体现了这个过程。它首先接受了一个名为 request 的参数,这个 request 就是 Django 为我们封装好的 HTTP 请求,它是类 HttpRequest 的一个实例。然后我们便直接返回了一个 HTTP 响应给用户,这个 HTTP 响应也是 Django 帮我们封装好的,它是类 HttpResponse 的一个实例,只是我们给它传了一个自定义的字符串参数。

浏览器接收到这个响应后就会在页面上显示出我们传递的内容 :欢迎访问我的博客首页!

配置项目 URL

还差最后一步了,我们前面建立了一个 urls.py 文件,并且绑定了 URL 和视图函数 index,但是 Django 并不知道。Django 匹配 URL 模式是在 blogproject\ 目录(即 settings.py 文件所在的目录)的 urls.py 下的,所以我们要把 blog 应用下的 urls.py 文件包含到 blogproject\urls.py 里去,打开这个文件看到如下内容:

blogproject/urls.py

"""
一大段注释
"""

from django.conf.urls import url
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
]

修改成如下的形式:

- from django.conf.urls import url
+ from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
+   url(r'', include('blog.urls')),
]

这里 - 表示删掉这一行,+ 表示添加这一行。

我们这里导入了一个 include 函数,然后利用这个函数把 blog 应用下的 urls.py 文件包含了进来。此外 include 前还有一个 r'',这是一个空字符串。这里也可以写其它字符串,Django 会把这个字符串和后面 include 的 urls.py 文件中的 URL 拼接。比如说如果我们这里把 r'' 改成 r'blog/',而我们在 blog.urls 中写的 URL 是 r'^$',即一个空字符串。那么 Django 最终匹配的就是 blog/ 加上一个空字符串,即 blog/。

运行结果

激活虚拟环境,运行 python manage.py runserver 打开开发服务器,在浏览器输入开发服务器的地址 http://127.0.0.1:8000/,可以看到 Django 返回的内容了。

欢迎访问我的博客首页!

使用 Django 模板系统

这基本上就上 Django 的开发流程了,写好处理 HTTP 请求和返回 HTTP 响应的视图函数,然后把视图函数绑定到相应的 URL 上。

但是等一等!我们看到在视图函数里返回的是一个 HttpResponse 类的实例,我们给它传入了一个希望显示在用户浏览器上的字符串。但是我们的博客不可能只显示这么一句话,它有可能会显示很长很长的内容。比如我们发布的博客文章列表,或者一大段的博客文章。我们不能每次都把这些大段大段的内容传给 HttpResponse

Django 对这个问题给我们提供了一个很好的解决方案,叫做模板系统。Django 要我们把大段的文本写到一个文件里,然后 Django 自己会去读取这个文件,再把读取到的内容传给 HttpResponse。让我们用模板系统来改造一下上面的例子。

首先在我们的项目根目录(即 manage.py 文件所在目录)下建立一个名为 templates 的文件夹,用来存放我们的模板。然后在 templates\ 目录下建立一个名为 blog 的文件夹,用来存放和 blog 应用相关的模板。

当然模板存放在哪里是无关紧要的,只要 Django 能够找到的就好。但是我们建立这样的文件夹结构的目的是把不同应用用到的模板隔离开来,这样方便以后维护。我们在 templates\blog 目录下建立一个名为 index.html 的文件,此时你的目录结构应该是这样的:

blogproject\
    manage.py
    blogproject\
        __init__.py
        settings.py
        ...
    blog\
        __init__.py
        models.py
        ,,,
    templates\
        blog\
            index.html

再一次强调 templates\ 目录位于项目根目录,而 index.html 位于 templates\blog 目录下,而不是 blog 应用下,如果弄错了你可能会得到一个TemplateDoesNotExist 异常。如果遇到这个异常,请回来检查一下模板目录结构是否正确。

在 templates\blog\index.html 文件里写入下面的代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
</head>
<body>
<h1>{{ welcome }}</h1>
</body>
</html>

这是一个标准的 HTML 文档,只是里面有两个比较奇怪的地方:{{ title }}{{ welcome }}。这是 Django 规定的语法。用 {{ }} 包起来的变量叫做模板变量。Django 在渲染这个模板的时候会根据我们传递给模板的变量替换掉这些变量。最终在模板中显示的将会是我们传递的值。

注意:index.html 必须以 UTF-8 的编码格式保存,且小心不要往里面添加一些特殊字符,否则极有可能得到一个 UnicodeDecodeError 这样的错误。

模板写好了,还得告诉 Django 去哪里找模板,在 settings.py 文件里设置一下模板文件所在的路径。在 settings.py 找到 TEMPLATES 选项,它的内容是这样的:

blogproject/settings.py

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

其中 DIRS 就是设置模板的路径,在 [] 中写入 os.path.join(BASE_DIR, 'templates'),即像下面这样:

blogproject/settings.py

TEMPLATES = [
    {
        ...
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        ...
    },
]

这里 BASE_DIR 是 settings.py 在配置开头前面定义的变量,记录的是工程根目录 blogproject\ 的值(注意是最外层的 blogproject\ 目录)。在这个目录下有模板文件所在的目录 templates\,于是利用os.path.join 把这两个路径连起来,构成完整的模板路径,Django 就知道去这个路径下面找我们的模板了。

视图函数可以改一下了:

blog/views.py

from django.http import HttpResponse
from django.shortcuts import render

def index(request):
    return render(request, 'blog/index.html', context={
                      'title': '我的博客首页', 
                      'welcome': '欢迎访问我的博客首页'
                  })

这里我们不再是直接把字符串传给 HttpResponse 了,而是调用 Django 提供的 render 函数。这个函数根据我们传入的参数来构造 HttpResponse

我们首先把 HTTP 请求传了进去,然后 render 根据第二个参数的值 blog/index.html 找到这个模板文件并读取模板中的内容。之后 render 根据我们传入的 context 参数的值把模板中的变量替换为我们传递的变量的值,{{ title }} 被替换成了 context 字典中 title 对应的值,同理 {{ welcome }} 也被替换成相应的值。

最终,我们的 HTML 模板中的内容字符串被传递给 HttpResponse 对象并返回给浏览器(Django 在 render 函数里隐式地帮我们完成了这个过程),这样用户的浏览器上便显示出了我们写的 HTML 模板的内容。

总结

本章节的代码位于:Step5: blog index view

如果遇到问题,请通过下面的方式寻求帮助。

  • 在下方评论区留言。
  • 将问题的详细描述通过邮件发送到 djangostudyteam@163.com,一般会在 24 小时内回复。
  • Pythonzhcn 社区的新手问答版块 发布帖子。

-- EOF --


51 条评论 / 29 人参与
每天早睡早起的脑子不太好

博主 我在cmd 运行完python manage.py runserver 后 没有报错,但是访问127.0.0.1:8000/时总是显示最初的 

it worked 界面 在开始的将语言调成 zh-hans 后再运行python manage.py runserver后也是这种情况 希望您能帮忙解答一下 谢谢


每天早睡早起的脑子不太好 每天早睡早起的脑子不太好

我找了一圈 好像要换个端口 python manage.py runserver 8001 然后访问 127.0.0.1:8001


zzz
- from django.conf.urls import url
+ from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
url(r'^admin/', admin.site.urls),
+ url(r'', include('blog.urls')),
]

这里的 - 和 + 是真的删除和增加,不是python的语法,所以你的文件应该为

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

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'', include('blog.urls')),
]

wb臭鱼烂虾真的多

博主你好,十分感谢你的教程,我在设置最上方的about contact等html跳转的时候遇到了一些问题.

我在view.py中写了def about(request):    return HttpResponseRedirect('blog/about.html')

在url.py中写了url(r'^about$', views.about, name='about'),

在base.html中也写了<a href="{% url 'blog:about' %}" data-hover="关于">

但是还是page not found ,想请教一下你是哪里出错了,十分感谢


追梦人物 [博主] wb臭鱼烂虾真的多

url 和 redirect 的 url 要匹配,你url没有匹配 .html 后缀。


wb臭鱼烂虾真的多 追梦人物 [博主]

还要再麻烦您一下,我还是没有搞好,应该怎么匹配html后缀呀,感谢


追梦人物 [博主] wb臭鱼烂虾真的多

url(r'^about\.html$', views.about, name='about')

参考 python re 正则表达式模块的文档学习如何写正则表达式。


gitliuhao
from django.urls import path

0xTars

jango2.0以上改成这个

blog\urls.py

from django.urls import path
from .import views
urlpatterns = [
path(r'',views.index,name='index'),
]
blogproject\urls.py

from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path(r'admin/', admin.site.urls),
path(r'',include('blog.urls'))
]

Python loves the author 0xTars

不行啊


黄鮕 Python loves the author

这个django2.0版本以上需要修改的东西有点多,我的做法如下(这样好像能够顺利往下执行):

#blog/urls.py

from django.urls import path 

from . import views

 urlpatterns = [ path('',views.index,name='index'),]

#blogproject/urls.py

import django,os

 from django.contrib import admin

 from django.urls import include, pathos.environ.setdefault("DJANGO_SETTINGS_MODULE", "blogproject.settings")django.setup() 

urlpatterns = [ path('admin/', admin.site.urls),

                         path('',include('blog.urls')),]


追梦人物 [博主] 黄鮕

是的,基本框架不变,但不兼容的代码需要自行参照 2.0 的文档解决。


暴走的小优001

[04/Mar/2018 15:36:04] "GET / HTTP/1.1" 200 1774Not Found: /favicon.ico[04/Mar/2018 15:36:04] "GET /favicon.ico HTTP/1.1" 404 1941  访问网站后会报错,看不到欢迎访问我的博客首页!请问如何解决


Linux-2017

Django version 2.0.1, 找不到 url(r'^$', views.index, name='index'), 这个路由


追梦人物 [博主] Linux-2017

当前教程不确定能兼容2.0以上版本


sinx-wang Linux-2017

改为path('', views.index, name='index')就好


Python loves the author sinx-wang

改成了,还是不行啊


黄鮕 Python loves the author

2.0版本以上确实不兼容,我也遇到这问题了。


dbb2xbb 黄鮕

Django 2.0以上版本以path来代替此前的url方法,path有自己的语法。

若在2.0版本上使用原有url的方法,则须导入re_path方法,其用法与url方法一致。

如下方代码所示:

from django.contrib import admin
from django.urls import path,re_path


urlpatterns = [
    re_path(r'(\d+)',views.show)
]

Arno Fan

如下格式配置 使用模板系统,不需额外配置 setting.py 中的 TEMPLATES DIRS

blogproject\
manage.py
blogproject\
__init__.py
settings.py
...

blog\
templates\
blog\
index.html
__init__.py
models.py
...

Arno Fan

播主的文章非常棒!!! 不过这里有个建议,每个app应用的模板及静态资源目录,放置在app下。manage.py同级的`templates`只用来定制`admin`的模板是不是更好呢?

> 我看官方的文档是这么建议的。


mysodalife

建议博主增加些namespace的介绍


xushuangang

您好:

关于url的include使用时,经测试得出结论是两个url的拼接,这时候一定要注意正则表达式的使用,

例:

url:http://127.0.0.1:8000/blog/test/

第一级url可以写成

url(r'^blog',include('blog.urls')), 或

url(r'blog',include('blog.urls')),

第二级url写成

url(r'test/$',views.index,name='index'),

也就是第一级的正则不能写$(匹配末尾),第二级不能写^(匹配首)


追梦人物 [博主] xushuangang

Cool,就是你说的这样,要正确理解正则表达式规则和include函数的作用。


mysodalife xushuangang

第一级的正则不能写$是对的,但是第二级是可以写^的吧,亲测有效,而且Django官方文档也是这么写的。


ljm9104 xushuangang

我认为写成这样更合适

第一级

url(r'^blog/',include('blog.urls')), 

第二级

url(r'^test/$',views.index,name='index'),

你写的第二级匹配的范围比较大,最好加上  ^,重要的是第一级末尾不能$,那样的话就匹配截止了


zhb1207

博主~ 求教~

我在文件里添加了注释

"""

就像这样的

"""

然后就会提示说

UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position 2: ordinal not in range(128)

删除了就没问题了,文件开头已经标注了 # coding:utf-8

请问这是什么原因? 多谢多谢~


追梦人物 [博主] zhb1207

改为 # encoding:utf-8 试试


zhb1207 追梦人物 [博主]

多谢~ 我今天没改又试了一次 又没有问题了。

还有之前在写comments遇到的问题,今天再运行一次就好了。 难道Django也会抽么。。。。


Tove_cv

在配置文件 settings.py 里设置模板路径,要在 TEMPLATES 那段代码的 ‘DIRS’: [] 填入  os.path.join(BASE_DIR, 'templates'):

blogproject/settings.py

TEMPLATES = [
{
...
'DIRS': [os.path.join(BASE_DIR, 'templates')],
...
},
]

然后我在 settings.py 文件内底部看见另一行代码,和需要填入的代码极度相似:

TEMPLATE_DIRS = (os.path.join(BASE_DIR,  'templates'),)

我想问问,这两处的代码有什么关系吗?


Stallionshell Tove_cv

应该是添加了后面一段 前面可以不需要吧 你试试?


xxxxhelloxxxx Stallionshell

忘了在哪里看到的,好像是stackoverflow

应该是前面写的DIR是标准的template 路径用法,后面那个TEMPLATE_DIRS是兼容过去老版本的....

删掉哪个都是能找到的.....

还有一个'APP_DIRS': True,这个开着的话django会自动从你的app文件夹下面找的,

你就是都删掉了也能找到.......


Vincent567

当我用之前的视图函数的时候:

return render(request, 'blog/index.html', context={ 'title': '我的博客首页', 'welcome': '欢迎访问我的博客首页' })

我可以正常运行,但是网页是乱的

当我使用新的view函数时:

def index(request): post_list = Post.objects.all().order_by('-created_time') return render(request, 'blog/index.html', context={'post_list': post_list})

会报错:

RuntimeError: Model class blog.models.Category doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS.

都要调试疯了ORZ。。。。求博主解答万分感谢


Semi Vincent567

你POST这个model引入了吗


追梦人物 [博主] Vincent567

报错信息说的很明确了,Category所在的app没用在settings.py 配置,你需要把它接入那个配置项的列表


Vincent567 追梦人物 [博主]

也就是在setting中的app中添加一项么?我当时好像试过的还是报错,但是我之后按照教程从头做了一次就没有报错了...谢谢博主


Rayutn

博主好,问一下为什么

return render(request, 'blog/index.html',context={ 'title':'My Blog First Page',#不能传入中文(问题待解决!!!) 'welcome':'Welcome to visit My Blog First Page'})

里面的title和welcome变量不能传入中文呢?找了很多的编码问题解决解决方法仍然不行。

错误是:SyntaxError: (unicode error) 'utf-8' codec can't decode byte 0xc4 in position 0: invalid continuation byte


追梦人物 [博主] Rayutn

编码问题,Python2 还是 python3? python 在顶部加 #encoding: utf-8 申明


Rayutn 追梦人物 [博主]

Python3,顶部加编码声明没有用,我把py文件顶部都加了#encoding=utf-8,但是还是一样的编码问题,还是只能传入非中文字符串


夜尘_Panda
博主好 我index存放再templates/blogs 中 我在blog/url.py中写的是
urlpatterns = [
url(r' ', views.index, name="index"),
]
像什么
urlpatterns = [
url(r'^$', views.index, name="index"),
]
或者
urlpatterns = [
url(r'^blogs/$', views.index, name="index"),
]
都写过了 他都是返回Page not found at/

Page not found (404)
Request Method: GET
Request URL: http://127.0.0.1:8000/
Using the URLconf defined in myblog.urls, Django tried these URL patterns, in this order:
^admin/
^index/$
The current URL, , didn't match any of these.

请问我该如何配置?

pcdelphi 夜尘_Panda
看提示你定义的url是127.0.0.1:8000/index
是不是blogproject\urls.py中定义了url。
把blogproject\urls.py贴出来看看。

我也是菜鸟,大家一起研究

夜尘_Panda pcdelphi
from django.conf.urls import url, include
from django.contrib import admin


urlpatterns = [
# Examples:
# url(r'^$', 'myblog.views.home', name='home'),
# url(r'^blog/', include('blog.urls')),

url(r'^admin/', include(admin.site.urls)),#包含一个url文件
url(r'^index/$', include('blog.urls')),
#url(r'^index/', bv.index),
]
是这样子的

追梦人物 [博主] 夜尘_Panda
^index/$ 这个地方出错了,你的 url 写错了,把 $ 符号去掉,访问 127.0.0.1/index 即可

盖子是个熊孩子
谢谢博主分享,看到目前为止感觉收获挺多,比django的tutorial要实用一点。

HaddyYang
render还有可以加载一些模板变量 ^_^

HaddyYang HaddyYang
Templates中的context_processors设置的方法都是会被render写到模板变量中

Robert_CH
'APP_DIRS': True 的条件下,django会自动在app下找templates的目录,不需要设置DIRS

追梦人物 [博主] Robert_CH
我的习惯一般是把所有模板同一放在根目录的 templates/ 下,用 app 名做分隔。当然在每个 app 下放一个 templates/ 也是可以的,这样就无需设置,Django 会自动寻找。

mihelloO
context={} 可以简化为 {}

追梦人物 [博主] mihelloO
嗯,python 有两种传入参数的方法,都可以的。

mihelloO 追梦人物 [博主]
博主永远战斗在第一线。牛逼。