Django知识点笔记(4)

简介: Django知识点笔记(4)

八、Django的组件

中间件

中间件顾名思义,是介于request与response处理之间的一道处理过程,相对比较轻量级,并且在全局上改变django的输入与输出。因为改变的是全局,所以需要谨慎实用,用不好会影响到性能。


Django的中间件的定义:


Middleware is a framework of hooks into Django’s request/response processing.

It’s a light, low-level “plugin” system for globally altering Django’s input or output.

MiddleWare,是 Django 请求/响应处理的钩子框架。

它是一个轻量级的、低级的“插件”系统,用于全局改变 Django 的输入或输出。【输入指代的就是客户端像服务端django发送数据,输出指代django根据客户端要求处理数据的结果返回给客户端】

如果你想修改请求,例如被传送到view中的HttpRequest对象。 或者你想修改view返回的HttpResponse对象,这些都可以通过中间件来实现。


django框架内部声明了很多的中间件,这些中间件有着各种各种的用途,有些没有被使用,有些被默认开启使用了。而被开启使用的中间件,都是在settngs.py的MIDDLEWARE中注册使用的。

Django默认的Middleware

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

自定义中间件

(1)定义中间件、

创建存放自定义中间件的文件这里选择在app01里创建mdws.py文件:

from django.utils.deprecation import MiddlewareMixin
class Md1(MiddlewareMixin):
    def process_request(self, request):
        print("Md1请求")
        # return HttpResponse("Md1中断")    # 拦截
    def process_response(self, request, response):
        print("Md1返回")
        return response
class Md2(MiddlewareMixin):
    def process_request(self, request):
        print("Md2请求")
        # return HttpResponse("Md2中断")
    def process_response(self, request, response):
        print("Md2返回")
        return response

process_request默认返回None,返回None,则继续执行下一个中间件的process_request;一旦返回响应体对象,则会拦截返回。


process_response必须有一个形参response,并return response;这是view函数返回的响应体,像接力棒一样传承给最后的客户端。

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

(2) 注册中间件

MIDDLEWARE = [
    ...
    'app01.mdws.Md1',
    'app01.mdws.Md2'
]

(3)构建index路由

# path('index/', views.index),
def index(request):
    print("index 视图函数执行...")
    return HttpResponse("hello yuan")

启动项目,访问index路径:

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

后台打印结果:

Md1请求

Md2请求

index 视图函数执行...

Md2返回

Md1返回

所以,通过结果我们看出中间件的执行顺序:

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

中间件应用

1. 做IP访问频率限制

某些IP访问服务器的频率过高,进行拦截,比如限制每分钟不能超过20次。

Cookie与Session

Cookie翻译成中文是小甜点,小饼干的意思。在HTTP中它表示服务器送给客户端浏览器的小甜点。其实Cookie是key-value结构,类似于一个python中的字典。随着服务器端的响应发送给客户端浏览器。然后客户端浏览器会把Cookie保存起来,当下一次再访问服务器时把Cookie再发送给服务器。 Cookie是由服务器创建,然后通过响应发送给客户端的一个键值对。客户端会保存Cookie,并会标注出Cookie的来源(哪个服务器的Cookie)。当客户端向服务器发出请求时会把所有这个服务器Cookie包含在请求中发送给服务器,这样服务器就可以识别客户端了!


cookie可以理解为每一个浏览器针对每一个服务器创建的key-value结构的本地存储文件


(1)cookie流程图

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

第一次访问设置了cookie键值返回给浏览器,浏览器进行了保存,下次访问时会带着这组键值(cookie)去访问浏览器。信息保存在浏览器并不怎么安全。

(2)cookie语法

# (1) 设置cookie:
res = HttpResponse(...) 或 rep = render(request, ...) 或 rep = redirect() 
res.set_cookie(key,value,max_age...)
res.set_signed_cookie(key,value,salt='加密盐',...) 
# (2) 获取cookie:
request.COOKIES     request.get_signed_cookie('xx',salt='xxxxxx')  
# (3) 删除cookie
response.delete_cookie("cookie_key",path="/",domain=name).

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

