我试图使用Python请求模块将一些数据和文件发送到我的django rest应用程序,但得到以下错误。
raise MultiPartParserError('Invalid boundary in multipart: %s' % boundary)
MultiPartParserError: Invalid boundary in multipart: None
代码: -
import requests
payload={'admins':[
{'first_name':'john'
,'last_name':'white'
,'job_title':'CEO'
,'email':'test1@gmail.com'
},
{'first_name':'lisa'
,'last_name':'markel'
,'job_title':'CEO'
,'email':'test2@gmail.com'
}
],
'company-detail':{'description':'We are a renowned engineering company'
,'size':'1-10'
,'industry':'Engineering'
,'url':'http://try.com'
,'logo':''
,'addr1':'1280 wick ter'
,'addr2':'1600'
,'city':'rkville'
,'state':'md'
,'zip_cd':'12000'
,'phone_number_1':'408-393-254'
,'phone_number_2':'408-393-221'
,'company_name':'GOOGLE'}
}
files = {'upload_file':open('./test.py','rb')}
import json
headers = {'content-type' : 'application/json'}
headers = {'content-type' : 'multipart/form-data'}
#r = requests.post('http://127.0.0.1:8080/api/create-company-profile/',data=json.dumps(payload),headers=headers,files=files)
r = requests.post('http://127.0.0.1:8080/api/create-company-profile/',data=payload,headers=headers,files=files)
print r.status_code
print r.text
Django代码: -
class CompanyCreateApiView(CreateAPIView):
parser_classes = (MultiPartParser, FormParser,)
def post(self, request, *args, **kwargs):
print 'request ==', request.data
答案 0 :(得分:7)
好的,我忘记了你的标题。根据{{3}}:
Content-Type = "Content-Type" ":" media-type
MIME提供了许多" multipart"类型 - 封装 单个邮件正文中的一个或多个实体。 所有多部分类型共享一个通用语法,...并且必须包含边界参数作为媒体类型的一部分 值。
以下是包含multipart / form-data的请求:
POST /myapp/company/ HTTP/1.1
Host: localhost:8000
Content-Length: 265
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: python-requests/2.9.0
Connection: keep-alive
Content-Type: multipart/form-data; boundary=63c5979328c44e2c869349443a94200e
--63c5979328c44e2c869349443a94200e
Content-Disposition: form-data; name="hello"
world
--63c5979328c44e2c869349443a94200e
Content-Disposition: form-data; name="mydata"; filename="data.txt"
line 1
line 2
line 3
line 4
--63c5979328c44e2c869349443a94200e--
查看数据部分是如何被边界分开的:
--63c5979328c44e2c869349443a94200e--
我们的想法是使用某些东西作为不太可能出现在数据中的边界。请注意,边界包含在请求的Content-Type
标题中。
该请求是由此代码生成的:
import requests
myfile = {'mydata': open('data.txt','rb')}
r = requests.post(url,
#headers = myheaders
data = {'hello': 'world'},
files = myfile
)
看起来您正在仔细关注django-rest-framework the spec中的以下注释:
注意:开发客户端应用程序时,请务必确保 您在HTTP中发送数据时设置Content-Type标头 请求。
如果您未设置内容类型,则大多数客户端将默认使用 ' application / x-www-form-urlencoded',这可能不是你想要的。
但是当您使用requests
时,如果您自己指定了Content-Type
标题,那么requests
会假设您知道自己在做什么,而且它并没有。用您提供的Content-Type
标头覆盖Content-Type
标头。
根据要求,您未在Content-Type
标题中提供边界。你怎么能?您没有组装请求的主体并创建边界来分隔各种数据,因此您无法知道边界是什么。
当django-rest-framework
注释表明您的请求中应包含Content-Type
标题时,其真正含义是:
您或您用于创建请求的任何程序都需要包含一个
Content-Type
标题。
所以@AChampion在评论中完全正确:在所有requests
文档广告之后,让Content-Type header
提供requests
:
请求完成了Python HTTP / 1.1
的所有工作
requests
的工作方式如下:如果您提供files
关键字arg,则请求使用Content-Type
multipart/form-data
标头,并在标头中指定边界;然后requests
使用边界组装请求的主体。如果您提供data
关键字参数,则请求使用Content-Type
application/x-www-form-urlencoded
,它只会将字典中的所有键和值组合成以下格式:
x=10&y=20
无需边界。
并且,如果您同时提供files
关键字arg和data
关键字arg,则请求使用Content-Type
multipart/form-data
。
答案 1 :(得分:1)
我使用django-rest-framework中的CreateAPIView
尝试了您的代码。修复代码生成的所有初步错误后,我无法重现边界错误。
目录结构:
my_site/
myapp/
views.py
serializers.py
urls.py
models.py
mysite/
settings.py
urls.py
<强>程序my_app / views.py:强>
from rest_framework.generics import CreateAPIView
from rest_framework.response import Response
from rest_framework.parsers import MultiPartParser, FormParser
from myapp.serializers import CompanySerializer
from myapp.models import Company
class CompanyCreateApiView(CreateAPIView):
parser_classes = (MultiPartParser, FormParser,) #Used to parse the Request.
queryset = Company.objects.all() #The contents of the Response.
serializer_class = CompanySerializer #Determines how the contents of the Response will be converted to json.
#Required. Defined in myapp/serializers.py
def post(self, request, *args, **kwargs):
print('data ==', request.data)
print('myjson ==', request.data["myjson"].read())
print('mydata ==', request.data["mydata"].read())
queryset = self.get_queryset()
serializer = CompanySerializer(queryset, many=True)
return Response(serializer.data)
<强>的myapp / serializers.py:强>
from rest_framework import serializers
from myapp.models import Company
#Directions for converting a model instance into json:
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company #The model this serializer applies to.
#By default all fields are converted to json.
#To limit which fields should be converted:
fields = ("name", "email")
#Or, if its easier you can do this:
#exclude = ('id',)
<强>的myapp / urls.py:强>
from django.conf.urls import url
from . import views
urlpatterns = (
url(r'^company/', views.CompanyCreateApiView.as_view() ),
)
<强> my_site / urls.py:强>
from django.conf.urls import include, url
from django.contrib import admin
urlpatterns = [
url(r'^admin/', include(admin.site.urls) ),
url(r'^myapp/', include("myapp.urls") ),
]
<强>的myapp / models.py:强>
from django.db import models
# Create your models here.
class Company(models.Model):
name = models.CharField(max_length=50)
email = models.CharField(max_length=50)
def __str__(self):
return "{} {}".format(self.name, self.email)
<强> my_site / settings.py:强>
...
...
DEFAULT_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
)
THIRD_PARTY_APPS = (
'rest_framework',
)
LOCAL_APPS = (
'myapp',
)
INSTALLED_APPS = DEFAULT_APPS + THIRD_PARTY_APPS + LOCAL_APPS
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
#'DEFAULT_PERMISSION_CLASSES': [
# 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
#]
}
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware',
)
...
...
请注意,我没有必要禁用csrf令牌。
<强> requests_client.py:强>
import requests
import json
from io import StringIO
my_dict = {
'admins': [
{'first_name':'john'
,'last_name':'white'
,'job_title':'CEO'
,'email':'test1@gmail.com'
},
{'first_name':'lisa'
,'last_name':'markel'
,'job_title':'CEO'
,'email':'test2@gmail.com'
}
],
'company-detail': {
'description': 'We are a renowned engineering company'
,'size':'1-10'
,'industry':'Engineering'
,'url':'http://try.com'
,'logo':''
,'addr1':'1280 wick ter'
,'addr2':'1600'
,'city':'rkville'
,'state':'md'
,'zip_cd':'12000'
,'phone_number_1':'408-393-254'
,'phone_number_2':'408-393-221'
,'company_name':'GOOGLE'
}
}
url = 'http://localhost:8000/myapp/company/'
#StringIO creates a file-like object in memory, rather than on disk:
#python3.4:
#with StringIO(json.dumps(my_dict)) as json_file, open("data.txt", 'rb') as data_file:
#python2.7:
with StringIO(json.dumps(my_dict).decode('utf-8')) as json_file, open("data.txt", 'rb') as data_file:
myfiles = [
("mydata", ("data.txt", data_file, "text/plain")),
("myjson", ("json.json", json_file, "application/json")),
]
r = requests.post(url, files=myfiles)
print(r.status_code)
print(r.text)
request_client.py终端窗口中的输出:
200
[{"name":"GE","email":"ge@ge.com"},{"name":"APPL","email":"appl@appl.com"}]
django服务器窗口中的输出:
...
...
Quit the server with CONTROL-C.
data == <QueryDict: {'mydata': [<InMemoryUploadedFile: data.txt (text/plain)>],
'myjson': [<InMemoryUploadedFile: json.json (application/json)>]}>
myjson == b'{"admins": [{"first_name": "john", "last_name": "white", "email": "test1@gmail.com", "job_title": "CEO"},
{"first_name": "lisa", "last_name": "markel", "email": "test2@gmail.com", "job_title": "CEO"}], "company-detail":
{"description": "We are a renowned engineering company", "phone_number_2": "408-393-221", "phone_number_1": "408-393-254",
"addr2": "1600", "addr1": "1280 wick ter", "logo": "", "size": "1-10", "city": "rkville", "url": "http://try.com",
"industry": "Engineering", "state": "md", "company_name": "GOOGLE", "zip_cd": "12000"}}'
mydata == b'line 1\nline 2\nline 3\nline 4\n'
[18/Dec/2015 13:41:57] "POST /myapp/company/ HTTP/1.1" 200 75
答案 2 :(得分:1)
顺便说一下,在开发过程中使用requests
时阅读django的大量html错误响应是一种不那么痛苦的方法:
$ python requests_client.py > error.html (send output to the file error.html)
然后在浏览器中执行:
File > Open File
并导航至error.html
。阅读浏览器中的错误说明,然后找出django项目中的错误。
然后返回终端窗口并按键盘上的向上箭头键返回:
$ python requests_client.py > error.html
点击Return
,然后刷新显示error.html
的浏览器窗口。根据需要重复。 (不要错误地反复刷新浏览器并认为发送新请求 - 您必须再次运行request_client.py以发送新请求。)
答案 3 :(得分:0)
为了使用requests
,我必须在我的django项目中禁用csrf tokens
,否则我在发送帖子请求时会出错:
CSRF验证失败。请求中止。
您正在看到此消息 因为在提交表单时此站点需要CSRF cookie。这个 出于安全原因需要cookie,以确保您的浏览器 没有被第三方劫持。
settings.py:
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
#'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
接下来,您的payload
不是json数据。 json数据是一个字符串,但您的payload
是一个python字典。而且,你不能只在你的字典中包装引号,因为json数据不能包含单引号 - 你只使用它。所以,你需要将你的python字典转换为json。
此处是requests
客户:
import requests
import json
from io import StringIO
my_dict = {
'admins': [
{'first_name':'john'
,'last_name':'white'
,'job_title':'CEO'
,'email':'test1@gmail.com'
},
{'first_name':'lisa'
,'last_name':'markel'
,'job_title':'CEO'
,'email':'test2@gmail.com'
}
],
'company-detail': {
'description': 'We are a renowned engineering company'
,'size':'1-10'
,'industry':'Engineering'
,'url':'http://try.com'
,'logo':''
,'addr1':'1280 wick ter'
,'addr2':'1600'
,'city':'rkville'
,'state':'md'
,'zip_cd':'12000'
,'phone_number_1':'408-393-254'
,'phone_number_2':'408-393-221'
,'company_name':'GOOGLE'
}
}
url = 'http://localhost:8000/myapp/upload/'
#StringIO creates a file-like object in memory, rather than on disk:
with StringIO(json.dumps(my_dict)) as json_file, open("data.txt", 'rb') as data_file:
myfiles = [
("mydata", ("data.txt", data_file, "text/plain")),
("myjson", ("json.json", json_file, "application/json")),
]
r = requests.post(url, files=myfiles)
print(r.text)
请参阅requests
文档的Advanced Usage部分。
这是基于django类的视图(尽管不是Django Rest Framework
类视图):
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
from django.views.generic import View
from django.views.generic import View
class FileUploadView(View):
def post(self, request):
file_dict = request.FILES
print(file_dict)
for name in file_dict:
print(name)
print(file_dict[name].read())
print('*' * 50)
return HttpResponse('thanks')
客户端窗口中的输出:
django186p34)~/python_programs$ python django_client.py
thanks
(django186p34)~/python_programs$
django服务器窗口中的输出:
...
...
Quit the server with CONTROL-C.
<MultiValueDict: {'myjson': [<InMemoryUploadedFile: json.json (application/json)>],
'mydata': [<InMemoryUploadedFile: data.txt (text/plain)>]}>
**************************************************
myjson
b'{"admins": [{"job_title": "CEO", "last_name": "white", "first_name": "john", "email": "test1@gmail.com"},
{"job_title": "CEO", "last_name": "markel", "first_name": "lisa", "email": "test2@gmail.com"}], "company-detail":
{"description": "We are a renowned engineering company", "city": "rkville", "state": "md", "company_name": "GOOGLE",
"addr1": "1280 wick ter", "url": "http://try.com", "phone_number_2": "408-393-221", "industry": "Engineering", "logo": "", "addr2": "1600",
"phone_number_1": "408-393-254", "size": "1-10", "zip_cd": "12000"}}'
**************************************************
mydata
b'line 1\nline 2\nline 3\nline 4\n'
**************************************************
[16/Dec/2015 07:34:06] "POST /myapp/upload/ HTTP/1.1" 200 6
显然,requests <--> django mind meld
允许您将POST数据与multipart / form-data混合,您可以这样做:
with open('data.txt', 'rb') as f:
myfile = {'myfile': f}
r = requests.post(url, data={"hello": "world"}, files=myfile)
print(r.text)
加上这种观点:
from django.http import HttpResponse
from django.views.generic import View
class FileUploadView(View):
def post(self, request):
x = request.POST
print(x)
print(x["hello"])
file_dict = request.FILES
print(file_dict)
print('*' * 50)
for name in file_dict:
print(name)
print(file_dict[name].read())
print('*' * 50)
return HttpResponse('thanks')
我在服务器窗口中获得以下输出:
...
...
Quit the server with CONTROL-C.
<QueryDict: {'hello': ['world']}>
world
<MultiValueDict: {'myfile': [<InMemoryUploadedFile: data.txt ()>]}>
**************************************************
myfile
b'line 1\nline 2\nline 3\nline 4\n'
**************************************************
[16/Dec/2015 8:04:17] "POST /myapp/upload/ HTTP/1.1" 200 6
要在字典中允许使用UTF-8字符,转换为json时必须执行一些额外步骤。请参阅here。
答案 4 :(得分:0)
使用参数上传文件时:
请勿覆盖标题
将其他参数与upload_file一起放入文件dict。
input ={"md5":"xxxx","key":"xxxxx","sn":"xxxx"}
files = {"pram1":"abc",
"pram2":json.dumps(input),
"upload_file": open('/home/gliu/abc', 'rb')}
res = requests.post(url, files=files)