Posiadanie przez Django plików do pobrania

245

Chcę, aby użytkownicy w witrynie mogli pobierać pliki, których ścieżki są zasłonięte, więc nie można ich pobrać bezpośrednio.

Na przykład chciałbym, aby adres URL był mniej więcej taki: http://example.com/download/?f=somefile.txt

A na serwerze wiem, że wszystkie pliki do pobrania znajdują się w folderze /home/user/files/.

Czy istnieje sposób, aby Django wyświetlał ten plik do pobrania, zamiast próbować znaleźć adres URL i wyświetlić, aby go wyświetlić?

Damon
źródło
2
Dlaczego po prostu nie używasz do tego Apache? Apache obsługuje zawartość statyczną szybciej i prościej niż kiedykolwiek Django.
S.Lott,
22
Nie używam Apache, ponieważ nie chcę, aby pliki były dostępne bez uprawnień opartych na Django.
damon
3
Jeśli chcesz wziąć pod uwagę uprawnienia użytkownika, musisz podać plik w widoku Django
Łukasz
127
Właśnie, dlatego zadaję to pytanie.
damon

Odpowiedzi:

189

Dla „najlepszego z obu światów” można połączyć rozwiązanie S.Lott z modułem xsendfile : django generuje ścieżkę do pliku (lub samego pliku), ale faktyczną obsługą plików zajmuje się Apache / Lighttpd. Po skonfigurowaniu pliku mod_xsendfile integracja z twoim widokiem zajmuje kilka linii kodu:

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

Oczywiście będzie to działać tylko wtedy, gdy masz kontrolę nad serwerem lub jeśli Twoja firma hostingowa ma skonfigurowany plik mod_xsendfile.

EDYTOWAĆ:

mimetype jest zastąpiony przez content_type dla django 1.7

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

EDYCJA: Aby tonginx sprawdzić , używa zamiast nagłówka X-Sendfile.X-Accel-Redirectapache

elo80ka
źródło
6
Jeśli twoja nazwa pliku lub ścieżka_do_pliku zawiera znaki inne niż ascii, takie jak „ä” lub „ö”, smart_strto nie działa zgodnie z przeznaczeniem, ponieważ moduł X-Sendfile apache nie może dekodować ciągu zakodowanego w smart_str. Dlatego na przykład nie można podać pliku „Örinää.mp3”. A jeśli pominąć smart_str, sam Django generuje błąd kodowania ascii, ponieważ wszystkie nagłówki są kodowane do formatu ascii przed wysłaniem. Jedynym sposobem na obejście tego problemu jest zmniejszenie nazw plików X-sendfile do tych, które składają się tylko z ascii.
Ciantic
3
Aby być bardziej zrozumiałym: S.Lott ma prosty przykład, po prostu serwowanie plików prosto z django, nie jest wymagana żadna inna konfiguracja. elo80ka ma bardziej wydajny przykład, w którym serwer WWW obsługuje pliki statyczne, a django nie musi. Ten drugi ma lepszą wydajność, ale może wymagać większej konfiguracji. Oba mają swoje miejsca.
rocketmonkeys
1
@Ciantic, patrz odpowiedź btimby na coś, co wygląda na rozwiązanie problemu z kodowaniem.
mlissner
Czy to rozwiązanie działa z następującą konfiguracją serwera WWW? Back-end: 2 lub więcej indywidualnych serwerów Apache + mod_wsgi (VPS) skonfigurowanych do wzajemnej replikacji. Front-end: 1 serwer proxy Nginx (VPS) wykorzystujący równoważenie obciążenia w górę, działający w trybie round-robin.
Daniel
12
mimetype jest zastępowany przez content_type dla django 1.7
ismailsunni
88

„Pobieranie” to po prostu zmiana nagłówka HTTP.

Zobacz http://docs.djangoproject.com/en/dev/ref/request-response/#telling-the-browser-to-treat-the-response-as-a-file-attachment, aby dowiedzieć się, jak odpowiedzieć przy pobieraniu .

Potrzebujesz tylko jednej definicji adresu URL dla "/download".

Żądanie GETlub POSTsłownik będzie zawierał "f=somefile.txt"informacje.

Twoja funkcja widoku po prostu połączy ścieżkę podstawową z wartością „ f”, otworzy plik, utworzy i zwróci obiekt odpowiedzi. Powinien mieć mniej niż 12 linii kodu.

