Django muss herunterladbare Dateien bereitstellen

245

Ich möchte, dass Benutzer auf der Website Dateien herunterladen können, deren Pfade verdeckt sind, damit sie nicht direkt heruntergeladen werden können.

Zum Beispiel möchte ich, dass die URL ungefähr so ​​lautet: http://example.com/download/?f=somefile.txt

Und auf dem Server weiß ich, dass sich alle herunterladbaren Dateien im Ordner befinden /home/user/files/.

Gibt es eine Möglichkeit, Django dazu zu bringen, diese Datei zum Herunterladen bereitzustellen, anstatt zu versuchen, eine URL und eine Ansicht zu finden, um sie anzuzeigen?

Damon
quelle
2
Warum verwenden Sie dazu nicht einfach Apache? Apache liefert statische Inhalte schneller und einfacher als Django es jemals könnte.
S.Lott
22
Ich verwende Apache nicht, weil ich nicht möchte, dass auf die Dateien ohne Berechtigungen zugegriffen werden kann, die auf Django basieren.
Damon
3
Wenn Sie Benutzerberechtigungen berücksichtigen möchten, müssen Sie die Datei über Djangos Ansicht
Łukasz
127
Genau deshalb stelle ich diese Frage.
Damon

Antworten:

189

Für das "Beste aus zwei Welten" können Sie die Lösung von S.Lott mit dem xsendfile-Modul kombinieren : django generiert den Pfad zur Datei (oder zur Datei selbst), aber die eigentliche Dateiservierung wird von Apache / Lighttpd übernommen. Sobald Sie mod_xsendfile eingerichtet haben, dauert die Integration in Ihre Ansicht einige Codezeilen:

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

Dies funktioniert natürlich nur, wenn Sie die Kontrolle über Ihren Server haben oder Ihr Hosting-Unternehmen mod_xsendfile bereits eingerichtet hat.

BEARBEITEN:

Der Mimetyp wird für django 1.7 durch content_type ersetzt

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

EDIT: Für nginxüberprüfen diese , verwendet es X-Accel-Redirectanstelle von apacheX-Sendfile - Header.

elo80ka
quelle
6
Wenn Ihr Dateiname oder path_to_file Nicht-ASCII-Zeichen wie "ä" oder "ö" enthält, smart_strfunktioniert das nicht wie vorgesehen, da das Apache-Modul X-Sendfile die mit smart_str codierte Zeichenfolge nicht dekodieren kann. So kann beispielsweise die Datei "Örinää.mp3" nicht bedient werden. Und wenn man den smart_str weglässt, löst der Django selbst einen ASCII-Codierungsfehler aus, da alle Header vor dem Senden im ASCII-Format codiert werden. Die einzige Möglichkeit, dieses Problem zu umgehen, besteht darin, die Dateinamen von X-sendfile auf diejenigen zu reduzieren, die nur aus ASCII bestehen.
Ciantic
3
Um es klarer zu machen: S.Lott hat das einfache Beispiel, nur Dateien direkt von Django zu liefern, kein anderes Setup erforderlich. elo80ka hat das effizientere Beispiel, bei dem der Webserver statische Dateien verarbeitet und Django dies nicht muss. Letzteres hat eine bessere Leistung, erfordert jedoch möglicherweise mehr Setup. Beide haben ihre Plätze.
Raketenmonkeys
1
@Ciantic, siehe btimbys Antwort für eine Lösung für das Codierungsproblem.
mlissner
Funktioniert diese Lösung mit der folgenden Webserverkonfiguration? Back-End: 2 oder mehr einzelne Apache + mod_wsgi-Server (VPS), die so eingerichtet sind, dass sie sich gegenseitig replizieren. Front-End: 1 Nginx-Proxy-Server (VPS) mit Upstream-Lastausgleich für Round-Robin.
Daniel
12
mimetype wird durch content_type für django 1.7 ersetzt
ismailsunni
88

Ein "Download" ist einfach eine Änderung des HTTP-Headers.

Informationen zum Antworten mit einem Download finden Sie unter http://docs.djangoproject.com/de/dev/ref/request-response/#telling-the-browser-to-treat-the-response-as-a-file-attachment .

Sie benötigen nur eine URL-Definition für "/download".

Die Anfrage GEToder das POSTWörterbuch enthält die "f=somefile.txt"Informationen.

Ihre Ansichtsfunktion führt einfach den Basispfad mit dem fWert " " zusammen, öffnet die Datei, erstellt ein Antwortobjekt und gibt es zurück. Es sollten weniger als 12 Codezeilen sein.

