Repository: ImperialCollegeLondon/django-drf-filepond (language: python)

Issue:
Check support for Python 3.11 and Django 4.2
This has been raised in a comment in #71. Need to check use of the library under Python 3.11 and Django 4.2 and resolve any issues.

Thanks @jugurtha114 for highlighting this.


Discussion / hints:
@jugurtha114 are you experiencing a particular problem using this library with Python 3.11 and Django 4.2? I can confirm that library should work with Python 3.11 and Django 4.2 - there are 3 warnings generated when running the test suite which I'll investigate and address but functionality shouldn't be affected. I'll also add Python 3.11 to the CI workflow.

If you're having a particular problem then it would be great if you could describe it in this issue and I can investigate. many thanks.

Fix patch:
diff --git a/tox.ini b/tox.ini
index 2d8db07..855281a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -18,6 +18,7 @@ envlist =
     py38
     py39
     py310
+    py311
     coverage
 
 [testenv]
@@ -54,6 +55,6 @@ depends =
     py38
     py39
     py310
-
+    py311
 
 ; pytest configuration is provided in pytest.ini


Test patch:
diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml
index 7ab87bc..727db39 100644
--- a/.github/workflows/python-test.yml
+++ b/.github/workflows/python-test.yml
@@ -27,7 +27,7 @@ jobs:
       fail-fast: false
       matrix:
         os: [ubuntu-20.04, ubuntu-latest]
-        python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9, '3.10']
+        python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9, '3.10', '3.11']
         exclude:
           - os: ubuntu-20.04
             python-version: '3.7'
@@ -37,6 +37,8 @@ jobs:
             python-version: '3.9'
           - os: ubuntu-20.04
             python-version: '3.10'
+          - os: ubuntu-20.04
+            python-version: '3.11'
           - os: ubuntu-latest
             python-version: '2.7'
           - os: ubuntu-latest
diff --git a/tests/settings.py b/tests/settings.py
index 892b914..13cf490 100644
--- a/tests/settings.py
+++ b/tests/settings.py
@@ -119,7 +119,9 @@
 
 USE_I18N = True
 
-USE_L10N = True
+# Deprecated in Django 4.2 and will be removed in 5.0
+# Removing since not currently relevant to tests.
+# USE_L10N = True
 
 USE_TZ = True
 
diff --git a/tests/test_fetch_view.py b/tests/test_fetch_view.py
index 9e61cb1..4bcf93d 100644
--- a/tests/test_fetch_view.py
+++ b/tests/test_fetch_view.py
@@ -1,25 +1,26 @@
-import cgi
 import logging
 import os
+# Switched to using Message rather than cgi.parse_header for parsing and
+# checking header params since cgi is deprecated and will be removed in py3.13
+from email.message import Message
 
-from django.test.testcases import TestCase
-from django.urls import reverse
-from httpretty import register_uri
 import httpretty
 import requests
-from requests.exceptions import ConnectionError
-from rest_framework.exceptions import NotFound
-
+from django.test.testcases import TestCase
+from django.urls import reverse
 from django_drf_filepond import drf_filepond_settings
 from django_drf_filepond.models import TemporaryUpload
 from django_drf_filepond.views import FetchView
+from httpretty import register_uri
+from requests.exceptions import ConnectionError
+from rest_framework.exceptions import NotFound
 from rest_framework.response import Response
 
 # Python 2/3 support
 try:
-    from unittest.mock import patch, MagicMock
+    from unittest.mock import MagicMock, patch
 except ImportError:
-    from mock import patch, MagicMock
+    from mock import MagicMock, patch
 
 LOG = logging.getLogger(__name__)
 
@@ -234,12 +235,15 @@ def test_fetch_with_filename(self):
         self.assertTrue('Content-Disposition' in response,
                         ('Response does not contain a required '
                          'Content-Disposition header.'))
-        cdisp = cgi.parse_header(response['Content-Disposition'])
+        msg = Message()
+        msg['content-type'] = response['Content-Disposition']
         self.assertTrue(
-            'filename' in cdisp[1],
+            msg.get_param('filename'),
             ('Parsed Content-Disposition header doesn\'t contain '
              'filename parameter'))
