如何在Django REST框架中上传多个文件

z9smfwbn  于 2023-10-21  发布在  Go
关注(0)|答案(8)|浏览(161)

在django rest框架中,我可以使用danialfarid/ng-file-upload上传单个文件
views.py:

class PhotoViewSet(viewsets.ModelViewSet):
    serializer_class = PhotoSerializer
    parser_classes = (MultiPartParser, FormParser,)
    queryset=Photo.objects.all()

    def perform_create(self, serializer):
        serializer.save(blogs=Blogs.objects.latest('created_at'),
                   image=self.request.data.get('image'))

serializers.py:

class PhotoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Photo

models.py:

class Photo(models.Model):
    blogs = models.ForeignKey(Blogs, related_name='blogs_img')
    image = models.ImageField(upload_to=content_file_name)

当我尝试上传多个文件时。我进去
Chrome开发者工具:请求有效载荷

------WebKitFormBoundaryjOsYUxPLKB1N69Zn
Content-Disposition: form-data; name="image[0]"; filename="datacable.jpg"
Content-Type: image/jpeg

------WebKitFormBoundaryjOsYUxPLKB1N69Zn
Content-Disposition: form-data; name="image[1]"; filename="datacable2.jpg"
Content-Type: image/jpeg

回应:

{"image":["No file was submitted."]}

我不知道如何写序列化上传多个文件。

tnkciper

tnkciper1#

我设法解决这个问题,我希望它能帮助社区
serializers.py:

class FileListSerializer ( serializers.Serializer ) :
    image = serializers.ListField(
                       child=serializers.FileField( max_length=100000,
                                         allow_empty_file=False,
                                         use_url=False )
                                )
    def create(self, validated_data):
        blogs=Blogs.objects.latest('created_at')
        image=validated_data.pop('image')
        for img in image:
            photo=Photo.objects.create(image=img,blogs=blogs,**validated_data)
        return photo

class PhotoSerializer(serializers.ModelSerializer):

    class Meta:
        model = Photo
        read_only_fields = ("blogs",)

views.py:

class PhotoViewSet(viewsets.ModelViewSet):
    serializer_class = FileListSerializer
    parser_classes = (MultiPartParser, FormParser,)
    queryset=Photo.objects.all()
lf5gs5x2

lf5gs5x22#

我不太清楚,但这是工作...这是我的观点。

def perform_create(self, serializer):
    obj = serializer.save()
    for f in self.request.data.getlist('files'):
        mf = MyFile.objects.create(file=f)
        obj.files.add(mf)
ccrfmcuu

ccrfmcuu3#

以下是如何在博客API上上传多个文件:
models.py

class Blogs(models.Model):
    ...
 

class Photo(models.Model):
    blogs = models.ForeignKey(Blogs, related_name='blogs_img')
    image = models.ImageField(upload_to=content_file_name)

serializers.py

class PhotoSerializer(serializers.ModelSerializer):

    class Meta:
        model = Photo
        fields = ['blogs', 'image',]

class BlogsSerializer(serializers.ModelSerializer):
    photos = serializers.SerializerMethodField()

    def get_photos(self, obj):
        photos = Photo.objects.filter(blogs=obj)
        return PhotoSerializer(photos, many=True, read_only=False).data

    class Meta:
        model = Blogs
        fields = [
            ...
            'photos',
    ]

views.py

class BlogsViewSet(viewsets.ModelViewSet):
    serializer_class = BlogsSerializer
    queryset = Blogs.objects.all()

    def create(self, request, *args, **kwargs):
        instance_data = request.data
        data = {key: value for key, value in instance_data.items()}
        serializer = self.get_serializer(data=data)
        serializer.is_valid(raise_exception=True)
        instance = serializer.save()

        if request.FILES:
            photos = dict((request.FILES).lists()).get('photos', None)
            if photos:
                for photo in photos:
                    photo_data = {}
                    photo_data["blogs"] = instance.pk
                    photo_data["image"] = photo
                    photo_serializer = PhotoSerializer(data=photo_data)
                    photo_serializer.is_valid(raise_exception=True)
                    photo_serializer.save()

        return Response(serializer.data)
ruoxqz4g

ruoxqz4g4#

我已经用这个解决方案解决了这个问题
models.py:

class Product(models.Model):
   title = models.CharField(max_length=255)
   description = models.CharField(max_length=255)

class Images(models.Model):
   product = model.ForeignKey('Product', related_name="images", on_delete=models.CASCADE)
   image = models.ImageField(upload_to=upload_path)

serializers.py

class ImageSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Images
        fields = '__all__'

views.py

class ImagesViewSet(ModelViewSet):
    queryset = models.Images.objects.all()
    serializer_class = serializers.ImageSerializer
    
    # overwrite create method from the CreateModelMixin
    def create(self, request, *args, **kwargs):
        data = request.data
        images = data.getlist('image')
        
        # if no images call parent method it will return error
        if not images:
            return super().create(request, *args, **kwargs)

        # verify only without creating the images
        serializer_lst = []
        for image in images:
            data['image'] = image
            serializer = self.get_serializer(data=data)
            serializer.is_valid(raise_exception=True)
            serializer_lst.append(serializer)
        
        serializers_data = [] # this is to collect data for all created images and return as list in the response
        for serializer in serializer_lst:
            self.perform_create(serializer)
            serializers_data.append(serializer.data)
            headers = self.get_success_headers(serializer.data)
        
        return Response(serializers_data, status=status.HTTP_201_CREATED, headers=headers)