S.Lott
quelle
49
Dies ist im Wesentlichen die richtige (einfache) Antwort, aber eine Warnung: Wenn Sie den Dateinamen als Parameter übergeben, kann der Benutzer möglicherweise jede Datei herunterladen (dh was ist, wenn Sie "f = / etc / passwd" übergeben?). Es gibt viele von Dingen, die helfen, dies zu verhindern (Benutzerberechtigungen usw.), aber seien Sie sich nur dieses offensichtlichen, aber allgemeinen Sicherheitsrisikos bewusst. Es ist im Grunde nur eine Teilmenge der Überprüfungseingabe: Wenn Sie einen Dateinamen an eine Ansicht übergeben, überprüfen Sie den Dateinamen in dieser Ansicht!
Raketenmonkeys
9
Eine sehr einfache Lösung für dieses Sicherheitsrisiko:filepath = filepath.replace('..', '').replace('/', '')
Dualität_
7
Wenn Sie eine Tabelle zum Speichern von Dateiinformationen verwenden, einschließlich der Benutzer, die diese herunterladen sollen, müssen Sie lediglich den Primärschlüssel und nicht den Dateinamen senden, und die App entscheidet, was zu tun ist.
Edward Newell
30

Für eine sehr einfache, aber nicht effiziente oder skalierbare Lösung können Sie einfach die integrierte Django- serveAnsicht verwenden. Dies ist hervorragend für schnelle Prototypen oder einmalige Arbeiten geeignet. Wie in dieser Frage bereits erwähnt, sollten Sie in der Produktion so etwas wie Apache oder Nginx verwenden.

from django.views.static import serve
filepath = '/some/path/to/local/file.txt'
return serve(request, os.path.basename(filepath), os.path.dirname(filepath))
Cory
quelle
Auch sehr nützlich, um einen Fallback zum Testen unter Windows bereitzustellen.
Amir Ali Akbari
Ich mache ein eigenständiges Django-Projekt, das wie ein Desktop-Client funktionieren soll, und das hat perfekt funktioniert. Vielen Dank!
Daigorocub
1
warum ist es nicht effizient?
Zinking
2
@zinking, weil Dateien im Allgemeinen über etwas wie Apache und nicht über den Django-Prozess bereitgestellt werden sollten
Cory
1
Über welche Leistungsnachteile sprechen wir hier? Werden Dateien in den Arbeitsspeicher geladen oder ähnliches, wenn sie über Django bereitgestellt werden? Warum kann Django nicht mit der gleichen Effizienz wie Nginx dienen?
Gershom
27

S.Lott hat die "gute" / einfache Lösung und elo80ka hat die "beste" / effiziente Lösung. Hier ist eine "bessere" / mittlere Lösung - kein Server-Setup, aber effizienter für große Dateien als das naive Update:

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

Grundsätzlich übernimmt Django weiterhin das Bereitstellen der Datei, lädt jedoch nicht das Ganze auf einmal in den Speicher. Auf diese Weise kann Ihr Server (langsam) eine große Datei bereitstellen, ohne die Speichernutzung zu erhöhen.

Auch hier ist das X-SendFile von S.Lott für größere Dateien immer noch besser. Aber wenn Sie sich damit nicht beschäftigen können oder wollen, werden Sie mit dieser mittleren Lösung ohne Probleme eine bessere Effizienz erzielen.

Raketenmonkeys
quelle
4
Das Snippet ist nicht gut. Dieser Ausschnitt basiert auf dem django.core.servers.httpbaseundokumentierten privaten Modul, das oben im Code " NICHT FÜR PRODUKTIONSVERWENDUNG VERWENDEN !!! " ein großes Warnzeichen hat, das seit seiner Erstellung in der Datei enthalten ist . In jedem Fall wurde die FileWrapperFunktionalität , auf die sich dieses Snippet stützt, in Django 1.9 entfernt.
Eykanal
16

Versuchte @ Rocketmonkeys-Lösung, aber heruntergeladene Dateien wurden als * .bin gespeichert und mit zufälligen Namen versehen. Das ist natürlich nicht gut. Das Hinzufügen einer weiteren Zeile von @ elo80ka löste das Problem.
Hier ist der Code, den ich jetzt verwende:

from wsgiref.util import FileWrapper
from django.http import HttpResponse

