อัปโหลดไฟล์ Django Rest Framework


100

ฉันใช้ 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)

เนื่องจากวิธีการโพสต์บรรทัดสุดท้ายควรส่งคืนข้อมูลทั้งหมดฉันมีคำถามหลายข้อ:

  • วิธีตรวจสอบว่ามีอะไรอยู่ในrequest.FILES?
  • วิธีการจัดลำดับฟิลด์ไฟล์?
  • ฉันจะใช้ parser ได้อย่างไร

13
เพียงแค่ทราบถึง MODS: Django ได้อัปเกรดอย่างมากตั้งแต่ปี 2013 ดังนั้นหากมีคนอื่นโพสต์คำถามเดียวกันในตอนนี้ โปรดอย่ายิงมันลง ^ _ ^
Jessi

แล้ว Base64 ล่ะ?
Hojat Modaresi

คำตอบ:


67

ใช้FileUploadParserทั้งหมดนี้อยู่ในคำขอ ใช้วิธีการใส่แทนคุณจะพบตัวอย่างในเอกสาร :)

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)

12
@pleasedontbelong ทำไมจึงใช้วิธี PUT ที่นี่แทน POST?
Md. Tanvir Raihan

8
สวัสดี @pleasedontbelong ถ้าจะสร้างสถิติใหม่จะเป็น POST แทนหรือไม่? และจะยังใช้งานได้กับ FileUploadParser หรือไม่
chrizonline

1
@pleasedontbelong RTan ถามคำถามที่ดีงาม การอ่าน RFC-2616 ให้ความละเอียดอ่อนที่ฉันไม่รู้มาจนถึงตอนนี้ "ความแตกต่างพื้นฐานระหว่างคำขอ POST และ PUT สะท้อนให้เห็นในความหมายที่แตกต่างกันของ Request-URI URI ในคำขอ POST ระบุทรัพยากรที่จะจัดการกับเอนทิตีที่ปิดล้อมทรัพยากรนั้นอาจเป็นกระบวนการรับข้อมูลเกตเวย์ ไปยังโปรโตคอลอื่น ๆ หรือเอนทิตีแยกต่างหากที่ยอมรับคำอธิบายประกอบในทางตรงกันข้าม URI ในคำขอ PUT จะระบุเอนทิตีที่มาพร้อมกับคำขอ "
dudeman

3
ทำไมต้อง FileUploadParser "FileUploadParser มีไว้สำหรับการใช้งานกับไคลเอนต์ดั้งเดิมที่สามารถอัปโหลดไฟล์เป็นคำขอข้อมูลดิบสำหรับการอัปโหลดทางเว็บหรือสำหรับไคลเอนต์ที่รองรับการอัปโหลดแบบหลายส่วนคุณควรใช้ตัวแยกวิเคราะห์ MultiPartParser แทน" ดูเหมือนจะไม่ใช่ตัวเลือกที่ดีโดยทั่วไป ยิ่งไปกว่านั้นฉันไม่เห็นการอัปโหลดไฟล์ที่ต้องการการดูแลเป็นพิเศษ
x-yuri

3
ถึง second @ x-yuri DRF บ่นเกี่ยวกับส่วนหัว Content-Disposition ว่างเปล่าเมื่อฉันใช้ FileUploadParser MultiPartParser นั้นง่ายกว่ามากเนื่องจากเพียงแค่ถือว่าชื่อไฟล์เป็นชื่อไฟล์ที่กำหนดในฟิลด์แบบฟอร์ม
David Zwart

74

ฉันใช้สแต็กเดียวกันและกำลังมองหาตัวอย่างการอัปโหลดไฟล์ แต่กรณีของฉันง่ายกว่าเนื่องจากฉันใช้ ModelViewSet แทน APIView กุญแจสำคัญกลายเป็นตะขอบันทึกล่วงหน้า ฉันลงเอยด้วยการใช้มันร่วมกับโมดูลอัปโหลดไฟล์เชิงมุมดังนี้:

# 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]
        });
    };
});

11
pre_save เลิกใช้แล้วใน drf 3.x
Guy S

