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:
request.FILES
?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)
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]
});
};
});
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
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 ,
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:
Lorsque je déclenche le noeud final GET, je veux tous les champs ci-dessus pour chaque fichier téléchargé.
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
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.
def post(self,request):
serializer = ProductSerializer(data=request.DATA, files=request.FILES)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
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)
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
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"