wdebmtf2

wdebmtf25#

我花了一段时间才找到一个有效的解决方案,我想与你分享什么最终为我工作。我使用reactjsDRF
以下是我的模型:

class MediaFile(models.Model):
    article = models.ForeignKey(Articles, on_delete=models.CASCADE, null=True)
    caption = models.CharField(max_length=500, null=True, blank=True)
    file = models.FileField('photo of article', upload_to=set_filename,
                            blank=True, null=True, default='')
    added = models.DateTimeField(auto_now_add=True)

视图为标准viewsets.ModelViewSet

class MediaFilesViewSet(viewsets.ModelViewSet):
    serializer_class = FileListSerializer
    parser_classes = (parsers.MultiPartParser, parsers.FormParser,)
    queryset=MediaFile.objects.all()

ArticleSerializer中,我添加了:

def create(self, validated_data):
    request = self.context.get('request')
    user = request.user
    instance = Articles.objects.create(**validated_data)
    instance.publisher = user
    instance.save()
    images = request.FILES
    if images:
        try:
            for f in images.getlist('mediafiles'):
                instance.mediafile_set.get_or_create(file=f, caption=f.name)
                instance.save()
        except Exception as e:
            print(e)
    return instance

前端结构:

postChangeImage = (event) =>{
    this.setState({
        is_images: true
    });
    let files = event.target.files;
    const files_array = Object.values(files);

    this.setState(
        {imagefile: files}
    );

    console.log('IMAGES ARRAY', files_array);
    files_array.map(value => {
        const urls = URL.createObjectURL(value);
        this.setState((prevState)=>(
            {postfolio:[urls, ...prevState.postfolio]}
            ));
    });

};

并发布:

for (let i=0; i< this.state.imagefile.length; i++) {
                    form_data.append(`mediafiles`, this.state.imagefile[i]);
                }
r7knjye2

r7knjye26#

这个问题的最佳答案对我不起作用,但查尔斯的建议非常有效。在我的例子中,我需要上传多个文件并将它们分配给特定的批处理。每个批次都分配给一个特定的用户。
下面是使用ReactJS来发出POST请求的更多上下文,沿着使用的序列化器和Postman窗口:
API.py**

from convert_files.models import File
from rest_framework import viewsets, permissions
from rest_framework.parsers import MultiPartParser, JSONParser
from .serializers import BatchSerializer

class BatchViewSet(viewsets.ModelViewSet):
    permission_classes = [
        permissions.IsAuthenticated
    ]

    def perform_create(self, serializer):
        obj = serializer.save(owner=self.request.user)
        for f in self.request.data.getlist('files'):
            mf = File.objects.create(office_file=f)
            obj.files.add(mf)

    parser_classes = (MultiPartParser, JSONParser, )

    serializer_class = BatchSerializer

    http_method_names = ['get','post','delete','put','patch', 'head']

    def get_queryset(self):
        return self.request.user.batches.all()

serializers.py

from rest_framework import serializers
from convert_files.models import File, Batch

class FileSerializer(serializers.ModelSerializer):
    class Meta:
        model = File
        fields = '__all__'

class BatchSerializer(serializers.ModelSerializer):
    files = FileSerializer(many=True, required = False)

    class Meta:
        model = Batch
        fields =  '__all__'

models.py

from django.db import models
from django.conf import settings
from django.contrib.auth.models import User

from .extra import ContentTypeRestrictedFileField

def make_upload_path(instance, filename):
    """Generates upload path for FileField"""
    return settings.OFFICE_OUTPUT_FILES_URL + "/%s" % (filename)

class Batch(models.Model):
    name = models.CharField(max_length=100, blank=True)
    description = models.TextField(blank=True)
    date_posted = models.DateTimeField(default=datetime.datetime.now)
    owner = models.ForeignKey(User, related_name="batches", 
                            on_delete=models.CASCADE, null=True)

class File(models.Model):
    name = models.CharField(max_length=100, blank=True)
    office_file = ContentTypeRestrictedFileField(
        upload_to           = make_upload_path,
        content_types       = ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
                            'application/vnd.ms-excel','application/msword',
                            'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
        max_upload_size     = 10485760,
    )

    files = models.ForeignKey(Batch, on_delete=models.CASCADE, null=True, 
                                    related_name='files', related_query_name='files')

FileUpload.js

import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { addBatch } from '../actions/batches';

