bbs技术点总结
js只要用到内置对象,直接用new生成就可以了
1. 用户注册上传头像
<div class="form-group">
<label for="myfile">头像
{% load static %}
<img id="myimg" src="{% static 'img/default.png' %}" alt="" width="80" style="margin-left: 20px"></label>
<input type="file" id="myfile" name="avater" style="display: none">
</div>
<script>
//文本域变化事件
$('#myfile').change(function () {
//文件阅读器对象
//1.先生成一个文件阅读器对象
let myFileReadObj = new FileReader(); // 文件阅读器
//2. 获取用户上传的头像文件
let fileObj = $(this)[0].files[0]; //获取文件
// 3.将文件对象交给文件阅读器读取
myFileReadObj.readAsDataURL(fileObj) //异步操作,io操作,这行代码文件还没有读完,就已经开始执行下一句代码,在前端不显示
//4.利用文件阅读器将文件展示到页面去 修改src属性
//等待文件阅读器加载完之后,在执行
myFileReadObj.onload = function(){ $('#myimg').attr('src',myFileReadObj.result)}
})
- 这里我们要用到文本与变化事件,利用文件阅读器对象,我们要先生成一个文件阅读器对象,
- 获取用户上传的头像文件读取出来
- 将文件对象交给文件阅读器读取出来文件
- 利用文件阅读器将文件展示到页面上去,就要修改src属性,
myFileReadObj.readAsDataURL(fileObj)
和$('#myimg').attr('src',myFileReadObj.result)
这两部是一个异步操作,如果你在执上一代码的同时文件还没有读取出来,同时他还在执行下一句代码,这样会造成你上传之后在前端是不显示你上传的头像,空白区,我们要等文件加载完毕在执行下一局,myFileReadObj.onload
$('#id_commit').click(function () {
// 发送ajax请求 我们发送的数据中即包含普通的键值也包含文件
let formDataObj = new FormData();
// 1.添加普通的键值对
{#console.log($('#myform').serializeArray()) // [{},{},{},{},{}] 只包含普通键值对#}
$.each($('#myform').serializeArray(),function (index,obj) {
{#console.log(index,obj)#} // obj = {}
formDataObj.append(obj.name,obj.value)
});
// 2.添加文件数据
formDataObj.append('avatar',$('#myfile')[0].files[0]);
// 3.发送ajax请求
$.ajax({
url:"",
type:'post',
data:formDataObj,
// 需要指定两个关键性的参数
contentType:false,
processData:false,
success:function (args) {
if (args.code==1000){
// 跳转到登陆页面
window.location.href = args.url
}els
// 如何将对应的错误提示展示到对应的input框下面
// forms组件渲染的标签的id值都是 id_字段名
$.each(args.msg,function (index,obj) {
{#console.log(index,obj) // username ["用户名不能为空"]#}
let targetId = '#id_' + index;
$(targetId).next().text(obj[0]).parent().addClass('has-error')
})
}
}
})
})
def register(request):
# 产生一个空对象
register_form = myforms.MyRegForm()
if request.method == 'POST':
back_dic = {'code':'', 'msg': ''}
# 校验数据是否合法
register_form = myforms.MyRegForm(request.POST)
# 判断数据是否合法
if register_form.is_valid():
clean_data = register_form.cleaned_data # 将校验通过的数据字典赋值给一个变量
# 将字典里面吗的confirm_password键值对删除
clean_data.pop('confirm_password')
# 用户头像
file_obj = request.FILES.get('avatar')
"""
针对用户头像一定要判断是否传之,不能直接添加到字典里面去
"""
if file_obj:
clean_data['avatar'] = file_obj
# 直接操作数据库保存到字典里面
models.UserInfo.objects.create_user(**clean_data) # 将键值对**打散传到数据库
# 判断正确跳转到登录页面
back_dic['url'] = '/login/'
else:
back_dic['code'] = 2000
back_dic['msg'] = register_form.errors
return JsonResponse(back_dic)
return render(request, 'register.html', locals())
前端:
-
头像的功能完成之后,剩下的就是将利用ajax将文件发送到后端,前端要先利用内置对象获取数据,添加不同键值对,我们可以利用
serializeArray()
拿到他所有的键值对,利用each循环拿到每一个对象的键值对 -
利用append添加文件数据,发送ajax请求,这里面我们要指定两个关键参数
contentType: false
,processData: false
, -
如果后端保存数据成功,就跳转到后端写好传过来的指定页面,如果校验数据失败就在input框下面展现出对应的错误
-
当我们看见错误的信息之后,如果把鼠标放上去,指定的错误就会消失,给所有的input框绑定获取焦点事件,将input下面的span标签和input外面的div标签修改内容和属性
后端
- 校验数据输入的是否合法,将合法的数据赋值给一个变量,赋值给一个变量方便我们删除确认密码的键值对,因为我们在写models的时候没有这个字段,针对用户头像一定要判断是否传之,不能直接添加到字典里面去,在models里面我们给他默认了一个头像
- 操作数据库保存数据,定义字典将信息返回给ajax,ajax都到在页面展示对应的页面信息
2. 生成登录验证码
如何生成一个验证码,然后点击它就可以刷新呢。
利用pip3 install pillow ,这个是图片相关的模块。
导入模块:from PIL import Image, ImageDraw, ImageFont
- Image 生成图片
- ImageDraw 能够在图片上添加东西
- ImageFont 控制字体的样式
推导1:
直接获取后端生成的图片二进制数据发送给前端
with open(r'static/img/111.jpg','rb') as f:
data = f.read()
return HttpResponse(data)
推导2
利用pillow模块动态产生图片
img_obj = Image.new('RGB',(430,35),'green')
img_obj = Image.new('RGB',(430,35),get_random())
# 将图片对象 保存起来
with open('xxx.png','wb') as f:
img_obj.save(f,'png')
# 再将文件对象读取出来
with open('xxx.png','rb') as f:
data = f.read()
return HttpResponse(data)
推导3
我们可以看到利用文件存储繁琐而且IO操作效率低,我们可以借助内存管理模块
from io import BytesIO,StringIO
img_obj = Image.new('RGB', (430, 35), get_random())
io_obj = BytesIO()
img_obj.save(io_obj,'png') # 生成一个内存管理器对象
return HttpResponse(io_obj.getvalue()) # 从内存管理器中读取二进制的图片数据返回给前端
推导4
写成图片验证码
img_obj = Image.new('RGB', (430, 35), get_random())
img_draw = ImageDraw.Draw(img_obj) # 产生一个画笔对象
img_font = ImageFont.truetype('static/font/222.ttf',30) # 字体样式 大小
最终成型
import random
def get_random():
return random.randint(0,255),random.randint(0,255),random.randint(0,255)
def get_code(request):
img_obj = Image.new('RGB', (430, 35), get_random()) # 注意:这里的430,35要和前端的一致
img_draw = ImageDraw.Draw(img_obj)
img_font = ImageFont.truetype('static/font/222.ttf',30)
code = ''
for i in range(5):
random_upper = chr(random.randint(65,90))
random_lower = chr(random.randint(97,122))
random_int = str(random.randint(0,9))
tmp = random.choice([random_lower,random_upper,random_int])
img_draw.text((i*60+60,-2),tmp,get_random(),img_font)
code += tmp
print(code)
request.session['code'] = code
io_obj = BytesIO()
img_obj.save(io_obj,'png')
return HttpResponse(io_obj.getvalue())
- 实现低级验证码图片刷新验证码,这样设置后每次点击图片相当于超后端发送一次get请求获取一个新的验证码图片
{# 点击验证码图片刷新验证码 #}
$('#code_img').click(function () {
$(this).attr('src', '{% url "get_code" %}?') // src='/get_code/?'url后面加?的操作
});
3. admin的使用
以前写图书的展示列表,我们给它添加增删改查的功能,特别的麻烦,现在有很多张表的展示,我们不可能慢慢的写这些功能,django给我们提供了admin后台管理,我们可以利用admin实现增删改查,添加数据。
首先要创建超级用户,只有超级用户才能够操作admin的这些功能。
刚开始登录进去的时候只用一张用户表,我们想要添加其他的表,必须添加注册
from django.contrib import admin
from app01 import models
# Register your models here.admin
admin.site.register(models.UserInfo)
admin.site.register(models.Blog)
admin.site.register(models.Article)
admin.site.register(models.Atricle2Tag)
admin.site.register(models.UpAndDown)
admin.site.register(models.Category)
admin.site.register(models.Tag)
admin.site.register(models.Comment)
这样我们就可以使用这些表,但是他们是英文的,我们可以在model.py里面改变他们的名字
class Meta:
verbose_name_plural='用户表'
我们进去操作添加添加一条数据显示的是对象,
class UserInfo(AbstractUser):
...
def __str__(self):
return self.username
避免造成不便语义不明,打印出来
在model里面的字段添加verbose_name='创建时间'
会在admin后天帮助我们把字段的名称改成中文
4. 制作站点
打开博客园,在博客园的url后面输入别人的站点名称就可以进到他们的站点,如https://home.cnblogs.com/the3times在后面输入the3times
就会进到这个人的博客园的主页,这个是url来配置的,利用urlre
匹配规则就可以实现
url(r'^(?P<username>w+)/$',views.site,name='site'),
5. media的配置
网站用户使用的静态文件默认放在static里面,用户上传的静态文件应该单独放在一个文件夹下,可以使用media配置,该配置可以让用户上传的所有的文件 固定存放在指定的文件夹下
settings.py
# 配置用户上传的文件
MEDIA_ROOT = os.path.jion(BASE_DIR,'media')
我们配置后,我们上传文件会自动生成media文件,比如我们注册上传的头像会到这个地方,你存的路径在数据库中也不会改变,
假如你在model.py里面存的路径是avatar/111.png
media里面就会多出一个存放头像的avatar/111.png
这个就是你存放的头像路径。
class UserInfo(AbstractUser):
avatar = models.FileField(upload_to='avatar/', default='avatar/default.png', verbose_name='用户头像')
在前端点击这个头像能够查看到这个头像,需要我们在后端开设一个指定的文件夹资源
在url.py配置参数
from django.views.static import serve
from bbs import settings
url(r'^media/(?P<path>.*)',serve,{'document_root':settings.MEDIA_ROOT}),
如果你还想暴漏更多资源,在settings修改文件夹名,想暴露源码也可以,问题就大了
最后在html配置,渲染后的路径/media/avatar/default.png/
<img class="media-object" src="/media/{{ article_obj.blog.userinfo.avatar }}" alt="..." width="40">
这样就可以看到别人的头像
6. 站点左边栏展示
左侧边栏展示如何展示的,要清楚orm的查询
# 1.查询当前用户所有分类及分类下的文章数
category_list=models.Category.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name','count_num','pk')
# 2.查询当前用户所有标签及标签的文章数
tag_list=models.Tag.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name','count_num','pk') # <QuerySet [('tank的标签一', 1), ('tank的标签二', 1), ('tank的标签三', 2)]>
# 3.按照日期归档
date_list=models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(count_num=Count('pk')).values_list('month','count_num')
这里的日期归档我们按照年月归档,但是数据库里面有年月日,我们如何把日去掉官方提供了一个方法
from django.db.models.functions import TruncMonth
Sales.objects
.annotate(month=TruncMonth('timestamp')) # Truncate to month and add to select list
.values('month') # Group By month
.annotate(c=Count('id')) # Select the count of the grouping
.values('month', 'c') # (might be redundant, haven't tested) select month and count
上面的日期归档就是按照这个模式搬出来的
7. 侧边栏筛选功能
- 显示分类做好之后,点击文章标签、分类、日期归档统计的链接后,显示该条件下的所有文章列表
博客园url设计案例
https://www.cnblogs.com/jason/tag/Python/ 标签
https://www.cnblogs.com/jason/category/850028.html 分类
https://www.cnblogs.com/jason/archive/2016/10.html 日期
规律:前面的还是站点名,后面是每个分类后的url
按照这个模式设计我们的url
- 个人站点文章是我们筛选后该站点用户所有的文章,这些标签,分类都是在在上面筛选过后加条件在筛选一次
- 设计url,处理该url视图函数进一步过滤符合条件的文章
- 为了显示在一个页面,就在站点的视图里面进行筛选,这样就不要开辟页面
- 按照上面的url设计在每个分类的后面还有参数,点击不同的文章分类后面出现不同的url,利用每个文章分类的主键值为文章列表
# url(r'^(?P<username>w+)/category/(d+)/',views.site),
# url(r'^(?P<username>w+)/tag/(d+)/',views.site),
# url(r'^(?P<username>w+)/archive/(w+)/',views.site),
前面是匹配站点的名称,后面跟不同的分类,最后匹配主键值
三句和成一句
url(r'^(?P<username>w+)/(?P<condition>category|tag|archive/)(?P<param>.*)/',views.site),
后端业务逻辑
def site(request,username,**kwargs):
# 先校验当前用户名的站点是否存在
user_obj = models.UserInfo.objects.filter(username=username).first()
# 不存在404页面
if not user_obj:
return render(request,'error.html')
# 先到个人站点
blog = user_obj.blog
# 查询当前个人站点下所有的文章
article_list = models.Article.objects.filter(blog=blog)
if kwargs:
# print(kwargs) # {'condition': 'tag', 'param': '1'}
condition = kwargs.get('condition')
param = kwargs.get('param')
# 判断用户到底想按照哪个条件筛选数据
if condition == 'category':
article_list = article_list.filter(category_id=param)
elif condition == 'tag':
article_list = article_list.filter(tags__id=param)
else:
year, month = param.split('-')
article_list = article_list.filter(create_time__year=year, create_time__month=month)
# 1.查询当前用户所有分类及分类下的文章数
category_list = models.Category.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name','count_num','pk')
# 2.查询当前用户所有标签及标签的文章数
tag_list = models.Tag.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name','count_num','pk')
# 3.按照日期归档
date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(count_num=Count('pk')).values_list('month','count_num')
return render(request,'site.html',locals())
解析:**kwargs
可能接收额外的参数,先写上去,判断kwargs是否有值,有值再进行操作,``` {'condition': 'tag', 'param': '1'}我们点击标签,后面出现我们url设计好的tag,
1是标签1的主键值,拿到之后,判断用户像按照哪个条件筛选的,
article_list = article_list.filter(tags__id=param)`这个在宅筛选当前站点用户所有的文章之后在进行一次条件筛选
前端
<!-- 标签 -->
{% for tag in tag_list %}
<p><a href="/{{ username }}/tag/{{ tag.pk }}/">{{ tag.name }}({{tag.c}})</a></p>
{% endfor %}
<!-- 分类 -->
{% for category in category_list %}
<p><a href="/{{username}}/category/{{ category.pk }}">{{ category.name }}({{category.c}})</a></p>
{% endfor %}
<!-- 归档 -->
{% for archive in archive_list %}
<p><a href="/{{ username }}/archive/{{ archive.month|date:'Y-m' }}/">{{ archive.month|date:'Y年m月' }}({{ archive.c }})</a></p>
{% endfor %}
/{{username}}/category/{{ category.pk }}
把这些url补全,这里的主键值,我们在获取文章查询当前用户所有分类及分类的时候写上去,这样我们就可以在前端拿到主键值values_list('name','count_num','pk')
8. inclusion_tag的制作
我们制作完站点页面之后制作文章详情页,我们可以在站点详情页上面显示文章,但是在一个页面上显示,显示的代码在后端特别的冗余,我们从新开辟一个文章详情页
- 我们建立文章详情页之后,左侧的侧边栏就不会显示,因为我们需要站点的一些数据
- 该侧边栏在许多页面显示。
- 直接拷贝代码冗余
将侧边栏制作成inclusion_tag
-
在应用下创建一个名字必须叫templatetags文件夹
-
在该文件夹内创建一个任意名字的py文件
-
在该py文件内固定先写两汉代码
from django import template register = template.Library()
后端封装代码
from django import template
from django.db.models import Count
from django.db.models.functions import TruncMonth
from app01 import models
register = template.Library()
# 自定义inclusion_tag
@register.inclusion_tag('left_meun.html')
def left_meun(username):
user_obj = models.UserInfo.objects.filter(username=username).first()
# 1.查询当前用户所有分类及分类下的文章数
blog = user_obj.blog
category_list = models.Category.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list(
'name', 'count_num', 'pk')
# 2.查询当前用户所有标签及标签的文章数
tag_list = models.Tag.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name',
'count_num',
# 3.按照日期归档
date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values(
'month').annotate(count_num=Count('pk')).values_list('month', 'count_num')
return locals()
前端建立一个letf_meun.html把左侧边栏的代码放在这里
<div class="panel panel-success">...</div>
<div class="panel panel-primary">...</div>
<div class="panel panel-danger">... </div>
在需要侧边栏的地方写如在base.py
{% load mytag %}
{% left_meun username %}
这样在每个页面需要左侧边栏的地方都可以显示了。这样就可以避免大量的冗余代码,也可以少些
根评论子评论
- 允许根评论和子评论(评论评论的评论),可以评论自己的文章。
- 用户未登录不能评论且隐藏评论输入框(request.user.is_authenticated)。
- 评论内容有两种渲染方式:
- 刷新页面时,从后端取出评论数据,前端循环展示
- 评论后DOM操作临时将评论内容渲染到评论列表,使用的是js的模版字符串语法。
- 根评论朝后端提交的数据:文章主键、评论内容、
- 子评论朝后端提交的数据:文章主键、评论内容、父评论主键
- 获取父评论的方式:给回复按钮绑定一个自定义属性,属性值为父评论主键
- 区分子评论和根评论关键在于是否有父评论,这里面为了统一,提交根评论时也携带父评论(只不过值为null,因为数据库该字段支持为空)。
后端
- 需要登录后才能评论,所以使用一个登录校验装饰器
- 后端逻辑比较简单,接收评论内容、文章主键、父评论主键
- 评论内容为空值,响应提示信息
- 使用事物同时更新文章表和评论表。
代码主要都在前端
//设置一个全局的parentId字段
let parentId =null
// 用户发表评论按钮发送ajax请求
$('#id_submit').click(function () {
//先拿到用户评论的内容
let conTent = $('#id_comment').val();
//因为子评论存的时候不应该有@人名,所以我们要手动去除@username
if (parentId){
let indexNum = conTent.indexOf('
') + 1;//找到
对应的索引。然后切片,但是骨头不顾尾要+1
conTent = conTent.slice(indexNum)//将indexNum之前的所有数据清楚,只保留后面的部分
}
$.ajax({
url:'/comment/',
type:'post',
data: {
'article_id':'{{ article_obj.pk }}',
'content':conTent,
// 如果parentId没有值,就是null,后面数据库可以为null没任何问题
'parent_id':parentId,
'csrfmiddlewaretoken':'{{ csrf_token }}',
},
success:function (args) {
if (args.code==1000)
{
$('#error').text(args.msg)
//评论框里面的内容清空
$('id_comment').val('');
//临时渲染
let userName='{{ request.user.username }}';
let temp = `
<li class="list-group-item">
<span>${userName}</span>
<span><a href="#" class="pull-right">回复</a></span>
<div>
${conTent}
</div>
</li>
`
//添加到ul里面
$('.list-group').append(temp)
//清空全局的parentId
parentId = null;
}
}
})
})
// 给回复按钮绑定点击事件
$('.reply').click(function () {
//需要评论对应的评论人姓名,还需要评论的主键值
//获取用户名和主键值,自定义属性
let commentUsername = $(this).attr('username');
//直接修改全局
parentId =$(this).attr('comment_id');
//拼接信息塞给评论框
$('#id_comment').val('@'+ commentUsername + '
').focus()
})
解析步骤:
-
用户放松ajax请求
-
拿到用户评论内容
-
临时渲染评论框,但是只显示一个人的名字
-
这个时候把临时渲染的评论框加到ul里面,根评论完成
-
子评论给回复按钮绑定点击事件
-
这个时候我们要拿到对应的评论人的用户名,还有评论的id主键值
-
如何获取用户名和主键值,给他们自定义属性
<span><a class="pull-right reply" username="{{ comment.user.username }}" comment_id="{{ comment.pk }}">回复</a></span>
-
拼接信息给评论框
-
发送信息如何发送呢,设置一个全局的子评论字段,子评论的内容直接修改全局
-
但是我们发送的时候parentId没有值,就是null,正好数据库的parent_id字段可以为null没有任何问题
-
评论时存储的有@用户名,所以我们要手动去除@username
-
找到 ,因为我们在拼接信息的时候 前面就是@username用户名,我们截取到 ,切片固头不顾尾+1
let indexNum = conTent.indexOf(' ') + 1
-
将indexNum之前的所有数据清除,保留后面的部分
-
这个时候我们在写根评论的时候还有子评论的主键值,要清除全局的parentId
后端
# 开启事务操作两种表
from django.db import transaction
def comment(request):
# 自己也能评论
if request.is_ajax():
if request.method == 'POST':
back_dic = {'code': 1000, 'msg': ''}
if request.user.is_authenticated:
article_id = request.POST.get('article_id')
content = request.POST.get('content')
parent_id = request.POST.get('parent_id')
# 直接操作评论表存数据,两张表
with transaction.atomic():
models.Article. objects.filter(pk=article_id).update(comment_num=F('comment_num') + 1)
models.Comment.objects.create(parent_id=parent_id,user=request.user, article_id=article_id, content=content)
back_dic['msg'] = '评论成功'
else:
back_dic['code'] = 10001
back_dic['msg'] = '用户未登录'
return JsonResponse(back_dic)
-
我们需要操作多张表的时候可以开启事务
-
因为子评论可以为空,直接接收,可以省很多事情
beautifulsuop4
-
编辑别的博客复制在富文本编辑器,会有html页面代码,截取文本内容代码一起截取
-
xss攻击,如在里面使用script代码写内容,保存不显示
-
筛选标签去除html代码
content = request.POST.get('content')
soup = BeautifulSoup(content, 'html.parser') // 把要筛选的内容放进去
tags = soup.find_all() // 找到所有标签
for tag in tags:
if tag.name == 'script': //
找到script标签
tag.decompose() // 删除标签
desc = soup.text[0:150] // 截取文章描述
article_obj = models.Article.objects.create(
title=title,
content=str(soup),
desc=desc,
category_id=category_id,
blog=request.user.blog
)
富文本编辑器遗留问题
- 在上传图片会出现下载问题
- 403:forbidden
- 自定义富文本编辑器
前端
添加参数:uploadJson:'/路径/'
自定义参数:extraFileUploadParams : {}
添加: csrfmiddlewaretoken:'{{ csrf_token }}'
后端:
- 获取上传的图片文件的键
- 拼接上传路径
- 返回media开放的资源路径
import os
from bbs import settings
def upload_img(request):
back_dic = {'error':0,'url':''}
if request.method == 'POST':
file_obj = request.FILES.get('imgFile')//这里不知道键是多少,用request.FILES打印看一下
# 手动拼接
file_path = os.path.join(settings.BASE_DIR,'media','article_img')
if not os.path.isdir(file_path):
os.mkdir(file_path)
file_img = os.path.join(file_path,file_obj.name)
with open(file_img,'wb') as f:
for i in file_obj:
f.write(i)
back_dic['url'] = '/media/article_img/%s'%file_obj.name
return JsonResponse(back_dic)
- 获取键,手动拼接路径,判断是否存在文件夹,不存在创建
- 用
with open
保存文件 - 开放资源路径
修改头像问题
修改头像出现了csrf-403-forbidden问题
$('#id_set_avatar').click(function () {
let formDataObj = new FormData(); // 将普通数据和文件添加到该对象中
formDataObj.append('avatar', $('#myfile')[0].files[0]);
formDataObJ.append('csrfmiddelwaretoken':'{{csrf_token}}')
$.ajax({
url: '{% url 'set_avatar' %}',
type: 'post',
data:formDataObj,
contentType: false, // 必须的
processData: false,
// 必须的
success: function (args) {
if (args.code===1000){
window.location.href = args.url
}
},
})
})
这个是正确的书写,我在加中间件的csrf的时候
$('#id_set_avatar').click(function () {
let formDataObj = new FormData(); // 将普通数据和文件添加到该对象中
$.ajax({
url: '{% url 'set_avatar' %}',
type: 'post',
data:{'avatar', $('#myfile')[0].files[0],
'csrfmiddelwaretoken':'{{csrf_token}}'}// 因为他的数据本身就是一个对象,这样写对象套对象访问不到这个数据
contentType: false, // 必须的
processData: false,
// 必须的
success: function (args) {
if (args.code===1000){
window.location.href = args.url
}
},
})
})