Merge pull request #443 from JuroOravec/jo-drop-support-django-32-40-41

This commit is contained in:
Juro Oravec 2024-04-19 09:41:35 +02:00 committed by GitHub
commit be4b1f1a02
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 95 additions and 84 deletions

View file

@ -144,10 +144,10 @@ Django-components supports all supported combinations versions of [Django](https
| Python version | Django version |
|----------------|--------------------------|
| 3.8 | 3.2, 4.0, 4.1, 4.2 |
| 3.9 | 3.2, 4.0, 4.1, 4.2 |
| 3.10 | 3.2, 4.0, 4.1, 4.2, 5.0 |
| 3.11 | 4.1, 4.2, 5.0 |
| 3.8 | 4.2 |
| 3.9 | 4.2 |
| 3.10 | 4.2, 5.0 |
| 3.11 | 4.2, 5.0 |
| 3.12 | 4.2, 5.0 |
## Create your first component

View file

@ -6,8 +6,7 @@ from django.test import override_settings
from django_components import component
from django_components.middleware import CSS_DEPENDENCY_PLACEHOLDER, JS_DEPENDENCY_PLACEHOLDER
from tests.django_test_setup import * # NOQA
from tests.testutils import Django30CompatibleSimpleTestCase as SimpleTestCase
from tests.testutils import create_and_process_template_response
from tests.testutils import BaseTestCase, create_and_process_template_response
class SlottedComponent(component.Component):
@ -67,7 +66,7 @@ EXPECTED_JS = """<script src="test.js"></script>"""
@override_settings(COMPONENTS={"RENDER_DEPENDENCIES": True})
class RenderBenchmarks(SimpleTestCase):
class RenderBenchmarks(BaseTestCase):
def setUp(self):
component.registry.clear()
component.registry.register("test_component", SlottedComponent)

View file

@ -14,9 +14,6 @@ authors = [
]
classifiers = [
"Framework :: Django",
"Framework :: Django :: 3.2",
"Framework :: Django :: 4.0",
"Framework :: Django :: 4.1",
"Framework :: Django :: 4.2",
"Framework :: Django :: 5.0",
"Operating System :: OS Independent",
@ -29,7 +26,7 @@ classifiers = [
"Programming Language :: Python :: 3.12",
]
dependencies = [
'Django>=3.2',
'Django>=4.2',
]
license = {text = "MIT"}

View file

@ -1,7 +1,7 @@
import re
import textwrap
from collections import defaultdict
from typing import Dict, List, Tuple
from typing import Any, Callable, Dict, List, Tuple
from urllib import request
Version = Tuple[int, ...]
@ -37,7 +37,7 @@ def get_python_supported_version(url: str) -> list[Version]:
return parse_supported_versions(content)
def get_supported_versions(url: str):
def get_django_to_pythoon_versions(url: str):
with request.urlopen(url) as response:
response_content = response.read()
@ -66,6 +66,32 @@ def get_supported_versions(url: str):
return parse_supported_versions(content)
def get_django_supported_versions(url: str) -> List[Tuple[int, ...]]:
"""Extract Django versions from the HTML content, e.g. `5.0` or `4.2`"""
with request.urlopen(url) as response:
response_content = response.read()
content = response_content.decode("utf-8")
content = cut_by_content(
content,
"<table class='django-supported-versions'>",
"</table>",
)
rows = re.findall(r"<tr>(.*?)</tr>", content.replace("\n", " "))
versions: List[Tuple[int, ...]] = []
# NOTE: Skip first row as that's headers
for row in rows[1:]:
data: List[str] = re.findall(r"<td>(.*?)</td>", row)
# NOTE: First column is version like `5.0` or `4.2 LTS`
version_with_test = data[0]
version = version_with_test.split(" ")[0]
version_tuple = tuple(map(int, version.split(".")))
versions.append(version_tuple)
return versions
def get_latest_version(url: str):
with request.urlopen(url) as response:
response_content = response.read()
@ -200,14 +226,20 @@ def build_ci_python_versions(python_to_django: Dict[str, str]):
return lines_formatted
def filter_dict(d: Dict, filter_fn: Callable[[Any], bool]):
return dict(filter(filter_fn, d.items()))
def main():
active_python = get_python_supported_version("https://devguide.python.org/versions/")
django_to_python = get_supported_versions("https://docs.djangoproject.com/en/dev/faq/install/")
django_to_python = get_django_to_pythoon_versions("https://docs.djangoproject.com/en/dev/faq/install/")
django_supported_versions = get_django_supported_versions("https://www.djangoproject.com/download/")
latest_version = get_latest_version("https://www.djangoproject.com/download/")
python_to_django = build_python_to_django(django_to_python, latest_version)
supported_django_to_python = filter_dict(django_to_python, lambda item: item[0] in django_supported_versions)
python_to_django = build_python_to_django(supported_django_to_python, latest_version)
python_to_django = dict(filter(lambda item: item[0] in active_python, python_to_django.items()))
python_to_django = filter_dict(python_to_django, lambda item: item[0] in active_python)
tox_envlist = build_tox_envlist(python_to_django)
print("Add this to tox.ini:\n")

View file

@ -6,7 +6,7 @@ from django.urls import include, path
# isort: off
from .django_test_setup import settings
from .testutils import Django30CompatibleSimpleTestCase as SimpleTestCase
from .testutils import BaseTestCase
# isort: on
@ -18,7 +18,7 @@ urlpatterns = [
]
class TestAutodiscover(SimpleTestCase):
class TestAutodiscover(BaseTestCase):
def setUp(self):
settings.SETTINGS_MODULE = "tests.test_autodiscover" # noqa
@ -38,7 +38,7 @@ class TestAutodiscover(SimpleTestCase):
self.assertEqual(imported_components_count, 1)
class TestLoaderSettingsModule(SimpleTestCase):
class TestLoaderSettingsModule(BaseTestCase):
def tearDown(self) -> None:
del settings.SETTINGS_MODULE # noqa
@ -106,7 +106,7 @@ class TestLoaderSettingsModule(SimpleTestCase):
)
class TestBaseDir(SimpleTestCase):
class TestBaseDir(BaseTestCase):
def setUp(self):
settings.BASE_DIR = Path(__file__).parent.resolve() / "test_structures" / "test_structure_1" # noqa
settings.SETTINGS_MODULE = "tests_fake.test_autodiscover_fake" # noqa
@ -125,7 +125,7 @@ class TestBaseDir(SimpleTestCase):
self.assertEqual(sorted(dirs), sorted(expected))
class TestFilepathToPythonModule(SimpleTestCase):
class TestFilepathToPythonModule(BaseTestCase):
def test_prepares_path(self):
self.assertEqual(
_filepath_to_python_module(Path("tests.py")),

View file

@ -8,14 +8,14 @@ from django.test import override_settings
# isort: off
from .django_test_setup import * # NOQA
from .testutils import Django30CompatibleSimpleTestCase as SimpleTestCase
from .testutils import BaseTestCase
# isort: on
from django_components import component
class ComponentTest(SimpleTestCase):
class ComponentTest(BaseTestCase):
def test_empty_component(self):
class EmptyComponent(component.Component):
pass
@ -260,7 +260,7 @@ class ComponentTest(SimpleTestCase):
)
class InlineComponentTest(SimpleTestCase):
class InlineComponentTest(BaseTestCase):
def test_inline_html_component(self):
class InlineHTMLComponent(component.Component):
template = "<div class='inline'>Hello Inline</div>"
@ -382,7 +382,7 @@ class InlineComponentTest(SimpleTestCase):
)
class ComponentMediaTests(SimpleTestCase):
class ComponentMediaTests(BaseTestCase):
def test_component_media_with_strings(self):
class SimpleComponent(component.Component):
class Media:
@ -491,7 +491,7 @@ class ComponentMediaTests(SimpleTestCase):
)
class ComponentIsolationTests(SimpleTestCase):
class ComponentIsolationTests(BaseTestCase):
def setUp(self):
class SlottedComponent(component.Component):
template_name = "slotted_template.html"
@ -539,7 +539,7 @@ class ComponentIsolationTests(SimpleTestCase):
)
class SlotBehaviorTests(SimpleTestCase):
class SlotBehaviorTests(BaseTestCase):
def setUp(self):
class SlottedComponent(component.Component):
template_name = "slotted_template.html"