จากประสบการณ์ของฉันไม่จำเป็นต้องมีการดูแลเป็นพิเศษสำหรับช่องไฟล์
x-yuri

@ Guy-S, perform_create, perform_update, เมธอด perform_destroy แทนที่เวอร์ชันแบบเก่า 2.x pre_save, post_save, pre_delete และ post_delete method ซึ่งไม่มีแล้ว: django-rest-framework.org/api-guide/generic-views / # วิธีการ
Rufat

38

ในที่สุดฉันก็สามารถอัพโหลดภาพโดยใช้ 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

14
ทำไม destination.close () ถูกวางไว้ที่ด้านในสำหรับลูป
ชง

12
ดูเหมือนว่ามันจะดีกว่าถ้าใช้with open('/Users/Username/' + up_file.name, 'wb+') as destination:และลบการปิดทั้งหมด
Chuck Wilbur

ModelViewSetมันเป็นเรื่องง่ายในการใช้ นอกจากนี้พวกเขามักจะนำไปใช้งานได้ดีกว่า
x-yuri

ฉันอาศัยคำตอบนี้มาทั้งวัน ... จนกระทั่งพบว่าเมื่อคุณต้องการอัปโหลดไฟล์หลาย ๆ ไฟล์มันไม่FileUploadParserจำเป็น แต่MultiPartParser!
Olivier Pons

13

หลังจากใช้เวลา 1 วันฉันก็พบว่า ...

สำหรับผู้ที่ต้องการอัปโหลดไฟล์และส่งข้อมูลบางอย่างไม่มีวิธี fwd โดยตรงที่คุณจะทำให้มันทำงานได้ มีปัญหาที่เปิดอยู่ในข้อกำหนด json api สำหรับสิ่งนี้ ความเป็นไปได้อย่างหนึ่งที่ฉันเห็นคือการใช้multipart/relatedดังที่แสดงไว้ที่นี่แต่ฉันคิดว่ามันยากมากที่จะนำไปใช้ใน drf

ในที่สุดสิ่งที่ฉันได้ดำเนินการคือส่งคำขอเป็นformdata. คุณจะส่งแต่ละไฟล์เป็นไฟล์และข้อมูลอื่น ๆ ทั้งหมดเป็นข้อความ ตอนนี้สำหรับการส่งข้อมูลเป็นข้อความคุณมีสองทางเลือก กรณีที่ 1) คุณสามารถส่งข้อมูลแต่ละรายการเป็นคู่ค่าคีย์หรือกรณี 2) คุณสามารถมีคีย์เดียวที่เรียกว่าข้อมูลและส่ง 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 -> ไม่จำเป็นต้องมีการเปลี่ยนแปลงพิเศษไม่แสดง serializer ของฉันที่นี่เนื่องจากมีความยาวเกินไปเนื่องจากการอนุมาน 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 เป็นคู่ค่าคีย์คุณไม่จำเป็นต้องมีคลาสตัวแยกวิเคราะห์ที่กำหนดเอง MultipartParser ของ DRF จะทำงาน แต่สำหรับกรณีที่สองหรือหากคุณมีซีเรียลไลเซอร์ซ้อนกัน (เช่นที่ฉันแสดง) คุณจะต้องมีตัวแยกวิเคราะห์ที่กำหนดเองดังที่แสดงด้านล่าง

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 ในค่า

ตัวอย่างคำขอใน post man สำหรับทั้งสองกรณี: กรณีที่ 1 กรณีที่ 1,

กรณีที่ 2 กรณี 2


ฉันอยากจะหลีกเลี่ยงกรณีที่ 2 การสร้างบันทึกฐานข้อมูลหนึ่งรายการต่อคำขอควรเป็นเรื่องปกติ
x-yuri

มีประโยชน์มากขอบคุณมาก แต่ฉันไม่เข้าใจทำไมคุณถึงแปลงข้อมูล dict เป็น QueryDict ใน parser ในกรณีของฉันใน Django ข้อมูลพจนานุกรมปกติจะทำงานได้อย่างสมบูรณ์โดยไม่ต้องแปลง
Metehan Gülaç

