Я пытаюсь отправить некоторые данные и файл с помощью модуля запросов Python в мое приложение rest django, но получаю ошибку ниже.

    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

Код Джанго: -

class CompanyCreateApiView(CreateAPIView):
    parser_classes = (MultiPartParser, FormParser,)
    def post(self, request, *args, **kwargs):
        print 'request ==', request.data
4
user1050619 17 Дек 2015 в 06:31

4 ответа

Лучший ответ

Хорошо, я забыл о ваших заголовках. Согласно спецификации:

Content-Type   = "Content-Type" ":" media-type

MIME предусматривает несколько «многочастных» типов - инкапсуляцию одного или нескольких объектов в одном теле сообщения. Все составные типы имеют общий синтаксис, ... и ДОЛЖНЫ включать граничный параметр как часть значения типа мультимедиа.

Вот как выглядит запрос, содержащий 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 документы :

Примечание. При разработке клиентских приложений всегда не забывайте устанавливать заголовок Content-Type при отправке данных в HTTP-запросе.

Если вы не установите тип контента, большинство клиентов по умолчанию будут использовать 'application / x-www-form-urlencoded', что может оказаться не тем, что вы хотели.

Но когда вы используете requests, если вы сами указываете заголовок Content-Type, тогда requests предполагает, что вы знаете, что делаете, и он не перезаписывает ваш {{X3} } заголовок с заголовком 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.

8
7stud 19 Дек 2015 в 10:56

Кстати, вот менее болезненный способ прочитать объемные html-ответы django при использовании requests при разработке:

$ 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, чтобы отправить новый запрос).

1
7stud 19 Дек 2015 в 00:30

Я попробовал ваш код с CreateAPIView из django-rest-framework. После исправления всех предварительных ошибок, которые выдает ваш код, я не смог воспроизвести ошибку границы.

Структура каталога:

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
1
7stud 19 Дек 2015 в 00:27

Чтобы использовать requests, мне пришлось отключить csrf tokens в моем проекте django, в противном случае я получил ошибки при отправке почтовых запросов:

Ошибка проверки CSRF. Запрос прерван.

Вы видите это сообщение, потому что этому сайту требуется файл cookie CSRF при отправке форм. Этот файл 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 документов.

Вот он с представлением на основе класса 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, и вы можете сделать это:

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. См. здесь .

0
Community 23 Май 2017 в 10:29