Python Django Rest框架文件上传

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20473572/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 20:32:47  来源:igfitidea点击:

Django Rest Framework File Upload

pythondjangoangularjsdjango-rest-framework

提问by Pawan

I am using Django Rest Framework and AngularJs to upload a file. My view file looks like this:

我正在使用 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)

As the last line of post method should return all the data, I have several questions:

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

  • how to check if there is anything in request.FILES?
  • how to serialize file field?
  • how should I use parser?
  • 如何检查里面是否有东西request.FILES
  • 如何序列化文件字段?
  • 我应该如何使用解析器?

采纳答案by pleasedontbelong

Use the FileUploadParser, it's all in the request. Use a put method instead, you'll find an example in the docs :)

使用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)

回答by ybendana

I'm using the same stack and was also looking for an example of file upload, but my case is simpler since I use the ModelViewSet instead of APIView. The key turned out to be the pre_save hook. I ended up using it together with the angular-file-upload module like so:

我正在使用相同的堆栈,并且也在寻找文件上传的示例,但我的情况更简单,因为我使用的是 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]
        });
    };
});

回答by Vipul J

Finally I am able to upload image using Django. Here is my working code

最后我可以使用 Django 上传图像。这是我的工作代码

views.py

视图.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()  # File should be closed only after all chuns are added

        # ...
        # do some stuff with uploaded file
        # ...
        return Response(up_file.name, status.HTTP_201_CREATED)

urls.py

网址.py

urlpatterns = patterns('', 
url(r'^imageUpload', views.FileUploadView.as_view())

curl request to upload

curl 上传请求

curl -X POST -S -H -u "admin:password" -F "[email protected];type=image/jpg" 127.0.0.1:8000/resourceurl/imageUpload

回答by Jadav Bheda

I solved this problem with ModelViewSet and ModelSerializer. Hope this will help community.

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

I also preffer to have validation and Object->JSON (and vice-versa) login in serializer itself rather than in views.

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

Lets understand it by example.

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

Say, I want to create FileUploader API. Where it will be storing fields like id, file_path, file_name, size, owner etc in database. See sample model below:

说,我想创建 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)

Now, For APIs this is what I want:

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

  1. GET:
  1. 得到:

When I fire the GET endpoint, I want all above fields for every uploaded file.

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

  1. POST:
  1. 邮政:

But for user to create/upload file, why she has to worry about passing all these fields. She can just upload the file and then, I suppose, serializer can get rest of the fields from uploaded FILE.

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

Searilizer:Question:I created below serializer to serve my purpose. But not sure if its the right way to implement it.

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)

Viewset for reference:

供参考的视图集:

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

回答by anjaneyulubatta505

In django-rest-framework request data is parsed by the Parsers.
http://www.django-rest-framework.org/api-guide/parsers/

在 django-rest-framework 中,请求数据由Parsers.
http://www.django-rest-framework.org/api-guide/parsers/

By default django-rest-framework takes parser class JSONParser. It will parse the data into json. so, files will not be parsed with it.
If we want files to be parsed along with other data we should use one of the below parser classes.

默认情况下 django-rest-framework 采用解析器类JSONParser。它将数据解析为json。所以,文件不会被它解析。
如果我们希望文件与其他数据一起被解析,我们应该使用以下解析器类之一。

FormParser
MultiPartParser
FileUploadParser

回答by Nithin

After spending 1 day on this, I figured out that ...

在花了 1 天的时间之后,我发现......

For someone who needs to upload a file and send some data, there is no straight fwd way you can get it to work. There is an open issuein json api specs for this. One possibility i have seen is to use multipart/relatedas shown here, but i think its very hard to implement it in drf.

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

Finally what i had implemented was to send the request as formdata. You would send each file as fileand all other data as text. Now for sending the data as text you have two choices. case 1) you can send each data as key value pair or case 2) you can have a single key called dataand send the whole json as string in value.

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

The first method would work out of the box if you have simple fields, but will be a issue if you have nested serializes. The multipart parser wont be able to parse the nested fields.

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

Below i am providing the implementation for both the cases

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

Models.py

模型.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 special changes needed, not showing my serializer here as its too lengthy because of the writable ManyToMany Field implimentation.

serializers.py -> 不需要特殊更改,由于可写的 ManyToMany 字段实现,这里没有显示我的序列化程序太长。

views.py

视图.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'

Now, if you are following the first method and is only sending non-Json data as key value pairs, you don't need a custom parser class. DRF'd MultipartParser will do the job. But for the second case or if you have nested serializers (like i have shown) you will need custom parser as shown below.

现在,如果您遵循第一种方法并且仅将非 Json 数据作为键值对发送,则不需要自定义解析器类。DRF 的 MultipartParser 将完成这项工作。但是对于第二种情况,或者如果您有嵌套的序列化程序(如我所示),您将需要自定义解析器,如下所示。

utils.py

实用程序.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)

This serializer would basically parse any json content in the values.

这个序列化器基本上会解析值中的任何 json 内容。

The request example in post man for both cases: case 1 case 1,

两种情况下 post man 中的请求示例: case 1 情况1

Case 2 case2

案例二 案例2

回答by sidhu Munagala

    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)

回答by Syed Faizan

def post(self,request):
        serializer = ProductSerializer(data=request.DATA, files=request.FILES)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)

回答by Wolfgang Leon

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.

我想写另一个我觉得更干净、更容易维护的选项。我们将使用 defaultRouter 为我们的视图集添加 CRUD url,我们将添加一个固定的 url,指定同一视图集中的上传者视图。

**** 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)

Project's main urls.py

项目的主要 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),
]

.- 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.

当我们将 @action 装饰器添加到我们的类方法 'uploader' 时,魔法就会发生。通过指定“methods=['put']”参数,我们只允许PUT请求;非常适合文件上传。

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.

我还添加了参数“parser_classes”以显示您可以选择将解析您的内容的解析器。我从 rest_framework_csv 包中添加了 CSVParser,以演示如果需要此功能,我们如何仅接受某些类型的文件,在我的情况下,我只接受“内容类型:文本/csv”。注意:如果您要添加自定义解析器,则需要在 ViewSet 的 parsers_classes 中指定它们,因为在访问上传器方法解析器之前,请求会将允许的 media_type 与主(类)解析器进行比较。

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.

现在我们需要告诉 Django 如何去这个方法以及可以在我们的 url 中实现的位置。那是我们添加固定 url 的时候(简单目的)。该 Url 将采用稍后将在方法中传递的“文件名”参数。我们需要将这个方法“uploader”传递给 PostsViewSet.as_view 方法,在列表中指定 http 协议('PUT')。

When we land in the following url

当我们登陆以下网址时

 http://example.com/posts/uploader/ 

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

它将期望一个带有指定“Content-Type”和 Content-Disposition:attachment 的标头的 PUT 请求;文件名=“something.csv”。

curl -v -u user:pass http://example.com/posts/uploader/ --upload-file ./something.csv --header "Content-type:text/csv"

回答by x-yuri

From my experience, you don't need to do anything particular about file fields, you just tell it to make use of the file field:

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

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)),
]

and you're ready to upload files:

您已准备好上传文件:

curl -sS http://example.com/api/photos/ -F 'file=@/path/to/file'

Add -F field=valuefor each extra field your model has. And don't forget to add authentication.

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