Przesyłanie plików Django Rest Framework

100

Do przesłania pliku używam Django Rest Framework i AngularJs. Mój plik widoku wygląda następująco:

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)

Ponieważ ostatnia linia metody postu powinna zwrócić wszystkie dane, mam kilka pytań:

  • jak sprawdzić, czy coś jest w środku request.FILES?
  • jak serializować pole pliku?
  • jak używać parsera?
Pawan
źródło
14
TYLKO UWAGA DO MODÓW: Django znacznie się zaktualizowało od 2013 roku. Więc jeśli ktoś inny opublikuje teraz to samo pytanie. PROSZĘ nie strzelać do nich ^ _ ^.
Jessi
A co z Base64?
Hojat Modaresi

Odpowiedzi:

67

Użyj FileUploadParser , wszystko jest w żądaniu. Zamiast tego użyj metody put, przykład znajdziesz w dokumentacji :)

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)
zadowolona
źródło
12
@pleasedontbelong, dlaczego zamiast POST zastosowano metodę PUT?
Md. Tanvir Raihan
8
cześć @pleasedontbelong, jeśli tworzy nowy rekord, czy zamiast tego będzie to POST? i czy nadal będzie działać z FileUploadParser?
chrizonline
1
@pleasedontbelong RTan zadaje całkiem dobre pytanie. Czytanie RFC-2616 dostarcza subtelności, o której do tej pory nie byłem świadomy. „Podstawowa różnica między żądaniami POST i PUT znajduje odzwierciedlenie w odmiennym znaczeniu identyfikatora URI żądania. Identyfikator URI w żądaniu POST identyfikuje zasób, który będzie obsługiwał zamkniętą jednostkę. Ten zasób może być procesem akceptacji danych, bramą do innego protokołu lub osobnej jednostki, która akceptuje adnotacje. Natomiast identyfikator URI w żądaniu PUT identyfikuje podmiot dołączony do żądania ”
dudeman
3
Dlaczego FileUploadParser? „FileUploadParser jest przeznaczony do użytku z natywnymi klientami, którzy mogą przesyłać plik jako żądanie surowych danych. W przypadku przesyłania przez Internet lub klientów natywnych z obsługą przesyłania wieloczęściowego należy zamiast tego użyć parsera MultiPartParser”. Generalnie nie wydaje się to dobrą opcją. Co więcej, nie widzę, aby przesyłane pliki wymagały szczególnego traktowania .
x-yuri
3
Po drugie @ x-yuri, DRF skarży się, że nagłówek Content-Disposition jest pusty, gdy używam FileUploadParser. MultiPartParser jest znacznie prostszy, ponieważ zakłada po prostu, że nazwa pliku jest nazwą podaną w polach formularza.
David Zwart
74

Używam tego samego stosu i szukałem również przykładu przesyłania plików, ale mój przypadek jest prostszy, ponieważ używam ModelViewSet zamiast APIView. Okazało się, że kluczem był hak pre_save. Skończyło się na tym, że użyłem go razem z modułem przesyłania plików kątowych w następujący sposób:

# 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]
        });
    };
});
ybendana
źródło
11
pre_save jest przestarzałe w drf 3.x
Guy S
Z mojego doświadczenia wynika , że w przypadku pól plików nie jest wymagane żadne specjalne traktowanie .
x-yuri
Metody @ Guy-S, perform_create, perform_update, perform_destroy zastępują metody pre_save, post_save, pre_delete i post_delete w starym stylu, które nie są już dostępne: django-rest-framework.org/api-guide/generic-views / # metody
Rufat
38

Wreszcie mogę przesłać obraz za pomocą Django. Oto mój działający kod

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 żądanie przesłania

