date: 2022-01-09title: Django之视图函数 #标题
tags: #标签
categories: python # 分类

一个视图函数(类),简称视图,是一个简单的Python 函数(类),它接受Web请求并且返回Web响应。

响应可以是一张网页的HTML内容,一个重定向,一个404错误,一个XML文档,或者一张图片。

无论视图本身包含什么逻辑,都要返回响应。代码写在哪里也无所谓,只要它在你当前项目目录下面。除此之外没有更多的要求了——可以说“没有什么神奇的地方”。为了将代码放在某处,大家约定成俗将视图放置在项目(project)或应用程序(app)目录中的名为views.py的文件中。

request对象

在说视图函数之前,先来看看视图函数中的request对象,参考官方文档

当一个页面被请求时,Django就会创建一个包含本次请求原信息(请求报文中的请求行、首部信息、内容主体等)的HttpRequest对象。Django会将这个对象自动传递给响应的视图函数,一般视图函数约定俗成地使用 request 参数承接这个对象。

在之前urls之路由分发章节,我们也写了几个视图函数,下面是一个简单的视图函数(可以看到每个视图函数中,都有一个request形参,就是用来接收HTTPRequest对象的):

  1. from django.shortcuts import render
  2. def index(request):
  3. return render(request, 'app02.html')
  4. def home_page(request):
  5. return render(request, 'app02_home.html')

下面来看看请求相关常用的值:

  • path_info:返回用户访问url,不包括域名
  • method:请求中使用的HTTP方法的字符串表示,全大写表示。
  • GET:包含所有HTTP GET参数的类字典对象
  • POST:包含所有HTTP POST参数的类字典对象
  • body:请求体,byte类型 request.POST的数据就是从body里面提取到的

注意:所有属性应该都是只读的,除非另有说明。

下面是个简单样例:

1、urls.py文件内容如下:

  1. from django.conf.urls import url
  2. from apps import views
  3. urlpatterns = [
  4. url(r'', views.page),
  5. ]

2、views.py文件内容如下:

data列表就是用于存放request请求中的各种数据,至于其含义,可以自行查阅官方文档

  1. from django.shortcuts import render, HttpResponse
  2. def page(request):
  3. if request.method == 'GET':
  4. return render(request, 'login.html')
  5. else:
  6. data = [
  7. {
  8. 'path_info': request.path_info,
  9. 'path': request.path,
  10. 'method': request.method,
  11. 'meta': request.META,
  12. 'scheme': request.scheme,
  13. 'body': request.body,
  14. 'encoding': request.encoding,
  15. 'get_data': request.GET,
  16. 'post_data': request.POST,
  17. 'cookie': request.COOKIES,
  18. 'files': request.FILES,
  19. 'user': request.user,
  20. 'session': request.session
  21. }
  22. ]
  23. print(data)
  24. return HttpResponse('登录成功')

3、html文件内容如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <h1>欢迎来到登录页面</h1>
  9. <form action="/index" method="post">
  10. <label>
  11. 用户名:<input type="text" name="username">
  12. </label>
  13. <br/>
  14. <label>
  15. 密&nbsp;&nbsp;&nbsp;码:<input type="password" name="password">
  16. </label>
  17. <br/>
  18. <label>
  19. 选择文件:<input type="file" name="file">
  20. </label>
  21. <br/>
  22. <input type="submit">
  23. </form>
  24. </body>
  25. </html>

当访问页面后,按照页面信息进行登录并上传文件,pycharm就会打印类似于下面的数据:

Django之视图函数 - 图1

看起来有点乱,我们可以对其进行粘贴,然后去JSON在线解析网站对其进行解析,如下是解析后的内容:

