Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@
/lib
/build
.Python
.tox
.idea
77 changes: 77 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
sudo: false

language: python

cache: pip

python:
- 2.7
- 3.3
- 3.4
- 3.5
- 3.6

env:
- DJANGO=1.4
- DJANGO=1.5
- DJANGO=1.6
- DJANGO=1.7
- DJANGO=1.8
- DJANGO=1.9
- DJANGO=1.10
- DJANGO=1.11

install:
- pip install -q tox

script:
- tox -e py${TRAVIS_PYTHON_VERSION//./}-django${DJANGO//./}

matrix:
exclude:
- python: 3.3
env: DJANGO=1.3
- python: 3.3
env: DJANGO=1.4
- python: 3.3
env: DJANGO=1.5
- python: 3.3
env: DJANGO=1.6
- python: 3.3
env: DJANGO=1.7
- python: 3.3
env: DJANGO=1.9
- python: 3.3
env: DJANGO=1.10
- python: 3.3
env: DJANGO=1.11
- python: 3.4
env: DJANGO=1.3
- python: 3.4
env: DJANGO=1.4
- python: 3.4
env: DJANGO=1.5
- python: 3.4
env: DJANGO=1.6
- python: 3.4
env: DJANGO=1.7
- python: 3.5
env: DJANGO=1.3
- python: 3.5
env: DJANGO=1.4
- python: 3.5
env: DJANGO=1.5
- python: 3.5
env: DJANGO=1.6
- python: 3.5
env: DJANGO=1.7
- python: 3.6
env: DJANGO=1.3
- python: 3.6
env: DJANGO=1.4
- python: 3.6
env: DJANGO=1.5
- python: 3.6
env: DJANGO=1.6
- python: 3.6
env: DJANGO=1.7
3 changes: 3 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
Django Sendfile
===============

.. image:: https://img.shields.io/travis/mpasternak/django-sendfile.svg
:target: https://travis-ci.org/mpasternak/django-sendfile

This is a wrapper around web-server specific methods for sending files to web clients. This is useful when Django needs to check permissions associated files, but does not want to serve the actual bytes of the file itself. i.e. as serving large files is not what Django is made for.

Note this should not be used for regular file serving (e.g. css etc), only for cases where you need Django to do some work before serving the actual file.
Expand Down
3 changes: 3 additions & 0 deletions examples/protected_downloads/download/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ def __unicode__(self):
@models.permalink
def get_absolute_url(self):
return ('download', [self.pk], {})

class Meta:
app_label = 'download'
22 changes: 16 additions & 6 deletions examples/protected_downloads/download/urls.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
from django.conf.urls.defaults import *

from .views import download, download_list

urlpatterns = patterns('',
url(r'^$', download_list),
url(r'(?P<download_id>\d+)/$', download, name='download'),
)
import django

if django.VERSION >= (1,9,0):
from django.conf.urls import *
urlpatterns = [
url(r'^$', download_list),
url(r'(?P<download_id>\d+)/$', download, name='download'),
]
else:
from django.conf.urls.defaults import *

urlpatterns = patterns(
'',
url(r'^$', download_list),
url(r'(?P<download_id>\d+)/$', download, name='download'),
)
4 changes: 2 additions & 2 deletions examples/protected_downloads/download/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
from django.db.models import Q
from django.template import RequestContext

from sendfile import sendfile
from sendfile.core import sendfile

from .models import Download
from download.models import Download


def download(request, download_id):
Expand Down
18 changes: 9 additions & 9 deletions examples/protected_downloads/manage.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#!/usr/bin/env python

from __future__ import absolute_import
# from __future__ import absolute_import

from django.core.management import execute_manager
try:
from . import settings # Assumed to be in the same directory.
except ImportError:
import sys
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
sys.exit(1)
#!/usr/bin/env python
import os
import sys

sys.path = [ os.path.join(os.path.dirname(__file__), '..', '..'), ] + sys.path

if __name__ == "__main__":
execute_manager(settings)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
21 changes: 16 additions & 5 deletions examples/protected_downloads/urls.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
from django.conf.urls.defaults import *


from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
(r'^', include('protected_downloads.download.urls')),
(r'^admin/', include(admin.site.urls)),
)
import django
if django.VERSION >= (1,9,0):
from django.conf.urls import *
urlpatterns = [
url(r'^', include('protected_downloads.download.urls')),
url(r'^admin/', include(admin.site.urls)),
]