function FileUpload() {

  const dispatch = useDispatch();
  let formData = new FormData()

  const onDrop = useCallback((acceptedFiles) => {
    for (var i = 0; i < acceptedFiles.length; i++) {
      formData.append("files", acceptedFiles[i], acceptedFiles[i].name)
    }
    dispatch(addBatch(formData));
  })

...

** Postman **

Image of POST request in Postman for Multiple File Upload to DRF

2ekbmq32

2ekbmq327#

使用“图像字典(数组)”
今天我尝试了Arindam的解决方案。它工作得很好,但是过了一段时间,我发现我的前端(端口3000)向自己发出了一个GET请求,寻找一个不在那里的图像,而不是在后端(端口8000)。(例如,GET http://localhost:3000/media/images/products/default.png-返回404:Not found).对我有效的是稍微修改一下代码,这是我的解决方案。
models.py

class Product(models.Model):
    title = models.CharField(max_length=255)
    description = models.CharField(max_length=255)
    price = models.FloatField()
    quantity = models.IntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)
    modified_at = models.DateTimeField(auto_now=True)
    active = models.BooleanField(default=False)
    slug = models.SlugField(max_length=255, unique=True)
    created_at = models.DateTimeField(auto_now_add=True)
    modified_at = models.DateTimeField(auto_now=True)
...

class ProductImage(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='images')
    image = models.ImageField("Image", upload_to=upload_to, default='products/default.png')

serializers.py

...
class ProductImageSerializer(serializers.ModelSerializer):
    class Meta:
        model = ProductImage
        fields = ['id', 'image', 'product']
        extra_kwargs = {
        'product': {'required': False},
        }

class ProductSerializer(serializers.ModelSerializer):
    images = ProductImageSerializer(many=True, required=False)

    class Meta:
        model = Product
        fields = ['id', 'title', 'description', 'images', 'price', 'quantity', 'active', 'slug', 'created_at', 'modified_at']
        read_only_fields = ['slug']
        #lookup_field = 'slug'

    def create(self, validated_data):        
        product = Product.objects.create(**validated_data)
        try:
            # try to get and save images (if any)
            images_data = dict((self.context['request'].FILES).lists()).get('images', None)
            for image in images_data:
                ProductImage.objects.create(product=product, image=image)
        except:
            # if no images are available - create using default image
            ProductImage.objects.create(product=product)
        return product

views.py

class ProductViewSet(viewsets.ModelViewSet):
    queryset = Product.objects.all()
    permission_classes = [permissions.AllowAny]
    serializer_class = ProductSerializer
    parser_classes = [MultiPartParser, FormParser]
    #lookup_field = 'slug'

编辑:在settings.py

import os
...
BASE_DIR = Path(__file__).resolve().parent.parent
...
MEDIA_ROOT = os.path.join(BASE_DIR, 'media').replace('\\', '/')
MEDIA_URL = '/media/'

我张贴在这里,以帮助有人(或我再次在未来)与多个文件上传或多个图像上传,因为我花了2天的时间在网上查找教程和答案,以帮助我解决这个问题。我可能并没有做得很完美,因为我最近才开始探索Django REST框架(和Python),但我希望它能有所帮助。

8gsdolmq

8gsdolmq8#

我想建议另一种基于list_serializer_class的方法。使用perform_create添加主键。也许有人会发现这个方法很有用。

models.py

class PrimaryModel(models.Model):
    title = models.CharField(
        max_length=100
        )

class File(models.Model):
    file = models.FileField(
        upload_to=directory_path,
        verbose_name='File'
        )
    primary_model = models.ForeignKey(
        PrimaryModel,
        on_delete=models.CASCADE,
        related_name="files",
        )

serializers.py

class FileListSerializer(serializers.ListSerializer):

    def create(self, validated_data):
        files = [File(**item) for item in validated_data]
        return File.objects.bulk_create(files)

class FileSerializer(serializers.ModelSerializer):

    class Meta:
        model = File
        exclude = ["primary_model"]
        list_serializer_class = FileListSerializer

views.py

from rest_framework import generics
from rest_framework.permissions import IsAuthenticated
from django.http import Http404
from django.db import transaction
from django.core.exceptions import ValidationError

...

class FileUpload(generics.CreateAPIView):
    permission_classes = [IsAuthenticated]
    serializer_class = FileSerializer
    queryset = File.objects.all()

    def get_object(self):
        try:
            id = self.kwargs.get('pk', None)
            obj = PrimaryModel.objects.get(id=id)
            return obj
        except PrimaryModel.DoesNotExist:
            raise Http404

    def perform_create(self, serializer):
        primary_model = self.get_object()
        return serializer.save(primary_model=primary_model)

    @transaction.atomic
    def create(self, request, *args, **kwargs):
        files = request.FILES.getlist("files")
        if len(files)==0:
            return Response(status=status.HTTP_400_BAD_REQUEST)
        data_list = [{"file": file} for file in files]
        serializer = self.get_serializer(data=data_list, many=True)
        serializer.is_valid(raise_exception=True)
        try:
            self.perform_create(serializer)
            return Response(
                serializer.data, status=status.HTTP_201_CREATED)
        except ValidationError as e:
            return Response(
                e, status=status.HTTP_400_BAD_REQUEST)

url.py

...

urlpatterns = [
    path(
        'primary_model/<int:pk>/upload_files/',
        views.FileUpload.as_view(),
        name='upload_files',
        ),
]

相关问题