Маючи Джанго подавати файли, що завантажуються


245

Я хочу, щоб користувачі на сайті мали змогу завантажувати файли, шляхи яких невідомі, щоб їх не можна було безпосередньо завантажити.

Наприклад, я хотів би, щоб URL був приблизно таким: http://example.com/download/?f=somefile.txt

І на сервері я знаю, що всі завантажувані файли перебувають у папці /home/user/files/.

Чи є спосіб змусити Джанго обслуговувати цей файл для завантаження, на відміну від спроби знайти URL-адресу та Переглянути для відображення?


2
Чому ви просто не використовуєте Apache для цього? Apache подає статичний вміст швидше і простіше, ніж коли-небудь міг Джанго.
S.Lott

22
Я не використовую Apache, тому що я не хочу, щоб файли були доступні без дозволів, що базуються в Django.
damon

3
Якщо ви хочете взяти до уваги дозволи користувачів, вам доведеться подавати файл через перегляд Django
Łukasz

127
Саме тому я і задаю це питання.
damon

Відповіді:


189

Для "найкращого з обох світів" ви могли б поєднати рішення S.Lott з модулем xsendfile : django генерує шлях до файлу (або самого файлу), але фактичне обслуговування файлів обробляє Apache / Lighttpd. Після налаштування mod_xsendfile інтеграція з представленням зображення займає кілька рядків коду:

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

Звичайно, це спрацює лише в тому випадку, якщо у вас є контроль над вашим сервером, або якщо ваша хостинг-компанія вже встановила mod_xsendfile

Редагувати:

mimetype замінюється content_type для django 1.7

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

EDIT: Для nginxперевірки цього використовується X-Accel-Redirectзамість apacheзаголовка X-Sendfile.


6
Якщо ваше ім'я файлу або path_to_file включає символи, які не належать ascii, такі як "ä" або "ö", smart_strце не працює за призначенням, оскільки модуль apache X-Sendfile не може декодувати кодований рядок smart_str. Так, наприклад, файл "Örinää.mp3" не може бути поданий. І якщо хтось опускає smart_str, сам Django видає помилку кодування ascii, оскільки всі заголовки кодуються до формату ascii перед відправкою. Єдиний спосіб, який мені відомо, щоб обійти цю проблему, - це зменшити файли файлів X-sendfile до тих, які складаються лише з ascii.
Циантік

3
Для того, щоб бути більш зрозумілим: S.Lott має простий приклад, просто обслуговуючи файли прямо з django, інші налаштування не потрібні. elo80ka має більш ефективний приклад, коли веб-сервер обробляє статичні файли, а джанго не потрібно. Останній має кращі показники, але може вимагати більше налаштувань. В обох є свої місця.
rocketmonkeys

1
@Ciantic, див. Відповідь btimby на те, що схоже на рішення проблеми кодування.
mlissner

Чи працює це рішення з наступною конфігурацією веб-сервера? Бек-енд: 2 або більше серверів Apache + mod_wsgi (VPS), створених для копіювання один одного. Фронтальний: 1 сервер проксі-сервера nginx (VPS), що використовує балансування навантаження за течією, роблячи кругообіг.
Даніель

12
mimetype замінюється content_type для django 1.7
ismailsunni

88

"Завантаження" - це просто зміна заголовка HTTP.

Див. Http://docs.djangoproject.com/en/dev/ref/request-response/#telling-the-browser-to-treat-the-response-as-a-file-attachment про те, як відповісти на завантаження .

Вам потрібно лише одне визначення URL-адреси для "/download".

Інформація про запит GETчи POSTсловник матиме "f=somefile.txt"інформацію.

Ваша функція перегляду просто об'єднає базовий шлях зі значенням " f", відкриє файл, створить і поверне об'єкт відповіді. Він повинен бути менше 12 рядків коду.


49
Це по суті правильна (проста) відповідь, але одна обережність - передача імені файлу як параметра означає, що користувач може потенційно завантажувати будь-який файл (наприклад, що робити, якщо ви передасте "f = / etc / passwd"?) Є багато речей, які допомагають запобігти цьому (дозволи користувачів та ін.), але просто пам’ятайте про цей очевидний, але загальний ризик безпеки. Це в основному лише підмножина перевірки вводу: Якщо ви передаєте ім'я файлу в представлення, перевірте ім'я файлу в цьому поданні!
racketmonkeys

