mirror of
https://github.com/jazzband/django-downloadview.git
synced 2026-03-16 22:40:25 +00:00
Merge pull request #56 from benoitbryon/25-single-backend
Closes #25 - Introduced SmartDownloadMiddleware.
This commit is contained in:
commit
c32313ca8e
9 changed files with 175 additions and 107 deletions
23
CHANGELOG
23
CHANGELOG
|
|
@ -21,10 +21,20 @@ documentation.
|
|||
- Bugfix #49 - Fixed ``content`` assertion in ``assert_download_response``:
|
||||
checks only response's ``streaming_content`` attribute.
|
||||
|
||||
- Feature #50 - Introduced
|
||||
:class:`~django_downloadview.middlewares.DownloadDispatcherMiddleware` that
|
||||
iterates over a list of configurable download middlewares. Allows to plug
|
||||
several download middlewares with different configurations.
|
||||
- Feature #50 - Introduced ``django_downloadview.DownloadDispatcherMiddleware``
|
||||
that iterates over a list of configurable download middlewares. Allows to
|
||||
plug several download middlewares with different configurations.
|
||||
|
||||
This middleware is mostly dedicated to internal usage. It is used by
|
||||
``SmartDownloadMiddleware`` described below.
|
||||
|
||||
- Feature #42 - Documentation shows how to stream generated content (yield).
|
||||
Introduced ``django_downloadview.StringIteratorIO``.
|
||||
|
||||
- Refactoring #51 - Dropped support of Python 2.6
|
||||
|
||||
- Refactoring #25 - Introduced ``django_downloadview.SmartDownloadMiddleware``
|
||||
which allows to setup multiple optimization rules for one backend.
|
||||
|
||||
Deprecates the following settings related to previous single-and-global
|
||||
middleware:
|
||||
|
|
@ -35,11 +45,6 @@ documentation.
|
|||
* ``NGINX_DOWNLOAD_MIDDLEWARE_WITH_BUFFERING``
|
||||
* ``NGINX_DOWNLOAD_MIDDLEWARE_LIMIT_RATE``
|
||||
|
||||
- Feature #42 - Documentation shows how to stream generated content (yield).
|
||||
Introduced ``django_downloadview.StringIteratorIO``.
|
||||
|
||||
- Refactoring #51 - Dropped support of Python 2.6
|
||||
|
||||
- Refactoring #52 - ObjectDownloadView now inherits from SingleObjectMixin and
|
||||
BaseDownloadView (was DownloadMixin and BaseDetailView).
|
||||
Simplified DownloadMixin.render_to_response() signature.
|
||||
|
|
|
|||
|
|
@ -65,18 +65,19 @@ MIDDLEWARE_CLASSES = [
|
|||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django_downloadview.DownloadDispatcherMiddleware'
|
||||
'django_downloadview.SmartDownloadMiddleware'
|
||||
]
|
||||
|
||||
|
||||
# Uncomment the following lines to enable global Nginx optimizations.
|
||||
DOWNLOADVIEW_MIDDLEWARES = (
|
||||
('default', 'django_downloadview.nginx.XAccelRedirectMiddleware',
|
||||
{'source_url': '/media/nginx/',
|
||||
'destination_url': '/nginx-optimized-by-middleware/'}),
|
||||
)
|
||||
# Specific configuration for django_downloadview.SmartDownloadMiddleware.
|
||||
DOWNLOADVIEW_BACKEND = 'django_downloadview.nginx.XAccelRedirectMiddleware'
|
||||
DOWNLOADVIEW_RULES = [
|
||||
{'source_url': '/media/nginx/',
|
||||
'destination_url': '/nginx-optimized-by-middleware/'},
|
||||
]
|
||||
|
||||
|
||||
# Test/development settings.
|
||||
DEBUG = True
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ from django_downloadview.files import (StorageFile, # NoQA
|
|||
from django_downloadview.response import (DownloadResponse, # NoQA
|
||||
ProxiedDownloadResponse)
|
||||
from django_downloadview.middlewares import (BaseDownloadMiddleware, # NoQA
|
||||
DownloadDispatcherMiddleware)
|
||||
DownloadDispatcherMiddleware,
|
||||
SmartDownloadMiddleware)
|
||||
from django_downloadview.views import (PathDownloadView, # NoQA
|
||||
ObjectDownloadView,
|
||||
StorageDownloadView,
|
||||
|
|
|
|||
|
|
@ -5,11 +5,19 @@ Download middlewares capture :py:class:`django_downloadview.DownloadResponse`
|
|||
responses and may replace them with optimized download responses.
|
||||
|
||||
"""
|
||||
import collections
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
from django_downloadview.response import DownloadResponse
|
||||
from django_downloadview.utils import import_member
|
||||
|
||||
|
||||
#: Sentinel value to detect whether configuration is to be loaded from Django
|
||||
#: settings or not.
|
||||
AUTO_CONFIGURE = object()
|
||||
|
||||
|
||||
def is_download_response(response):
|
||||
|
|
@ -70,28 +78,20 @@ class RealDownloadMiddleware(BaseDownloadMiddleware):
|
|||
|
||||
|
||||
class DownloadDispatcherMiddleware(BaseDownloadMiddleware):
|
||||
"""Download middleware that dispatches job to several middlewares.
|
||||
|
||||
The list of Children middlewares is read in `DOWNLOADVIEW_MIDDLEWARES`
|
||||
setting.
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
"Download middleware that dispatches job to several middleware instances."
|
||||
def __init__(self, middlewares=AUTO_CONFIGURE):
|
||||
#: List of children middlewares.
|
||||
self.middlewares = []
|
||||
self.load_middlewares_from_settings()
|
||||
self.middlewares = middlewares
|
||||
if self.middlewares is AUTO_CONFIGURE:
|
||||
self.auto_configure_middlewares()
|
||||
|
||||
def load_middlewares_from_settings(self):
|
||||
def auto_configure_middlewares(self):
|
||||
"""Populate :attr:`middlewares` from
|
||||
``settings.DOWNLOADVIEW_MIDDLEWARES``."""
|
||||
for (key, import_string, kwargs) in getattr(settings,
|
||||
'DOWNLOADVIEW_MIDDLEWARES',
|
||||
[]):
|
||||
if ':' in import_string:
|
||||
module_string, attr_string = import_string.split(':', 1)
|
||||
else:
|
||||
module_string, attr_string = import_string.rsplit('.', 1)
|
||||
module = __import__(module_string, globals(), locals(),
|
||||
[attr_string], -1)
|
||||
factory = getattr(module, attr_string)
|
||||
factory = import_member(import_string)
|
||||
middleware = factory(**kwargs)
|
||||
self.middlewares.append((key, middleware))
|
||||
|
||||
|
|
@ -102,6 +102,57 @@ class DownloadDispatcherMiddleware(BaseDownloadMiddleware):
|
|||
return response
|
||||
|
||||
|
||||
class SmartDownloadMiddleware(BaseDownloadMiddleware):
|
||||
"""Easy to configure download middleware."""
|
||||
def __init__(self,
|
||||
backend_factory=AUTO_CONFIGURE,
|
||||
backend_options=AUTO_CONFIGURE):
|
||||
"""Constructor."""
|
||||
#: :class:`DownloadDispatcher` instance that can hold multiple
|
||||
#: backend instances.
|
||||
self.dispatcher = DownloadDispatcherMiddleware(middlewares=[])
|
||||
#: Callable (typically a class) to instanciate backend (typically a
|
||||
#: :class:`DownloadMiddleware` subclass).
|
||||
self.backend_factory = backend_factory
|
||||
if self.backend_factory is AUTO_CONFIGURE:
|
||||
self.auto_configure_backend_factory()
|
||||
#: List of positional or keyword arguments to instanciate backend
|
||||
#: instances.
|
||||
self.backend_options = backend_options
|
||||
if self.backend_options is AUTO_CONFIGURE:
|
||||
self.auto_configure_backend_options()
|
||||
|
||||
def auto_configure_backend_factory(self):
|
||||
"Assign :attr:`backend_factory` from ``settings.DOWNLOADVIEW_BACKEND``"
|
||||
try:
|
||||
self.backend_factory = import_member(settings.DOWNLOADVIEW_BACKEND)
|
||||
except AttributeError:
|
||||
raise ImproperlyConfigured('SmartDownloadMiddleware requires '
|
||||
'settings.DOWNLOADVIEW_BACKEND')
|
||||
|
||||
def auto_configure_backend_options(self):
|
||||
"""Populate :attr:`dispatcher` using :attr:`factory` and
|
||||
``settings.DOWNLOADVIEW_RULES``."""
|
||||
try:
|
||||
options_list = settings.DOWNLOADVIEW_RULES
|
||||
except AttributeError:
|
||||
raise ImproperlyConfigured('SmartDownloadMiddleware requires '
|
||||
'settings.DOWNLOADVIEW_RULES')
|
||||
for key, options in enumerate(options_list):
|
||||
args = []
|
||||
kwargs = {}
|
||||
if isinstance(options, collections.Mapping): # Using kwargs.
|
||||
kwargs = options
|
||||
else:
|
||||
args = options
|
||||
middleware_instance = self.backend_factory(*args, **kwargs)
|
||||
self.dispatcher.middlewares.append((key, middleware_instance))
|
||||
|
||||
def process_download_response(self, request, response):
|
||||
"""Use :attr:`dispatcher` to process download response."""
|
||||
return self.dispatcher.process_download_response(request, response)
|
||||
|
||||
|
||||
class NoRedirectionMatch(Exception):
|
||||
"""Response object does not match redirection rules."""
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
.. warning::
|
||||
|
||||
These settings are deprecated since version 1.3. You can now provide custom
|
||||
configuration via `DOWNLOADVIEW_MIDDLEWARES` setting. See :doc:`/settings`
|
||||
configuration via `DOWNLOADVIEW_BACKEND` setting. See :doc:`/settings`
|
||||
for details.
|
||||
|
||||
"""
|
||||
|
|
@ -22,7 +22,12 @@ if middleware in settings.MIDDLEWARE_CLASSES:
|
|||
'{middleware} middleware has been renamed as of django-downloadview '
|
||||
'version 1.3. You may use '
|
||||
'"django_downloadview.nginx.SingleXAccelRedirectMiddleware" instead, '
|
||||
'or upgrade to "django_downloadview.DownloadDispatcherMiddleware". ')
|
||||
'or upgrade to "django_downloadview.SmartDownloadDispatcher". ')
|
||||
|
||||
|
||||
deprecated_msg = 'settings.{deprecated} is deprecated. You should combine ' \
|
||||
'"django_downloadview.SmartDownloadDispatcher" with ' \
|
||||
'with DOWNLOADVIEW_BACKEND and DOWNLOADVIEW_RULES instead.'
|
||||
|
||||
|
||||
#: Default value for X-Accel-Buffering header.
|
||||
|
|
@ -39,10 +44,7 @@ if middleware in settings.MIDDLEWARE_CLASSES:
|
|||
DEFAULT_WITH_BUFFERING = None
|
||||
setting_name = 'NGINX_DOWNLOAD_MIDDLEWARE_WITH_BUFFERING'
|
||||
if hasattr(settings, setting_name):
|
||||
warnings.warn('settings.{deprecated} is deprecated. You should combine '
|
||||
'"django_downloadview.DownloadDispatcherMiddleware" with '
|
||||
'with DOWNLOADVIEW_MIDDLEWARES instead.'.format(
|
||||
deprecated=setting_name),
|
||||
warnings.warn(deprecated_msg.format(deprecated=setting_name),
|
||||
DeprecationWarning)
|
||||
if not hasattr(settings, setting_name):
|
||||
setattr(settings, setting_name, DEFAULT_WITH_BUFFERING)
|
||||
|
|
@ -61,10 +63,7 @@ if not hasattr(settings, setting_name):
|
|||
DEFAULT_LIMIT_RATE = None
|
||||
setting_name = 'NGINX_DOWNLOAD_MIDDLEWARE_LIMIT_RATE'
|
||||
if hasattr(settings, setting_name):
|
||||
warnings.warn('settings.{deprecated} is deprecated. You should combine '
|
||||
'"django_downloadview.DownloadDispatcherMiddleware" with '
|
||||
'with DOWNLOADVIEW_MIDDLEWARES instead.'.format(
|
||||
deprecated=setting_name),
|
||||
warnings.warn(deprecated_msg.format(deprecated=setting_name),
|
||||
DeprecationWarning)
|
||||
if not hasattr(settings, setting_name):
|
||||
setattr(settings, setting_name, DEFAULT_LIMIT_RATE)
|
||||
|
|
@ -83,10 +82,7 @@ if not hasattr(settings, setting_name):
|
|||
DEFAULT_EXPIRES = None
|
||||
setting_name = 'NGINX_DOWNLOAD_MIDDLEWARE_EXPIRES'
|
||||
if hasattr(settings, setting_name):
|
||||
warnings.warn('settings.{deprecated} is deprecated. You should combine '
|
||||
'"django_downloadview.DownloadDispatcherMiddleware" with '
|
||||
'with DOWNLOADVIEW_MIDDLEWARES instead.'.format(
|
||||
deprecated=setting_name),
|
||||
warnings.warn(deprecated_msg.format(deprecated=setting_name),
|
||||
DeprecationWarning)
|
||||
if not hasattr(settings, setting_name):
|
||||
setattr(settings, setting_name, DEFAULT_EXPIRES)
|
||||
|
|
@ -96,18 +92,12 @@ if not hasattr(settings, setting_name):
|
|||
DEFAULT_SOURCE_DIR = settings.MEDIA_ROOT
|
||||
setting_name = 'NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_ROOT'
|
||||
if hasattr(settings, setting_name):
|
||||
warnings.warn('settings.{deprecated} is deprecated. You should combine '
|
||||
'"django_downloadview.DownloadDispatcherMiddleware" with '
|
||||
'with DOWNLOADVIEW_MIDDLEWARES instead.'.format(
|
||||
deprecated=setting_name),
|
||||
warnings.warn(deprecated_msg.format(deprecated=setting_name),
|
||||
DeprecationWarning)
|
||||
DEFAULT_SOURCE_DIR = settings.NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_ROOT
|
||||
setting_name = 'NGINX_DOWNLOAD_MIDDLEWARE_SOURCE_DIR'
|
||||
if hasattr(settings, setting_name):
|
||||
warnings.warn('settings.{deprecated} is deprecated. You should combine '
|
||||
'"django_downloadview.DownloadDispatcherMiddleware" with '
|
||||
'with DOWNLOADVIEW_MIDDLEWARES instead.'.format(
|
||||
deprecated=setting_name),
|
||||
warnings.warn(deprecated_msg.format(deprecated=setting_name),
|
||||
DeprecationWarning)
|
||||
if not hasattr(settings, setting_name):
|
||||
setattr(settings, setting_name, DEFAULT_SOURCE_DIR)
|
||||
|
|
@ -117,10 +107,7 @@ if not hasattr(settings, setting_name):
|
|||
DEFAULT_SOURCE_URL = settings.MEDIA_URL
|
||||
setting_name = 'NGINX_DOWNLOAD_MIDDLEWARE_SOURCE_URL'
|
||||
if hasattr(settings, setting_name):
|
||||
warnings.warn('settings.{deprecated} is deprecated. You should combine '
|
||||
'"django_downloadview.DownloadDispatcherMiddleware" with '
|
||||
'with DOWNLOADVIEW_MIDDLEWARES instead.'.format(
|
||||
deprecated=setting_name),
|
||||
warnings.warn(deprecated_msg.format(deprecated=setting_name),
|
||||
DeprecationWarning)
|
||||
if not hasattr(settings, setting_name):
|
||||
setattr(settings, setting_name, DEFAULT_SOURCE_URL)
|
||||
|
|
@ -130,18 +117,12 @@ if not hasattr(settings, setting_name):
|
|||
DEFAULT_DESTINATION_URL = None
|
||||
setting_name = 'NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_URL'
|
||||
if hasattr(settings, setting_name):
|
||||
warnings.warn('settings.{deprecated} is deprecated. You should combine '
|
||||
'"django_downloadview.DownloadDispatcherMiddleware" with '
|
||||
'with DOWNLOADVIEW_MIDDLEWARES instead.'.format(
|
||||
deprecated=setting_name),
|
||||
warnings.warn(deprecated_msg.format(deprecated=setting_name),
|
||||
DeprecationWarning)
|
||||
DEFAULT_SOURCE_DIR = settings.NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_URL
|
||||
setting_name = 'NGINX_DOWNLOAD_MIDDLEWARE_DESTINATION_URL'
|
||||
if hasattr(settings, setting_name):
|
||||
warnings.warn('settings.{deprecated} is deprecated. You should combine '
|
||||
'"django_downloadview.DownloadDispatcherMiddleware" with '
|
||||
'with DOWNLOADVIEW_MIDDLEWARES instead.'.format(
|
||||
deprecated=setting_name),
|
||||
warnings.warn(deprecated_msg.format(deprecated=setting_name),
|
||||
DeprecationWarning)
|
||||
if not hasattr(settings, setting_name):
|
||||
setattr(settings, setting_name, DEFAULT_DESTINATION_URL)
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ class APITestCase(unittest.TestCase):
|
|||
# Middlewares:
|
||||
'BaseDownloadMiddleware',
|
||||
'DownloadDispatcherMiddleware',
|
||||
'SmartDownloadMiddleware',
|
||||
# Testing:
|
||||
'assert_download_response',
|
||||
'setup_view',
|
||||
|
|
|
|||
|
|
@ -31,3 +31,17 @@ def url_basename(url, content_type):
|
|||
|
||||
"""
|
||||
return url.split('/')[-1]
|
||||
|
||||
|
||||
def import_member(import_string):
|
||||
"""Import one member of Python module by path.
|
||||
|
||||
>>> import os.path
|
||||
>>> imported = import_member('os.path.supports_unicode_filenames')
|
||||
>>> os.path.supports_unicode_filenames is imported
|
||||
True
|
||||
|
||||
"""
|
||||
module_name, factory_name = str(import_string).rsplit('.', 1)
|
||||
module = __import__(module_name, globals(), locals(), [factory_name], -1)
|
||||
return getattr(module, factory_name)
|
||||
|
|
|
|||
|
|
@ -36,31 +36,31 @@ implemented by storage. Let's setup an optimization rule based on that URL.
|
|||
Setup XAccelRedirect middlewares
|
||||
********************************
|
||||
|
||||
Make sure ``django_downloadview.DownloadDispatcherMiddleware`` is in
|
||||
Make sure ``django_downloadview.SmartDownloadMiddleware`` is in
|
||||
``MIDDLEWARE_CLASSES`` of your `Django` settings.
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: /../demo/demoproject/settings.py
|
||||
:language: python
|
||||
:lines: 61-68
|
||||
:lines: 62-69
|
||||
|
||||
Then register as many
|
||||
:class:`~django_downloadview.nginx.middlewares.XAccelRedirectMiddleware`
|
||||
instances as you wish in ``DOWNLOADVIEW_MIDDLEWARES``.
|
||||
Then set ``django_downloadview.nginx.XAccelRedirectMiddleware`` as
|
||||
``DOWNLOADVIEW_BACKEND``:
|
||||
|
||||
.. literalinclude:: /../demo/demoproject/settings.py
|
||||
:language: python
|
||||
:lines: 72-76
|
||||
:lines: 73
|
||||
|
||||
The first item is an identifier.
|
||||
Then register as many ``DOWNLOADVIEW_RULES`` as you wish:
|
||||
|
||||
The second item is the import path of
|
||||
:class:`~django_downloadview.nginx.middlewares.XAccelRedirectMiddleware` class.
|
||||
.. literalinclude:: /../demo/demoproject/settings.py
|
||||
:language: python
|
||||
:lines: 74-77
|
||||
|
||||
The third item is a dictionary of keyword arguments passed to the middleware
|
||||
factory. In the example above, we capture responses by ``source_url`` and
|
||||
convert them to internal redirects to ``destination_url``.
|
||||
Each item in ``DOWNLOADVIEW_RULES`` is a dictionary of keyword arguments passed
|
||||
to the middleware factory. In the example above, we capture responses by
|
||||
``source_url`` and convert them to internal redirects to ``destination_url``.
|
||||
|
||||
.. autoclass:: django_downloadview.nginx.middlewares.XAccelRedirectMiddleware
|
||||
:members:
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ MIDDLEWARE_CLASSES
|
|||
******************
|
||||
|
||||
If you plan to setup reverse-proxy optimizations, add
|
||||
``django_downloadview.DownloadDispatcherMiddleware`` to ``MIDDLEWARE_CLASSES``.
|
||||
``django_downloadview.SmartDownloadMiddleware`` to ``MIDDLEWARE_CLASSES``.
|
||||
It is a response middleware. Move it after middlewares that compute the
|
||||
response content such as gzip middleware.
|
||||
|
||||
|
|
@ -26,40 +26,54 @@ Example:
|
|||
|
||||
.. literalinclude:: /../demo/demoproject/settings.py
|
||||
:language: python
|
||||
:lines: 61-68
|
||||
:lines: 62-69
|
||||
|
||||
|
||||
************************
|
||||
DOWNLOADVIEW_MIDDLEWARES
|
||||
************************
|
||||
********************
|
||||
DOWNLOADVIEW_BACKEND
|
||||
********************
|
||||
|
||||
:default: []
|
||||
|
||||
If you plan to setup reverse-proxy :doc:`optimizations </optimizations/index>`,
|
||||
setup ``DOWNLOADVIEW_MIDDLEWARES`` value. This setting is used by
|
||||
:py:class:`~django_downloadview.middlewares.DownloadDispatcherMiddleware`.
|
||||
It is the list of handlers that will be given the opportunity to capture
|
||||
download responses and convert them to internal redirects for use with
|
||||
reverse-proxies.
|
||||
|
||||
The list expects items ``(id, path, options)`` such as:
|
||||
|
||||
* ``id`` is an identifier
|
||||
* ``path`` is the import path of some download middleware factory (typically a
|
||||
class).
|
||||
* ``options`` is a dictionary of keyword arguments passed to the middleware
|
||||
factory.
|
||||
This setting is used by
|
||||
:class:`~django_downloadview.middlewares.SmartDownloadMiddleware`.
|
||||
It is the import string of a callable (typically a class) of an optimization
|
||||
backend (typically a :class:`~django_downloadview.BaseDownloadMiddleware`
|
||||
subclass).
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: /../demo/demoproject/settings.py
|
||||
:language: python
|
||||
:lines: 72-76
|
||||
:lines: 73
|
||||
|
||||
See :doc:`/optimizations/index` for details about middlewares and their
|
||||
options.
|
||||
See :doc:`/optimizations/index` for a list of available backends (middlewares).
|
||||
|
||||
.. note::
|
||||
When ``django_downloadview.SmartDownloadMiddleware`` is in your
|
||||
``MIDDLEWARE_CLASSES``, this setting must be explicitely configured (no default
|
||||
value). Else, you can ignore this setting.
|
||||
|
||||
You can register several middlewares. It allows you to setup several
|
||||
conversion rules with distinct source/destination patterns.
|
||||
|
||||
******************
|
||||
DOWNLOADVIEW_RULES
|
||||
******************
|
||||
|
||||
This setting is used by
|
||||
:class:`~django_downloadview.middlewares.SmartDownloadMiddleware`.
|
||||
It is a list of positional arguments or keyword arguments that will be used to
|
||||
instanciate class mentioned as ``DOWNLOADVIEW_BACKEND``.
|
||||
|
||||
Each item in the list can be either a list of positional arguments, or a
|
||||
dictionary of keyword arguments. One item cannot contain both positional and
|
||||
keyword arguments.
|
||||
|
||||
Here is an example containing one rule using keyword arguments:
|
||||
|
||||
.. literalinclude:: /../demo/demoproject/settings.py
|
||||
:language: python
|
||||
:lines: 74-77
|
||||
|
||||
See :doc:`/optimizations/index` for details about builtin backends
|
||||
(middlewares) and their options.
|
||||
|
||||
When ``django_downloadview.SmartDownloadMiddleware`` is in your
|
||||
``MIDDLEWARE_CLASSES``, this setting must be explicitely configured (no default
|
||||
value). Else, you can ignore this setting.
|
||||
|
|
|
|||
Loading…
Reference in a new issue