View file

@ -7,7 +7,7 @@ from django.urls import include, path
# isort: off
from .django_test_setup import * # noqa
from .testutils import Django30CompatibleSimpleTestCase as SimpleTestCase
from .testutils import BaseTestCase
# isort: on
@ -110,7 +110,7 @@ class CustomClient(Client):
super().__init__(*args, **kwargs)
class TestComponentAsView(SimpleTestCase):
class TestComponentAsView(BaseTestCase):
def setUp(self):
self.client = CustomClient()

View file

@ -6,7 +6,7 @@ from django.test import override_settings
from django_components import component
from .django_test_setup import * # NOQA
from .testutils import Django30CompatibleSimpleTestCase as SimpleTestCase
from .testutils import BaseTestCase
class SimpleComponent(component.Component):
@ -77,7 +77,7 @@ component.registry.register(name="simple_component", component=SimpleComponent)
component.registry.register(name="outer_context_component", component=OuterContextComponent)
class ContextTests(SimpleTestCase):
class ContextTests(BaseTestCase):
def test_nested_component_context_shadows_parent_with_unfilled_slots_and_component_tag(
self,
):
@ -198,7 +198,7 @@ class ContextTests(SimpleTestCase):
self.assertNotIn("<h1>Shadowing variable = NOT SHADOWED</h1>", rendered, rendered)
class ParentArgsTests(SimpleTestCase):
class ParentArgsTests(BaseTestCase):
def test_parent_args_can_be_drawn_from_context(self):
template = Template(
"{% load component_tags %}{% component_dependencies %}"
@ -241,7 +241,7 @@ class ParentArgsTests(SimpleTestCase):
self.assertNotIn("<h1>Shadowing variable = NOT SHADOWED</h1>", rendered, rendered)
class ContextCalledOnceTests(SimpleTestCase):
class ContextCalledOnceTests(BaseTestCase):
def test_one_context_call_with_simple_component(self):
template = Template(
"{% load component_tags %}{% component_dependencies %}"
@ -302,7 +302,7 @@ class ContextCalledOnceTests(SimpleTestCase):
)
class ComponentsCanAccessOuterContext(SimpleTestCase):
class ComponentsCanAccessOuterContext(BaseTestCase):
def test_simple_component_can_use_outer_context(self):
template = Template(
"{% load component_tags %}{% component_dependencies %}"
@ -312,7 +312,7 @@ class ComponentsCanAccessOuterContext(SimpleTestCase):
self.assertIn("outer_value", rendered, rendered)
class IsolatedContextTests(SimpleTestCase):
class IsolatedContextTests(BaseTestCase):
def test_simple_component_can_pass_outer_context_in_args(self):
template = Template(
"{% load component_tags %}{% component_dependencies %}"
@ -330,7 +330,7 @@ class IsolatedContextTests(SimpleTestCase):
self.assertNotIn("outer_value", rendered, rendered)
class IsolatedContextSettingTests(SimpleTestCase):
class IsolatedContextSettingTests(BaseTestCase):
def setUp(self):
self.patcher = patch(
"django_components.app_settings.AppSettings.CONTEXT_BEHAVIOR",
@ -385,7 +385,7 @@ class IsolatedContextSettingTests(SimpleTestCase):
self.assertNotIn("outer_value", rendered, rendered)
class OuterContextPropertyTests(SimpleTestCase):
class OuterContextPropertyTests(BaseTestCase):
@override_settings(
COMPONENTS={"context_behavior": "global"},
)

View file

@ -9,8 +9,7 @@ from django_components.middleware import ComponentDependencyMiddleware
from .django_test_setup import * # NOQA
from .test_templatetags import SimpleComponent
from .testutils import Django30CompatibleSimpleTestCase as SimpleTestCase
from .testutils import create_and_process_template_response
from .testutils import BaseTestCase, create_and_process_template_response
class SimpleComponentAlternate(component.Component):
@ -44,7 +43,7 @@ class MultistyleComponent(component.Component):
@override_settings(COMPONENTS={"RENDER_DEPENDENCIES": True})
class ComponentMediaRenderingTests(SimpleTestCase):
class ComponentMediaRenderingTests(BaseTestCase):
def setUp(self):
# NOTE: component.registry is global, so need to clear before each test
component.registry.clear()

View file

@ -1,10 +1,10 @@
from django.conf import settings
from .django_test_setup import * # NOQA
from .testutils import Django30CompatibleSimpleTestCase as SimpleTestCase
from .testutils import BaseTestCase
class ValidateWrongContextBehaviorValueTestCase(SimpleTestCase):
class ValidateWrongContextBehaviorValueTestCase(BaseTestCase):
def setUp(self) -> None:
settings.COMPONENTS["context_behavior"] = "invalid_value"
return super().setUp()
@ -20,7 +20,7 @@ class ValidateWrongContextBehaviorValueTestCase(SimpleTestCase):
app_settings.CONTEXT_BEHAVIOR
class ValidateCorrectContextBehaviorValueTestCase(SimpleTestCase):
class ValidateCorrectContextBehaviorValueTestCase(BaseTestCase):
def setUp(self) -> None:
settings.COMPONENTS["context_behavior"] = "isolated"
return super().setUp()

View file

@ -6,7 +6,7 @@ from django.template import Context, Template, TemplateSyntaxError
# isort: off
from .django_test_setup import * # NOQA
from .testutils import Django30CompatibleSimpleTestCase as SimpleTestCase
from .testutils import BaseTestCase
# isort: on
@ -85,7 +85,7 @@ class ComponentWithDefaultAndRequiredSlot(component.Component):
template_name = "template_with_default_and_required_slot.html"
class ComponentTemplateTagTest(SimpleTestCase):
class ComponentTemplateTagTest(BaseTestCase):
def setUp(self):
# NOTE: component.registry is global, so need to clear before each test
component.registry.clear()
@ -202,7 +202,7 @@ class ComponentTemplateTagTest(SimpleTestCase):
template.render(Context({}))
class ComponentSlottedTemplateTagTest(SimpleTestCase):
class ComponentSlottedTemplateTagTest(BaseTestCase):
def setUp(self):
# NOTE: component.registry is global, so need to clear before each test
component.registry.clear()
@ -522,7 +522,7 @@ class ComponentSlottedTemplateTagTest(SimpleTestCase):
raise e
class SlottedTemplateRegressionTests(SimpleTestCase):
class SlottedTemplateRegressionTests(BaseTestCase):
def setUp(self):
# NOTE: component.registry is global, so need to clear before each test
component.registry.clear()
@ -565,7 +565,7 @@ class SlottedTemplateRegressionTests(SimpleTestCase):
)
class MultiComponentTests(SimpleTestCase):
class MultiComponentTests(BaseTestCase):
def setUp(self):
component.registry.clear()
@ -623,7 +623,7 @@ class MultiComponentTests(SimpleTestCase):
self.assertHTMLEqual(rendered, self.expected_result("", second_slot_content))
class TemplateInstrumentationTest(SimpleTestCase):
class TemplateInstrumentationTest(BaseTestCase):
saved_render_method: Callable # Assigned during setup.
@classmethod
@ -685,7 +685,7 @@ class TemplateInstrumentationTest(SimpleTestCase):
self.assertIn("simple_template.html", templates_used)
class NestedSlotTests(SimpleTestCase):
class NestedSlotTests(BaseTestCase):
class NestedComponent(component.Component):
template_name = "nested_slot_template.html"
@ -744,7 +744,7 @@ class NestedSlotTests(SimpleTestCase):
self.assertHTMLEqual(rendered, "<p>Override</p>")
class ConditionalSlotTests(SimpleTestCase):
class ConditionalSlotTests(BaseTestCase):
class ConditionalComponent(component.Component):
template_name = "conditional_template.html"
@ -819,7 +819,7 @@ class ConditionalSlotTests(SimpleTestCase):
self.assertHTMLEqual(rendered, '<p id="a">Override A</p><p id="b">Override B</p>')
class SlotSuperTests(SimpleTestCase):
class SlotSuperTests(BaseTestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
@ -906,7 +906,7 @@ class SlotSuperTests(SimpleTestCase):
)
class TemplateSyntaxErrorTests(SimpleTestCase):
class TemplateSyntaxErrorTests(BaseTestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
@ -1013,7 +1013,7 @@ class TemplateSyntaxErrorTests(SimpleTestCase):
).render(Context({}))
class ComponentNestingTests(SimpleTestCase):
class ComponentNestingTests(BaseTestCase):
@classmethod
def setUpClass(cls) -> None:
super().setUpClass()
@ -1081,7 +1081,7 @@ class ComponentNestingTests(SimpleTestCase):
self.assertHTMLEqual(rendered, expected)
class ConditionalIfFilledSlotsTests(SimpleTestCase):
class ConditionalIfFilledSlotsTests(BaseTestCase):
class ComponentWithConditionalSlots(component.Component):
template_name = "template_with_conditional_slots.html"
@ -1197,7 +1197,7 @@ class ConditionalIfFilledSlotsTests(SimpleTestCase):
self.assertHTMLEqual(rendered, expected)
class RegressionTests(SimpleTestCase):
class RegressionTests(BaseTestCase):
"""Ensure we don't break the same thing AGAIN."""
def setUp(self):
@ -1244,7 +1244,7 @@ class RegressionTests(SimpleTestCase):
self.assertHTMLEqual(rendered, expected)
class IterationFillTest(SimpleTestCase):
class IterationFillTest(BaseTestCase):
"""Tests a behaviour of {% fill .. %} tag which is inside a template {% for .. %} loop."""
class ComponentSimpleSlotInALoop(django_components.component.Component):

View file

@ -2,7 +2,7 @@ from unittest.mock import Mock
from django.template import Context
from django.template.response import TemplateResponse
from django.test import SimpleTestCase, TestCase
from django.test import SimpleTestCase
from django_components.middleware import ComponentDependencyMiddleware
@ -11,21 +11,8 @@ response_stash = None
middleware = ComponentDependencyMiddleware(get_response=lambda _: response_stash)
class Django30CompatibleSimpleTestCase(SimpleTestCase):
def assertHTMLEqual(self, left, right):
left = left.replace(' type="text/javascript"', "")
left = left.replace(' type="text/css"', "")
right = right.replace(' type="text/javascript"', "")
right = right.replace(' type="text/css"', "")
super(Django30CompatibleSimpleTestCase, self).assertHTMLEqual(left, right)
def assertInHTML(self, needle, haystack, count=None, msg_prefix=""):
haystack = haystack.replace(' type="text/javascript"', "")
haystack = haystack.replace(' type="text/css"', "")
super().assertInHTML(needle, haystack, count, msg_prefix)
class Django30CompatibleTestCase(Django30CompatibleSimpleTestCase, TestCase):
# TODO: Use this class to manage component registry cleanup before/after tests.
class BaseTestCase(SimpleTestCase):
pass

19
tox.ini
View file

@ -4,10 +4,10 @@
[tox]
envlist =
py38-django{32,40,41,42}
py39-django{32,40,41,42}
py310-django{32,40,41,42,50}
py311-django{41,42,50}
py38-django{42}
py39-django{42}
py310-django{42,50}
py311-django{42,50}
py312-django{42,50}
flake8
isort
@ -16,10 +16,10 @@ envlist =
[gh-actions]
python =
3.8: py38-django{32,40,41,42}
3.9: py39-django{32,40,41,42}
3.10: py310-django{32,40,41,42,50}
3.11: py311-django{41,42,50}
3.8: py38-django{42}
3.9: py39-django{42}
3.10: py310-django{42,50}
3.11: py311-django{42,50}
3.12: py312-django{42,50}, flake8, isort, coverage, mypy
fail_on_no_env = True
@ -29,9 +29,6 @@ isolated_build = true
package = wheel
wheel_build_env = .pkg
deps =
django32: Django>=3.2,<3.3
django40: Django>=4.0,<4.1
django41: Django>=4.1,<4.2
django42: Django>=4.2,<4.3
django50: Django>=5.0,<5.1
pytest