curl -X POST -S -H -u "admin:password" -F "[email protected];type=image/jpg" 127.0.0.1:8000/resourceurl/imageUpload
Vipul J.
źródło
14
dlaczego destination.close () znajduje się wewnątrz pętli for?
makerj
12
Wydaje się, że byłoby lepiej użyć with open('/Users/Username/' + up_file.name, 'wb+') as destination:i całkowicie usunąć zamknięcie
Chuck Wilbur
Jest prostszy w użyciu ModelViewSet. Poza tym najprawdopodobniej zaimplementowali to lepiej.
x-yuri
Polegałem na tej odpowiedzi przez cały dzień ... dopóki nie odkryłem, że jeśli chcesz przesłać wiele plików, nie FileUploadParserjest to potrzebne, ale MultiPartParser!
Olivier Pons
13

Po spędzeniu na tym 1 dnia doszedłem do wniosku, że ...

Dla kogoś, kto musi przesłać plik i wysłać dane, nie ma prostego sposobu, w jaki można go uruchomić. W specyfikacji API JSON występuje otwarty problem . Jedną z możliwości, które widziałem, jest użycie, multipart/relatedjak pokazano tutaj , ale myślę, że bardzo trudno jest go zaimplementować w drf.

W końcu zaimplementowałem wysłanie żądania jako formdata. Możesz wysłać każdy plik jako plik, a wszystkie inne dane jako tekst. Teraz, jeśli chodzi o wysyłanie danych jako tekst, masz dwie możliwości. przypadek 1) możesz wysłać każde dane jako parę klucz-wartość lub przypadek 2) możesz mieć pojedynczy klucz o nazwie data i wysłać cały plik json jako ciąg znaków w wartości.

Pierwsza metoda zadziałałaby po wyjęciu z pudełka, jeśli masz proste pola, ale będzie problemem, jeśli masz zagnieżdżone serializacje. Parser wieloczęściowy nie będzie w stanie przeanalizować zagnieżdżonych pól.

Poniżej przedstawiam implementację dla obu przypadków

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 -> nie są potrzebne żadne specjalne zmiany, nie pokazuję tutaj mojego serializatora jako zbyt długiego z powodu zapisywalnej implementacji pola ManyToMany.

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'

Teraz, jeśli postępujesz zgodnie z pierwszą metodą i wysyłasz tylko dane inne niż Json jako pary klucz-wartość, nie potrzebujesz niestandardowej klasy parsera. DRF'd MultipartParser wykona zadanie. Ale w drugim przypadku lub jeśli masz zagnieżdżone serializatory (jak pokazałem), będziesz potrzebować niestandardowego parsera, jak pokazano poniżej.

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)

Ten serializator zasadniczo przeanalizowałby zawartość json w wartościach.

Przykład żądania w post man dla obu przypadków: sprawa 1 przypadek 1,

Przypadek 2 przypadek2

Nithin
źródło
Wolałbym raczej uniknąć przypadku 2. Tworzenie jednego rekordu bazy danych na żądanie powinno być w porządku przez większość czasu.
x-yuri
bardzo pomocne, bardzo dziękuję. Ale nie rozumiem, dlaczego konwertujesz dane dyktowania na QueryDict w parserze? W moim przypadku w Django normalne dane słownikowe działają doskonale bez konwersji.
Metehan Gülaç
Wypróbowałem inny scenariusz, korzystając z odpowiedzi, o której wspomniałeś i działa pomyślnie. możesz spojrzeć na moją odpowiedź .
Metehan Gülaç
jeśli to działa, stackoverflow.com/questions/64547729/ ... powinno też działać., ale tak nie jest.
sadat
9

Z mojego doświadczenia wynika, że ​​nie musisz robić nic szczególnego z polami pliku, po prostu każ mu korzystać z pola pliku:

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)),
]

i jesteś gotowy do przesłania plików:

curl -sS http://example.com/api/photos/ -F 'file=@/path/to/file'

Dodaj -F field=valuedla każdego dodatkowego pola, które ma twój model. I nie zapomnij dodać uwierzytelniania.

x-yuri
źródło
7

Rozwiązałem ten problem za pomocą ModelViewSet i ModelSerializer. Mam nadzieję, że to pomoże społeczności.

