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)
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 훅으로 밝혀졌습니다. 나는 다음과 같이 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]
});
};
});
마지막으로 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 "file=@img.jpg;type=image/jpg" 127.0.0.1:8000/resourceurl/imageUpload
이것에 하루를 보낸 후, 나는 그것을 알아 냈습니다 ...
파일을 업로드하고 일부 데이터를 보내야하는 사람에게는 파일을 작동시킬 수있는 직접적인 방법이 없습니다. 이에 대한 json api 사양에 공개 된 문제 가 있습니다. 내가 본 한 가지 가능성은 여기 에 multipart/related
표시된 것처럼 사용 하는 것이지만 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 필드 구현으로 인해 여기에 너무 긴 것으로 표시되지 않습니다.
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가 작업을 수행합니다. 그러나 두 번째 경우 또는 중첩 된 직렬 변환기가있는 경우 (내가 보여준 것처럼) 아래와 같이 사용자 지정 파서가 필요합니다.
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)
이 serializer는 기본적으로 값의 모든 json 콘텐츠를 구문 분석합니다.
두 경우 모두에 대한 post man의 요청 예 : case 1 ,
제 경험상, 파일 필드에 대해 특별히 할 필요가 없습니다. 그냥 파일 필드를 사용하라고 말하면됩니다.
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로이 문제를 해결했습니다. 이것이 커뮤니티에 도움이되기를 바랍니다.
나는 또한 뷰가 아닌 serializer 자체에서 유효성 검사 및 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 : 질문 : 내 목적을 달성하기 위해 아래에 serializer를 만들었습니다. 그러나 그것을 구현하는 올바른 방법인지 확실하지 않습니다.
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
django-rest-framework에서 요청 데이터는 Parsers
.
http://www.django-rest-framework.org/api-guide/parsers/
기본적으로 django-rest-framework는 파서 클래스를 사용 JSONParser
합니다. 데이터를 json으로 구문 분석합니다. 따라서 파일이 파싱되지 않습니다.
파일을 다른 데이터와 함께 파싱하려면 아래 파서 클래스 중 하나를 사용해야합니다.
FormParser
MultiPartParser
FileUploadParser
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)
def post(self,request):
serializer = ProductSerializer(data=request.DATA, files=request.FILES)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
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)
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),
]
.- 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.
이제 Django에게이 메서드로 이동하는 방법과 URL에서 구현할 수있는 위치를 알려야합니다. 이때 고정 URL을 추가합니다 (단순한 목적). 이 URL은 나중에 메서드에서 전달 될 "파일 이름"인수를받습니다. 이 메서드 "uploader"를 전달하여 목록에 http 프로토콜 ( 'PUT')을 지정하여 PostsViewSet.as_view 메서드에 전달해야합니다.
다음 URL에 도착하면
http://example.com/posts/uploader/
"Content-Type"및 Content-Disposition : attachment를 지정하는 헤더가있는 PUT 요청을 예상합니다. filename = "something.csv"입니다.
curl -v -u user:pass http://example.com/posts/uploader/ --upload-file ./something.csv --header "Content-type:text/csv"
참고 URL : https://stackoverflow.com/questions/20473572/django-rest-framework-file-upload
'Nice programing' 카테고리의 다른 글
보기는 WebViewPage 또는 WebViewPage에서 파생되어야합니다. (0) | 2020.10.23 |
---|---|
일대일 관계를 언제 사용해야합니까? (0) | 2020.10.23 |
Thread가 추상 클래스가 아니고 start ()가 final이 아닌 이유는 무엇입니까? (0) | 2020.10.23 |
Linux에서 Java를 사용하여 Active Directory에 대해 인증 (0) | 2020.10.23 |
.keystore 파일을 분실 했습니까? (0) | 2020.10.23 |