diff --git a/.zuul.yaml b/.zuul.yaml
index d4488ac2f9..a02f1c8d77 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -54,11 +54,11 @@
- project:
check:
jobs:
- - horizon-openstack-tox-py35dj20:
- voting: false
+ - horizon-openstack-tox-py35dj20
- horizon-selenium-headless
- horizon-dsvm-tempest-plugin
gate:
jobs:
+ - horizon-openstack-tox-py35dj20
- horizon-selenium-headless
- horizon-dsvm-tempest-plugin
diff --git a/horizon/base.py b/horizon/base.py
index 9e60f10ad4..4ed7d19ca8 100644
--- a/horizon/base.py
+++ b/horizon/base.py
@@ -27,7 +27,6 @@ import inspect
import logging
import os
-import django
from django.conf import settings
from django.conf.urls import include
from django.conf.urls import url
@@ -58,12 +57,7 @@ def _decorate_urlconf(urlpatterns, decorator, *args, **kwargs):
for pattern in urlpatterns:
if getattr(pattern, 'callback', None):
decorated = decorator(pattern.callback, *args, **kwargs)
- if django.VERSION >= (1, 10):
- pattern.callback = decorated
- else:
- # prior to 1.10 callback was a property and we had
- # to modify the private attribute behind the property
- pattern._callback = decorated
+ pattern.callback = decorated
if getattr(pattern, 'url_patterns', []):
_decorate_urlconf(pattern.url_patterns, decorator, *args, **kwargs)
diff --git a/horizon/templatetags/shellfilter.py b/horizon/templatetags/shellfilter.py
index 108ca6204b..d3f2ac599b 100644
--- a/horizon/templatetags/shellfilter.py
+++ b/horizon/templatetags/shellfilter.py
@@ -10,15 +10,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-import django
from django import template
from django.template import defaultfilters
from django.utils import safestring
-if django.VERSION >= (1, 9):
- register = template.Library()
-else:
- register = template.base.Library()
+register = template.Library()
@register.filter(is_safe=True)
diff --git a/horizon/test/unit/test_base.py b/horizon/test/unit/test_base.py
index 344c80444f..a0fc9ed755 100644
--- a/horizon/test/unit/test_base.py
+++ b/horizon/test/unit/test_base.py
@@ -22,7 +22,6 @@ from importlib import import_module
import six
from six import moves
-import django
from django.conf import settings
from django.contrib.auth.models import User
from django.core.exceptions import ImproperlyConfigured
@@ -310,10 +309,7 @@ class HorizonTests(BaseHorizonTests):
self.client.logout()
resp = self.client.get(url)
- if django.VERSION >= (1, 9):
- self.assertRedirects(resp, settings.TESTSERVER + redirect_url)
- else:
- self.assertRedirects(resp, redirect_url)
+ self.assertRedirects(resp, settings.TESTSERVER + redirect_url)
# Set SSL settings for test server
settings.SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL',
diff --git a/openstack_auth/tests/unit/test_auth.py b/openstack_auth/tests/unit/test_auth.py
index c48702c472..2dd193ce11 100644
--- a/openstack_auth/tests/unit/test_auth.py
+++ b/openstack_auth/tests/unit/test_auth.py
@@ -13,7 +13,6 @@
import uuid
-import django
from django.conf import settings
from django.contrib import auth
from django import test
@@ -395,10 +394,7 @@ class OpenStackAuthTestsV2(OpenStackAuthTestsMixin, test.TestCase):
response = self.client.get(url, form_data)
if next:
- if django.VERSION >= (1, 9):
- expected_url = next
- else:
- expected_url = 'http://testserver%s' % next
+ expected_url = next
self.assertEqual(response['location'], expected_url)
else:
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL)
@@ -444,10 +440,7 @@ class OpenStackAuthTestsV2(OpenStackAuthTestsMixin, test.TestCase):
response = self.client.get(url, form_data)
if next:
- if django.VERSION >= (1, 9):
- expected_url = next
- else:
- expected_url = 'http://testserver%s' % next
+ expected_url = next
self.assertEqual(response['location'], expected_url)
else:
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL)
@@ -768,10 +761,7 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin,
response = self.client.get(url, form_data)
if next:
- if django.VERSION >= (1, 9):
- expected_url = next
- else:
- expected_url = 'http://testserver%s' % next
+ expected_url = next
self.assertEqual(response['location'], expected_url)
else:
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL)
@@ -816,10 +806,7 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin,
response = self.client.get(url, form_data)
if next:
- if django.VERSION >= (1, 9):
- expected_url = next
- else:
- expected_url = 'http://testserver%s' % next
+ expected_url = next
self.assertEqual(response['location'], expected_url)
else:
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL)
diff --git a/openstack_dashboard/dashboards/identity/projects/tests.py b/openstack_dashboard/dashboards/identity/projects/tests.py
index d5b6d91bd0..26d1bac440 100644
--- a/openstack_dashboard/dashboards/identity/projects/tests.py
+++ b/openstack_dashboard/dashboards/identity/projects/tests.py
@@ -17,7 +17,6 @@ import logging
import os
import unittest
-import django
from django import http
from django.test.utils import override_settings
from django.urls import reverse
@@ -277,14 +276,9 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
res = self.client.get(reverse('horizon:identity:projects:create'))
self.assertTemplateUsed(res, views.WorkflowView.template_name)
- if django.VERSION >= (1, 10):
- pattern = ('')
- else:
- pattern = ('')
+ pattern = ('')
self.assertContains(res, pattern, html=True)
workflow = res.context['workflow']
diff --git a/openstack_dashboard/dashboards/identity/users/tests.py b/openstack_dashboard/dashboards/identity/users/tests.py
index c062cddf7f..b0d6f59e40 100644
--- a/openstack_dashboard/dashboards/identity/users/tests.py
+++ b/openstack_dashboard/dashboards/identity/users/tests.py
@@ -18,7 +18,6 @@
from socket import timeout as socket_timeout
-import django
from django import http
from django.test.utils import override_settings
from django.urls import reverse
@@ -238,19 +237,18 @@ class UsersViewTests(test.BaseAdminViewTests):
api.keystone.role_list(IgnoreArg()).AndReturn(self.roles.list())
api.keystone.get_default_role(IgnoreArg()) \
.AndReturn(self.roles.first())
- if django.VERSION >= (1, 9):
- if api.keystone.VERSIONS.active >= 3:
- api.keystone.tenant_list(
- IgnoreArg(), domain=domain_id).AndReturn(
- [self.tenants.list(), False])
- else:
- api.keystone.tenant_list(
- IgnoreArg(), user=None).AndReturn(
- [self.tenants.list(), False])
+ if api.keystone.VERSIONS.active >= 3:
+ api.keystone.tenant_list(
+ IgnoreArg(), domain=domain_id).AndReturn(
+ [self.tenants.list(), False])
+ else:
+ api.keystone.tenant_list(
+ IgnoreArg(), user=None).AndReturn(
+ [self.tenants.list(), False])
- api.keystone.role_list(IgnoreArg()).AndReturn(self.roles.list())
- api.keystone.get_default_role(IgnoreArg()) \
- .AndReturn(self.roles.first())
+ api.keystone.role_list(IgnoreArg()).AndReturn(self.roles.list())
+ api.keystone.get_default_role(IgnoreArg()) \
+ .AndReturn(self.roles.first())
self.mox.ReplayAll()
@@ -291,19 +289,18 @@ class UsersViewTests(test.BaseAdminViewTests):
api.keystone.role_list(IgnoreArg()).AndReturn(self.roles.list())
api.keystone.get_default_role(IgnoreArg()) \
.AndReturn(self.roles.first())
- if django.VERSION >= (1, 9):
- if api.keystone.VERSIONS.active >= 3:
- api.keystone.tenant_list(
- IgnoreArg(), domain=domain_id).AndReturn(
- [self.tenants.list(), False])
- else:
- api.keystone.tenant_list(
- IgnoreArg(), user=None).AndReturn(
- [self.tenants.list(), False])
+ if api.keystone.VERSIONS.active >= 3:
+ api.keystone.tenant_list(
+ IgnoreArg(), domain=domain_id).AndReturn(
+ [self.tenants.list(), False])
+ else:
+ api.keystone.tenant_list(
+ IgnoreArg(), user=None).AndReturn(
+ [self.tenants.list(), False])
- api.keystone.role_list(IgnoreArg()).AndReturn(self.roles.list())
- api.keystone.get_default_role(IgnoreArg()) \
- .AndReturn(self.roles.first())
+ api.keystone.role_list(IgnoreArg()).AndReturn(self.roles.list())
+ api.keystone.get_default_role(IgnoreArg()) \
+ .AndReturn(self.roles.first())
self.mox.ReplayAll()
@@ -347,19 +344,18 @@ class UsersViewTests(test.BaseAdminViewTests):
api.keystone.role_list(IgnoreArg()).AndReturn(self.roles.list())
api.keystone.get_default_role(IgnoreArg()) \
.AndReturn(self.roles.first())
- if django.VERSION >= (1, 9):
- if api.keystone.VERSIONS.active >= 3:
- api.keystone.tenant_list(
- IgnoreArg(), domain=domain_id).AndReturn(
- [self.tenants.list(), False])
- else:
- api.keystone.tenant_list(
- IgnoreArg(), user=None).AndReturn(
- [self.tenants.list(), False])
+ if api.keystone.VERSIONS.active >= 3:
+ api.keystone.tenant_list(
+ IgnoreArg(), domain=domain_id).AndReturn(
+ [self.tenants.list(), False])
+ else:
+ api.keystone.tenant_list(
+ IgnoreArg(), user=None).AndReturn(
+ [self.tenants.list(), False])
- api.keystone.role_list(IgnoreArg()).AndReturn(self.roles.list())
- api.keystone.get_default_role(IgnoreArg()) \
- .AndReturn(self.roles.first())
+ api.keystone.role_list(IgnoreArg()).AndReturn(self.roles.list())
+ api.keystone.get_default_role(IgnoreArg()) \
+ .AndReturn(self.roles.first())
self.mox.ReplayAll()
diff --git a/openstack_dashboard/dashboards/project/instances/tests.py b/openstack_dashboard/dashboards/project/instances/tests.py
index 9a67c827d4..8e2b5cd989 100644
--- a/openstack_dashboard/dashboards/project/instances/tests.py
+++ b/openstack_dashboard/dashboards/project/instances/tests.py
@@ -1783,18 +1783,11 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
# NOTE(adriant): Django 1.11 changes the checked syntax to use html5
# "checked" rather than XHTML's "checked='checked'".
- if django.VERSION >= (1, 11):
- checked_box = (
- ''
- )
- else:
- checked_box = (
- ''
- )
+ checked_box = (
+ ''
+ )
if only_one_network:
self.assertContains(res, checked_box, html=True)
else:
diff --git a/openstack_dashboard/dashboards/project/routers/tests.py b/openstack_dashboard/dashboards/project/routers/tests.py
index c9423864df..71d6a4f7bc 100644
--- a/openstack_dashboard/dashboards/project/routers/tests.py
+++ b/openstack_dashboard/dashboards/project/routers/tests.py
@@ -13,7 +13,6 @@
# under the License.
import copy
-import django
from django import http
from django.urls import reverse
@@ -590,14 +589,9 @@ class RouterActionTests(RouterMixin, test.TestCase):
self.assertTemplateUsed(res, 'project/routers/update.html')
self.assertContains(res, 'Router Type')
- if django.VERSION >= (1, 10):
- pattern = ('')
- else:
- pattern = ('')
+ pattern = ('')
self.assertContains(res, pattern, html=True)
self.assertNotContains(res, 'centralized')
diff --git a/openstack_dashboard/dashboards/project/security_groups/tests.py b/openstack_dashboard/dashboards/project/security_groups/tests.py
index 95cce0b116..a97332ab60 100644
--- a/openstack_dashboard/dashboards/project/security_groups/tests.py
+++ b/openstack_dashboard/dashboards/project/security_groups/tests.py
@@ -21,7 +21,6 @@ import cgi
from mox3.mox import IsA
import six
-import django
from django.conf import settings
from django import http
from django.urls import reverse
@@ -478,9 +477,7 @@ class SecurityGroupsViewTests(test.TestCase):
sec_group_list = self.security_groups.list()
rule = self.security_group_rules.first()
- api.neutron.security_group_list(
- IsA(http.HttpRequest)).AndReturn(sec_group_list)
- if django.VERSION >= (1, 9):
+ for i in range(2):
api.neutron.security_group_list(
IsA(http.HttpRequest)).AndReturn(sec_group_list)
@@ -503,13 +500,9 @@ class SecurityGroupsViewTests(test.TestCase):
sec_group_list = self.security_groups.list()
rule = self.security_group_rules.first()
- for i in range(3):
+ for i in range(6):
api.neutron.security_group_list(
IsA(http.HttpRequest)).AndReturn(sec_group_list)
- if django.VERSION >= (1, 9):
- for i in range(3):
- api.neutron.security_group_list(
- IsA(http.HttpRequest)).AndReturn(sec_group_list)
self.mox.ReplayAll()
@@ -559,10 +552,7 @@ class SecurityGroupsViewTests(test.TestCase):
icmp_rule = self.security_group_rules.list()[1]
# Call POST 5 times (*2 if Django >= 1.9)
- call_post = 5
- if django.VERSION >= (1, 9):
- call_post *= 2
-
+ call_post = 5 * 2
for i in range(call_post):
api.neutron.security_group_list(
IsA(http.HttpRequest)).AndReturn(sec_group_list)
@@ -921,9 +911,7 @@ class SecurityGroupsViewTests(test.TestCase):
sec_group_list = self.security_groups.list()
rule = self.security_group_rules.first()
- api.neutron.security_group_list(
- IsA(http.HttpRequest)).AndReturn(sec_group_list)
- if django.VERSION >= (1, 9):
+ for i in range(2):
api.neutron.security_group_list(
IsA(http.HttpRequest)).AndReturn(sec_group_list)
diff --git a/openstack_dashboard/dashboards/project/volumes/tests.py b/openstack_dashboard/dashboards/project/volumes/tests.py
index 52e6f996c4..89d687e590 100644
--- a/openstack_dashboard/dashboards/project/volumes/tests.py
+++ b/openstack_dashboard/dashboards/project/volumes/tests.py
@@ -17,7 +17,6 @@ import copy
import mock
import six
-import django
from django.conf import settings
from django.forms import widgets
from django.template.defaultfilters import slugify
@@ -634,12 +633,8 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.assertFormError(res, 'form', None,
"The volume size cannot be less than the "
"snapshot size (40GiB)")
- if django.VERSION >= (1, 9):
- self.assertEqual(3, self.mock_volume_type_list.call_count)
- self.assertEqual(2, self.mock_volume_type_default.call_count)
- else:
- self.assertEqual(2, self.mock_volume_type_list.call_count)
- self.assertEqual(1, self.mock_volume_type_default.call_count)
+ self.assertEqual(3, self.mock_volume_type_list.call_count)
+ self.assertEqual(2, self.mock_volume_type_default.call_count)
self.mock_volume_snapshot_get.assert_called_with(test.IsHttpRequest(),
str(snapshot.id))
@@ -812,12 +807,8 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.assertFormError(res, 'form', None, msg)
- if django.VERSION >= (1, 9):
- self.assertEqual(3, self.mock_volume_type_list.call_count)
- self.assertEqual(2, self.mock_volume_type_default.call_count)
- else:
- self.assertEqual(2, self.mock_volume_type_list.call_count)
- self.assertEqual(1, self.mock_volume_type_default.call_count)
+ self.assertEqual(3, self.mock_volume_type_list.call_count)
+ self.assertEqual(2, self.mock_volume_type_default.call_count)
self.assertEqual(2, self.mock_tenant_limit_usages.call_count)
self.mock_image_get.assert_called_with(test.IsHttpRequest(),
@@ -861,14 +852,9 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.assertFormError(res, 'form', None,
"The volume size cannot be less than the "
"image minimum disk size (30GiB)")
- if django.VERSION >= (1, 9):
- self.assertEqual(3, self.mock_volume_type_list.call_count)
- self.assertEqual(2, self.mock_volume_type_default.call_count)
- self.assertEqual(2, self.mock_availability_zone_list.call_count)
- else:
- self.assertEqual(2, self.mock_volume_type_list.call_count)
- self.assertEqual(1, self.mock_volume_type_default.call_count)
- self.assertEqual(1, self.mock_availability_zone_list.call_count)
+ self.assertEqual(3, self.mock_volume_type_list.call_count)
+ self.assertEqual(2, self.mock_volume_type_default.call_count)
+ self.assertEqual(2, self.mock_availability_zone_list.call_count)
self.mock_image_get.assert_called_with(test.IsHttpRequest(),
str(image.id))
@@ -932,16 +918,10 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
' have 20GiB of your quota available.']
self.assertEqual(res.context['form'].errors['__all__'], expected_error)
- if django.VERSION >= (1, 9):
- self.assertEqual(3, self.mock_volume_type_list.call_count)
- self.assertEqual(2, self.mock_volume_type_default.call_count)
- self.assertEqual(2, self.mock_volume_list.call_count)
- self.assertEqual(2, self.mock_availability_zone_list.call_count)
- else:
- self.assertEqual(2, self.mock_volume_type_list.call_count)
- self.assertEqual(1, self.mock_volume_type_default.call_count)
- self.assertEqual(1, self.mock_volume_list.call_count)
- self.assertEqual(1, self.mock_availability_zone_list.call_count)
+ self.assertEqual(3, self.mock_volume_type_list.call_count)
+ self.assertEqual(2, self.mock_volume_type_default.call_count)
+ self.assertEqual(2, self.mock_volume_list.call_count)
+ self.assertEqual(2, self.mock_availability_zone_list.call_count)
self.assertEqual(2, self.mock_tenant_limit_usages.call_count)
self.mock_volume_snapshot_list.assert_called_with(
@@ -993,14 +973,9 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
' volumes.']
self.assertEqual(res.context['form'].errors['__all__'], expected_error)
- if django.VERSION >= (1, 9):
- self.assertEqual(3, self.mock_volume_type_list.call_count)
- self.assertEqual(2, self.mock_volume_type_default.call_count)
- self.assertEqual(2, self.mock_availability_zone_list.call_count)
- else:
- self.assertEqual(2, self.mock_volume_type_list.call_count)
- self.assertEqual(1, self.mock_volume_type_default.call_count)
- self.assertEqual(1, self.mock_availability_zone_list.call_count)
+ self.assertEqual(3, self.mock_volume_type_list.call_count)
+ self.assertEqual(2, self.mock_volume_type_default.call_count)
+ self.assertEqual(2, self.mock_availability_zone_list.call_count)
self.mock_volume_snapshot_list.assert_called_with(
test.IsHttpRequest(), search_opts=SEARCH_OPTS)
diff --git a/openstack_dashboard/test/helpers.py b/openstack_dashboard/test/helpers.py
index 6226ff304a..8d3fb12020 100644
--- a/openstack_dashboard/test/helpers.py
+++ b/openstack_dashboard/test/helpers.py
@@ -25,7 +25,6 @@ import os
import traceback
import unittest
-import django
from django.conf import settings
from django.contrib.messages.storage import default_storage
from django.core.handlers import wsgi
@@ -342,14 +341,10 @@ class TestCase(horizon_helpers.TestCase):
Asserts that the given response issued a 302 redirect without
processing the view which is redirected to.
"""
- if django.VERSION >= (1, 9):
- loc = six.text_type(response._headers.get('location', None)[1])
- loc = http.urlunquote(loc)
- expected_url = http.urlunquote(expected_url)
- self.assertEqual(loc, expected_url)
- else:
- self.assertEqual(response._headers.get('location', None),
- ('Location', settings.TESTSERVER + expected_url))
+ loc = six.text_type(response._headers.get('location', None)[1])
+ loc = http.urlunquote(loc)
+ expected_url = http.urlunquote(expected_url)
+ self.assertEqual(loc, expected_url)
self.assertEqual(response.status_code, 302)
def assertNoFormErrors(self, response, context_name="form"):
diff --git a/releasenotes/notes/django-2.0-b37c6e91d20519fa.yaml b/releasenotes/notes/django-2.0-b37c6e91d20519fa.yaml
new file mode 100644
index 0000000000..b829768d46
--- /dev/null
+++ b/releasenotes/notes/django-2.0-b37c6e91d20519fa.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+ - |
+ Django 2.0 support is added as experimental.
+ Support for Django 1.10 or older releases is dropped.
+ Django 1.11 (LTS) is still the primary supported Django version.