web-dev-qa-db-de.com

Django Rest Framework Datei-Upload

Ich benutze Django Rest Framework und AngularJs, um eine Datei hochzuladen. Meine Ansichtsdatei sieht folgendermaßen aus:

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)

Da die letzte Zeile der Post-Methode alle Daten zurückgeben soll, habe ich mehrere Fragen:

  • wie überprüfe ich, ob etwas in request.FILES ist?
  • wie kann man das Dateifeld serialisieren?
  • wie soll ich Parser verwenden?
60
Pawan

Verwenden Sie den FileUploadParser , es ist alles in der Anfrage . Verwenden Sie stattdessen eine put-Methode.

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

Ich verwende denselben Stack und suchte auch nach einem Beispiel für das Hochladen von Dateien, aber mein Fall ist einfacher, da ich ModelViewSet anstelle von APIView verwende. Der Schlüssel erwies sich als pre_save-Hook. Am Ende habe ich es zusammen mit dem Modul zum Hochladen von Dateien verwendet:

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

Endlich kann ich Bilder mit Django hochladen. Hier ist mein Arbeitscode

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

curl-Anfrage zum Hochladen

curl -X POST -S -H -u "admin:password" -F "[email protected];type=image/jpg" 127.0.0.1:8000/resourceurl/imageUpload
23
Vipul J

Nachdem ich einen Tag damit verbracht hatte, habe ich herausgefunden, dass ...

Für jemanden, der eine Datei hochladen und Daten senden muss, gibt es keine direkte Möglichkeit, wie Sie sie zum Laufen bringen können. Dafür gibt es eine offene Ausgabe in Json-API-Spezifikationen. Eine Möglichkeit, die ich gesehen habe, ist, multipart/related wie gezeigt hier zu verwenden, aber ich denke, es ist sehr schwer, es in drf zu implementieren.

Schließlich habe ich die Anfrage als formdata gesendet. Sie würden jede Datei als file und alle anderen Daten als Text senden. __ Wenn Sie die Daten als Text senden möchten, haben Sie zwei Möglichkeiten. Fall 1) Sie können alle Daten als Schlüsselwertpaar senden oder Fall 2) Sie können einen einzelnen Schlüssel mit dem Namen data haben und den gesamten Json als String in value senden. 

Die erste Methode würde sofort funktionieren, wenn Sie über einfache Felder verfügen, aber ein Problem, wenn Sie Serialisierungen verschachtelt haben. Der Multipart-Parser kann die verschachtelten Felder nicht analysieren.

Im Folgenden stelle ich die Implementierung für beide Fälle bereit

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 -> Es sind keine besonderen Änderungen erforderlich. Mein Serializer wird hier nicht angezeigt, da er aufgrund der beschreibbaren ManyToMany Field-Implementierung zu lang ist.

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'

Wenn Sie nun die erste Methode befolgen und nur Nicht-Json-Daten als Schlüsselwertpaare senden, benötigen Sie keine benutzerdefinierte Parser-Klasse. Der DRF-MultipartParser erledigt die Arbeit. Für den zweiten Fall oder wenn Sie geschachtelte Serialisierer haben (wie ich es gezeigt habe), benötigen Sie einen benutzerdefinierten Parser (siehe unten).

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)

Dieser Serialisierer würde grundsätzlich jeden Json-Inhalt in den Werten parsen.

Das Anforderungsbeispiel in Postman für beide Fälle: Fall 1  case 1

Fall 2  case2

4
nithin

Ich habe dieses Problem mit ModelViewSet und ModelSerializer gelöst. Hoffe, das wird der Gemeinschaft helfen.

Ich bevorzuge es auch, Validierung und Object-> JSON (und umgekehrt) im Serializer selbst und nicht in Ansichten einzuloggen.

Lasst es uns am Beispiel verstehen.

Sagen wir, ich möchte FileUploader API erstellen. Wo werden Felder wie id, file_path, file_name, size, owner usw. in der Datenbank gespeichert. Siehe Beispielmodell unten:

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)

Nun, für APIs ist das was ich will:

  1. ERHALTEN:

Wenn ich den GET-Endpunkt abfeuere, möchte ich für jede hochgeladene Datei alle oben genannten Felder.

  1. POST:

Aber für Benutzer zum Erstellen/Hochladen von Dateien, warum muss sie sich sorgen, all diese Felder zu übergeben. Sie kann die Datei einfach hochladen und dann, glaube ich, der Serializer die restlichen Felder aus der hochgeladenen Datei holen können.

Searilizer: Frage: Ich habe unterhalb des Serializers erstellt, um meinen Zweck zu erfüllen. Aber nicht sicher, ob es der richtige Weg ist, um es umzusetzen.

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 als Referenz:

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
3
Jadav Bheda

Meiner Erfahrung nach brauchen Sie nichts Besonderes an Dateifeldern zu tun, sondern sagen es nur, um das Dateifeld zu verwenden:

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

und Sie können Dateien hochladen:

curl -sS http://example.com/api/photos/ -F '[email protected]/path/to/file'

Fügen Sie -F field=value für jedes zusätzliche Feld hinzu, das Ihr Modell enthält. Und vergessen Sie nicht, die Authentifizierung hinzuzufügen.

1
x-yuri
def post(self,request):
        serializer = ProductSerializer(data=request.DATA, files=request.FILES)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
0
Syed Faizan
    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
sidhu Munagala

Im Django-Rest-Framework werden Anforderungsdaten von Parsers analysiert.
http://www.Django-rest-framework.org/api-guide/parsers/

Standardmäßig verwendet Django-rest-framework die Parserklasse JSONParser. Es wird die Daten in Json analysieren. Dateien werden also nicht mit diesem analysiert.
Wenn Dateien zusammen mit anderen Daten analysiert werden sollen, sollten wir eine der folgenden Parserklassen verwenden.

FormParser
MultiPartParser
FileUploadParser
0

Ich möchte eine weitere Option schreiben, die meiner Meinung nach sauberer und leichter zu pflegen ist. Wir verwenden den defaultRouter, um CRUD-URLs für unser Viewset hinzuzufügen, und fügen einen weiteren festen URL hinzu, der die Uploader-Ansicht innerhalb desselben Viewsets angibt.

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

Haupt-URLs des Projekts.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.

Die Magie geschieht, wenn wir @action decorator zu unserer Klassenmethode 'uploader' hinzufügen. Wenn Sie das Argument "Methods = ['put']" angeben, lassen wir nur PUT-Anforderungen zu. perfekt für das Hochladen von Dateien. 

Ich habe auch das Argument "parser_classes" hinzugefügt, um zu zeigen, dass Sie den Parser auswählen können, der Ihren Inhalt analysiert. Ich habe CSVParser aus dem rest_framework_csv-Paket hinzugefügt, um zu zeigen, wie wir nur bestimmte Dateitypen akzeptieren können, wenn diese Funktionalität erforderlich ist. In meinem Fall akzeptiere ich nur "Content-Type: text/csv". Anmerkung: Wenn Sie benutzerdefinierte Parser hinzufügen, müssen Sie sie in parsers_classes im ViewSet angeben, da die Anforderung den zulässigen media_type mit den Parsern der Klasse (Klasse) vergleicht, bevor Sie auf die Parser der Uploader-Methode zugreifen. 

Nun müssen wir Django sagen, wie er zu dieser Methode gehen soll und wo in unseren URLs implementiert werden kann. Dann fügen wir die feste URL hinzu (einfache Zwecke). Diese URL nimmt ein Argument "Dateiname" an, das später in der Methode übergeben wird. Wir müssen diese Methode "uploader" übergeben, wobei das http-Protokoll ('PUT') in einer Liste an die Methode PostsViewSet.as_view angegeben wird. 

Wenn wir in der folgenden URL landen 

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

es erwartet eine PUT-Anforderung mit Kopfzeilen, die "Content-Type" und Content-Disposition: attachment angeben. Dateiname = "etwas.csv".

curl -v -u user:pass http://example.com/posts/uploader/ --upload-file ./something.csv --header "Content-type:text/csv"
0
Wolfgang Leon