[{
    'path_info': '/index',
    'path': '/index',
    'method': 'POST',
    'meta': {
        'ALLUSERSPROFILE': 'C:\\ProgramData',
        'APPDATA': 'C:\\Users\\Tenyuns\\AppData\\Roaming',
        'CLASSPATH': '.;D:\\software\\java\\lib\\dt.jar;D:\\software\\java\\lib\\tools.jar;',
        'COMMONPROGRAMFILES': 'C:\\Program Files\\Common Files',
        'COMMONPROGRAMFILES(X86)': 'C:\\Program Files (x86)\\Common Files',
        'COMMONPROGRAMW6432': 'C:\\Program Files\\Common Files',
        'COMPUTERNAME': 'TENYUNS-KLQSALP',
        'COMSPEC': 'C:\\Windows\\system32\\cmd.exe',
        'DJANGO_SETTINGS_MODULE': 'first_pro.settings',
        'DRIVERDATA': 'C:\\Windows\\System32\\Drivers\\DriverData',
        'HOMEDRIVE': 'C:',
        'HOMEPATH': '\\Users\\Tenyuns',
        'IDEA_INITIAL_DIRECTORY': 'C:\\Windows\\System32',
        'JAVA_HOME': 'D:\\software\\java',
        'JETBRAINS_LICENSE_SERVER': 'http://fls.jetbrains-agent.com',
        'LOCALAPPDATA': 'C:\\Users\\Tenyuns\\AppData\\Local',
        'LOGONSERVER': '\\\\TENYUNS-KLQSALP',
        'NUMBER_OF_PROCESSORS': '8',
        'ONEDRIVE': 'C:\\Users\\Tenyuns\\OneDrive',
        'ONEDRIVECONSUMER': 'C:\\Users\\Tenyuns\\OneDrive',
        'OS': 'Windows_NT',
        'PATH': 'D:\\software\\xshell\\Xlpd 6\\;D:\\software\\xshell\\Xshell 6\\;D:\\software\\xshell\\Xmanager 6\\;D:\\software\\python\\Scripts\\;D:\\software\\python\\;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Windows\\System32\\OpenSSH\\;"D:\\software\\java\\bin;D:\\software\\java\\jre\\bin;";D:\\software\\mysql-5.7.30-winx64\\bin;;D:\\software\\WinSCP\\;D:\\software\\PyCharm 2019.3.3\\bin;D:\\software\\Microsoft VS Code\\bin',
        'PATHEXT': '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.PY;.PYW',
        'PROCESSOR_ARCHITECTURE': 'AMD64',
        'PROCESSOR_IDENTIFIER': 'Intel64 Family 6 Model 142 Stepping 10, GenuineIntel',
        'PROCESSOR_LEVEL': '6',
        'PROCESSOR_REVISION': '8e0a',
        'PROGRAMDATA': 'C:\\ProgramData',
        'PROGRAMFILES': 'C:\\Program Files',
        'PROGRAMFILES(X86)': 'C:\\Program Files (x86)',
        'PROGRAMW6432': 'C:\\Program Files',
        'PSMODULEPATH': 'C:\\Program Files\\WindowsPowerShell\\Modules;C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules',
        'PUBLIC': 'C:\\Users\\Public',
        'PYCHARM': 'D:\\software\\PyCharm 2019.3.3\\bin;',
        'PYCHARM_DISPLAY_PORT': '63342',
        'PYCHARM_HOSTED': '1',
        'PYTHONIOENCODING': 'UTF-8',
        'PYTHONPATH': 'D:\\django_demo\\first_pro;D:\\software\\PyCharm 2019.3.3\\plugins\\python\\helpers\\pycharm_matplotlib_backend;D:\\software\\PyCharm 2019.3.3\\plugins\\python\\helpers\\pycharm_display',
        'PYTHONUNBUFFERED': '1',
        'SYSTEMDRIVE': 'C:',
        'SYSTEMROOT': 'C:\\Windows',
        'TEMP': 'C:\\Users\\Tenyuns\\AppData\\Local\\Temp',
        'TMP': 'C:\\Users\\Tenyuns\\AppData\\Local\\Temp',
        'USERDOMAIN': 'TENYUNS-KLQSALP',
        'USERDOMAIN_ROAMINGPROFILE': 'TENYUNS-KLQSALP',
        'USERNAME': 'Tenyuns',
        'USERPROFILE': 'C:\\Users\\Tenyuns',
        'WINDIR': 'C:\\Windows',
        'WXDRIVE_START_ARGS': '--wxdrive-setting=0 --disable-gpu --disable-software-rasterizer --enable-features=NetworkServiceInProcess',
        '__COMPAT_LAYER': 'RunAsAdmin',
        'RUN_MAIN': 'true',
        'SERVER_NAME': 'activate.navicat.com',
        'GATEWAY_INTERFACE': 'CGI/1.1',
        'SERVER_PORT': '8000',
        'REMOTE_HOST': '',
        'CONTENT_LENGTH': '103',
        'SCRIPT_NAME': '',
        'SERVER_PROTOCOL': 'HTTP/1.1',
        'SERVER_SOFTWARE': 'WSGIServer/0.2',
        'REQUEST_METHOD': 'POST',
        'PATH_INFO': '/index',
        'QUERY_STRING': '',
        'REMOTE_ADDR': '127.0.0.1',
        'CONTENT_TYPE': 'application/x-www-form-urlencoded',
        'HTTP_HOST': '127.0.0.1:8000',
        'HTTP_CONNECTION': 'keep-alive',
        'HTTP_CACHE_CONTROL': 'max-age=0',
        'HTTP_SEC_CH_UA': '"Google Chrome";v="95", "Chromium";v="95", ";Not A Brand";v="99"',
        'HTTP_SEC_CH_UA_MOBILE': '?0',
        'HTTP_SEC_CH_UA_PLATFORM': '"Windows"',
        'HTTP_UPGRADE_INSECURE_REQUESTS': '1',
        'HTTP_ORIGIN': 'http://127.0.0.1:8000',
        'HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36',
        'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
        'HTTP_SEC_FETCH_SITE': 'same-origin',
        'HTTP_SEC_FETCH_MODE': 'navigate',
        'HTTP_SEC_FETCH_USER': '?1',
        'HTTP_SEC_FETCH_DEST': 'document',
        'HTTP_REFERER': 'http://127.0.0.1:8000/',
        'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br',
        'HTTP_ACCEPT_LANGUAGE': 'zh-CN,zh;q=0.9,en;q=0.8',
        'HTTP_COOKIE': 'csrftoken=TXyBJNxWBWWIQAaf1fwO7NAxnbH9QUDvlGinb2u1vLR9Wo5N85U8Ky1L36XiI4Gr',
        'wsgi.input': < _io.BufferedReader name = 884 > ,
        'wsgi.errors': < _io.TextIOWrapper name = '<stderr>'
        mode = 'w'
        encoding = 'utf-8' > ,
        'wsgi.version': (1, 0),
        'wsgi.run_once': False,
        'wsgi.url_scheme': 'http',
        'wsgi.multithread': True,
        'wsgi.multiprocess': False,
        'wsgi.file_wrapper': < class 'wsgiref.util.FileWrapper' >
    },
    'scheme': 'http',
    'body': b 'username=lvjianzhao&password=jianzhao87.&file=Django%E4%B9%8BURL%E8%B7%AF%E7%94%B1%E7%B3%BB%E7%BB%9F.md',
    'encoding': None,
    'get_data': < QueryDict: {} > ,
    'post_data': < QueryDict: {
        'username': ['lvjianzhao'],
        'password': ['jianzhao87.'],
        'file': ['Django之URL路由系统.md']
    } > ,
    'cookie': {
        'csrftoken': 'TXyBJNxWBWWIQAaf1fwO7NAxnbH9QUDvlGinb2u1vLR9Wo5N85U8Ky1L36XiI4Gr'
    },
    'files': < MultiValueDict: {} > ,
    'user': < SimpleLazyObject: < function AuthenticationMiddleware.process_request. < locals > . < lambda > at 0x000001E2F0636280 >> ,
    'session': < django.contrib.sessions.backends.db.SessionStore object at 0x000001E2F0687970 >
}]
[09 / Nov / 2021 15: 29: 43]
"POST /index HTTP/1.1"
200 5293

