Django'nun indirilebilir dosyalar sunmasını sağlama


245

Sitedeki kullanıcıların doğrudan indirilememeleri için yolları gizlenmiş dosyaları indirmelerini istiyorum.

Örneğin, URL'nin böyle bir şey olmasını istiyorum: http://example.com/download/?f=somefile.txt

Ve sunucuda, indirilebilir tüm dosyaların klasörde olduğunu biliyorum /home/user/files/.

Django'yu, bir URL bulmaya ve görüntülemek için Görünüm'ün aksine bu dosyayı indirmek üzere sunmanın bir yolu var mı?


2
Bunu yapmak için neden sadece Apache kullanmıyorsunuz? Apache, statik içeriği Django'dan daha hızlı ve daha basit bir şekilde sunar.
S.Lott

22
Apache kullanmıyorum çünkü Django'da bulunan izinler olmadan dosyalara erişilmesini istemiyorum.
damon

3
Kullanıcı izinlerini dikkate almak istiyorsanız, Django'nun görünümü üzerinden dosya sunmanız gerekir
Łukasz

127
Kesinlikle, bu yüzden bu soruyu soruyorum.
damon

Yanıtlar:


189

"Her iki dünyanın en iyisi" için S.Lott'un çözümünü xsendfile modülü ile birleştirebilirsiniz : django dosyanın (veya dosyanın kendisinin) yolunu oluşturur, ancak asıl dosya sunumu Apache / Lighttpd tarafından işlenir. Mod_xsendfile'ı kurduktan sonra, görünümünüzle entegrasyon birkaç satır kod alır:

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

Tabii ki, bu sadece sunucunuz üzerinde kontrolünüz varsa veya barındırma şirketinizde mod_xsendfile önceden ayarlanmışsa çalışır.

DÜZENLE:

mimetype, django 1.7 için content_type ile değiştirildi

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

DÜZENLEME: For nginxkontrol bu , kullandığı X-Accel-Redirectyerine apacheX-Sendfile başlığındaki.


6
Dosya adınız veya path_to_file, "ä" veya "ö" gibi ascii olmayan karakterler içeriyorsa, smart_strX-Sendfile apache modülü smart_str kodlu dizginin kodunu çözemediğinden, amaçlandığı gibi çalışmaz. Bu nedenle, örneğin "Örinää.mp3" dosyası sunulamaz. Ve biri smart_str'i atlarsa, Django kendisi ascii kodlama hatası atar çünkü tüm başlıklar göndermeden önce ascii biçimine kodlanır. Bu sorunu atlatmak için bildiğim tek yol, X-sendfile dosya adlarını yalnızca ascii olanlara azaltmaktır.
Ciantic

3
Daha açık olmak gerekirse: S.Lott basit bir örneğe sahiptir, sadece django'dan dosya sunmak, başka bir kuruluma gerek yoktur. elo80ka, web sunucusunun statik dosyaları işlediği ve django'nun gerekmediği daha verimli bir örneğe sahiptir. İkincisi daha iyi performansa sahiptir, ancak daha fazla kurulum gerektirebilir. Her ikisinin de yerleri var.
rocketmonkey

1
@Ciantic, kodlama sorununa bir çözüm gibi görünen şeyler için btimby'nin cevabına bakın.
mlissner

Bu çözüm aşağıdaki web sunucusu yapılandırmasıyla çalışıyor mu? Arka uç: Birbirini çoğaltmak için ayarlanmış 2 veya daha fazla Apache + mod_wsgi ayrı (VPS) sunucusu. Ön uç: yukarı akış yük dengelemesini kullanan 1 adet yuvarlak xginx proxy (VPS) sunucusu.
Daniel

12

88

"İndirme" basitçe bir HTTP üstbilgisi değişikliğidir.

İndirme işlemine nasıl yanıt verileceği konusunda http://docs.djangoproject.com/tr/dev/ref/request-response/#telling-the-browser-to-treat-the-response-as-a-file-attachment adresine bakın. .

İçin yalnızca bir URL tanımına ihtiyacınız var "/download".

İsteğin GETveya POSTsözlüğün bilgileri olacaktır "f=somefile.txt".

Görüntüleme işleviniz temel yolu " f" değeriyle birleştirir , dosyayı açar, bir yanıt nesnesi oluşturur ve döndürür. 12 satırdan az kod olmalıdır.


