Django Rest框架文件上传

Paw*_*wan 76 python django angularjs 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)
Run Code Online (Sandbox Code Playgroud)

由于post方法的最后一行应该返回所有数据,我有几个问题:

  • 如何检查是否有任何内容request.FILES
  • 如何序列化文件字段?
  • 我该如何使用解析器?

ybe*_*ana 72

我正在使用相同的堆栈,并且还在寻找文件上传的示例,但我的情况更简单,因为我使用ModelViewSet而不是APIView.密钥竟然是pre_save钩子.我最终将它与angular-file-upload模块一起使用,如下所示:

# 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]
        });
    };
});
Run Code Online (Sandbox Code Playgroud)

  • 在drf 3.x中不推荐使用pre_save (10认同)

ple*_*ong 52

使用FileUploadParser,它都在请求中.使用put方法代替,你会在docs中找到一个例子:)

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)
Run Code Online (Sandbox Code Playgroud)

  • @pleasedontbelong为什么在这里使用PUT方法而不是POST? (6认同)
  • 嗨@pleasedontbelong,如果它正在创建一个新记录,它会改为POST吗?它还能用于FileUploadParser吗? (5认同)
  • 为什么是 FileUploadParser?“FileUploadParser 用于可以将文件作为原始数据请求上传的本机客户端。对于基于 Web 的上传或支持分段上传的本机客户端,您应该改用 MultiPartParser 解析器。” 一般来说,这似乎不是一个好的选择。更重要的是,我没有看到文件上传需要任何特定的[处理](/sf/answers/3784138851/)。 (3认同)
  • 对于第二个@ x-yuri,DRF抱怨当我使用FileUploadParser时Content-Disposition标头为空。MultiPartParser更为简单,因为它仅假定文件名是“表单”字段中的给定文件名。 (2认同)

Vip*_*l J 29

最后,我可以使用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)
Run Code Online (Sandbox Code Playgroud)

urls.py

urlpatterns = patterns('', 
url(r'^imageUpload', views.FileUploadView.as_view())
Run Code Online (Sandbox Code Playgroud)

curl请求上传

curl -X POST -S -H -u "admin:password" -F "file=@img.jpg;type=image/jpg" 127.0.0.1:8000/resourceurl/imageUpload
Run Code Online (Sandbox Code Playgroud)

  • 为什么destination.close()放在for循环的内部? (14认同)
  • 似乎最好使用`with open('/ Users/Username /'+ up_file.name,'wb +')作为目标:`并完全删除关闭 (9认同)
  • 我一整天都依赖这个回答者......直到我发现当你要上传多个文件时,需要的不是`FileUploadParser`,而是`MultiPartParser`! (3认同)

sad*_*dat 9

如果有人对 Django Rest Framework 的 ModelViewset 的最简单示例感兴趣。

模型是,

class MyModel(models.Model):
    name = models.CharField(db_column='name', max_length=200, blank=False, null=False, unique=True)
    imageUrl = models.FileField(db_column='image_url', blank=True, null=True, upload_to='images/')

    class Meta:
        managed = True
        db_table = 'MyModel'
Run Code Online (Sandbox Code Playgroud)

序列化器,

class MyModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = MyModel
        fields = "__all__"
Run Code Online (Sandbox Code Playgroud)

视图是,

class MyModelView(viewsets.ModelViewSet):
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer
Run Code Online (Sandbox Code Playgroud)

在邮递员中测试,

在此处输入图片说明


Nit*_*hin 8

在此上花了1天后,我发现...

对于需要上载文件并发送一些数据的人,没有直接的前进方式可以使它工作。json api规范中有一个未解决的问题。我看到的一种可能性是使用此处multipart/related所示的方法,但是我认为很难在drf中实现它。

最后,我实现的是将请求发送为formdata。您将每个文件作为文件发送,所有其他数据作为文本发送。现在,以文本形式发送数据有两种选择。情况1)您可以将每个数据作为键值对发送,或者情况2)您可以有一个名为data的键,并将整个json作为值字符串发送。

如果您具有简单的字段,则第一种方法开箱即用,但如果嵌套了序列化,则将是一个问题。多部分解析器将无法解析嵌套字段。

下面我提供两种情况的实现

型号

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')
Run Code Online (Sandbox Code Playgroud)

serializers.py->无需特殊更改,由于可写的ManyToMany Field含义,此处没有显示我的序列化程序太长。

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'
Run Code Online (Sandbox Code Playgroud)

现在,如果您遵循第一种方法,并且仅将非Json数据作为键值对发送,则不需要自定义解析器类。DRF的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)
Run Code Online (Sandbox Code Playgroud)

该序列化程序将基本上解析值中的所有json内容。

邮递员在两种情况下的请求示例:案例1 情况1

情况二 案例2


Ach*_*ake 8

模型.py

from django.db import models

import uuid