filename = "/home/stackoverflow-addict/private-folder(not-porn)/image.jpg"
wrapper = FileWrapper(file(filename))
response = HttpResponse(wrapper, content_type='text/plain')
response['Content-Disposition'] = 'attachment; filename=%s' % os.path.basename(filename)
response['Content-Length'] = os.path.getsize(filename)
return response

Sie können jetzt Dateien in einem privaten Verzeichnis (weder in / media noch / public_html) speichern und sie über Django bestimmten Benutzern oder unter bestimmten Umständen zugänglich machen.
Ich hoffe es hilft.

Dank @ elo80ka, @ S.Lott und @Rocketmonkeys für die Antworten haben wir die perfekte Lösung gefunden, die alle kombiniert =)

Salvatorelab
quelle
1
Danke, genau das habe ich gesucht!
Ihatecache
1
Fügen Sie filename="%s"im Header "Content-Disposition" doppelte Anführungszeichen um den Dateinamen ein , um Probleme mit Leerzeichen in Dateinamen zu vermeiden. Referenzen: Dateinamen mit Leerzeichen werden beim Download abgeschnitten . Wie
Christian Long
1
Ihre Lösungen funktionieren für mich. Aber ich hatte den Fehler "Ungültiges Startbyte ..." für meine Datei. Es wurde gelöst mitFileWrapper(open(path.abspath(file_name), 'rb'))
Mark Mishyn
FileWrapperwurde entfernt seit Django 1.9
freethebees
Es ist möglich zu verwendenfrom wsgiref.util import FileWrapper
Kriss
15

Erwähnen Sie nur das in Django 1.10 verfügbare FileResponse- Objekt

Bearbeiten: Ich bin gerade auf meine eigene Antwort gestoßen, als ich nach einer einfachen Möglichkeit gesucht habe, Dateien über Django zu streamen. Hier ist ein vollständigeres Beispiel (für mich in Zukunft). Es wird davon ausgegangen, dass der FileField-Name lautetimported_file

views.py

from django.views.generic.detail import DetailView   
from django.http import FileResponse
class BaseFileDownloadView(DetailView):
  def get(self, request, *args, **kwargs):
    filename=self.kwargs.get('filename', None)
    if filename is None:
      raise ValueError("Found empty filename")
    some_file = self.model.objects.get(imported_file=filename)
    response = FileResponse(some_file.imported_file, content_type="text/csv")
    # https://docs.djangoproject.com/en/1.11/howto/outputting-csv/#streaming-large-csv-files
    response['Content-Disposition'] = 'attachment; filename="%s"'%filename
    return response

class SomeFileDownloadView(BaseFileDownloadView):
    model = SomeModel

urls.py

...
url(r'^somefile/(?P<filename>[-\w_\\-\\.]+)$', views.SomeFileDownloadView.as_view(), name='somefile-download'),
...
Shadi
quelle
1
Vielen Dank! Es ist die einfachste Lösung zum Herunterladen von Binärdateien und funktioniert.
Julia Zhao
13

Es wurde oben erwähnt, dass die mod_xsendfile-Methode keine Nicht-ASCII-Zeichen in Dateinamen zulässt.

Aus diesem Grund steht für mod_xsendfile ein Patch zur Verfügung, mit dem jede Datei gesendet werden kann, sofern der Name url-codiert ist, und der zusätzliche Header:

X-SendFile-Encoding: url

Wird auch gesendet.

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

btimby
quelle
Der Patch ist jetzt in der Corer-Bibliothek zusammengefasst.
mlissner
7

Versuchen Sie: https://pypi.python.org/pypi/django-sendfile/

"Abstraktion zum Auslagern von Datei-Uploads auf den Webserver (z. B. Apache mit mod_xsendfile), sobald Django die Berechtigungen usw. überprüft hat."

Roberto Rosario
quelle
2
Zu der Zeit (vor 1 Jahr) hatte meine persönliche Abzweigung die Nicht-Apache-Datei, die einen Fallback bereitstellte, den das ursprüngliche Repository noch nicht enthielt.
Roberto Rosario
Warum haben Sie den Link entfernt?
Kiok46
@ kiok46 Konflikt mit Github-Richtlinien. Bearbeitet, um auf die kanonische Adresse zu verweisen.
Roberto Rosario
6

Sie sollten sendfile apis verwenden, die von gängigen Servern wie apacheoder nginx in der Produktion bereitgestellt werden . Viele Jahre lang habe ich die sendfile-API dieser Server zum Schutz von Dateien verwendet. Anschließend wurde eine einfache Middleware-basierte Django-App für diesen Zweck erstellt, die sowohl für Entwicklungs- als auch für Produktionszwecke geeignet ist . Hier können Sie auf den Quellcode zugreifen .
UPDATE: In der neuen Version verwendet der pythonAnbieter DjangoFileResponse falls verfügbar, und Unterstützung für viele Serverimplementierungen von lighthttp, caddy bis hiawatha

