json - update - fileuploadparser
Usando Django Rest Framework, ¿cómo puedo cargar un archivo Y enviar una carga JSON? (4)
Envío JSON y una imagen para crear / actualizar un objeto de producto. A continuación se muestra una vista de creación que funciona para mí.
Serializador
class ProductCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = [
"id",
"product_name",
"product_description",
"product_price",
]
def create(self,validated_data):
return Product.objects.create(**validated_data)
Ver
from rest_framework import generics,status
from rest_framework.parsers import FormParser,MultiPartParser
class ProductCreateAPIView(generics.CreateAPIView):
queryset = Product.objects.all()
serializer_class = ProductCreateSerializer
permission_classes = [IsAdminOrIsSelf,]
parser_classes = (MultiPartParser,FormParser,)
def perform_create(self,serializer,format=None):
owner = self.request.user
if self.request.data.get(''image'') is not None:
product_image = self.request.data.get(''image'')
serializer.save(owner=owner,product_image=product_image)
else:
serializer.save(owner=owner)
Ejemplo de prueba:
def test_product_creation_with_image(self):
url = reverse(''products_create_api'')
self.client.login(username=''testaccount'',password=''testaccount'')
data = {
"product_name" : "Potatoes",
"product_description" : "Amazing Potatoes",
"image" : open("local-filename.jpg","rb")
}
response = self.client.post(url,data)
self.assertEqual(response.status_code,status.HTTP_201_CREATED)
Estoy tratando de escribir un controlador de la API de Django Rest Framework que pueda recibir un archivo así como una carga JSON. He establecido el MultiPartParser como el analizador de controlador.
Sin embargo, parece que no puedo hacer ambas cosas. Si envío la carga útil con el archivo como una solicitud de varias partes, la carga de JSON está disponible de forma modificada en el request.data (primera parte de texto hasta los primeros dos puntos como la clave, el resto son los datos). Puedo enviar los parámetros en parámetros de formulario estándar muy bien, pero el resto de mi API acepta las cargas JSON y quería ser coherente. El request.body no se puede leer a medida que aumenta *** RawPostDataException: You cannot access body after reading from request''s data stream
Por ejemplo, un archivo y esta carga útil en el cuerpo de la solicitud:
{"title":"Document Title", "description":"Doc Description"}
Se convierte en
<QueryDict: {u''fileUpload'': [<InMemoryUploadedFile: 20150504_115355.jpg (image/jpeg)>, <InMemoryUploadedFile: Front end lead.doc (application/msword)>], u''{%22title%22'': [u''"Document Title", "description":"Doc Description"}'']}>
¿Hay alguna forma de hacer esto? ¿Puedo comer mi pastel, conservarlo y no subir de peso?
Edit: Se sugirió que esto podría ser una copia de la imagen de carga de Django REST Framework: "Los datos enviados no eran un archivo" . No lo es. La carga y la solicitud se realizan en varias partes, y tenga en cuenta que el archivo y la carga están bien. Incluso puedo completar la solicitud con variables de formulario estándar. Pero quiero ver si puedo obtener una carga JSON allí.
Para alguien que necesita cargar un archivo y enviar algunos datos, no existe una manera directa de hacerlo funcionar. Hay un problema abierto en las especificaciones de json api para esto. Una posibilidad que he visto es usar multipart/related
como se muestra here , pero creo que es muy difícil implementarlo en drf.
Finalmente, lo que había implementado fue enviar la solicitud como formdata
. Enviaría cada archivo como archivo y todos los demás datos como texto. Ahora, para enviar los datos como texto, puede tener una sola clave llamada datos y enviar todo el json como cadena en valor.
Modelos.py
class Posts(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False)
caption = models.TextField(max_length=1000)
media = models.ImageField(blank=True, default="", upload_to="posts/")
tags = models.ManyToManyField(''Tags'', related_name=''posts'')
serializers.py -> no se necesitan cambios especiales, no se muestra mi serializador aquí porque es demasiado largo debido a la implementación de campo ManyToMany Field que se puede escribir.
vistas.py
class PostsViewset(viewsets.ModelViewSet):
serializer_class = PostsSerializer
parser_classes = (MultipartJsonParser, parsers.JSONParser)
queryset = Posts.objects.all()
lookup_field = ''id''
Necesitará un analizador personalizado como se muestra a continuación para analizar json.
utils.py
from django.http import QueryDict
import json
from rest_framework import parsers
class MultipartJsonParser(parsers.MultiPartParser):
def parse(self, stream, media_type=None, parser_context=None):
result = super().parse(
stream,
media_type=media_type,
parser_context=parser_context
)
data = {}
# find the data field and parse it
data = json.loads(result.data["data"])
qdict = QueryDict('''', mutable=True)
qdict.update(data)
return parsers.DataAndFiles(qdict, result.files)
Sé que este es un hilo viejo, pero acabo de encontrar esto. Tuve que usar MultiPartParser
para obtener mi archivo y datos adicionales para encontrarlos juntos. Así es como se ve mi código:
# views.py
class FileUploadView(views.APIView):
parser_classes = (MultiPartParser,)
def put(self, request, filename, format=None):
file_obj = request.data[''file'']
ftype = request.data[''ftype'']
caption = request.data[''caption'']
# ...
# do some stuff with uploaded file
# ...
return Response(status=204)
Mi código AngularJS usando ng-file-upload
es:
file.upload = Upload.upload({
url: "/api/picture/upload/" + file.name,
data: {
file: file,
ftype: ''final'',
caption: ''This is an image caption''
}
});
Tengo un problema similar, aquí está mi solución:
Primero agregue esto a su configuración ( settings.py ):
''DEFAULT_PARSER_CLASSES'': (
''rest_framework.parsers.JSONParser'',
''rest_framework.parsers.MultiPartParser'',
''rest_framework.parsers.FileUploadParser'',
),
Luego en su Serializador (ej: ''archivo''):
file = serializers.FileField()
Y en su vista agregue:
parser_classes = (FileUploadParser, JSONParser)
Con esto podría publicar un archivo y varios campos, pero debe especificar:
- el formato de la publicación como ''multiparte''
- y este encabezado http:
HTTP_CONTENT_DISPOSITION = "archivo adjunto; nombre_archivo = su_nombre_archivo.jpg"