Skip to content

Commit e00abf6

Browse files
authored
[deps] Upgrade Django 5.x and channels #987
Added support for Django >=5.1,<5.3 and Python >=3.12, 3.14 - Dropped support for Python < 3.9 - Dropped support for Django < 4.2 - Upgraded django-taggit~=6.0.0 - Upgraded postgres version in docker-compose (Django 5.2 requires Postgresql 14+) Closes #987
1 parent 4881326 commit e00abf6

File tree

21 files changed

+78
-77
lines changed

21 files changed

+78
-77
lines changed

.github/workflows/ci.yml

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,30 @@ on:
1313
jobs:
1414
build:
1515
name: Python==${{ matrix.python-version }} | ${{ matrix.django-version }}
16-
runs-on: ubuntu-22.04
16+
runs-on: ubuntu-24.04
1717

1818
strategy:
1919
fail-fast: false
2020
matrix:
2121
python-version:
22-
- "3.8"
2322
- "3.9"
2423
- "3.10"
24+
- "3.11"
25+
- "3.12"
26+
- "3.13"
2527
django-version:
26-
- django~=3.2.0
27-
- django~=4.1.0
2828
- django~=4.2.0
29+
- django~=5.1.0
30+
- django~=5.2.0
31+
exclude:
32+
# Django 5.1+ requires Python >=3.10
33+
- python-version: "3.9"
34+
django-version: django~=5.1.0
35+
- python-version: "3.9"
36+
django-version: django~=5.2.0
37+
# Python 3.13 supported only in Django >=5.1.3
38+
- python-version: "3.13"
39+
django-version: django~=4.2.0
2940

3041
steps:
3142
- uses: actions/checkout@v4

docker-compose.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# NOTE: This Docker image is for development purposes only.
22

3-
version: "3"
4-
53
services:
64
controller:
75
image: openwisp/controller-development:latest
@@ -21,7 +19,7 @@ services:
2119
entrypoint: redis-server --appendonly yes
2220

2321
postgres:
24-
image: postgis/postgis:13-3.3-alpine
22+
image: postgis/postgis:17-3.5-alpine
2523
environment:
2624
POSTGRES_PASSWORD: openwisp2
2725
POSTGRES_USER: openwisp2

docs/developer/installation.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Developer Installation Instructions
1010
Dependencies
1111
------------
1212

13-
- Python >= 3.8
13+
- Python >= 3.9
1414
- OpenSSL
1515

1616
Installing for Development

openwisp_controller/config/base/vpn.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from copy import deepcopy
77
from subprocess import CalledProcessError, TimeoutExpired
88

9-
import django
109
import shortuuid
1110
from cache_memoize import cache_memoize
1211
from django.core.cache import cache
@@ -856,13 +855,9 @@ def register_auto_ip_stopper(cls, func):
856855
cls._auto_ip_stopper_funcs.append(func)
857856

858857
def _get_unique_checks(self, exclude=None, include_meta_constraints=False):
859-
if django.VERSION < (4, 1):
860-
# TODO: Remove when dropping support for Django 3.2
861-
unique_checks, date_checks = super()._get_unique_checks(exclude)
862-
else:
863-
unique_checks, date_checks = super()._get_unique_checks(
864-
exclude, include_meta_constraints
865-
)
858+
unique_checks, date_checks = super()._get_unique_checks(
859+
exclude, include_meta_constraints
860+
)
866861

867862
if not self.vpn._vxlan_vni:
868863
# If VNI is not specified in VXLAN tunnel configuration,

openwisp_controller/config/tests/test_controller.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1346,7 +1346,7 @@ def test_ip_fields_not_duplicated(self):
13461346
self.assertIsNone(c1.device.management_ip)
13471347
self.assertEqual(c2.device.management_ip, '192.168.1.99')
13481348
# other organization is not affected
1349-
self.assertEquals(c3.device.last_ip, '127.0.0.1')
1349+
self.assertEqual(c3.device.last_ip, '127.0.0.1')
13501350
self.assertEqual(c3.device.management_ip, '192.168.1.99')
13511351

13521352
with self.subTest('test interaction with DeviceChecksumView caching'):

openwisp_controller/config/tests/test_template.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
from openwisp_utils.tests import catch_signal
1414

15-
from ...tests.utils import TransactionTestMixin
1615
from .. import settings as app_settings
1716
from ..signals import config_modified, config_status_changed
1817
from ..tasks import logger as task_logger
@@ -356,7 +355,10 @@ def test_context_regression(self):
356355
template_qs = Template.objects.filter(type='vpn')
357356
self.assertEqual(template_qs.count(), 1)
358357
t = template_qs.first()
359-
self.assertDictContainsSubset(_original_context, t.get_context())
358+
context = t.get_context()
359+
# check all items from original context exist in template context
360+
for key, value in _original_context.items():
361+
self.assertEqual(context.get(key), value)
360362
self.assertEqual(app_settings.CONTEXT, _original_context)
361363

