Repository: openwisp/openwisp-utils (language: python)

Issue:
[deps] Add support for django 3.2 and django 4.0 alpha
Let's make sure this module can run with Django 3.2 and Django 4.0 alpha.

Let's also test with python 3.8 on CI. We can drop python 3.6 and django 2.2.


Discussion / hints:
Hey I am interested in contributing to the open wisp ecosystem. Can you tell me what needs to be done?
Hi @bamblebam and welcome,

the task consists in running this module with the django versions mentioned, fix any tests which may fail, update the CI config accordingly: https://github.com/openwisp/openwisp-utils/blob/master/.github/workflows/ci.yml#L24-L29. 
Alright I will take a look

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7b42e748..96f79b27 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -21,12 +21,16 @@ jobs:
       fail-fast: false
       matrix:
         python-version:
-          - 3.6
           - 3.7
+          - 3.8
         django-version:
-          - django~=2.2.0
           - django~=3.0.0
           - django~=3.1.0
+          - django~=3.2.0
+        include:
+          - django-version: django~=4.0a1
+            python-version: 3.8
+            experimental: true
 
     steps:
     - uses: actions/checkout@v2
diff --git a/openwisp_utils/admin.py b/openwisp_utils/admin.py
index 12cb5521..144c6efd 100644
--- a/openwisp_utils/admin.py
+++ b/openwisp_utils/admin.py
@@ -1,6 +1,6 @@
 from django.contrib.admin import ModelAdmin, StackedInline
 from django.urls import reverse
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
 
 
 class TimeReadonlyAdminMixin(object):
diff --git a/openwisp_utils/admin_theme/admin.py b/openwisp_utils/admin_theme/admin.py
index 56988f79..54a3e452 100644
--- a/openwisp_utils/admin_theme/admin.py
+++ b/openwisp_utils/admin_theme/admin.py
@@ -2,7 +2,7 @@
 
 from django.conf import settings
 from django.contrib import admin
-from django.utils.translation import ugettext_lazy
+from django.utils.translation import gettext_lazy
 
 from . import settings as app_settings
 from .dashboard import get_dashboard_context
@@ -16,7 +16,7 @@ class OpenwispAdminSite(admin.AdminSite):
     # h1 text
     site_header = getattr(settings, 'OPENWISP_ADMIN_SITE_HEADER', 'OpenWISP')
     # text at the top of the admin index page
-    index_title = ugettext_lazy(
+    index_title = gettext_lazy(
         getattr(settings, 'OPENWISP_ADMIN_INDEX_TITLE', 'Network Administration')
     )
     enable_nav_sidebar = False
diff --git a/openwisp_utils/admin_theme/apps.py b/openwisp_utils/admin_theme/apps.py
index 662c8f42..74e1c206 100644
--- a/openwisp_utils/admin_theme/apps.py
+++ b/openwisp_utils/admin_theme/apps.py
@@ -1,5 +1,5 @@
 from django.apps import AppConfig
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
 
 from . import settings as app_settings
 from .checks import admin_theme_settings_checks
diff --git a/openwisp_utils/base.py b/openwisp_utils/base.py
index 03492e0a..5daf993f 100644
--- a/openwisp_utils/base.py
+++ b/openwisp_utils/base.py
@@ -1,7 +1,7 @@
 import uuid
 
 from django.db import models
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
 from model_utils.fields import AutoCreatedField, AutoLastModifiedField
 from openwisp_utils.utils import get_random_key
 from openwisp_utils.validators import key_validator
diff --git a/openwisp_utils/validators.py b/openwisp_utils/validators.py
index cc2bad10..9868fd8a 100644
--- a/openwisp_utils/validators.py
+++ b/openwisp_utils/validators.py
@@ -1,5 +1,5 @@
 from django.core.validators import RegexValidator, _lazy_re_compile
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
 
 key_validator = RegexValidator(
     _lazy_re_compile('^[^\\s/\\.]+$'),


Test patch:
diff --git a/tests/test_project/admin.py b/tests/test_project/admin.py
index 2ca5ae0f..af062752 100644
--- a/tests/test_project/admin.py
+++ b/tests/test_project/admin.py
@@ -1,7 +1,7 @@
 from django.contrib import admin
 from django.contrib.auth.models import User
 from django.forms import ModelForm
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
 from openwisp_utils.admin import (
     AlwaysHasChangedMixin,
     HelpTextStackedInline,
diff --git a/tests/test_project/api/views.py b/tests/test_project/api/views.py
index e72a81b9..ad8bec47 100644
--- a/tests/test_project/api/views.py
+++ b/tests/test_project/api/views.py
@@ -1,5 +1,5 @@
 from django.http import JsonResponse
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
 from django.views import View
 
 from ..models import Project
diff --git a/tests/test_project/apps.py b/tests/test_project/apps.py
index 71906403..2dba6e72 100644
--- a/tests/test_project/apps.py
+++ b/tests/test_project/apps.py
@@ -1,5 +1,5 @@
 from django.db.models import Case, Count, Sum, When
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
 from openwisp_utils.admin_theme import (
     register_dashboard_chart,
     register_dashboard_template,
@@ -18,6 +18,7 @@ class TestAppConfig(ApiAppConfig):
         'DEFAULT_THROTTLE_RATES': {'test': '10/minute'},
         'TEST': True,
     }
+    default_auto_field = 'django.db.models.AutoField'
 
     def ready(self, *args, **kwargs):
         super().ready(*args, **kwargs)
diff --git a/tests/test_project/models.py b/tests/test_project/models.py
index 9e260591..b630824d 100644
--- a/tests/test_project/models.py
+++ b/tests/test_project/models.py
@@ -1,6 +1,6 @@
 from django.core.exceptions import ValidationError
 from django.db import models
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
 from openwisp_utils.base import KeyField, TimeStampedEditableModel, UUIDModel
 
 
diff --git a/tests/test_project/tests/test_storage.py b/tests/test_project/tests/test_storage.py
index 786284de..5090efea 100644
--- a/tests/test_project/tests/test_storage.py
+++ b/tests/test_project/tests/test_storage.py
@@ -15,7 +15,7 @@ def create_dir(*paths: str):
     and all the intermidiate ones according to the joined path using os.makedirs
     """
     joined_path = os.path.join(*paths)
-    os.makedirs(joined_path)
+    os.makedirs(joined_path, exist_ok=True)
     return joined_path
 
 
diff --git a/tests/test_project/tests/test_test_utils.py b/tests/test_project/tests/test_test_utils.py
index aa29f5cc..425771c6 100644
--- a/tests/test_project/tests/test_test_utils.py
+++ b/tests/test_project/tests/test_test_utils.py
@@ -15,7 +15,7 @@
 
 from ..models import Shelf
 
-status_signal = Signal(providing_args=['status'])
+status_signal = Signal()  # providing_args=['status']
 
 
 class TestUtils(TestCase):