Skip to content

Commit

Permalink
Docs! (#33)
Browse files Browse the repository at this point in the history
* Remove most of README and move into docs
* Fixup docstrings
* Rename django_sendfile.sendfile module to utils

Something that I missed when I renamed the package: the was a name
conflict between the imported sendfile function and the module it came
from as the former was imported directly into __init__.py

* Test against isort as well as flake8
* Mention security stuff
  • Loading branch information
moggers87 authored Feb 11, 2020
1 parent ae2040b commit 383be13
Show file tree
Hide file tree
Showing 31 changed files with 578 additions and 224 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
/include
/lib
/build
/docs/_build
.Python
.tox
/examples/protected_downloads/htmlcov
Expand Down
16 changes: 10 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ python:
- "3.8"
- pypy3.6-7.1.1

matrix:
include:
- python: "3.7"
env: TOX_ENV=docs
install: pip install tox
script: tox -e $TOX_ENV
- python: "3.7"
env: TOX_ENV=lint,isort
install: pip install tox
script: tox -e $TOX_ENV

install: pip install tox-travis codecov

Expand All @@ -17,9 +27,3 @@ script: tox
after_success:
- cd examples/protected_downloads
- codecov

matrix:
include:
- python: "3.6"
env: TOX_ENV=flake8
script: tox -e $TOX_ENV
194 changes: 9 additions & 185 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ Django Sendfile 2
.. image:: https://travis-ci.org/moggers87/django-sendfile2.svg?branch=master
:target: https://travis-ci.org/moggers87/django-sendfile2

- Download: https://pypi.org/project/django-sendfile2/
- Source: https://github.com/moggers87/django-sendfile2
.. inclusion-marker-do-not-remove-start
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
Expand All @@ -16,69 +15,19 @@ 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.

The interface is a single function `sendfile(request, filename,
attachment=False, attachment_filename=None)`, which returns a HTTPResponse
object.

::

from django_sendfile import sendfile
# send myfile.pdf to user
return sendfile(request, '/home/john/myfile.pdf')

# send myfile.pdf as an attachment (with name myfile.pdf)
return sendfile(request, '/home/john/myfile.pdf', attachment=True)
# send myfile.pdf as an attachment with a different name
return sendfile(request, '/home/john/myfile.pdf', attachment=True, attachment_filename='full-name.pdf')



Backends are specified using the setting `SENDFILE_BACKEND`. Currently
available backends are:

* `django_sendfile.backends.development` - for use with Django development server
only. DO NOT USE IN PRODUCTION
* `django_sendfile.backends.simple` - "simple" backend that uses Django file objects
to attempt to stream files from disk (note middleware may cause files to be
loaded fully into memory)
* `django_sendfile.backends.xsendfile` - sets X-Sendfile header (as used by
mod_xsendfile/Apache and Lighthttpd)
* `django_sendfile.backends.mod_wsgi` - sets Location with 200 code to trigger
internal redirect (daemon mode mod_wsgi only - see below)
* `django_sendfile.backends.nginx` - sets X-Accel-Redirect header to trigger internal
redirect to file

If you want to write your own backend simply create a module with a `sendfile`
function matching:

::

def sendfile(request, filename):
'''Return HttpResponse object for serving file'''


Then specify the full path to the module in `SENDFILE_BACKEND`. You only need
to implement the sending of the file. Adding the content-disposition headers
etc is done elsewhere.

Supported Django Versions
=========================

Django 2.1, 2.2, and 3.0 are currently supported by this library.
- Download: https://pypi.org/project/django-sendfile2/
- Source: https://github.com/moggers87/django-sendfile2
- Documentation: https://django-sendfile2.readthedocs.io/

Supported Python Versions
=========================

Python 3.5, 3.6. 3.7, and 3.8 are currently supported by this library.

Installation
============

::
Supported Django Versions
=========================

pip install django-sendfile2
Django 2.1, 2.2, and 3.0 are currently supported by this library.

Fork
====
Expand All @@ -88,136 +37,11 @@ This project is a fork of `django-sendfile
appears mostly dead and has a number of outstanding bugs (especially with
Python 3).

Development backend
===================

The Development backend is only meant for use while writing code. It uses
Django's static file serving code to do the job, which is only meant for
development. It reads the whole file into memory and the sends it down the
wire - not good for big files, but OK when you are just testing things out.

It will work with the Django dev server and anywhere else you can run Django.

Simple backend
==============

This backend is one step up from the development backend. It uses Django's
`django.core.files.base.File` class to try and stream files from disk. However
some middleware (e.g. GzipMiddleware) that rewrites content will causes the
entire file to be loaded into memory. So only use this backend if you are not
using middleware that rewrites content or you only have very small files.


xsendfile backend
=================

Install either mod_xsendfile_ in Apache_ or use Lighthttpd_. You may need to
configure mod_xsendfile_, but that should be as simple as:

::

XSendFile On

In your virtualhost file/conf file.


mod_wsgi backend
================

The mod_wsgi backend will only work when using mod_wsgi in daemon mode, not in
embedded mode. It requires a bit more work to get it to do the same job as
xsendfile though. However some may find it easier to setup, as they don't need
to compile and install mod_xsendfile_.

Firstly there are two more Django settings:

* `SENDFILE_ROOT` - this is a directoy where all files that will be used with
sendfile must be located
* `SENDFILE_URL` - internal URL prefix for all files served via sendfile

These settings are needed as this backend makes mod_wsgi_ send an internal
redirect, so we have to convert a file path into a URL. This means that the
files are visible via Apache_ by default too. So we need to get Apache_ to
hide those files from anything that's not an internal redirect. To so this we
can use some mod_rewrite_ magic along these lines:

::

RewriteEngine On
# see if we're on an internal redirect or not
RewriteCond %{THE_REQUEST} ^[\S]+\ /private/
RewriteRule ^/private/ - [F]

Alias /private/ /home/john/Development/myapp/private/
<Directory /home/john/Development/myapp/private/>
Order deny,allow
Allow from all
</Directory>


In this case I have also set:

::

SENDFILE_ROOT = '/home/john/Development/myapp/private/'
SENDFILE_URL = '/private'


All files are stored in a folder called 'private'. We forbid access to this
folder (`RewriteRule ^/private/ - [F]`) if someone tries to access it directly
(`RewriteCond %{THE_REQUEST} ^[\S]+\ /private/`) by checking the original
request (`THE_REQUEST`).

Allegedly `IS_SUBREQ` can be used to `perform the same job
<http://www.mail-archive.com/[email protected]/msg96718.html>`_,
but I was unable to get this working.


Nginx backend
=============

As with the mod_wsgi backend you need to set two extra settings:

* `SENDFILE_ROOT` - this is a directory where all files that will be used with
sendfile must be located
* `SENDFILE_URL` - internal URL prefix for all files served via sendfile

You then need to configure Nginx to only allow internal access to the files you
wish to serve. More details on this `are here
<https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile/>`_.

For example though, if I use the Django settings:

::

SENDFILE_ROOT = '/home/john/Development/django-sendfile/examples/protected_downloads/protected'
SENDFILE_URL = '/protected'

Then the matching location block in nginx.conf would be:

::

location /protected/ {
internal;
root /home/john/Development/django-sendfile/examples/protected_downloads;
}

You need to pay attention to whether you have trailing slashes or not on the
SENDFILE_URL and root values, otherwise you may not get the right URL being
sent to Nginx and you may get 404s. You should be able to see what file Nginx
is trying to load in the error.log if this happens. From there it should be
fairly easy to work out what the right settings are.

Funding
=======

If you have found django-sendfile2 to be useful and would like to see its continued
development, please consider `buying me a coffee
<https://ko-fi.com/moggers87>`__.

.. _mod_xsendfile: https://tn123.org/mod_xsendfile/
.. _Apache: http://httpd.apache.org/
.. _Lighthttpd: http://www.lighttpd.net/
.. _mod_wsgi: http://www.modwsgi.org/
.. _mod_rewrite: http://httpd.apache.org/docs/current/mod/mod_rewrite.html
<https://ko-fi.com/moggers87>`_.

.. inclusion-marker-do-not-remove-end
2 changes: 1 addition & 1 deletion django_sendfile/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .sendfile import sendfile # noqa
from ._version import get_versions
from .utils import sendfile # noqa

__version__ = get_versions()['version']
del get_versions
Expand Down
13 changes: 7 additions & 6 deletions django_sendfile/backends/development.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@


def sendfile(request, filename, **kwargs):
'''
Send file using django dev static file server.
"""
Send file using Django dev static file server.
DO NOT USE IN PRODUCTION
this is only to be used when developing and is provided
for convenience only
'''
.. warning::
Do not use in production. This is only to be used when developing and
is provided for convenience only
"""
dirname = os.path.dirname(filename)
basename = os.path.basename(filename)
return serve(request, basename, dirname)
2 changes: 1 addition & 1 deletion django_sendfile/backends/simple.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from email.utils import parsedate_tz, mktime_tz
from email.utils import mktime_tz, parsedate_tz
import os
import re
import stat
Expand Down
5 changes: 3 additions & 2 deletions django_sendfile/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
import shutil

from django.conf import settings
from django.http import HttpResponse, Http404, HttpRequest
from django.http import Http404, HttpRequest, HttpResponse
from django.test import TestCase
from django.utils.encoding import smart_str

from django_sendfile.sendfile import sendfile as real_sendfile, _get_sendfile
from .utils import _get_sendfile
from .utils import sendfile as real_sendfile


def sendfile(request, filename, **kwargs):
Expand Down
24 changes: 12 additions & 12 deletions django_sendfile/sendfile.py → django_sendfile/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,25 @@ def _get_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
"""
Create a response to send file using backend configured in ``SENDFILE_BACKEND``
Filename is the absolute path to the file to send.
``filename`` is the absolute path to the file to send.
If attachment is True the content-disposition header will be set accordingly.
If ``attachment`` is ``True`` the ``Content-Disposition`` header will be set accordingly.
This will typically prompt the user to download the file, rather
than view it. But even if False, the user may still be prompted, depending
than view it. But even if ``False``, the user may still be prompted, depending
on the browser capabilities and configuration.
The content-disposition filename depends on the value of attachment_filename:
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
``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)
'''
If neither ``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):
Expand Down
23 changes: 23 additions & 0 deletions docs/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?= -W
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build

# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

apidocs:
cd ..; sphinx-apidoc -f -e -o docs/ django_sendfile "*tests*"

.PHONY: help apidocs Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
Loading

0 comments on commit 383be13

Please sign in to comment.