参考
需求
django 收取到前端上传的文件后转发到微信服务器
requsts 上传文件
流式上传
with open('massive-body') as f:
requests.post('http://some.url/streamed', data=f)
块编码请求
def gen():
yield 'hi'
yield 'there'
requests.post('http://some.url/chunked', data=gen())
POST 多个分块编码的文件
>>> url = 'http://httpbin.org/post'
>>> multiple_files = [
('images', ('foo.png', open('foo.png', 'rb'), 'image/png')),
('images', ('bar.png', open('bar.png', 'rb'), 'image/png'))]
>>> r = requests.post(url, files=multiple_files)
>>> r.text
{
...
'files': {'images': 'data:image/png;base64,iVBORw ....'}
'Content-Type': 'multipart/form-data; boundary=3131623adb2043caaeb5538cc7aa0b3a',
...
}
requests.models.py
请求编码混入
class RequestEncodingMixin(object):
@staticmethod
def _encode_files(files, data):
"""Build the body for a multipart/form-data request.
Will successfully encode files when passed as a dict or a list of
tuples. Order is retained if data is a list of tuples but arbitrary
if parameters are supplied as a dict.
The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype) # 文件格式
or 4-tuples (filename, fileobj, contentype, custom_headers).
"""
if (not files):
raise ValueError("Files must be provided.")
elif isinstance(data, basestring):
raise ValueError("Data must not be a string.")
new_fields = []
fields = to_key_val_list(data or {})
files = to_key_val_list(files or {})
for field, val in fields:
if isinstance(val, basestring) or not hasattr(val, '__iter__'):
val = [val]
for v in val:
if v is not None:
# Don't call str() on bytestrings: in Py3 it all goes wrong.
if not isinstance(v, bytes):
v = str(v)
new_fields.append(
(field.decode('utf-8') if isinstance(field, bytes) else field,
v.encode('utf-8') if isinstance(v, str) else v))
# 遍历files获取2/3元组/list格式的数据.
for (k, v) in files:
# support for explicit filename
ft = None
fh = None
if isinstance(v, (tuple, list)):
if len(v) == 2:
fn, fp = v
elif len(v) == 3:
fn, fp, ft = v
else:
fn, fp, ft, fh = v
else:
fn = guess_filename(v) or k
fp = v
if isinstance(fp, (str, bytes, bytearray)): # 内存数据
fdata = fp
elif hasattr(fp, 'read'): # 文件对象, 支持read
fdata = fp.read()
elif fp is None:
continue
else:
fdata = fp
rf = RequestField(name=k, data=fdata, filename=fn, headers=fh)
rf.make_multipart(content_type=ft)
new_fields.append(rf)
body, content_type = encode_multipart_formdata(new_fields)
return body, content_type
django 中上传文件
settings.py
# 设置上传文件为临时文件,避免使用内存文件
FILE_UPLOAD_HANDLERS = [
'django.core.files.uploadhandler.TemporaryFileUploadHandler',
]
serializers.py 上传文件字段
class MaterialCreateSerializer(serializers.ModelSerializer):
name = serializers.CharField(
required=False, max_length=30, help_text=u'标题')
content = serializers.FileField(
required=True, help_text=u'材料内容',
validators=[
FileExtensionValidator(
['png', 'jpg', 'jpeg', 'mp4', 'mp3']
)
]
)
views.py
class XxxView(generics.GenericAPIView):
permission_classes = ()
authentication_classes = ()
serializer_class = MaterialCreateSerializer
def post(self, request, *args, **kwargs):
"""
诗经里景区服务号上传摇一摇功能图片素材
---
parameters:
- name: object
pytype: serializers.MaterialCreateSerializer
paramType: body
"""
serializer = self.get_serializer(data=request.data)
if not serializer.is_valid():
logger.error(
'WXApiView serializer err:{}'.format(serializer.errors))
return Response(
serializer.errors, status=status.HTTP_400_BAD_REQUEST)
data = serializer.validated_data
url = data.get('url')
file_data = data.get('image')
if file_data.closed:
fp = open(file_data.file.name, 'r') # 从不执行
elif isinstance(file_data, TemporaryUploadedFile):
fp = file_data.file.file # file对象
# 使用requets中的多文件上传, 也可使用但文件
multiple_files = [
('file', ('file', fp, file_data.content_type)),
]
resp = _post(url=url, files=multiple_files)
if not fp.closed:
fp.close()
return resp
note
requests 方法中调用fp.read后,不会调用fp.close
django orm 读取数据后,会主动关闭, 默认存储oss 中也会fp.read->fp.close。