else:
from django.conf.urls.defaults import *
urlpatterns = patterns(
'',
(r'^', include('protected_downloads.download.urls')),
(r'^admin/', include(admin.site.urls)),
)
92 changes: 0 additions & 92 deletions sendfile/__init__.py
Original file line number Diff line number Diff line change
@@ -1,95 +1,3 @@
VERSION = (0, 3, 11)
__version__ = '.'.join(map(str, VERSION))

import os.path
from mimetypes import guess_type
import unicodedata


def _lazy_load(fn):
_cached = []
def _decorated():
if not _cached:
_cached.append(fn())
return _cached[0]
def clear():
while _cached:
_cached.pop()
_decorated.clear = clear
return _decorated


@_lazy_load
def _get_sendfile():
try:
from importlib import import_module
except ImportError:
from django.utils.importlib import import_module
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured

backend = getattr(settings, 'SENDFILE_BACKEND', None)
if not backend:
raise ImproperlyConfigured('You must specify a value for SENDFILE_BACKEND')
module = import_module(backend)
return module.sendfile



def sendfile(request, filename, attachment=False, attachment_filename=None, mimetype=None, encoding=None):
'''
create a response to send file using backend configured in SENDFILE_BACKEND

If attachment is True the content-disposition header will be set.
This will typically prompt the user to download the file, rather
than view it. The content-disposition filename depends on the
value of attachment_filename:

None (default): Same as filename
False: No content-disposition filename
String: Value used as filename

If no mimetype or encoding are specified, then they will be guessed via the
filename (using the standard python mimetypes module)
'''
_sendfile = _get_sendfile()

if not os.path.exists(filename):
from django.http import Http404
raise Http404('"%s" does not exist' % filename)

guessed_mimetype, guessed_encoding = guess_type(filename)
if mimetype is None:
if guessed_mimetype:
mimetype = guessed_mimetype
else:
mimetype = 'application/octet-stream'

response = _sendfile(request, filename, mimetype=mimetype)
if attachment:
if attachment_filename is None:
attachment_filename = os.path.basename(filename)
parts = ['attachment']
if attachment_filename:
try:
from django.utils.encoding import force_text
except ImportError:
# Django 1.3
from django.utils.encoding import force_unicode as force_text
attachment_filename = force_text(attachment_filename)
ascii_filename = unicodedata.normalize('NFKD', attachment_filename).encode('ascii','ignore')
parts.append('filename="%s"' % ascii_filename)
if ascii_filename != attachment_filename:
from django.utils.http import urlquote
quoted_filename = urlquote(attachment_filename)
parts.append('filename*=UTF-8\'\'%s' % quoted_filename)
response['Content-Disposition'] = '; '.join(parts)

response['Content-length'] = os.path.getsize(filename)
response['Content-Type'] = mimetype
if not encoding:
encoding = guessed_encoding
if encoding:
response['Content-Encoding'] = encoding

return response
6 changes: 5 additions & 1 deletion sendfile/backends/xsendfile.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
from django.http import HttpResponse
import six


def sendfile(request, filename, **kwargs):
response = HttpResponse()
response['X-Sendfile'] = unicode(filename).encode('utf-8')
if six.PY2:
response['X-Sendfile'] = unicode(filename).encode('utf-8')
else:
response['X-Sendfile'] = filename # .encode('utf-8')

return response
Loading