Preferuję też walidację i logowanie do obiektu -> JSON (i odwrotnie) w samym serializatorze, a nie w widokach.

Zrozummy to na przykładzie.

Powiedzmy, chcę utworzyć FileUploader API. Gdzie będzie przechowywać pola takie jak id, ścieżka_pliku, nazwa_pliku, rozmiar, właściciel itp. W bazie danych. Zobacz przykładowy model poniżej:

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)

Teraz w przypadku interfejsów API tego chcę:

  1. OTRZYMAĆ:

Kiedy uruchamiam punkt końcowy GET, chcę mieć wszystkie powyższe pola dla każdego przesłanego pliku.

  1. POCZTA:

Ale aby użytkownik utworzył / przesłał plik, dlaczego musi się martwić o przejście wszystkich tych pól. Może po prostu przesłać plik, a następnie, jak przypuszczam, serializator może pobrać pozostałe pola z przesłanego PLIKU.

Searilizer: Pytanie: Stworzyłem poniższy serializator, aby służyć moim celom. Ale nie jestem pewien, czy to właściwy sposób na jego wdrożenie.

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)

Zestaw widoków w celach informacyjnych:

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
Jadav Bheda
źródło
Jaką logikę walidacji zawiera FileUploaderSerializer.validatemetoda?
x-yuri
6

Jeśli ktoś jest zainteresowany najłatwiejszym przykładem z ModelViewset dla Django Rest Framework.

Model jest,

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'

Serializator,

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

A widok jest,

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

Test w Postman,

wprowadź opis obrazu tutaj

sadat
źródło
I jak możemy wysłać żądanie przy użyciu ajax. Czym właściwie jest imageUrl?
Eduard Grigoryev
imageUrl jest plikiem w żądaniu.
sadat
1
    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)
sidhu Munagala
źródło
1
def post(self,request):
        serializer = ProductSerializer(data=request.DATA, files=request.FILES)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
Syed Faizan
źródło
1

Chciałbym napisać inną opcję, która moim zdaniem jest czystsza i łatwiejsza w utrzymaniu. Będziemy używać defaultRouter do dodawania adresów URL CRUD dla naszego zestawu widoków i dodamy jeszcze jeden stały adres URL określający widok przesyłającego w tym samym zestawie widoków.

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

Główny adres urls.py projektu

**** 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.

Magia dzieje się, gdy dodamy dekorator @action do naszej metody klasy „uploader”. Określając argument "methods = ['put']", zezwalamy tylko na żądania PUT; idealny do przesyłania plików.

Dodałem również argument "parser_classes", aby pokazać, że możesz wybrać parser, który przeanalizuje twoją zawartość. Dodałem CSVParser z pakietu rest_framework_csv, aby zademonstrować, jak możemy akceptować tylko określone typy plików, jeśli ta funkcjonalność jest wymagana, w moim przypadku akceptuję tylko "Content-Type: text / csv". Uwaga: Jeśli dodajesz niestandardowe parsery, musisz określić je w parsers_classes w zestawie widoku, ponieważ żądanie porówna dozwolony typ_media z parserami głównymi (klasowymi) przed uzyskaniem dostępu do parserów metody przesyłania.

Teraz musimy powiedzieć Django, jak przejść do tej metody i gdzie można ją zaimplementować w naszych adresach URL. Wtedy dodajemy stały adres URL (proste cele). Ten adres URL przyjmie argument „nazwa pliku”, który zostanie później przekazany w metodzie. Musimy przekazać tę metodę „uploader”, określając protokół http („PUT”) na liście do metody PostsViewSet.as_view.

Kiedy wylądujemy w następującym adresie URL

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

będzie oczekiwać żądania PUT z nagłówkami określającymi „Content-Type” i Content-Disposition: załącznik; filename = "coś.csv".