当我们访问不设置cookie的视图函数(含有csrf是因为我没有将中间件注释掉,后面会单独讲csrf)

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

20210527153548522.png

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

当我们访问xx

20210527153548522.png

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

session

Django 提供对匿名会话(session)的完全支持。这个会话框架让你可以存储和取回每个站点访客任意数据。它在服务器端存储数据, 并以cookies的形式进行发送和接受数据。

(1)session流程图


’第一次‘访问服务器,服务器会去请求中所携带的cookie里去get一个sessionid(名字可以去配置里改)的键去获取值,如果没有,则会在服务器创建一个键session_key,对应的值为session_data,(session_data里是与用户相关的一组组数据),将session_key返回给浏览器,此时浏览器里的cookie便多了一组{sessionid:'wfwafa'}的键值。而服务器那遍也可以根据用户的session_key找到对应的session_data从里面获取数据了。


等到下次访问的时候,服务器根据浏览器请求所携带的cookie找到对应的session_data,再去根据客户要求对里面的东西进行增删改查。

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

(2)session语法与案例


# 1、设置Sessions值

      request.session['session_name'] ="admin"

# 2、获取Sessions值

      session_name = request.session["session_name"]

# 3、删除Sessions值

      del request.session["session_name"]

# 4、flush()

 # 删除当前的会话数据并删除会话的Cookie。这用于确保前面的会话数据不可以再次被用户的浏览器访问

​def s_login(request):
    if request.method == "GET":
        return render(request, "login.html")
    else:
        user = request.POST.get("user")
        pwd = request.POST.get("pwd")
        try:
            # user_obj = User.objects.get(user=user,pwd=pwd)
            # 写session
            # request.session["is_login"] = True
            # request.session["username"] = user_obj.user
            return redirect("/s_index/")
        except:
            return redirect("/s_login/")
def s_index(request):
    # 读session
    is_login = request.session.get("is_login")
    if is_login:
        username = request.session.get("username")
        return render(request, "index.html", {"user": username})
    else:
        return redirect("/s_login/")
'''
shop.html:
<p>
客户端最后一次访问时间:{{ last_time|default:"第一次访问" }}
</p>
<h3>商品页面</h3>
'''
def shop(request):
    last_time = request.session.get("last_time")
    now = datetime.datetime.now().strftime("%Y-%m-%d %X")
    request.session["last_time"] = now
    return render(request, "shop.html", {"last_time": last_time})
def s_logout(request):
    # request.session.flush()
    del request.session["username"]
    del request.session["is_login"]
    return redirect("/s_login/")

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

session 在服务器端,cookie 在客户端(浏览器)


session 默认被存在在服务器的一个文件里(不是内存)


session 的运行依赖 session id,而 session id 是存在 cookie 中的.


session 可以放在 文件、数据库、或内存中都可以。


用户验证这种场合一般会用 session

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

20210527153548522.png

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

20210527153548522.png

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

还有默认值

(3)session配置

# Django默认支持Session,并且默认是将Session数据存储在数据库中,即:django_session 表中。

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

# 配置 settings.py
    SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默认)
    SESSION_COOKIE_NAME = "sessionid"               # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
    SESSION_COOKIE_PATH = "/"                               # Session的cookie保存的路径(默认)
    SESSION_COOKIE_DOMAIN = None                             # Session的cookie保存的域名(默认)
    SESSION_COOKIE_SECURE = False                            # 是否Https传输cookie(默认)
    SESSION_COOKIE_HTTPONLY = True                           # 是否Session的cookie只支持http传输(默认)
    SESSION_COOKIE_AGE = 1209600                             # Session的cookie失效日期(2周)(默认)
    SESSION_EXPIRE_AT_BROWSER_CLOSE = False                  # 是否关闭浏览器使得Session过期(默认)
    SESSION_SAVE_EVERY_REQUEST = False                       # 是否每次请求都保存Session,默认修改之后才保存(默认)