362364
with self.subTest(
@@ -517,7 +519,6 @@ def test_regression_preventing_from_fixing_invalid_conf(self):
517519

518520

519521
class TestTemplateTransaction(
520-
TransactionTestMixin,
521522
CreateConfigTemplateMixin,
522523
TestVpnX509Mixin,
523524
TransactionTestCase,
@@ -554,7 +555,7 @@ def test_config_status_modified_after_change(self):
554555
with catch_signal(config_status_changed) as handler:
555556
t.config['interfaces'][0]['name'] = 'eth2'
556557
t.full_clean()
557-
with self.assertNumQueries(9):
558+
with self.assertNumQueries(10):
558559
t.save()
559560
c.refresh_from_db()
560561
handler.assert_not_called()

openwisp_controller/connection/base/models.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import collections
22
import logging
33

4+
import django
45
import jsonschema
56
from django.core.exceptions import ValidationError
67
from django.db import models, transaction
@@ -24,6 +25,7 @@
2425
ORGANIZATION_COMMAND_SCHEMA,
2526
ORGANIZATION_ENABLED_COMMANDS,
2627
get_command_callable,
28+
get_command_choices,
2729
get_command_schema,
2830
)
2931
from ..exceptions import NoWorkingDeviceConnectionError
@@ -408,7 +410,18 @@ class AbstractCommand(TimeStampedEditableModel):
408410
status = models.CharField(
409411
max_length=12, choices=STATUS_CHOICES, default=STATUS_CHOICES[0][0]
410412
)
411-
type = models.CharField(max_length=16, choices=COMMAND_CHOICES)
413+
type = models.CharField(
414+
max_length=16,
415+
choices=(
416+
COMMAND_CHOICES
417+
if django.VERSION < (5, 0)
418+
# In Django 5.0+, choices are normalized at model definition,
419+
# creating a static list of tuples that doesn't update when command
420+
# are dynamically registered or unregistered. Using a callable
421+
# ensures we always get the current choices from the registry.
422+
else get_command_choices
423+
),
424+
)
412425
input = JSONField(
413426
blank=True,
414427
null=True,

openwisp_controller/connection/commands.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,10 @@ def _unregister_command_choice(command):
155155
ORGANIZATION_COMMAND_SCHEMA[org_id] = OrderedDict()
156156
for command in commands:
157157
ORGANIZATION_COMMAND_SCHEMA[org_id][command] = COMMANDS[command]['schema']
158+
159+
160+
def get_command_choices():
161+
"""
162+
Returns the command choices.
163+
"""
164+
return COMMAND_CHOICES

openwisp_controller/connection/migrations/0007_command.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import collections
44
import uuid
55

6+
import django
67
import django.db.migrations.operations.special
78
import django.db.models.deletion
89
import django.utils.timezone
@@ -11,7 +12,7 @@
1112
import swapper
1213
from django.db import migrations, models
1314

14-
from ..commands import COMMAND_CHOICES
15+
from ..commands import COMMAND_CHOICES, get_command_choices
1516
from . import assign_command_permissions_to_groups
1617

1718

@@ -65,7 +66,9 @@ class Migration(migrations.Migration):
6566
(
6667
'type',
6768
models.CharField(
68-
choices=COMMAND_CHOICES,
69+
choices=COMMAND_CHOICES
70+
if django.VERSION < (5, 0)
71+
else get_command_choices,
6972
max_length=16,
7073
),
7174
),

openwisp_controller/connection/tests/test_models.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
from openwisp_utils.tests import capture_any_output, catch_signal
1313

14-
from ...tests.utils import TransactionTestMixin
1514
from .. import settings as app_settings
1615
from ..commands import (
1716
COMMANDS,
@@ -888,7 +887,7 @@ def test_command_multiple_connections(self, connect_mocked):
888887
self.assertIn(command.connection, [dc1, dc2])
889888

890889

891-
class TestModelsTransaction(TransactionTestMixin, BaseTestModels, TransactionTestCase):
890+
class TestModelsTransaction(BaseTestModels, TransactionTestCase):
892891
def _prepare_conf_object(self, organization=None):
893892
if not organization:
894893
organization = self._create_org(name='org1')
@@ -1121,12 +1120,12 @@ def test_chunk_size(self):
11211120
organization=org, name='device3', mac_address='33:33:33:33:33:33'
11221121
)
11231122
)
1124-
with self.assertNumQueries(31):
1123+
with self.assertNumQueries(32):
11251124
credential = self._create_credentials(auto_add=True, organization=org)
11261125
self.assertEqual(credential.deviceconnection_set.count(), 3)
11271126

11281127
with mock.patch.object(Credentials, 'chunk_size', 2):
1129-
with self.assertNumQueries(33):
1128+
with self.assertNumQueries(35):
11301129
credential = self._create_credentials(
11311130
name='Mocked Credential', auto_add=True, organization=org
11321131
)

0 commit comments

Comments
 (0)