Procházet zdrojové kódy

Django 4.2, Python 3.11 (#4098)

* Add Python 3.10 to test suite

* Support Django 4.2, remove Python 3.7 support and add Python 3.11 support

---------

Co-authored-by: Craig Weber <crgwbr@gmail.com>
master
Viggo de Vries před 1 rokem
rodič
revize
c7d5676354
Žádný účet není propojen s e-mailovou adresou tvůrce revize

+ 8
- 8
.github/workflows/test.yml Zobrazit soubor

@@ -18,11 +18,11 @@ jobs:
18 18
     strategy:
19 19
       fail-fast: true
20 20
       matrix:
21
-        python-version: [3.7, 3.8, 3.9]
22
-        django-version: [3.1, 3.2]
21
+        python-version: ['3.8', '3.9', '3.10', '3.11']
22
+        django-version: ['3.2', '4.0', '4.1', '4.2']
23 23
     services:
24 24
       postgres:
25
-        image: postgres:10
25
+        image: postgres:14
26 26
         ports:
27 27
           - 5432/tcp
28 28
         options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
@@ -54,7 +54,7 @@ jobs:
54 54
     - name: Upload coverage to Codecov
55 55
       uses: codecov/codecov-action@v1.5.2
56 56
       with:
57
-        fail_ci_if_error: true
57
+        fail_ci_if_error: false
58 58
   lint_python:
59 59
     runs-on: ubuntu-latest
60 60
     steps:
@@ -62,7 +62,7 @@ jobs:
62 62
     - name: Set up Python ${{ matrix.python-version }}
63 63
       uses: actions/setup-python@v2
64 64
       with:
65
-        python-version: 3.8
65
+        python-version: '3.11'
66 66
     - name: Install dependencies
67 67
       run: |
68 68
         python -m pip install --upgrade pip
@@ -78,7 +78,7 @@ jobs:
78 78
     - name: Set up NodeJS
79 79
       uses: actions/setup-node@v2
80 80
       with:
81
-        node-version: '14'
81
+        node-version: '16'
82 82
     - name: Install dependencies
83 83
       run: |
84 84
         npm ci
@@ -92,7 +92,7 @@ jobs:
92 92
     - name: Set up Python ${{ matrix.python-version }}
93 93
       uses: actions/setup-python@v2
94 94
       with:
95
-        python-version: 3.8
95
+        python-version: '3.11'
96 96
     - name: Build sandbox
97 97
       run: |
98 98
         python -m pip install --upgrade pip
@@ -104,7 +104,7 @@ jobs:
104 104
     - name: Set up Python ${{ matrix.python-version }}
105 105
       uses: actions/setup-python@v2
106 106
       with:
107
-        python-version: 3.8
107
+        python-version: '3.11'
108 108
     - name: Build docs
109 109
       run: |
110 110
         make docs

+ 1
- 1
Dockerfile Zobrazit soubor

@@ -1,4 +1,4 @@
1
-FROM python:3.8
1
+FROM python:3.11
2 2
 ENV PYTHONUNBUFFERED 1
3 3
 
4 4
 RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -

+ 1
- 1
docs/source/conf.py Zobrazit soubor

@@ -52,7 +52,7 @@ extensions = [
52 52
     'sphinx.ext.todo',
53 53
     'sphinx.ext.coverage',
54 54
     'sphinx.ext.viewcode',
55
-    'sphinxcontrib.napoleon',
55
+    'sphinx.ext.napoleon',
56 56
     'sphinxcontrib.spelling',
57 57
     'sphinx_issues',
58 58
 ]

+ 2
- 3
gulpfile.js/subtasks/copy.js Zobrazit soubor

@@ -11,8 +11,7 @@ module.exports = function(done) {
11 11
         .pipe(gulp.dest("src/oscar/static/oscar/js/jquery"));
12 12
 
13 13
     gulp.src([
14
-        "node_modules/bootstrap/dist/js/bootstrap.bundle.js",
15
-        "node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"
14
+        "node_modules/bootstrap/dist/js/*",
16 15
     ]).pipe(gulp.dest("src/oscar/static/oscar/js/bootstrap4"));
17 16
 
18 17
     gulp.src("node_modules/bootstrap/fonts/*")
@@ -23,7 +22,7 @@ module.exports = function(done) {
23 22
         "node_modules/tempusdominus-bootstrap-4/build/css/*.min.css"
24 23
     ]).pipe(gulp.dest("src/oscar/static/oscar/js/bootstrap4-datetimepicker"));
25 24
 
26
-    gulp.src("node_modules/moment/min/moment-with-locales.min.js")
25
+    gulp.src("node_modules/moment/min/*")
27 26
         .pipe(gulp.dest("src/oscar/static/oscar/js/bootstrap4-datetimepicker"));
28 27
 
29 28
     gulp.src("node_modules/inputmask/dist/jquery.inputmask.min.js")

+ 1
- 1
requirements.txt Zobrazit soubor

@@ -13,7 +13,7 @@ django-redis>=4.12,<5.3
13 13
 pysolr>=3.9,<3.10
14 14
 redis>=3.5,<4.6
15 15
 requests>=2.25,<3
16
-uWSGI>=2.0.19,<2.1
16
+uWSGI>=2.0.19
17 17
 whitenoise>=5.2,<6.5
18 18
 
19 19
 # Linting

+ 14
- 11
setup.py Zobrazit soubor

@@ -18,7 +18,7 @@ sys.path.append(os.path.join(PROJECT_DIR, 'src'))
18 18
 from oscar import get_version  # noqa isort:skip
19 19
 
20 20
 install_requires = [
21
-    'django>=3.1,<3.3',
21
+    'django>=3.2,<4.3',
22 22
     # PIL is required for image fields, Pillow is the "friendly" PIL fork
23 23
     'pillow>=6.0',
24 24
     # We use the ModelFormSetView from django-extra-views for the basket page
@@ -43,15 +43,14 @@ install_requires = [
43 43
     'django-widget-tweaks>=1.4.1',
44 44
 ]
45 45
 
46
-sorl_thumbnail_version = 'sorl-thumbnail>=12.6,<12.7'
47
-easy_thumbnails_version = 'easy-thumbnails>=2.7,<2.8'
46
+sorl_thumbnail_version = 'sorl-thumbnail>=12.9,<12.10'
47
+easy_thumbnails_version = 'easy-thumbnails>=2.7,<2.8.6'
48 48
 
49 49
 docs_requires = [
50
-    'Sphinx>=4.2,<4.3',
51
-    'sphinxcontrib-napoleon==0.7',
52
-    'sphinxcontrib-spelling==7.2.1',
50
+    'Sphinx>=5.0',
51
+    'sphinxcontrib-spelling==7.5.1',
53 52
     'sphinx_rtd_theme==1.0.0',
54
-    'sphinx-issues==1.2.0',
53
+    'sphinx-issues==3.0.1',
55 54
     sorl_thumbnail_version,
56 55
     easy_thumbnails_version,
57 56
 ]
@@ -60,11 +59,12 @@ test_requires = [
60 59
     'WebTest>=2.0,<2.1',
61 60
     'coverage>=5.4,<5.5',
62 61
     'django-webtest>=1.9,<1.10',
63
-    'psycopg2-binary>=2.8,<2.9',
62
+    'psycopg2-binary>=2.8,<2.10',
64 63
     'pytest-django>=3.7,<3.9',
65 64
     'pytest-xdist>=2.2,<3',
66 65
     'tox>=3.21,<4',
67 66
     'freezegun>=1.1,<2',
67
+    'pytz',
68 68
     sorl_thumbnail_version,
69 69
     easy_thumbnails_version,
70 70
 ]
@@ -87,7 +87,7 @@ setup(
87 87
     package_dir={'': 'src'},
88 88
     packages=find_packages('src'),
89 89
     include_package_data=True,
90
-    python_requires='>=3.7',
90
+    python_requires='>=3.8',
91 91
     install_requires=install_requires,
92 92
     extras_require={
93 93
         'docs': docs_requires,
@@ -99,16 +99,19 @@ setup(
99 99
         'Development Status :: 5 - Production/Stable',
100 100
         'Environment :: Web Environment',
101 101
         'Framework :: Django',
102
-        'Framework :: Django :: 3.1',
103 102
         'Framework :: Django :: 3.2',
103
+        'Framework :: Django :: 4.0',
104
+        'Framework :: Django :: 4.1',
105
+        'Framework :: Django :: 4.2',
104 106
         'Intended Audience :: Developers',
105 107
         'License :: OSI Approved :: BSD License',
106 108
         'Operating System :: Unix',
107 109
         'Programming Language :: Python',
108 110
         'Programming Language :: Python :: 3',
109
-        'Programming Language :: Python :: 3.7',
110 111
         'Programming Language :: Python :: 3.8',
111 112
         'Programming Language :: Python :: 3.9',
113
+        'Programming Language :: Python :: 3.10',
114
+        'Programming Language :: Python :: 3.11',
112 115
         'Topic :: Software Development :: Libraries :: Application Frameworks']
113 116
 )
114 117
 

+ 8
- 5
src/oscar/apps/basket/abstract_models.py Zobrazit soubor

@@ -126,7 +126,7 @@ class AbstractBasket(models.Model):
126 126
         lost.
127 127
         """
128 128
         if self.id is None:
129
-            return self.lines.none()
129
+            return self.lines.model.objects.none()
130 130
         if self._lines is None:
131 131
             self._lines = (
132 132
                 self.lines
@@ -523,7 +523,7 @@ class AbstractBasket(models.Model):
523 523
     @property
524 524
     def num_items(self):
525 525
         """Return number of items"""
526
-        return sum(line.quantity for line in self.lines.all())
526
+        return sum(line.quantity for line in self.all_lines())
527 527
 
528 528
     @property
529 529
     def num_items_without_discount(self):
@@ -599,9 +599,12 @@ class AbstractBasket(models.Model):
599 599
         The basket can contain multiple lines with the same product, but
600 600
         different options and stockrecords. Those quantities are summed up.
601 601
         """
602
-        matching_lines = self.lines.filter(product=product)
603
-        quantity = matching_lines.aggregate(Sum('quantity'))['quantity__sum']
604
-        return quantity or 0
602
+        if self.id:
603
+            matching_lines = self.lines.filter(product=product)
604
+            quantity = matching_lines.aggregate(Sum('quantity'))['quantity__sum']
605
+            return quantity or 0
606
+
607
+        return 0
605 608
 
606 609
     def line_quantity(self, product, stockrecord, options=None):
607 610
         """

+ 5
- 3
src/oscar/apps/catalogue/abstract_models.py Zobrazit soubor

@@ -220,8 +220,8 @@ class AbstractCategory(MP_Node):
220 220
             is_public=False, path__rstartswith=OuterRef("path"), depth__lt=OuterRef("depth")
221 221
         )
222 222
         self.get_descendants_and_self().update(
223
-            ancestors_are_public=Exists(
224
-                included_in_non_public_subtree.values("id"), negated=True))
223
+            ancestors_are_public=~Exists(
224
+                included_in_non_public_subtree.values("id")))
225 225
 
226 226
         # Correctly populate ancestors_are_public
227 227
         self.refresh_from_db()
@@ -599,7 +599,9 @@ class AbstractProduct(models.Model):
599 599
         """
600 600
         Test if this product has any stockrecords
601 601
         """
602
-        return self.stockrecords.exists()
602
+        if self.id:
603
+            return self.stockrecords.exists()
604
+        return False
603 605
 
604 606
     @property
605 607
     def num_stockrecords(self):

+ 7
- 0
src/oscar/apps/dashboard/views.py Zobrazit soubor

@@ -306,6 +306,13 @@ class PopUpWindowDeleteMixin(PopUpWindowMixin):
306 306
         else:
307 307
             return response
308 308
 
309
+    def post(self, request, *args, **kwargs):
310
+        """
311
+        Calls the delete() method on the fetched object and then
312
+        redirects to the success URL, or closes the popup, it it is one.
313
+        """
314
+        return self.delete(request, *args, **kwargs)
315
+
309 316
 
310 317
 class LoginView(auth_views.LoginView):
311 318
     template_name = 'oscar/dashboard/login.html'

+ 3
- 0
src/oscar/apps/dashboard/vouchers/views.py Zobrazit soubor

@@ -186,6 +186,9 @@ class VoucherDeleteView(generic.DeleteView):
186 186
             self.object.voucher_set.update_count()
187 187
         return response
188 188
 
189
+    def post(self, request, *args, **kwargs):
190
+        return self.delete(request, *args, **kwargs)
191
+
189 192
     def get_success_url(self):
190 193
         messages.warning(self.request, _("Voucher deleted"))
191 194
         if self.object.voucher_set is not None:

+ 1
- 1
src/oscar/apps/shipping/abstract_models.py Zobrazit soubor

@@ -83,7 +83,7 @@ class AbstractOrderAndItemCharges(AbstractBase):
83 83
                 incl_tax=D('0.00'))
84 84
 
85 85
         charge = self.price_per_order
86
-        for line in basket.lines.all():
86
+        for line in basket.all_lines():
87 87
             if line.product.is_shipping_required:
88 88
                 charge += line.quantity * self.price_per_item
89 89
 

+ 1
- 1
src/oscar/apps/shipping/scales.py Zobrazit soubor

@@ -30,6 +30,6 @@ class Scale(object):
30 30
 
31 31
     def weigh_basket(self, basket):
32 32
         weight = D('0.0')
33
-        for line in basket.lines.all():
33
+        for line in basket.all_lines():
34 34
             weight += self.weigh_product(line.product) * line.quantity
35 35
         return weight

+ 4
- 0
tests/integration/basket/test_forms.py Zobrazit soubor

@@ -81,6 +81,10 @@ class TestBasketLineForm(TestCase):
81 81
         form = self.build_form(quantity=6)
82 82
         self.assertTrue(form.is_valid())
83 83
         form.save()
84
+        # We set the _lines to None because the basket caches the lines here.
85
+        # We want the basket to do the query again.
86
+        # basket.num_items() will otherwise not return the correct values
87
+        self.basket._lines = None
84 88
         form = self.build_form(quantity=11)
85 89
         self.assertFalse(form.is_valid())
86 90
 

+ 4
- 2
tests/integration/core/test_loading.py Zobrazit soubor

@@ -1,7 +1,7 @@
1 1
 import sys
2 2
 from os.path import dirname
3 3
 
4
-from django.apps import AppConfig, apps
4
+from django.apps import apps
5 5
 from django.conf import settings
6 6
 from django.test import TestCase, override_settings
7 7
 
@@ -11,6 +11,8 @@ from oscar.core.loading import (
11 11
 from tests import temporary_python_path
12 12
 from tests._site.loader import DummyClass
13 13
 
14
+CustomerConfig = get_class("customer.apps", "CustomerConfig")
15
+
14 16
 
15 17
 class TestClassLoading(TestCase):
16 18
     """
@@ -98,7 +100,7 @@ class ClassLoadingWithLocalOverrideTests(TestCase):
98 100
     def test_overriding_view_is_possible_without_overriding_app(self):
99 101
         # If test fails, it's helpful to know if it's caused by order of
100 102
         # execution
101
-        customer_app_config = AppConfig.create('oscar.apps.customer')
103
+        customer_app_config = CustomerConfig.create('oscar.apps.customer')
102 104
         customer_app_config.ready()
103 105
         self.assertEqual(customer_app_config.summary_view.__module__,
104 106
                          'tests._site.apps.customer.views')

+ 2
- 2
tests/integration/order/test_models.py Zobrazit soubor

@@ -413,12 +413,12 @@ class OrderTests(TestCase):
413 413
     @override_settings(SECRET_KEY='order_hash_secret')
414 414
     def test_verification_hash_generation(self):
415 415
         order = OrderFactory(number='111000')
416
-        self.assertEqual(order.verification_hash(), '111000:UJrZWNPLsq7zf1r17c3v1Q6DUmE')
416
+        self.assertEqual(order.verification_hash(), '111000:Ra7jPGqhkcTm6VXKWEdKtFjA0wBIRVqdxED4bp313os')
417 417
 
418 418
     @override_settings(SECRET_KEY='order_hash_secret')
419 419
     def test_check_verification_hash_valid(self):
420 420
         order = OrderFactory(number='111000')
421
-        self.assertTrue(order.check_verification_hash('111000:UJrZWNPLsq7zf1r17c3v1Q6DUmE'))
421
+        self.assertTrue(order.check_verification_hash('111000:Ra7jPGqhkcTm6VXKWEdKtFjA0wBIRVqdxED4bp313os'))
422 422
 
423 423
     @override_settings(SECRET_KEY='order_hash_secret')
424 424
     def test_check_verification_hash_invalid_signature(self):

+ 2
- 2
tests/settings.py Zobrazit soubor

@@ -152,6 +152,7 @@ PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher']
152 152
 ROOT_URLCONF = 'tests._site.urls'
153 153
 LOGIN_REDIRECT_URL = '/accounts/'
154 154
 STATIC_URL = '/static/'
155
+STATIC_ROOT = location('public/static')
155 156
 MEDIA_URL = '/media/'
156 157
 PUBLIC_ROOT = location('public')
157 158
 MEDIA_ROOT = os.path.join(PUBLIC_ROOT, 'media')
@@ -169,7 +170,6 @@ OSCAR_INITIAL_LINE_STATUS = 'a'
169 170
 OSCAR_LINE_STATUS_PIPELINE = {'a': ('b', ), 'b': ()}
170 171
 
171 172
 SECRET_KEY = 'notverysecret'
172
-# Removed in Django 4.0, then we need to update the hashes to SHA-256 in tests/integration/order/test_models.py
173
-DEFAULT_HASHING_ALGORITHM = 'sha1'
173
+
174 174
 TEST_RUNNER = 'django.test.runner.DiscoverRunner'
175 175
 FIXTURE_DIRS = [location('unit/fixtures')]

+ 8
- 6
tox.ini Zobrazit soubor

@@ -1,6 +1,6 @@
1 1
 [tox]
2 2
 envlist =
3
-    py{37,38,39}-django{31,32}
3
+    py{38,39,310,311}-django{32,40,41,42}
4 4
     lint
5 5
     sandbox
6 6
     docs
@@ -11,11 +11,13 @@ commands = coverage run --parallel -m pytest {posargs}
11 11
 extras = test
12 12
 pip_pre = true
13 13
 deps =
14
-    django31: django>=3.1,<3.2
15 14
     django32: django>=3.2,<3.3
15
+    django40: django>=4.0,<4.1
16
+    django41: django>=4.1,<4.2
17
+    django42: django>=4.2,<4.3
16 18
 
17 19
 [testenv:lint]
18
-basepython = python3.8
20
+basepython = python3.11
19 21
 deps =
20 22
     -r{toxinidir}/requirements.txt
21 23
 allowlist_externals = npm
@@ -28,16 +30,16 @@ commands =
28 30
 
29 31
 
30 32
 [testenv:sandbox]
31
-basepython = python3.8
33
+basepython = python3.11
32 34
 deps =
33 35
     -r{toxinidir}/requirements.txt
34
-    django>=3.2,<3.3
36
+    django>=4.2,<4.3
35 37
 allowlist_externals = make
36 38
 commands =
37 39
     make build_sandbox
38 40
 
39 41
 [testenv:docs]
40
-basepython = python3.8
42
+basepython = python3.11
41 43
 allowlist_externals = make
42 44
 changedir = {toxinidir}/docs
43 45
 pip_pre = false

Načítá se…
Zrušit
Uložit