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'')