Parcourir la source

Drop support for Django 2.2 and Python 3.6

master
Samir Shah il y a 3 ans
Parent
révision
f53bc49507

+ 3
- 3
.github/workflows/test.yml Voir le fichier

@@ -15,8 +15,8 @@ jobs:
15 15
     strategy:
16 16
       fail-fast: true
17 17
       matrix:
18
-        python-version: [3.6, 3.7, 3.8, 3.9]
19
-        django-version: [2.2, 3.1, 3.2]
18
+        python-version: [3.7, 3.8, 3.9]
19
+        django-version: [3.1, 3.2]
20 20
     services:
21 21
       postgres:
22 22
         image: postgres:10
@@ -59,7 +59,7 @@ jobs:
59 59
     - name: Set up Python ${{ matrix.python-version }}
60 60
       uses: actions/setup-python@v2
61 61
       with:
62
-        python-version: 3.7
62
+        python-version: 3.8
63 63
     - name: Install dependencies
64 64
       run: |
65 65
         python -m pip install --upgrade pip

+ 2
- 2
Dockerfile Voir le fichier

@@ -1,7 +1,7 @@
1
-FROM python:3.7
1
+FROM python:3.8
2 2
 ENV PYTHONUNBUFFERED 1
3 3
 
4
-RUN curl -sL https://deb.nodesource.com/setup_12.x | bash -
4
+RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
5 5
 RUN apt-get install -y nodejs
6 6
 
7 7
 COPY ./requirements.txt /requirements.txt

+ 1
- 1
docs/source/internals/contributing/running-tests.rst Voir le fichier

@@ -8,7 +8,7 @@ Testing requirements
8 8
 You'll need:
9 9
 
10 10
 - A running SQL server (PostgreSQL, or SQLite with `--sqlite` parameters)
11
-- python3.6 or python3.7
11
+- python3.7 or higher.
12 12
 
13 13
 Running tests
14 14
 -------------

+ 8
- 9
docs/source/ref/settings.rst Voir le fichier

@@ -99,13 +99,12 @@ sorting, filtering, etc.
99 99
 The default is::
100 100
 
101 101
     OSCAR_SEARCH_FACETS = {
102
-        'fields': OrderedDict([
103
-            ('product_class', {'name': _('Type'), 'field': 'product_class'}),
104
-            ('rating', {'name': _('Rating'), 'field': 'rating'}),
105
-        ]),
106
-        'queries': OrderedDict([
107
-            ('price_range',
108
-             {
102
+        'fields': {
103
+            'product_class': {'name': _('Type'), 'field': 'product_class'},
104
+            'rating': {'name': _('Rating'), 'field': 'rating'},
105
+        },
106
+        'queries': {
107
+            'price_range': {
109 108
                  'name': _('Price range'),
110 109
                  'field': 'price',
111 110
                  'queries': [
@@ -116,8 +115,8 @@ The default is::
116 115
                      (_('40 to 60'), '[40 TO 60]'),
117 116
                      (_('60+'), '[60 TO *]'),
118 117
                  ]
119
-             }),
120
-        ]),
118
+             },
119
+        },
121 120
     }
122 121
 
123 122
 ``OSCAR_PRODUCT_SEARCH_HANDLER``

+ 10
- 0
docs/source/releases/v3.2.rst Voir le fichier

@@ -14,6 +14,9 @@ Oscar 3.2 release notes (in development)
14 14
 Compatibility
15 15
 ~~~~~~~~~~~~~
16 16
 
17
+Oscar 3.2 is compatible with Django 3.1 and Django 3.2 and Python versions 3.7 to 3.9.
18
+
19
+Support for Django 2.2 has been dropped. Support for Python 3.6 has been dropped.
17 20
 
18 21
 .. _new_in_3.2:
19 22
 
@@ -46,3 +49,10 @@ Python package dependencies:
46 49
 
47 50
 
48 51
 Javascript and CSS dependencies:
