Django之视图层

视图层

1、视图函数

视图函数,简称视图,属于Django的视图层,默认定义在views.py文件中,是用来处理web请求信息以及返回响应信息的函数,所以研究视图函数只需熟练掌握两个对象即可:请求对象(HttpRequest)和响应对象(HttpResponse)

2、请求对象(HttpRequest)

django将http协议请求报文中的请求行、首部信息、内容主体封装到了HttpRequest对象中(类似于我们自定义框架的environ参数)。 django会将HttpRequest对象当做参数传给视图函数的第一个参数request,在视图函数中,通过访问该对象的属性便可以提取http协议的请求数据

2.1、HttpRequest对象常用属性part1

#一.HttpRequest.method
  获取请求使用的方法(值为纯大写的字符串格式)。例如:"GET"、"POST"
   应该通过该属性的值来判断请求方法

# 二.HttpRequest.GET
  值为一个类似于字典的QueryDict对象,封装了GET请求的所有参数,可通过HttpRequest.GET.get('键')获取相对应的值,可以在url中加入?k=v的方式传递参数.
  ## 前端:浏览器输入:
  http://127.0.0.1:8000/app02/home/12/?name=123
  ##后端: 
  print(request.GET)
  <QueryDict: {'name': ['123']}>
  

#三.HttpRequest.POST
   值为一个类似于字典的QueryDict对象,封装了POST请求所包含的表单数据,可通过HttpRequest.POST.get('键')获取相对应的值
   针对表单中checkbox类型的input标签、select标签提交的数据,键对应的值为多个,需要用:HttpRequest.POST.getlist("hobbies")获取存有多个值的列表,同理也有HttpRequest.GET.getlist("键")

案例:

urls.py

Copyfrom django.urls import re_path
from app01 import views

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

Views.py

Copyfrom django.shortcuts import render,HttpResponse

def login(request):
    if request.method == 'GET':
        # 当请求url为:http://127.0.0.1:8001/login/?a=1&b=2&c=3&c=4&c=5
        # 请求方法是GET,?后的请求参数都存放于request.GET中
        print(request.GET)
        # 输出<QueryDict: {'a': ['1'], 'b': ['2'], 'c': ['3', '4', '5']}>

        # 获取?后参数的方式为
        a=request.GET.get('a') # 1
        b=request.GET.get('b') # 2
        c=request.GET.getlist('c') # ['3', '4', '5']

        return render(request,'login.html')
    elif request.method == 'POST':
        # 在输入框内输入用户名egon、年龄18,选择爱好,点击提交
        # 请求方法为POST,表单内的数据都会存放于request.POST中
        print(request.POST) 
        # 输出<QueryDict: {..., 'name': ['egon'], 'age': ['18'], 'hobbies': ['music', 'read']}>

        # 获取表单中数据的方式为
        name=request.POST.get('name') # egon
        age=request.POST.get('age') # 18
        hobbies=request.POST.getlist('hobbies') # ['music', 'read']

        return HttpResponse('提交成功')

在templates目录下新建login.html

Copy<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
</head>
<body>

<!--
method="post"代表在提交表单时会以POST方法提交表单数据
action="/login/" 代表表单数据的提交地址为http://127.0.0.1:8001/login/,可以简写为action="/login/",或者action=""
-->
<form action="http://127.0.0.1:8001/login/" method="post">
    {% csrf_token %} <!--强调:必须加上这一行,后续我们会详细介绍-->
    <p>用户名:<input type="text" name="name"></p>
    <p>年龄:<input type="text" name="age"></p>
    <p>
        爱好:
        <input type="checkbox" name="hobbies" value="music">音乐
        <input type="checkbox" name="hobbies" value="read">阅读
        <input type="checkbox" name="hobbies" value="dancing">跳舞
    </p>
    <p><input type="submit" value="提交"></p>

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

2.2、HttpRequest对象常用属性part2

2.2.1 HttpRequest.body

当浏览器基于http协议的POST方法提交数据时,数据会被放到请求体中发送给django,django会将接收到的请求体数据存放于HttpRequest.body属性中,因为该属性的值为Bytes类型,所以通常情况下直接处理Bytes、并从中提取有用数据的操作是复杂而繁琐的,好在django会对它做进一步的处理与封装以便我们更为方便地提取数据.

Request.body是如何将二进制转换成QueryDict字典的呢?这个需要从form表单提交数据的编码格式说起:

