Saya menggunakan Django Rest Framework dan AngularJs untuk mengunggah file. File tampilan saya terlihat seperti ini:
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)
Karena baris terakhir metode posting harus mengembalikan semua data, saya punya beberapa pertanyaan:
- bagaimana cara memeriksa apakah ada sesuatu
request.FILES
? - bagaimana cara membuat serial file field?
- bagaimana cara menggunakan parser?
Jawaban:
Gunakan FileUploadParser , semuanya ada dalam permintaan. Gunakan metode put sebagai gantinya, Anda akan menemukan contoh di dokumen :)
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)
sumber
Saya menggunakan tumpukan yang sama dan juga mencari contoh unggahan file, tetapi kasus saya lebih sederhana karena saya menggunakan ModelViewSet, bukan APIView. Kuncinya ternyata adalah hook pre_save. Saya akhirnya menggunakannya bersama dengan modul angular-file-upload seperti:
# 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] }); }; });
sumber
Akhirnya saya bisa mengunggah gambar menggunakan Django. Ini kode kerja saya
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() # 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
urlpatterns = patterns('', url(r'^imageUpload', views.FileUploadView.as_view())
curl permintaan untuk mengunggah
curl -X POST -S -H -u "admin:password" -F "[email protected];type=image/jpg" 127.0.0.1:8000/resourceurl/imageUpload
sumber
with open('/Users/Username/' + up_file.name, 'wb+') as destination:
dan melepas tutup seluruhnyaModelViewSet
. Juga, mereka kemungkinan besar menerapkannya dengan lebih baik.FileUploadParser
diperlukan, tetapiMultiPartParser
!Setelah menghabiskan 1 hari untuk ini, saya menemukan bahwa ...
Untuk seseorang yang perlu mengupload file dan mengirim beberapa data, tidak ada cara langsung untuk membuatnya bekerja. Ada masalah terbuka dalam spesifikasi json api untuk ini. Salah satu kemungkinan yang saya lihat adalah menggunakan
multipart/related
seperti yang ditunjukkan di sini , tetapi saya rasa sangat sulit untuk menerapkannya di drf.Akhirnya apa yang telah saya terapkan adalah mengirim permintaan sebagai
formdata
. Anda akan mengirim setiap file sebagai file dan semua data lainnya sebagai teks. Sekarang untuk mengirim data sebagai teks Anda memiliki dua pilihan. case 1) Anda dapat mengirim setiap data sebagai key value pair atau case 2) Anda dapat memiliki satu kunci yang disebut data dan mengirim seluruh json sebagai nilai string.Metode pertama akan bekerja di luar kotak jika Anda memiliki bidang sederhana, tetapi akan menjadi masalah jika Anda memiliki serialisasi bersarang. Pengurai multibagian tidak dapat mengurai bidang bertingkat.
Di bawah ini saya memberikan implementasi untuk kedua kasus tersebut
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 -> tidak diperlukan perubahan khusus, tidak menampilkan serializer saya di sini karena terlalu panjang karena implimentasi ManyToMany Field yang dapat ditulis.
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'
Sekarang, jika Anda mengikuti metode pertama dan hanya mengirim data non-Json sebagai key value pair, Anda tidak memerlukan class parser kustom. MultipartParser DRF akan melakukan pekerjaan itu. Tetapi untuk kasus kedua atau jika Anda memiliki serializers bersarang (seperti yang telah saya tunjukkan) Anda akan memerlukan parser khusus seperti yang ditunjukkan di bawah ini.
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 ini pada dasarnya akan mengurai konten json apa pun dalam nilai.
Contoh request di post man untuk kedua kasus: case 1 ,
Kasus 2
sumber
Dari pengalaman saya, Anda tidak perlu melakukan sesuatu yang khusus tentang bidang file, Anda cukup memberi tahu untuk menggunakan bidang file:
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)), ]
dan Anda siap mengunggah file:
curl -sS http://example.com/api/photos/ -F 'file=@/path/to/file'
Tambahkan
-F field=value
untuk setiap bidang ekstra yang dimiliki model Anda. Dan jangan lupa untuk menambahkan otentikasi.sumber
Saya memecahkan masalah ini dengan ModelViewSet dan ModelSerializer. Semoga ini bisa membantu masyarakat.
Saya juga lebih suka memiliki validasi dan Object-> JSON (dan sebaliknya) login di serializer itu sendiri daripada di views.
Mari kita pahami dengan contoh.
Katakanlah, saya ingin membuat API FileUploader. Dimana akan menyimpan field seperti id, file_path, file_name, size, owner dll dalam database. Lihat contoh model di bawah ini:
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)
Sekarang, Untuk API inilah yang saya inginkan:
Ketika saya mengaktifkan titik akhir GET, saya ingin semua bidang di atas untuk setiap file yang diunggah.
Tetapi bagi pengguna untuk membuat / mengunggah file, mengapa dia harus khawatir tentang melewati semua bidang ini. Dia bisa mengunggah file dan kemudian, saya kira, serializer bisa mendapatkan sisa bidang dari FILE yang diunggah.
Searilizer: Pertanyaan: Saya membuat serializer di bawah ini untuk memenuhi tujuan saya. Tetapi tidak yakin apakah itu cara yang tepat untuk menerapkannya.
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 untuk referensi:
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
sumber
FileUploaderSerializer.validate
yang dikandung metode?Jika ada yang tertarik dengan contoh termudah dengan ModelViewset for Django Rest Framework.
Modelnya adalah,
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'
The Serializer,
class MyModelSerializer(serializers.ModelSerializer): class Meta: model = MyModel fields = "__all__"
Dan Pandangannya adalah,
class MyModelView(viewsets.ModelViewSet): queryset = MyModel.objects.all() serializer_class = MyModelSerializer
Uji di Postman,
sumber
def post(self,request): serializer = ProductSerializer(data=request.DATA, files=request.FILES) if serializer.is_valid(): serializer.save() return Response(serializer.data)
sumber
Dalam django-rest-framework, data permintaan diurai oleh
Parsers
.http://www.django-rest-framework.org/api-guide/parsers/
Secara default django-rest-framework menggunakan kelas parser
JSONParser
. Ini akan mengurai data menjadi json. jadi, file tidak akan diurai dengannya.Jika kita ingin file diurai bersama dengan data lain kita harus menggunakan salah satu kelas parser di bawah ini.
sumber
application/json
,application/x-www-form-urlencoded
danmultipart/form-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)
sumber
Saya ingin menulis opsi lain yang menurut saya lebih bersih dan lebih mudah dirawat. Kami akan menggunakan defaultRouter untuk menambahkan URL CRUD untuk kumpulan tampilan kami dan kami akan menambahkan satu lagi url tetap yang menentukan tampilan pengunggah dalam kumpulan tampilan yang sama.
**** 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 utama proyek
**** 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), ]
.- BACA AKU.
Keajaiban terjadi ketika kita menambahkan @action decorator ke metode kelas 'uploader' kita. Dengan menetapkan argumen "method = ['put']", kita hanya mengizinkan permintaan PUT; sempurna untuk mengunggah file.
Saya juga menambahkan argumen "parser_classes" untuk menunjukkan bahwa Anda dapat memilih parser yang akan mengurai konten Anda. Saya menambahkan CSVParser dari paket rest_framework_csv, untuk mendemonstrasikan bagaimana kami hanya dapat menerima jenis file tertentu jika fungsi ini diperlukan, dalam kasus saya, saya hanya menerima "Jenis Konten: teks / csv". Catatan: Jika Anda menambahkan Parser kustom, Anda harus menetapkannya di parsers_classes di ViewSet karena permintaan akan membandingkan media_type yang diizinkan dengan parser utama (class) sebelum mengakses parser metode uploader.
Sekarang kita perlu memberitahu Django bagaimana pergi ke metode ini dan dimana bisa diimplementasikan di url kita. Saat itulah kami menambahkan url tetap (Tujuan sederhana). Url ini akan mengambil argumen "nama file" yang akan diteruskan dalam metode nanti. Kita perlu meneruskan metode ini "uploader", menentukan protokol http ('PUT') dalam daftar ke metode PostsViewSet.as_view.
Saat kita mendarat di url berikut
itu akan mengharapkan permintaan PUT dengan tajuk yang menentukan "Jenis-Konten" dan Isi-Disposisi: lampiran; filename = "sesuatu.csv".
curl -v -u user:pass http://example.com/posts/uploader/ --upload-file ./something.csv --header "Content-type:text/csv"
sumber
parser_classes
tidak ada batasan file mana yang dapat diunggah. Ini memungkinkan Anda memutuskan format mana yang dapat digunakan untuk membuat permintaan. Setelah dipikir-pikir, cara Anda menangani unggahan ... sepertinya Anda memasukkan data dari CSV ke dalam database. Bukan apa yang diminta OP.Ini adalah salah satu pendekatan yang saya terapkan semoga akan membantu.
class Model_File_update(APIView): parser_classes = (MultiPartParser, FormParser) permission_classes = [IsAuthenticated] # it will check if the user is authenticated or not authentication_classes = [JSONWebTokenAuthentication] # it will authenticate the person by JSON web token def put(self, request): id = request.GET.get('id') obj = Model.objects.get(id=id) serializer = Model_Upload_Serializer(obj, data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=200) else: return Response(serializer.errors, status=400)
sumber
Anda dapat menggeneralisasi jawaban @ Nithin untuk bekerja secara langsung dengan sistem serializer DRF yang ada dengan membuat kelas parser untuk mengurai bidang tertentu yang kemudian dimasukkan langsung ke serializers DRF standar:
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
Ini digunakan seperti:
class MyFileViewSet(ModelViewSet): parser_classes = [gen_MultipartJsonParser(['tags', 'permissions'])] # ^^^^^^^^^^^^^^^^^^^ # Fields that need to be further JSON parsed ....
sumber
Jika Anda menggunakan ModelViewSet, sebenarnya Anda sudah selesai! Ia menangani setiap hal untuk Anda! Anda hanya perlu meletakkan field di ModelSerializer Anda dan mengaturnya
content-type=multipart/form-data;
di klien Anda.TAPI seperti yang Anda ketahui, Anda tidak dapat mengirim file dalam format json. (ketika tipe konten disetel ke application / json di klien Anda). Kecuali Anda menggunakan format Base64.
Jadi, Anda punya dua pilihan:
ModelViewSet
danModelSerializer
tangani pekerjaan itu dan kirim permintaan menggunakancontent-type=multipart/form-data;
ModelSerializer
sebagaiBase64ImageField (or) Base64FileField
dan beri tahu klien Anda untuk menyandikan file keBase64
dan menyetelcontent-type=application/json
sumber
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')
Kirim permintaan posting ke
api/files
dengan file Anda dilampirkan keform-data
bidangfile
. File akan diupload ke/media
folder dan record db akan ditambahkan dengan id dan nama file.sumber