52
+
53
+
54
+Deprecated features
55
+~~~~~~~~~~~~~~~~~~~
56
+
57
+- The ``annotate_form_field`` template tag is deprecated. It's functionality of annotating form fields with
58
+  their widget type is now built in to Django.

+ 2
- 3
setup.py Voir le fichier

@@ -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>=2.2,<3.3',
21
+    'django>=3.1,<3.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
@@ -87,6 +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 91
     install_requires=install_requires,
91 92
     extras_require={
92 93
         'docs': docs_requires,
@@ -98,7 +99,6 @@ setup(
98 99
         'Development Status :: 5 - Production/Stable',
99 100
         'Environment :: Web Environment',
100 101
         'Framework :: Django',
101
-        'Framework :: Django :: 2.2',
102 102
         'Framework :: Django :: 3.1',
103 103
         'Framework :: Django :: 3.2',
104 104
         'Intended Audience :: Developers',
@@ -106,7 +106,6 @@ setup(
106 106
         'Operating System :: Unix',
107 107
         'Programming Language :: Python',
108 108
         'Programming Language :: Python :: 3',
109
-        'Programming Language :: Python :: 3.6',
110 109
         'Programming Language :: Python :: 3.7',
111 110
         'Programming Language :: Python :: 3.8',
112 111
         'Programming Language :: Python :: 3.9',

+ 1
- 1
src/oscar/apps/basket/views.py Voir le fichier

@@ -5,6 +5,7 @@ from django.http import JsonResponse, QueryDict
5 5
 from django.shortcuts import redirect
6 6
 from django.template.loader import render_to_string
7 7
 from django.urls import reverse
8
+from django.utils.http import url_has_allowed_host_and_scheme
8 9
 from django.utils.translation import gettext_lazy as _
9 10
 from django.views.generic import FormView, View
10 11
 from extra_views import ModelFormSetView
@@ -12,7 +13,6 @@ from extra_views import ModelFormSetView
12 13
 from oscar.apps.basket.signals import (
13 14
     basket_addition, voucher_addition, voucher_removal)
14 15
 from oscar.core import ajax
15
-from oscar.core.compat import url_has_allowed_host_and_scheme
16 16
 from oscar.core.loading import get_class, get_classes, get_model
17 17
 from oscar.core.utils import is_ajax, redirect_to_referrer, safe_referrer
18 18
 

+ 2
- 2
src/oscar/apps/customer/forms.py Voir le fichier

@@ -9,12 +9,12 @@ from django.contrib.auth.password_validation import validate_password
9 9
 from django.contrib.sites.shortcuts import get_current_site
10 10
 from django.core.exceptions import ValidationError
11 11
 from django.utils.crypto import get_random_string
12
+from django.utils.http import url_has_allowed_host_and_scheme
12 13
 from django.utils.translation import gettext_lazy as _
13 14
 from django.utils.translation import pgettext_lazy
14 15
 
15 16
 from oscar.apps.customer.utils import get_password_reset_url, normalise_email
16
-from oscar.core.compat import (
17
-    existing_user_fields, get_user_model, url_has_allowed_host_and_scheme)
17
+from oscar.core.compat import existing_user_fields, get_user_model
18 18
 from oscar.core.loading import get_class, get_model, get_profile_class
19 19
 from oscar.core.utils import datetime_combine
20 20
 from oscar.forms import widgets

+ 12
- 14
src/oscar/apps/dashboard/orders/views.py Voir le fichier

@@ -1,5 +1,4 @@
1 1
 import datetime
2
-from collections import OrderedDict
3 2
 from decimal import Decimal as D
4 3
 from decimal import InvalidOperation
5 4
 
@@ -117,16 +116,16 @@ class OrderListView(BulkEditMixin, ListView):
117 116
     form_class = OrderSearchForm
118 117
     paginate_by = settings.OSCAR_DASHBOARD_ITEMS_PER_PAGE
119 118
     actions = ('download_selected_orders', 'change_order_statuses')
120
-    CSV_COLUMNS = (
121
-        ('number', _('Order number')),
122
-        ('value', _('Order value')),
123
-        ('date', _('Date of purchase')),
124
-        ('num_items', _('Number of items')),
125
-        ('status', _('Order status')),
126
-        ('customer', _('Customer email address')),
127
-        ('shipping_address_name', _('Deliver to name')),
128
-        ('billing_address_name', _('Bill to name')),
129
-    )
119
+    CSV_COLUMNS = {
120
+        'number', _('Order number'),
121
+        'value', _('Order value'),
122
+        'date', _('Date of purchase'),
123
+        'num_items', _('Number of items'),
124
+        'status', _('Order status'),
125
+        'customer', _('Customer email address'),
126
+        'shipping_address_name', _('Deliver to name'),
127
+        'billing_address_name', _('Bill to name'),
128
+    }
130 129
 
131 130
     def dispatch(self, request, *args, **kwargs):
132 131
         # base_queryset is equal to all orders the user is allowed to access
@@ -382,11 +381,10 @@ class OrderListView(BulkEditMixin, ListView):
382 381
             % self.get_download_filename(request)
383 382
         writer = UnicodeCSVWriter(open_file=response)
384 383
 
385
-        ordered_columns = OrderedDict(self.CSV_COLUMNS)
386
-        writer.writerow(ordered_columns.values())
384
+        writer.writerow(self.CSV_COLUMNS.values())
387 385
         for order in orders:
388 386
             row_values = self.get_row_values(order)
389
-            writer.writerow([row_values.get(column, "") for column in ordered_columns])
387
+            writer.writerow([row_values.get(column, "") for column in self.CSV_COLUMNS])
390 388
         return response
391 389
 
392 390
     def change_order_statuses(self, request, orders):

+ 2
- 3
src/oscar/apps/order/abstract_models.py Voir le fichier

@@ -1,5 +1,4 @@
1 1
 import logging
2
-from collections import OrderedDict
3 2
 from decimal import Decimal as D
4 3
 
5 4
 from django.conf import settings
@@ -260,7 +259,7 @@ class AbstractOrder(models.Model):
260 259
             return ''
261 260
 
262 261
         # Collect all events by event-type
263
-        event_map = OrderedDict()
262
+        event_map = {}
264 263
         for event in events:
265 264
             event_name = event.event_type.name
266 265
             if event_name not in event_map:
@@ -719,7 +718,7 @@ class AbstractLine(models.Model):
719 718
         """
720 719
         Returns a dict of shipping events that this line has been through
721 720
         """
722
-        status_map = OrderedDict()
721
+        status_map = {}
723 722
         for event in self.shipping_events.all():
724 723
             event_type = event.event_type
725 724
             event_name = event_type.name

+ 1
- 3
src/oscar/apps/search/facets.py Voir le fichier

@@ -1,5 +1,3 @@
1
-from collections import OrderedDict
2
-
3 1
 from django.conf import settings
4 2
 from haystack.query import SearchQuerySet
5 3
 from purl import URL
@@ -27,7 +25,7 @@ class FacetMunger(object):
27 25
         self.facet_counts = facet_counts
28 26
 
29 27
     def facet_data(self):
30
-        facet_data = OrderedDict()
28
+        facet_data = {}
31 29
         # Haystack can return an empty dict for facet_counts when e.g. Solr
32 30
         # isn't running. Skip facet munging in that case.
33 31
         if self.facet_counts:

+ 0
- 26
src/oscar/core/compat.py Voir le fichier

@@ -1,7 +1,5 @@
1 1
 import csv
2
-import re
3 2
 
4
-from django import template
5 3
 from django.conf import settings
6 4
 from django.contrib.auth.models import User
7 5
 from django.core.exceptions import ImproperlyConfigured
@@ -17,14 +15,6 @@ except ValueError:
17 15
                                " 'app_label.model_name'")
18 16
 
19 17
 
20
-# Backward-compatible import for url_has_allowed_host_and_scheme.
21
-try:
22
-    # Django 3.0 and above
23
-    from django.utils.http import url_has_allowed_host_and_scheme  # noqa F401
24
-except ImportError:
25
-    from django.utils.http import is_safe_url as url_has_allowed_host_and_scheme  # noqa F401 isort:skip
26
-
27
-
28 18
 def get_user_model():
29 19
     """
30 20
     Return the User model. Doesn't require the app cache to be fully
@@ -129,19 +119,3 @@ class UnicodeCSVWriter:
129 119
     def writerows(self, rows):
130 120
         for row in rows:
131 121
             self.writerow(row)
132
-
133
-
134
-class FormFieldNode(template.Node):
135
-    """"
136
-    Add the widget type to a BoundField. Until 3.1, Django did not make this available by default.
137
-
138
-    Used by `oscar.templatetags.form_tags.annotate_form_field`
139
-    """
140
-    def __init__(self, field_str):
141
-        self.field = template.Variable(field_str)
142
-
143
-    def render(self, context):
144
-        field = self.field.resolve(context)
145
-        if not hasattr(field, 'widget_type') and hasattr(field, 'field'):
146
-            field.widget_type = re.sub(r'widget$|input$', '', field.field.widget.__class__.__name__.lower())
147
-        return ''

+ 1
- 2
src/oscar/core/utils.py Voir le fichier

@@ -8,13 +8,12 @@ from babel.dates import format_timedelta as format_td
8 8
 from django.conf import settings
9 9
 from django.shortcuts import redirect, resolve_url
10 10
 from django.template.defaultfilters import date as date_filter
11
+from django.utils.http import url_has_allowed_host_and_scheme
11 12
 from django.utils.module_loading import import_string
12 13
 from django.utils.text import slugify as django_slugify
13 14
 from django.utils.timezone import get_current_timezone, is_naive, make_aware
14 15
 from django.utils.translation import get_language, to_locale
15 16
 
16
-from oscar.core.compat import url_has_allowed_host_and_scheme
17
-
18 17
 SLUGIFY_RE = re.compile(r'[^\w\s-]', re.UNICODE)
19 18
 
20 19
 

+ 18
- 21
src/oscar/defaults.py Voir le fichier

@@ -1,5 +1,3 @@
1
-from collections import OrderedDict
2
-
3 1
 from django.urls import reverse_lazy
4 2
 from django.utils.translation import gettext_lazy as _
5 3
 
@@ -216,11 +214,11 @@ OSCAR_DASHBOARD_DEFAULT_ACCESS_FUNCTION = 'oscar.apps.dashboard.nav.default_acce
216 214
 
217 215
 # Search facets
218 216
 OSCAR_SEARCH_FACETS = {
219
-    'fields': OrderedDict([
217
+    'fields': {
220 218
         # The key for these dicts will be used when passing facet data
221 219
         # to the template. Same for the 'queries' dict below.
222
-        ('product_class', {'name': _('Type'), 'field': 'product_class'}),
223
-        ('rating', {'name': _('Rating'), 'field': 'rating'}),
220
+        'product_class': {'name': _('Type'), 'field': 'product_class'},
221
+        'rating': {'name': _('Rating'), 'field': 'rating'},
224 222
         # You can specify an 'options' element that will be passed to the
225 223
         # SearchQuerySet.facet() call.
226 224
         # For instance, with Elasticsearch backend, 'options': {'order': 'term'}
@@ -230,22 +228,21 @@ OSCAR_SEARCH_FACETS = {
230 228
         # items without a specific facet:
231 229
         # http://wiki.apache.org/solr/SimpleFacetParameters#facet.method
232 230
         # 'options': {'missing': 'true'}
233
-    ]),
234
-    'queries': OrderedDict([
235
-        ('price_range',
236
-         {
237
-             'name': _('Price range'),
238
-             'field': 'price',
239
-             'queries': [
240
-                 # This is a list of (name, query) tuples where the name will
241
-                 # be displayed on the front-end.
242
-                 (_('0 to 20'), '[0 TO 20]'),
243
-                 (_('20 to 40'), '[20 TO 40]'),
244
-                 (_('40 to 60'), '[40 TO 60]'),
245
-                 (_('60+'), '[60 TO *]'),
246
-             ]
247
-         }),
248
-    ]),
231
+    },
232
+    'queries': {
233
+        'price_range': {
234
+            'name': _('Price range'),
235
+            'field': 'price',
236
+            'queries': [
237
+                # This is a list of (name, query) tuples where the name will
238
+                # be displayed on the front-end.
239
+                (_('0 to 20'), '[0 TO 20]'),
240
+                (_('20 to 40'), '[20 TO 40]'),
241
+                (_('40 to 60'), '[40 TO 60]'),
242
+                (_('60+'), '[60 TO *]'),
243
+            ]
244
+        },
245
+    },
249 246
 }
250 247
 
251 248
 OSCAR_PRODUCT_SEARCH_HANDLER = None

+ 0
- 1
src/oscar/templates/oscar/dashboard/catalogue/product_update.html Voir le fichier

@@ -1,5 +1,4 @@
1 1
 {% extends 'oscar/dashboard/layout.html' %}
2
-{% load form_tags %}
3 2
 {% load i18n %}
4 3
 
5 4
 {% block body_class %}{{ block.super }} create-page catalogue{% endblock %}

+ 0
- 2
src/oscar/templates/oscar/dashboard/partials/form_field.html Voir le fichier

@@ -1,4 +1,3 @@
1
-{% load form_tags %}
2 1
 {% load widget_tweaks %}
3 2
 
4 3
 {% if field.is_hidden %}
@@ -8,7 +7,6 @@
8 7
         Make the field widget type available to templates so we can mark-up
9 8
         checkbox and radio inputs differently to other widgets.
10 9
     {% endcomment %}
11
-    {% annotate_form_field field %}
12 10
 
13 11
     {% block control_group %}
14 12
         <div class="form-group{% if style == 'horizontal' %} row{% endif %}{% if field.errors %} error{% endif %}">

+ 0
- 2
src/oscar/templates/oscar/partials/form_field.html Voir le fichier

@@ -1,4 +1,3 @@
1
-{% load form_tags %}
2 1
 {% load widget_tweaks %}
3 2
 
4 3
 {% if field.is_hidden %}
@@ -8,7 +7,6 @@
8 7
         Make the field widget type available to templates so we can mark-up
9 8
         checkbox and radio inputs differently to other widgets.
10 9
     {% endcomment %}
11
-    {% annotate_form_field field %}
12 10
 
13 11
     {% block control_group %}
14 12
         <div class="form-group{% if style == "horizontal" %} row{% endif %}">

+ 2
- 2
src/oscar/templatetags/category_tags.py Voir le fichier

@@ -67,8 +67,8 @@ class CheapCategoryInfo(dict, metaclass=CategoryFieldPassThroughMetaClass):
67 67
         yield self
68 68
 
69 69
 
70
-@register.simple_tag(name="category_tree")   # noqa: C901 too complex
71
-def get_annotated_list(depth=None, parent=None):
70
+@register.simple_tag(name="category_tree")
71
+def get_annotated_list(depth=None, parent=None):    # noqa: C901 too complex
72 72
     """
73 73
     Gets an annotated list from a tree branch.
74 74
 

+ 10
- 12
src/oscar/templatetags/form_tags.py Voir le fichier

@@ -1,8 +1,9 @@
1
-import django
1
+import warnings
2
+
2 3
 from django import template
3 4
 from django.template.base import TextNode
4 5
 
5
-from oscar.core.compat import FormFieldNode
6
+from oscar.utils.deprecation import RemovedInOscar32Warning
6 7
 
7 8
 register = template.Library()
8 9
 
@@ -10,15 +11,12 @@ register = template.Library()
10 11
 @register.tag
11 12
 def annotate_form_field(parser, token):
12 13
     """
13
-    Set an attribute on a form field with the widget type
14
-
15
-    This means templates can use the widget type to render things differently
16
-    if they want to. Until 3.1, Django did not make this available by default.
14
+    Used to set an attribute on a form field with the widget type. This is now
15
+    done by Django itself.
17 16
     """
18
-    args = token.split_contents()
19
-    if len(args) < 2:
20
-        raise template.TemplateSyntaxError(
21
-            "annotate_form_field tag requires a form field to be passed")
22
-    if django.VERSION < (3, 1):
23
-        return FormFieldNode(args[1])
17
+    warnings.warn(
18
+        "The annotate_form_field template tag is deprecated and will be removed in the next version of django-oscar",
19
+        RemovedInOscar32Warning,
20
+        stacklevel=2
21
+    )
24 22
     return TextNode('')

+ 2
- 2
src/oscar/templatetags/history_tags.py Voir le fichier

@@ -26,8 +26,8 @@ def recently_viewed_products(context, current_product=None):
26 26
             'request': request}
27 27
 
28 28
 
29
-@register.simple_tag(takes_context=True)  # noqa (too complex (11))
30
-def get_back_button(context):
29
+@register.simple_tag(takes_context=True)
30
+def get_back_button(context):   # noqa (too complex (11))
31 31
     """
32 32
     Show back button, custom title available for different urls, for
33 33
     example 'Back to search results', no back button if user came from other

+ 17
- 20
tests/integration/search/test_munger.py Voir le fichier

@@ -1,5 +1,3 @@
1
-from collections import OrderedDict
2
-
3 1
 from django.test import TestCase
4 2
 from django.test.utils import override_settings
5 3
 from django.utils.translation import gettext_lazy as _
@@ -39,24 +37,23 @@ FACET_COUNTS_WITH_PRICE_RANGE_SELECTED = {
39 37
 
40 38
 
41 39
 SEARCH_FACETS = {
42
-    'fields': OrderedDict([
43
-        ('product_class', {'name': _('Type'), 'field': 'product_class'}),
44
-        ('rating', {'name': _('Rating'), 'field': 'rating'}),
45
-        ('category', {'name': _('Category'), 'field': 'category'}),
46
-    ]),
47
-    'queries': OrderedDict([
48
-        ('price_range',
49
-         {
50
-             'name': _('Price range'),
51
-             'field': 'price',
52
-             'queries': [
53
-                 (_('0 to 20'), '[0 TO 20]'),
54
-                 (_('20 to 40'), '[20 TO 40]'),
55
-                 (_('40 to 60'), '[40 TO 60]'),
56
-                 (_('60+'), '[60 TO *]'),
57
-             ]
58
-         }),
59
-    ]),
40
+    'fields': {
41
+        'product_class': {'name': _('Type'), 'field': 'product_class'},
42
+        'rating': {'name': _('Rating'), 'field': 'rating'},
43
+        'category': {'name': _('Category'), 'field': 'category'},
44
+    },
45
+    'queries': {
46
+        'price_range': {
47
+            'name': _('Price range'),
48
+            'field': 'price',
49
+            'queries': [
50
+                (_('0 to 20'), '[0 TO 20]'),
51
+                (_('20 to 40'), '[20 TO 40]'),
52
+                (_('40 to 60'), '[40 TO 60]'),
53
+                (_('60+'), '[60 TO *]'),
54
+            ]
55
+        },
56
+    },
60 57
 }
61 58
 
62 59
 

+ 5
- 6
tox.ini Voir le fichier

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

Chargement…
Annuler
Enregistrer