Merge pull request #188 from tari/django-4.0-compat

Update compatibility for Django 4.0
This commit is contained in:
Rémy HUBSCHER 2022-01-05 13:38:34 +01:00 committed by GitHub
commit 7e9f81d758
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 67 additions and 49 deletions

View file

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

View file

@ -53,8 +53,6 @@ INSTALLED_APPS = (
"django.contrib.sites",
"django.contrib.messages",
"django.contrib.staticfiles",
# Stuff that must be at the end.
"django_nose",
)
@ -111,15 +109,6 @@ DOWNLOADVIEW_RULES += [
# 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 = [

View file

@ -21,6 +21,6 @@ setup(
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"]},
)

View file

@ -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):
@ -32,7 +32,7 @@ class TextIteratorIO(io.TextIOBase):
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

View file

@ -4,7 +4,7 @@ Download middlewares capture :py:class:`django_downloadview.DownloadResponse`
responses and may replace them with optimized download responses.
"""
import collections
import collections.abc
import copy
import os
@ -14,14 +14,6 @@ from django.core.exceptions import ImproperlyConfigured
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.
@ -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.
@ -87,11 +85,8 @@ class RealDownloadMiddleware(BaseDownloadMiddleware):
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:
@ -107,27 +102,35 @@ class DownloadDispatcherMiddleware(BaseDownloadMiddleware):
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
@ -160,7 +163,7 @@ class SmartDownloadMiddleware(BaseDownloadMiddleware):
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
@ -172,10 +175,6 @@ class SmartDownloadMiddleware(BaseDownloadMiddleware):
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."""
@ -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)

View file

@ -23,6 +23,7 @@ setup(
'Framework :: Django :: 2.2',
'Framework :: Django :: 3.1',
'Framework :: Django :: 3.2',
'Framework :: Django :: 4.0',
],
keywords=" ".join(
[

23
tox.ini
View file

@ -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
@ -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]
@ -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
@ -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/