临时重定向

下面是一个简单项目,各文件内容如下:

1、urls.py

from django.conf.urls import url
from apps import views

urlpatterns = [
    url(r'^login/', views.login),
]

2、views.py

from django.shortcuts import render, HttpResponse


def login(request):
    if request.method == 'GET':
        return render(request, 'login.html')
    else:
        print(request.POST)
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username == 'lvjianzhao' and password == '123.com':
            return HttpResponse('<h1>登录成功</h1>')
        else:
            return HttpResponse('<h1>登录失败</h1>')

3、login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>欢迎来到登录页面</h1>
<form action="/login/" method="post">
    <label>
        用户名:<input type="text" name="username">
    </label>
    <br/>
    <label>
        密&nbsp;&nbsp;&nbsp;码:<input type="password" name="password">
    </label>
    <br/>
    <input type="submit">

</form>
</body>
</html>

当我们访问项目进行登录是,登录成功后的页面如下:

Django之视图函数 - 图2

可以看到,登录成功后的页面还是http://127.0.0.1:8000/login/这个 url,也就是说,登录成功后,还是走的login视图函数返回的页面,那么在实际工作中,当登录成功后,就会跳转到项目首页或者某个页面,反正登录成功后的url肯定不是和登录使用的一个视图,也就是用到了重定向,待登录成功后,重定向到项目首页。

