Загрузка Файла Django Rest Framework
Я использую Django Rest Framework и AngularJs для загрузки файла. Мой вид файла выглядит так:
class ProductList(APIView):
authentication_classes = (authentication.TokenAuthentication,)
def get(self,request):
if request.user.is_authenticated():
userCompanyId = request.user.get_profile().companyId
products = Product.objects.filter(company = userCompanyId)
serializer = ProductSerializer(products,many=True)
return Response(serializer.data)
def post(self,request):
serializer = ProductSerializer(data=request.DATA, files=request.FILES)
if serializer.is_valid():
serializer.save()
return Response(data=request.DATA)
поскольку последняя строка метода post должна возвращать все данные, у меня есть несколько вопросов:
- как проверить, есть ли что-нибудь в
request.FILES
? - как сериализовать поле файла?
- как я должен использовать парсер?
8 ответов:
использовать FileUploadParser, Это все в запросе. Использовать метод put, вы найдете пример в документации :)
class FileUploadView(views.APIView): parser_classes = (FileUploadParser,) def put(self, request, filename, format=None): file_obj = request.FILES['file'] # do some stuff with uploaded file return Response(status=204)
Я использую тот же стек и также искал пример загрузки файла, но мой случай проще, так как я использую ModelViewSet вместо APIView. Ключ оказался крючком pre_save. Я закончил тем, что использовал его вместе с модулем угловой загрузки файлов следующим образом:
# Django class ExperimentViewSet(ModelViewSet): queryset = Experiment.objects.all() serializer_class = ExperimentSerializer def pre_save(self, obj): obj.samplesheet = self.request.FILES.get('file') class Experiment(Model): notes = TextField(blank=True) samplesheet = FileField(blank=True, default='') user = ForeignKey(User, related_name='experiments') class ExperimentSerializer(ModelSerializer): class Meta: model = Experiment fields = ('id', 'notes', 'samplesheet', 'user') // AngularJS controller('UploadExperimentCtrl', function($scope, $upload) { $scope.submit = function(files, exp) { $upload.upload({ url: '/api/experiments/' + exp.id + '/', method: 'PUT', data: {user: exp.user.id}, file: files[0] }); }; });
наконец, я могу загрузить изображение с помощью Django. Вот мой рабочий код
views.py
class FileUploadView(APIView): parser_classes = (FileUploadParser, ) def post(self, request, format='jpg'): up_file = request.FILES['file'] destination = open('/Users/Username/' + up_file.name, 'wb+') for chunk in up_file.chunks(): destination.write(chunk) destination.close() # ... # do some stuff with uploaded file # ... return Response(up_file.name, status.HTTP_201_CREATED)
urls.py
urlpatterns = patterns('', url(r'^imageUpload', views.FileUploadView.as_view())
curl запрос на загрузку
curl -X POST -S -H -u "admin:password" -F "file=@img.jpg;type=image/jpg" 127.0.0.1:8000/resourceurl/imageUpload
я решил эту проблему с ModelViewSet и ModelSerializer. Надеюсь, это поможет сообществу.
Я также предпочитаю иметь проверку и объект - >JSON (и наоборот) войти в сам сериализатор, а не в представления.
давайте разберемся на примере.
скажем, я хочу создать API FileUploader. Где он будет хранить такие поля, как id, file_path, file_name, size, owner и т. д. В базе данных. См. пример модели ниже:
class FileUploader(models.Model): file = models.FileField() name = models.CharField(max_length=100) #name is filename without extension version = models.IntegerField(default=0) upload_date = models.DateTimeField(auto_now=True, db_index=True) owner = models.ForeignKey('auth.User', related_name='uploaded_files') size = models.IntegerField(default=0)
Теперь, Для API вот что я хочу:
- GET:
когда я запускаю конечную точку GET, я хочу, чтобы все поля выше для каждого загруженного файла.
- сообщение:
но для пользователя, чтобы создать / загрузить файл, почему она должна беспокоиться о передаче всех этих полей. Она может просто загрузить файл, а затем, я полагаю, сериализатор может получить остальные поля из загруженного файла.
Searilizer: вопрос: I создан ниже сериализатор, чтобы служить моей цели. Но не уверен, что это правильный способ его реализации.
class FileUploaderSerializer(serializers.ModelSerializer): # overwrite = serializers.BooleanField() class Meta: model = FileUploader fields = ('file','name','version','upload_date', 'size') read_only_fields = ('name','version','owner','upload_date', 'size') def validate(self, validated_data): validated_data['owner'] = self.context['request'].user validated_data['name'] = os.path.splitext(validated_data['file'].name)[0] validated_data['size'] = validated_data['file'].size #other validation logic return validated_data def create(self, validated_data): return FileUploader.objects.create(**validated_data)
Viewset для справки:
class FileUploaderViewSet(viewsets.ModelViewSet): serializer_class = FileUploaderSerializer parser_classes = (MultiPartParser, FormParser,) # overriding default query set queryset = LayerFile.objects.all() def get_queryset(self, *args, **kwargs): qs = super(FileUploaderViewSet, self).get_queryset(*args, **kwargs) qs = qs.filter(owner=self.request.user) return qs
проведя 1 день на этом, я понял, что ...
для тех, кто должен загрузить файл и отправить некоторые данные, нет прямого способа fwd вы можете заставить его работать. Существует открыть вопрос в спецификациях api json для этого. Одной из возможностей является использование
multipart/related
как показано здесь, но я думаю, что его очень трудно реализовать в drf.наконец то, что я реализовал, было отправить запрос как
formdata
. Вы бы отправили каждый файл как file и все остальные данные в виде текста. Теперь для отправки данных в виде текста у вас есть два варианта. случай 1) Вы можете отправить каждый данные в качестве пары значений ключа или случай 2) Вы можете иметь один ключ под названием сведения и отправить весь json в виде строки в значении.первый метод будет работать из коробки, если у вас есть простые поля, но будет проблемой, если у вас есть вложенные сериализации. Составной парсер не сможет анализировать вложенные поля.
ниже я обеспечение реализации для обоих случаев
Models.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 -> никаких специальных изменений не требуется, не показывая мой сериализатор здесь, как его слишком долго из-за записи manytomany поля имплиментации.
views.py
class PostsViewset(viewsets.ModelViewSet): serializer_class = PostsSerializer #parser_classes = (MultipartJsonParser, parsers.JSONParser) use this if you have simple key value pair as data with no nested serializers #parser_classes = (parsers.MultipartParser, parsers.JSONParser) use this if you want to parse json in the key value pair data sent queryset = Posts.objects.all() lookup_field = 'id'
теперь, если вы следуете первому методу и отправляете только данные, отличные от Json, в качестве пар значений ключей, вам не нужен пользовательский класс синтаксического анализатора. ФПИ бы MultipartParser будет делать эту работу. Но для во втором случае или если у вас есть вложенные сериализаторы (как я показал) вам понадобится настраиваемый парсер, как показано ниже.
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 = {} # for case1 with nested serializers # parse each field with json for key, value in result.data.items(): if type(value) != str: data[key] = value continue if '{' in value or "[" in value: try: data[key] = json.loads(value) except ValueError: data[key] = value else: data[key] = value # for case 2 # 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)
этот сериализатор будет в основном анализировать любое содержимое json в значениях.
в django-rest-framework данные запроса анализируются
Parsers
.
http://www.django-rest-framework.org/api-guide/parsers/по умолчанию django-rest-framework принимает класс парсера
JSONParser
. Он будет анализировать данные в json. таким образом, файлы не будут анализироваться с ним.
Если мы хотим, чтобы файлы были проанализированы вместе с другими данными, мы должны использовать один из следующих классов анализатора.FormParser MultiPartParser FileUploadParser
from rest_framework import status from rest_framework.response import Response class FileUpload(APIView): def put(request): try: file = request.FILES['filename'] #now upload to s3 bucket or your media file except Exception as e: print e return Response(status, status.HTTP_500_INTERNAL_SERVER_ERROR) return Response(status, status.HTTP_200_OK)