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
に何かがあるかどうかを確認する方法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)
私は同じスタックを使用しており、ファイルのアップロードの例も探していましたが、APIViewの代わりにModelViewSetを使用しているため、私のケースは簡単です。キーはpre_saveフックであることが判明しました。私はそれをangle-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]
});
};
});
最後に、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 -X POST -S -H -u "admin:password" -F "[email protected];type=image/jpg" 127.0.0.1:8000/resourceurl/imageUpload
これに1日費やした後、私は...
ファイルをアップロードしてデータを送信する必要がある人にとっては、それを機能させるための直接的な方法はありません。このためのJSON API仕様には 未解決の問題 があります。私が見た可能性の1つは、示されているようにmultipart/related
を使用することです ここ ですが、DRFで実装するのは非常に難しいと思います。
最後に、私が実装したのは、リクエストをformdata
として送信することでした。各ファイルをfileとして送信し、他のすべてのデータをテキストとして送信します。データをテキストとして送信するには、2つの選択肢があります。ケース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フィールド実装のため、ここではシリアライザーが長すぎると表示されません。
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されたMultipartParserが仕事をします。しかし、2番目の場合またはネストされたシリアライザー(iが示したように)がある場合、以下に示すカスタムパーサーが必要になります。
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コンテンツを解析します。
私の経験から、ファイルフィールドについて特別なことをする必要はなく、ファイルフィールドを利用するように指示するだけです。
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
を追加します。そして、認証を追加することを忘れないでください。
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)
さて、APIの場合、これは私が欲しいものです:
GETエンドポイントを起動すると、アップロードされたファイルごとに上記のすべてのフィールドが必要になります。
しかし、ユーザーがファイルを作成/アップロードするには、なぜこれらすべてのフィールドを渡すことを心配する必要があります。彼女はファイルをアップロードするだけで、その後、シリアライザーはアップロードされたFILEから残りのフィールドを取得できます。
Searilizer:Question:目的に合わせてシリアライザの下に作成しました。しかし、それを実装する正しい方法であるかどうかはわかりません。
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
def post(self,request):
serializer = ProductSerializer(data=request.DATA, files=request.FILES)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
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)
Django-rest-frameworkでは、リクエストデータはParsers
によって解析されます。
http://www.Django-rest-framework.org/api-guide/parsers/
デフォルトでは、Django-rest-frameworkはパーサークラスJSONParser
を取ります。データをJSONに解析します。そのため、ファイルは解析されません。
他のデータと一緒にファイルを解析したい場合は、以下のパーサークラスのいずれかを使用する必要があります。
FormParser
MultiPartParser
FileUploadParser
クリーンで保守しやすいと思う別のオプションを作成したいと思います。 defaultRouterを使用してビューセットのCRUD URLを追加し、同じビューセット内のアップローダービューを指定するもう1つの固定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)
プロジェクトのメイン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。
@actionデコレータをクラスメソッド「uploader」に追加すると、魔法が発生します。 「methods = ['put']」引数を指定することにより、PUTリクエストのみが許可されます。ファイルのアップロードに最適です。
また、コンテンツを解析するパーサーを選択できることを示すために、引数「parser_classes」を追加しました。この機能が必要な場合に特定のタイプのファイルのみを受け入れる方法を示すために、rest_framework_csvパッケージからCSVParserを追加しました。私の場合、「Content-Type:text/csv」のみを受け入れます。注:カスタムパーサーを追加する場合、要求はアップローダーメソッドパーサーにアクセスする前に許可されたmedia_typeをメイン(クラス)パーサーと比較するため、ViewSetのparsers_classesでそれらを指定する必要があります。
ここで、Djangoにこのメソッドに移動する方法と、URLで実装できる場所を指示する必要があります。そのとき、固定URLを追加します(単純な目的)。このUrlは、後にメソッドに渡される「ファイル名」引数を取ります。リスト内のhttpプロトコル(「PUT」)を指定してこのメソッド「uploader」をPostsViewSet.as_viewメソッドに渡す必要があります。
次のURLに着地したとき
http://example.com/posts/uploader/
「Content-Type」およびContent-Disposition:添付ファイルを指定するヘッダーを持つPUTリクエストが期待されます。 filename = "something.csv"。
curl -v -u user:pass http://example.com/posts/uploader/ --upload-file ./something.csv --header "Content-type:text/csv"