264 votes

Faire en sorte que Django serve des fichiers téléchargeables

Je veux que les utilisateurs du site puissent télécharger des fichiers dont les chemins d'accès sont masqués et ne peuvent donc pas être téléchargés directement.

Par exemple, je voudrais que l'URL soit quelque chose comme ceci : http://example.com/download/?f=somefile.txt

Et sur le serveur, je sais que tous les fichiers téléchargeables résident dans le dossier /home/user/files/ .

Existe-t-il un moyen de faire en sorte que Django serve ce fichier à télécharger plutôt que d'essayer de trouver une URL et une vue pour l'afficher ?

2 votes

Pourquoi n'utilisez-vous pas simplement Apache pour faire cela ? Apache sert le contenu statique plus rapidement et plus simplement que Django ne pourra jamais le faire.

27 votes

Je n'utilise pas Apache car je ne veux pas que les fichiers soient accessibles sans les permissions qui sont basées dans Django.

3 votes

Si vous voulez prendre en compte les permissions des utilisateurs, vous devez servir le fichier à travers la vue de Django.

195voto

elo80ka Points 4450

Pour obtenir le "meilleur des deux mondes", vous pourriez combiner la solution de S.Lott avec la solution Module xsendfile mod_xsendfile : django génère le chemin d'accès au fichier (ou le fichier lui-même), mais le service réel du fichier est géré par Apache/Lighttpd. Une fois que vous avez configuré mod_xsendfile, l'intégration à votre vue ne prend que quelques lignes de code :

from django.utils.encoding import smart_str

response = HttpResponse(mimetype='application/force-download') # mimetype is replaced by content_type for django 1.7
response['Content-Disposition'] = 'attachment; filename=%s' % smart_str(file_name)
response['X-Sendfile'] = smart_str(path_to_file)
# It's usually a good idea to set the 'Content-Length' header too.
# You can also set any other required headers: Cache-Control, etc.
return response

Bien sûr, cela ne fonctionnera que si vous avez le contrôle de votre serveur, ou si votre hébergeur a déjà configuré mod_xsendfile.

EDITAR:

mimetype est remplacé par content_type pour django 1.7

response = HttpResponse(content_type='application/force-download')  

EDITAR: Pour nginx vérifier este il utilise X-Accel-Redirect au lieu de apache En-tête X-Sendfile.

6 votes

Si votre nom de fichier, ou le chemin d'accès au fichier, comprend des caractères non ascii tels que "ä" ou "ö", la fonction smart_str ne fonctionne pas comme prévu car le module apache X-Sendfile ne peut pas décoder la chaîne encodée smart_str. Ainsi, par exemple, le fichier "Örinää.mp3" ne peut pas être servi. Et si l'on omet la smart_str, Django lui-même lance une erreur d'encodage ascii parce que tous les fichiers en-têtes sont codés au format ascii avant d'être envoyés. Le seul moyen que je connaisse pour contourner ce problème est de réduire les noms de fichiers X-sendfile à ceux qui ne sont composés que d'ascii.

3 votes

Pour être plus clair : S.Lott a un exemple simple, qui consiste à servir des fichiers directement depuis django, sans autre configuration. elo80ka a un exemple plus efficace, où le serveur web gère les fichiers statiques et django n'a pas à le faire. Cette dernière solution offre de meilleures performances, mais peut nécessiter davantage de configuration. Les deux ont leur place.

1 votes

@Ciantic, voir la réponse de btimby pour ce qui semble être une solution au problème d'encodage.

87voto

S.Lott Points 207588

Un "téléchargement" est simplement un changement d'en-tête HTTP.

Ver http://docs.djangoproject.com/en/dev/ref/request-response/#telling-the-browser-to-treat-the-response-as-a-file-attachment pour savoir comment répondre par un téléchargement.

Vous n'avez besoin que d'une seule définition d'URL pour "/download" .

La demande GET o POST le dictionnaire aura le "f=somefile.txt" informations.

Votre fonction de visualisation fusionnera simplement le chemin de base avec le " f ", ouvrir le fichier, créer et renvoyer un objet de réponse. Il devrait y avoir moins de 12 lignes de code.

51 votes

Il s'agit essentiellement de la réponse correcte (simple), mais il faut faire attention - le fait de passer le nom du fichier en tant que paramètre signifie que l'utilisateur peut potentiellement télécharger tout (par exemple, que se passe-t-il si vous passez "f=/etc/passwd" ?) Il y a beaucoup de choses qui permettent d'éviter cela (les permissions des utilisateurs, etc.), mais il faut être conscient de ce risque de sécurité évident mais courant. Il s'agit simplement d'un sous-ensemble de la validation de l'entrée : Si vous passez un nom de fichier à une vue, vérifiez le nom de fichier dans cette vue !

9 votes

A très simple pour ce problème de sécurité : filepath = filepath.replace('..', '').replace('/', '')

8 votes

Si vous utilisez une table pour stocker des informations sur les fichiers, y compris les utilisateurs qui doivent pouvoir les télécharger, il vous suffit d'envoyer la clé primaire, et non le nom du fichier, et l'application décide de ce qu'il faut faire.

35voto

Cory Points 4442

Pour une très simple mais pas efficace ni évolutif vous pouvez simplement utiliser la solution intégrée de django serve vue. C'est excellent pour les prototypes rapides ou les travaux ponctuels, mais comme cela a été mentionné tout au long de cette question, vous devriez utiliser quelque chose comme apache ou nginx en production.

from django.views.static import serve
filepath = '/some/path/to/local/file.txt'
return serve(request, os.path.basename(filepath), os.path.dirname(filepath))

0 votes

Très utile également pour fournir une solution de repli pour les tests sous Windows.

0 votes

Je réalise un projet django autonome, destiné à fonctionner un peu comme un client de bureau, et cela a parfaitement fonctionné. Merci !

1 votes

Pourquoi n'est-il pas efficace ?

27voto

Rocketmonkeys Points 1122

S.Lott a la "bonne"/simple solution, et elo80ka a la "meilleure"/efficace solution. Voici une solution "meilleure"/moyenne - pas de configuration de serveur, mais plus efficace pour les gros fichiers que la solution naïve :

http://djangosnippets.org/snippets/365/

En fait, Django s'occupe toujours de servir le fichier, mais ne le charge pas entièrement en mémoire en une seule fois. Cela permet à votre serveur de servir (lentement) un gros fichier sans augmenter l'utilisation de la mémoire.

Encore une fois, X-SendFile de S.Lott est toujours meilleur pour les gros fichiers. Mais si vous ne pouvez ou ne voulez pas vous en préoccuper, cette solution intermédiaire vous permettra d'être plus efficace sans vous embêter.

6 votes

Ce bout de phrase n'est pas bon. Ce bout de phrase s'appuie sur le django.core.servers.httpbase module privé non documenté, qui a un grand signe d'avertissement en haut du code " NE PAS UTILISER POUR LA PRODUCTION !!! ", qui a été dans le dossier depuis sa création . En tout état de cause, le FileWrapper La fonctionnalité sur laquelle s'appuie cet extrait a été supprimée dans django 1.9.

13voto

btimby Points 841

Il a été mentionné plus haut que la méthode mod_xsendfile n'autorise pas les caractères non ASCII dans les noms de fichiers.

Pour cette raison, je dispose d'un correctif pour le mod_xsendfile qui permettra d'envoyer n'importe quel fichier, à condition que le nom soit codé en url, et que l'en-tête supplémentaire :

X-SendFile-Encoding: url

est également envoyé.

http://ben.timby.com/?p=149

0 votes

Le patch est maintenant plié dans la bibliothèque du carottier.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X