49
Bu aslında doğru (basit) cevaptır, ancak bir uyarı - dosya adını parametre olarak iletmek, kullanıcının herhangi bir dosyayı potansiyel olarak indirebileceği anlamına gelir (yani. "F = / etc / passwd" iletirseniz?) (kullanıcı izinleri, vb.) önlemeye yardımcı olan, ancak bu açık ancak ortak güvenlik riskinin farkında olun. Temel olarak girişin doğrulanması için bir alt kümedir: Bir görünüme dosya adı iletirseniz, o görünümdeki dosya adını kontrol edin!
rocketmonkey

9
Bir çok basit bu güvenlik endişe düzeltme:filepath = filepath.replace('..', '').replace('/', '')
duality_

7
Dosya bilgilerini depolamak için hangi kullanıcıların indirebileceği de dahil olmak üzere bir tablo kullanıyorsanız, göndermeniz gereken tek şey dosya adı değil birincil anahtardır ve uygulama ne yapacağınıza karar verir.
Edward Newell

30

Çok basit ama etkili olmayan veya ölçeklenebilir bir çözüm için, yerleşik django servegörünümünü kullanabilirsiniz. Bu, hızlı prototipler veya bir kerelik işler için mükemmeldir, ancak bu soru boyunca belirtildiği gibi, üretimde apache veya nginx gibi bir şey kullanmalısınız.

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

Ayrıca Windows üzerinde test için bir yedek sağlamak için çok yararlıdır.
Amir Ali Akbari

Bir masaüstü istemcisi gibi çalışmayı amaçlayan bağımsız bir django projesi yapıyorum ve bu mükemmel çalıştı. Teşekkürler!
daigorocub

1
neden verimli değil?
14'te

2
@zinking çünkü dosyalar genellikle django işlemi yerine apache gibi bir şeyle sunulmalıdır
Cory

1
Burada ne tür performans dezavantajlarından bahsediyoruz? Dosyalar django aracılığıyla sunuluyorsa RAM'e veya başka bir şeye yüklenir mi? Neden django nginx ile aynı verimlilikte hizmet veremiyor?
Gershom

27

S.Lott "iyi" / basit bir çözüme sahiptir ve elo80ka "en iyi" / verimli bir çözüme sahiptir. İşte "daha iyi" / orta çözüm - sunucu kurulumu yok, ancak büyük dosyalar için naif düzeltmeden daha verimli:

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

Temel olarak, Django dosyayı sunmaya devam eder, ancak her şeyi bir kerede belleğe yüklemez. Bu, sunucunuzun bellek kullanımını hızlandırmadan büyük bir dosyaya (yavaşça) hizmet vermesini sağlar.

Yine, S.Lott'un X-SendFile daha büyük dosyalar için hala daha iyidir. Ancak bununla uğraşmak istemiyorsanız veya istemiyorsanız, bu orta çözüm size sorunsuz bir şekilde daha fazla verimlilik kazandıracaktır.


4
Bu pasaj iyi değil. Bu parçalama , ilk oluşturulduğundan beri dosyada bulunan " ÜRETİM KULLANIMI İÇİN KULLANMAYIN !!! " django.core.servers.httpbasekodunun üstünde büyük bir uyarı işaretine sahip olan belgelenmemiş özel modüle dayanır . Her durumda, bu pasajın dayandığı işlevler django 1.9'da kaldırılmıştır. FileWrapper
eykanal

16

@Rocketmonkeys çözümü denendi, ancak indirilen dosyalar * .bin olarak saklandı ve rastgele adlar verildi. Tabii ki bu iyi değil. @ Elo80ka'dan başka bir satır eklemek sorunu çözdü.
İşte şimdi kullanıyorum kodu:

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

Artık dosyaları özel bir dizinde (/ media veya / public_html içinde değil) saklayabilir ve django aracılığıyla belirli kullanıcılara veya belirli koşullar altında açığa çıkarabilirsiniz.
Umarım yardımcı olur.

@ Elo80ka, cevaplar için @ S.Lott ve @Rocketmonkeys sayesinde hepsini bir araya getiren mükemmel çözümü elde ettiniz =)


1
Teşekkür ederim, tam olarak aradığım şey buydu!
ihatecache

1
filename="%s"Dosya adlarındaki boşluklarla ilgili sorunları önlemek için Content-Disposition üstbilgisindeki dosya adının başına çift tırnak ekleyin . Referanslar: Boşluklu dosya adları
Christian Long

1
Çözümleriniz benim için çalışıyor. Ancak dosyam için "geçersiz başlangıç ​​baytı ..." hatası aldım. ÇözüldüFileWrapper(open(path.abspath(file_name), 'rb'))
Mark Mishyn

FileWrapperDjango 1.9'dan beri kaldırıldı
freethebees

Kullanmak mümkündürfrom wsgiref.util import FileWrapper
Kriss

