Skip to content

Commit c91f910

Browse files
authored
Add support for Django 3.1 (django-oscar#3516)
1 parent 6d9e6dd commit c91f910

File tree

10 files changed

+58
-30
lines changed

10 files changed

+58
-30
lines changed

.travis.yml

+6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ matrix:
2222
env: TOXENV=py37-django30
2323
- python: 3.8
2424
env: TOXENV=py38-django30
25+
- python: 3.6
26+
env: TOXENV=py36-django31
27+
- python: 3.7
28+
env: TOXENV=py37-django31
29+
- python: 3.8
30+
env: TOXENV=py38-django31
2531

2632
- python: 3.7
2733
env: TOXENV=lint

docs/source/releases/v3.0.rst

+5
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ Backwards incompatible changes
4747
Projects that have forked the ``partner`` app will need to generate their own migration
4848
to rename this field.
4949

50+
- The ``annotate_form_field`` template tag will now set the ``widget_type`` in `the format of Django 3.1`_: so no longer
51+
``CheckboxField``, but just ``checkbox``.
52+
53+
.. _`the format of Django 3.1`: https://docs.djangoproject.com/en/3.1/ref/forms/api/#django.forms.BoundField.widget_type
54+
5055
Bug fixes
5156
~~~~~~~~~
5257

setup.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from oscar import get_version # noqa isort:skip
1919

2020
install_requires = [
21-
'django>=2.2,<3.1',
21+
'django>=2.2,<3.2',
2222
# PIL is required for image fields, Pillow is the "friendly" PIL fork
2323
'pillow>=6.0',
2424
# We use the ModelFormSetView from django-extra-views for the basket page
@@ -37,7 +37,7 @@
3737
# Used for oscar.test.newfactories
3838
'factory-boy>=2.4.1,<3.0',
3939
# Used for automatically building larger HTML tables
40-
'django-tables2>=2.2,<2.3',
40+
'django-tables2>=2.3,<2.4',
4141
# Used for manipulating form field attributes in templates (eg: add
4242
# a css class)
4343
'django-widget-tweaks>=1.4.1',

src/oscar/apps/payment/forms.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from datetime import date
44

55
from django import forms
6+
from django.core import validators
67
from django.core.exceptions import ImproperlyConfigured
78
from django.utils.translation import gettext_lazy as _
89

@@ -140,10 +141,10 @@ def clean(self, value):
140141

141142
def compress(self, data_list):
142143
if data_list:
143-
if data_list[1] in forms.fields.EMPTY_VALUES:
144+
if data_list[1] in validators.EMPTY_VALUES:
144145
error = self.error_messages['invalid_year']
145146
raise forms.ValidationError(error)
146-
if data_list[0] in forms.fields.EMPTY_VALUES:
147+
if data_list[0] in validators.EMPTY_VALUES:
147148
error = self.error_messages['invalid_month']
148149
raise forms.ValidationError(error)
149150
year = int(data_list[1])
@@ -186,10 +187,10 @@ def clean(self, value):
186187

187188
def compress(self, data_list):
188189
if data_list:
189-
if data_list[1] in forms.fields.EMPTY_VALUES:
190+
if data_list[1] in validators.EMPTY_VALUES:
190191
error = self.error_messages['invalid_year']
191192
raise forms.ValidationError(error)
192-
if data_list[0] in forms.fields.EMPTY_VALUES:
193+
if data_list[0] in validators.EMPTY_VALUES:
193194
error = self.error_messages['invalid_month']
194195
raise forms.ValidationError(error)
195196
year = int(data_list[1])

src/oscar/core/compat.py

+18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import csv
2+
import re
23

4+
from django import template
35
from django.conf import settings
46
from django.contrib.auth.models import User
57
from django.core.exceptions import ImproperlyConfigured
@@ -127,3 +129,19 @@ def writerow(self, row):
127129
def writerows(self, rows):
128130
for row in rows:
129131
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 ''

src/oscar/templates/oscar/dashboard/partials/form_field.html

+4-4
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<div class="form-group {% if field.errors %}error{% endif %}">
1515

1616
{% block label %}
17-
{% if not nolabel and field.widget_type != 'CheckboxInput' %}
17+
{% if not nolabel and field.widget_type != 'checkbox' %}
1818
<label for="{{ field.auto_id }}" class="{% if style|default:"stacked" != 'stacked' %}col-sm-4{% endif%} control-label{% if field.field.required %} required{% endif %}">
1919
{{ field.label|safe }}
2020
{% if field.field.required %} <span>*</span>{% endif %}
@@ -23,14 +23,14 @@
2323
{% endblock %}
2424

2525
{% block controls %}
26-
<div class="{% if style|default:"stacked" != 'stacked' %}col-sm-8{% endif %}{% if field.widget_type == 'CheckboxInput' %} checkbox{% endif %}">
26+
<div class="{% if style|default:"stacked" != 'stacked' %}col-sm-8{% endif %}{% if field.widget_type == 'checkbox' %} checkbox{% endif %}">
2727
{% block widget %}
28-
{% if field.widget_type == 'CheckboxInput' %}
28+
{% if field.widget_type == 'checkbox' %}
2929
<label for="{{ field.auto_id }}" class="checkbox {% if field.field.required %}required{% endif %}">
3030
{% render_field field %}
3131
{{ field.label|safe }}{% if field.field.required %} <span>*</span>{% endif %}
3232
</label>
33-
{% elif field.widget_type == 'RadioSelect' %}
33+
{% elif field.widget_type == 'radioselect' %}
3434
<label for="{{ field.auto_id }}" class="controls {% if field.field.required %}required{% endif %}">
3535
{% render_field field %}
3636
</label>

src/oscar/templates/oscar/partials/form_field.html

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,17 @@
1414
<div class="form-group {% if field.errors %}has-error{% endif %}">
1515

1616
{% block label %}
17-
{% if not nolabel and field.widget_type != 'CheckboxInput' %}
17+
{% if not nolabel and field.widget_type != 'checkbox' %}
1818
<label for="{{ field.auto_id }}" class="{% if style|default:"stacked" != 'stacked' %}col-sm-4{% endif%} control-label{% if field.field.required %} required{% endif %}">
1919
{{ field.label|safe }}
2020
</label>
2121
{% endif %}
2222
{% endblock %}
2323

2424
{% block controls %}
25-
<div class="{% if style|default:"stacked" != 'stacked' %}col-sm-7{% endif %}{% if field.widget_type == 'CheckboxInput' %} checkbox{% endif %}">
25+
<div class="{% if style|default:"stacked" != 'stacked' %}col-sm-7{% endif %}{% if field.widget_type == 'checkbox' %} checkbox{% endif %}">
2626
{% block widget %}
27-
{% if field.widget_type == 'CheckboxInput' %}
27+
{% if field.widget_type == 'checkbox' %}
2828
<label for="{{ field.auto_id }}" {% if field.field.required %}class="required"{% endif %}>
2929
{% render_field field %}
3030
{{ field.label|safe }}{% if field.field.required %} <span>*</span>{% endif %}

src/oscar/templatetags/form_tags.py

+8-13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
import django
12
from django import template
3+
from django.template.base import TextNode
4+
5+
from oscar.core.compat import FormFieldNode
26

37
register = template.Library()
48

@@ -9,21 +13,12 @@ def annotate_form_field(parser, token):
913
Set an attribute on a form field with the widget type
1014
1115
This means templates can use the widget type to render things differently
12-
if they want to. Django doesn't make this available by default.
16+
if they want to. Until 3.1, Django did not make this available by default.
1317
"""
1418
args = token.split_contents()
1519
if len(args) < 2:
1620
raise template.TemplateSyntaxError(
1721
"annotate_form_field tag requires a form field to be passed")
18-
return FormFieldNode(args[1])
19-
20-
21-
class FormFieldNode(template.Node):
22-
def __init__(self, field_str):
23-
self.field = template.Variable(field_str)
24-
25-
def render(self, context):
26-
field = self.field.resolve(context)
27-
if hasattr(field, 'field'):
28-
field.widget_type = field.field.widget.__class__.__name__
29-
return ''
22+
if django.VERSION < (3, 1):
23+
return FormFieldNode(args[1])
24+
return TextNode('')

tests/settings.py

+2
Original file line numberDiff line numberDiff line change
@@ -168,5 +168,7 @@
168168
OSCAR_LINE_STATUS_PIPELINE = {'a': ('b', ), 'b': ()}
169169

170170
SECRET_KEY = 'notverysecret'
171+
# Deprecated in Django 4.0, then we need to update the hashes to SHA-256 in tests/integration/order/test_models.py
172+
DEFAULT_HASHING_ALGORITHM = 'sha1'
171173
TEST_RUNNER = 'django.test.runner.DiscoverRunner'
172174
FIXTURE_DIRS = [location('unit/fixtures')]

tox.ini

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tox]
22
envlist =
3-
py{36,37,38}-django{22,30}
3+
py{36,37,38}-django{22,30,31}
44
lint
55
sandbox
66
docs
@@ -13,12 +13,13 @@ pip_pre = true
1313
deps =
1414
django22: django>=2.2,<2.3
1515
django30: django>=3.0,<3.1
16-
16+
django31: django>=3.1,<3.2
1717

1818
[testenv:lint]
1919
basepython = python3.7
2020
deps =
2121
-r{toxinidir}/requirements.txt
22+
allowlist_externals = npm
2223
commands =
2324
npm ci
2425
flake8 src tests setup.py
@@ -32,13 +33,13 @@ basepython = python3.7
3233
deps =
3334
-r{toxinidir}/requirements.txt
3435
django>=2.2,<2.3
35-
whitelist_externals = make
36+
allowlist_externals = make
3637
commands =
3738
make build_sandbox
3839

3940
[testenv:docs]
4041
basepython = python3.7
41-
whitelist_externals = make
42+
allowlist_externals = make
4243
changedir = {toxinidir}/docs
4344
pip_pre = false
4445
deps =

0 commit comments

Comments
 (0)