Verwendung

pip install django-fileprovider
  • fileproviderApp hinzufügen zuINSTALLED_APPS Einstellungen ,
  • hinzufügen fileprovider.middleware.FileProviderMiddlewarezuMIDDLEWARE_CLASSES Einstellungen
  • FILEPROVIDER_NAMEEinstellungen auf nginxoder apachein der Produktion einstellen , standardmäßig pythonfür Entwicklungszwecke.

Setzen Sie in Ihren Klassen- oder Funktionsansichten den Antwortheaderwert X-Fileauf den absoluten Pfad zur Datei. Beispielsweise,

def hello(request):  
   // code to check or protect the file from unauthorized access
   response = HttpResponse()  
   response['X-File'] = '/absolute/path/to/file'  
   return response  

django-fileprovider so implementiert, dass Ihr Code nur minimal geändert werden muss.

Nginx-Konfiguration

Um die Datei vor direktem Zugriff zu schützen, können Sie die Konfiguration als festlegen

 location /files/ {
  internal;
  root   /home/sideffect0/secret_files/;
 }

Hier wird nginxeine Standort-URL festgelegt, auf die /files/nur intern zugegriffen werden kann. Wenn Sie die obige Konfiguration verwenden, können Sie X-File wie folgt festlegen:

response['X-File'] = '/files/filename.extension' 

Wenn Sie dies mit der Nginx-Konfiguration tun, wird die Datei geschützt und Sie können die Datei auch von Django aus steuern views

Renjith Thankachan
quelle
2

Django empfiehlt, dass Sie einen anderen Server verwenden, um statische Medien bereitzustellen (ein anderer Server, der auf demselben Computer ausgeführt wird, ist in Ordnung). Sie empfehlen die Verwendung von Servern wie lighttp .

Dies ist sehr einfach einzurichten. Jedoch. Wenn 'somefile.txt' auf Anfrage generiert wird (der Inhalt ist dynamisch), möchten Sie möglicherweise, dass Django ihn bereitstellt.

Django Docs - Statische Dateien

kjfletch
quelle
2
def qrcodesave(request): 
    import urllib2;   
    url ="http://chart.apis.google.com/chart?cht=qr&chs=300x300&chl=s&chld=H|0"; 
    opener = urllib2.urlopen(url);  
    content_type = "application/octet-stream"
    response = HttpResponse(opener.read(), content_type=content_type)
    response["Content-Disposition"]= "attachment; filename=aktel.png"
    return response 
Saurabh Chandra Patel
quelle
0

Ein weiteres Projekt, das Sie sich ansehen sollten: http://readthedocs.org/docs/django-private-files/en/latest/usage.html Sieht vielversprechend aus, hat es aber noch nicht selbst getestet.

Grundsätzlich abstrahiert das Projekt die mod_xsendfile-Konfiguration und ermöglicht Ihnen folgende Aktionen:

from django.db import models
from django.contrib.auth.models import User
from private_files import PrivateFileField

def is_owner(request, instance):
    return (not request.user.is_anonymous()) and request.user.is_authenticated and
                   instance.owner.pk = request.user.pk

class FileSubmission(models.Model):
    description = models.CharField("description", max_length = 200)
        owner = models.ForeignKey(User)
    uploaded_file = PrivateFileField("file", upload_to = 'uploads', condition = is_owner)
avlnx
quelle
1
request.user.is_authenticated ist eine Methode, kein Attribut. (nicht request.user.is_anonymous ()) ist genau das gleiche wie request.user.is_authenticated (), da is_authenticated die Umkehrung von is_anonymous ist.
explodiert
@explodes Noch schlimmer, dieser Code ist direkt aus den Dokumenten von django-private-files...
Armando Pérez Marqués
0

Ich habe ein Projekt dazu gemacht. Sie können sich mein Github-Repo ansehen:

https://github.com/nishant-boro/django-rest-framework-download-expert

Dieses Modul bietet eine einfache Möglichkeit, Dateien zum Herunterladen im Django Rest Framework mithilfe des Apache-Moduls Xsendfile bereitzustellen. Es bietet außerdem die zusätzliche Funktion, Downloads nur für Benutzer einer bestimmten Gruppe bereitzustellen

nicks_4317
quelle