ฉันลองใช้สถานการณ์อื่นโดยใช้คำตอบที่คุณพูดถึงและทำงานได้สำเร็จ คุณสามารถดูที่ฉันคำตอบ
Metehan Gülaç

ถ้าสิ่งนี้ใช้งานได้stackoverflow.com/questions/64547729/…ก็ควรใช้งานได้เช่นกัน แต่มันไม่ได้
sadat

9

จากประสบการณ์ของฉันคุณไม่จำเป็นต้องทำอะไรเป็นพิเศษเกี่ยวกับฟิลด์ไฟล์คุณเพียงแค่บอกให้ใช้ประโยชน์จากฟิลด์ไฟล์:

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สำหรับแต่ละฟิลด์พิเศษที่โมเดลของคุณมี และอย่าลืมเพิ่มการตรวจสอบสิทธิ์


7

ฉันแก้ปัญหานี้ด้วย ModelViewSet และ ModelSerializer หวังว่านี่จะช่วยชุมชนได้

ฉันยังคาดหวังที่จะมีการตรวจสอบความถูกต้องและการเข้าสู่ระบบ Object-> JSON (และในทางกลับกัน) ใน serializer มากกว่าในมุมมอง

มาทำความเข้าใจกับตัวอย่าง

พูดว่าฉันต้องการสร้าง 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 นี่คือสิ่งที่ฉันต้องการ:

  1. รับ:

เมื่อฉันเริ่มต้น GET endpoint ฉันต้องการช่องด้านบนสำหรับทุกไฟล์ที่อัปโหลด

  1. โพสต์:

แต่สำหรับผู้ใช้ในการสร้าง / อัปโหลดไฟล์ทำไมเธอต้องกังวลเกี่ยวกับการส่งผ่านฟิลด์เหล่านี้ทั้งหมด เธอสามารถอัปโหลดไฟล์จากนั้นฉันคิดว่า serializer สามารถรับฟิลด์ที่เหลือจาก 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)

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ประกอบด้วยอะไรบ้าง?
x-yuri

6

หากใครสนใจตัวอย่างที่ง่ายที่สุดด้วย 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'

Serializer,

class MyModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = MyModel
        fields = "__all__"

และมุมมองคือ

class MyModelView(viewsets.ModelViewSet):
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer

ทดสอบในบุรุษไปรษณีย์

ป้อนคำอธิบายภาพที่นี่


และเราจะส่งคำขอโดยใช้ ajax ได้อย่างไร จริงๆแล้ว imageUrl คืออะไร?
Eduard Grigoryev

imageUrl คือไฟล์ในการร้องขอ
sadat


0

ใน django-rest-framework ขอข้อมูลจะถูกแยกวิเคราะห์โดยParsers.
http://www.django-rest-framework.org/api-guide/parsers/

โดยส่วนที่เหลือ Django JSONParserกรอบเริ่มต้นใช้เวลาระดับ มันจะแยกวิเคราะห์ข้อมูลเป็น json ดังนั้นไฟล์จะไม่ถูกแยกวิเคราะห์ด้วย
หากเราต้องการแยกวิเคราะห์ไฟล์พร้อมกับข้อมูลอื่น ๆ เราควรใช้หนึ่งในคลาส parser ด้านล่างนี้

FormParser
MultiPartParser
FileUploadParser

ในรุ่นปัจจุบันของ DRF 3.8.2 ก็จะแยกเป็นค่าเริ่มต้นapplication/json, และapplication/x-www-form-urlencoded multipart/form-data
liquidki

0
    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)

0

ฉันต้องการเขียนตัวเลือกอื่นที่ฉันรู้สึกว่าสะอาดกว่าและดูแลรักษาง่ายกว่า เราจะใช้ defaultRouter เพื่อเพิ่ม URL CRUD สำหรับ viewset ของเราและเราจะเพิ่ม 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),
]

.- README.

