python - tutorial - integrar angularjs con django
Imagen de carga de Django REST Framework: "Los datos enviados no eran un archivo" (2)
El problema que está enfrentando es que el marco REST de Django
espera que los archivos se carguen como datos de formularios de varias partes
, a través de los métodos estándar de carga de archivos.
Este suele
ser un campo de
file
, pero el
objeto
Blob
JavaScript también funciona
para AJAX.
Está buscando cargar los archivos con una cadena codificada en base64, en lugar del archivo sin formato, que
no es compatible de manera predeterminada
.
Hay implementaciones de un
Base64ImageField
por ahí
, pero la más prometedora
vino por una solicitud de extracción
.
Dado que estos fueron diseñados principalmente para Django REST framework 2.x, mejoré el de la solicitud de extracción y creé uno que debería ser compatible con DRF 3.
serializers.py
from rest_framework import serializers
class Base64ImageField(serializers.ImageField):
"""
A Django REST framework field for handling image-uploads through raw post data.
It uses base64 for encoding and decoding the contents of the file.
Heavily based on
https://github.com/tomchristie/django-rest-framework/pull/1268
Updated for Django REST framework 3.
"""
def to_internal_value(self, data):
from django.core.files.base import ContentFile
import base64
import six
import uuid
# Check if this is a base64 string
if isinstance(data, six.string_types):
# Check if the base64 string is in the "data:" format
if ''data:'' in data and '';base64,'' in data:
# Break out the header from the base64 content
header, data = data.split('';base64,'')
# Try to decode the file. Return validation error if it fails.
try:
decoded_file = base64.b64decode(data)
except TypeError:
self.fail(''invalid_image'')
# Generate file name:
file_name = str(uuid.uuid4())[:12] # 12 characters are more than enough.
# Get the file name extension:
file_extension = self.get_file_extension(file_name, decoded_file)
complete_file_name = "%s.%s" % (file_name, file_extension, )
data = ContentFile(decoded_file, name=complete_file_name)
return super(Base64ImageField, self).to_internal_value(data)
def get_file_extension(self, file_name, decoded_file):
import imghdr
extension = imghdr.what(file_name, decoded_file)
extension = "jpg" if extension == "jpeg" else extension
return extension
Esto debería usarse en reemplazo del
ImageField
estándar proporcionado por el marco REST de Django.
Entonces tu serializador se convertiría
class ImageSerializer(serializers.ModelSerializer):
image = Base64ImageField(
max_length=None, use_url=True,
)
class Meta:
model = Image
fields = ("id", ''image'', ''owner'', ''time_created'', )
Esto debería permitirle especificar una cadena codificada en base64 o el objeto
Blob
estándar que el marco REST de Django normalmente espera.
Me estoy inclinando a cargar archivos en Django, y aquí encuentro un problema que debería ser trivial, con el error:
Los datos enviados no eran un archivo. Verifique el tipo de codificación en el formulario.
Debajo está el detalle.
Nota: También miré en Django Rest Framework ImageField , y probé
serializer = ImageSerializer(data=request.data, files=request.FILES)
pero consigo
TypeError:
__init__()
obtuvo un argumento de palabra clave inesperado ''archivos''
Tengo un modelo de
Image
que me gustaría interactuar a través del marco Django REST:
modelos.py
class Image(models.Model):
image = models.ImageField(upload_to=''item_images'')
owner = models.ForeignKey(
User, related_name=''uploaded_item_images'',
blank=False,
)
time_created = models.DateTimeField(auto_now_add=True)
serializers.py
class ImageSerializer(serializers.ModelSerializer):
image = serializers.ImageField(
max_length=None, use_url=True,
)
class Meta:
model = Image
fields = ("id", ''image'', ''owner'', ''time_created'', )
settings.py
''DEFAULT_PARSER_CLASSES'': (
''rest_framework.parsers.JSONParser'',
''rest_framework.parsers.FormParser'',
''rest_framework.parsers.MultiPartParser'',
),
El front-end (usando AngularJS y
angular-restmod
o
$resource
) envía datos
JSON
con el
owner
y la
image
del formulario:
Entrada:
{"owner": 5, "image": "data:image/jpeg;base64,/9j/4QqdRXhpZgAATU0A..."}
En el backend,
request.data
muestra
{u''owner'': 5, u''image'': u''data:image/jpeg;base64,/9j/4QqdRXhpZgAATU0AKgAAA..."}
Pero entonces
ImageSerializer(data=request.data).errors
muestra el error
ReturnDict([(''image'', [u''The submitted data was not a file. Check the encoding type on the form.''])])
¿Me pregunto qué debo hacer para corregir el error?
EDITAR: parte JS
Los códigos
frontales
relacionados constan de dos partes: una
directive
angular-file-dnd
(disponible
here
) para colocar el archivo en la página y
angular-restmod
, que proporciona operaciones CRUD:
<!-- The template: according to angular-file-dnd, -->
<!-- it will store the dropped image into variable $scope.image -->
<div file-dropzone="[image/png, image/jpeg, image/gif]" file="image" class=''method'' data-max-file-size="3" file-name="imageFileName">
<div layout=''row'' layout-align=''center''>
<i class="fa fa-upload" style=''font-size:50px;''></i>
</div>
<div class=''text-large''>Drap & drop your photo here</div>
</div>
# A simple `Image` `model` to perform `POST`
$scope.image_resource = Image.$build();
$scope.upload = function() {
console.log("uploading");
$scope.image_resource.image = $scope.image;
$scope.image_resource.owner = Auth.get_profile().user_id;
return $scope.image_resource.$save();
};
Una actualización sobre el problema: en este momento cambié a usar
ng-file-upload
, que envía datos de imagen en el formato adecuado.
Me encontré con el mismo problema hace unos días. Aquí está mi vista del marco de descanso django para manejar la carga de archivos
views.py
class PhotoUploadView(APIView):
parser_classes = (FileUploadParser,)
def post(self, request):
user = self.request.user
if not user:
return Response(status=status.HTTP_403_FORBIDDEN)
profile = None
data = None
photo = None
file_form = FileUploadForm(request.POST,request.FILES)
if file_form.is_valid():
photo = request.FILES[''file'']
else:
return Response(ajax_response(file_form),status=status.HTTP_406_NOT_ACCEPTABLE)
try:
profile = Organizer.objects.get(user=user)
profile.photo = photo
profile.save()
data = OrganizersSerializer(profile).data
except Organizer.DoesNotExist:
profile = Student.objects.get(user=user)
profile.photo = photo
profile.save()
data = StudentsSerializer(profile).data
return Response(data)
En el front-end, utilicé lib de angular-file-upload .
Aquí está mi entrada de archivo
<div ng-file-drop="" ng-file-select="" ng-model="organizer.photo" class="drop-box" drag-over-class="{accept:''dragover'', reject:''dragover-err'', delay:100}" ng-multiple="false" allow-dir="true" accept="image/*">
Drop Images or PDFs<div>here</div>
</div>
Y aquí está mi servicio de carga
main.js
(function () {
''use strict'';
angular
.module(''trulii.utils.services'')
.factory(''UploadFile'', UploadFile);
UploadFile.$inject = [''$cookies'', ''$http'',''$upload'',''$window'',''Authentication''];
/**
* @namespace Authentication
* @returns {Factory}
*/
function UploadFile($cookies, $http,$upload,$window,Authentication) {
/**
* @name UploadFile
* @desc The Factory to be returned
*/
var UploadFile = {
upload_file: upload_file,
};
return UploadFile;
function upload_file(file) {
return $upload.upload({
url: ''/api/users/upload/photo/'', // upload.php script, node.js route, or servlet url
//method: ''POST'' or ''PUT'',
//headers: {''Authorization'': ''xxx''}, // only for html5
//withCredentials: true,
file: file, // single file or a list of files. list is only for html5
//fileName: ''doc.jpg'' or [''1.jpg'', ''2.jpg'', ...] // to modify the name of the file(s)
//fileFormDataName: myFile, // file formData name (''Content-Disposition''), server side request form name
// could be a list of names for multiple files (html5). Default is ''file''
//formDataAppender: function(formData, key, val){} // customize how data is added to the formData.
// See #40#issuecomment-28612000 for sample code
})
}
}
})();