Загрузка Файла 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 53

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 вот что я хочу:

  1. GET:

когда я запускаю конечную точку GET, я хочу, чтобы все поля выше для каждого загруженного файла.

  1. сообщение:

но для пользователя, чтобы создать / загрузить файл, почему она должна беспокоиться о передаче всех этих полей. Она может просто загрузить файл, а затем, я полагаю, сериализатор может получить остальные поля из загруженного файла.

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 в значениях.

пример запроса в post man для обоих случаев: case 1 case 1,

корпус 2 case2

в 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)
def post(self,request):
        serializer = ProductSerializer(data=request.DATA, files=request.FILES)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)