赞
踩
对于文件(文本文件,或图片文件等等文件)从客户端上传到服务端,对于常规情况,也就是真实服务端和客户端,我们往往是需要给files这个参数的。
具体来说,就像这样:
- import requests
-
- header = {
- "Accept": "application/json, text/plain, */*",
- "Content-Type": "multipart/form-data",
- "Cookie": "ssssss"
- }
-
- url = "http://test.quasar.oa.com:8082/assess/api/AssessObject/ImportStaff"
-
- # 接下来注意,上传文件需要一个files的参数,同时上传文件时,传入的是一个文件句柄。
- path = (os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test.xlsx'))
- files = {'file': open(path, 'rb')} # 这里key为file,绑定的对象就是一个文件句柄。这里注意此处字典的key,不一定非得叫 ‘file’, 也可以是其他的名称。
-
- # 也有人选择给key绑定一个元组对象,性质是一样的,效果也是一样的,就是换了个写法,如:
- files = {
- 'file': ('test.png', # 文件名称
- open('../file/test.png', 'rb'), # 文件句柄
- 'image/png', # 文件类型
- {'Expires': '0'} # 其他参数,非必传
- )
- }
-
- data= {'user_name': 'aa', 'page_num': 15} # 这里可以给一些其他的请求数据。
-
- # 然后就可以发送请求了
- res = requests.post(url=url, headers=headers, files=files,data=data)

这里还有一些关于 request 的参数介绍,也可以了解下:接口测试——requests 的基本了解 - 知乎
ok,但是,对于django工程而言,在进行一些测试的时候,往往我们并不需要这么麻烦的去构建一个真实的client,去访问服务,然后进行测试。往往我们都是选择使用django自带的test体系,去完成相关测试,这样往往会方便很多。对于发get,post请求,以及上传文件等操作,我们都可以使用 django.test.client 来弄,会特别方便,且快捷,因为这就相当于,在要测试的django工程里,基于django框架,在其内部做测试,就是从工程自己内部去访问工程的各个接口,去检测工程内部的接口是否正常,以及,接口对应的功能模块,函数等,是否能够按照预期处理数据,并按照预期返回我们想要的结果。
比如:
- >>> from django.test.client import Client
- >>> c = Client()
- >>> response = c.post('/login/', {'username': 'john', 'password': 'smith'})
- >>> response.status_code
- 200
- >>> response = c.get('/customer/details/')
- >>> response.content
- '<!DOCTYPE html...'
这就是个非常典型的用法,当然,实际操作中,我们基本不会直接的这么用。
先进一步了解相关用法:
- from django.test.client import Client
-
- # Client(是可以填入参数的) enforce_csrf_checks,默认值是 False,会忽略CSRF检查。改成 True 会强制进行CSRF检查。
- csrf_client = Client(enforce_csrf_checks=True)
-
- # 也可以使用关键字参数来指定默认的请求报头
- c = Client(HTTP_USER_AGENT='Mozilla/5.0')
-
- # 另外,在发get, post 等请求的时候,get, post,等本身是可以带参数的,如:
- c.get(path, data={}, follow=False, **extra)
path : 就是请求的url,注意用django.test.client.Client()时发请求给url时,是不需要给http://...,而是直接给路由即可, 比如 '/login',就是从工程的根路由开始提供路由路径即可。
data :需要传入的参数数据
follow :这个参数是追踪的意思,主要用于重定向的场景,当值为True的时候,client会追踪任何重定向,返回的response有redirect_chain属性,包括所有重定向过程中的url和状态码组成的元祖列表。
extra : 关键字参数可用作请求报头
关于重定向追踪的演示 eg:
- >>> response = c.get('/redirect_me/', follow=True)
- >>> response.redirect_chain
- [(u'http://testserver/next/', 302), (u'http://testserver/final/', 302)]
-
post 请求的解构:
post(path, data={}, content_type=MULTIPART_CONTENT, follow=False, **extra)
和 get请求的参数大同小异,这里看下 content_type 参数,如果提供content_type参数(例如 text/xml),数据会被作为报头中Content-Type的类型进行POST上传。如果不提供content_type参数,数据会被作为multipart/form-data类型上传。
options(path, data='', content_type='application/octet-stream', follow=False, extra)**
做OPTIONS请求,对测试REST接口很有用。data被用作请求的主体。
put(path, data='', content_type='application/octet-stream', follow=False, extra)**
做PUT请求,测试RESTful接口。
patch(path, data='', content_type='application/octet-stream', follow=False, extra)**
做PATCH请求,测试RESTful接口。
delete(path, data='', content_type='application/octet-stream', follow=False, extra)**
做DELETE请求,测试RESTful接口
相关内容也可参考:Django单元测试工具test client使用详解_cecellialiu的博客-CSDN博客
用 client 验证登录:
在验证登录前通常需要前创建一个用户,最好用django的内建模块创建:
- from django.contrib.auth.models import User
- from django.test.client import Client
-
- user_test = User.objects.create_user('test', 'test@example.com')
然后,就是用 client 绑定这个用户:
client = Client(user=user_test)
然后,还记得上面的 extra 参数么。
- extra.setdefault('content_type', 'application/json')
- extra.setdefault('HTTP_AUTHORIZATION', 'apikey %s:%s' % (user_test.username, user_test.api_key.key))
接下来,就是发请求了。
- client.get(path, data, **extra)
-
- client.post(path, data, **extra)
-
- client.put(path, data, **extra)
-
- client.delete(path, data, **extra)
到这client的客户端就可以了,至于服务端的具体验证.....其实,大体就是,从request里获取到user的信息,然后,在和数据库里user的信息做对比,如果信息无误,那就是登录成功,或者就是确认是该用户,然后该干嘛干嘛就是了。
用 client 上传文件:
这个时候,就跟正常真实客户端上传文件,有所不同了。client在上传文件的过程中,是不需要提供files这个关键字的。
看个例子:
- def test_upload_file_success():
- """ Test generic_uploader Upload file API success """
- data = {
- 'filename': 'log.log',
- 'script_name': 'scriptname'
- }
- # prepare a dummy package to upload
- flength = 45272 # .045MB
- with tempfile.NamedTemporaryFile() as f:
- f.write(b"\0" * flength)
- # reset seek position to 0 to read full content
- f.seek(0)
- # 这里的关键字可以不叫files,叫什么都行,只是需要跟代码的处理逻辑保持一致即可
- data['files'] = f
- res = client.post(url, data=data)
相同的是,都是传入了一个句柄。
然后就是具体的post请求处理过程:
- @csrf_exempt
- @require_POST
- def generic_uploader(request):
- res = {'status': False}
- try:
- request_data = request.POST.dict()
- # 这里的files就是刚才data里给出的key,所以说,files关键字可以是其他的名字,只要用例侧和post请求的处理侧保持一致即可。
- if 'files' in request.FILES:
- error = upload_file(request, request_data, build_obj.logs_path())
- if error:
- return error
- res['status'] = True
-
- except Exception as err:
- err_msg = (
- 'Exception when calling the generic uploader service: %s' % err)
- log.exception(err_msg)
- res['error'] = (err_msg)
- return JsonResponse(res)

然后,看下文件的保存过程:
- def upload_file(request, request_data, log_path):
- """
- Store the file to the correct storage location. Fail the API
- call if a file with the same name already exists.
- """
-
- filename = request_data['filename']
- dst = os.path.join(log_path, filename)
- if os.path.exists(dst):
- res = {'status': False, 'error': 'Error, %s already exists' % dst}
- return JsonResponse(res, status=409)
- with open(dst, 'wb') as f:
- for chunk in request.FILES['files'].chunks():
- f.write(chunk)
- request.FILES['files'].close()
这里注意:从request.FILES中获得的真实的文件。这个字典的每个输入都是一个UploadedFile对象——一个上传之后的文件的简单的包装。
看个日志呗:

request.FILES对象,就是<MultiValueDict:{}>对象,这是一个特殊的字典对象。字典中的key,取决于post请求中绑定句柄的那个key,值当然对应的就是句柄对象了。
至此,关于client的内容先扯到这。看下,这个过程中涉及的,两个东西,一个是tempfile,一个是chunks()。
先说说chunks(),这东西,有点迭代器与生成器的意思。当文件本身特别大的时候,往往不适合用read()去操作文件,这样有可能会很占内存,这时候,chunks()就是个比较好的选择,它就像是生成器与迭代器,你调用一次,它就拿一次文件里的内容,默认的这个值是2.5M,当然这个值是可以调节的。然后就一直 for 下去,慢慢搬运文件内容呗。这样就等于保证效率的同时,也不影响内存的使用。
再就是tempfile临时文件系统。他的作用,就是造一个临时文件,通常这个临时文件,在关闭以后,就会自动被销毁。当然可以添加参数让其不自动销毁。
tempfile.NamedTemporaryFile(delete=False)
在有些场景下对于临时文件的存储有一定的格式要求,比如后缀等,这里我们将临时文件的后缀设置为常用的txt格式,同样的,只需要在NamedTemporaryFile的参数中进行配置即可:
- import tempfile
-
- file = tempfile.NamedTemporaryFile(delete=False, suffix='.txt')
这里也可参考:善用tempfile库创建python进程中的临时文件 - DECHIN - 博客园
这里注意,一般情况,临时文件生成的地方,linux一般就是在 /tmp下,但是有个参数注意一下,在django中,使用临时文件系统的时候,并不是非得让临时文件,必须存放到 /tmp下的,是可以换地方的,这就涉及一个参数:FILE_UPLOAD_TEMP_DIR,这个参数可以放到django的settings.py里,这样就可以指定临时文件的存放目录了。
它的应用场景是:
临时文件在python项目中时常会被使用到,其作用在于随机化的创建不重名的文件,路径一般都是放在Linux系统下的/tmp目录。如果项目中并不需要持久化的存储一个文件,就可以采用临时文件的形式进行存储和读取,在使用之后可以自行决定是删除还是保留。
要想安全的创建名字唯一的临时文件,以防止被试图破坏应用或窃取数据的人猜出,这很有难度。tempfile模块提供了多个函数来安全的创建临时文件系统资源。TemporaryFile()打开并返回一个未命名的文件,NamedTemporaryFile()打开并返回一个命名文件,SpooledTemporaryFile在将内容写入磁盘之前先将其保存在内存中,TemporaryDirectory是一个上下文管理器,上下文关闭时会删除这个目录。
如果应用需要临时文件来存储数据,而不需要与其他程序共享这些文件,则应当使用TemporaryFile()函数创建文件。这个函数会创建一个文件,而且如果平台支持,它会立即断开这个新文件的链接。这样一来,其他程序就不可能找到或打开这个文件,因为文件系统表中根本没有这个文件的引用。对于TemporaryFile()创建的文件,无论通过调用close()还是结合使用上下文管理器API和with语句,关闭文件时都会自动删除这个文件。
有些情况下,可能非常需要一个命名的临时文件。对于跨多个进程甚至主机的应用来说,为文件命名是在应用不同部分之间传递文件的最简单的方法。NamedTemporaryFile()函数会创建一个文件,但不会断开它的链接,所以会保留它的文件名(用name属性访问)。
关于tempfile的更多用法可以参考:Python3标准库:tempfile临时文件系统对象 - 走看看
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。