form表单对提交的表单数据有两种常用的编码格式,可以通过属性enctype进行设置,如下

  • 编码格式1(默认的编码格式):enctype="application/x-www-form-urlencoded"

  • 编码格式2(使用form表单上传文件时只能用该编码):enctype="multipart/form-data"

  • 如果form表单提交数据是按照编码格式1,那么request.body中数据的格式类似于GET方法的数据格式,如k1=v1&k2=v2,此时django会将request.body中的数据提取出来封装到request.POST中方便我们提取.

  • 如果form表单提交数据是按照编码格式2,那么request.body中数据的格式为

    b'------WebKitFormBoundaryKtcwuksQltpNprep\r\nContent-Disposition: form-data;......'

    此时django会将request.body中的数据提取出来封装到request.POST中,将上传的文件数据专门提取出来封装到request.FILES属性中.

2.2.2 Request.FILES

login.html

      <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <form action="" method="post" enctype="multipart/form-data">
        <p>username:
            <input type="text" name="username">
        </p>
        <p>password:
            <input type="text" name="password">
        </p>
        <p>上传头像:
            <input type="file" name="header_img">
        </p>
        <input type="submit">
    </form>
    </body>
    </html>

view.py

    def login(request):
      if request.method=="POST":
          file_obj=request.FILES.get('header_img')
          username=request.POST.get('username')
          password=request.POST.get('password')
          if username=="lpc" and password=="123":
              _url=reverse('app01:index')
              print(_url)
              with open('templates/head.png','wb') as f:
                  for line in file_obj:
                      f.write(line)
              return  redirect(_url)
          else:
              return HttpResponse('用户名或密码错误')
    	 return render(request,'login.html')
  

如果使用form表单POST上传文件的话,文件数据将包含在HttpRequest.FILES属性中。

  该属性值为一个类似于字典的对象,可以包含多组key:value(对应多个上传的文件),其中每个key为 中name属性的值,而value则为对应的文件数据
  强调:HttpRequest.FILES 只有在请求的方法为POST 且提交的

带有enctype="multipart/form-data" 的情况下才会包含数据。否则,FILES 将为一个空的类似于字典的对象。
  

2.3 Request.path/Requeest.get_full_path

一.HttpRequest.path
  获取url地址的路径部分,只包含路径部分

二.HttpRequest.get_full_path()
  获取url地址的完整path,既包含路径又包含参数部分

如果请求地址是http://127.0.0.1:8001/order/?name=egon&age=10#_label3,
HttpRequest.path的值为"/order/"
HttpRequest.get_full_path()的值为"/order/?name=egon&age=10"

案例:

urls.py

Copyfrom django.urls import path,register_converter,re_path
from app01 import views

urlpatterns = [
    re_path(r'^order',views.order),
]

views.py

Copyfrom django.shortcuts import render,HttpResponse

# 针对请求的url地址:http://127.0.0.1:8001/order/?name=egon&age=10#_label3
# 从域名后的最后一个“/”开始到“?”为止是路径部分,即/order/
# 从“?”开始到“#”为止之间的部分为参数部分,即name=egon&age=10
def order(request):
    print(request.path) # 结果为“/order/”
    print(request.get_full_path()) # 结果为"/order/?name=egon&age=10"

    return HttpResponse('order page')

2.4 META、COOKIES、session、user、is_ajax.

一.HttpRequest.META
   值为包含了HTTP协议的请求头数据的Python字典,字典中的key及期对应值的解释如下
    CONTENT_LENGTH —— 请求的正文的长度(是一个字符串)。
    CONTENT_TYPE —— 请求的正文的MIME类型。
    HTTP_ACCEPT —— 响应可接收的Content-Type。
    HTTP_ACCEPT_ENCODING —— 响应可接收的编码。
    HTTP_ACCEPT_LANGUAGE —— 响应可接收的语言。
    HTTP_HOST —— 客服端发送数据的目标主机与端口
    HTTP_REFERER —— Referring 页面。
    HTTP_USER_AGENT —— 客户端使用的软件版本信息
    QUERY_STRING —— 单个字符串形式的查询字符串(未解析过的形式)。
    REMOTE_ADDR —— 客户端的IP地址。
    REMOTE_HOST —— 客户端的主机名。
    REMOTE_USER —— 服务器认证后的用户。
    REQUEST_METHOD —— 一个字符串,例如"GET" 或"POST"。
    SERVER_NAME —— 服务器的主机名。
    SERVER_PORT —— 服务器的端口(是一个字符串)。
   从上面可以看到,除 CONTENT_LENGTH 和 CONTENT_TYPE 之外,HTTP协议的请求头数据转换为 META 的键时,
    都会
    1、将所有字母大写
    2、将单词的连接符替换为下划线
    3、加上前缀HTTP_。
    所以,一个叫做 X-Bender 的头部将转换成 META 中的 HTTP_X_BENDER 键。
   
注意:下述常用属性暂且了解即可,待我们讲到专门的知识点时再专门详细讲解
二.HttpRequest.COOKIES
  一个标准的Python 字典,包含所有的cookie。键和值都为字符串。

三.HttpRequest.session
  一个既可读又可写的类似于字典的对象,表示当前的会话。只有当Django 启用会话的支持时才可用。