用户认证组件

Django默认已经提供了认证系统Auth模块,我们认证的时候,会使用auth模块里面给我们提供的表。(基于session进一步封装,帮助我们登录认证)认证系统包含:


用户管理


权限


用户组


密码哈希系统


用户登录或内容显示的表单和视图


一个可插拔的后台系统 admin  

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

(1)Django用户模型类

Django认证系统中提供了用户模型类User保存用户的数据,默认的User包含以下常见的基本字段:

image.png

上面缺少一些字段,所以后面我们会对当前内置的用户模型进行改造,比如说它里面没有手机号字段,后面我们需要加上。

(2)重要方法

Django 用户认证(Auth)组件需要导入 auth 模块

# 认证模块
from django.contrib import auth
# 对应数据库用户表,可以继承扩展
from django.contrib.auth.models import User #这张表便是auth_user表1.

但是记得注册(新增)用户的时候不要用create而是create_user,因为这样保存的密码依旧是铭文的。同理用User.object.get()一样会用铭文去和密文匹配,除非拿到Django的加密算法不然会匹配失败。

(1)用户对象

create() # 创建一个普通用户,密码是明文的。
create_user() # 创建一个普通用户,密码是密文的。
create_superuser() # 与create_user() 相同,但是设置is_staff 和is_superuser 为True。
set_password(*raw_password*)
# 设置用户的密码为给定的原始字符串,并负责密码的。 不会保存User对象。当None为raw_password时,密码将设置为一个不可用的密码。
check_password(*raw_password*)
# 如果给定的raw_password是用户的真实密码,则返回True,可以在校验用户密码时使用。

(2)认证方法

auth.authenticate(username,password) 
# 将输入的密码转为密文去认证,认证成功返回用户对象,失败则返回None

(3)登录和注销方法

from django.contrib import auth
# 该函数接受一个HttpRequest对象,以及一个认证了的User对象。此函数使用django的session框架给某个已认证的用户附加上session id等信息。
auth.login() 
# 该函数接受一个HttpRequest对象,无返回值。当调用该函数时,当前请求的session信息会全部清除。该用户即使没有登录,使用该函数也不会报错。
auth.logout() 

(4)request.user

Django有一个默认中间件,叫做AuthenticationMiddleware,每次请求进来都会去session中去一个userid,取不到的话,赋值request.user = AnonymousUser() , 一个匿名用户对象。

当用户组件auth.login一旦执行,将userid到session中后,再有请求进入Django,将注册的userid对应的user对象赋值给request.user,即再后面的任何视图函数中都可以从request.user中取到该客户端的登录对象。

(5)自定义用户表


from django.contrib.auth.models import AbstractUser


设置Auth认证模块使用的用户模型为我们自己定义的用户模型


格式:“子应用目录名.模型类名”


AUTH_USER_MODEL = 'users.User'

Django其实有两个配置文件,我们平时用的经常改的在settings.py中,其实还有一个:

from django.conf import global_settings

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

这个全局settings点进去看一下就行了 一般要改的直接在项目目录下的settings里直接覆盖就行

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

(app名字.app的modal里的表名)

from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
    phone_num = models.IntegerField(verbose_name='电话号')

然后进行数据库迁移

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

示例

我们这里不做注册添加用户和继承续写auth表了,我们在python console自己创建几个用户,记得用create_user()

(先导入这张表望截图了from django.contrib.auth.models import User)

96447814-120fc980-1245-11eb-938d-6ea408716c72.png20210527153548522.png

views.py

