Наличие в Django загружаемых файлов

207

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

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

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

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

  • 2
    Почему вы просто не используете Apache для этого? Apache обслуживает статический контент быстрее и проще, чем когда-либо мог Django.
  • 16
    Я не использую Apache, потому что я не хочу, чтобы файлы были доступны без разрешений, основанных на Django.
Показать ещё 2 комментария
Теги:
download

14 ответов

161
Лучший ответ

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

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 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.

  • 6
    Если ваше имя файла или path_to_file содержит не-ascii символы, такие как «ä» или «ö», smart_str не работает smart_str образом, так как X-Sendfile модуля Apache не может декодировать строку, закодированную в smart_str. Так, например, файл «Örinää.mp3» не может быть предоставлен. И если не указывать smart_str, сам Django выдает ошибку кодирования ascii, потому что все заголовки перед отправкой кодируются в формат ascii. Единственный способ, который я знаю, чтобы обойти эту проблему - это сократить имена файлов X-sendfile до тех, которые состоят только из ascii.
  • 3
    Чтобы быть более понятным: S.Lott имеет простой пример, просто обслуживая файлы прямо из django, никаких других настроек не требуется. У elo80ka есть более эффективный пример, где веб-сервер обрабатывает статические файлы, а django не обязан. Последний имеет лучшую производительность, но может потребовать больше настроек. У обоих есть свои места.
Показать ещё 5 комментариев
75

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

  • 44
    По сути, это правильный (простой) ответ, но одно предостережение - передача имени файла в качестве параметра означает, что пользователь может потенциально загрузить любой файл (т.е. что делать, если вы передадите «f = / etc / passwd»?). о вещах, которые помогают предотвратить это (пользовательские разрешения и т. д.), но следует помнить об этой очевидной, но распространенной угрозе безопасности. По сути, это просто подмножество проверяющего ввода: если вы передаете имя файла в представление, проверьте имя файла в этом представлении!
  • 7
    Очень простое решение этой проблемы безопасности: filepath = filepath.replace('..', '').replace('/', '')
Показать ещё 1 комментарий
25

Для очень простого , но не эффективного или масштабируемого решения вы можете просто использовать встроенное представление 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))
  • 0
    Также очень полезно для предоставления запасного варианта для тестирования в Windows.
  • 0
    Я делаю автономный проект Django, предназначенный для работы вроде настольного клиента, и это сработало отлично. Спасибо!
Показать ещё 5 комментариев
24

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

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

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

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

  • 2
    Этот фрагмент не очень хорош. Этот фрагмент основан на недокументированном закрытом модуле django.core.servers.httpbase , который имеет большой предупреждающий знак в верхней части кода « НЕ ИСПОЛЬЗОВАТЬ ДЛЯ ПРОИЗВОДСТВА ИСПОЛЬЗОВАНИЯ !!! », который находится в файле с момента его появления. впервые создан . В любом случае, функциональность FileWrapper , на FileWrapper опирается этот фрагмент, была удалена в django 1.9.
13

Tried @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 nor/public_html) и подвергать их через django определенным пользователям или при определенных обстоятельствах.
Надеюсь, поможет.

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

  • 1
    Спасибо, это было именно то, что я искал!
  • 1
    Добавьте двойные кавычки вокруг имени файла filename="%s" в заголовке Content-Disposition, чтобы избежать проблем с пробелами в именах файлов. Ссылки: Имена файлов с пробелами усекаются при загрузке. Как кодировать параметр имени файла заголовка Content-Disposition в HTTP?
Показать ещё 3 комментария
12

Было упомянуто выше, что метод mod_xsendfile не позволяет содержать символы не ASCII в именах файлов.

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

X-SendFile-Encoding: url

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

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

  • 0
    Патч теперь свернут в библиотеку corer.
9

Просто отметив 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'),
...
  • 1
    Большое спасибо! Это самое простое решение для загрузки бинарных файлов, и оно работает.
6

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

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

  • 2
    В то время (1 год назад) мой личный форк содержал резервный файл, не относящийся к Apache, который исходный репозиторий еще не включал.
  • 0
    Почему вы удалили ссылку?
Показать ещё 1 комментарий
5

Вы должны использовать sendfile apis, заданный популярными серверами, например apache или nginx в производстве. Много лет я использовал sendfile api этих серверов для защиты файлов. Затем было создано простое промежуточное программное обеспечение django для этой цели, подходящее как для разработки, так и для производства. Вы можете получить доступ к исходному коду здесь. UPDATE: в новой версии python поставщик использует django FileResponse, если он доступен, а также добавляет поддержку многих реализаций сервера от lighthttp, caddy to 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 impemented таким образом, чтобы ваш код нуждался только в минимальной модификации.

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

Чтобы защитить файл от прямого доступа, вы можете установить конфигурацию как

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

Здесь nginx устанавливает адрес url /files/ только для доступа к внутреннему, если вы используете вышеуказанную конфигурацию, вы можете установить X-File как,

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

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

1
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 
1

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

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

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

0

Предоставление защищенного доступа к статической папке html с помощью https://github.com/johnsensible/django-sendfile: https://gist.github.com/iutinvg/9907731

0

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

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню