Я использую 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)
источник
Я использую тот же стек и также искал пример загрузки файла, но мой случай проще, поскольку я использую 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] }); }; });
источник
Наконец-то я могу загрузить изображение с помощью 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() # 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 запрос на загрузку
curl -X POST -S -H -u "admin:password" -F "file=@img.jpg;type=image/jpg" 127.0.0.1:8000/resourceurl/imageUpload
источник
with open('/Users/Username/' + up_file.name, 'wb+') as destination:
и полностью удалить закрытиеModelViewSet
. Кроме того, они, скорее всего, реализовали это лучше.FileUploadParser
то, что нужно, ноMultiPartParser
!Потратив на это 1 день, я понял, что ...
Для тех, кому нужно загрузить файл и отправить некоторые данные, не существует прямого способа заставить его работать. Для этого есть открытая проблема в спецификациях 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 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 в значениях.
Пример запроса в почтовом ящике для обоих случаев: case 1 ,
Случай 2
источник
По моему опыту, вам не нужно делать ничего особенного с полями файла, вы просто указываете ему использовать поле файла:
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, мне нужны все указанные выше поля для каждого загруженного файла.
Но для того, чтобы пользователь мог создать / загрузить файл, почему он должен беспокоиться о передаче всех этих полей. Она может просто загрузить файл, а затем, я полагаю, сериализатор сможет получить остальные поля из загруженного ФАЙЛА.
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)
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
источник
FileUploaderSerializer.validate
содержит метод?Если кого-то интересует самый простой пример с ModelViewset для 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
Тест в почтальоне,
источник
def post(self,request): serializer = ProductSerializer(data=request.DATA, files=request.FILES) if serializer.is_valid(): serializer.save() return Response(serializer.data)
источник
В django-rest-framework данные запроса анализируются с помощью
Parsers
.http://www.django-rest-framework.org/api-guide/parsers/
По умолчанию django-rest-framework принимает класс парсера
JSONParser
. Он проанализирует данные в json. поэтому файлы не будут анализироваться с его помощью.Если мы хотим, чтобы файлы анализировались вместе с другими данными, мы должны использовать один из следующих классов парсеров.
источник
application/json
,application/x-www-form-urlencoded
иmultipart/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)
источник
Я хотел бы написать еще один вариант, который, как мне кажется, чище и проще в обслуживании. Мы будем использовать defaultRouter для добавления URL-адресов CRUD для нашего набора представлений, и мы добавим еще один фиксированный 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), ]
.- ПРОЧТИ МЕНЯ.
Волшебство происходит, когда мы добавляем декоратор @action к нашему методу класса 'uploader'. Указав аргумент "methods = ['put']", мы разрешаем только запросы PUT; идеально подходит для загрузки файлов.
Я также добавил аргумент "parser_classes", чтобы показать, что вы можете выбрать парсер, который будет анализировать ваш контент. Я добавил CSVParser из пакета rest_framework_csv, чтобы продемонстрировать, как мы можем принимать файлы только определенного типа, если эта функция требуется, в моем случае я принимаю только «Content-Type: text / csv». Примечание. Если вы добавляете собственные синтаксические анализаторы, вам необходимо указать их в parsers_classes в ViewSet, так как запрос будет сравнивать разрешенный media_type с основными (классовыми) синтаксическими анализаторами перед доступом к синтаксическим анализаторам методов загрузчика.
Теперь нам нужно указать Django, как перейти к этому методу и где его можно реализовать в наших URL-адресах. Вот когда мы добавляем фиксированный URL (Простые цели). Этот URL-адрес примет аргумент «имя файла», который будет передан в метод позже. Нам нужно передать этот метод «загрузчик», указав протокол http ('PUT') в списке методу PostsViewSet.as_view.
Когда мы попадаем на следующий URL
он будет ожидать запроса PUT с заголовками, указывающими «Content-Type» и Content-Disposition: attachment; filename = "something.csv".
curl -v -u user:pass http://example.com/posts/uploader/ --upload-file ./something.csv --header "Content-type:text/csv"
источник
parser_classes
нет ограничений на то, какие файлы могут быть загружены. Это позволяет вам решить, какие форматы можно использовать для отправки запросов. Если подумать, то, как вы обрабатываете загрузку ... кажется, что вы помещаете данные из CSV в базу данных. Не то, что спрашивал ОП.Это один из подходов, которые я применил, надеюсь, он поможет.
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)
источник
Вы можете обобщить ответ @Nithin для работы непосредственно с существующей системой сериализатора DRF, создав класс парсера для анализа определенных полей, которые затем подаются непосредственно в стандартные сериализаторы 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 ....
источник
Если вы используете ModelViewSet, на самом деле все готово! Он все сделает за вас! Вам просто нужно поместить поле в свой ModelSerializer и установить его
content-type=multipart/form-data;
в своем клиенте.НО, как вы знаете, вы не можете отправлять файлы в формате json. (когда в вашем клиенте для типа содержимого установлено значение application / json). Если вы не используете формат Base64.
Итак, у вас есть два варианта:
ModelViewSet
иModelSerializer
обработать задание и отправить запрос, используяcontent-type=multipart/form-data;
ModelSerializer
какBase64ImageField (or) Base64FileField
и сообщите своему клиенту, чтобы он закодировал файлBase64
и установилcontent-type=application/json
источник
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
папку, и будет добавлена запись базы данных с идентификатором и именем файла.источник