15

Django 1.10'da bulunan FileResponse nesnesinden bahsetmek

Düzenleme: Sadece Django üzerinden dosya akışı için kolay bir yol ararken kendi cevabımda koştu, işte burada daha eksiksiz bir örnek (gelecekteki bana). FileField adınınimported_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'),
...

1
Çok teşekkür ederim! İkili dosyaları indirmek için en basit çözüm ve çalışıyor.
Julia Zhao

13

Yukarıda mod_xsendfile yönteminin dosya adlarında ASCII olmayan karakterlere izin vermediğinden bahsedilmiştir.

Bu nedenle, mod url kodlanmış olduğu sürece herhangi bir dosyanın gönderilmesine izin verecek mod_xsendfile için bir düzeltme eki ve ek üstbilgi var:

X-SendFile-Encoding: url

Ayrıca gönderilir.

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


Yama şimdi çekirdek kütüphanesine katlandı.
mlissner

7

Deneyin: https://pypi.python.org/pypi/django-sendfile/

"Django izinleri kontrol ettikten sonra dosya yüklemelerini web sunucusuna boşaltmak için soyutlama (örn. Mod_xsendfile ile Apache)"


2
O zamanlar (1 yıl önce) kişisel çatalım, orijinal depo henüz dahil etmediği yedek sunan Apache olmayan bir dosyaya sahipti.
Roberto Rosario

Bağlantıyı neden kaldırdınız?
kiok46

@ kiok46 Github politikalarıyla çatışma. Standart adrese yönlendirmek için düzenlendi.
Roberto Rosario

6

Üretimdeki apacheveya nginxüretimdeki popüler sunucular tarafından verilen sendfile apislerini kullanmalısınız . Uzun yıllar dosyaları korumak için bu sunucuların sendfile api kullanıyordum. Sonra kaynak kodu erişebilirsiniz geliştirme ve üretim hem purpose.You için uygun bu amaç için app django dayalı basit ara yazılım yarattı burada .
GÜNCELLEME: yeni sürümde pythonsağlayıcı FileResponsevarsa django kullanır ve ayrıca lighthttp, caddy'den hiawatha'ya kadar birçok sunucu uygulaması için destek ekler

kullanım

pip install django-fileprovider
  • ayarlara fileprovideruygulama ekleyin INSTALLED_APPS,
  • eklemek fileprovider.middleware.FileProviderMiddlewareiçin MIDDLEWARE_CLASSESayarlarda
  • FILEPROVIDER_NAMEayarları üretimde nginxveya apacheüretimde ayarlarsanız , varsayılan pythonolarak geliştirme amaçlıdır.

sınıf tabanlı veya işlev görünümlerinizde yanıt üstbilgisi X-Filedeğerini dosyanın mutlak yoluna ayarlayın . Örneğin,

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 kodunuzun yalnızca minimum modifikasyona ihtiyaç duyacağı şekilde kısıtlanmıştır.

Nginx yapılandırması

Dosyayı doğrudan erişimden korumak için yapılandırmayı aşağıdaki gibi ayarlayabilirsiniz:

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

Burada , yalnızca dahili olarak nginxerişilen bir konum URL'si ayarlar /files/, yukarıdaki yapılandırmayı kullanıyorsanız X Dosyasını şu şekilde ayarlayabilirsiniz:

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

Bunu nginx yapılandırması ile yaparak dosya korunacak ve ayrıca dosyayı django'dan kontrol edebilirsiniz. views


2

Django, statik medya sunmak için başka bir sunucu kullanmanızı önerir (aynı makinede çalışan başka bir sunucu iyidir.) Bu tür sunucuların lighttp olarak kullanılmasını önerirler .

Bunu ayarlamak çok basit. Ancak. istek üzerine 'somefile.txt' oluşturulursa (içerik dinamiktir) django'nun bunu sunmasını isteyebilirsiniz.

Django Belgeleri - Statik Dosyalar


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 

0

Göz atmak için başka bir proje: http://readthedocs.org/docs/django-private-files/en/latest/usage.html Gelecek vaat ediyor, henüz kendimi test etmedim.

Temel olarak proje mod_xsendfile yapılandırmasını soyutlar ve aşağıdakileri yapmanızı sağlar:

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)

1
request.user.is_authenticated bir öznitelik değil, bir yöntemdir. (değil request.user.is_anonymous ()), request.user.is_authenticated () ile tamamen aynıdır, çünkü is_authenticated is_anonymous öğesinin tersidir.
patlar

@explodes Daha da kötüsü, bu kod doğru belgelerin django-private-files...
Armando Pérez Marqués



Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.