class File(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    file = models.FileField(blank=False, null=False)
    
    def __str__(self):
        return self.file.name
Run Code Online (Sandbox Code Playgroud)

序列化程序.py

from rest_framework import serializers
from .models import File

class FileSerializer(serializers.ModelSerializer):
    class Meta:
        model = File
        fields = "__all__"
Run Code Online (Sandbox Code Playgroud)

视图.py

from django.shortcuts import render
from rest_framework.parsers import FileUploadParser
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status

from .serializers import FileSerializer


class FileUploadView(APIView):
    permission_classes = []
    parser_class = (FileUploadParser,)

    def post(self, request, *args, **kwargs):

      file_serializer = FileSerializer(data=request.data)

      if file_serializer.is_valid():
          file_serializer.save()
          return Response(file_serializer.data, status=status.HTTP_201_CREATED)
      else:
          return Response(file_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Run Code Online (Sandbox Code Playgroud)

网址.py

from apps.files import views as FileViews

urlpatterns = [
    path('api/files', FileViews.FileUploadView.as_view()),
]
Run Code Online (Sandbox Code Playgroud)

设置.py

# file uload parameters
MEDIA_URL =  '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
Run Code Online (Sandbox Code Playgroud)

api/files您的文件附加到一个form-data字段发送一个 post 请求file。该文件将上传到/media文件夹,并添加一个带有 id 和文件名的 db 记录。


Jad*_*eda 7

我用 ModelViewSet 和 ModelSerializer 解决了这个问题。希望这会对社区有所帮助。

我也更喜欢在序列化程序本身而不是在视图中进行验证和 Object->JSON(反之亦然)登录。

让我们通过例子来理解它。

说,我想创建 FileUploader API。它将在数据库中存储 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)
Run Code Online (Sandbox Code Playgroud)

现在,对于 API,这就是我想要的:

  1. 得到:

当我触发 GET 端点时,我希望每个上传的文件都包含上述所有字段。

  1. 邮政:

但是对于用户创建/上传文件,她为什么要担心传递所有这些字段。她可以上传文件,然后,我想,序列化程序可以从上传的文件中获取其余字段。

Searilizer: 问题:我创建了下面的序列化程序来达到我的目的。但不确定它是否是实现它的正确方法。

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)
Run Code Online (Sandbox Code Playgroud)

供参考的视图集:

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
Run Code Online (Sandbox Code Playgroud)


x-y*_*uri 6

根据我的经验,您不需要对文件字段做任何特别的事情,只需告诉它使用文件字段即可:

from rest_framework import routers, serializers, viewsets

class Photo(django.db.models.Model):
    file = django.db.models.ImageField()

    def __str__(self):
        return self.file.name

class PhotoSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Photo
        fields = ('id', 'file')   # <-- HERE

class PhotoViewSet(viewsets.ModelViewSet):
    queryset = models.Photo.objects.all()
    serializer_class = PhotoSerializer

router = routers.DefaultRouter()
router.register(r'photos', PhotoViewSet)

api_urlpatterns = ([
    url('', include(router.urls)),
], 'api')
urlpatterns += [
    url(r'^api/', include(api_urlpatterns)),
]
Run Code Online (Sandbox Code Playgroud)

您就可以上传文件了:

curl -sS http://example.com/api/photos/ -F 'file=@/path/to/file'
Run Code Online (Sandbox Code Playgroud)

-F field=value为模型具有的每个其他字段添加。并且不要忘记添加身份验证。

  • 这。我不确定所有高票答案中有什么大惊小怪的,也许是摆弄解析器,而这对于早期版本的 DRF 来说是必要的,但截至 2021 年,通用类仅适用于基本用例,即问题似乎主要是关于什么的。 (4认同)

Wol*_*eon 5

I'd like to write another option that I feel is cleaner and easier to maintain. We'll be using the defaultRouter to add CRUD urls for our viewset and we'll add one more fixed url specifying the uploader view within the same viewset.

**** views.py 

from rest_framework import viewsets, serializers
from rest_framework.decorators import action, parser_classes
from rest_framework.parsers import JSONParser, MultiPartParser
from rest_framework.response import Response
from rest_framework_csv.parsers import CSVParser
from posts.models import Post
from posts.serializers import PostSerializer     


class PostsViewSet(viewsets.ModelViewSet):

    queryset = Post.objects.all()
    serializer_class = PostSerializer 
    parser_classes = (JSONParser, MultiPartParser, CSVParser)


    @action(detail=False, methods=['put'], name='Uploader View', parser_classes=[CSVParser],)
    def uploader(self, request, filename, format=None):
        # Parsed data will be returned within the request object by accessing 'data' attr  
        _data = request.data

        return Response(status=204)
Run Code Online (Sandbox Code Playgroud)

Project's main urls.py

**** urls.py 

from rest_framework import routers
from posts.views import PostsViewSet


router = routers.DefaultRouter()
router.register(r'posts', PostsViewSet)

urlpatterns = [
    url(r'^posts/uploader/(?P<filename>[^/]+)$', PostsViewSet.as_view({'put': 'uploader'}), name='posts_uploader')
    url(r'^', include(router.urls), name='root-api'),
    url('admin/', admin.site.urls),
]
Run Code Online (Sandbox Code Playgroud)

.- README.

The magic happens when we add @action decorator to our class method 'uploader'. By specifying "methods=['put']" argument, we are only allowing PUT requests; perfect for file uploading.

I also added the argument "parser_classes" to show you can select the parser that will parse your content. I added CSVParser from the rest_framework_csv package, to demonstrate how we can accept only certain type of files if this functionality is required, in my case I'm only accepting "Content-Type: text/csv". Note: If you're adding custom Parsers, you'll need to specify them in parsers_classes in the ViewSet due the request will compare the allowed media_type with main (class) parsers before accessing the uploader method parsers.

Now we need to tell Django how to go to this method and where can be implemented in our urls. That's when we add the fixed url (Simple purposes). This Url will take a "filename" argument that will be passed in the method later on. We need to pass this method "uploader", specifying the http protocol ('PUT') in a list to the PostsViewSet.as_view method.

When we land in the following url

 http://example.com/posts/uploader/ 
Run Code Online (Sandbox Code Playgroud)

it will expect a PUT request with headers specifying "Content-Type" and Content-Disposition: attachment; filename="something.csv".

curl -v -u user:pass http://example.com/posts/uploader/ --upload-file ./something.csv --header "Content-type:text/csv"
Run Code Online (Sandbox Code Playgroud)