我正在使用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
中是否有任何内容?答案 0 :(得分:60)
我使用相同的堆栈并且还在寻找文件上传的示例,但我的情况更简单,因为我使用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]
});
};
});
答案 1 :(得分:43)
在请求中使用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)
答案 2 :(得分:26)
最后,我可以使用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
答案 3 :(得分:6)
如果有人对ModelViewset for Django Rest Framework最简单的示例感兴趣。
模型是
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'
序列化器,
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = "__all__"
视图是
class MyModelView(viewsets.ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
邮递员测试,
答案 4 :(得分:5)
花了一天时间,我发现了......
对于需要上传文件并发送一些数据的人来说,没有直接的方法可以让它发挥作用。 json api规范中有一个open issue。我看到的一种可能性是使用multipart/related
如here所示,但我认为很难在drf中实现它。
最后我实施的是将请求发送为formdata
。您可以将每个文件作为文件发送,将所有其他数据作为文本发送。
现在,要将数据作为文本发送,您有两个选择。情况1)您可以将每个数据作为键值对或案例发送2)您可以拥有一个名为 data 的键,并将整个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 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'
现在,如果您遵循第一种方法并且仅将非Json数据作为键值对发送,则您不需要自定义解析器类。 DRF&#d; 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内容。
答案 5 :(得分:4)
我用ModelViewSet和ModelSerializer解决了这个问题。希望这会有助于社区。 p>
我还希望在序列化程序本身而不是在视图中进行验证和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)
现在,对于API,这就是我想要的:
当我触发GET端点时,我希望每个上传文件的所有上述字段。
但是对于用户创建/上传文件,为什么她不得不担心传递所有这些字段。她可以上传文件然后,我想,序列化程序可以从上传的文件中获取其余字段。
<强> 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)
视图集供参考:
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
答案 6 :(得分:4)
根据我的经验,您不需要对文件字段做任何特别的事情,只需告诉它使用文件字段即可:
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)),
]
您就可以上传文件了:
curl -sS http://example.com/api/photos/ -F 'file=@/path/to/file'
为模型中的每个额外字段添加-F field=value
。并且不要忘记添加身份验证。
答案 7 :(得分:1)
一些解决方案已被弃用(request.data 应该用于 Django 3.0+)。其中一些不验证输入。另外,我会很欣赏带有 swagger 注释的解决方案。所以我推荐使用以下代码:
from drf_yasg.utils import swagger_auto_schema
from rest_framework import serializers
from rest_framework.parsers import MultiPartParser
from rest_framework.response import Response
from rest_framework.views import APIView
class FileUploadAPI(APIView):
parser_classes = (MultiPartParser, )
class InputSerializer(serializers.Serializer):
image = serializers.ImageField()
@swagger_auto_schema(
request_body=InputSerializer
)
def put(self, request):
input_serializer = self.InputSerializer(data=request.data)
input_serializer.is_valid(raise_exception=True)
# process file
file = input_serializer.validated_data['image']
return Response(status=204)
答案 8 :(得分:1)
models.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
serializers.py
from rest_framework import serializers
from .models import File
class FileSerializer(serializers.ModelSerializer):
class Meta:
model = File
fields = "__all__"
views.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)
urls.py
from apps.files import views as FileViews
urlpatterns = [
path('api/files', FileViews.FileUploadView.as_view()),
]
settings.py
# file uload parameters
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
将您的文件附加到api/files
字段form-data
上,向file
发送发布请求。该文件将被上传到/media
文件夹中,并添加ID和文件名的数据库记录。
答案 9 :(得分:1)
如果您正在使用ModelViewSet,那么实际上您已经做好了!它为您处理一切!您只需将字段放入ModelSerializer中,并在客户端中设置content-type=multipart/form-data;
。
但是您知道您不能以json格式发送文件。 (在客户端中将content-type设置为application / json时)。除非您使用Base64格式。
因此,您有两种选择:
ModelViewSet
和ModelSerializer
处理作业并使用content-type=multipart/form-data;
发送请求ModelSerializer
中的字段设置为Base64ImageField (or) Base64FileField
,并告诉客户端将文件编码为Base64
并设置content-type=application/json
答案 10 :(得分:0)
from rest_framework import status, generics
from rest_framework.response import Response
from rest_framework import serializers
import logging
logger = logging.getLogger(__name__)`enter code here`
class ImageUploadSerializer(serializers.Serializer):
file = serializers.FileField()
class UploadImages(generics.GenericAPIView):
serializer_class = ImageUploadSerializer
permission_classes = [IsAuthenticated, ]
def post(self, request):
try:
data = self.serializer_class(data=request.data)
if data.is_valid() is False:
return Response({'error': ERROR_MESSAGES.get('400')}, status=status.HTTP_400_BAD_REQUEST)
is_file_upload_success, file_item = save_aws_article_image(data.validated_data.get('file'),
request.user, upload_type)
if is_file_upload_success:
logger.info('{0} file uploaded {1}'.format(file_item['file_obj'].path, datetime.now()))
return Response({'path': file_item['file_obj'].path, 'id': file_item['file_obj'].uuid,
'name': file_item['file_obj'].name},
status=status.HTTP_201_CREATED)
except Exception as e:
logger.error(e, exc_info=True)
return Response({"error": e}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
答案 11 :(得分:0)
您可以通过生成解析器类来解析特定字段,然后直接将其直接馈入标准DRF序列化器中,来概括@Nithin的答案以直接与DRF的现有序列化器系统一起工作:
from django.http import QueryDict
import json
from rest_framework import parsers
def gen_MultipartJsonParser(json_fields):
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 = {}
# find the data field and parse it
qdict = QueryDict('', mutable=True)
for json_field in json_fields:
json_data = result.data.get(json_field, None)
if not json_data:
continue
data = json.loads(json_data)
if type(data) == list:
for d in data:
qdict.update({json_field: d})
else:
qdict.update({json_field: data})
return parsers.DataAndFiles(qdict, result.files)
return MultipartJsonParser
用法如下:
class MyFileViewSet(ModelViewSet):
parser_classes = [gen_MultipartJsonParser(['tags', 'permissions'])]
# ^^^^^^^^^^^^^^^^^^^
# Fields that need to be further JSON parsed
....
答案 12 :(得分:0)
<mat-calendar [selected]="selectedDate" (selectedChange)="onSelect($event)" [minDate]="leaveMinDate"> </mat-calendar>
答案 13 :(得分:0)
我想写一个我觉得更干净,更易于维护的选项。我们将使用defaultRouter为我们的视图集添加CRUD网址,并且还将添加一个固定的网址,以指定同一视图集内的上载器视图。
**** 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)
项目的主要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),
]
.-自述文件。
当我们将@action装饰器添加到类方法'uploader'时,魔术发生了。通过指定“ methods = ['put']”参数,我们仅允许PUT请求;非常适合文件上传。
我还添加了参数“ parser_classes”,以显示您可以选择将解析内容的解析器。我从rest_framework_csv包中添加了CSVParser,以演示如果需要此功能时我们如何仅接受某些类型的文件,在我的情况下,我仅接受“ Content-Type:text / csv”。 注意:如果要添加自定义解析器,则需要在ViewSet的parsers_classes中指定它们,因为在访问上载器方法解析器之前,请求会将允许的media_type与主(类)解析器进行比较。
现在,我们需要告诉Django如何使用此方法以及可以在我们的url中实现的位置。那就是我们添加固定网址的时候(简单目的)。该网址将带有“文件名”参数,该参数将在以后的方法中传递。我们需要传递此方法“ uploader”,并在列表中为PostsViewSet.as_view方法指定http协议('PUT')。
当我们进入以下网址
http://example.com/posts/uploader/
它将期望一个带有标头的PUT请求,该标头指定“ Content-Type”和Content-Disposition:附件; filename =“ something.csv”。
curl -v -u user:pass http://example.com/posts/uploader/ --upload-file ./something.csv --header "Content-type:text/csv"
答案 14 :(得分:0)
def post(self,request):
serializer = ProductSerializer(data=request.DATA, files=request.FILES)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
答案 15 :(得分:0)
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)
答案 16 :(得分:0)
在django-rest-framework中,请求数据由Parsers
解析
http://www.django-rest-framework.org/api-guide/parsers/
默认情况下,django-rest-framework需要解析器类JSONParser
。它会将数据解析为json。所以,文件不会用它解析。
如果我们想要将文件与其他数据一起解析,我们应该使用以下解析器类之一。
FormParser
MultiPartParser
FileUploadParser