S.Lott
źródło
49
Jest to w zasadzie poprawna (prosta) odpowiedź, ale jedna uwaga - przekazanie nazwy pliku jako parametru oznacza, że ​​użytkownik może potencjalnie pobrać dowolny plik (tj. Co, jeśli przekażesz „f = / etc / passwd”?) Jest wiele rzeczy, które pomagają temu zapobiec (uprawnienia użytkownika itp.), ale należy pamiętać o oczywistym, ale powszechnym zagrożeniu bezpieczeństwa. Jest to po prostu podzbiór sprawdzania poprawności danych wejściowych: jeśli podasz nazwę pliku do widoku, sprawdź nazwę pliku w tym widoku!
rocketmonkeys,
9
Bardzo prosta poprawka dla tego koncernu bezpieczeństwa:filepath = filepath.replace('..', '').replace('/', '')
duality_
7
Jeśli używasz tabeli do przechowywania informacji o plikach, w tym o tym, którzy użytkownicy powinni ją pobrać, wystarczy wysłać klucz podstawowy, a nie nazwę pliku, a aplikacja zdecyduje, co zrobić.
Edward Newell,
30

Aby uzyskać bardzo proste, ale nieefektywne lub skalowalne rozwiązanie, możesz po prostu użyć wbudowanego servewidoku django . Jest to doskonałe do szybkich prototypów lub jednorazowej pracy, ale jak wspomniano w tym pytaniu, powinieneś używać czegoś takiego jak apache lub nginx w produkcji.

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
źródło
Również bardzo przydatny do zapewnienia rezerwowego do testowania w systemie Windows.
Amir Ali Akbari
Robię samodzielny projekt django, który ma działać jak klient stacjonarny i działało to doskonale. Dzięki!
daigorocub
1
dlaczego nie jest wydajny?
zinking
2
@zinking, ponieważ pliki powinny być generalnie obsługiwane przez coś w rodzaju apache, a nie przez proces django
Cory
1
O jakich wadach wydajności mówimy tutaj? Czy pliki są ładowane do pamięci RAM, czy coś w tym rodzaju, jeśli są obsługiwane przez django? Dlaczego django nie jest w stanie działać z taką samą wydajnością jak nginx?
Gershom,
27

S.Lott ma „dobre” / proste rozwiązanie, a elo80ka ma „najlepsze” / wydajne rozwiązanie. Oto „lepsze” / środkowe rozwiązanie - bez konfiguracji serwera, ale bardziej wydajne dla dużych plików niż naiwna poprawka:

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

Zasadniczo Django nadal obsługuje udostępnianie pliku, ale nie ładuje całej pamięci naraz. Pozwala to serwerowi (powoli) podawać duży plik bez zwiększania zużycia pamięci.

Ponownie, X-SendFile S.Lott jest nadal lepszy dla większych plików. Ale jeśli nie możesz lub nie chcesz się tym przejmować, to to środkowe rozwiązanie zapewni Ci lepszą wydajność bez kłopotów.

Rocketmonkeys
źródło
4
Ten fragment kodu nie jest dobry. To wycięcie opiera się na django.core.servers.httpbasenieudokumentowanym module prywatnym, który ma duży znak ostrzegawczy w górnej części kodu „ NIE UŻYWAJ DO UŻYTKU PRODUKCYJNEGO !!! ”, który znajduje się w pliku od momentu jego pierwszego utworzenia . W każdym razie FileWrapperfunkcjonalność, na której opiera się ten fragment kodu, została usunięta w wersji django 1.9.
eykanal
16

Próbowałem rozwiązania @Rocketmonkeys, ale pobrane pliki były przechowywane jako * .bin i otrzymywały losowe nazwy. Oczywiście to nie w porządku. Dodanie kolejnej linii z @ elo80ka rozwiązało problem.
Oto kod, którego teraz używam:

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

Możesz teraz przechowywać pliki w prywatnym katalogu (nie wewnątrz / media ani / public_html) i udostępniać je za pośrednictwem django pewnym użytkownikom lub w określonych okolicznościach.
Mam nadzieję, że to pomoże.

Dzięki @ elo80ka, @ S.Lott i @Rocketmonkeys za odpowiedzi, otrzymaliśmy idealne rozwiązanie łączące je wszystkie =)

Salvatorelab
źródło
1
Dziękuję, właśnie tego szukałem!
ihatecache
1
Dodaj cudzysłowy wokół nazwy pliku filename="%s"w nagłówku Content-Disposition, aby uniknąć problemów ze spacjami w nazwach plików. Odniesienia: Nazwy plików ze spacjami są obcinane podczas pobierania , Jak zakodować parametr nazwy nagłówka Content-Disposition w HTTP?
Christian Long
1
Twoje rozwiązania działają dla mnie. Ale mój plik miał błąd „nieprawidłowy bajt początkowy ...”. Rozwiązał to za pomocąFileWrapper(open(path.abspath(file_name), 'rb'))
Mark Mishyn
FileWrapperzostał usunięty od Django 1.9
freethebees
Możliwe jest użyciefrom wsgiref.util import FileWrapper
Kriss
15

Wystarczy wspomnieć o obiekcie FileResponse dostępnym w Django 1.10

