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
Django Rest Framework File Upload
提问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,这就是我想要的:
- GET:
- 得到:
When I fire the GET endpoint, I want all above fields for every uploaded file.
当我触发 GET 端点时,我希望每个上传的文件都包含上述所有字段。
- POST:
- 邮政:
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
,
回答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模型具有的每个额外字段。并且不要忘记添加身份验证。