11.HttpRequest.user(用户认证组件下使用)

  一个 AUTH_USER_MODEL 类型的对象,表示当前登录的用户。

2.HttpRequest.is_ajax()

  如果请求是通过XMLHttpRequest 发起的,则返回True,方法是检查 HTTP_X_REQUESTED_WITH 相应的首部是否是字符串'XMLHttpRequest'。

  大部分现代的 JavaScript 库都会发送这个头部。如果你编写自己的 XMLHttpRequest 调用(在浏览器端),你必须手工设置这个值来让 is_ajax() 可以工作。

  如果一个响应需要根据请求是否是通过AJAX 发起的,并且你正在使用某种形式的缓存例如Django 的 cache middleware,
   你应该使用 vary_on_headers('HTTP_X_REQUESTED_WITH') 装饰你的视图以让响应能够正确地缓存。

3、响应对象 HTTPResponse

响应可以是任何形式的内容,比如一个HTML文件的内容,一个重定向,一个404错误,一个XML文档,或者一张图片等。总之,无论视图本身包含什么逻辑,都要返回响应,具体的说,响应对象主要有三种形式:HttpResponse,render,redirect

from django.shortcuts import HttpResponse,render,redirect

3.1、HttpResponse()

括号内直接跟一个具体的字符串作为响应体,比较直接很简单,所以这里主要介绍后面两种形式。

3.2、render()

render(request, template_name,[, context])
参数:
	  1、request:用于生成响应的请求对象,固定必须传入的第一个参数

    2、template_name:要使用的模板的完整名称,必须传入,render默认会去templates目录下查找模板文件

    3、context:可选参数,可以传入一个字典用来替换模块文件中的变量

综上,render的功能可以总结为:根据给定字典渲染模板文件,并返回一个渲染后的 HttpResponse对象。

3.3、redirect()

参数可以是:

  • 一个模型:将调用模型的get_absolute_url() 函数
  • 一个视图,可以带有参数:将使用urlresolvers.reverse 来反向解析名称
  • 一个绝对的或相对的URL,将原封不动的作为重定向的位置。
  • 默认返回一个临时的重定向;传递permanent=True 可以返回一个永久的重定向。

示例:

你可以用多种方式使用redirect() 函数。

传递一个具体的ORM对象(了解即可)

将调用具体ORM对象的get_absolute_url() 方法来获取重定向的URL:

from django.shortcuts import redirect

def my_view(request):
    ...
    object = MyModel.objects.get(...)
    return redirect(object)

传递一个视图的名称

def my_view(request):

    return redirect('some-view-name', foo='bar')

传递要重定向到的一个具体的网址

def my_view(request):
    ...
    return redirect('/some/url/')

当然也可以是一个完整的网址

def my_view(request):
    ...
    return redirect('http://example.com/')

默认情况下,redirect() 返回一个临时重定向。以上所有的形式都接收一个permanent 参数;如果设置为True,将返回一个永久的重定向:

def my_view(request):
    ...
    object = MyModel.objects.get(...)
    return redirect(object, permanent=True)

扩展阅读:

临时重定向(响应状态码:302)和永久重定向(响应状态码:301)对普通用户来说是没什么区别的,它主要面向的是搜索引擎的机器人。

A页面临时重定向到B页面,那搜索引擎收录的就是A页面。

A页面永久重定向到B页面,那搜索引擎收录的就是B页面。

4、 JsonResponse

向前端返回一个json格式字符串的两种方式

4.1 方式一: 导入json模块,列表转化为json,HttpResponse返回

import json

def my_view(request):
    data=['egon','kevin']
    return HttpResponse(json.dumps(data))

4.1 方式二:JsonResponse

from django.http import JsonResponse

def my_view(request):
    data=['egon','kevin']
    return JsonResponse(data,safe=False)
    #默认safe=True代表只能序列化字典对象,safe=False代表可以序列化字典以外的对象

5、 FBV和CBV

django的视图层由两种形式构成:FBV和CBV

1、FBV基于函数的视图(Function base view),我们之前一直介绍的都是FBV

2、CBV基于类的视图(Class base view)

案例:

urls.py

from django.urls import path,register_converter,re_path
from app01 import views

urlpatterns = [
    re_path(r'^login/',views.LoginView.as_view()), # 必须调用类下的方法as_view
]

views.py

from django.shortcuts import render,HttpResponse,redirect
from django.views import View