def index(request):
    # request.user:当前登录对象
    '''
        1. 在AuthenticationMiddleware中间件的process_request方法中取user_id
            user_id = request.session.get('user_id')
        2.  导入User表 from django.contrib.auth.models import User
            user = User.object.get(pk=user_id)
            if user:
                request.user = user
            else:
                # 取不到返回一个匿名用户 所有属性都为零值(空或者None)
                request.user = AnonymousUser()
        因为是在中间件中完成因此在任何视图函数中都可以使用request.user
        只要完成了登录即auth.login(request, user) request.user就是登录对象
        如果没有登录过或者失败,那就是匿名对象
    '''
    if request.user.id:  # 随便取request.user.name
        return HttpResponse('登陆成功')
    else:
        return HttpResponse('请先去登录')
def login(request):
    if request.method == 'GET':
        return render(request, 'login.html')
    else:
        account = request.POST.get('account')
        pwd = request.POST.get('pwd')
        # 找到返回对象 没找到返回None
        user = auth.authenticate(username=account, password=pwd)
        if user:
            # request.session['user_id'] = user.pk
            auth.login(request, user)  # 登陆成功后如果是第一次访问session表里的session就新增了
            return redirect(reverse('students:index'))
        else:
            return HttpResponse('登陆失败')
def logout(request):
    # request.session.flush()
    auth.logout(request)
    return redirect(reverse('students:login'))

登陆前

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

登陆中

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