curl -v -u user:pass http://example.com/posts/uploader/ --upload-file ./something.csv --header "Content-type:text/csv"
Wolfgang Leon
źródło
Sugerujesz więc przesłanie pliku, a następnie dołączenie go do jakiegoś rekordu bazy danych. A co, jeśli z jakiegoś powodu dołączanie nigdy nie nastąpi? Dlaczego nie zrobić tego w jednej prośbie? parser_classesnie ma ograniczenia, które pliki można przesyłać. Pozwala Ci zdecydować, które formaty mogą być używane do składania żądań. Po namyśle, sposób w jaki obsługujesz przesyłanie ... wygląda na to, że umieszczasz dane z CSV w bazie danych. Nie to, o co pytał OP.
x-yuri
@ x-yuri mówiąc „plik CSV to plik”, a pytanie brzmi; Jak sprawdzić, czy żądanie zawiera dane? Korzystając z tej metody, znajdziesz dane w request.data. _data = request.data due PUT jest używany. Jak powiedziałeś, parser_classes są po to, aby zdecydować, które formaty mogą być użyte do żądania, a zatem przy użyciu dowolnego innego formatu, którego NIE chcesz, zostaną wówczas wykluczone, dodając dodatkową warstwę bezpieczeństwa. To, co zrobisz ze swoimi danymi, zależy od Ciebie. Używając opcji „Try Except”, możesz sprawdzić, czy „dołączanie nigdy się nie zdarza”, chociaż nie ma takiej potrzeby, to nie jest to, co robi kod. Te są wykonane w 1 żądaniu
Wolfgang Leon
1

Jeśli używasz ModelViewSet, właściwie to wszystko! Zajmuje się wszystkim za Ciebie! Wystarczy umieścić pole w swoim ModelSerializer i ustawić content-type=multipart/form-data;w swoim kliencie.

ALE jak wiesz, nie możesz wysyłać plików w formacie json. (gdy typ zawartości jest ustawiony na application / json w kliencie). Chyba że używasz formatu Base64.

Masz więc dwie możliwości:

  • pozwól ModelViewSeti ModelSerializerobsłuż zlecenie i wyślij zapytanie za pomocącontent-type=multipart/form-data;
  • ustaw pole w ModelSerializeras Base64ImageField (or) Base64FileFieldi powiedz klientowi, aby zakodował plik Base64i ustawił rozszerzeniecontent-type=application/json
Hojat Modaresi
źródło
1

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

Wyślij prośbę o wpis na adres api/filesz dołączonym plikiem do form-datapola file. Plik zostanie przesłany do /mediafolderu i dodany zostanie rekord bazy danych z identyfikatorem i nazwą pliku.

Achala Dissanayake
źródło
0

W django-rest-framework dane żądania są analizowane przez Parsers.
http://www.django-rest-framework.org/api-guide/parsers/

Domyślnie django-rest-framework przyjmuje klasę parsera JSONParser. Sparsuje dane do formatu json. więc pliki nie zostaną z nim przeanalizowane.
Jeśli chcemy, aby pliki były parsowane razem z innymi danymi, powinniśmy użyć jednej z poniższych klas parsera.

FormParser
MultiPartParser
FileUploadParser
anjaneyulubatta505
źródło
W obecnej wersji DRF 3.8.2, będzie analizować domyślnie application/json, application/x-www-form-urlencodedoraz multipart/form-data.
liquidki
0

To jedno z podejść, które mam nadzieję, że pomoże.

     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)
Harshit Trivedi
źródło
0

Możesz uogólnić odpowiedź @ Nithin, aby pracować bezpośrednio z istniejącym systemem serializatorów DRF, generując klasę parsera w celu przeanalizowania określonych pól, które są następnie przekazywane bezpośrednio do standardowych serializatorów 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

Jest to używane w następujący sposób:

class MyFileViewSet(ModelViewSet):
    parser_classes = [gen_MultipartJsonParser(['tags', 'permissions'])]
    #                                           ^^^^^^^^^^^^^^^^^^^
    #                              Fields that need to be further JSON parsed
    ....
Ross Rogers
źródło