class LoginView(View):
    def dispatch(self, request, *args, **kwargs): # 可在该方法内做一些预处理操作
        # 当请求url为:http://127.0.0.1:8008/login/会先触发dispatch的执行
        # 如果http协议的请求方法为GET,则调用下述get方法
        # 如果http协议的请求方法为POST,则调用下述post方法
        obj=super().dispatch(request, *args, **kwargs) # 必须继承父类的dispatch功能
        return obj # 必须返回obj

    def get(self,request):
        return render(request,'login.html')

    def post(self,request):
        name=request.POST.get('name')
        pwd=request.POST.get('pwd')
        if name  == 'egon' and pwd == '123':
            res='登录成功'
        else:
            res='用户名或密码错误'
        return HttpResponse(res)

测试:

python manage.py runserver 8001 
# 验证GET请求:在浏览器输入:http://127.0.0.1:8001/login/
# 验证POST请求:在表单内输入数据然后提交

定义在函数内部的函数,引用了外部函数的民称空间,或者是作用于,这就内部函数就叫闭包函数.

5.1 CBV源码阅读

从路由匹配开始

re_path(r'^login/',views.LoginView.as_view()), # 必须调用类下的方法as_view

查看as_view函数.

@classonlymethod
    def as_view(cls, **initkwargs):
        # cls 代表我自己写的类.
        # ......
        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get
            self.request = request
            self.args = args
            self.kwargs = kwargs
            # 将我们自己写的视图类中的参数进行赋值
            return self.dispatch(request, *args, **kwargs)  # 调自己写的类中的dispatch
           # 对象在查找属性或方法的时候 顺序是什么?  先从自己找 再从产生对象的类中找  再去类的父类中找...
            """也就意味着你在看源码的时候 你一定要牢记上面的话"""
        view.view_class = cls
        view.view_initkwargs = initkwargs

        # take name and docstring from class
        update_wrapper(view, cls, updated=())

        # and possible attributes set by decorators
        # like csrf_exempt from dispatch
        update_wrapper(view, cls.dispatch, assigned=())
        return view   # 返回view函数的内存地址.做映射.
      

查看dispatch函数

    def dispatch(self, request, *args, **kwargs):
        # Try to dispatch to the right method; if a method doesn't exist,
        # 尽量用正确的方法调度;如果不存在方法,
        # defer to the error handler. Also defer to the error handler if the
        # 遵从错误处理程序。如果
        # request method isn't on the approved list.
				# 请求方法不在已批准的列表中。
        
        
  # http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
        # handler = getattr(自己写的类产生的对象,'小写的请求方法(get\post)','获取不到对应的方法就报错')
            # 拿到对应请求的函数地址.
        else:
            handler = self.http_method_not_allowed
            # 没有对应的请求,报405错误.
        return handler(request, *args, **kwargs)
               # 调用获取到的函数.
   def http_method_not_allowed(self, request, *args, **kwargs):
        logger.warning(
            'Method Not Allowed (%s): %s', request.method, request.path,
            extra={'status_code': 405, 'request': request}
        )
        return http.HttpResponseNotAllowed(self._allowed_methods())

5.2 加装饰器

5.2.1 FBV加装饰器

def wrapper(func):
    def inner(*args,**kwargs):
        start_time=time.time()
        res=func(*args,**kwargs)
        end_time = time.time()
        print('used:',end_time-start_time)
        return res
    return inner
@wrapper
def home(request,*args):
    _url=reverse('app02:home',args=(1,))
    print(_url)
    print(request.GET)
    return HttpResponse("hello home app02")

任何装饰器都可以安装.

5.2.2 CBV加装饰器

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

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

def wrapper(func):
    def inner(*args,**kwargs):
        start_time=time.time()
        res=func(*args,**kwargs)
        end_time = time.time()
        print('used:',end_time-start_time)
        return res
    return inner
 
from django.utils.decorators import method_decorator
# 第一种@method_decorator(wrapper,name='dispatch') 针对该类中所有的方法
class myCBV(View):
    # 第二种 @method_decorator(wrapper) 针对该类中所有的方法
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)
    # 第三种 @method_decorator(wrapper) 只针对该get的方法
    def get(self,request):
        return HttpResponse('我是get请求')
    def post(self,requst):
        return HttpResponse('我是post请求')


5.2.2.1注意:cbv加装饰器csrf_exempt装饰器.

from django.views.decorators.csrf import csrf_exempt
django.utils.decorators import method_decorator
# 第一种@method_decorator(csrf_exempt,name='dispatch') 针对dispatch中路由分发
# 没有效果 @method_decorator(csrf_exempt,name='post') 
class myCBV(View):
    # 第二种 @method_decorator(csrf_exempt) 针对dispatch中路由分发
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)
    def get(self,request):
        return HttpResponse('我是get请求')
    # 没有效果@method_decorator(csrf_exempt)
    def post(self,requst):
        return HttpResponse('我是post请求')

除了csrf_exempt之外 所有的其他装饰器 在CBV上面都有三种方式

posted @ 2023-06-11 22:14  FirstReed  阅读(8)  评论(0编辑  收藏  举报