test framework example español create django django-rest-framework django-unittest

example - django rest framework serializer



¿Cómo puedo probar la carga de archivos binarios con el cliente de prueba de django-rest-framework? (4)

Tengo una aplicación Django con una vista que acepta que se cargue un archivo. Usando el marco REST de Django, estoy subclasificando APIView e implementando el método post () de esta manera:

class FileUpload(APIView): permission_classes = (IsAuthenticated,) def post(self, request, *args, **kwargs): try: image = request.FILES[''image''] # Image processing here. return Response(status=status.HTTP_201_CREATED) except KeyError: return Response(status=status.HTTP_400_BAD_REQUEST, data={''detail'' : ''Expected image.''})

Ahora intento escribir un par de pruebas de unidad para asegurarme de que se requiere autenticación y que un archivo subido realmente se procesa.

class TestFileUpload(APITestCase): def test_that_authentication_is_required(self): self.assertEqual(self.client.post(''my_url'').status_code, status.HTTP_401_UNAUTHORIZED) def test_file_is_accepted(self): self.client.force_authenticate(self.user) image = Image.new(''RGB'', (100, 100)) tmp_file = tempfile.NamedTemporaryFile(suffix=''.jpg'') image.save(tmp_file) with open(tmp_file.name, ''rb'') as data: response = self.client.post(''my_url'', {''image'': data}, format=''multipart'') self.assertEqual(status.HTTP_201_CREATED, response.status_code)

Pero esto falla cuando el marco REST intenta codificar la solicitud

Traceback (most recent call last): File "/home/vagrant/.virtualenvs/myapp/lib/python3.3/site-packages/django/utils/encoding.py", line 104, in force_text s = six.text_type(s, encoding, errors) UnicodeDecodeError: ''utf-8'' codec can''t decode byte 0xff in position 118: invalid start byte During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/home/vagrant/webapp/myproject/myapp/tests.py", line 31, in test_that_jpeg_image_is_accepted response = self.client.post(''my_url'', { ''image'': data}, format=''multipart'') File "/home/vagrant/.virtualenvs/myapp/lib/python3.3/site- packages/rest_framework/test.py", line 76, in post return self.generic(''POST'', path, data, content_type, **extra) File "/home/vagrant/.virtualenvs/myapp/lib/python3.3/site-packages/rest_framework/compat.py", line 470, in generic data = force_bytes_or_smart_bytes(data, settings.DEFAULT_CHARSET) File "/home/vagrant/.virtualenvs/myapp/lib/python3.3/site-packages/django/utils/encoding.py", line 73, in smart_text return force_text(s, encoding, strings_only, errors) File "/home/vagrant/.virtualenvs/myapp/lib/python3.3/site-packages/django/utils/encoding.py", line 116, in force_text raise DjangoUnicodeDecodeError(s, *e.args) django.utils.encoding.DjangoUnicodeDecodeError: ''utf-8'' codec can''t decode byte 0xff in position 118: invalid start byte. You passed in b''--BoUnDaRyStRiNg/r/nContent-Disposition: form-data; name="image"; filename="tmpyz2wac.jpg"/r/nContent-Type: image/jpeg/r/n/r/n/xff/xd8/xff[binary data omitted]'' (<class ''bytes''>)

¿Cómo puedo hacer que el cliente de prueba envíe los datos sin intentar decodificarlos como UTF-8?


Al probar las cargas de archivos, debe pasar el objeto de transmisión a la solicitud, no los datos .

Esto fue señalado en los comentarios por arocks

Pase {''image'': file} en su lugar

Pero eso no explicaba por completo por qué era necesario (y tampoco coincidía con la pregunta). Para esta pregunta específica, deberías estar haciendo

class TestFileUpload(APITestCase): def test_file_is_accepted(self): self.client.force_authenticate(self.user) image = Image.new(''RGB'', (100, 100)) tmp_file = tempfile.NamedTemporaryFile(suffix=''.jpg'') image.save(tmp_file) response = self.client.post(''my_url'', {''image'': tmp_file}, format=''multipart'') self.assertEqual(status.HTTP_201_CREATED, response.status_code)

Esto coincidirá con una solicitud estándar de Django, donde el archivo se transfiere como un objeto de flujo y lo maneja Django REST Framework. Cuando acaba de pasar los datos del archivo, Django y Django REST Framework lo interpretan como una cadena, lo que causa problemas porque está esperando una transmisión.

Y para quienes vengan aquí buscando otro error común, por qué las cargas de archivos simplemente no funcionarán, pero los datos de formularios normales: asegúrese de establecer format="multipart" al crear la solicitud .

Esto también da un problema similar, y fue señalado por @RobinElvin en los comentarios

Fue porque me faltaba format = ''multipart''


No es tan sencillo entender cómo hacerlo si desea utilizar el método PATCH, pero encontré la solución en esta pregunta .

from django.test.client import BOUNDARY, MULTIPART_CONTENT, encode_multipart with open(tmp_file.name, ''rb'') as fp: response = self.client.patch( ''my_url'', encode_multipart(BOUNDARY, {''image'': fp}), content_type=MULTIPART_CONTENT )


Para aquellos en Windows, la respuesta es un poco diferente. Tenía que hacer lo siguiente:

resp = None with tempfile.NamedTemporaryFile(suffix=''.jpg'', delete=False) as tmp_file: image = Image.new(''RGB'', (100, 100), "#ddd") image.save(tmp_file, format="JPEG") tmp_file.close() # create status update with open(tmp_file.name, ''rb'') as photo: resp = self.client.post(''/api/articles/'', {''title'': ''title'', ''content'': ''content'', ''photo'': photo, }, format=''multipart'') os.remove(tmp_file.name)

La diferencia, como se señala en esta respuesta ( https://.com/a/23212515/72350 ), el archivo no se puede utilizar después de que se cerró en Windows. En Linux, la respuesta de @Meistro debería funcionar.


Usuarios de Python 3: asegúrese de open el archivo en mode=''rb'' (leer, binario). De lo contrario, cuando las llamadas de Django read en el archivo, el códec de utf-8 comenzará a asfixiarse de inmediato. El archivo debe decodificarse como binario, no utf-8, ascii o cualquier otra codificación.

# This won''t work in Python 3 with open(tmp_file.name) as fp: response = self.client.post(''my_url'', {''image'': fp}, format=''multipart'') # Set the mode to binary and read so it can be decoded as binary with open(tmp_file.name, ''rb'') as fp: response = self.client.post(''my_url'', {''image'': fp}, format=''multipart'')