ความมหัศจรรย์เกิดขึ้นเมื่อเราเพิ่มผู้ตกแต่ง @action ลงใน 'ตัวอัปโหลด' เมธอดคลาสของเรา การระบุอาร์กิวเมนต์ "method = ['put']" แสดงว่าเราอนุญาตเฉพาะคำขอ PUT เท่านั้น เหมาะสำหรับการอัพโหลดไฟล์

ฉันยังเพิ่มอาร์กิวเมนต์ "parser_classes" เพื่อแสดงว่าคุณสามารถเลือกตัวแยกวิเคราะห์ที่จะแยกวิเคราะห์เนื้อหาของคุณได้ ฉันเพิ่ม CSVParser จากแพ็กเกจ rest_framework_csv เพื่อแสดงให้เห็นว่าเรายอมรับเฉพาะไฟล์บางประเภทได้อย่างไรหากจำเป็นต้องใช้ฟังก์ชันนี้ในกรณีของฉันฉันยอมรับเฉพาะ "Content-Type: text / csv" เท่านั้น หมายเหตุ: หากคุณกำลังเพิ่ม Parsers แบบกำหนดเองคุณจะต้องระบุใน parsers_classes ใน ViewSet เนื่องจากคำขอจะเปรียบเทียบ media_type ที่อนุญาตกับตัวแยกวิเคราะห์หลัก (class) ก่อนที่จะเข้าถึงตัวแยกวิเคราะห์วิธีการอัปโหลด

ตอนนี้เราต้องบอก Django ว่าจะไปที่วิธีนี้และสามารถนำไปใช้งานได้ที่ไหนใน url ของเรา นั่นคือเมื่อเราเพิ่ม URL คงที่ (วัตถุประสงค์ง่าย ๆ ) URL นี้จะใช้อาร์กิวเมนต์ "filename" ซึ่งจะถูกส่งต่อในวิธีการในภายหลัง เราจำเป็นต้องส่งวิธีนี้ "ตัวอัปโหลด" โดยระบุโปรโตคอล http ('PUT') ในรายการไปยังเมธอด PostsViewSet.as_view

เมื่อเราเข้าสู่ url ต่อไปนี้

 http://example.com/posts/uploader/ 

คาดว่าจะมีคำขอ 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 ลงในฐานข้อมูล ไม่ใช่สิ่งที่ OP ถาม
x-yuri

@ x-yuri โดยพูดว่า "ไฟล์ CSV คือไฟล์" และคำถามคือ; จะตรวจสอบได้อย่างไรว่ามีข้อมูลในคำขอหรือไม่? เมื่อใช้วิธีนี้คุณจะพบข้อมูลใน request.data _data = request.data เนื่องจากกำลังใช้ PUT เช่นเดียวกับที่คุณพูด parser_classes มีไว้เพื่อตัดสินใจว่ารูปแบบใดที่สามารถใช้ในการร้องขอได้ด้วยเหตุนี้โดยใช้รูปแบบอื่นใดที่คุณไม่ต้องการจากนั้นจะไม่รวมการเพิ่มชั้นความปลอดภัยเพิ่มเติม คุณจะทำอย่างไรกับข้อมูลของคุณขึ้นอยู่กับคุณ การใช้ "ลองยกเว้น" คุณสามารถตรวจสอบได้ว่า "การแนบไม่เคยเกิดขึ้น" ซึ่งไม่จำเป็นต้องใช้รหัสนั้นไม่ใช่ สิ่งเหล่านี้ทำใน 1 คำขอ
Wolfgang Leon

0

นี่เป็นแนวทางหนึ่งที่ฉันใช้หวังว่าจะช่วยได้

     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)

0

คุณสามารถสรุปคำตอบของ @ Nithin เพื่อทำงานโดยตรงกับระบบ serializer ที่มีอยู่ของ DRF โดยการสร้างคลาส parser เพื่อแยกวิเคราะห์ฟิลด์เฉพาะที่ป้อนเข้าในซีเรียลไลเซอร์ 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
    ....

0

หากคุณกำลังใช้ 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

0

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โฟลเดอร์และบันทึก db จะถูกเพิ่มด้วย id และชื่อไฟล์

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.