web-dev-qa-db-fra.com

Téléchargement de fichiers Django Rest Framework

J'utilise Django Rest Framework et AngularJs pour télécharger un fichier. Mon fichier de vue ressemble à ceci:

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)

Comme la dernière ligne de la méthode post doit renvoyer toutes les données, j'ai plusieurs questions:

  • comment vérifier s'il y a quelque chose dans request.FILES?
  • comment sérialiser un champ de fichier?
  • comment dois-je utiliser l'analyseur?
60
Pawan

Utilisez FileUploadParser , tout est dans la requête . Utilisez plutôt une méthode put, vous trouverez un exemple dans la documentation :)

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
pleasedontbelong

J'utilise la même pile et je cherchais aussi un exemple de téléchargement de fichier, mais mon cas est plus simple puisque j'utilise ModelViewSet au lieu de APIView. La clé s'est avérée être le hook pre_save. J'ai fini par l'utiliser avec le module de téléchargement de fichiers angulaires comme ceci:

# 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

Enfin, je suis capable de télécharger une image en utilisant Django. Voici mon code de travail

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

demande curl pour uploader

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

Après avoir passé une journée là-dessus, j'ai compris que ...

Pour quelqu'un qui a besoin de télécharger un fichier et d'envoyer des données, il n'y a pas de moyen direct de le faire fonctionner. Il existe une spécification open issue in json api pour cela. Une possibilité que j’ai vue est d’utiliser multipart/related comme indiqué ici , mais je pense que c’est très difficile à implémenter dans drf.

Enfin, ce que j’avais implémenté était d’envoyer la demande sous la forme formdata. Vous enverriez chaque fichier sous la forme fichier et toutes les autres données sous forme de texte .Vous avez maintenant le choix pour envoyer les données sous forme de texte. cas 1) vous pouvez envoyer chaque donnée sous forme de paire clé/valeur ou cas 2) vous pouvez avoir une clé unique appelée data et envoyer le json entier sous forme de chaîne en valeur. 

La première méthode fonctionnerait immédiatement si vous avez des champs simples, mais posera un problème si vous avez sérialisé imbriqué. L'analyseur en plusieurs parties ne sera pas en mesure d'analyser les champs imbriqués.

Ci-dessous, je fournis la mise en œuvre pour les deux cas

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 -> pas de modifications spéciales nécessaires, ne montrant pas mon sérialiseur ici car il est trop long à cause de l'implémentation de ManyToMany Field en écriture.

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'

Désormais, si vous suivez la première méthode et envoyez uniquement des données non-Json sous forme de paires clé-valeur, vous n'avez pas besoin d'une classe d'analyse syntaxique personnalisée. Le MultipartParser de DRF fera le travail. Mais pour le second cas ou si vous avez des sérialiseurs imbriqués (comme je l’ai montré), vous aurez besoin d’un analyseur syntaxique personnalisé, comme indiqué ci-dessous.

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)

Ce sérialiseur analysera fondamentalement tout contenu JSON dans les valeurs.

L’exemple de requête dans post man pour les deux cas: cas 1  case 1

Affaire 2  case2

4
nithin

J'ai résolu ce problème avec ModelViewSet et ModelSerializer. J'espère que cela aidera la communauté.

Je préfère également avoir la validation et la connexion Objet-> JSON (et vice-versa) dans le sérialiseur lui-même plutôt que dans les vues.

Permet de comprendre par exemple.

Dites, je veux créer une API FileUploader. Où il va stocker des champs comme id, chemin_fichier, nom_fichier, taille, propriétaire, etc. dans la base de données. Voir exemple de modèle ci-dessous:

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)

Maintenant, pour les API c'est ce que je veux:

  1. OBTENIR:

Lorsque je déclenche le noeud final GET, je veux tous les champs ci-dessus pour chaque fichier téléchargé.

  1. POSTER:

Mais pour que l’utilisateur crée/télécharge un fichier, pourquoi elle doit se soucier de transmettre tous ces champs. Elle peut simplement télécharger le fichier et ensuite, je suppose, le sérialiseur peut récupérer les champs restants à partir du fichier téléchargé.

Searilizer: Question: J'ai créé le sérialiseur ci-dessous pour répondre à mes besoins. Mais pas sûr que ce soit la bonne façon de le mettre en œuvre.

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)

Vues pour référence:

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

D'après mon expérience, vous n'avez rien à faire de particulier sur les champs de fichier, vous lui indiquez simplement d'utiliser le champ de fichier:

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

et vous êtes prêt à télécharger des fichiers:

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

Ajoutez -F field=value pour chaque champ supplémentaire de votre modèle. Et n'oubliez pas d'ajouter une authentification.

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

Dans Django-rest-framework, les données de la demande sont analysées par la variable Parsers.
http://www.Django-rest-framework.org/api-guide/parsers/

Par défaut, Django-rest-framework utilise la classe d'analyseur JSONParser. Il analysera les données dans JSON. alors, les fichiers ne seront pas analysés avec elle.
Si nous voulons que les fichiers soient analysés avec d'autres données, nous devrions utiliser l'une des classes d'analyse ci-dessous .

FormParser
MultiPartParser
FileUploadParser
0
Anjaneyulu Batta

J'aimerais écrire une autre option qui, à mon avis, est plus propre et plus facile à gérer. Nous utiliserons le routeur defaultRouter pour ajouter les URL CRUD à notre ensemble de vues, et nous ajouterons une autre URL fixe définissant la vue de l'utilitaire de téléchargement dans le même ensemble.

**** 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 principal du projet

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

.- LISEZMOI.

La magie se produit lorsque nous ajoutons @action decorator à notre méthode de classe 'uploader'. En spécifiant l'argument "methods = ['put']", nous n'autorisons que les requêtes PUT; parfait pour le téléchargement de fichiers. 

J'ai également ajouté l'argument "parser_classes" pour montrer que vous pouvez sélectionner l'analyseur qui analysera votre contenu. J'ai ajouté CSVParser du paquetage rest_framework_csv pour montrer comment nous ne pouvons accepter que certains types de fichiers si cette fonctionnalité est requise. Dans mon cas, je n'accepte que "Content-Type: text/csv". Remarque: Si vous ajoutez des analyseurs personnalisés, vous devrez les spécifier dans parsers_classes dans ViewSet car la requête comparera le type de média autorisé avec les analyseurs principaux (de classe) avant d'accéder aux analyseurs de méthode de téléchargement. 

Maintenant, nous devons dire à Django comment utiliser cette méthode et où elle peut être implémentée dans nos URL. C'est à ce moment que nous ajoutons l'URL fixe (fins simples). Cette URL prendra un argument "filename" qui sera passé dans la méthode plus tard. Nous devons passer cette méthode "uploader", en spécifiant le protocole http ("PUT") dans une liste de la méthode PostsViewSet.as_view. 

Quand on atterrit dans l'URL suivante 

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

il attendra une requête PUT avec des en-têtes spécifiant "Content-Type" et Content-Disposition: attachment; nom de fichier = "quelque chose.csv".

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