Edycja: Właśnie natrafiłem na własną odpowiedź, szukając łatwego sposobu przesyłania strumieniowego plików przez Django, więc oto bardziej kompletny przykład (dla mnie przyszłości). Zakłada się, że nazwa FileField toimported_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
źródło
1
Dziękuję Ci bardzo! Jest to najprostsze rozwiązanie do pobierania plików binarnych i działa.
Julia Zhao
13

Wspomniano powyżej, że metoda mod_xsendfile nie dopuszcza znaków innych niż ASCII w nazwach plików.

Z tego powodu mam łatkę do pliku mod_xsendfile, która pozwoli na wysłanie dowolnego pliku, o ile nazwa jest zakodowana w url, oraz dodatkowy nagłówek:

X-SendFile-Encoding: url

Jest również wysyłany.

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

btimby
źródło
Łatka jest teraz złożona do biblioteki corer.
mlissner
7

Spróbuj: https://pypi.python.org/pypi/django-sendfile/

„Abstrakcja w celu odciążenia przesyłania plików na serwer WWW (np. Apache z mod_xsendfile) po sprawdzeniu uprawnień przez Django itp.”

Roberto Rosario
źródło
2
W tym czasie (1 rok temu) mój osobisty rozwidlenie miał plik inny niż Apache służący do tworzenia kopii zapasowych, którego oryginalne repozytorium jeszcze nie zawierało.
Roberto Rosario
Dlaczego usunąłeś link?
kiok46
@ kiok46 Konflikt z zasadami Github. Edytowane, aby wskazywało adres kanoniczny.
Roberto Rosario,
6

Powinieneś używać apli sendfile podanych przez popularne serwery, takie jak apachelub nginx produkcyjne. Przez wiele lat korzystałem z interfejsu API sendfile tych serwerów do ochrony plików. Następnie stworzył prostą middleware w oparciu Django aplikacji do tego celu nadaje się zarówno do rozwoju i produkcji purpose.You może uzyskać dostęp do kodu źródłowego tutaj .
AKTUALIZACJA: w nowej wersji pythondostawca używa django, FileResponsejeśli jest dostępny, a także dodaje obsługę wielu implementacji serwera, od lighthttp, caddy po hiawatha

Stosowanie

pip install django-fileprovider
  • dodaj fileprovideraplikację do INSTALLED_APPSustawień,
  • dodaj fileprovider.middleware.FileProviderMiddlewaredo MIDDLEWARE_CLASSESustawień
  • ustawić FILEPROVIDER_NAMEustawienia na nginxlub apachew produkcji, domyślnie jest to pythondo celów programistycznych.

w widokach opartych na klasach lub funkcjach ustaw X-Filewartość nagłówka odpowiedzi na bezwzględną ścieżkę do pliku. Na przykład,

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 usprawnione w taki sposób, że Twój kod będzie wymagał jedynie minimalnej modyfikacji.

Konfiguracja Nginx

Aby chronić plik przed bezpośrednim dostępem, możesz ustawić konfigurację jako

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

Tutaj nginxustawia adres URL /files/dostępu tylko wewnętrznie, jeśli używasz powyższej konfiguracji, możesz ustawić plik X jako,

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

Robiąc to z konfiguracją nginx, plik będzie chroniony, a także możesz kontrolować plik z django views

Renjith Thankachan
źródło
2

Django zaleca użycie innego serwera do obsługi nośników statycznych (inny serwer działający na tym samym komputerze jest w porządku) . Zalecają użycie takich serwerów jak lighttp .

Jest to bardzo proste w konfiguracji. Jednak. jeśli plik „somefile.txt” jest generowany na żądanie (treść jest dynamiczna), możesz chcieć, aby django go obsługiwał.

Dokumenty Django - pliki statyczne

kjfletch
źródło
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
źródło
0

Kolejny projekt do obejrzenia: http://readthedocs.org/docs/django-private-files/en/latest/usage.html Wygląda obiecująco, sam jeszcze go nie testowałem.

Zasadniczo projekt wyodrębnia konfigurację pliku mod_xsendfile i umożliwia wykonywanie następujących czynności:

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
źródło
1
request.user.is_authenticated to metoda, a nie atrybut. (nie request.user.is_anonymous ()) jest dokładnie taki sam jak request.user.is_authenticated (), ponieważ is_authenticated to odwrotność is_anonymous.
wybucha
@explodes Co gorsza, ten kod pochodzi bezpośrednio z dokumentów django-private-files...
Armando Pérez Marqués
0

Zrobiłem projekt na ten temat. Możesz spojrzeć na moje repozytorium github:

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

Ten moduł zapewnia prosty sposób udostępniania plików do pobrania w środowisku odpoczynku django za pomocą modułu Apache Xsendfile. Ma także dodatkową funkcję udostępniania pobrań tylko użytkownikom należącym do określonej grupy

nicks_4317
źródło