登录后(记得注掉csrf中间件或者form表单里加上{%csrf_token%}

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

session表

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

Django的分页器

批量插入数据

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

from django.core.paginator import Paginator

(1) index视图

def index(request):
    '''
    批量导入数据:
    Booklist=[]
    for i in range(100):
        Booklist.append(Book(title="book"+str(i),price=30+i*i))
    Book.objects.bulk_create(Booklist)
    分页器的使用:
    book_list=Book.objects.all()
    paginator = Paginator(book_list, 10)
    print("count:",paginator.count)           #数据总数
    print("num_pages",paginator.num_pages)    #总页数
    print("page_range",paginator.page_range)  #页码的列表
    page1=paginator.page(1) # 第1页的page对象
    for i in page1:         # 遍历第1页的所有数据对象
        print(i)
    print(page1.object_list) #第1页的所有数据
    page2=paginator.page(2)
    print(page2.has_next())            #是否有下一页
    print(page2.next_page_number())    #下一页的页码
    print(page2.has_previous())        #是否有上一页
    print(page2.previous_page_number()) #上一页的页码
    # 抛错
    #page=paginator.page(12)   # error:EmptyPage
    #page=paginator.page("z")   # error:PageNotAnInteger
    '''
    book_list = Book.objects.all()
    paginator = Paginator(book_list, 10)
    page = request.GET.get('page', 1)
    current_page = int(page)
    try:
        print(page)
        book_list = paginator.page(page)
    except PageNotAnInteger:
        book_list = paginator.page(1)
    except EmptyPage:
        book_list = paginator.page(paginator.num_pages)
    return render(request, "index.html", {"book_list": book_list, "paginator": paginator, "currentPage": current_page})

(2) index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" 
    integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<div class="container">
    <h4>分页器</h4>
    <ul>
        {% for book in book_list %}
             <li>{{ book.title }} -----{{ book.price }}</li>
        {% endfor %}
     </ul>
    <ul class="pagination" id="pager">
                 {% if book_list.has_previous %}
                    <li class="previous"><a href="/index/?page={{ book_list.previous_page_number }}">上一页</a></li>
                 {% else %}
                    <li class="previous disabled"><a href="#">上一页</a></li>
                 {% endif %}
                 {% for num in paginator.page_range %}
                     {% if num == currentPage %}
                       <li class="item active"><a href="/index/?page={{ num }}">{{ num }}</a></li>
                     {% else %}
                       <li class="item"><a href="/index/?page={{ num }}">{{ num }}</a></li>
                     {% endif %}
                 {% endfor %}
                 {% if book_list.has_next %}
                    <li class="next"><a href="/index/?page={{ book_list.next_page_number }}">下一页</a></li>
                 {% else %}
                    <li class="next disabled"><a href="#">下一页</a></li>
                 {% endif %}
            </ul>
</div>
</body>
</html>

FBV与CBV

1 FBV :function based view

2 BCV:class based view

前后端分离模式

在开发Web应用中,有两种应用模式:

       1.前后端不分离[客户端看到的内容和所有界面效果都是由服务端提供出来的。

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

       2.前后端分离(把前端的界面效果(html,css,js分离到另一个服务端,python服务端只需要返回数据即可)

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

前端形成一个独立的网站,服务端构成一个独立的网站

api接口

应用程序编程接口(Application Programming Interface,API接口),就是应用程序对外提供了一个操作数据的入口,这个入口可以是一个函数或类方法,也可以是一个url地址或者一个网络地址。当客户端调用这个入口,应用程序则会执行对应代码操作,给客户端完成相对应的功能。


当然,api接口在工作中是比较常见的开发内容,有时候,我们会调用其他人编写的api接口,有时候,我们也需要提供api接口给其他人操作。由此就会带来一个问题,api接口往往都是一个函数、类方法、或者url或其他网络地址,不断是哪一种,当api接口编写过程中,我们都要考虑一个问题就是这个接口应该怎么编写?接口怎么写的更加容易维护和清晰,这就需要大家在调用或者编写api接口的时候要有一个明确的编写规范!!!


为了在团队内部形成共识、防止个人习惯差异引起的混乱,我们都需要找到一种大家都觉得很好的接口实现规范,而且这种规范能够让后端写的接口,用途一目了然,减少客户端和服务端双方之间的合作成本。


目前市面上大部分公司开发人员使用的接口实现规范主要有:restful、RPC。


RPC( Remote Procedure Call ): 翻译成中文:远程过程调用[远程服务调用]. 从字面上理解就是访问/调用远程服务端提供的api接口。这种接口一般以服务或者过程式代码提供。


服务端提供一个唯一的访问入口地址:http://api.xxx.com/http://www.xx.com/api 或者基于其他协议的地址


客户端请求服务端的时候,所有的操作都理解为动作(action),一般web开发时,对应的就是HTTP请求的post请求


通过请求体参数,指定要调用的接口名称和接口所需的参数


action=get_all_student&class=301&sex=1


m=get_all_student&sex=1&age=22


command=100&sex=1&age=22


rpc接口多了,对应函数名和参数就多了,前端在请求api接口时难找.对于年代久远的rpc服务端的代码也容易出现重复的接口


restful: 翻译成中文: 资源状态转换.(表征性状态转移)


把服务端提供的所有的数据/文件都看成资源, 那么通过api接口请求数据的操作,本质上来说就是对资源的操作了.


因此,restful中要求,我们把当前接口对外提供哪种资源进行操作,就把资源的名称写在url地址。


web开发中操作资源,最常见的最通用的无非就是增删查改,所以restful要求在地址栏中声明要操作的资源是什么。然后通过http请求动词来说明对该资源进行哪一种操作.


POST http://www.xxx.com/api/students/ 添加学生数据


GET http://www.xxx.com/api/students/ 获取所有学生


GET http://www.xxx.com/api/students/1/ 获取id=pk的学生


DELETE http://www.xxx.com/api/students/1/ 删除id=pk的一个学生


PUT http://www.xxx.com/api/students/1/ 修改一个学生的全部信息 [id,name,sex,age,]


PATCH http://www.xxx.com/api/students/1/ 修改一个学生的部分信息[age]


也就是说,我们仅需要通过url地址上的资源名称结合HTTP请求动作,就可以说明当前api接口的功能是什么了。restful是以资源为主的api接口规范,体现在地址上就是资源就是以名词表达。rpc则以动作为主的api接口规范,体现在接口名称上往往附带操作数据的动作。

RESTful API规范

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征)性状态转移。 它首次出现在2000年Roy Fielding的博士论文中。


RESTful是一种专门为Web 开发而定义API接口的设计风格,尤其适用于前后端分离的应用模式中。


这种风格的理念认为后端开发任务就是提供数据的,对外提供的是数据资源的访问接口,所以在定义接口时,客户端访问的URL路径就表示这种要操作的数据资源。


而对于数据资源分别使用POST、DELETE、GET、UPDATE等请求动作来表达对数据的增删查改。

image.png

restful规范是一种通用的规范,不限制语言和开发框架的使用。事实上,我们可以使用任何一门语言,任何一个框架都可以实现符合restful规范的API接口。


参考文档:RESTful 架构详解 | 菜鸟教程


接口实现过程中,会存在幂等性。所谓幂等性是指代客户端发起多次同样请求时,是否对于服务端里面的资源产生不同结果。如果多次请求,服务端结果还是一样,则属于幂等接口,如果多次请求,服务端产生结果是不一样的,则属于非幂等接口。

请求方式 是否幂等 是否安全
GET 幂等 安全
POST 不幂等 不安全
PUT/PATCH 幂等 不安全
DELETE 幂等 不安全

CBV使用

之前我们用的视图函数叫FBV(也就是函数型视图函数),这里我们来试试CBV(类视图函数)的写法。类视图函数可以让代码看起来更简洁,用起来更方便。

# FBV
# def index(request):
#     if request.method == "GET":
#
#         return HttpResponse("GET")
#     elif request.method == "POST":
#
#         return HttpResponse("POST")
#
#     elif request.method == "DELETE":
#         return HttpResponse("DELETE")
# CBV模式: 基于restful开发
class IndexView(View):
    def get(self, request):
        return HttpResponse("CBV GET")
    def post(self, request):
        return HttpResponse("CBV POST")
class BookView(View):
    def get(self, request):
        # 获取数据
        book_list = Book.objects.all()
        # 序列化:json
        data_json = serializers.serialize("json", book_list)
        return HttpResponse(data_json, content_type="json")
# FBV模式
# path('index/', views.index),
# CBV模式
path("index/",views.IndexView.as_view()),
path("books/",views.BookView.as_view())

csrftoken(跨站请求伪造)

CSRF(Cross-Site Request Forgery,跨站点伪造请求)是一种网络攻击方式,该攻击可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击站点,从而在未授权的情况下执行在权限保护之下的操作,具有很大的危害性。具体来讲,可以这样理解CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件、发消息,盗取你的账号,添加系统管理员,甚至于购买商品、虚拟货币转账等。

-- 企业邮箱

-- pornhub.com

参考文章


token其实就是一个令牌,用于用户验证的,token的诞生离不开CSRF。正是由于上面的Cookie/Session的状态保持方式会出现CSRF,所以才有了token。


解除中间件注释


无csrf_token数据的post请求

基本使用

一、form表单提交

在html页面form表单中直接添加{% csrf_token%}

多了句

<input type="hidden" name="csrfmiddlewaretoken" value="HL4osWvcOA03Mv4YEaBmUGdujF4axvtcmvM02jhbrilpgRW7wLf1e2cmUE7r8CC4">

64位随机字符串,前32是salt后32是token


这样提交post请求时会多一组键值对,执行到 'django.middleware.csrf.CsrfViewMiddleware'中间件的时候内部会进行校验。浏览器请求携带cookie里的token和请求头/体里的token进行比对(两者不一样)。


下同

二、ajax提交

也要导入 {% csrf_token %} 不然没有[name="csrfmiddlewaretoken"]

方式1:放在请求数据中。

$.ajax({
  url: '/csrf_test/',
  method: 'post',
  data: {'name': $('[name="name"]').val(),
         'password': $('[name="password"]').val(),
         'csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val()           
        },
  success: function (data) {
    console.log('成功了')
    console.log(data)           },
})

方式2:放在请求头(个人建议)

$.ajax({
            url: '/csrf_test/',
            method: 'post',
            headers:{'X-CSRFToken':'token值'},  // 注意放到引号里面
            data:{}
}

全局使用,局部禁csrf

(1) 在视图函数上加装饰器

from django.views.decorators.csrf import csrf_exempt,csrf_protect
@csrf_exempt
def 函数名(request):  # 加上装饰器后,这个视图函数,就没有csrf校验了

(2) 视图类

from django.views.decorators.csrf import csrf_exempt,csrf_protect
from django.utils.decorators import method_decorator
@method_decorator(csrf_exempt,name='dispatch')
class index(View):
    def get(self,request):
        return HttpResponse("GET")
    def post(self,request):
        return HttpResponse("POST")

示例(前后端分离 不适用渲染{%csrf_token%}

def login(request):
    if request.method == 'GET':
        print('oooooooopppppppppppp')
        return render(request, 'students/login.html')
    else:
        account = request.POST.get('account')
        pwd = request.POST.get('password')
        user = auth.authenticate(username=account, password=pwd)
        dct = {'state': False, 'msg': '登陆失败'}
        if user:
            auth.login(request, user)
            dct['state'] = True
            dct['msg'] = ''
        return JsonResponse(dct)
def get_tokens(request):
    tok = get_token(request)
    return HttpResponse(tok)
<script>
    $.ajax({
        url:'http://127.0.0.1:8000/students/get_tokens/',
        success:function (res){
            console.log(res)
            localStorage.setItem('token',res)
        }
    })
    $('#aja_btn').click(function (){
        $.ajax({
            // headers:{'X-CSRFToken':localStorage.getItem('token')},
            url: 'http://127.0.0.1:8000/students/login/',
            type:'post',
            data:{
                csrfmiddlewaretoken:localStorage.getItem('token'),
                account:$('#Input').val(),
                password:$('#Password').val()
            },
            success:function (res){
                console.log(res)
                if(res.state){
                    location.href = '/students/index/'
                }else {
                    $('#err').html(res.msg)
                }
            }
        })
    })
</script>

记得要确保get_tokens在白名单中。 当然上面的示例放在头里也可以,当头和体里都找不到的时候才会出问题。

原理:

我们可以看到 cookie中的csrf-token和我们的token并不一样

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

当我们的csrf中间件在头或者数据中找到我们提交的token时,由于每次请求携带着cookie,它会根据这两组字符串进行比对,解出唯一的secret。(前32位为加密盐,后32位为数据)

注:不光是我们自己写的网站有这一机制,其它网站也存在,如csdn:

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

上传文件

form表单上传文件

(记得修改表单提交的编码方式)

<h3>form表单上传文件</h3>
<form action="/upload_file/" method="post" enctype="multipart/form-data">
    <p><input type="file" name="upload_file_form"></p>
    <input type="submit">
</form>
def index(request):
    return render(request,"index.html")
def upload_file(request):
    print("FILES:",request.FILES)# 得到文件对象类字典
    print("POST:",request.POST)# 得到类字典里有csrftoken(如果编码方式没变 还能得到文件名)
    file = request.FILES
    with open(file.name,'wb') as f:
        for i in file:
            f.write(i)
    return HttpResponse("上传成功!")

写文件的时候记得用‘wb'  文件名可以直接 request.FILES.get('file').name

Ajax(基于FormData)

FormData是什么呢?

XMLHttpRequest Level 2添加了一个新的接口FormData.利用FormData对象,我们可以通过JavaScript用一些键值对来模拟一系列表单控件,我们还可以使用XMLHttpRequest的send()方法来异步的提交这个"表单".比起普通的ajax,使用FormData的最大优点就是我们可以异步上传一个二进制文件.

所有主流浏览器的较新版本都已经支持这个对象了,比如Chrome 7+、Firefox 4+、IE 10+、Opera 12+、Safari 5+。

<h3>Ajax上传文件</h3>
<p><input type="text" name="username" id="username" placeholder="username"></p>
<p><input type="file" name="upload_file_ajax" id="upload_file_ajax"></p>
<button id="upload_button">提交</button>
{#注意button标签不要用在form表单中使用#}
<script>
    $("#upload_button").click(function(){
        var username=$("#username").val();
        var upload_file=$("#upload_file_ajax")[0].files[0];
        var formData=new FormData();
        formData.append("username",username);
        formData.append("upload_file_ajax",upload_file);
        $.ajax({
            url:"/upload_file/",
            type:"POST",
            data:formData,
            contentType:false,
            processData:false,
            success:function(){
                alert("上传成功!")
            }
        });
    })
</script>
def index(request):
    return render(request,"index.html")
def upload_file(request):
    print("FILES:",request.FILES)
    print("POST:",request.POST)
    return HttpResponse("上传成功!")

ImageField 和 FileField

ImageField 和 FileField 可以分别对图片和文件进行上传到指定的文件夹中。

1.在下面的 models.py 中 :

picture = models.ImageField(upload_to='avatars/', default="avatars/default.png",blank=True, null=True)

注:定义 ImageField 字段时必须制定参数 upload_to这个字段要写相对路径,


这个参数会加在 settings.py 中的 MEDIA_ROOT后面, 形成一个路径, 这个路径就是上 传图片的存放位置,默认在Django项目根路径下,也就是MEDIA_ROOT默认是Django根目录

所以要先设置好 mysite/settings.py中的 settings.py 中的 MEDIA_ROOT

class Userinfo(models.Model):
    name = models.CharField(max_length=32)
    avatar_img = models.FileField("avatars/") 
username = request.POST.get("username")
#获取文件对象
file = request.FILES.get("file")   
#插入数据,将图片对象直接赋值给字段
user = Userinfo.objects.create(name=username,avatar_img=file)

Django会在项目的根目录创建avatars文件夹,将上传文件下载到该文件夹中,avatar字段保存的是文件的相对路径。


2.在 mysite/settings.py中 :


MEDIA_ROOT = os.path.join(BASE_DIR,"media")

MEDIA_URL='/media/'

MEDIA_ROOT:存放 media 的路径, 这个值加上 upload_to的值就是真实存放上传图片文件位置


MEDIA_URL:给这个属性设值之后,静态文件的链接前面会加上这个值,如果设置这个值,则UserInfo.avatar.url自动替换成:/media/avatars/default.png,可以在模板中直接调用:。

3.url.py:

from django.views.static import serve
# 添加media 配置
re_path(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),

最后再给大家补充一个用户文件夹路径

def user_directory_path(instance, filename):
    return os.path.join(instance.name,"avatars", filename)
class Userinfo(models.Model):
    name = models.CharField(max_length=32)
    avatar_img = models.FileField(upload_to=user_directory_path)  .

4.FileField 和 ImageFiled 相同。


相关文章
|
7天前
|
前端开发 数据库 数据安全/隐私保护
|
5月前
|
SQL 前端开发 JavaScript
Django零基础-快速了解基本框架笔记-附案例
Django零基础-快速了解基本框架笔记-附案例
62 0
|
5月前
|
存储 安全 数据库
关于“Python”Django 管理网站的核心知识点整理大全52
关于“Python”Django 管理网站的核心知识点整理大全52
36 0
|
5月前
|
JSON 前端开发 JavaScript
前端知识笔记(三十七)———Django与Ajax
前端知识笔记(三十七)———Django与Ajax
47 0
|
5月前
|
JSON 前端开发 JavaScript
前端知识笔记(二)———Django与Ajax
前端知识笔记(二)———Django与Ajax
56 0
|
存储 缓存 中间件
Django相关知识点回顾(二)
Django相关知识点回顾(二)
|
JSON 关系型数据库 数据库
|
Python
Django知识点-URL路由 name=
Django知识点-URL路由 name=
68 0
|
SQL XML 缓存
Django知识点笔记(3)
Django知识点笔记(3)
Django知识点笔记(3)
|
SQL 数据可视化 前端开发
Django知识点笔记(2)
Django知识点笔记(2)
Django知识点笔记(2)