-        fname = cdisp[1]['filename']
+        # fname = cdisp[1]['filename']
+        fname = msg.get_param('filename')
+
         self.assertContains(response, '*This is the file content!*',
                             status_code=200)
         self.assertEqual(
@@ -256,13 +260,15 @@ def test_fetch_without_filename(self):
         self.assertTrue('Content-Disposition' in response,
                         ('Response does not contain a required '
                          'Content-Disposition header.'))
-        cdisp = cgi.parse_header(response['Content-Disposition'])
+        
+        msg = Message()
+        msg['content-type'] = response['Content-Disposition']
         self.assertTrue(
-            'filename' in cdisp[1],
+            msg.get_param('filename'),
             ('Parsed Content-Disposition header doesn\'t contain '
              'filename parameter'))
+        fname = msg.get_param('filename')
 
-        fname = cdisp[1]['filename']
         self.assertContains(response, '*This is the file content!*',
                             status_code=200)
         LOG.debug('Returned filename is <%s>' % fname)
@@ -286,12 +292,15 @@ def test_fetch_head_with_filename(self):
         self.assertTrue('Content-Disposition' in response,
                         ('Response does not contain a required '
                          'Content-Disposition header.'))
-        cdisp = cgi.parse_header(response['Content-Disposition'])
+
+        msg = Message()
+        msg['content-type'] = response['Content-Disposition']
         self.assertTrue(
-            'filename' in cdisp[1],
+            msg.get_param('filename'),
             ('Parsed Content-Disposition header doesn\'t contain '
              'filename parameter'))
-        fname = cdisp[1]['filename']
+        fname = msg.get_param('filename')
+
         self.assertEqual(
             filename, fname,
             'Returned filename is not equal to the provided filename value.')
@@ -334,12 +343,15 @@ def test_fetch_head_without_filename(self):
         self.assertTrue('Content-Disposition' in response,
                         ('Response does not contain a required '
                          'Content-Disposition header.'))
-        cdisp = cgi.parse_header(response['Content-Disposition'])
+
+        msg = Message()
+        msg['content-type'] = response['Content-Disposition']
         self.assertTrue(
-            'filename' in cdisp[1],
+            msg.get_param('filename'),
             ('Parsed Content-Disposition header doesn\'t contain '
              'filename parameter'))
-        fname = cdisp[1]['filename']
+        fname = msg.get_param('filename')
+        
         self.assertEqual(
             len(fname), 22,
             'Returned filename is not a 22 character auto-generated name.')
diff --git a/tests/test_load_view.py b/tests/test_load_view.py
index 9988fd6..1db4f7c 100644
--- a/tests/test_load_view.py
+++ b/tests/test_load_view.py
@@ -1,15 +1,17 @@
 import logging
-
-from django.test.testcases import TestCase
-from django_drf_filepond.utils import _get_file_id
-from django_drf_filepond.models import TemporaryUpload, StoredUpload
-from django.core.files.uploadedfile import SimpleUploadedFile
-from django.urls import reverse
-import django_drf_filepond.drf_filepond_settings as local_settings
-import cgi
 import os
 import shutil
+# Switched to using Message rather than cgi.parse_header for parsing and
+# checking header params since cgi is deprecated and will be removed in py3.13
+from email.message import Message
+
 import django_drf_filepond.api
+import django_drf_filepond.drf_filepond_settings as local_settings
+from django.core.files.uploadedfile import SimpleUploadedFile
+from django.test.testcases import TestCase
+from django.urls import reverse
+from django_drf_filepond.models import StoredUpload, TemporaryUpload
+from django_drf_filepond.utils import _get_file_id
 
 LOG = logging.getLogger(__name__)
 
@@ -57,10 +59,14 @@ def _check_file_response(self, response, filename, file_content):
         self.assertTrue('Content-Disposition' in response,
                         ('Response does not contain a required '
                          'Content-Disposition header.'))
-        cdisp = cgi.parse_header(response['Content-Disposition'])
-        self.assertTrue('filename' in cdisp[1], ('Content-Disposition'
-                        ' header doesn\'t contain filename parameter'))
-        fname = cdisp[1]['filename']
+
+        msg = Message()
+        msg['content-type'] = response['Content-Disposition']
+        self.assertTrue(
+            msg.get_param('filename'),
+            'Content-Disposition header doesn\'t contain filename parameter')
+        fname = msg.get_param('filename')
+
         self.assertEqual(filename, fname, ('Returned filename is not '
                          'equal to the provided filename value.'))
 
@@ -96,8 +102,7 @@ def setUp(self):
 
         # Now set up a stored version of this upload
         su = StoredUpload(upload_id=self.upload_id,
-                          file=('%s'
-                                     % (self.fn)),
+                          file=('%s' % (self.fn)),
                           uploaded=tu.uploaded)
         su.save()
 
diff --git a/tests/test_load_view_storages.py b/tests/test_load_view_storages.py
index fba3692..5018394 100644
--- a/tests/test_load_view_storages.py
+++ b/tests/test_load_view_storages.py
@@ -1,13 +1,14 @@
-import cgi
-from io import BytesIO
 import logging
 import os
+# Switched to using Message rather than cgi.parse_header for parsing and
+# checking header params since cgi is deprecated and will be removed in py3.13
+from email.message import Message
+from io import BytesIO
 
+import django_drf_filepond.drf_filepond_settings as local_settings
 from django.test.testcases import TestCase
 from django.urls import reverse
 from django.utils import timezone
-
-import django_drf_filepond.drf_filepond_settings as local_settings
 from django_drf_filepond.models import StoredUpload
 from django_drf_filepond.utils import _get_file_id
 
@@ -69,10 +70,13 @@ def _check_file_response(self, response, filename, file_content):
         self.assertTrue('Content-Disposition' in response,
                         ('Response does not contain a required '
                          'Content-Disposition header.'))
-        cdisp = cgi.parse_header(response['Content-Disposition'])
-        self.assertTrue('filename' in cdisp[1], ('Content-Disposition'
-                        ' header doesn\'t contain filename parameter'))
-        fname = cdisp[1]['filename']
+
+        msg = Message()
+        msg['content-type'] = response['Content-Disposition']
+        self.assertTrue(
+            msg.get_param('filename'),
+            'Content-Disposition header doesn\'t contain filename parameter')
+        fname = msg.get_param('filename')
         self.assertEqual(filename, fname, ('Returned filename is not '
                          'equal to the provided filename value.'))
 
diff --git a/tests/test_restore_view.py b/tests/test_restore_view.py
index 2e55885..0b4cdfd 100644
--- a/tests/test_restore_view.py
+++ b/tests/test_restore_view.py
@@ -1,14 +1,16 @@
 import logging
+import os
+# Switched to using Message rather than cgi.parse_header for parsing and
+# checking header params since cgi is deprecated and will be removed in py3.13
+from email.message import Message
 
-from django.test.testcases import TestCase
-from django_drf_filepond.utils import _get_file_id
-from django_drf_filepond.models import TemporaryUpload
+import django_drf_filepond.drf_filepond_settings as drf_fp_settings
+from django.conf import settings
 from django.core.files.uploadedfile import SimpleUploadedFile
+from django.test.testcases import TestCase
 from django.urls import reverse
-from django.conf import settings
-import django_drf_filepond.drf_filepond_settings as drf_fp_settings
-import cgi
-import os
+from django_drf_filepond.models import TemporaryUpload
+from django_drf_filepond.utils import _get_file_id
 
 LOG = logging.getLogger(__name__)
 
@@ -111,10 +113,13 @@ def test_restore_successful_request(self):
         self.assertTrue('Content-Disposition' in response,
                         ('Response does not contain a required '
                          'Content-Disposition header.'))
-        cdisp = cgi.parse_header(response['Content-Disposition'])
-        self.assertTrue('filename' in cdisp[1], ('Content-Disposition'
-                        ' header doesn\'t contain filename parameter'))
-        fname = cdisp[1]['filename']
+
+        msg = Message()
+        msg['content-type'] = response['Content-Disposition']
+        self.assertTrue(
+            msg.get_param('filename'),
+            'Content-Disposition header doesn\'t contain filename parameter')
+        fname = msg.get_param('filename')
         self.assertEqual(
             self.fn, fname, ('Returned filename is not equal to the '
                              'provided filename value.'))