前后端传输数据的编码格式主要有三种urlencoded;formdata;json
Ajax提交urlencoded格式数据
Ajax给后台发送数据的默认编码格式是urlencoded,比如username=abcde&password=123456的形式。Django后端拿到符合urlencoded编码格式的数据都会自动帮你解析分装到request.POST中,与form表单提交的数据相同。
/手动构造数据data
$(“#btnSubmit”).click(function(){
$.ajax({
url:’/login/’,//也可以反向解析{%url’login’%}
type:’post’,
data:{
‘username’:$(“#id_username”).val(),
‘password’:$(‘#id_password’).val()
},
//上面data为提交数据,下面data形参指代的就是异步提交的返回结果data
success:function(data){
}
});
};
//.serialize()方法可将<input>,<textarea>以及<select>表单序列化
//成urlencoded格式数据
$(“#btnSubmit”).click(function(){
letdata=$(“#loginForm”).serialize();
$.ajax({
url:”/login/”,//别忘了加斜杠
type:$(“#loginForm”).attr(‘method’),
data:data,
//下面data形参指代的就是异步提交的返回结果data
success:function(data){
}
});
});
Ajax通过FormData上传文件
Ajax上传文件需要借助于js内置对象FormData,另外上传文件时表单千万别忘了加enctype=”multipart/form-data”属性。
$(“#submitFile”).click(function(){
letformData=newFormData($(“#upload-form”));
$.ajax({
url:”/upload/”,//也可以写{%url’upload’%}
type:”post”,
data:formData,
//这两个要必须写
processData:false,//不预处理数据因为FormData已经做了
contentType:false,//不指定编码了因为FormData已经做了
success:function(data){
console.log(data);
}
});
});
//案例2,同时上传文件并提交其它数据
$(“#submitFile”).click(function(){
//js取到文件,一定要加0
letmyfile=$(“#id_file”).files[0];
//生成一个FormData对象
letformdata=newFormData();
//加其它值
formdata.append(‘name’,$(“#id_name”).val());
//加文件
formdata.append(‘myfile’,myfile);
$.ajax({
url:’/upload/’,//url别忘了加/杠
type:’post’,
//这两个要必须写
processData:false,//不预处理数据因为FormData已经做了
contentType:false,//不指定编码了因为FormData已经做了
data:formdata,
success:function(data){
console.log(data);
}
});
});
Ajax提交Json格式数据
前后端传输数据的时候一定要确保声明的编码格式跟数据真正的格式是一致的。如果你通过Ajax发送Json格式数据给Django后端,请一定注意以下三点:
contentType参数指定成application/json;
数据是真正的json格式数据;
Django后端不会帮你处理json格式数据需要你自己去request.body获取并处理。
$(“#submitBtn”).click(function(){
vardata_obj={‘name’:’abcdef’,’password’:123456};//Javascript对象
$.ajax({
url:”,
type:’post’,
contentType:’application/json’,//一定要指定格式contentType
data:JSON.stringify(data_obj),//转换成json字符串格式
success:function(data){
console.log(data)
}
});
});
Ajax发送POST请求时如何通过CSRF认证
//第一种方式直接在发送数据中加入csrfmiddlewaretoken
$(“#btn”).on(“click”,function(){
$.ajax({
url:”/some_url/”,
type:”POST”,
data:{
//写在模板中,才会被渲染
‘csrfmiddlewaretoken’:{{csrf_token}},
//其它数据
‘username’:$(“#id_username”).val(),
‘password’:$(‘#id_password’).val()
},
success:function(data){
}
});
});
//通过jquery选择器获取csrfmiddlewaretoken
$(“#btn”).on(“click”,function(){
$.ajax({
url:”/some_url/”,
type:”POST”,
data:{
‘csrfmiddlewaretoken’:$(‘[name=”csrfmiddlewaretoken”]’).val(),
‘username’:$(“#id_username”).val(),
‘password’:$(‘#id_password’).val()
},
success:function(data){
}
});
});
//使用jquery.cookie.js调用请求头cookie中的csrftoken
<scriptsrc=”/static/jquery.cookie.js”>
<script>
$(“#btn”).on(“click”,function(){
$.ajax({
url:”/some_url/”,
type:”POST”,
headers:{“X-CSRFToken”:$.cookie(‘csrftoken’)},
data:$(“#form1″).serialize()
});
})
</script>
Django Ajax案例1:联动下拉菜单
联动下拉菜单是Web开发中一个被经常使用的应用。比如当你从一个列表从选择一个国家的时候,联动下拉菜单会同步显示属于该国家所有城市列表供用户选择。今天我们就教你如何使用Django Ajax生成联动下拉菜单。
模型如下:
classCountry(models.Model):
name=models.CharField(verbose_name=”国家”,max_length=50)
def__str__(self):
returnself.name
classCity(models.Model):
name=models.CharField(verbose_name=”城市”,max_length=50)
country=models.ForeignKey(Country,on_delete=models.CASCADE,verbose_name=”国家”,)
def__str__(self):
returnself.name
表单中对应国家和城市下拉菜单的DOM元素id分别为id_country和id_city。当用户选择国家后,ajax会携带国家的id值向后台发送请求获得当前国家的所有城市清单,并在前端渲染显示。
{%blockcontent%}
<h2>创建用户-联动下拉菜单</h2>
<formmethod=”post”class=”form-horizontal”role=’form’action=””>
{%csrf_token%}
{{form.as_p}}
<buttontype=”submit”class=”btnbtn-primary”>Submit</button>
</form>
{%endblock%}
<scriptsrc=”https://code.jquery.com/jquery-3.1.0.min.js”></script>
<script>
$(“#id_country”).change(function(){
varcountry_id=$(this).val();
$.ajax({
url:’/ajax/load_cities/’,
data:{
‘country_id’:country_id
},
type:’GET’,
dataType:’json’,
success:function(data){
varcontent=”;
//对结果进行遍历,生成下拉菜单
$.each(data,function(i,item){
content ='<optionvalue=’ item.id ‘>’ item.name ‘</option>’
});
$(‘#id_city’).html(content)
},
});
});
</script>
Django负责处理视图Ajax请求的视图函数
defajax_load_cities(request):
ifrequest.method==’GET’:
country_id=request.GET.get(‘country_id’,None)
ifcountry_id:
data=list(City.objects.filter(country_id=country_id).values(“id”,”name”))
returnJsonResponse(data,safe=False)
Django Ajax案例2:Ajax上传文件
前端模板及js文件如下所示, 请注意我们是如何在表单中加入了enctype属性,如何使用FormData上传文件,并解决了csrftoken问题的。
{%blockcontent%}
<formaction=””method=”post”enctype=”multipart/form-data”id=”form”>
<ulclass=”errorlist”></ul>
{%csrf_token%}
{{form.as_p}}
<inputtype=”button”class=”btnbtn-infoform-control”value=”submit”id=”btn”/>
</form>
<tableclass=”tabletable-striped”id=”result”>
</table>
{%endblock%}
{%blockjs%}
<scriptsrc=”https://cdn.jsdelivr.net/jquery.cookie/1.4.1/jquery.cookie.min.js”>
</script>
<script>
varcsrftoken=$.cookie(‘csrftoken’);
functioncsrfSafeMethod(method){
return(/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$(document).ready(function(){
$(‘#btn’).click(function(e){
e.preventDefault();
//构建FormData对象
varform_data=newFormData();
form_data.append(‘file’,$(‘#id_file’).files[0]);
$.ajax({
url:’/file/ajax_upload/’,
data:form_data,
type:’POST’,
dataType:’json’,
//告诉jQuery不要去处理发送的数据,发送对象。
processData:false,
//告诉jQuery不要去设置Content-Type请求头
contentType:false,
//获取POST所需的csrftoken
beforeSend:function(xhr,settings){
if(!csrfSafeMethod(settings.type)&&!this.crossDomain){
xhr.setRequestHeader(“X-CSRFToken”,csrftoken);
}},
success:function(data){
if(data[‘error_msg’]){
varcontent='<li>’ data[‘error_msg’] ‘</li>’;
$(‘ul.errorlist’).html(content);
}
else
{
varcontent='<thead><tr>’
‘<th>NameandURL</th>’
‘<th>Size</th>’
‘</tr></thead><tbody>’;
$.each(data,function(i,item){
content=content
‘<tr><td>’
“<ahref='”
item[‘url’]
“‘>”
item[‘url’]
‘</a></td><td>’
item[‘size’]
‘</td><td><tr>’
});
content=content “</tbody>”;
$(‘#result’).html(content);
}
},
});
});
});
</script>
{%endblock%}
Django负责处理视图Ajax请求的视图函数
#handlingAJAXrequests
defajax_upload(request):
ifrequest.method==”POST”:
form=FileUploadModelForm(data=request.POST,files=request.FILES)
ifform.is_valid():
form.save()
#Obtainthelatestfilelist
files=File.objects.all().order_by(‘-id’)
data=[]
forfileinfiles:
data.append({
“url”:file.file.url,
“size”:filesizeformat(file.file.size),
})
returnJsonResponse(data,safe=False)
else:
data={‘error_msg’:”Onlyjpg,pdfandxlsxfilesareallowed.”}
returnJsonResponse(data)
returnJsonResponse({‘error_msg’:’onlyPOSTmethodaccpeted.’})
Django默认处理方式会出现一个问题,所有文件都存储在一个文件夹里。不同用户上传的有相同名字的文件可能会相互覆盖。另外用户还可能上传一些不安全的文件如js和exe文件,我们必需对允许上传文件的类型进行限制。因此我们在利用Django处理文件上传时必需考虑如下3个因素:
注意:以上事项对于上传图片是同样适用的。
Django文件上传一般有3种方式(如下所示)。我们会针对3种方式分别提供代码示范。
使用一般的自定义表单上传,在视图中手动编写代码处理上传的文件
使用由模型创建的表单(ModelForm)上传,使用form.save()方法自动存储
使用Ajax实现文件异步上传,上传页面无需刷新即可显示新上传的文件
项目创建与设置
先使用django-admin startproject命令创建一个叫file_project的项目,然后cd进入file_project, 使用python manage.py startapp创建一个叫file_upload的app。
#file_project/settings.py
INSTALLED_APPS=[
‘django.contrib.admin’,
‘django.contrib.auth’,
‘django.contrib.contenttypes’,
‘django.contrib.sessions’,
‘django.contrib.messages’,
‘django.contrib.staticfiles’,
‘file_upload’,
]
STATIC_URL=’/static/’
STATICFILES_DIRS=[os.path.join(BASE_DIR,”static”),]
#Defaultprimarykeyfieldtype
#https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD=’django.db.models.BigAutoField’
MEDIA_ROOT=os.path.join(BASE_DIR,’media’)
MEDIA_URL=’/media/’
#file_project/urls.py
创建模型#file_upload/models.py
fromdjango.dbimportmodels
importos
importuuid
#Createyourmodelshere.
#Defineuserdirectorypath
defuser_directory_path(instance,filename):
ext=filename.split(‘.’)[-1]
filename='{}.{}’.format(uuid.uuid4().hex[:10],ext)
returnos.path.join(“files”,filename)
classFile(models.Model):
file=models.FileField(upload_to=user_directory_path,null=True)
upload_method=models.CharField(max_length=20,verbose_name=”UploadMethod”)
URLConf配置:一共包括3个urls, 分别对应普通表单上传,ModelForm上传和显示文件清单。
#file_upload/urls.py
fromdjango.urlsimportre_path,path
from.importviews
#namespace
app_name=”file_upload”
urlpatterns=[
#UploadFileWithoutUsingModelForm
re_path(r’^upload1/$’,views.file_upload,name=’file_upload’),
#UploadFilesUsingModelForm
re_path(r’^upload2/$’,views.model_form_upload,name=’model_form_upload’),
#ViewFileList
path(‘file/’,views.file_list,name=’file_list’),
]
使用一般表单上传文件
我们先定义一个一般表单FileUploadForm,并通过clean方法对用户上传的文件进行验证,如果上传的文件名不以jpg, pdf或xlsx结尾,将显示表单验证错误信息。关于表单的自定义和验证更多内容见Django基础: 表单forms的设计与使用。
#file_upload/forms.py
fromdjangoimportforms
from.modelsimportFile
#Regularform
classFileUploadForm(forms.Form):
file=forms.FileField(widget=forms.ClearableFileInput(attrs={‘class’:’form-control’}))
upload_method=forms.CharField(label=”UploadMethod”,max_length=20,
widget=forms.TextInput(attrs={‘class’:’form-control’}))
defclean_file(self):
file=self.cleaned_data[‘file’]
ext=file.name.split(‘.’)[-1].lower()
ifextnotin[“jpg”,”pdf”,”xlsx”]:
raiseforms.ValidationError(“Onlyjpg,pdfandxlsxfilesareallowed.”)
#returncleaneddataisveryimportant.
returnfile
使用clean方法对表单字段进行验证时,别忘了return验证过的数据,即cleaned_data。只有返回了cleaned_data, 视图中才可以使用form.cleaned_data.get(‘xxx’)获取验证过的数据。
当用户的请求方法为POST时,我们通过form.cleaned_data.get(‘file’)获取通过验证的文件,并调用自定义的handle_uploaded_file方法来对文件进行重命名,写入文件。如果用户的请求方法不为POST,则渲染一个空的FileUploadForm在upload_form.html里。我们还定义了一个file_list方法来显示文件清单。
fromdjango.shortcutsimportrender,redirect
from.modelsimportFile
from.formsimportFileUploadForm,FileUploadModelForm
importos
importuuid
fromdjango.httpimportJsonResponse
fromdjango.template.defaultfiltersimportfilesizeformat
#Createyourviewshere.
#Showfilelist
deffile_list(request):
files=File.objects.all().order_by(“-id”)
returnrender(request,’file_upload/file_list.html’,{‘files’:files})
#RegularfileuploadwithoutusingModelForm
deffile_upload(request):
ifrequest.method==”POST”:
form=FileUploadForm(request.POST,request.FILES)
ifform.is_valid():
#getcleaneddata
upload_method=form.cleaned_data.get(“upload_method”)
raw_file=form.cleaned_data.get(“file”)
new_file=File()
new_file.file=handle_uploaded_file(raw_file)
new_file.upload_method=upload_method
new_file.save()
returnredirect(“/file/”)
else:
form=FileUploadForm()
returnrender(request,’file_upload/upload_form.html’,
{‘form’:form,’heading’:’UploadfileswithRegularForm’}
)
defhandle_uploaded_file(file):
ext=file.name.split(‘.’)[-1]
file_name='{}.{}’.format(uuid.uuid4().hex[:10],ext)
#filepathrelativeto’media’folder
file_path=os.path.join(‘files’,file_name)
absolute_file_path=os.path.join(‘media’,’files’,file_name)
directory=os.path.dirname(absolute_file_path)
ifnotos.path.exists(directory):
os.makedirs(directory)
withopen(absolute_file_path,’wb ‘)asdestination:
forchunkinfile.chunks():
destination.write(chunk)
returnfile_path
构建文件写入绝对路径时请用os.path.join方法,因为不同系统文件夹分隔符不一样。写入文件前一个良好的习惯是使用os.path.exists检查目标文件夹是否存在,如果不存在先创建文件夹,再写入。
#file_upload/templates/upload_form.html
{%extends”file_upload/base.html”%}
{%blockcontent%}
{%ifheading%}
<h3>{{heading}}</h3>
{%endif%}
<formaction=””method=”post”enctype=”multipart/form-data”>
{%csrf_token%}
{{form.as_p}}
<buttonclass=”btnbtn-infoform-control”type=”submit”value=”submit”>Upload</button>
</form>
{%endblock%}
# file_upload/templates/file_list.html
{%extends”file_upload/base.html”%}
{%blockcontent%}
<h3>FileList</h3>
<p><ahref=”/file/upload1/”>RegularFormUpload</a>|<ahref=”/file/upload2/”>ModelFormUpload</a>
|<ahref=”/file/upload3/”>AjaxUpload</a></p>
{%iffiles%}
<tableclass=”tabletable-striped”>
<tbody>
<tr>
<td>Filename&URL</td>
<td>Filesize</td>
<td>UploadMethod</td>
</tr>
{%forfileinfiles%}
<tr>
<td><ahref=”{{file.file.url}}”>{{file.file.url}}</a></td>
<td>{{file.file.size|filesizeformat}}</td>
<td>{{file.upload_method}}</td>
</tr>
{%endfor%}
</tbody>
</table>
{%else%}
<p>Nofilesuploadedyet.Pleaseclick<ahref=”{%url’file_upload:file_upload’%}”>here</a>
touploadfiles.</p>
{%endif%}
{%endblock%}
上传文件的大小默认是以B显示的,数字非常大。使用Django模板过滤器filesizeformat可以将文件大小显示为人们可读的方式,如MB,KB。
使用ModelForm上传文件
fromdjangoimportforms
from.modelsimportFile
#Modelform
classFileUploadModelForm(forms.ModelForm):
classMeta:
model=File
fields=(‘file’,’upload_method’,)
widgets={
‘upload_method’:forms.TextInput(attrs={‘class’:’form-control’}),
‘file’:forms.ClearableFileInput(attrs={‘class’:’form-control’}),
}
defclean_file(self):
file=self.cleaned_data[‘file’]
ext=file.name.split(‘.’)[-1].lower()
ifextnotin[“jpg”,”pdf”,”xlsx”]:
raiseforms.ValidationError(“Onlyjpg,pdfandxlsxfilesareallowed.”)
#returncleaneddataisveryimportant.
returnfile
使用ModelForm处理文件上传的视图model_form_upload方法非常简单,只需调用form.save()即可,无需再手动编写代码写入文件。
#file_upload/views.py
fromdjango.shortcutsimportrender,redirect
from.modelsimportFile
from.formsimportFileUploadForm,FileUploadModelForm
importos
importuuid
fromdjango.httpimportJsonResponse
fromdjango.template.defaultfiltersimportfilesizeformat
#Createyourviewshere.
#UploadFilewithModelForm
defmodel_form_upload(request):
ifrequest.method==”POST”:
form=FileUploadModelForm(request.POST,request.FILES)
ifform.is_valid():
form.save()#一句话足以
returnredirect(“/file/”)
else:
form=FileUploadModelForm()
returnrender(request,’file_upload/upload_form.html’,
{‘form’:form,’heading’:’UploadfileswithModelForm’}
)