需要做的更改如下:

######### views.py文件
from django.shortcuts import render, HttpResponse, redirect
# 导入redirect模块

def login(request):
    if request.method == 'GET':
        return render(request, 'login.html')
    else:
        print(request.POST)
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username == 'lvjianzhao' and password == '123.com':
            return redirect('/home/')         # 重定向到指定路径
        else:
            return HttpResponse('<h1>登录失败</h1>')


# 这里是首页的视图函数
def home(request):
    return HttpResponse('<h1>欢迎访问此项目首页</h1>')

######### urls.py文件
from django.conf.urls import url
from apps import views

urlpatterns = [
    url(r'^login/', views.login),
    url(r'^home/', views.home),       # 增加home的路由
]

再次访问项目并登录成功后,就可以重定向到正确的URL了,同时状态码也为“302”:

Django之视图函数 - 图3

CBV和FBV

关于CBV和FBV相关概念,我都是看FBV和CBV这个视频来做的笔记,如果想更好的理解,建议直接看视频。

  • FBV(function base views) 就是在视图里使用函数处理请求,如下:
# views.py文件内容
def login(request):
    if request.method == 'GET':
        return render(request, 'login.html')
    else:
        print(request.POST)
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username == 'lvjianzhao' and password == '123.com':
            return redirect('/home/')
        else:
            return HttpResponse('<h1>登录失败</h1>')


def home(request):
    return HttpResponse('<h1>欢迎访问此项目首页</h1>')
  • CBV(class base views) 就是在视图里使用类处理请求。

Python是一个面向对象的编程语言,如果只用函数来开发,有很多面向对象的优点就错失了(继承、封装、多态)。所以Django在后来加入了Class-Based-View。可以让我们用类写View。

这样做的优点主要下面两种:

  • 提高了代码的复用性,可以使用面向对象的技术,比如Mixin(多继承)
  • 可以用不同的函数针对不同的HTTP方法处理,而不是通过很多if判断,提高代码可读性

由于在之前的代码中,都是直接使用函数来处理请求的,所以这里就不说FBV了,一起来看下CBV是怎么写的。

下面是CBV的代码示例,但里面涉及到源码,只有真正理解源码,才会更熟练的去使用CBV,如果阅读源码能力较弱,可以看FBV和CBV这个视频,让人家带着去理解源码。

############## views.py文件内容如下:
from django.shortcuts import render, HttpResponse
# 导入View类
from django.views import View


# 需要继承 View类(这个类是django给提供的)
class MyView(View):

    # 方法名必须使用get、post、put....等http支持的方法,具体缘由可以去看源码
    # 对应的方法名,就是处理对应的get、post请求
    def get(self, request):
        return render(request, 'login.html')

    def post(self, request):
        return HttpResponse('<h1>登录成功</h1>')




