Skip to content

Commit

Permalink
Merge pull request #188 from tari/django-4.0-compat
Browse files Browse the repository at this point in the history
Update compatibility for Django 4.0
  • Loading branch information
Natim committed Jan 5, 2022
2 parents 0578729 + e9fbb74 commit 7e9f81d
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 49 deletions.
20 changes: 17 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,24 @@ jobs:
runs-on: ubuntu-latest
strategy:
fail-fast: false
max-parallel: 5
matrix:
python-version: ['3.6', '3.7', '3.8', '3.9']
django-version: ['2.2', '3.1', '3.2', 'main']
python-version: ['3.6', '3.7', '3.8', '3.9', '3.10']
django-version: ['2.2', '3.1', '3.2', '4.0', 'main']
exclude:
# Django prior to 3.2 does not support Python 3.10
- django-version: '2.2'
python-version: '3.10'
- django-version: '3.1'
python-version: '3.10'
# Django after 3.2 dropped support for Python prior to 3.8
- django-version: '4.0'
python-version: '3.6'
- django-version: 'main'
python-version: '3.6'
- django-version: '4.0'
python-version: '3.7'
- django-version: 'main'
python-version: '3.7'

steps:
- uses: actions/checkout@v2
Expand Down
11 changes: 0 additions & 11 deletions demo/demoproject/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@
"django.contrib.sites",
"django.contrib.messages",
"django.contrib.staticfiles",
# Stuff that must be at the end.
"django_nose",
)


Expand Down Expand Up @@ -111,15 +109,6 @@

# Test/development settings.
DEBUG = True
TEST_RUNNER = "django_nose.NoseTestSuiteRunner"
NOSE_ARGS = [
"--verbosity=2",
"--no-path-adjustment",
"--nocapture",
"--all-modules",
"--with-coverage",
"--with-doctest",
]


TEMPLATES = [
Expand Down
2 changes: 1 addition & 1 deletion demo/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
packages=["demoproject"],
include_package_data=True,
zip_safe=False,
install_requires=["django-downloadview", "django-nose"],
install_requires=["django-downloadview", "pytest-django"],
entry_points={"console_scripts": ["demo = demoproject.manage:main"]},
)
4 changes: 2 additions & 2 deletions django_downloadview/io.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Low-level IO operations, for use with file wrappers."""
import io

from django.utils.encoding import force_bytes, force_text
from django.utils.encoding import force_bytes, force_str


class TextIteratorIO(io.TextIOBase):
Expand Down Expand Up @@ -32,7 +32,7 @@ def _read1(self, n=None):
break
else:
# Make sure we handle text.
self._left = force_text(self._left)
self._left = force_str(self._left)
ret = self._left[:n]
self._left = self._left[len(ret) :]
return ret
Expand Down
55 changes: 27 additions & 28 deletions django_downloadview/middlewares.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
responses and may replace them with optimized download responses.
"""
import collections
import collections.abc
import copy
import os

Expand All @@ -14,14 +14,6 @@
from django_downloadview.response import DownloadResponse
from django_downloadview.utils import import_member

try:
from django.utils.deprecation import MiddlewareMixin
except ImportError:

class MiddlewareMixin(object):
def __init__(self, get_response=None):
super(MiddlewareMixin, self).__init__()


#: Sentinel value to detect whether configuration is to be loaded from Django
#: settings or not.
Expand All @@ -38,12 +30,18 @@ def is_download_response(response):
return isinstance(response, DownloadResponse)


class BaseDownloadMiddleware(MiddlewareMixin):
class BaseDownloadMiddleware:
"""Base (abstract) Django middleware that handles download responses.
Subclasses **must** implement :py:meth:`process_download_response` method.
"""
def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
response = self.get_response(request)
return self.process_response(request, response)

