diff --git a/.coveragerc b/.coveragerc
deleted file mode 100644
index 9a717ec..0000000
--- a/.coveragerc
+++ /dev/null
@@ -1,14 +0,0 @@
-[run]
-branch = True
-include =
- django_bleach/*
-omit =
- .env/*
- */tests/*
- testproject/*
-[report]
- precision = 1
-[html]
- directory = ./htmlcov
-[xml]
- output = ./coverage.xml
diff --git a/.editorconfig b/.editorconfig
index ce420e5..43f572e 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -11,4 +11,10 @@ indent_style = space
indent_size = 4
[*.py]
-max_line_length = 120
+max_line_length = 80
+
+[*.toml]
+indent_size = 2
+
+[*.yml]
+indent_size = 2
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index bc6aaec..033674a 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -7,3 +7,11 @@ updates:
include: "scope"
schedule:
interval: weekly
+- package-ecosystem: pip
+ directory: "/"
+ schedule:
+ interval: weekly
+ groups:
+ python-packages:
+ patterns:
+ - "*"
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index d92db81..9572b06 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -16,7 +16,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v4
with:
- python-version: 3.9
+ python-version: 3.11
- run: python -m pip install sphinx
- name: Build docs
run: |
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 1a66f56..8a25f78 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -1,14 +1,14 @@
name: Lint
-on: [push, pull_request]
+on: [ push, pull_request ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
- flake8:
- name: flake8
+ ruff:
+ name: ruff
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -16,32 +16,13 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v4
with:
- python-version: 3.9
+ python-version: "3.11"
cache: 'pip'
- - name: Install flake8
- run: pip install --upgrade flake8
- - name: Run flake8
- uses: liskin/gh-problem-matcher-wrap@v2
- with:
- linters: flake8
- run: flake8
-
- isort:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v3
- - name: Set up Python
- uses: actions/setup-python@v4
- with:
- python-version: 3.9
- cache: 'pip'
- - run: python -m pip install isort
- - name: isort
- uses: liskin/gh-problem-matcher-wrap@v2
- with:
- linters: isort
- run: isort --check --diff django_bleach
+ - run: |
+ python -m pip install --upgrade pip
+ pip install ruff
+ - name: Run Ruff
+ run: ruff django_bleach
codespell:
runs-on: ubuntu-latest
@@ -51,7 +32,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v4
with:
- python-version: 3.9
+ python-version: 3.11
cache: 'pip'
- run: python -m pip install codespell
- name: codespell
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 8c0dd52..3214047 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -1,65 +1,110 @@
name: Tests
-on: [push, pull_request]
+on:
+ push:
+ branches:
+ - main
+ pull_request:
concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
+ group: ${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
unit-tests:
- runs-on: ${{ matrix.os }}
+ name: Python ${{ matrix.python-version }}
+ runs-on: ubuntu-22.04
+
strategy:
fail-fast: false
matrix:
- python-version: [3.8, 3.9, 3.10.1]
- django-version: [
- '3.2',
- '4.0',
- '4.1',
- ]
- os: [
- ubuntu-20.04,
- ]
+ python-version:
+ - 3.8
+ - 3.9
+ - '3.10'
+ - '3.11'
+ - '3.12'
steps:
- uses: actions/checkout@v3
- - name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v4
+
+ - uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
+ allow-prereleases: true
+ cache: pip
+ cache-dependency-path: 'requirements/*.txt'
+
- name: Install dependencies
run: |
- python -m pip install --upgrade pip
- pip install django==${{ matrix.django-version }} "bleach>=5,<6" coverage mock
- python setup.py install
- - name: Run coverage
- run: |
- coverage run --rcfile .coveragerc testproject/manage.py test --failfast django_bleach
+ python -m pip install --upgrade pip setuptools wheel
+ python -m pip install --upgrade 'tox>=4.0.0rc3'
+
+ - name: Run tox targets for ${{ matrix.python-version }}
+ run: tox run -f py$(echo ${{ matrix.python-version }} | tr -d .)
+
+ - name: Upload coverage data
+ uses: actions/upload-artifact@v3
+ with:
+ name: coverage-data
+ path: '.coverage.*'
- - name: Upload Coverage to Codecov
- uses: codecov/codecov-action@v3
+ coverage:
+ name: Coverage
+ runs-on: ubuntu-22.04
+ needs: unit-tests
+ steps:
+ - uses: actions/checkout@v3
+
+ - uses: actions/setup-python@v4
+ with:
+ python-version: '3.11'
+
+ - name: Install dependencies
+ run: python -m pip install --upgrade coverage[toml]
+
+ - name: Download data
+ uses: actions/download-artifact@v3
+ with:
+ name: coverage-data
+
+ - name: Fail if coverage is <100%
+ run: |
+ python -m coverage combine
+ python -m coverage html --skip-covered --skip-empty
+ python -m coverage report --fail-under=100
+
+ - name: Upload HTML report
+ if: ${{ failure() }}
+ uses: actions/upload-artifact@v3
+ with:
+ name: html-report
+ path: htmlcov
- pre-release:
- runs-on: ${{ matrix.os }}
+ unit-tests-future-versions:
+ # Runs for all Django/Python versions which are not yet supported
+ runs-on: ubuntu-22.04
strategy:
- fail-fast: true
+ fail-fast: false
matrix:
- os: [
- ubuntu-20.04,
+ python-version: ['3.11', '3.12']
+ django-version: [
+ 'https://github.com/django/django/archive/main.tar.gz'
]
steps:
- uses: actions/checkout@v3
- - name: Set up Python 3.9
+ - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
- python-version: 3.9
+ allow-prereleases: true
+ cache: pip
+ cache-dependency-path: 'requirements/*.txt'
+ python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
- python -m pip install --upgrade pip
- pip install --pre django "bleach>=5,<6" coverage mock
- python setup.py install
- - name: Run tests
- run: |
- python testproject/manage.py test django_bleach
+ python -m pip install --upgrade pip setuptools wheel
+ python -m pip install --upgrade 'tox>=4.0.0rc3'
+
+ - name: Run tox targets for ${{ matrix.python-version }}
+ run: tox run -f py$(echo ${{ matrix.python-version }} | tr -d .)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 397d99e..738ebb2 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -20,10 +20,11 @@ repos:
- id: django-upgrade
args: [--target-version, "3.2"]
- - repo: https://github.com/PyCQA/flake8
- rev: 6.1.0
+ - repo: https://github.com/charliermarsh/ruff-pre-commit
+ rev: "v0.0.278"
hooks:
- - id: flake8
+ - id: ruff
+ args: [--fix, --exit-non-zero-on-fix]
- repo: https://github.com/asottile/yesqa
rev: v1.5.0
@@ -36,7 +37,8 @@ repos:
- id: check-merge-conflict
- id: mixed-line-ending
- - repo: https://github.com/pycqa/isort
- rev: 5.12.0
+ - repo: https://github.com/ambv/black
+ rev: 23.7.0
hooks:
- - id: isort
+ - id: black
+ name: Black
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a80829a..f6d41ec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,9 +4,18 @@ Change Log
This document records all notable changes to [django-bleach](https://github.com/marksweb/django-bleach).
This project adheres to [Semantic Versioning](https://semver.org/).
-[unreleased](https://github.com/marksweb/django-bleach/compare/3.0.1...master) changes
+[unreleased](https://github.com/marksweb/django-bleach/compare/3.1.0...master) changes
-------------------------------------------------------------------------------------
+Version 3.1.0
+=============
+**05-08-2023**
+
+* Added support for django 4.2
+* Added support for python 3.12
+* Revamp package for easier maintenance
+
+
Version 3.0.1
=============
**11-10-2022**
diff --git a/django_bleach/__init__.py b/django_bleach/__init__.py
index 656582b..a3ce962 100644
--- a/django_bleach/__init__.py
+++ b/django_bleach/__init__.py
@@ -1,3 +1,3 @@
-__version__ = "3.0.1"
+__version__ = "3.1.0"
VERSION = __version__.split(".")
diff --git a/django_bleach/forms.py b/django_bleach/forms.py
index 45eb19b..68f0ae3 100644
--- a/django_bleach/forms.py
+++ b/django_bleach/forms.py
@@ -1,38 +1,37 @@
+from importlib import import_module
+
+import bleach
+from bleach.css_sanitizer import CSSSanitizer
from django import forms
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.utils.safestring import mark_safe
-import bleach
-import warnings
-from bleach.css_sanitizer import CSSSanitizer
-from importlib import import_module
-
from django_bleach.utils import get_bleach_default_options
def load_widget(path):
- """ Load custom widget for the form field """
+ """Load custom widget for the form field"""
i = path.rfind(".")
- module, attr = path[:i], path[i + 1:]
+ module, attr = path[:i], path[i + 1 :]
try:
mod = import_module(module)
except (ImportError, ValueError) as e:
error_message = "Error importing widget for BleachField %s: '%s'"
- raise ImproperlyConfigured(error_message % (path, e))
+ raise ImproperlyConfigured(error_message % (path, e)) from e
try:
cls = getattr(mod, attr)
- except AttributeError:
+ except AttributeError as e:
raise ImproperlyConfigured(
f"Module '{module}' does not define a '{attr}' widget"
- )
+ ) from e
return cls
def get_default_widget():
- """ Get the default widget or the widget defined in settings """
+ """Get the default widget or the widget defined in settings"""
default_widget = forms.Textarea
if hasattr(settings, "BLEACH_DEFAULT_WIDGET"):
default_widget = load_widget(settings.BLEACH_DEFAULT_WIDGET)
@@ -40,13 +39,22 @@ def get_default_widget():
class BleachField(forms.CharField):
- """ Bleach form field """
- empty_values = [None, "", [], (), {}]
+ """Bleach form field"""
- def __init__(self, allowed_tags=None, allowed_attributes=None,
- allowed_styles=None, allowed_protocols=None,
- strip_comments=None, strip_tags=None, css_sanitizer=None, *args, **kwargs):
+ empty_values = [None, "", [], (), {}]
+ def __init__(
+ self,
+ allowed_tags=None,
+ allowed_attributes=None,
+ allowed_styles=None,
+ allowed_protocols=None,
+ strip_comments=None,
+ strip_tags=None,
+ css_sanitizer=None,
+ *args,
+ **kwargs,
+ ):
self.widget = get_default_widget()
super().__init__(*args, **kwargs)
@@ -57,15 +65,8 @@ def __init__(self, allowed_tags=None, allowed_attributes=None,
self.bleach_options["tags"] = allowed_tags
if allowed_attributes is not None:
self.bleach_options["attributes"] = allowed_attributes
- if allowed_styles is not None:
- warnings.warn(
- "allowed_styles will be deprecated, use css_sanitizer instead",
- DeprecationWarning
- )
-
- if css_sanitizer:
- warnings.warn("allowed_styles argument is ignored since css_sanitizer is favoured over allowed_styles")
- self.bleach_options["css_sanitizer"] = CSSSanitizer(allowed_css_properties=allowed_styles)
+ if allowed_styles:
+ css_sanitizer = CSSSanitizer(allowed_css_properties=allowed_styles)
if css_sanitizer is not None:
self.bleach_options["css_sanitizer"] = css_sanitizer
if allowed_protocols is not None:
diff --git a/django_bleach/models.py b/django_bleach/models.py
index d7357ce..0ce17e4 100644
--- a/django_bleach/models.py
+++ b/django_bleach/models.py
@@ -1,19 +1,25 @@
-from django.db import models
-from django.utils.safestring import mark_safe
-
-import warnings
from bleach import clean
from bleach.css_sanitizer import CSSSanitizer
+from django.db import models
+from django.utils.safestring import mark_safe
from . import forms
from .utils import get_bleach_default_options
class BleachField(models.TextField):
- def __init__(self, allowed_tags=None, allowed_attributes=None,
- allowed_styles=None, allowed_protocols=None,
- strip_tags=None, strip_comments=None, css_sanitizer=None, *args, **kwargs):
-
+ def __init__(
+ self,
+ allowed_tags=None,
+ allowed_attributes=None,
+ allowed_styles=None,
+ allowed_protocols=None,
+ strip_tags=None,
+ strip_comments=None,
+ css_sanitizer=None,
+ *args,
+ **kwargs,
+ ):
super().__init__(*args, **kwargs)
self.bleach_kwargs = get_bleach_default_options()
@@ -23,13 +29,7 @@ def __init__(self, allowed_tags=None, allowed_attributes=None,
if allowed_attributes:
self.bleach_kwargs["attributes"] = allowed_attributes
if allowed_styles:
- warnings.warn(
- "allowed_styles will be deprecated, use css_sanitizer instead",
- DeprecationWarning
- )
- if css_sanitizer:
- warnings.warn("allowed_styles argument is ignored since css_sanitizer is favoured over allowed_styles")
- self.bleach_kwargs["css_sanitizer"] = CSSSanitizer(allowed_css_properties=allowed_styles)
+ css_sanitizer = CSSSanitizer(allowed_css_properties=allowed_styles)
if css_sanitizer:
self.bleach_kwargs["css_sanitizer"] = css_sanitizer
if allowed_protocols:
@@ -40,21 +40,23 @@ def __init__(self, allowed_tags=None, allowed_attributes=None,
self.bleach_kwargs["strip_comments"] = strip_comments
def formfield(self, form_class=forms.BleachField, **kwargs):
- """ Makes the field for a ModelForm """
+ """Makes the field for a ModelForm"""
# If field doesn't have any choices add kwargs expected by BleachField.
if not self.choices:
kwargs.setdefault("widget", forms.get_default_widget())
- kwargs.update({
- "max_length": self.max_length,
- "allowed_tags": self.bleach_kwargs.get("tags"),
- "allowed_attributes": self.bleach_kwargs.get("attributes"),
- "css_sanitizer": self.bleach_kwargs.get("css_sanitizer"),
- "allowed_protocols": self.bleach_kwargs.get("protocols"),
- "strip_tags": self.bleach_kwargs.get("strip"),
- "strip_comments": self.bleach_kwargs.get("strip_comments"),
- "required": not self.blank,
- })
+ kwargs.update(
+ {
+ "max_length": self.max_length,
+ "allowed_tags": self.bleach_kwargs.get("tags"),
+ "allowed_attributes": self.bleach_kwargs.get("attributes"),
+ "css_sanitizer": self.bleach_kwargs.get("css_sanitizer"),
+ "allowed_protocols": self.bleach_kwargs.get("protocols"),
+ "strip_tags": self.bleach_kwargs.get("strip"),
+ "strip_comments": self.bleach_kwargs.get("strip_comments"),
+ "required": not self.blank,
+ }
+ )
return super().formfield(form_class=form_class, **kwargs)
diff --git a/django_bleach/templatetags/bleach_tags.py b/django_bleach/templatetags/bleach_tags.py
index 67601b5..a8916a3 100644
--- a/django_bleach/templatetags/bleach_tags.py
+++ b/django_bleach/templatetags/bleach_tags.py
@@ -1,11 +1,9 @@
+import bleach
from django import template
from django.utils.safestring import mark_safe
-import bleach
-
from django_bleach.utils import get_bleach_default_options
-
register = template.Library()
diff --git a/django_bleach/tests/test_forms.py b/django_bleach/tests/test_forms.py
index 6a60dba..877aa22 100644
--- a/django_bleach/tests/test_forms.py
+++ b/django_bleach/tests/test_forms.py
@@ -1,9 +1,8 @@
+from bleach.css_sanitizer import CSSSanitizer
from django import forms
from django.test import TestCase, override_settings
from django.utils.safestring import SafeString
-from bleach.css_sanitizer import CSSSanitizer
-
from django_bleach.forms import BleachField
from testproject.constants import (
ALLOWED_ATTRIBUTES,
@@ -16,175 +15,157 @@
class TestBleachField(TestCase):
-
def test_empty(self):
- """ Test that the empty_value arg is returned for any input empty value """
- for requested_empty_value in ('', None):
+ """
+ Test that the empty_value arg is returned for any input empty value
+ """
+ for requested_empty_value in ("", None):
field = BleachField(empty_value=requested_empty_value)
for empty_value in field.empty_values:
- self.assertEqual(field.to_python(empty_value), requested_empty_value)
+ self.assertEqual(
+ field.to_python(empty_value), requested_empty_value
+ )
def test_return_type(self):
- """ Test bleached values are SafeString objects """
+ """Test bleached values are SafeString objects"""
field = BleachField()
self.assertIsInstance(field.to_python("some text"), SafeString)
- def test_deprecation_allowed_styles(self):
- with self.assertWarns(DeprecationWarning):
- BleachField(allowed_styles=ALLOWED_STYLES)
-
- def test_prefer_css_sanitizer_over_allowed_styles(self):
- with self.assertWarnsMessage(
- UserWarning,
- "allowed_styles argument is ignored since css_sanitizer is favoured over allowed_styles"
- ):
- field = BleachField(
- allowed_styles=['color', 'text-align'],
- css_sanitizer=CSSSanitizer(allowed_css_properties=ALLOWED_CSS_PROPERTIES)
- )
- self.assertIsInstance(field.bleach_options["css_sanitizer"], CSSSanitizer)
- self.assertEqual(field.bleach_options["css_sanitizer"].allowed_css_properties, ALLOWED_CSS_PROPERTIES)
-
def test_bleaching(self):
- """ Test values are bleached """
+ """Test values are bleached"""
test_data = {
- 'no_tags': "
Heading
",
- 'no_strip': "Heading
",
- 'bleach_strip': ""
- "",
- 'bleach_attrs': "google.com",
- 'bleach_css_sanitizer': "item",
+ "no_tags": "Heading
",
+ "no_strip": "Heading
",
+ "bleach_strip": ""
+ '',
+ "bleach_attrs": 'google.com',
+ "bleach_css_sanitizer": 'item',
}
form = BleachForm(data=test_data)
form.is_valid()
+ self.assertEqual(form.cleaned_data["no_tags"], "Heading")
self.assertEqual(
- form.cleaned_data['no_tags'], 'Heading'
- )
- self.assertEqual(
- form.cleaned_data['no_strip'],
- '<h1>Heading</h1>'
+ form.cleaned_data["no_strip"], "<h1>Heading</h1>"
)
self.assertEqual(
- form.cleaned_data['bleach_strip'],
- 'alert("Hello World")'
+ form.cleaned_data["bleach_strip"], 'alert("Hello World")'
)
self.assertEqual(
- form.cleaned_data['bleach_attrs'],
- 'google.com'
+ form.cleaned_data["bleach_attrs"],
+ 'google.com',
)
self.assertNotEqual(
- form.cleaned_data['bleach_css_sanitizer'],
- test_data['bleach_css_sanitizer']
+ form.cleaned_data["bleach_css_sanitizer"],
+ test_data["bleach_css_sanitizer"],
)
def test_tags(self):
- """ Test allowed tags are rendered"""
+ """Test allowed tags are rendered"""
test_data = {
- 'no_tags': "No tags here
",
- 'no_strip': "No tags here",
- 'bleach_strip': "",
- 'bleach_attrs': "google.com",
- 'bleach_css_sanitizer': "item"
+ "no_tags": "No tags here
",
+ "no_strip": "No tags here",
+ "bleach_strip": "",
+ "bleach_attrs": 'google.com',
+ "bleach_css_sanitizer": 'item',
}
form = BleachForm(data=test_data)
form.is_valid()
- self.assertEqual(form.cleaned_data['no_tags'], "No tags here")
- self.assertEqual(form.cleaned_data['no_strip'], "No tags here")
+ self.assertEqual(form.cleaned_data["no_tags"], "No tags here")
+ self.assertEqual(form.cleaned_data["no_strip"], "No tags here")
self.assertEqual(
- form.cleaned_data['bleach_strip'],
- test_data['bleach_strip']
+ form.cleaned_data["bleach_strip"], test_data["bleach_strip"]
)
self.assertEqual(
- form.cleaned_data['bleach_attrs'],
- test_data['bleach_attrs']
+ form.cleaned_data["bleach_attrs"], test_data["bleach_attrs"]
)
self.assertEqual(
- form.cleaned_data['bleach_css_sanitizer'],
- test_data['bleach_css_sanitizer']
+ form.cleaned_data["bleach_css_sanitizer"],
+ test_data["bleach_css_sanitizer"],
)
def test_attrs(self):
- """ Test allowed attributes are rendered """
- list_html = "" \
- "- one
" \
- "- two
" \
- "
"
+ """Test allowed attributes are rendered"""
+ list_html = (
+ '"
+ )
test_data = {
- 'no_strip': "",
- 'no_tags': list_html,
- 'bleach_strip': list_html,
- 'bleach_attrs': list_html,
- 'bleach_css_sanitizer': list_html
+ "no_strip": "",
+ "no_tags": list_html,
+ "bleach_strip": list_html,
+ "bleach_attrs": list_html,
+ "bleach_css_sanitizer": list_html,
}
form = BleachForm(data=test_data)
form.is_valid()
- self.assertEqual(form.cleaned_data['no_tags'], '\none\ntwo')
+ self.assertEqual(form.cleaned_data["no_tags"], "\none\ntwo")
self.assertEqual(
- form.cleaned_data['bleach_strip'],
- ''
+ form.cleaned_data["bleach_strip"],
+ "",
)
self.assertEqual(
- form.cleaned_data['bleach_attrs'],
- test_data['bleach_strip']
+ form.cleaned_data["bleach_attrs"], test_data["bleach_strip"]
)
self.assertEqual(
- form.cleaned_data['bleach_css_sanitizer'],
- ''
+ form.cleaned_data["bleach_css_sanitizer"],
+ "",
)
-@override_settings(BLEACH_DEFAULT_WIDGET='testproject.forms.CustomBleachWidget')
+@override_settings(
+ BLEACH_DEFAULT_WIDGET="testproject.forms.CustomBleachWidget"
+)
class TestCustomWidget(TestCase):
-
def setUp(self):
class CustomForm(forms.Form):
# Define form inside function with overridden settings so
# get_default_widget() sees the modified setting.
no_tags = BleachField(
- max_length=100,
- strip_tags=True,
- allowed_tags=[]
+ max_length=100, strip_tags=True, allowed_tags=[]
)
no_strip = BleachField(
- max_length=100,
- allowed_tags=None,
- allowed_attributes=None
+ max_length=100, allowed_tags=None, allowed_attributes=None
)
bleach_strip = BleachField(
max_length=100,
strip_comments=True,
strip_tags=True,
- allowed_tags=ALLOWED_TAGS
+ allowed_tags=ALLOWED_TAGS,
)
bleach_attrs = BleachField(
max_length=100,
strip_tags=False,
allowed_tags=ALLOWED_TAGS,
allowed_protocols=ALLOWED_PROTOCOLS,
- allowed_attributes=ALLOWED_ATTRIBUTES
+ allowed_attributes=ALLOWED_ATTRIBUTES,
)
bleach_styles = BleachField(
max_length=100,
strip_tags=False,
- allowed_attributes=['style'],
+ allowed_attributes=["style"],
allowed_tags=ALLOWED_TAGS,
- allowed_styles=ALLOWED_STYLES
+ allowed_styles=ALLOWED_STYLES,
)
bleach_css_sanitizer = BleachField(
max_length=100,
strip_tags=False,
- allowed_attributes=['style'],
+ allowed_attributes=["style"],
allowed_tags=ALLOWED_TAGS,
- css_sanitizer=CSSSanitizer(allowed_css_properties=ALLOWED_CSS_PROPERTIES)
+ css_sanitizer=CSSSanitizer(
+ allowed_css_properties=ALLOWED_CSS_PROPERTIES
+ ),
)
+
self.CustomForm = CustomForm
def test_custom_widget_type(self):
- """ Test widget class matches BLEACH_DEFAULT_WIDGET """
+ """Test widget class matches BLEACH_DEFAULT_WIDGET"""
for field in self.CustomForm().fields.values():
self.assertIsInstance(field.widget, CustomBleachWidget)
@@ -194,40 +175,35 @@ def test_custom_widget_bleaches_content(self):
widget
"""
test_data = {
- 'no_tags': 'Heading
',
- 'no_strip': 'Heading
',
- 'bleach_strip': ''
- '',
- 'bleach_attrs': (
+ "no_tags": "Heading
",
+ "no_strip": "Heading
",
+ "bleach_strip": ""
+ '',
+ "bleach_attrs": (
'google.com'
'google.com'
),
- 'bleach_styles': 'item',
- 'bleach_css_sanitizer': 'item'
+ "bleach_styles": 'item',
+ "bleach_css_sanitizer": 'item',
}
form = self.CustomForm(data=test_data)
form.is_valid()
+ self.assertEqual(form.cleaned_data["no_tags"], "Heading")
self.assertEqual(
- form.cleaned_data['no_tags'], 'Heading'
- )
- self.assertEqual(
- form.cleaned_data['no_strip'],
- '<h1>Heading</h1>'
+ form.cleaned_data["no_strip"], "<h1>Heading</h1>"
)
self.assertEqual(
- form.cleaned_data['bleach_strip'],
- 'alert("Hello World")'
+ form.cleaned_data["bleach_strip"], 'alert("Hello World")'
)
self.assertEqual(
- form.cleaned_data['bleach_attrs'],
- 'google.comgoogle.com'
+ form.cleaned_data["bleach_attrs"],
+ 'google.comgoogle.com',
)
self.assertNotEqual(
- form.cleaned_data['bleach_styles'],
- test_data['bleach_styles']
+ form.cleaned_data["bleach_styles"], test_data["bleach_styles"]
)
self.assertNotEqual(
- form.cleaned_data['bleach_css_sanitizer'],
- test_data['bleach_css_sanitizer']
+ form.cleaned_data["bleach_css_sanitizer"],
+ test_data["bleach_css_sanitizer"],
)
diff --git a/django_bleach/tests/test_modelformfield.py b/django_bleach/tests/test_modelformfield.py
index d0e909f..aa9d186 100644
--- a/django_bleach/tests/test_modelformfield.py
+++ b/django_bleach/tests/test_modelformfield.py
@@ -11,17 +11,16 @@
class BleachContentModelForm(forms.ModelForm):
class Meta:
model = BleachContent
- fields = '__all__'
+ fields = "__all__"
class TestModelFormField(TestCase):
-
def setUp(self):
model_form = BleachContentModelForm()
- self.form_field = model_form.fields['content']
- self.choice_form_field = model_form.fields['choice']
- self.blank_field_form_field = model_form.fields['blank_field']
- self.model_field = BleachContent()._meta.get_field('content')
+ self.form_field = model_form.fields["content"]
+ self.choice_form_field = model_form.fields["choice"]
+ self.blank_field_form_field = model_form.fields["blank_field"]
+ self.model_field = BleachContent()._meta.get_field("content")
def test_formfield_type(self):
"""
@@ -33,25 +32,26 @@ def test_default_widget_type(self):
"""
Widget class is Textarea when BLEACH_DEFAULT_WIDGET is not set.
"""
- form = forms.modelform_factory(Person, fields='__all__')()
+ form = forms.modelform_factory(Person, fields="__all__")()
self.assertIsInstance(
- form.fields['biography'].widget, forms.Textarea,
+ form.fields["biography"].widget,
+ forms.Textarea,
)
@override_settings(
- BLEACH_DEFAULT_WIDGET='testproject.forms.CustomBleachWidget'
+ BLEACH_DEFAULT_WIDGET="testproject.forms.CustomBleachWidget"
)
def test_custom_widget_type(self):
"""
Widget class matches BLEACH_DEFAULT_WIDGET setting.
"""
- form = forms.modelform_factory(Person, fields='__all__')()
+ form = forms.modelform_factory(Person, fields="__all__")()
self.assertIsInstance(
- form.fields['biography'].widget, CustomBleachWidget
+ form.fields["biography"].widget, CustomBleachWidget
)
@override_settings(
- BLEACH_DEFAULT_WIDGET='testproject.forms.CustomBleachWidget'
+ BLEACH_DEFAULT_WIDGET="testproject.forms.CustomBleachWidget"
)
def test_widget_override(self):
"""
@@ -59,16 +59,17 @@ def test_widget_override(self):
"""
form = forms.modelform_factory(
Person,
- fields='__all__',
+ fields="__all__",
widgets={"biography": CustomBleachWidget},
)()
self.assertIsInstance(
- form.fields['biography'].widget, CustomBleachWidget
+ form.fields["biography"].widget, CustomBleachWidget
)
def test_same_allowed_args(self):
"""
- Check model and form's allowed arguments (tags, attributes, ...) are same
+ Check model and form's allowed arguments (tags, attributes, ...)
+ are the same
"""
form_allowed_args: dict = self.form_field.bleach_options
model_allowed_args: dict = self.model_field.bleach_kwargs
@@ -102,17 +103,16 @@ class CustomBleachedFormField(bleach_forms.BleachField):
class OverriddenBleachContentModelForm(forms.ModelForm):
class Meta:
model = BleachContent
- fields = '__all__'
+ fields = "__all__"
field_classes = {
"content": CustomBleachedFormField,
}
class TestModelFormFieldOverrides(TestCase):
-
def setUp(self):
model_form = OverriddenBleachContentModelForm()
- self.form_field = model_form.fields['content']
+ self.form_field = model_form.fields["content"]
def test_formfield_type(self):
"""
diff --git a/django_bleach/tests/test_models.py b/django_bleach/tests/test_models.py
index 42969da..a830eca 100644
--- a/django_bleach/tests/test_models.py
+++ b/django_bleach/tests/test_models.py
@@ -1,32 +1,31 @@
+from bleach.css_sanitizer import CSSSanitizer
from django.db import models
from django.test import TestCase
from django.utils.safestring import SafeString
-from bleach.css_sanitizer import CSSSanitizer
-
from django_bleach.models import BleachField
from testproject.constants import (
ALLOWED_ATTRIBUTES,
ALLOWED_CSS_PROPERTIES,
ALLOWED_PROTOCOLS,
- ALLOWED_STYLES,
ALLOWED_TAGS,
)
class BleachContent(models.Model):
- """ Bleach test model"""
- CHOICES = (
- ('f', 'first choice'),
- ('s', 'second choice')
- )
+ """Bleach test model"""
+
+ CHOICES = (("f", "first choice"), ("s", "second choice"))
content = BleachField(
allowed_attributes=ALLOWED_ATTRIBUTES,
allowed_protocols=ALLOWED_PROTOCOLS,
- css_sanitizer=CSSSanitizer(allowed_css_properties=ALLOWED_CSS_PROPERTIES),
+ css_sanitizer=CSSSanitizer(
+ allowed_css_properties=ALLOWED_CSS_PROPERTIES
+ ),
allowed_tags=ALLOWED_TAGS,
+ allowed_styles=ALLOWED_CSS_PROPERTIES,
strip_comments=True,
- strip_tags=True
+ strip_tags=True,
)
choice = BleachField(choices=CHOICES)
blank_field = BleachField(blank=True)
@@ -34,47 +33,32 @@ class BleachContent(models.Model):
class TestBleachModelField(TestCase):
- """ Test model field """
+ """Test model field"""
def test_bleaching(self):
- """ Test values are bleached """
+ """Test values are bleached"""
test_data = {
- 'no_tags': "Heading
",
- 'no_strip': "Heading
",
- 'bleach_strip': """""",
- 'bleach_attrs': "google.com",
- 'bleach_css_sanitizer': "item",
- 'bleach_comment': "",
+ "no_tags": "Heading
",
+ "no_strip": "Heading
",
+ "bleach_strip": """""",
+ "bleach_attrs": 'google.com',
+ "bleach_css_sanitizer": 'item',
+ "bleach_comment": "",
}
expected_values = {
- 'no_tags': "Heading",
- 'no_strip': "Heading",
- 'bleach_strip': """alert("Hello World")""",
- 'bleach_attrs':
- "google.com",
- 'bleach_css_sanitizer': "item",
- 'bleach_comment': ""
+ "no_tags": "Heading",
+ "no_strip": "Heading",
+ "bleach_strip": """alert("Hello World")""",
+ "bleach_attrs": "google.com",
+ "bleach_css_sanitizer": 'item',
+ "bleach_comment": "",
}
for key, value in test_data.items():
obj = BleachContent.objects.create(content=value)
self.assertEqual(obj.content, expected_values[key])
- def test_deprecation_allowed_styles(self):
- with self.assertWarns(DeprecationWarning):
- BleachField(allowed_styles=ALLOWED_STYLES)
-
- def test_prefer_css_sanitizer_over_allowed_styles(self):
- with self.assertWarnsMessage(
- UserWarning,
- "allowed_styles argument is ignored since css_sanitizer is favoured over allowed_styles"
- ):
- field = BleachField(allowed_styles=['color', 'text-align'],
- css_sanitizer=CSSSanitizer(allowed_css_properties=ALLOWED_CSS_PROPERTIES))
- self.assertIsInstance(field.bleach_kwargs["css_sanitizer"], CSSSanitizer)
- self.assertEqual(field.bleach_kwargs["css_sanitizer"].allowed_css_properties, ALLOWED_CSS_PROPERTIES)
-
def test_retrieved_values_are_template_safe(self):
obj = BleachContent.objects.create(content="some content")
obj.refresh_from_db()
@@ -98,35 +82,38 @@ def test_saved_none_values_are_none(self):
class BleachNullableContent(models.Model):
- """ Bleach test model"""
+ """Bleach test model"""
+
content = BleachField(
allowed_attributes=ALLOWED_ATTRIBUTES,
allowed_protocols=ALLOWED_PROTOCOLS,
- css_sanitizer=CSSSanitizer(allowed_css_properties=ALLOWED_CSS_PROPERTIES),
+ css_sanitizer=CSSSanitizer(
+ allowed_css_properties=ALLOWED_CSS_PROPERTIES
+ ),
allowed_tags=ALLOWED_TAGS,
strip_comments=True,
strip_tags=True,
blank=True,
- null=True
+ null=True,
)
class TestBleachNullableModelField(TestCase):
- """ Test model field """
+ """Test model field"""
def test_bleaching(self):
- """ Test values are bleached """
+ """Test values are bleached"""
test_data = {
- 'none': None,
- 'empty': "",
- 'whitespaces': " ",
- 'linebreak': "\n",
+ "none": None,
+ "empty": "",
+ "whitespaces": " ",
+ "linebreak": "\n",
}
expected_values = {
- 'none': None,
- 'empty': "",
- 'whitespaces': " ",
- 'linebreak': "\n",
+ "none": None,
+ "empty": "",
+ "whitespaces": " ",
+ "linebreak": "\n",
}
for key, value in test_data.items():
diff --git a/django_bleach/tests/test_settings.py b/django_bleach/tests/test_settings.py
index 9705800..78cbda5 100644
--- a/django_bleach/tests/test_settings.py
+++ b/django_bleach/tests/test_settings.py
@@ -1,73 +1,92 @@
+from unittest.mock import patch
+
+from bleach.css_sanitizer import CSSSanitizer
from django.core.exceptions import ImproperlyConfigured
from django.forms import Textarea
from django.test import TestCase, override_settings
-from bleach.css_sanitizer import CSSSanitizer
-from unittest.mock import patch
-
from django_bleach.forms import get_default_widget
from django_bleach.utils import get_bleach_default_options
-from testproject.constants import ALLOWED_ATTRIBUTES, ALLOWED_CSS_PROPERTIES, ALLOWED_PROTOCOLS, ALLOWED_TAGS
+from testproject.constants import (
+ ALLOWED_ATTRIBUTES,
+ ALLOWED_CSS_PROPERTIES,
+ ALLOWED_PROTOCOLS,
+ ALLOWED_TAGS,
+)
from testproject.forms import CustomBleachWidget
class TestBleachOptions(TestCase):
-
- @patch('django_bleach.utils.settings',
- BLEACH_ALLOWED_ATTRIBUTES=ALLOWED_ATTRIBUTES)
+ @patch(
+ "django_bleach.utils.settings",
+ BLEACH_ALLOWED_ATTRIBUTES=ALLOWED_ATTRIBUTES,
+ )
def test_custom_attrs(self, settings):
bleach_args = get_bleach_default_options()
- self.assertEqual(bleach_args['attributes'], ALLOWED_ATTRIBUTES)
+ self.assertEqual(bleach_args["attributes"], ALLOWED_ATTRIBUTES)
- @patch('django_bleach.utils.settings',
- BLEACH_ALLOWED_PROTOCOLS=ALLOWED_PROTOCOLS)
+ @patch(
+ "django_bleach.utils.settings",
+ BLEACH_ALLOWED_PROTOCOLS=ALLOWED_PROTOCOLS,
+ )
def test_custom_proto(self, settings):
bleach_args = get_bleach_default_options()
- self.assertEqual(bleach_args['protocols'], ALLOWED_PROTOCOLS)
+ self.assertEqual(bleach_args["protocols"], ALLOWED_PROTOCOLS)
- @patch('django_bleach.utils.settings',
- BLEACH_ALLOWED_STYLES=ALLOWED_CSS_PROPERTIES)
+ @patch(
+ "django_bleach.utils.settings",
+ BLEACH_ALLOWED_STYLES=ALLOWED_CSS_PROPERTIES,
+ )
def test_custom_styles(self, settings):
bleach_args = get_bleach_default_options()
- self.assertIsInstance(bleach_args['css_sanitizer'], CSSSanitizer)
- self.assertEqual(bleach_args['css_sanitizer'].allowed_css_properties, ALLOWED_CSS_PROPERTIES)
+ self.assertIsInstance(bleach_args["css_sanitizer"], CSSSanitizer)
+ self.assertEqual(
+ bleach_args["css_sanitizer"].allowed_css_properties,
+ ALLOWED_CSS_PROPERTIES,
+ )
- @patch('django_bleach.utils.settings', BLEACH_ALLOWED_TAGS=ALLOWED_TAGS)
+ @patch("django_bleach.utils.settings", BLEACH_ALLOWED_TAGS=ALLOWED_TAGS)
def test_custom_tags(self, settings):
bleach_args = get_bleach_default_options()
- self.assertEqual(bleach_args['tags'], ALLOWED_TAGS)
+ self.assertEqual(bleach_args["tags"], ALLOWED_TAGS)
- @patch('django_bleach.utils.settings', BLEACH_STRIP_TAGS=True)
+ @patch("django_bleach.utils.settings", BLEACH_STRIP_TAGS=True)
def test_strip_tags(self, settings):
bleach_args = get_bleach_default_options()
- self.assertEqual(bleach_args['strip'], True)
+ self.assertEqual(bleach_args["strip"], True)
- @patch('django_bleach.utils.settings', BLEACH_STRIP_COMMENTS=True)
+ @patch("django_bleach.utils.settings", BLEACH_STRIP_COMMENTS=True)
def test_strip_comments(self, settings):
bleach_args = get_bleach_default_options()
- self.assertEqual(bleach_args['strip_comments'], True)
+ self.assertEqual(bleach_args["strip_comments"], True)
class TestDefaultWidget(TestCase):
- """ Test form field widgets """
+ """Test form field widgets"""
- @override_settings(BLEACH_DEFAULT_WIDGET='django.forms.widgets.Textarea')
+ @override_settings(BLEACH_DEFAULT_WIDGET="django.forms.widgets.Textarea")
def test_default_widget(self):
self.assertEqual(get_default_widget(), Textarea)
- @patch('django_bleach.forms.settings',
- BLEACH_DEFAULT_WIDGET='testproject.forms.CustomBleachWidget')
+ @patch(
+ "django_bleach.forms.settings",
+ BLEACH_DEFAULT_WIDGET="testproject.forms.CustomBleachWidget",
+ )
def test_custom_widget(self, settings):
self.assertEqual(get_default_widget(), CustomBleachWidget)
- @patch('django_bleach.forms.settings',
- BLEACH_DEFAULT_WIDGET='testproject.forms.NoneExistentWidget')
+ @patch(
+ "django_bleach.forms.settings",
+ BLEACH_DEFAULT_WIDGET="testproject.forms.NoneExistentWidget",
+ )
def test_attribute_err(self, settings):
with self.assertRaises(ImproperlyConfigured):
get_default_widget()
- @patch('django_bleach.forms.settings',
- BLEACH_DEFAULT_WIDGET='testproject.forms2.CustomBleachWidget')
+ @patch(
+ "django_bleach.forms.settings",
+ BLEACH_DEFAULT_WIDGET="testproject.forms2.CustomBleachWidget",
+ )
def test_import_err(self, settings):
with self.assertRaises(ImproperlyConfigured):
get_default_widget()
diff --git a/django_bleach/tests/test_templatetags.py b/django_bleach/tests/test_templatetags.py
index f497bea..e1dc9e8 100644
--- a/django_bleach/tests/test_templatetags.py
+++ b/django_bleach/tests/test_templatetags.py
@@ -3,45 +3,38 @@
class TestBleachTemplates(TestCase):
- """ Test template tags """
+ """Test template tags"""
def test_bleaching(self):
- """ Test that unsafe tags are sanitised """
+ """Test that unsafe tags are sanitised"""
context = Context(
- {'some_unsafe_content': ''},
+ {"some_unsafe_content": ''},
)
template_to_render = Template(
- '{% load bleach_tags %}'
- '{{ some_unsafe_content|bleach }}'
+ "{% load bleach_tags %}" "{{ some_unsafe_content|bleach }}"
)
rendered_template = template_to_render.render(context)
self.assertInHTML(
'<script>alert("Hello World!")</script>',
- rendered_template
+ rendered_template,
)
def test_bleaching_none(self):
- """ Test that None is handled properly as an input """
- context = Context(
- {'none_value': None}
- )
+ """Test that None is handled properly as an input"""
+ context = Context({"none_value": None})
template_to_render = Template(
- '{% load bleach_tags %}'
- '{{ none_value|bleach }}'
+ "{% load bleach_tags %}" "{{ none_value|bleach }}"
)
rendered_template = template_to_render.render(context)
- self.assertEqual(
- 'None',
- rendered_template
- )
+ self.assertEqual("None", rendered_template)
def test_bleaching_tags(self):
- """ Test provided tags are kept """
+ """Test provided tags are kept"""
context = Context(
- {'some_unsafe_content': ''}
+ {"some_unsafe_content": ''}
)
template_to_render = Template(
- '{% load bleach_tags %}'
+ "{% load bleach_tags %}"
'{{ some_unsafe_content|bleach:"script" }}'
)
rendered_template = template_to_render.render(context)
@@ -50,28 +43,26 @@ def test_bleaching_tags(self):
)
def test_linkify(self):
- """ Test bleach linkify """
- url = 'www.google.com'
- context = Context({'link_this': url})
+ """Test bleach linkify"""
+ url = "www.google.com"
+ context = Context({"link_this": url})
template_to_render = Template(
- '{% load bleach_tags %}'
- '{{ link_this|bleach_linkify|safe }}'
+ "{% load bleach_tags %}" "{{ link_this|bleach_linkify|safe }}"
)
rendered_template = template_to_render.render(context)
self.assertInHTML(
'{0}'.format(url),
- rendered_template
+ rendered_template,
)
def test_linkify_none(self):
- """ Test bleach linkify with None as an input """
- context = Context({'none_value': None})
+ """Test bleach linkify with None as an input"""
+ context = Context({"none_value": None})
template_to_render = Template(
- '{% load bleach_tags %}'
- '{{ none_value|bleach_linkify }}'
+ "{% load bleach_tags %}" "{{ none_value|bleach_linkify }}"
)
rendered_template = template_to_render.render(context)
self.assertEqual(
- 'None',
+ "None",
rendered_template,
)
diff --git a/django_bleach/utils.py b/django_bleach/utils.py
index de1a77b..5c0fd7a 100644
--- a/django_bleach/utils.py
+++ b/django_bleach/utils.py
@@ -1,6 +1,5 @@
-from django.conf import settings
-
from bleach.css_sanitizer import CSSSanitizer
+from django.conf import settings
def get_bleach_default_options():
@@ -11,7 +10,7 @@ def get_bleach_default_options():
"BLEACH_ALLOWED_STYLES": "css_sanitizer",
"BLEACH_STRIP_TAGS": "strip",
"BLEACH_STRIP_COMMENTS": "strip_comments",
- "BLEACH_ALLOWED_PROTOCOLS": "protocols"
+ "BLEACH_ALLOWED_PROTOCOLS": "protocols",
}
for setting, kwarg in bleach_settings.items():
diff --git a/docs/conf.py b/docs/conf.py
index 84132be..2a2f50a 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -25,30 +25,29 @@
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
import datetime
-
-extensions = ['sphinx.ext.autodoc']
+extensions = ["sphinx.ext.autodoc"]
# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
# The suffix of source filenames.
-source_suffix = '.rst'
+source_suffix = ".rst"
# The encoding of source files.
# source_encoding = 'utf-8-sig'
# The master toctree document.
-master_doc = 'index'
+master_doc = "index"
# General information about the project.
-project = 'django-bleach'
+project = "django-bleach"
copyright = f"{datetime.date.today().year}, Tim Heap, Mark Walker"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
-version = '3.0.1'
+version = "3.1.0"
# The full version, including alpha/beta/rc tags.
# release = '1.0.0'
@@ -64,7 +63,7 @@
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
-exclude_patterns = ['_build']
+exclude_patterns = ["_build"]
# The reST default role (used for this markup: `text`) to use for all documents.
# default_role = None
@@ -81,7 +80,7 @@
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
@@ -91,7 +90,7 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-html_theme = 'default'
+html_theme = "default"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
@@ -120,7 +119,7 @@
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
+html_static_path = ["_static"]
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
@@ -164,7 +163,7 @@
# html_file_suffix = None
# Output file base name for HTML help builder.
-htmlhelp_basename = 'django-bleach'
+htmlhelp_basename = "django-bleach"
# -- Options for LaTeX output --------------------------------------------------
@@ -184,8 +183,11 @@
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
(
- 'index', 'django-bleach.tex', 'django-bleach Documentation',
- 'Mark Walker', 'manual'
+ "index",
+ "django-bleach.tex",
+ "django-bleach Documentation",
+ "Mark Walker",
+ "manual",
),
]
@@ -215,8 +217,13 @@
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
- ('index', 'django-bleach', 'django-bleach Documentation',
- ['Mark Walker'], 1)
+ (
+ "index",
+ "django-bleach",
+ "django-bleach Documentation",
+ ["Mark Walker"],
+ 1,
+ )
]
# If true, show URL addresses after external links.
@@ -230,9 +237,13 @@
# dir menu entry, description, category)
texinfo_documents = [
(
- 'index', 'django-bleach', 'django-bleach Documentation',
- 'Mark Walker', 'django-bleach', 'One line description of project.',
- 'Miscellaneous'
+ "index",
+ "django-bleach",
+ "django-bleach Documentation",
+ "Mark Walker",
+ "django-bleach",
+ "One line description of project.",
+ "Miscellaneous",
),
]
diff --git a/pyproject.toml b/pyproject.toml
index 019b0d8..e7b863d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,3 +1,82 @@
[build-system]
# Minimum requirements for the build system to execute.
requires = ["setuptools", "wheel"] # PEP 508 specifications.
+
+[tool.black]
+line-length = 79
+target-version = ["py311"]
+
+[tool.coverage.run]
+branch = true
+parallel = true
+omit = [
+ "testproject/**"
+]
+
+[tool.coverage.paths]
+source = [
+ "django_bleach",
+ ".tox/**/site-packages"
+]
+
+[tool.coverage.report]
+show_missing = true
+
+[tool.ruff]
+# https://beta.ruff.rs/docs/configuration/
+line-length = 79
+select = [
+ "E", # pycodestyle errors
+ "W", # pycodestyle warnings
+ "F", # pyflakes
+ "I", # isort
+ "C", # flake8-comprehensions
+ "B", # flake8-bugbear
+ "Q", # flake8-quotes
+ "PLE", # pylint error
+ "PLR", # pylint refactor
+ "PLW", # pylint warning
+ "UP", # pyupgrade
+]
+
+exclude = [
+ ".eggs",
+ ".git",
+ ".mypy_cache",
+ ".ruff_cache",
+ ".env",
+ ".venv",
+ "**migrations/**",
+ "docs/**",
+ "node_modules",
+ "requirements",
+ "venv",
+ "testproject",
+]
+
+ignore = [
+ "B006", # Do not use mutable data structures for argument defaults
+ "B011", # tests use assert False
+ "B019", # Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks
+ "B905", # `zip()` without an explicit `strict=` parameter
+ "C901", # too complex functions
+ "E402", # module level import not at top of file
+ "E731", # do not assign a lambda expression, use a def
+ "PLR0911", # Too many return statements
+ "PLR0912", # Too many branches
+ "PLR0913", # Too many arguments to function call
+ "PLR0915", # Too many statements
+ "PLR2004", # Magic value used in comparison, consider replacing with a constant variable
+]
+
+[tool.ruff.per-file-ignores]
+"__init__.py" = [
+ "F401" # unused-import
+]
+
+[tool.ruff.isort]
+combine-as-imports = true
+known-first-party = [
+ "django_bleach",
+]
+extra-standard-library = ["dataclasses"]
diff --git a/requirements/compile.py b/requirements/compile.py
new file mode 100644
index 0000000..61af1c4
--- /dev/null
+++ b/requirements/compile.py
@@ -0,0 +1,201 @@
+#!/usr/bin/env python
+from __future__ import annotations
+
+import os
+import subprocess
+import sys
+from pathlib import Path
+
+if __name__ == "__main__":
+ os.chdir(Path(__file__).parent)
+ os.environ["CUSTOM_COMPILE_COMMAND"] = "requirements/compile.py"
+ os.environ["PIP_REQUIRE_VIRTUALENV"] = "0"
+ common_args = [
+ "-m",
+ "piptools",
+ "compile",
+ "--generate-hashes",
+ "--allow-unsafe",
+ ] + sys.argv[1:]
+ # mysqlclient requirements found on each version's "Databases" documentation page:
+ # https://docs.djangoproject.com/en/3.0/ref/databases/#mysql-db-api-drivers
+ subprocess.run(
+ [
+ "python3.8",
+ *common_args,
+ "-P",
+ "Django>=3.2a1,<3.3",
+ "-o",
+ "py38-django32.txt",
+ ],
+ check=True,
+ capture_output=True,
+ )
+ subprocess.run(
+ [
+ "python3.8",
+ *common_args,
+ "-P",
+ "Django>=4.0a1,<4.1",
+ "-o",
+ "py38-django40.txt",
+ ],
+ check=True,
+ capture_output=True,
+ )
+ subprocess.run(
+ [
+ "python3.8",
+ *common_args,
+ "-P",
+ "Django>=4.1a1,<4.2",
+ "-o",
+ "py38-django41.txt",
+ ],
+ check=True,
+ capture_output=True,
+ )
+ subprocess.run(
+ [
+ "python3.8",
+ *common_args,
+ "-P",
+ "Django>=4.2a1,<5.0",
+ "-o",
+ "py38-django42.txt",
+ ],
+ check=True,
+ capture_output=True,
+ )
+ subprocess.run(
+ [
+ "python3.9",
+ *common_args,
+ "-P",
+ "Django>=3.2a1,<3.3",
+ "-o",
+ "py39-django32.txt",
+ ],
+ check=True,
+ capture_output=True,
+ )
+ subprocess.run(
+ [
+ "python3.9",
+ *common_args,
+ "-P",
+ "Django>=4.0a1,<4.1",
+ "-o",
+ "py39-django40.txt",
+ ],
+ check=True,
+ capture_output=True,
+ )
+ subprocess.run(
+ [
+ "python3.9",
+ *common_args,
+ "-P",
+ "Django>=4.1a1,<4.2",
+ "-o",
+ "py39-django41.txt",
+ ],
+ check=True,
+ capture_output=True,
+ )
+ subprocess.run(
+ [
+ "python3.9",
+ *common_args,
+ "-P",
+ "Django>=4.2a1,<5.0",
+ "-o",
+ "py39-django42.txt",
+ ],
+ check=True,
+ capture_output=True,
+ )
+ subprocess.run(
+ [
+ "python3.10",
+ *common_args,
+ "-P",
+ "Django>=3.2a1,<3.3",
+ "-o",
+ "py310-django32.txt",
+ ],
+ check=True,
+ capture_output=True,
+ )
+ subprocess.run(
+ [
+ "python3.10",
+ *common_args,
+ "-P",
+ "Django>=4.0a1,<4.1",
+ "-o",
+ "py310-django40.txt",
+ ],
+ check=True,
+ capture_output=True,
+ )
+ subprocess.run(
+ [
+ "python3.10",
+ *common_args,
+ "-P",
+ "Django>=4.1a1,<4.2",
+ "-o",
+ "py310-django41.txt",
+ ],
+ check=True,
+ capture_output=True,
+ )
+ subprocess.run(
+ [
+ "python3.10",
+ *common_args,
+ "-P",
+ "Django>=4.2a1,<5.0",
+ "-o",
+ "py310-django42.txt",
+ ],
+ check=True,
+ capture_output=True,
+ )
+ subprocess.run(
+ [
+ "python3.11",
+ *common_args,
+ "-P",
+ "Django>=4.1a1,<4.2",
+ "-o",
+ "py311-django41.txt",
+ ],
+ check=True,
+ capture_output=True,
+ )
+ subprocess.run(
+ [
+ "python3.11",
+ *common_args,
+ "-P",
+ "Django>=4.2a1,<5.0",
+ "-o",
+ "py311-django42.txt",
+ ],
+ check=True,
+ capture_output=True,
+ )
+ subprocess.run(
+ [
+ "python3.12",
+ *common_args,
+ "-P",
+ "Django>=4.2a1,<5.0",
+ "-o",
+ "py312-django42.txt",
+ ],
+ check=True,
+ capture_output=True,
+ )
diff --git a/requirements/py310-django32.txt b/requirements/py310-django32.txt
new file mode 100644
index 0000000..cfa91aa
--- /dev/null
+++ b/requirements/py310-django32.txt
@@ -0,0 +1,94 @@
+#
+# This file is autogenerated by pip-compile with Python 3.10
+# by the following command:
+#
+# requirements/compile.py
+#
+bleach==5.0.1 \
+ --hash=sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a \
+ --hash=sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c
+ # via -r requirements.in
+coverage[toml]==7.2.7 \
+ --hash=sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f \
+ --hash=sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2 \
+ --hash=sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a \
+ --hash=sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a \
+ --hash=sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01 \
+ --hash=sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6 \
+ --hash=sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7 \
+ --hash=sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f \
+ --hash=sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02 \
+ --hash=sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c \
+ --hash=sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063 \
+ --hash=sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a \
+ --hash=sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5 \
+ --hash=sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959 \
+ --hash=sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97 \
+ --hash=sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6 \
+ --hash=sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f \
+ --hash=sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9 \
+ --hash=sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5 \
+ --hash=sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f \
+ --hash=sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562 \
+ --hash=sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe \
+ --hash=sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9 \
+ --hash=sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f \
+ --hash=sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb \
+ --hash=sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb \
+ --hash=sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1 \
+ --hash=sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb \
+ --hash=sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250 \
+ --hash=sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e \
+ --hash=sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511 \
+ --hash=sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5 \
+ --hash=sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59 \
+ --hash=sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2 \
+ --hash=sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d \
+ --hash=sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3 \
+ --hash=sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4 \
+ --hash=sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de \
+ --hash=sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9 \
+ --hash=sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833 \
+ --hash=sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0 \
+ --hash=sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9 \
+ --hash=sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d \
+ --hash=sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050 \
+ --hash=sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d \
+ --hash=sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6 \
+ --hash=sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353 \
+ --hash=sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb \
+ --hash=sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e \
+ --hash=sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8 \
+ --hash=sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495 \
+ --hash=sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2 \
+ --hash=sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd \
+ --hash=sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27 \
+ --hash=sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1 \
+ --hash=sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818 \
+ --hash=sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4 \
+ --hash=sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e \
+ --hash=sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850 \
+ --hash=sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3
+ # via -r requirements.in
+mock==5.1.0 \
+ --hash=sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744 \
+ --hash=sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d
+ # via -r requirements.in
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via bleach
+tinycss2==1.2.1 \
+ --hash=sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847 \
+ --hash=sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627
+ # via -r requirements.in
+tomli==2.0.1 \
+ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
+ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
+ # via coverage
+webencodings==0.5.1 \
+ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
+ --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
+ # via
+ # bleach
+ # tinycss2
diff --git a/requirements/py310-django40.txt b/requirements/py310-django40.txt
new file mode 100644
index 0000000..cfa91aa
--- /dev/null
+++ b/requirements/py310-django40.txt
@@ -0,0 +1,94 @@
+#
+# This file is autogenerated by pip-compile with Python 3.10
+# by the following command:
+#
+# requirements/compile.py
+#
+bleach==5.0.1 \
+ --hash=sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a \
+ --hash=sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c
+ # via -r requirements.in
+coverage[toml]==7.2.7 \
+ --hash=sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f \
+ --hash=sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2 \
+ --hash=sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a \
+ --hash=sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a \
+ --hash=sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01 \
+ --hash=sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6 \
+ --hash=sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7 \
+ --hash=sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f \
+ --hash=sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02 \
+ --hash=sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c \
+ --hash=sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063 \
+ --hash=sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a \
+ --hash=sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5 \
+ --hash=sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959 \
+ --hash=sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97 \
+ --hash=sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6 \
+ --hash=sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f \
+ --hash=sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9 \
+ --hash=sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5 \
+ --hash=sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f \
+ --hash=sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562 \
+ --hash=sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe \
+ --hash=sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9 \
+ --hash=sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f \
+ --hash=sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb \
+ --hash=sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb \
+ --hash=sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1 \
+ --hash=sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb \
+ --hash=sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250 \
+ --hash=sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e \
+ --hash=sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511 \
+ --hash=sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5 \
+ --hash=sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59 \
+ --hash=sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2 \
+ --hash=sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d \
+ --hash=sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3 \
+ --hash=sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4 \
+ --hash=sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de \
+ --hash=sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9 \
+ --hash=sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833 \
+ --hash=sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0 \
+ --hash=sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9 \
+ --hash=sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d \
+ --hash=sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050 \
+ --hash=sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d \
+ --hash=sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6 \
+ --hash=sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353 \
+ --hash=sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb \
+ --hash=sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e \
+ --hash=sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8 \
+ --hash=sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495 \
+ --hash=sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2 \
+ --hash=sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd \
+ --hash=sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27 \
+ --hash=sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1 \
+ --hash=sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818 \
+ --hash=sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4 \
+ --hash=sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e \
+ --hash=sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850 \
+ --hash=sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3
+ # via -r requirements.in
+mock==5.1.0 \
+ --hash=sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744 \
+ --hash=sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d
+ # via -r requirements.in
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via bleach
+tinycss2==1.2.1 \
+ --hash=sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847 \
+ --hash=sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627
+ # via -r requirements.in
+tomli==2.0.1 \
+ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
+ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
+ # via coverage
+webencodings==0.5.1 \
+ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
+ --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
+ # via
+ # bleach
+ # tinycss2
diff --git a/requirements/py310-django41.txt b/requirements/py310-django41.txt
new file mode 100644
index 0000000..cfa91aa
--- /dev/null
+++ b/requirements/py310-django41.txt
@@ -0,0 +1,94 @@
+#
+# This file is autogenerated by pip-compile with Python 3.10
+# by the following command:
+#
+# requirements/compile.py
+#
+bleach==5.0.1 \
+ --hash=sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a \
+ --hash=sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c
+ # via -r requirements.in
+coverage[toml]==7.2.7 \
+ --hash=sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f \
+ --hash=sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2 \
+ --hash=sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a \
+ --hash=sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a \
+ --hash=sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01 \
+ --hash=sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6 \
+ --hash=sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7 \
+ --hash=sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f \
+ --hash=sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02 \
+ --hash=sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c \
+ --hash=sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063 \
+ --hash=sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a \
+ --hash=sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5 \
+ --hash=sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959 \
+ --hash=sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97 \
+ --hash=sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6 \
+ --hash=sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f \
+ --hash=sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9 \
+ --hash=sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5 \
+ --hash=sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f \
+ --hash=sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562 \
+ --hash=sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe \
+ --hash=sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9 \
+ --hash=sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f \
+ --hash=sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb \
+ --hash=sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb \
+ --hash=sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1 \
+ --hash=sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb \
+ --hash=sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250 \
+ --hash=sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e \
+ --hash=sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511 \
+ --hash=sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5 \
+ --hash=sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59 \
+ --hash=sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2 \
+ --hash=sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d \
+ --hash=sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3 \
+ --hash=sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4 \
+ --hash=sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de \
+ --hash=sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9 \
+ --hash=sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833 \
+ --hash=sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0 \
+ --hash=sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9 \
+ --hash=sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d \
+ --hash=sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050 \
+ --hash=sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d \
+ --hash=sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6 \
+ --hash=sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353 \
+ --hash=sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb \
+ --hash=sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e \
+ --hash=sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8 \
+ --hash=sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495 \
+ --hash=sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2 \
+ --hash=sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd \
+ --hash=sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27 \
+ --hash=sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1 \
+ --hash=sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818 \
+ --hash=sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4 \
+ --hash=sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e \
+ --hash=sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850 \
+ --hash=sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3
+ # via -r requirements.in
+mock==5.1.0 \
+ --hash=sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744 \
+ --hash=sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d
+ # via -r requirements.in
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via bleach
+tinycss2==1.2.1 \
+ --hash=sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847 \
+ --hash=sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627
+ # via -r requirements.in
+tomli==2.0.1 \
+ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
+ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
+ # via coverage
+webencodings==0.5.1 \
+ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
+ --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
+ # via
+ # bleach
+ # tinycss2
diff --git a/requirements/py310-django42.txt b/requirements/py310-django42.txt
new file mode 100644
index 0000000..cfa91aa
--- /dev/null
+++ b/requirements/py310-django42.txt
@@ -0,0 +1,94 @@
+#
+# This file is autogenerated by pip-compile with Python 3.10
+# by the following command:
+#
+# requirements/compile.py
+#
+bleach==5.0.1 \
+ --hash=sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a \
+ --hash=sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c
+ # via -r requirements.in
+coverage[toml]==7.2.7 \
+ --hash=sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f \
+ --hash=sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2 \
+ --hash=sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a \
+ --hash=sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a \
+ --hash=sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01 \
+ --hash=sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6 \
+ --hash=sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7 \
+ --hash=sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f \
+ --hash=sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02 \
+ --hash=sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c \
+ --hash=sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063 \
+ --hash=sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a \
+ --hash=sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5 \
+ --hash=sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959 \
+ --hash=sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97 \
+ --hash=sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6 \
+ --hash=sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f \
+ --hash=sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9 \
+ --hash=sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5 \
+ --hash=sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f \
+ --hash=sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562 \
+ --hash=sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe \
+ --hash=sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9 \
+ --hash=sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f \
+ --hash=sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb \
+ --hash=sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb \
+ --hash=sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1 \
+ --hash=sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb \
+ --hash=sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250 \
+ --hash=sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e \
+ --hash=sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511 \
+ --hash=sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5 \
+ --hash=sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59 \
+ --hash=sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2 \
+ --hash=sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d \
+ --hash=sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3 \
+ --hash=sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4 \
+ --hash=sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de \
+ --hash=sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9 \
+ --hash=sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833 \
+ --hash=sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0 \
+ --hash=sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9 \
+ --hash=sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d \
+ --hash=sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050 \
+ --hash=sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d \
+ --hash=sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6 \
+ --hash=sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353 \
+ --hash=sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb \
+ --hash=sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e \
+ --hash=sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8 \
+ --hash=sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495 \
+ --hash=sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2 \
+ --hash=sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd \
+ --hash=sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27 \
+ --hash=sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1 \
+ --hash=sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818 \
+ --hash=sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4 \
+ --hash=sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e \
+ --hash=sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850 \
+ --hash=sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3
+ # via -r requirements.in
+mock==5.1.0 \
+ --hash=sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744 \
+ --hash=sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d
+ # via -r requirements.in
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via bleach
+tinycss2==1.2.1 \
+ --hash=sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847 \
+ --hash=sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627
+ # via -r requirements.in
+tomli==2.0.1 \
+ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
+ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
+ # via coverage
+webencodings==0.5.1 \
+ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
+ --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
+ # via
+ # bleach
+ # tinycss2
diff --git a/requirements/py311-django41.txt b/requirements/py311-django41.txt
new file mode 100644
index 0000000..7ceec45
--- /dev/null
+++ b/requirements/py311-django41.txt
@@ -0,0 +1,90 @@
+#
+# This file is autogenerated by pip-compile with Python 3.11
+# by the following command:
+#
+# requirements/compile.py
+#
+bleach==5.0.1 \
+ --hash=sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a \
+ --hash=sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c
+ # via -r requirements.in
+coverage[toml]==7.2.7 \
+ --hash=sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f \
+ --hash=sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2 \
+ --hash=sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a \
+ --hash=sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a \
+ --hash=sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01 \
+ --hash=sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6 \
+ --hash=sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7 \
+ --hash=sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f \
+ --hash=sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02 \
+ --hash=sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c \
+ --hash=sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063 \
+ --hash=sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a \
+ --hash=sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5 \
+ --hash=sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959 \
+ --hash=sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97 \
+ --hash=sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6 \
+ --hash=sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f \
+ --hash=sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9 \
+ --hash=sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5 \
+ --hash=sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f \
+ --hash=sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562 \
+ --hash=sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe \
+ --hash=sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9 \
+ --hash=sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f \
+ --hash=sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb \
+ --hash=sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb \
+ --hash=sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1 \
+ --hash=sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb \
+ --hash=sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250 \
+ --hash=sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e \
+ --hash=sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511 \
+ --hash=sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5 \
+ --hash=sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59 \
+ --hash=sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2 \
+ --hash=sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d \
+ --hash=sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3 \
+ --hash=sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4 \
+ --hash=sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de \
+ --hash=sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9 \
+ --hash=sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833 \
+ --hash=sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0 \
+ --hash=sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9 \
+ --hash=sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d \
+ --hash=sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050 \
+ --hash=sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d \
+ --hash=sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6 \
+ --hash=sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353 \
+ --hash=sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb \
+ --hash=sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e \
+ --hash=sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8 \
+ --hash=sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495 \
+ --hash=sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2 \
+ --hash=sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd \
+ --hash=sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27 \
+ --hash=sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1 \
+ --hash=sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818 \
+ --hash=sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4 \
+ --hash=sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e \
+ --hash=sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850 \
+ --hash=sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3
+ # via -r requirements.in
+mock==5.1.0 \
+ --hash=sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744 \
+ --hash=sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d
+ # via -r requirements.in
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via bleach
+tinycss2==1.2.1 \
+ --hash=sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847 \
+ --hash=sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627
+ # via -r requirements.in
+webencodings==0.5.1 \
+ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
+ --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
+ # via
+ # bleach
+ # tinycss2
diff --git a/requirements/py311-django42.txt b/requirements/py311-django42.txt
new file mode 100644
index 0000000..7ceec45
--- /dev/null
+++ b/requirements/py311-django42.txt
@@ -0,0 +1,90 @@
+#
+# This file is autogenerated by pip-compile with Python 3.11
+# by the following command:
+#
+# requirements/compile.py
+#
+bleach==5.0.1 \
+ --hash=sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a \
+ --hash=sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c
+ # via -r requirements.in
+coverage[toml]==7.2.7 \
+ --hash=sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f \
+ --hash=sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2 \
+ --hash=sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a \
+ --hash=sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a \
+ --hash=sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01 \
+ --hash=sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6 \
+ --hash=sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7 \
+ --hash=sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f \
+ --hash=sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02 \
+ --hash=sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c \
+ --hash=sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063 \
+ --hash=sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a \
+ --hash=sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5 \
+ --hash=sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959 \
+ --hash=sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97 \
+ --hash=sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6 \
+ --hash=sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f \
+ --hash=sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9 \
+ --hash=sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5 \
+ --hash=sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f \
+ --hash=sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562 \
+ --hash=sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe \
+ --hash=sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9 \
+ --hash=sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f \
+ --hash=sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb \
+ --hash=sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb \
+ --hash=sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1 \
+ --hash=sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb \
+ --hash=sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250 \
+ --hash=sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e \
+ --hash=sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511 \
+ --hash=sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5 \
+ --hash=sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59 \
+ --hash=sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2 \
+ --hash=sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d \
+ --hash=sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3 \
+ --hash=sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4 \
+ --hash=sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de \
+ --hash=sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9 \
+ --hash=sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833 \
+ --hash=sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0 \
+ --hash=sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9 \
+ --hash=sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d \
+ --hash=sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050 \
+ --hash=sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d \
+ --hash=sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6 \
+ --hash=sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353 \
+ --hash=sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb \
+ --hash=sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e \
+ --hash=sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8 \
+ --hash=sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495 \
+ --hash=sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2 \
+ --hash=sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd \
+ --hash=sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27 \
+ --hash=sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1 \
+ --hash=sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818 \
+ --hash=sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4 \
+ --hash=sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e \
+ --hash=sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850 \
+ --hash=sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3
+ # via -r requirements.in
+mock==5.1.0 \
+ --hash=sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744 \
+ --hash=sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d
+ # via -r requirements.in
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via bleach
+tinycss2==1.2.1 \
+ --hash=sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847 \
+ --hash=sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627
+ # via -r requirements.in
+webencodings==0.5.1 \
+ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
+ --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
+ # via
+ # bleach
+ # tinycss2
diff --git a/requirements/py312-django42.txt b/requirements/py312-django42.txt
new file mode 100644
index 0000000..801228b
--- /dev/null
+++ b/requirements/py312-django42.txt
@@ -0,0 +1,90 @@
+#
+# This file is autogenerated by pip-compile with Python 3.12
+# by the following command:
+#
+# requirements/compile.py
+#
+bleach==5.0.1 \
+ --hash=sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a \
+ --hash=sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c
+ # via -r requirements.in
+coverage[toml]==7.2.7 \
+ --hash=sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f \
+ --hash=sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2 \
+ --hash=sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a \
+ --hash=sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a \
+ --hash=sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01 \
+ --hash=sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6 \
+ --hash=sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7 \
+ --hash=sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f \
+ --hash=sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02 \
+ --hash=sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c \
+ --hash=sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063 \
+ --hash=sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a \
+ --hash=sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5 \
+ --hash=sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959 \
+ --hash=sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97 \
+ --hash=sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6 \
+ --hash=sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f \
+ --hash=sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9 \
+ --hash=sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5 \
+ --hash=sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f \
+ --hash=sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562 \
+ --hash=sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe \
+ --hash=sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9 \
+ --hash=sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f \
+ --hash=sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb \
+ --hash=sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb \
+ --hash=sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1 \
+ --hash=sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb \
+ --hash=sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250 \
+ --hash=sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e \
+ --hash=sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511 \
+ --hash=sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5 \
+ --hash=sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59 \
+ --hash=sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2 \
+ --hash=sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d \
+ --hash=sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3 \
+ --hash=sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4 \
+ --hash=sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de \
+ --hash=sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9 \
+ --hash=sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833 \
+ --hash=sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0 \
+ --hash=sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9 \
+ --hash=sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d \
+ --hash=sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050 \
+ --hash=sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d \
+ --hash=sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6 \
+ --hash=sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353 \
+ --hash=sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb \
+ --hash=sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e \
+ --hash=sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8 \
+ --hash=sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495 \
+ --hash=sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2 \
+ --hash=sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd \
+ --hash=sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27 \
+ --hash=sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1 \
+ --hash=sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818 \
+ --hash=sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4 \
+ --hash=sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e \
+ --hash=sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850 \
+ --hash=sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3
+ # via -r requirements.in
+mock==5.1.0 \
+ --hash=sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744 \
+ --hash=sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d
+ # via -r requirements.in
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via bleach
+tinycss2==1.2.1 \
+ --hash=sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847 \
+ --hash=sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627
+ # via -r requirements.in
+webencodings==0.5.1 \
+ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
+ --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
+ # via
+ # bleach
+ # tinycss2
diff --git a/requirements/py38-django32.txt b/requirements/py38-django32.txt
new file mode 100644
index 0000000..32db101
--- /dev/null
+++ b/requirements/py38-django32.txt
@@ -0,0 +1,94 @@
+#
+# This file is autogenerated by pip-compile with Python 3.8
+# by the following command:
+#
+# requirements/compile.py
+#
+bleach==5.0.1 \
+ --hash=sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a \
+ --hash=sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c
+ # via -r requirements.in
+coverage[toml]==7.2.7 \
+ --hash=sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f \
+ --hash=sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2 \
+ --hash=sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a \
+ --hash=sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a \
+ --hash=sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01 \
+ --hash=sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6 \
+ --hash=sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7 \
+ --hash=sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f \
+ --hash=sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02 \
+ --hash=sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c \
+ --hash=sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063 \
+ --hash=sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a \
+ --hash=sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5 \
+ --hash=sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959 \
+ --hash=sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97 \
+ --hash=sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6 \
+ --hash=sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f \
+ --hash=sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9 \
+ --hash=sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5 \
+ --hash=sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f \
+ --hash=sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562 \
+ --hash=sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe \
+ --hash=sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9 \
+ --hash=sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f \
+ --hash=sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb \
+ --hash=sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb \
+ --hash=sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1 \
+ --hash=sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb \
+ --hash=sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250 \
+ --hash=sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e \
+ --hash=sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511 \
+ --hash=sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5 \
+ --hash=sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59 \
+ --hash=sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2 \
+ --hash=sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d \
+ --hash=sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3 \
+ --hash=sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4 \
+ --hash=sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de \
+ --hash=sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9 \
+ --hash=sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833 \
+ --hash=sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0 \
+ --hash=sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9 \
+ --hash=sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d \
+ --hash=sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050 \
+ --hash=sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d \
+ --hash=sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6 \
+ --hash=sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353 \
+ --hash=sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb \
+ --hash=sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e \
+ --hash=sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8 \
+ --hash=sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495 \
+ --hash=sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2 \
+ --hash=sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd \
+ --hash=sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27 \
+ --hash=sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1 \
+ --hash=sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818 \
+ --hash=sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4 \
+ --hash=sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e \
+ --hash=sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850 \
+ --hash=sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3
+ # via -r requirements.in
+mock==5.1.0 \
+ --hash=sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744 \
+ --hash=sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d
+ # via -r requirements.in
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via bleach
+tinycss2==1.2.1 \
+ --hash=sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847 \
+ --hash=sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627
+ # via -r requirements.in
+tomli==2.0.1 \
+ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
+ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
+ # via coverage
+webencodings==0.5.1 \
+ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
+ --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
+ # via
+ # bleach
+ # tinycss2
diff --git a/requirements/py38-django40.txt b/requirements/py38-django40.txt
new file mode 100644
index 0000000..32db101
--- /dev/null
+++ b/requirements/py38-django40.txt
@@ -0,0 +1,94 @@
+#
+# This file is autogenerated by pip-compile with Python 3.8
+# by the following command:
+#
+# requirements/compile.py
+#
+bleach==5.0.1 \
+ --hash=sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a \
+ --hash=sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c
+ # via -r requirements.in
+coverage[toml]==7.2.7 \
+ --hash=sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f \
+ --hash=sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2 \
+ --hash=sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a \
+ --hash=sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a \
+ --hash=sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01 \
+ --hash=sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6 \
+ --hash=sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7 \
+ --hash=sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f \
+ --hash=sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02 \
+ --hash=sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c \
+ --hash=sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063 \
+ --hash=sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a \
+ --hash=sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5 \
+ --hash=sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959 \
+ --hash=sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97 \
+ --hash=sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6 \
+ --hash=sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f \
+ --hash=sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9 \
+ --hash=sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5 \
+ --hash=sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f \
+ --hash=sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562 \
+ --hash=sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe \
+ --hash=sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9 \
+ --hash=sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f \
+ --hash=sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb \
+ --hash=sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb \
+ --hash=sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1 \
+ --hash=sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb \
+ --hash=sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250 \
+ --hash=sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e \
+ --hash=sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511 \
+ --hash=sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5 \
+ --hash=sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59 \
+ --hash=sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2 \
+ --hash=sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d \
+ --hash=sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3 \
+ --hash=sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4 \
+ --hash=sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de \
+ --hash=sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9 \
+ --hash=sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833 \
+ --hash=sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0 \
+ --hash=sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9 \
+ --hash=sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d \
+ --hash=sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050 \
+ --hash=sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d \
+ --hash=sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6 \
+ --hash=sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353 \
+ --hash=sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb \
+ --hash=sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e \
+ --hash=sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8 \
+ --hash=sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495 \
+ --hash=sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2 \
+ --hash=sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd \
+ --hash=sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27 \
+ --hash=sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1 \
+ --hash=sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818 \
+ --hash=sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4 \
+ --hash=sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e \
+ --hash=sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850 \
+ --hash=sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3
+ # via -r requirements.in
+mock==5.1.0 \
+ --hash=sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744 \
+ --hash=sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d
+ # via -r requirements.in
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via bleach
+tinycss2==1.2.1 \
+ --hash=sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847 \
+ --hash=sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627
+ # via -r requirements.in
+tomli==2.0.1 \
+ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
+ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
+ # via coverage
+webencodings==0.5.1 \
+ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
+ --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
+ # via
+ # bleach
+ # tinycss2
diff --git a/requirements/py38-django41.txt b/requirements/py38-django41.txt
new file mode 100644
index 0000000..32db101
--- /dev/null
+++ b/requirements/py38-django41.txt
@@ -0,0 +1,94 @@
+#
+# This file is autogenerated by pip-compile with Python 3.8
+# by the following command:
+#
+# requirements/compile.py
+#
+bleach==5.0.1 \
+ --hash=sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a \
+ --hash=sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c
+ # via -r requirements.in
+coverage[toml]==7.2.7 \
+ --hash=sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f \
+ --hash=sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2 \
+ --hash=sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a \
+ --hash=sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a \
+ --hash=sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01 \
+ --hash=sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6 \
+ --hash=sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7 \
+ --hash=sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f \
+ --hash=sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02 \
+ --hash=sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c \
+ --hash=sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063 \
+ --hash=sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a \
+ --hash=sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5 \
+ --hash=sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959 \
+ --hash=sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97 \
+ --hash=sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6 \
+ --hash=sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f \
+ --hash=sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9 \
+ --hash=sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5 \
+ --hash=sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f \
+ --hash=sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562 \
+ --hash=sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe \
+ --hash=sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9 \
+ --hash=sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f \
+ --hash=sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb \
+ --hash=sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb \
+ --hash=sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1 \
+ --hash=sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb \
+ --hash=sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250 \
+ --hash=sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e \
+ --hash=sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511 \
+ --hash=sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5 \
+ --hash=sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59 \
+ --hash=sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2 \
+ --hash=sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d \
+ --hash=sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3 \
+ --hash=sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4 \
+ --hash=sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de \
+ --hash=sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9 \
+ --hash=sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833 \
+ --hash=sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0 \
+ --hash=sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9 \
+ --hash=sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d \
+ --hash=sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050 \
+ --hash=sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d \
+ --hash=sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6 \
+ --hash=sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353 \
+ --hash=sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb \
+ --hash=sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e \
+ --hash=sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8 \
+ --hash=sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495 \
+ --hash=sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2 \
+ --hash=sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd \
+ --hash=sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27 \
+ --hash=sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1 \
+ --hash=sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818 \
+ --hash=sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4 \
+ --hash=sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e \
+ --hash=sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850 \
+ --hash=sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3
+ # via -r requirements.in
+mock==5.1.0 \
+ --hash=sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744 \
+ --hash=sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d
+ # via -r requirements.in
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via bleach
+tinycss2==1.2.1 \
+ --hash=sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847 \
+ --hash=sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627
+ # via -r requirements.in
+tomli==2.0.1 \
+ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
+ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
+ # via coverage
+webencodings==0.5.1 \
+ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
+ --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
+ # via
+ # bleach
+ # tinycss2
diff --git a/requirements/py38-django42.txt b/requirements/py38-django42.txt
new file mode 100644
index 0000000..32db101
--- /dev/null
+++ b/requirements/py38-django42.txt
@@ -0,0 +1,94 @@
+#
+# This file is autogenerated by pip-compile with Python 3.8
+# by the following command:
+#
+# requirements/compile.py
+#
+bleach==5.0.1 \
+ --hash=sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a \
+ --hash=sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c
+ # via -r requirements.in
+coverage[toml]==7.2.7 \
+ --hash=sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f \
+ --hash=sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2 \
+ --hash=sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a \
+ --hash=sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a \
+ --hash=sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01 \
+ --hash=sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6 \
+ --hash=sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7 \
+ --hash=sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f \
+ --hash=sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02 \
+ --hash=sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c \
+ --hash=sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063 \
+ --hash=sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a \
+ --hash=sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5 \
+ --hash=sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959 \
+ --hash=sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97 \
+ --hash=sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6 \
+ --hash=sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f \
+ --hash=sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9 \
+ --hash=sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5 \
+ --hash=sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f \
+ --hash=sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562 \
+ --hash=sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe \
+ --hash=sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9 \
+ --hash=sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f \
+ --hash=sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb \
+ --hash=sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb \
+ --hash=sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1 \
+ --hash=sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb \
+ --hash=sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250 \
+ --hash=sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e \
+ --hash=sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511 \
+ --hash=sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5 \
+ --hash=sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59 \
+ --hash=sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2 \
+ --hash=sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d \
+ --hash=sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3 \
+ --hash=sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4 \
+ --hash=sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de \
+ --hash=sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9 \
+ --hash=sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833 \
+ --hash=sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0 \
+ --hash=sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9 \
+ --hash=sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d \
+ --hash=sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050 \
+ --hash=sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d \
+ --hash=sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6 \
+ --hash=sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353 \
+ --hash=sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb \
+ --hash=sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e \
+ --hash=sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8 \
+ --hash=sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495 \
+ --hash=sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2 \
+ --hash=sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd \
+ --hash=sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27 \
+ --hash=sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1 \
+ --hash=sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818 \
+ --hash=sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4 \
+ --hash=sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e \
+ --hash=sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850 \
+ --hash=sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3
+ # via -r requirements.in
+mock==5.1.0 \
+ --hash=sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744 \
+ --hash=sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d
+ # via -r requirements.in
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via bleach
+tinycss2==1.2.1 \
+ --hash=sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847 \
+ --hash=sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627
+ # via -r requirements.in
+tomli==2.0.1 \
+ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
+ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
+ # via coverage
+webencodings==0.5.1 \
+ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
+ --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
+ # via
+ # bleach
+ # tinycss2
diff --git a/requirements/py39-django32.txt b/requirements/py39-django32.txt
new file mode 100644
index 0000000..ea91274
--- /dev/null
+++ b/requirements/py39-django32.txt
@@ -0,0 +1,94 @@
+#
+# This file is autogenerated by pip-compile with Python 3.9
+# by the following command:
+#
+# requirements/compile.py
+#
+bleach==5.0.1 \
+ --hash=sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a \
+ --hash=sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c
+ # via -r requirements.in
+coverage[toml]==7.2.7 \
+ --hash=sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f \
+ --hash=sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2 \
+ --hash=sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a \
+ --hash=sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a \
+ --hash=sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01 \
+ --hash=sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6 \
+ --hash=sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7 \
+ --hash=sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f \
+ --hash=sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02 \
+ --hash=sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c \
+ --hash=sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063 \
+ --hash=sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a \
+ --hash=sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5 \
+ --hash=sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959 \
+ --hash=sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97 \
+ --hash=sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6 \
+ --hash=sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f \
+ --hash=sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9 \
+ --hash=sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5 \
+ --hash=sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f \
+ --hash=sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562 \
+ --hash=sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe \
+ --hash=sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9 \
+ --hash=sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f \
+ --hash=sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb \
+ --hash=sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb \
+ --hash=sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1 \
+ --hash=sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb \
+ --hash=sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250 \
+ --hash=sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e \
+ --hash=sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511 \
+ --hash=sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5 \
+ --hash=sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59 \
+ --hash=sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2 \
+ --hash=sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d \
+ --hash=sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3 \
+ --hash=sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4 \
+ --hash=sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de \
+ --hash=sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9 \
+ --hash=sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833 \
+ --hash=sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0 \
+ --hash=sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9 \
+ --hash=sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d \
+ --hash=sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050 \
+ --hash=sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d \
+ --hash=sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6 \
+ --hash=sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353 \
+ --hash=sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb \
+ --hash=sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e \
+ --hash=sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8 \
+ --hash=sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495 \
+ --hash=sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2 \
+ --hash=sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd \
+ --hash=sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27 \
+ --hash=sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1 \
+ --hash=sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818 \
+ --hash=sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4 \
+ --hash=sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e \
+ --hash=sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850 \
+ --hash=sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3
+ # via -r requirements.in
+mock==5.1.0 \
+ --hash=sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744 \
+ --hash=sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d
+ # via -r requirements.in
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via bleach
+tinycss2==1.2.1 \
+ --hash=sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847 \
+ --hash=sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627
+ # via -r requirements.in
+tomli==2.0.1 \
+ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
+ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
+ # via coverage
+webencodings==0.5.1 \
+ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
+ --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
+ # via
+ # bleach
+ # tinycss2
diff --git a/requirements/py39-django40.txt b/requirements/py39-django40.txt
new file mode 100644
index 0000000..ea91274
--- /dev/null
+++ b/requirements/py39-django40.txt
@@ -0,0 +1,94 @@
+#
+# This file is autogenerated by pip-compile with Python 3.9
+# by the following command:
+#
+# requirements/compile.py
+#
+bleach==5.0.1 \
+ --hash=sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a \
+ --hash=sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c
+ # via -r requirements.in
+coverage[toml]==7.2.7 \
+ --hash=sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f \
+ --hash=sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2 \
+ --hash=sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a \
+ --hash=sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a \
+ --hash=sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01 \
+ --hash=sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6 \
+ --hash=sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7 \
+ --hash=sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f \
+ --hash=sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02 \
+ --hash=sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c \
+ --hash=sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063 \
+ --hash=sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a \
+ --hash=sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5 \
+ --hash=sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959 \
+ --hash=sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97 \
+ --hash=sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6 \
+ --hash=sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f \
+ --hash=sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9 \
+ --hash=sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5 \
+ --hash=sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f \
+ --hash=sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562 \
+ --hash=sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe \
+ --hash=sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9 \
+ --hash=sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f \
+ --hash=sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb \
+ --hash=sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb \
+ --hash=sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1 \
+ --hash=sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb \
+ --hash=sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250 \
+ --hash=sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e \
+ --hash=sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511 \
+ --hash=sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5 \
+ --hash=sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59 \
+ --hash=sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2 \
+ --hash=sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d \
+ --hash=sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3 \
+ --hash=sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4 \
+ --hash=sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de \
+ --hash=sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9 \
+ --hash=sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833 \
+ --hash=sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0 \
+ --hash=sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9 \
+ --hash=sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d \
+ --hash=sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050 \
+ --hash=sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d \
+ --hash=sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6 \
+ --hash=sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353 \
+ --hash=sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb \
+ --hash=sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e \
+ --hash=sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8 \
+ --hash=sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495 \
+ --hash=sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2 \
+ --hash=sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd \
+ --hash=sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27 \
+ --hash=sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1 \
+ --hash=sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818 \
+ --hash=sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4 \
+ --hash=sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e \
+ --hash=sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850 \
+ --hash=sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3
+ # via -r requirements.in
+mock==5.1.0 \
+ --hash=sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744 \
+ --hash=sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d
+ # via -r requirements.in
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via bleach
+tinycss2==1.2.1 \
+ --hash=sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847 \
+ --hash=sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627
+ # via -r requirements.in
+tomli==2.0.1 \
+ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
+ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
+ # via coverage
+webencodings==0.5.1 \
+ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
+ --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
+ # via
+ # bleach
+ # tinycss2
diff --git a/requirements/py39-django41.txt b/requirements/py39-django41.txt
new file mode 100644
index 0000000..ea91274
--- /dev/null
+++ b/requirements/py39-django41.txt
@@ -0,0 +1,94 @@
+#
+# This file is autogenerated by pip-compile with Python 3.9
+# by the following command:
+#
+# requirements/compile.py
+#
+bleach==5.0.1 \
+ --hash=sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a \
+ --hash=sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c
+ # via -r requirements.in
+coverage[toml]==7.2.7 \
+ --hash=sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f \
+ --hash=sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2 \
+ --hash=sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a \
+ --hash=sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a \
+ --hash=sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01 \
+ --hash=sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6 \
+ --hash=sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7 \
+ --hash=sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f \
+ --hash=sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02 \
+ --hash=sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c \
+ --hash=sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063 \
+ --hash=sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a \
+ --hash=sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5 \
+ --hash=sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959 \
+ --hash=sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97 \
+ --hash=sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6 \
+ --hash=sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f \
+ --hash=sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9 \
+ --hash=sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5 \
+ --hash=sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f \
+ --hash=sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562 \
+ --hash=sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe \
+ --hash=sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9 \
+ --hash=sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f \
+ --hash=sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb \
+ --hash=sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb \
+ --hash=sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1 \
+ --hash=sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb \
+ --hash=sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250 \
+ --hash=sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e \
+ --hash=sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511 \
+ --hash=sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5 \
+ --hash=sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59 \
+ --hash=sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2 \
+ --hash=sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d \
+ --hash=sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3 \
+ --hash=sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4 \
+ --hash=sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de \
+ --hash=sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9 \
+ --hash=sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833 \
+ --hash=sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0 \
+ --hash=sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9 \
+ --hash=sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d \
+ --hash=sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050 \
+ --hash=sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d \
+ --hash=sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6 \
+ --hash=sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353 \
+ --hash=sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb \
+ --hash=sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e \
+ --hash=sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8 \
+ --hash=sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495 \
+ --hash=sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2 \
+ --hash=sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd \
+ --hash=sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27 \
+ --hash=sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1 \
+ --hash=sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818 \
+ --hash=sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4 \
+ --hash=sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e \
+ --hash=sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850 \
+ --hash=sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3
+ # via -r requirements.in
+mock==5.1.0 \
+ --hash=sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744 \
+ --hash=sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d
+ # via -r requirements.in
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via bleach
+tinycss2==1.2.1 \
+ --hash=sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847 \
+ --hash=sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627
+ # via -r requirements.in
+tomli==2.0.1 \
+ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
+ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
+ # via coverage
+webencodings==0.5.1 \
+ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
+ --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
+ # via
+ # bleach
+ # tinycss2
diff --git a/requirements/py39-django42.txt b/requirements/py39-django42.txt
new file mode 100644
index 0000000..ea91274
--- /dev/null
+++ b/requirements/py39-django42.txt
@@ -0,0 +1,94 @@
+#
+# This file is autogenerated by pip-compile with Python 3.9
+# by the following command:
+#
+# requirements/compile.py
+#
+bleach==5.0.1 \
+ --hash=sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a \
+ --hash=sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c
+ # via -r requirements.in
+coverage[toml]==7.2.7 \
+ --hash=sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f \
+ --hash=sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2 \
+ --hash=sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a \
+ --hash=sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a \
+ --hash=sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01 \
+ --hash=sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6 \
+ --hash=sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7 \
+ --hash=sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f \
+ --hash=sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02 \
+ --hash=sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c \
+ --hash=sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063 \
+ --hash=sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a \
+ --hash=sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5 \
+ --hash=sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959 \
+ --hash=sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97 \
+ --hash=sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6 \
+ --hash=sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f \
+ --hash=sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9 \
+ --hash=sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5 \
+ --hash=sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f \
+ --hash=sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562 \
+ --hash=sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe \
+ --hash=sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9 \
+ --hash=sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f \
+ --hash=sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb \
+ --hash=sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb \
+ --hash=sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1 \
+ --hash=sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb \
+ --hash=sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250 \
+ --hash=sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e \
+ --hash=sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511 \
+ --hash=sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5 \
+ --hash=sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59 \
+ --hash=sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2 \
+ --hash=sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d \
+ --hash=sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3 \
+ --hash=sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4 \
+ --hash=sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de \
+ --hash=sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9 \
+ --hash=sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833 \
+ --hash=sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0 \
+ --hash=sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9 \
+ --hash=sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d \
+ --hash=sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050 \
+ --hash=sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d \
+ --hash=sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6 \
+ --hash=sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353 \
+ --hash=sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb \
+ --hash=sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e \
+ --hash=sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8 \
+ --hash=sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495 \
+ --hash=sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2 \
+ --hash=sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd \
+ --hash=sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27 \
+ --hash=sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1 \
+ --hash=sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818 \
+ --hash=sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4 \
+ --hash=sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e \
+ --hash=sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850 \
+ --hash=sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3
+ # via -r requirements.in
+mock==5.1.0 \
+ --hash=sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744 \
+ --hash=sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d
+ # via -r requirements.in
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via bleach
+tinycss2==1.2.1 \
+ --hash=sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847 \
+ --hash=sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627
+ # via -r requirements.in
+tomli==2.0.1 \
+ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
+ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
+ # via coverage
+webencodings==0.5.1 \
+ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
+ --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
+ # via
+ # bleach
+ # tinycss2
diff --git a/requirements/requirements.in b/requirements/requirements.in
new file mode 100644
index 0000000..85e065e
--- /dev/null
+++ b/requirements/requirements.in
@@ -0,0 +1,4 @@
+bleach>=5,<6
+coverage[toml]
+mock
+tinycss2
diff --git a/setup.cfg b/setup.cfg
index 4b11a20..45ed9d2 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 3.0.1
+current_version = 3.1.0
commit = True
tag = False
@@ -8,8 +8,8 @@ search = __version__ = "{current_version}"
replace = __version__ = "{new_version}"
[bumpversion:file:docs/conf.py]
-search = version = '{current_version}'
-replace = version = '{new_version}'
+search = version = "{current_version}"
+replace = version = "{new_version}"
[bumpversion:file:CHANGELOG.md]
search =
@@ -26,46 +26,6 @@ replace =
[bdist_wheel]
universal = 1
-[flake8]
-accept-encodings = utf-8,utf-16
-max-line-length = 119
-exclude =
- *.egg-info,
- .git,
- .settings,
- .tox,
- build,
- dist,
- docs,
- requirements,
- tmp,
- *migrations*,
- tests,
- data
-ignore =
- E800,
- W503,
- C812,
- C813,
- C815,
- C818,
- C819,
- C408,
- C101
-
-[isort]
-line_length = 119
-multi_line_output = 3
-lines_after_imports = 2
-combine_as_imports = true
-include_trailing_comma = true
-balanced_wrapping = true
-skip = manage.py, migrations, .tox, .env
-known_standard_library = mock
-known_django = django
-known_first_party = django_bleach, test_project
-sections = FUTURE,STDLIB,DJANGO,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
-
[codespell]
skip = ./.git,./.env,./.venv,./.tox,./.eggs,./django_bleach/tests,./docs/_build,./testproject,./htmlcov
count =
diff --git a/setup.py b/setup.py
index ba56cc4..e910328 100755
--- a/setup.py
+++ b/setup.py
@@ -2,10 +2,8 @@
import codecs
import os
import re
-import sys
-from setuptools import find_packages, setup
-from setuptools.command.test import test as test_command
+from setuptools import find_packages, setup
try:
from sphinx.setup_command import BuildDoc
@@ -13,104 +11,78 @@
BuildDoc = None
-class Tox(test_command):
- user_options = [('tox-args=', 'a', "Arguments to pass to tox")]
-
- def initialize_options(self):
- test_command.initialize_options(self)
- self.tox_args = None
-
- def finalize_options(self):
- test_command.finalize_options(self)
- self.test_args = []
- self.test_suite = True
-
- def run_tests(self):
- # import here, cause outside the eggs aren't loaded
- import shlex
- import tox
- args = self.tox_args
- if args:
- args = shlex.split(self.tox_args)
- errno = tox.cmdline(args=args)
- sys.exit(errno)
-
-
def read(*parts):
file_path = os.path.join(os.path.dirname(__file__), *parts)
- return codecs.open(file_path, encoding='utf-8').read()
+ return codecs.open(file_path, encoding="utf-8").read()
def find_variable(variable, *parts):
version_file = read(*parts)
version_match = re.search(
- fr"^{variable} = ['\"]([^'\"]*)['\"]", version_file, re.M
+ rf"^{variable} = ['\"]([^'\"]*)['\"]", version_file, re.M
)
if version_match:
return str(version_match.group(1))
raise RuntimeError("Unable to find version string.")
-name = 'django-bleach'
-release = find_variable('__version__', 'django_bleach', '__init__.py')
-version = release.rstrip('.')
+name = "django-bleach"
+release = find_variable("__version__", "django_bleach", "__init__.py")
+version = release.rstrip(".")
setup(
name=name,
version=version,
- description='Easily use bleach with Django models and templates',
- long_description=read('README.rst'),
- long_description_content_type='text/x-rst',
- author='Tim Heap',
- maintainer='Mark Walker',
- maintainer_email='theshow+django-bleach@gmail.com',
- url='https://github.com/marksweb/django-bleach',
- license='MIT',
- packages=find_packages(exclude=('testproject*',)),
+ description="Easily use bleach with Django models and templates",
+ long_description=read("README.rst"),
+ long_description_content_type="text/x-rst",
+ author="Tim Heap",
+ maintainer="Mark Walker",
+ maintainer_email="theshow+django-bleach@gmail.com",
+ url="https://github.com/marksweb/django-bleach",
+ license="MIT",
+ packages=find_packages(exclude=("testproject*",)),
install_requires=[
- 'bleach[css]>=5,<6',
- 'Django>=3.2',
- ],
- python_requires='>=3.8',
- tests_require=[
- 'bleach[css]>=5,<6',
- 'mock',
- 'sphinx',
- 'tox'
+ "bleach[css]>=5,<6",
+ "Django>=3.2",
],
+ python_requires=">=3.8",
+ tests_require=["bleach[css]>=5,<6", "mock", "sphinx", "tox"],
cmdclass={
- 'build_sphinx': BuildDoc,
- 'test': Tox
+ "build_sphinx": BuildDoc,
},
command_options={
- 'build_sphinx': {
- 'project': ('setup.py', name),
- 'version': ('setup.py', version),
- 'release': ('setup.py', release),
- 'source_dir': ('setup.py', 'docs'),
- 'build_dir': ('setup.py', './docs/_build')
+ "build_sphinx": {
+ "project": ("setup.py", name),
+ "version": ("setup.py", version),
+ "release": ("setup.py", release),
+ "source_dir": ("setup.py", "docs"),
+ "build_dir": ("setup.py", "./docs/_build"),
}
},
package_data={},
classifiers=[
- 'Environment :: Web Environment',
- 'Intended Audience :: Developers',
- 'License :: OSI Approved :: MIT License',
- 'Operating System :: OS Independent',
- 'Programming Language :: Python',
- 'Programming Language :: Python :: 3 :: Only',
- 'Programming Language :: Python :: 3.8',
- 'Programming Language :: Python :: 3.9',
- 'Programming Language :: Python :: 3.10',
- 'Framework :: Django :: 3.2',
- 'Framework :: Django :: 4.0',
- 'Framework :: Django :: 4.1',
- 'Development Status :: 5 - Production/Stable',
+ "Environment :: Web Environment",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: MIT License",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3 :: Only",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Framework :: Django :: 3.2",
+ "Framework :: Django :: 4.0",
+ "Framework :: Django :: 4.1",
+ "Framework :: Django :: 4.2",
+ "Development Status :: 5 - Production/Stable",
],
project_urls={
- 'Documentation': 'https://django-bleach.readthedocs.io/',
- 'Release notes': 'https://github.com/marksweb/django-bleach/blob/main/CHANGELOG.md',
- 'Issues': 'https://github.com/marksweb/django-bleach/issues',
- 'Source': 'https://github.com/marksweb/django-bleach',
- }
+ "Documentation": "https://django-bleach.readthedocs.io/",
+ "Release notes": "https://github.com/marksweb/django-bleach/blob/main/CHANGELOG.md",
+ "Issues": "https://github.com/marksweb/django-bleach/issues",
+ "Source": "https://github.com/marksweb/django-bleach",
+ },
)
diff --git a/testproject/constants.py b/testproject/constants.py
index 19655b2..d3c8ae2 100644
--- a/testproject/constants.py
+++ b/testproject/constants.py
@@ -1,21 +1,12 @@
-ALLOWED_ATTRIBUTES = {
- '*': ['class', 'style'],
- 'a': ['href', 'title']
-}
+ALLOWED_ATTRIBUTES = {"*": ["class", "style"], "a": ["href", "title"]}
-ALLOWED_CSS_PROPERTIES = [
- 'color'
-]
+ALLOWED_CSS_PROPERTIES = ["color"]
ALLOWED_PROTOCOLS = [
- 'https',
- 'data',
+ "https",
+ "data",
]
ALLOWED_STYLES = ALLOWED_CSS_PROPERTIES
-ALLOWED_TAGS = [
- 'a',
- 'li',
- 'ul'
-]
+ALLOWED_TAGS = ["a", "li", "ul"]
diff --git a/testproject/forms.py b/testproject/forms.py
index 3671f1a..f70fd6b 100755
--- a/testproject/forms.py
+++ b/testproject/forms.py
@@ -14,58 +14,53 @@
class CustomBleachWidget(forms.Textarea):
-
def __init__(self, attrs=None):
- default_attrs = {'rows': 15, 'cols': 60}
+ default_attrs = {"rows": 15, "cols": 60}
default_attrs.update(attrs or {})
super().__init__(attrs=default_attrs)
class BleachForm(forms.Form):
- """ Form for testing BleachField """
- no_tags = BleachField(
- max_length=100,
- strip_tags=True,
- allowed_tags=[]
- )
+ """Form for testing BleachField"""
+
+ no_tags = BleachField(max_length=100, strip_tags=True, allowed_tags=[])
no_strip = BleachField(
- max_length=100,
- allowed_tags=None,
- allowed_attributes=None
+ max_length=100, allowed_tags=None, allowed_attributes=None
)
bleach_strip = BleachField(
max_length=100,
strip_comments=True,
strip_tags=True,
- allowed_tags=ALLOWED_TAGS
+ allowed_tags=ALLOWED_TAGS,
)
bleach_attrs = BleachField(
max_length=100,
strip_tags=False,
allowed_tags=ALLOWED_TAGS,
allowed_protocols=ALLOWED_PROTOCOLS,
- allowed_attributes=ALLOWED_ATTRIBUTES
+ allowed_attributes=ALLOWED_ATTRIBUTES,
)
bleach_styles = BleachField(
max_length=100,
strip_tags=False,
- allowed_attributes=['style'],
+ allowed_attributes=["style"],
allowed_tags=ALLOWED_TAGS,
- allowed_styles=ALLOWED_STYLES
+ allowed_styles=ALLOWED_STYLES,
)
bleach_css_sanitizer = BleachField(
max_length=100,
strip_tags=False,
- allowed_attributes=['style'],
+ allowed_attributes=["style"],
allowed_tags=ALLOWED_TAGS,
- css_sanitizer=CSSSanitizer(allowed_css_properties=ALLOWED_CSS_PROPERTIES)
+ css_sanitizer=CSSSanitizer(
+ allowed_css_properties=ALLOWED_CSS_PROPERTIES
+ ),
)
class PersonForm(forms.ModelForm):
-
class Meta:
model = Person
- fields = '__all__'
+ fields = "__all__"
diff --git a/testproject/manage.py b/testproject/manage.py
index 0bfef99..7a79795 100755
--- a/testproject/manage.py
+++ b/testproject/manage.py
@@ -1,11 +1,14 @@
#!/usr/bin/env python
import sys
import os
+
try:
from django.core.management import execute_manager
+
OLD_DJANGO = True
except ImportError:
from django.core.management import execute_from_command_line
+
OLD_DJANGO = False
if OLD_DJANGO:
@@ -20,7 +23,7 @@
)
sys.exit(1)
-BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
+BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
sys.path.insert(0, BASEDIR)
if __name__ == "__main__":
diff --git a/testproject/migrations/0001_initial.py b/testproject/migrations/0001_initial.py
index 8ab9bf8..2e66ad1 100644
--- a/testproject/migrations/0001_initial.py
+++ b/testproject/migrations/0001_initial.py
@@ -5,19 +5,30 @@
class Migration(migrations.Migration):
-
initial = True
- dependencies = [
- ]
+ dependencies = []
operations = [
migrations.CreateModel(
- name='Person',
+ name="Person",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(max_length=20)),
- ('biography', django_bleach.models.BleachField(max_length=100, verbose_name='Person biography')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("name", models.CharField(max_length=20)),
+ (
+ "biography",
+ django_bleach.models.BleachField(
+ max_length=100, verbose_name="Person biography"
+ ),
+ ),
],
),
]
diff --git a/testproject/models.py b/testproject/models.py
index 0305072..fc63969 100644
--- a/testproject/models.py
+++ b/testproject/models.py
@@ -9,9 +9,11 @@ class Person(models.Model):
name = models.CharField(max_length=20)
biography = BleachField(
max_length=100,
- verbose_name='Person biography',
- allowed_tags=['p', 'a', 'li', 'ul', 'strong'],
- allowed_attributes=['class', 'href', 'style'],
- allowed_protocols=['http', 'https'],
- css_sanitizer=CSSSanitizer(allowed_css_properties=['color', 'background-color'])
+ verbose_name="Person biography",
+ allowed_tags=["p", "a", "li", "ul", "strong"],
+ allowed_attributes=["class", "href", "style"],
+ allowed_protocols=["http", "https"],
+ css_sanitizer=CSSSanitizer(
+ allowed_css_properties=["color", "background-color"]
+ ),
)
diff --git a/testproject/settings.py b/testproject/settings.py
index 75079cc..16d2f80 100755
--- a/testproject/settings.py
+++ b/testproject/settings.py
@@ -6,47 +6,44 @@
PROJECT_PATH = os.path.abspath(os.path.dirname(__file__))
-PYTHON_VERSION = '%s.%s' % sys.version_info[:2]
+PYTHON_VERSION = "%s.%s" % sys.version_info[:2]
DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': os.path.join(PROJECT_PATH, 'django-bleach.db')
+ "default": {
+ "ENGINE": "django.db.backends.sqlite3",
+ "NAME": os.path.join(PROJECT_PATH, "django-bleach.db"),
}
}
-DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
+DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
DATABASE_SUPPORTS_TRANSACTIONS = True
INSTALLED_APPS = [
- 'django.contrib.auth',
- 'django.contrib.admin',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.sites',
- 'django.contrib.messages',
-
- 'django_bleach',
- 'testproject'
+ "django.contrib.auth",
+ "django.contrib.admin",
+ "django.contrib.contenttypes",
+ "django.contrib.sessions",
+ "django.contrib.sites",
+ "django.contrib.messages",
+ "django_bleach",
+ "testproject",
]
LANGUAGE_CODE = "en"
-LANGUAGES = (
- ('en', 'English'),
-)
+LANGUAGES = (("en", "English"),)
-ROOT_URLCONF = 'testproject.urls'
+ROOT_URLCONF = "testproject.urls"
DEBUG = True
TEMPLATES = [
{
- 'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'APP_DIRS': True,
- 'OPTIONS': {
- 'debug': False,
- 'context_processors': (
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
+ "APP_DIRS": True,
+ "OPTIONS": {
+ "debug": False,
+ "context_processors": (
"django.contrib.auth.context_processors.auth",
"django.template.context_processors.debug",
"django.template.context_processors.i18n",
@@ -54,20 +51,20 @@
"django.template.context_processors.request",
"django.template.context_processors.static",
"django.template.context_processors.tz",
- "django.contrib.messages.context_processors.messages"
- )
+ "django.contrib.messages.context_processors.messages",
+ ),
},
- 'DIRS': ('templates', )
+ "DIRS": ("templates",),
},
]
USE_TZ = True
-SECRET_KEY = 'blah'
+SECRET_KEY = "blah"
MIDDLEWARE = (
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware'
+ "django.contrib.sessions.middleware.SessionMiddleware",
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
+ "django.contrib.messages.middleware.MessageMiddleware",
)
# BLEACH_DEFAULT_WIDGET = 'testproject.forms.CustomBleachWidget'
diff --git a/testproject/urls.py b/testproject/urls.py
index d5c8aa4..fc5c223 100755
--- a/testproject/urls.py
+++ b/testproject/urls.py
@@ -4,6 +4,6 @@
urlpatterns = [
- path('', home, name='home'),
- path('model_form/', model_form, name='model_form'),
+ path("", home, name="home"),
+ path("model_form/", model_form, name="model_form"),
]
diff --git a/testproject/views.py b/testproject/views.py
index ba57cef..806b0b9 100755
--- a/testproject/views.py
+++ b/testproject/views.py
@@ -10,20 +10,19 @@ def home(request):
if request.POST:
form = BleachForm(request.POST)
if form.is_valid():
- return HttpResponseRedirect(request.path + '?ok')
+ return HttpResponseRedirect(request.path + "?ok")
else:
form = BleachForm()
- return render(request, 'home.html', {'form': form})
+ return render(request, "home.html", {"form": form})
def model_form(request):
-
if request.POST:
form = PersonForm(request.POST)
if form.is_valid():
form.save()
- return HttpResponseRedirect('?ok')
+ return HttpResponseRedirect("?ok")
else:
form = PersonForm()
try:
@@ -31,7 +30,11 @@ def model_form(request):
except OperationalError:
people = []
- return render(request, 'model_form.html', {
- 'form': form,
- 'people': people,
- })
+ return render(
+ request,
+ "model_form.html",
+ {
+ "form": form,
+ "people": people,
+ },
+ )
diff --git a/tox.ini b/tox.ini
index 88c2a75..ac9926a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,64 +1,23 @@
[tox]
-envlist =
- {py38,p39,p310}-django{32,4},
- flake8,
- docs,
- coverage,
- upload_coverage
+requires =
+ tox>=4.2
+env_list =
+ py312-django{42}
+ py311-django{42, 41}
+ py310-django{42, 41, 40, 32}
+ py39-django{42, 41, 40, 32}
+ py38-django{42, 41, 40, 32}
[testenv]
-commands =
- python -Wd testproject/manage.py test django_bleach
-
-setenv =
- PYTHONDONTWRITEBYTECODE=1
-
+package = wheel
deps =
- bleach
- django32: Django>=3.2,<3.3
- django4: Django>=4.0,<4.2
- mock
-
-extras =
- test
-
-[testenv:coverage]
-deps =
- -rtestproject/requirements.txt
-commands =
- coverage erase
- coverage run --rcfile .coveragerc testproject/manage.py test --failfast django_bleach
- coverage report --rcfile .coveragerc
- coverage html --rcfile .coveragerc
- coverage xml --rcfile .coveragerc
-
-[testenv:flake8]
-basepython = python
-deps =
- -rtestproject/requirements.txt
-commands =
- {envpython} -m flake8 {toxinidir}/django_bleach
-
-[testenv:docs]
-changedir = docs
-deps =
- sphinx
- sphinx-rtd-theme
-commands =
- sphinx-build -W -b html -d {envtmpdir}/doctrees . {toxinidir}/docs/_build/html
-
-[testenv:upload_coverage]
-deps = codacy-coverage
-passenv =
- CODACY_PROJECT_TOKEN
+ -r {toxinidir}/requirements/{envname}.txt
+set_env =
+ PYTHONDEVMODE = 1
+ PYTHONDONTWRITEBYTECODE=1
commands =
- python-codacy-coverage -r coverage.xml
-
-[flake8]
-exclude = *.egg-info,.git,.settings,.tox,build,dist,docs,requirements,tmp,*migrations*,tests,data
-ignore = E800, W503, C812, C813, C815, C818, C819, C408, C101
-max-line-length = 119
-# flake8-quotes
-inline-quotes = double
-# flake8-tidy-imports
-banned-modules = __future__ = this project supports python3 only
+ python \
+ -W error::ResourceWarning \
+ -W error::DeprecationWarning \
+ -W error::PendingDeprecationWarning \
+ -m coverage run testproject/manage.py test django_bleach