Наличие в Django загружаемых файлов - PullRequest
227 голосов
/ 21 июля 2009

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

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

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

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

Ответы [ 14 ]

184 голосов
/ 21 июля 2009

Для «лучшего из двух миров» вы можете объединить решение 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.

EDIT:

mimetype заменяется на content_type для django 1.7

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

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

85 голосов
/ 21 июля 2009

«Загрузка» - это просто изменение заголовка 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 строк кода.

27 голосов
/ 19 января 2014

Для очень простого , но не эффективного или масштабируемого решения, вы можете просто использовать встроенное представление 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))
27 голосов
/ 30 декабря 2010

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

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

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

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

15 голосов
/ 24 сентября 2013

Пробовал решение @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 за ответы, которые получили идеальное решение, объединяющее их всех =)

14 голосов
/ 20 марта 2017

Просто упомяну объект FileResponse , доступный в Django 1.10

Edit: Просто наткнулся на мой собственный ответ при поиске простого способа потоковой передачи файлов через 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'),
...
13 голосов
/ 14 марта 2011

Выше было упомянуто, что метод mod_xsendfile не допускает использование символов не-ASCII в именах файлов.

По этой причине у меня есть патч для mod_xsendfile, который позволит отправлять любой файл, если имя закодировано в URL, и дополнительный заголовок:

X-SendFile-Encoding: url

Также отправлено.

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

7 голосов
/ 14 апреля 2011

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

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

6 голосов
/ 12 декабря 2016

Вы должны использовать 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 голосов
/ 16 декабря 2013
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 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...