Bir dosya yüklemek için Django Rest Framework ve AngularJs kullanıyorum. Görünüm dosyam şöyle görünüyor:
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 post yönteminin tüm verileri döndürmesi gerektiğinden, birkaç sorum var:
request.FILES
'da herhangi bir şey olup olmadığını kontrol etmeFileUploadParser komutunu kullanın, hepsi isteğe bağlıdır.
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)
Aynı yığını kullanıyorum ve ayrıca bir dosya yükleme örneği de arıyordum, ancak benim durumum APIView yerine ModelViewSet kullandığım için daha kolay. Anahtar pre_save kanca olduğu ortaya çıktı. Bunun gibi açısal dosya yükleme modülüyle birlikte kullanmaya başladım:
# 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]
});
};
});
Sonunda Django kullanarak resim yükleyebiliyorum. İşte çalışma kodum
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())
yüklemek için curl isteği
curl -X POST -S -H -u "admin:password" -F "[email protected];type=image/jpg" 127.0.0.1:8000/resourceurl/imageUpload
Bunun için 1 gün geçirdikten sonra, anladım ki ...
Dosya yüklemesi ve bazı verileri göndermesi gereken biri için, çalışmasını sağlamak için hiçbir fwd yol yoktur. Bunun için json api özelliklerinde bir açık sayı var. Gördüğüm bir olasılık, burada gösterildiği gibi multipart/related
kullanmaktır, ancak onu drf içinde uygulamak çok zor.
Sonunda benim uygulamaya koyduğum, isteği formdata
olarak göndermek oldu. Her bir dosyayı dosya olarak ve diğer tüm verileri metin olarak gönderirsiniz .. __ Şimdi veriyi metin olarak göndermek için iki seçeneğiniz vardır. durum 1) her veriyi anahtar değer çifti olarak gönderebilirsiniz veya durum 2) data adında tek bir anahtara sahip olabilir ve tüm json değerini değer olarak dize olarak gönderebilirsiniz.
İlk yöntem, basit alanlarınız varsa kutudan çıkar, ancak serileştirmeleriniz varsa bir sorun çıkarır. Çok parçalı ayrıştırıcı, iç içe geçmiş alanları ayrıştıramaz.
Aşağıda her iki durum için de uygulama sunuyorum
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 -> özel bir değişiklik gerekmez, seri hale getiricimi burada yazılabilir ManyToMany Field uygulamasından dolayı çok uzun göstermez.
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'
Şimdi, ilk yöntemi izliyorsanız ve yalnızca Json dışı verileri anahtar değer çiftleri olarak gönderiyorsanız, özel bir ayrıştırıcı sınıfına ihtiyacınız yoktur. DRF'd MultipartParser işi yapacak. Ancak ikinci vaka için veya iç içe geçmişleştiricilere sahipseniz (gösterdiğim gibi) aşağıda gösterildiği gibi özel ayrıştırıcıya ihtiyacınız olacaktır.
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)
Bu seri hale getirici temelde herhangi bir json içeriğini değerlerde ayrıştırır.
Bu sorunu ModelViewSet ve ModelSerializer ile çözdüm. Umarım bu topluluğa yardımcı olur.
Ayrıca, görünümler yerine serileştiricinin kendisinde doğrulama ve Object-> JSON (ve bunun tersi) oturum açmayı da tercih ederim.
Örnek olarak anlayalım.
Diyelim ki, FileUploader API oluşturmak istiyorum. ID, file_path, file_name, size, owner vb gibi alanları veritabanında depolayacağı yer. Aşağıdaki örnek modele bakınız:
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)
Şimdi, API'ler için istediğim şey bu:
GET bitiş noktasını ateşlediğimde, yüklenen her dosya için yukarıdaki alanların hepsini istiyorum.
Ancak, kullanıcının dosya oluşturması/yüklemesi için neden tüm bu alanları geçme konusunda endişelenmesi gerekiyor. Sadece dosyayı yükleyebilir ve daha sonra, sanırım, seri hale getirici yüklenen FILE alanlarını geri alabilir.
Searilizer: Soru: Amacımı yerine getirmek için seri oluşturucunun altında yarattım. Fakat bunu uygulamanın doğru yolu olup olmadığından emin değilim.
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)
Referans için Viewset:
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
Tecrübelerime göre, dosya alanları hakkında özel bir şey yapmanıza gerek yoktur, sadece dosya alanından yararlanmanızı söyleyin:
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)),
]
ve dosyaları yüklemeye hazırsınız:
curl -sS http://example.com/api/photos/ -F '[email protected]/path/to/file'
Modelinizde bulunan her ekstra alan için -F field=value
ekleyin. Ve kimlik doğrulama eklemeyi unutmayın.
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 istek verisinde Parsers
tarafından ayrıştırılır.
http://www.Django-rest-framework.org/api-guide/parsers/
Varsayılan olarak Django-rest-framework ayrıştırıcı sınıfı JSONParser
alır. Verileri json'a ayrıştırır. bu nedenle, dosyalar onunla ayrıştırılmaz.
Dosyaların diğer verilerle birlikte ayrıştırılmasını istiyorsak, aşağıdaki ayrıştırıcı sınıflardan birini kullanmalıyız.
FormParser
MultiPartParser
FileUploadParser
Daha temiz ve bakımı kolay olduğunu düşündüğüm bir seçenek daha yazmak istiyorum. Görüntü setimize CRUD URL'leri eklemek için defaultRouter'ı kullanacağız ve aynı görünüm içinde yükleyici görünümünü belirten bir tane daha sabit URL ekleyeceğiz.
**** 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)
Projenin ana url.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.
Büyü, sınıf metodumuza 'uploader' a @ action dekoratör eklediğimizde olur. "Method = ['put']" argümanını belirterek, yalnızca PUT isteklerine izin veriyoruz; dosya yükleme için mükemmel.
Ayrıca, içeriğinizi ayrıştırıcı ayrıştırıcıyı seçebileceğinizi göstermek için "parser_classes" bağımsız değişkenini de ekledim. CSVParser'ı rest_framework_csv paketinden ekledim, bu işlevsellik gerekirse yalnızca belirli türdeki dosyaları nasıl kabul edebileceğimizi göstermek için benim durumumda sadece "Content-Type: text/csv" kabul ediyorum. Not: Özel Parsers ekliyorsanız, onları yükleme yöntemi ayrıştırıcılarına erişmeden önce izin verilen media_type ana (sınıf) ayrıştırıcılarla karşılaştıracağından, bunları ViewSet'te parsers_class içinde belirtmeniz gerekir.
Şimdi Django'ya bu yönteme nasıl gideceğimizi ve URL'lerimizde nereye uygulanabileceğini söylemeliyiz. O zaman sabit URL'yi ekleriz (Basit amaçlar). Bu URL, daha sonra yöntemde aktarılacak olan bir "dosya adı" argümanını alır. Bir listedeki http protokolünü ('PUT') belirten, PostsViewSet.as_view yöntemine bu yöntemi "uploader" olarak geçmemiz gerekiyor.
Aşağıdaki URL’ye girdiğimizde
http://example.com/posts/uploader/
"Content-Type" ve Content-Disposition: ekini belirten başlıklarla bir PUT isteği bekleyecektir; filename = "something.csv".
curl -v -u user:pass http://example.com/posts/uploader/ --upload-file ./something.csv --header "Content-type:text/csv"