练习+跨域问题
以下的代码实现跟实战项目供应链中相似功能的写法有些许不一样, 但达到的效果差不多.. 不必纠结.
# 需求
练练手,完成一个练习..
需求如下:
1. 有车型(CarModel)、车厂(CarFactory)、经销商(Distributor)三张表.
一个车厂可以生产多种车型、一个经销商可以出售多种车型、一个车型可以有多个经销商出售.
车型 - 车型名, 车型出厂价, 车厂id
车厂 - 车场名, 车厂地址, 联系电话
经销商 - 经销商名, 地址, 联系电话
2. 有用户表,基于Django内置user表,扩展mobile字段
3. 编写登陆接口, jwt的认证方式返回token
格式为: {status:100,msg:'登陆成功',token:'12faf73as'}
4. 管理员登陆后可以 新增,删除 车型、车厂、经销商
5. 普通用户登陆后,可以查看车型 (群查分页/有哪些车型,单查/某个车型的详情)
查车型返回车型信息,车厂名字,经销商名字和电话
加分项:
用户注册接口
管理员有用户锁定、删除功能
2
3
4
5
6
7
8
9
10
11
12
13
14
15
准备工作: 用pycharm创建新的Django项目: drf_car
# 创建四张表
step1: 创建表模型
step2: 在settings.py配置文件中进行用户扩展表的配置
step3: 执行两条数据库表的迁移命令.
app01/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
# -- 用户表
class UserInfo(AbstractUser):
mobile = models.CharField(max_length=32, verbose_name='用户电话')
# is_lock = models.BooleanField(default=True, verbose_name='是否锁定')
# -- 车型表
class CarModel(models.Model):
name = models.CharField(max_length=32, verbose_name='车型名')
# -- 出厂价 CharField、IntegerField、DecimalField 皆可
price = models.CharField(max_length=32, verbose_name='出厂价')
# -- 写在多的那方
carFactory = models.ForeignKey(to='CarFactory', on_delete=models.CASCADE)
# -- 虚拟字段 n:n该字段写在哪边都行
distributor = models.ManyToManyField(to='Distributor')
# -- 经销商表
class Distributor(models.Model):
name = models.CharField(max_length=32, verbose_name='经销商名')
addr = models.CharField(max_length=32, verbose_name='地址')
phone = models.CharField(max_length=32, verbose_name='电话')
# -- 车厂表
class CarFactory(models.Model):
name = models.CharField(max_length=32, verbose_name='车厂名')
addr = models.CharField(max_length=32, verbose_name='车厂地址')
phone = models.CharField(max_length=32, verbose_name='车厂电话')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
settings.py
AUTH_USER_MODEL = 'app01.UserInfo'
执行迁移命令
pycharm - Tools - Run manage.py Task..
(若更改了项目路径,就得自己手动写完整的命令啦!!)
"""命令:"""
makemigrations
migrate
使用的是默认的sqlite3,没有使用mysql
使用pycharm连接sqlite3数据库. 数据库 - 数据源 sqlite - 选择文件路径 - 先Test测试下连接是否成功 - 确认连接
(这里使用jdbc去连接的,何为jdbc?)
-- jdbc是java中连接关系型数据库的一个驱动,java官方写了一些连接数据库的接口.
不同的厂商oracle、mysql、sqlite基于这些接口来写具体的实现.
接口规范了子类的行为,方法是一样.. 方法里具体的实现不同.
-- pycharm是用java写的.
连接好后,可以观察到,通过app01的model模型创建出了7张表,都默认以app01作为前缀.
app01_carfactory
app01_carmodel
app01_carmodel_distributor
app01_distributor
app01_userinfo
app01_userinfo_groups
app01_userinfo_user_permissions
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
准备工作: 通过createsuperuser
命令创建两个超级用户!!
dc admin123 手动添加mobile手机号字段的值 123123123 超级用户
egon admin123 手动添加mobile手机号字段的值 234234234 并修改is_is_superuser字段的值为false. 变为普通人.
# 登陆接口
需求: 编写登陆接口, jwt的认证方式返回token
回顾: 第一次学drf/drf的路由组件.md 的内容
逻辑:前端传递<用户名密码or手机号密码>到后端. 若校验通过,签发Token.
若视图类就只继承APIView.里面写get、post等方法(自己写5个接口)
那么路由必须写成 path('api/login/', views.UserView.as_view()),
-- 使用postman对http://127.0.0.1:8000/api/login/ 发送post请求就会访问该路由. api/login/ 是我们自己商定的.
当然,若想自动生成路由.视图类必须得继承ViewSetMixin! 再加上APIView,即继承 ViewSet!!
(并不是说只有ViewSet这一种继承方式,可根据不同的需求,使用不同的视图组件进行不同组合.唯一确定的是必须包含ViewSetMixin)
这里我们采用自动生成路由的形式!还使用了action装饰器!!
2
3
4
5
6
7
8
# app01/views.py
下方是自定义用户表签发Token.
回顾: 第一次学drf/jwt.md/自定义用户表签发token 对应的内容
还有可以快速签发和认证,但快速的方式发送的json字符串的用户名必须是 username, 密码必须是 password.
回顾: 第一次学drf/jwt.md/快速签发和认证 对应的内容
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import ViewSet
from rest_framework_jwt.settings import api_settings
from .models import UserInfo
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
class UserView(ViewSet):
@action(methods=['POST'], detail=False)
def login(self, request):
# 1.取到前端传入的手机号和密码
mobile = request.data.get('mobile')
password = request.data.get('password')
# 2.去数据库中校验,若有这个用户,则登陆成功,签发token
# -- 这种方式不对,密码是明文的
# from .models import UserInfo
# UserInfo.objects.filter(mobile=mobile, password=password)
# -- 有问题,authenticate只能使用用户名和密码
# from django.contrib.auth import authenticate
# user = authenticate(request, mobile=mobile, password=password)
user = UserInfo.objects.filter(mobile=mobile).first()
if user and user.check_password(password):
# 用户正常登陆,签发Token
payload = jwt_payload_handler(user) # -- 通过user拿到payload
token = jwt_encode_handler(payload) # -- 通过payload拿到token
return Response(data={'code': 100, 'mag': '登录成功', 'token': token})
else:
return Response(data={'code': 999, 'msg': '手机号或密码错误.'})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework.routers import SimpleRouter
from app01 import views
router = SimpleRouter()
router.register('user', views.UserView, 'user')
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include(router.urls)),
]
2
3
4
5
6
7
8
9
10
11
12
13
# Postman验证
运行Django项目时,报rest_framework_jwt模块的错误,我确信写的没问题... 卸载重新安装该模块就好了.. 我真的会谢!!
# 其它需求实现
需求:
管理员登陆后可以 <新增,删除> 车型、车厂、经销商
普通用户登陆后,可以查看车型 (群查分页/有哪些车型,单查/某个车型的详情)
○ 管理员的操作
step1: 快速编写 车型(CarModel)、车厂(CarFactory)、经销商(Distributor)这三张表的接口 每张表的5个接口!!共15个.
step2: 写好后, 根据需求 <新增,删除> , 对写好的代码进行需改. 每个视图类的5个接口变为2个接口..Hhh
视图类继承 ModelViewSet --> 改为继承 GenericViewSet, CreateModelMixin, DestroyModelMixin
step3: 根据需求 <登陆后> <管理员> 需要添加 认证和权限 的功能.
○ 普通人的操作
step1: 再次快速编写 车型(CarModel) 的5个接口,根据需求
视图类继承 ModelViewSet --> 改为继承 GenericViewSet, ListModelMixin, RetrieveModelMixin
step2: 根据需求 <登陆后> 需要添加 认证和分页 的功能.
需求分析总结:
管理员操作对应的3个视图类所实现的接口 登陆 + 管理员 才能访问.
普通人操作对应的1个视图类所实现的接口 登陆 就可以访问,不管当前登陆的是管理员还是普通用户!!
# app01/serializer.py
在app01下新建serializer.py文件,写入以下内容,用于序列化
from .models import CarModel, CarFactory, Distributor
from rest_framework import serializers
class CarModelSerializer(serializers.ModelSerializer):
class Meta:
model = CarModel
fields = '__all__'
class CarFactorySerializer(serializers.ModelSerializer):
class Meta:
model = CarFactory
fields = '__all__'
class DistributorSerializer(serializers.ModelSerializer):
class Meta:
model = Distributor
fields = '__all__'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# app01/auth.py
在app01下新建auth.py文件,写入以下内容,用于认证
回顾: drf/10_jwt.md/自定义用户表的认证类 对应的内容
from rest_framework.authentication import BaseAuthentication
from rest_framework import exceptions
from rest_framework_jwt.settings import api_settings
import jwt
from .models import UserInfo
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
# -- 自定义认证类JWTAuthentication继承BaseAuthentication,重写authenticate方法.
class JWTAuthentication(BaseAuthentication):
def authenticate(self, request):
# -- 基本逻辑:认证通过,返回两个值;认证不通过,抛异常!
jwt_value = request.META.get("HTTP_TOKEN")
if not jwt_value:
# -- 登陆接口通过jwt的认证方式返回token,前端再次访问服务器带着Token..
raise exceptions.AuthenticationFailed("您没有携带Token,请携带Token后访问.")
try:
payload = jwt_decode_handler(jwt_value)
# -- payload里有用户ID,再通过用户id拿到当前登陆用户!
# 注:自己造出来的,后续取除了id,username这两字段以外,都可能有问题?? 嘛意思?
# 可优化:user = UserInfo(id=payload['user_id'], username=payload['username'])
user = UserInfo.objects.first(pk=payload['user_id']).first
except jwt.ExpiredSignature:
msg = '签名过期'
raise exceptions.AuthenticationFailed(msg)
except jwt.DecodeError:
msg = '解码错误'
raise exceptions.AuthenticationFailed(msg)
except jwt.InvalidTokenError:
msg = '不合法'
raise exceptions.AuthenticationFailed(msg)
# -- 返回的两个值,会给新的request.user,一旦认证通过,后续的request就能取出user!
# 也就是在权限类里我能拿到这个user.
return user, jwt_value
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# app01/permission.py
在app01下新建permission.py文件,写入以下内容,用于实现权限功能
回顾: drf/7_认证权限频率.md/权限类编写 对应的内容
from rest_framework.permissions import BasePermission
class SuperUserPermission(BasePermission):
def has_permission(self, request, view):
if request.user.is_superuser:
return True # -- 超级用户允许访问
else:
# -- 不是超级用户,在前端显示的信息是英文的
# 若想自定义显示的信息
self.message = "您不是超级用户,不能查看!!"
return False
2
3
4
5
6
7
8
9
10
11
12
# app01/serializer.py
在app01下新建page.py文件,写入以下内容,用于分页
from rest_framework.pagination import PageNumberPagination
# -- 基本分页类
class MyPageNumberPagination(PageNumberPagination):
page_size = 2
page_query_param = 'page'
page_size_query_param = 'size'
max_page_size = 5
2
3
4
5
6
7
8
9
# app01/views.py
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import ViewSet
from rest_framework.viewsets import ModelViewSet, GenericViewSet
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin
from rest_framework_jwt.settings import api_settings
from .models import UserInfo, CarModel, CarFactory, Distributor
from .serializer import CarModelSerializer, CarFactorySerializer, DistributorSerializer
from .page import MyPageNumberPagination
from .auth import JWTAuthentication
from .permission import SuperUserPermission
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
"""
登陆签发token
需求:编写登陆接口, jwt的认证方式返回token
"""
class UserView(ViewSet):
@action(methods=['POST'], detail=False)
def login(self, request):
mobile = request.data.get('mobile')
password = request.data.get('password')
user = UserInfo.objects.filter(mobile=mobile).first()
if user and user.check_password(password):
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return Response(data={'code': 100, 'mag': '登录成功', 'token': token})
else:
return Response(data={'code': 999, 'msg': '手机号或密码错误.'})
"""
管理员
需求:<管理员><登陆>后可以 新增,删除 车型、车厂、经销商
"""
# class CarModelView(ModelViewSet):
class CarModelView(GenericViewSet, CreateModelMixin, DestroyModelMixin):
# -- 注:想让其有分页、过滤功能,定义类变量parser_classes、filter_backends
queryset = CarModel.objects.all()
serializer_class = CarModelSerializer
# -- 认证、权限
authentication_classes = [JWTAuthentication] # -- 登陆后才能访问
permission_classes = [SuperUserPermission] # -- 超级用户才能访问
class CarFactoryView(GenericViewSet, CreateModelMixin, DestroyModelMixin):
queryset = CarFactory.objects.all()
serializer_class = CarFactorySerializer
authentication_classes = [JWTAuthentication]
permission_classes = [SuperUserPermission]
class DistributorView(GenericViewSet, CreateModelMixin, DestroyModelMixin):
queryset = Distributor.objects.all()
serializer_class = DistributorSerializer
authentication_classes = [JWTAuthentication]
permission_classes = [SuperUserPermission]
"""
普通人
需求:用户<登陆>后,可以查看车型 (群查分页/有哪些车型,单查/某个车型的详情)
"""
class CarModelSimpleView(GenericViewSet, ListModelMixin, RetrieveModelMixin):
queryset = CarModel.objects.all()
serializer_class = CarModelSerializer
# -- 认证、分页 不需要权限,只要登陆就能访问查看车型的接口,所以只设置了一个认证
authentication_classes = [JWTAuthentication]
pagination_class = MyPageNumberPagination
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# app01/urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework.routers import SimpleRouter
from app01 import views
router = SimpleRouter()
# -- 登陆接口
router.register('user', views.UserView, 'user')
# -- 管理员的接口 增/删 车型、车厂、经销商 -> 登陆 + 管理员 才能访问
router.register('car_model', views.CarModelView, 'car_model')
router.register('car_factory', views.CarFactoryView, 'car_factory')
router.register('distributor', views.DistributorView, 'distributor')
# -- 普通人的接口 查看车型(所有条分页/单条) -> 登陆 就能访问,不管是管理员还是普通用户
router.register('car_model_simple', views.CarModelSimpleView, 'CarModelSimpleView')
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include(router.urls)),
]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Postman验证
注:一般会将token过期时间设置为14天.. 默认的是300s.
# -- settings.py 里配置
import datetime
JWT_AUTH = {
# -- 可以指定过期时间,比如1天
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
}
2
3
4
5
6
7
▲ 登陆接口
▲ 新增车厂
同理, 管理员新增 经销商是一样的套路, 我直接在数据库里添加经销商的数据.. Hhh
▲ 删除车厂
同理, 管理员删除 车型、车厂、经销商都是一样的套路.. 就不一一实验啦!!
▲ 新增车型
有了车厂和经销商后,才能新增车型,因为车型model里有n:1和n:n的关联..
▲ 查看车型
查看的车型中, 经销商的信息显示的是id号, 可以编写代码显示经销商具体信息.. 略.
多说两句 (⁎⁍̴̛ᴗ⁍̴̛⁎)
若想打通前后端..
就登陆功能而言, 登陆上后, 前端需要将token保存起来 - 难点: 如何axios发送post请求,并且在请求头里带token.
管理员有任务锁定,删除功能..
前端列出一堆用户,一点击锁定,携带用户id和一个锁定标志0/1,将用户的is_lock字段置为True或False.
相应的登陆接口要改一改,若锁定了,就不让其登陆!
还有个难点: 跨域问题.
# 跨域问题及解决!!
# 同源策略
浏览器的同源策略:
它是浏览器的基本安全策略, 请求的url地址,必须与浏览器上的url地址处于同域上, 也就是域名,端口,协议相同.
也就是说不允许去不同的地址下拿数据!!
eg: 我在本地上的域名是127.0.0.1:8000,请求另外一个域名:127.0.0.1:8001 上的一段数据, 浏览器就会报错.目的: 防止恶意的网站窃取数据
若浏览器对javascript没有同源策略的保护
1) 那么做个仿淘宝前端 在页面中发送ajax请求 去淘宝服务器随便拿数据,都不需要后端啦.
2) 不同源的数据和资源(如HTTP头、Cookie、DOM、localStorage等)就能相互随意访问,根本没有隐私和安全可言.
会出现跨域问题的场景: 前后端分离 、web端/PC端 (移动端、前后端不分离不会出现这个问题.)
过程!!!: 浏览器的请求出去啦 -> 后端代码也执行啦 (访问已经发生) -> 但后端返回的数据被浏览器的同源策略给拦截啦.
已拦截跨源请求:
同源策略禁止读取位于 http://127.0.0.1:8001/SendAjax/ 的远程资源.(原因:CORS 头缺少 'Access-Control-Allow-Origin').
2
强调: 虽出现跨域问题,但请求出去啦, 服务端也执行啦. 所以post请求修改数据, 数据会成功修改, 只不过返回的数据前端接收不到..
# CORS
cors跨域资源共享, 是后端的一个技术. - 用于解决同源策略导致不能跨域的问题!!
简单来说, 服务端在响应头中加入一些字段, 就可以跨域啦!!
# 简单/非简单请求
浏览器将CORS请求分成两类: 简单请求 (simple request) 和 非简单请求 (not-so-simple request)
只要<同时>满足以下两大条件, 就属于简单请求; 否则为非简单请求.
1. 请求方法是以下三种方法之一:
HEAD
GET
POST
2. HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
★ Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
2
3
4
5
6
7
8
9
10
注: 也就是说Content-Type是json格式;或者get请求,请求头中带有一个Token. 这些请求都是非简单请求!!
浏览器对这两种请求的处理, 是不一样的!
简单请求只发送一次
非简单请求发送两次,第一次是OPTIPNS”预检“请求,只有“预检”通过后才再发送一次请求用于数据传输!
关于预检
- 请求方式:OPTIONS
- “预检”其实做检查,检查后端的跨域策略,如果通过则允许传输数据,检查不通过则不再发送真正想要发送的消息
- 如何“预检”
-> 若复杂请求是PUT等请求,则服务端需要设置 允许 <某请求> ,否则“预检”不通过
Access-Control-Request-Method
-> 如果复杂请求设置了请求头,则服务端需要设置允许 <某请求头> , 否则“预检”不通过
Access-Control-Request-Headers
So,我们会遇到这样一个情况,json数据发送post请求,跨域不通过,查看时发现发送的是OPTIONS请求!!
2
3
4
5
6
7
8
9
10
# 中间件 - 解决跨域
简单请求解决跨域:
<服务器>设置响应头: Access-Control-Allow-Origin ="域名"
或"*"
一般来说, 为了安全起见, 不会写*
, 只会写前端地址..
但有些公司就写的*
, 那就是意味着该公司允许你偷它的数据,Hhh. 非简单请求解决跨域:
1> “预检”请求时, 允许<请求方式>则需<服务器>设置响应头: Access-Control-Allow-Method ="允许的请求方法"
或"*"
2> “预检”请求时, 允许<请求头>则需<服务器>设置响应头: Access-Control-Allow-Headers ="eg:token"
或"*"
一般都设置为*
Ps: 一时间卡壳,域名是啥? 联想到DNS域名解析就顿悟了..
前端 HomeView.vue
<template>
<div class="home">
<h1>首页</h1>
<button class="btn btn-success" @click="handleClick">点击</button>
</div>
</template>
<script>
export default {
name: 'HomeView',
data() {
return {}
},
methods: {
handleClick() {
// -- 简单请求
// this.$axios.get(this.$settings.base_url + '/api/v1/home/banners/').then(res => {
// console.log(res.data)
// })
// -- 非简单请求,在请求头里随便加点东西,比如加个token.
this.$axios.get(this.$settings.base_url + '/api/v1/home/banners/', {headers: {'token': '123wea1'}}).then(res => {
console.log(res.data)
})
}
},
components: {}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
在 apps/home/文件夹下创建middle.py文件! 写入一下内容. 并在dev.py的中间件里注册!!
apps/home/models.py
# 随便导入一个dev.py中的中间件,查看其源码,仿写
# from django.contrib.sessions.middleware import SessionMiddleware
from django.utils.deprecation import MiddlewareMixin
# -- 自己处理跨域问题,跟web框架无关,所有的框架都可以这样处理.
class CorsMiddle(MiddlewareMixin):
def process_request(self, request):
# -- 看打印结果,验证简单请求发一次,非简单请求发两次.
print(request.method)
def process_response(self, request, response):
# -- 在响应头中加入Access-Control-Allow-Origin,处理了简单请求! -- 任意网址
response["Access-Control-Allow-Origin"] = "*"
# -- 处理非简单请求
if request.method == 'OPTIONS':
response['Access-Control-Allow-Headers'] = '*' # 允许所有的头 -- 任意请求头
response['Access-Control-Allow-Method'] = '*' # 允许所有的请求方式 -- 任意请求方式
return response
"""记得注册中间件
MIDDLEWARE = [
# ... ... ...
'home.middle.CorsMiddle',
]
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
简单请求验证
非简单请求验证
# django-cors-headers
django-cors-headers让我们写,自己也要会写.
写一个中间件,从配置文件中取,取了后进行映射.
比如:映射到response['Access-Control-Allow-Headers']等..
官方文档: https://github.com/adamchainz/django-cors-headers
step1: pip install django-cors-headers==3.8.0
不指定版本,安装最新的,会自动将django升到4.x.
step2: 注册该第三方app.
INSTALLED_APPS = [
# ... ...
'corsheaders',
]
2
3
4
step3: 在中间件里进行配置.
MIDDLEWARE = [
# ... ...
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
]
2
3
4
5
step4: 在dev.py中接着添加以下内容.
# ▲ 跨域问题
# CORS_ALLOW_CREDENTIALS = True
# CORS_ORIGIN_WHITELIST = (
# '*'
# )
# CORS_ORIGIN_ALLOW_ALL = True # -- 加上此句允许所有的跨域源, 该操作很危险
# -- 不设置CORS_ORIGIN_ALLOW_ALL = True,换成这一句也可以成功.. 主要是要看看官方文档.
CORS_ALLOWED_ORIGINS = ["http://localhost:8080",]
# -- 非简单请求的请求方式和请求头
# - 允许的请求方式
CORS_ALLOW_METHODS = (
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
'VIEW',
)
# - 允许请求体里的头
CORS_ALLOW_HEADERS = (
'XMLHttpRequest',
'X_FILENAME',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
'Pragma',
# -- 切记要设置请求头中额外允许添加的字段
'token',
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38