def is_download_response(self, response):
"""Return True if ``response`` can be considered as a file download.
Expand Down Expand Up @@ -87,11 +85,8 @@ def is_download_response(self, response):
return False


class DownloadDispatcherMiddleware(BaseDownloadMiddleware):
"Download middleware that dispatches job to several middleware instances."

def __init__(self, get_response=None, middlewares=AUTO_CONFIGURE):
super(DownloadDispatcherMiddleware, self).__init__(get_response)
class DownloadDispatcher:
def __init__(self, middlewares=AUTO_CONFIGURE):
#: List of children middlewares.
self.middlewares = middlewares
if self.middlewares is AUTO_CONFIGURE:
Expand All @@ -107,27 +102,35 @@ def auto_configure_middlewares(self):
middleware = factory(**kwargs)
self.middlewares.append((key, middleware))

def process_download_response(self, request, response):
def dispatch(self, request, response):
"""Dispatches job to children middlewares."""
for (key, middleware) in self.middlewares:
response = middleware.process_response(request, response)
return response


class SmartDownloadMiddleware(BaseDownloadMiddleware):
class DownloadDispatcherMiddleware(BaseDownloadMiddleware):
"Download middleware that dispatches job to several middleware instances."

def __init__(self, get_response, middlewares=AUTO_CONFIGURE):
super(DownloadDispatcherMiddleware, self).__init__(get_response)
self.dispatcher = DownloadDispatcher(middlewares)

def process_download_response(self, request, response):
return self.dispatcher.dispatch(request, response)


class SmartDownloadMiddleware(DownloadDispatcherMiddleware):
"""Easy to configure download middleware."""

def __init__(
self,
get_response=None,
get_response,
backend_factory=AUTO_CONFIGURE,
backend_options=AUTO_CONFIGURE,
):
"""Constructor."""
super(SmartDownloadMiddleware, self).__init__(get_response)
#: :class:`DownloadDispatcher` instance that can hold multiple
#: backend instances.
self.dispatcher = DownloadDispatcherMiddleware(middlewares=[])
super(SmartDownloadMiddleware, self).__init__(get_response, middlewares=[])
#: Callable (typically a class) to instantiate backend (typically a
#: :class:`DownloadMiddleware` subclass).
self.backend_factory = backend_factory
Expand Down Expand Up @@ -160,7 +163,7 @@ def auto_configure_backend_options(self):
for key, options in enumerate(options_list):
args = []
kwargs = {}
if isinstance(options, collections.Mapping): # Using kwargs.
if isinstance(options, collections.abc.Mapping): # Using kwargs.
kwargs = options
else:
args = options
Expand All @@ -172,10 +175,6 @@ def auto_configure_backend_options(self):
middleware_instance = 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."""
Expand All @@ -185,7 +184,7 @@ class ProxiedDownloadMiddleware(RealDownloadMiddleware):
"""Base class for middlewares that use optimizations of reverse proxies."""

def __init__(
self, get_response=None, source_dir=None, source_url=None, destination_url=None
self, get_response, source_dir=None, source_url=None, destination_url=None
):
"""Constructor."""
super(ProxiedDownloadMiddleware, self).__init__(get_response)
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
'Framework :: Django :: 2.2',
'Framework :: Django :: 3.1',
'Framework :: Django :: 3.2',
'Framework :: Django :: 4.0',
],
keywords=" ".join(
[
Expand Down
23 changes: 19 additions & 4 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[tox]
envlist =
py{36,37,38,39}-dj{22,31,32}
py{38,39}-djmain
py{36,37,38,39,310}-dj{22,31,32}
py{38,39,310}-dj{40,main}
lint
sphinx
readme
Expand All @@ -12,12 +12,14 @@ python =
3.7: py37
3.8: py38, lint, sphinx, readme
3.9: py39
3.10: py310

[gh-actions:env]
DJANGO =
2.2: dj22
3.1: dj31
3.2: dj32
4.0: dj40
main: djmain

[testenv]
Expand All @@ -26,12 +28,18 @@ deps =
dj22: Django>=2.2,<3.0
dj31: Django>=3.1,<3.2
dj32: Django>=3.2,<3.3
dj40: Django>=4.0,<4.1
djmain: https://github.com/django/django/archive/main.tar.gz
nose
pytest
pytest-cov
commands =
pip install -e .
pip install -e demo
python -Wd {envbindir}/demo test --cover-package=django_downloadview --cover-package=demoproject --cover-xml {posargs: tests demoproject}
# doctests
pytest --cov=django_downloadview --cov=demoproject {posargs}
# all other test cases
coverage run --append {envbindir}/demo test {posargs: tests demoproject}
coverage xml
pip freeze
ignore_outcome =
djmain: True
Expand Down Expand Up @@ -65,3 +73,10 @@ commands =
[flake8]
max-line-length = 88
ignore = E203, W503

[coverage:run]
source = django_downloadview,demo

[pytest]
DJANGO_SETTINGS_MODULE = demoproject.settings
addopts = --doctest-modules --ignore=docs/

0 comments on commit 7e9f81d

Please sign in to comment.