9
Дуже просте виправлення цією проблемою безпеки:filepath = filepath.replace('..', '').replace('/', '')
duality_

7
Якщо ви використовуєте таблицю для зберігання інформації про файли, зокрема користувачів, які повинні мати можливість її завантажувати, то все, що вам потрібно надіслати, - це первинний ключ, а не ім'я файлу, і додаток вирішує, що робити.
Едвард Ньюелл

30

Для дуже простого, але неефективного чи масштабованого рішення ви можете просто використовувати вбудований режим serveперегляду джанго . Це відмінно підходить для швидких прототипів або одноразової роботи, але, як уже згадувалося в цьому питанні, у виробництві слід використовувати щось на зразок apache або nginx.

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

Також дуже корисно для надання резервної копії для тестування в Windows.
Амір Алі Акбарі

Я займаюся автономним проектом django, призначеним для роботи на зразок настільного клієнта, і це спрацювало чудово. Дякую!
daigorocub

1
чому це не ефективно?
цинкування

2
@zinking, оскільки файли, як правило, подаються через щось на зразок apache, а не через процес django
Cory

1
Про які недоліки продуктивності ми говоримо тут? Чи завантажуються файли в оперативну пам’ять чи щось подібне, якщо вони подаються через django? Чому джанго не здатний обслуговувати таку ж ефективність, як nginx?
Гершом

27

S.Lott має "гарне" / просте рішення, а elo80ka - "найкраще" / ефективне рішення. Ось "краще" / середнє рішення - без налаштування сервера, але більш ефективне для великих файлів, ніж наївне виправлення:

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

В основному, Django як і раніше обробляє файл, який обслуговує, але не завантажує всю справу відразу в пам'ять. Це дозволяє вашому серверу (повільно) обслуговувати великий файл, не збільшуючи використання пам'яті.

Знову-таки, X-SendFile S.Lott все ще кращий для великих файлів. Але якщо ви не можете або не хочете зациклюватися на цьому, то це середнє рішення дозволить вам підвищити ефективність без клопоту.


4
Цей фрагмент не годиться. Цей фрагмент покладається на django.core.servers.httpbaseнезадокументований приватний модуль, який має великий попереджувальний знак у верхній частині коду " НЕ ВИКОРИСТОВУЙТЕ ЗА ВИКОРИСТАННЯ ВИРОБНИЦТВА !!! ", який є у файлі з моменту його створення . У будь-якому випадку FileWrapperфункціонал, на який спирається цей фрагмент, був видалений у django 1.9.
ейканал

16

Спробував рішення @Rocketmonkeys, але завантажені файли зберігалися у форматі * .bin та давали випадкові імена. Звичайно, це не добре. Додавання ще одного рядка від @ elo80ka вирішило проблему.
Ось код, який я зараз використовую:

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

Тепер ви можете зберігати файли у приватному каталозі (не всередині / медіа та / public_html) та піддавати їх через django певним користувачам або за певних обставин.
Сподіваюся, це допомагає.

Завдяки @ elo80ka, @ S.Lott та @Rocketmonkeys за відповіді, ви отримали ідеальне рішення, поєднуючи їх усі =)


1
Дякую, саме це я шукав!
ihatecache

1
Додайте подвійні лапки навколо імені файлу filename="%s"у заголовку Content-Disposition, щоб уникнути проблем із пробілами у назвах файлів. Посилання: Імена файлів з пробілами усічені на завантаження , Як кодувати параметр ім'я файлу заголовка Content-Disposition в HTTP?
Крістіан Лонг

1
Ваші рішення працюють на мене. Але в моєму файлі була помилка "недійсний запуск байта ...". Вирішив цеFileWrapper(open(path.abspath(file_name), 'rb'))
Марк Мішин

FileWrapperбуло видалено з моменту Django 1.9
freethebees

Можна скористатисяfrom wsgiref.util import FileWrapper
Kriss

15

Просто згадуючи об’єкт FileResponse, доступний у Django 1.10