############## urls.py文件内容如下:

from django.conf.urls import url
from apps import views

# 正则匹配后,写上自己的 views.类名.as_view() 为固定写法
urlpatterns = [
    url(r'^login/', views.MyView.as_view()),
]

当我们理解CBV的源码后,会发现,CBV的关键是在dispatch这个调度方法上,那么我们就可以在dispatch这个方法上做些文章(在执行这个方法前,做一些事情),如下:

from django.shortcuts import render, HttpResponse
# 导入View类
from django.views import View


# 需要继承 View类(这个类是django给提供的)
class MyView(View):

    # 如果需要在执行请求前后做一些事情,那么可以通过重写dispatch方法来实现
    def dispatch(self, request, *args, **kwargs):
        print('请求来了')  # 调度之前做一些事情
        ret = super().dispatch(request, *args, **kwargs)  # 执行父类View中的dispatch方法,并用ret进行接收返回结果
        print('请求处理完了')  # 调度结束之后做的一些事情
        # return 下父类中dispatch返回的结果
        return ret

    # 方法名必须使用get、post、put....等http支持的方法,具体缘由可以去看源码
    # 对应的方法名,就是处理对应的get、post请求
    def get(self, request):
        print('处理请求中...')
        return render(request, 'login.html')

    def post(self, request):
        print('处理请求中...')
        return HttpResponse('<h1>登录成功</h1>')

给视图函数加装饰器

FBV加装饰器

# 示例1:
def n1(f):
    def n2(*args,**kwargs):
        print('请求之前')
        ret = f(*args,**kwargs)
        print('请求之后')
        return ret
    return n2
@n1
def home(request):
    print('home!!!')
    return render(request,'home.html')

# 示例2:
def is_login(f):
    def inner(request, *args, **kwargs):
        is_login = request.COOKIES.get('is_login')
        print(is_login, type(is_login))
        if is_login == 'True':
            ret = f(request, *args, **kwargs)
            return ret
        else:
            return redirect('login')

    return inner


@is_login
def home(request):
    return render(request, 'home.html')

CBV加装饰器

类中的方法与独立函数不完全相同,因此不能直接将函数装饰器应用于类中的方法 ,我们需要先将其转换为方法装饰器。

Django中提供了method_decorator装饰器用于将函数装饰器转换为方法装饰器。

from django.views import View
from django.utils.decorators import method_decorator       # 导入method_decorator 
from django.shortcuts import render, HttpResponse


# 装饰器
def n1(f):
    def n2(*args, **kwargs):
        print('请求之前')
        ret = f(*args, **kwargs)
        print('请求之后')
        return ret

    return n2


@method_decorator(n1, name='get')  # 方式一
class LoginView(View):
    # @method_decorator(n1)     # 方式二
    def dispatch(self, request, *args, **kwargs):
        ret = super().dispatch(request, *args, **kwargs)
        return ret

    # @method_decorator(n1)  # 方式三
    def get(self, request):
        print(f'{request.method}请求处理了')
        return render(request, 'login.html')

    def post(self, request):
        username = request.POST.get('username')
        password = request.POST.get('password')
        print(username, password)
        print(f'{request.method}请求处理了')
        return HttpResponse('登录成功!')

关于上述三种给CBV添加装饰器的方式区别如下(方式一不推荐使用,方式二和方式三,可以根据需要去使用):

  • 方式一:直接添加在类上,后面的name表示只给get添加装饰器。以这种方式如果想给多个方法加装饰器,需要写多层装饰器,因为name这个参数的值必须是个字符串,并且不能同时写两个方法,不推荐此种方式添加装饰器
  • 方式二:直接添加在dispatch方法上,这样相当于每个方法都添加了装饰器(因为在处理请求前,都需要先执行dispatch方法);
  • 方式三:添加在每一个函数中,哪个需要就给哪个添加。