Django обслуживает загружаемые файлы

245

Я хочу, чтобы пользователи на сайте могли загружать файлы, пути которых скрыты, поэтому их нельзя напрямую загрузить.

Например, я бы хотел, чтобы URL был примерно таким: http://example.com/download/?f=somefile.txt

И на сервере я знаю, что все загружаемые файлы находятся в папке /home/user/files/.

Есть ли способ заставить Django обслуживать этот файл для загрузки, в отличие от попыток найти URL и View для его отображения?

Дэймон
источник
2
Почему вы просто не используете Apache для этого? Apache обслуживает статический контент быстрее и проще, чем когда-либо мог Django.
S.Lott
22
Я не использую Apache, потому что я не хочу, чтобы файлы были доступны без разрешений, основанных на Django.
Деймон
3
Если вы хотите принять во внимание права пользователя, вы должны подать файл через взгляд Джанго
Лукаш
127
Именно поэтому я и задаю этот вопрос.
Деймон

Ответы:

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.

elo80ka
источник
6
Если ваше имя файла или path_to_file содержит не-ascii символы, такие как «ä» или «ö», smart_strони не работают должным образом, так как X-Sendfile модуля apache не может декодировать строку, закодированную в smart_str. Так, например, файл «Örinää.mp3» не может быть предоставлен. И если не указывать smart_str, Django сам выдает ошибку кодирования ascii, потому что все заголовки перед отправкой кодируются в формат ascii. Единственный способ, который я знаю, чтобы обойти эту проблему - это сократить имена файлов X-sendfile до тех, которые состоят только из ascii.
Ciantic
3
Чтобы быть более ясным: S.Lott имеет простой пример, просто обслуживая файлы прямо из django, никаких других настроек не требуется. У elo80ka есть более эффективный пример, где веб-сервер обрабатывает статические файлы, а django не обязан. Последний имеет лучшую производительность, но может потребовать больше настроек. У обоих есть свои места.
rocketmonkeys
1
@Ciantic, см. Ответ btimby о том, что похоже на решение проблемы кодирования.
mlissner
Работает ли это решение со следующей конфигурацией веб-сервера? Back-end: 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»?) о вещах, которые помогают предотвратить это (пользовательские разрешения и т. д.), но просто помните об этой очевидной, но распространенной угрозе безопасности. По сути, это просто подмножество проверяющего ввода: если вы передаете имя файла в представление, проверьте имя файла в этом представлении!
rocketmonkeys
9
Очень простое исправление этой проблемой безопасности:filepath = filepath.replace('..', '').replace('/', '')
duality_
7
Если вы используете таблицу для хранения информации о файле, в том числе о том, какие пользователи должны иметь возможность загрузить ее, то все, что вам нужно отправить, - это первичный ключ, а не имя файла, и приложение решит, что делать.
Эдвард Ньюэлл
30

Для очень простого, но неэффективного или масштабируемого решения вы можете просто использовать встроенное представление django 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))
Cory
источник
Также очень полезно для предоставления запасного варианта для тестирования в Windows.
Амир Али Акбари
Я делаю отдельный проект django, предназначенный для работы вроде настольного клиента, и это сработало отлично. Спасибо!
daigorocub
1
почему это не эффективно?
моргание
2
@zinking, потому что файлы, как правило, должны обслуживаться через что-то вроде apache, а не через процесс django
Кори
1
О каких недостатках производительности мы говорим здесь? Загружаются ли файлы в оперативную память или что-то в этом роде, если они обслуживаются через django? Почему django не может работать с такой же эффективностью, как nginx?
Гершом
27

S.Lott предлагает «хорошее» / простое решение, а elo80ka предлагает «лучшее» / эффективное решение. Вот «лучшее» / среднее решение - без настройки сервера, но более эффективное для больших файлов, чем наивное исправление:

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

По сути, Django по-прежнему обрабатывает файл, но не загружает все это в память сразу. Это позволяет вашему серверу (медленно) обслуживать большой файл без увеличения использования памяти.

Опять же, S.Lott X-SendFile все еще лучше для больших файлов. Но если вы не можете или не хотите беспокоиться об этом, тогда это промежуточное решение повысит вашу эффективность без хлопот.

rocketmonkeys
источник
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

Теперь вы можете хранить файлы в личном каталоге (не внутри / media или / public_html) и предоставлять их через django определенным пользователям или при определенных обстоятельствах.
Надеюсь, поможет.

Спасибо @ elo80ka, @ S.Lott и @Rocketmonkeys за ответы, они получили идеальное решение, объединив все из них =)

Salvatorelab
источник
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, так что вот более полный пример (для будущего меня). Предполагается, что имя FileFieldimported_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

btimby
источник
Патч теперь свернут в библиотеку corer.
mlissner
7

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

«Абстракция для выгрузки файлов на веб-сервер (например, Apache с mod_xsendfile) после проверки прав доступа Django и т. Д.»

Роберто Росарио
источник
2
В то время (1 год назад) мой личный форк содержал резервный файл, не относящийся к Apache, который исходный репозиторий еще не включал.
Роберто Росарио
Почему вы удалили ссылку?
kiok46
@ kiok46 Конфликт с политиками Github. Отредактировано, чтобы указать на канонический адрес.
Роберто Росарио
6

Вы должны использовать apis sendfile, предоставляемые популярными серверами, такими как apacheили nginx в производстве. Много лет я использовал sendfile api этих серверов для защиты файлов. Затем для этой цели было создано простое приложение django на основе промежуточного программного обеспечения, подходящее как для разработки, так и для производства. Вы можете получить доступ к исходному коду здесь .
ОБНОВЛЕНИЕ: в новой версии 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 /files/только для внутреннего доступа, если вы используете вышеуказанную конфигурацию, вы можете установить X-File как,

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

Делая это с конфигурацией nginx, файл будет защищен, а также вы можете управлять файлом из django views

Ренджит Саячан
источник
2

Django рекомендует использовать другой сервер для обслуживания статического носителя (другой сервер, работающий на той же машине, подойдет ). Они рекомендуют использовать такие серверы как lighttp .

Это очень просто настроить. Тем не мение. если 'somefile.txt' генерируется по запросу (контент динамический), вы можете захотеть, чтобы django его обслуживал.

Django Docs - Статические файлы

kjfletch
источник
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)
avlnx
источник
1
request.user.is_authenticated - это метод, а не атрибут. (not request.user.is_anonymous ()) - это то же самое, что и request.user.is_authenticated (), потому что is_authenticated является инверсией is_anonymous.
взрывается
@explodes Еще хуже, этот код прямо из документов django-private-files...
Армандо Перес Маркиз
0

Я сталкивался с той же проблемой более одного раза и реализовал ее с помощью модуля xsendfile и декораторов представления auth django-filelibrary . Не стесняйтесь использовать его как вдохновение для вашего собственного решения.

https://github.com/danielsokolowski/django-filelibrary

Даниэль Соколовский
источник
0

Я сделал проект по этому вопросу. Вы можете посмотреть на мой репозиторий github:

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

Этот модуль предоставляет простой способ обслуживания файлов для загрузки в django rest framework с использованием модуля Apache Xsendfile. Он также имеет дополнительную функцию загрузки загрузок только пользователям, принадлежащим к определенной группе.

nicks_4317
источник