Редагувати: Я просто зіткнувся з власною відповіддю під час пошуку простого способу передачі файлів через Django, ось ось більш повний приклад (для мене майбутнього). Це передбачає, що ім'я FileField єimported_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
Велике спасибі! Це найпростіше рішення для завантаження бінарних файлів, і воно працює.
Джулія Чжао

13

Вище було сказано, що метод mod_xsendfile не дозволяє використовувати символи, що не належать до ASCII, у назви файлів.

З цієї причини у мене є патч для mod_xsendfile, який дозволить надсилати будь-який файл, якщо ім'я кодується URL-адресою, та додатковий заголовок:

X-SendFile-Encoding: url

Також відправляється.

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


Патч тепер складено в бібліотеку corer.
mlissner

7

Спробуйте: https://pypi.python.org/pypi/django-sendfile/

"Абстракція для завантаження завантаження файлів на веб-сервер (наприклад, Apache з mod_xsendfile), як тільки Django перевірить дозволи та ін."


2
На той час (1 рік тому) моя особиста вилка мала файл Apache, який подає резервне копіювання, оригінальний сховище ще не включено.
Роберто Росаріо

Чому ви видалили посилання?
kiok46

@ kiok46 Конфлікт із політикою Github. Відредаговано, щоб вказати на канонічну адресу.
Роберто Росаріо

6

Ви повинні використовувати sendfile apis, наданий популярними серверами, такими як apacheабо nginx у виробництві. Багато років я використовував sendfile api цих серверів для захисту файлів. Потім створили простий додаток джанго на основі середнього програмного забезпечення, яке підходить як для розробки, так і для виробництва. Ви можете отримати доступ до вихідного коду тут .
ОНОВЛЕННЯ: у новій версії pythonпостачальник використовує django, FileResponseякщо є, а також додає підтримку для багатьох реалізацій сервера, починаючи від lighthttp, caddy до hiawatha

Використання

pip install django-fileprovider
  • додайте fileproviderдодаток до INSTALLED_APPSналаштувань,
  • додати fileprovider.middleware.FileProviderMiddlewareдо MIDDLEWARE_CLASSESналаштувань
  • встановити FILEPROVIDER_NAMEналаштування на виробництво nginxабо apacheу виробництві, за замовчуванням це призначене pythonдля розробки.

у переглядах класів або функцій встановіть X-Fileзначення заголовка відповіді на абсолютний шлях до файлу. Наприклад,

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 передбачається таким чином, що ваш код потребуватиме лише мінімальних змін.

Конфігурація Nginx

Щоб захистити файл від прямого доступу, ви можете встановити конфігурацію як

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

Тут nginxвстановлюється лише URL-адреса URL-адреси /files/доступу, якщо ви використовуєте конфігурацію вище, ви можете встановити X-файл як

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

Роблячи це з конфігурацією nginx, файл буде захищений, а також ви можете керувати файлом від django views


2

Django рекомендує використовувати інший сервер для подання статичних носіїв (інший сервер, який працює на тій же машині, добре). Вони рекомендують використовувати такі сервери, як lighttp .

Це дуже просто налаштувати. Однак. якщо 'somefile.txt' генерується на запит (вміст динамічний), ви, можливо, захочете django для його обслуговування.

Документи Джанго - статичні файли


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

Ще один проект, на який можна ознайомитися: http://readthedocs.org/docs/django-private-files/en/latest/usage.html Виглядає багатообіцяюче, ще не перевіряв його ще.

В основному проект резюмує конфігурацію mod_xsendfile і дозволяє робити такі дії, як:

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_authentication - це метод, а не атрибут. (not request.user.is_anonymous ()) - це саме те саме, що і request.user.is_authentication (), оскільки is_authentication є зворотним значенням is_anonymous.
вибухає

@explodes Навіть найгірше, що код прямо з документів django-private-files...
Armando Pérez Marqués



0

Я зробив проект з цього приводу. Ви можете подивитися на моєму рефіні Github:

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

Цей модуль пропонує простий спосіб подачі файлів для завантаження в рамці відпочинку django за допомогою модуля Apache Xsendfile. Він також має додаткову функцію подачі завантажень лише користувачам, що належать до певної групи

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.