From 26b4e30833b0410d0131c5e7bfa40b6f418ae135 Mon Sep 17 00:00:00 2001
From: Michael Youngstrom <myoungstrom@edx.org>
Date: Tue, 5 Jun 2018 14:21:37 -0400
Subject: [PATCH] Remove django 1.8 shim

---
 .../contentstore/tests/test_contentstore.py   |  10 +-
 .../course_creators/tests/test_admin.py       |   8 +-
 cms/envs/common.py                            |  11 +-
 cms/startup.py                                |   8 -
 common/djangoapps/student/helpers.py          |  10 +-
 common/djangoapps/student/tests/test_login.py |   9 +-
 .../third_party_auth/tests/specs/base.py      |  19 +-
 .../tests/specs/test_google.py                |   7 +-
 .../third_party_auth/tests/specs/test_lti.py  |  11 +-
 .../tests/specs/test_testshib.py              |   3 +-
 common/djangoapps/util/tests/test_db.py       |   7 -
 common/test/acceptance/pages/lms/admin.py     |   8 +-
 .../course_api/blocks/tests/test_api.py       |  17 +-
 lms/djangoapps/course_wiki/editors.py         |  12 +-
 lms/djangoapps/course_wiki/tests/tests.py     |   7 +-
 lms/djangoapps/courseware/tests/test_views.py |  17 +-
 lms/djangoapps/grades/tests/test_tasks.py     |  39 +---
 .../student_account/test/test_views.py        |   9 +-
 lms/envs/common.py                            |  10 +-
 lms/envs/load_test.py                         |   6 +-
 lms/startup.py                                |   7 -
 openedx/core/djangoapps/api_admin/widgets.py  |  12 +-
 .../djangoapps/cors_csrf/authentication.py    |   5 +-
 .../core/djangoapps/cors_csrf/middleware.py   |   9 +-
 .../cors_csrf/tests/test_middleware.py        |   9 +-
 .../external_auth/tests/test_shib.py          |  11 +-
 .../external_auth/tests/test_ssl.py           |  13 +-
 .../dot_overrides/validators.py               |  13 +-
 openedx/core/djangoapps/theming/storage.py    | 166 ------------------
 .../management/commands/email_opt_in_list.py  |   7 +-
 openedx/tests/util/__init__.py                |  30 ----
 requirements/edx-sandbox/base.txt             |   1 +
 requirements/edx-sandbox/shared.txt           |   1 +
 requirements/edx/base.in                      |   1 -
 requirements/edx/base.txt                     |  10 +-
 requirements/edx/coverage.txt                 |   1 +
 requirements/edx/development.txt              |  20 +--
 requirements/edx/paver.txt                    |   3 +-
 requirements/edx/pip-tools.txt                |   1 +
 requirements/edx/testing.txt                  |  17 +-
 40 files changed, 100 insertions(+), 465 deletions(-)

diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py
index d5c3bce6e30..c176998c4a5 100644
--- a/cms/djangoapps/contentstore/tests/test_contentstore.py
+++ b/cms/djangoapps/contentstore/tests/test_contentstore.py
@@ -12,11 +12,11 @@ from unittest import SkipTest
 from uuid import uuid4
 
 import ddt
-import django
 import lxml.html
 import mock
 from django.conf import settings
 from django.contrib.auth.models import User
+from django.middleware.csrf import _compare_salted_tokens
 from django.test import TestCase
 from django.test.utils import override_settings
 from edxval.api import create_video, get_videos_for_course
@@ -2235,13 +2235,7 @@ class SigninPageTestCase(TestCase):
         self.assertIsNotNone(csrf_token.value)
         self.assertIsNotNone(csrf_input_field)
 
-        # TODO: Remove Django 1.11 upgrade shim
-        # SHIM: _compare_salted_tokens was introduced in 1.10. Move the import and use only that branch post-upgrade.
-        if django.VERSION < (1, 10):
-            self.assertEqual(csrf_token.value, csrf_input_field.attrib["value"])
-        else:
-            from django.middleware.csrf import _compare_salted_tokens
-            self.assertTrue(_compare_salted_tokens(csrf_token.value, csrf_input_field.attrib["value"]))
+        self.assertTrue(_compare_salted_tokens(csrf_token.value, csrf_input_field.attrib["value"]))
 
 
 def _create_course(test, course_key, course_data):
diff --git a/cms/djangoapps/course_creators/tests/test_admin.py b/cms/djangoapps/course_creators/tests/test_admin.py
index 438b2e6bf80..66a014b6438 100644
--- a/cms/djangoapps/course_creators/tests/test_admin.py
+++ b/cms/djangoapps/course_creators/tests/test_admin.py
@@ -3,7 +3,6 @@ Tests course_creators.admin.py.
 """
 
 import mock
-import django
 from django.contrib.admin.sites import AdminSite
 from django.contrib.auth.models import User
 from django.core import mail
@@ -106,12 +105,7 @@ class CourseCreatorAdminTest(TestCase):
             # message sent. Admin message will follow.
             base_num_emails = 1 if expect_sent_to_user else 0
             if expect_sent_to_admin:
-                # TODO: Remove Django 1.11 upgrade shim
-                # SHIM: Usernames come back as unicode in 1.10+, remove this shim post-upgrade
-                if django.VERSION < (1, 10):
-                    context = {'user_name': 'test_user', 'user_email': u'test_user+courses@edx.org'}
-                else:
-                    context = {'user_name': u'test_user', 'user_email': u'test_user+courses@edx.org'}
+                context = {'user_name': u'test_user', 'user_email': u'test_user+courses@edx.org'}
 
                 self.assertEquals(base_num_emails + 1, len(mail.outbox), 'Expected admin message to be sent')
                 sent_mail = mail.outbox[base_num_emails]
diff --git a/cms/envs/common.py b/cms/envs/common.py
index 3b82d90008f..b67f1ac9000 100644
--- a/cms/envs/common.py
+++ b/cms/envs/common.py
@@ -46,8 +46,6 @@ import os
 import sys
 from datetime import timedelta
 
-import django
-
 import lms.envs.common
 # Although this module itself may not use these imported variables, other dependent modules may.
 from lms.envs.common import (
@@ -461,13 +459,6 @@ XQUEUE_INTERFACE = {
 
 ################################# Middleware ###################################
 
-# TODO: Remove Django 1.11 upgrade shim
-# SHIM: Remove birdcage references post-1.11 upgrade as it is only in place to help during that deployment
-if django.VERSION < (1, 9):
-    _csrf_middleware = 'birdcage.v1_11.csrf.CsrfViewMiddleware'
-else:
-    _csrf_middleware = 'django.middleware.csrf.CsrfViewMiddleware'
-
 MIDDLEWARE_CLASSES = [
     'crum.CurrentRequestUserMiddleware',
     'openedx.core.djangoapps.request_cache.middleware.RequestCache',
@@ -477,7 +468,7 @@ MIDDLEWARE_CLASSES = [
     'openedx.core.djangoapps.header_control.middleware.HeaderControlMiddleware',
     'django.middleware.cache.UpdateCacheMiddleware',
     'django.middleware.common.CommonMiddleware',
-    _csrf_middleware,
+    'django.middleware.csrf.CsrfViewMiddleware',
     'django.contrib.sites.middleware.CurrentSiteMiddleware',
 
     # Allows us to define redirects via Django admin
diff --git a/cms/startup.py b/cms/startup.py
index a791a3a9536..abf6f7cd139 100644
--- a/cms/startup.py
+++ b/cms/startup.py
@@ -5,10 +5,7 @@ Module for code that should run during Studio startup (deprecated)
 import django
 from django.conf import settings
 
-from openedx.core.djangoapps.monkey_patch import django_db_models_options
-
 # Force settings to run so that the python path is modified
-
 settings.INSTALLED_APPS  # pylint: disable=pointless-statement
 
 
@@ -19,9 +16,4 @@ def run():
     NOTE: DO **NOT** add additional code to this method or this file! The Platform Team
           is moving all startup code to more standard locations using Django best practices.
     """
-    # TODO: Remove Django 1.11 upgrade shim
-    # SHIM: We should be able to get rid of this monkey patch post-upgrade
-    if django.VERSION[0] == 1 and django.VERSION[1] < 10:
-        django_db_models_options.patch()
-
     django.setup()
diff --git a/common/djangoapps/student/helpers.py b/common/djangoapps/student/helpers.py
index b31651acad5..74c1464195f 100644
--- a/common/djangoapps/student/helpers.py
+++ b/common/djangoapps/student/helpers.py
@@ -8,7 +8,6 @@ import urllib
 import urlparse
 from datetime import datetime
 
-import django
 from django.conf import settings
 from django.core.exceptions import PermissionDenied
 from django.urls import NoReverseMatch, reverse
@@ -413,13 +412,8 @@ def create_or_set_user_attribute_created_on_site(user, site):
         UserAttribute.set_user_attribute(user, 'created_on_site', site.domain)
 
 
-# TODO: Remove Django 1.11 upgrade shim
-# SHIM: Compensate for behavior change of default authentication backend in 1.10
-if django.VERSION < (1, 10):
-    NEW_USER_AUTH_BACKEND = 'django.contrib.auth.backends.ModelBackend'
-else:
-    # We want to allow inactive users to log in only when their account is first created
-    NEW_USER_AUTH_BACKEND = 'django.contrib.auth.backends.AllowAllUsersModelBackend'
+# We want to allow inactive users to log in only when their account is first created
+NEW_USER_AUTH_BACKEND = 'django.contrib.auth.backends.AllowAllUsersModelBackend'
 
 # Disable this warning because it doesn't make sense to completely refactor tests to appease Pylint
 # pylint: disable=logging-format-interpolation
diff --git a/common/djangoapps/student/tests/test_login.py b/common/djangoapps/student/tests/test_login.py
index 089cbb15af2..6258ad83c21 100644
--- a/common/djangoapps/student/tests/test_login.py
+++ b/common/djangoapps/student/tests/test_login.py
@@ -24,7 +24,6 @@ from openedx.core.djangoapps.password_policy.compliance import (
     NonCompliantPasswordWarning
 )
 from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
-from openedx.tests.util import expected_redirect_url
 from student.tests.factories import RegistrationFactory, UserFactory, UserProfileFactory
 from student.views import login_oauth_token
 from third_party_auth.tests.utils import (
@@ -597,7 +596,7 @@ class ExternalAuthShibTest(ModuleStoreTestCase):
         """
         response = self.client.get(reverse('dashboard'))
         self.assertEqual(response.status_code, 302)
-        self.assertEqual(response['Location'], expected_redirect_url('/login?next=/dashboard'))
+        self.assertEqual(response['Location'], '/login?next=/dashboard')
 
     @unittest.skipUnless(settings.FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set")
     def test_externalauth_login_required_course_context(self):
@@ -608,7 +607,7 @@ class ExternalAuthShibTest(ModuleStoreTestCase):
         target_url = reverse('courseware', args=[text_type(self.course.id)])
         noshib_response = self.client.get(target_url, follow=True, HTTP_ACCEPT="text/html")
         self.assertEqual(noshib_response.redirect_chain[-1],
-                         (expected_redirect_url('/login?next={url}'.format(url=target_url)), 302))
+                         ('/login?next={url}'.format(url=target_url), 302))
         self.assertContains(noshib_response, (u"Sign in or Register | {platform_name}"
                                               .format(platform_name=settings.PLATFORM_NAME)))
         self.assertEqual(noshib_response.status_code, 200)
@@ -623,9 +622,9 @@ class ExternalAuthShibTest(ModuleStoreTestCase):
         # The 'courseware' page actually causes a redirect itself, so it's not the end of the chain and we
         # won't test its contents
         self.assertEqual(shib_response.redirect_chain[-3],
-                         (expected_redirect_url('/shib-login/?next={url}'.format(url=target_url_shib)), 302))
+                         ('/shib-login/?next={url}'.format(url=target_url_shib), 302))
         self.assertEqual(shib_response.redirect_chain[-2],
-                         (expected_redirect_url(target_url_shib), 302))
+                         (target_url_shib, 302))
         self.assertEqual(shib_response.status_code, 200)
 
 
diff --git a/common/djangoapps/third_party_auth/tests/specs/base.py b/common/djangoapps/third_party_auth/tests/specs/base.py
index 2842c56fa04..16d75b91408 100644
--- a/common/djangoapps/third_party_auth/tests/specs/base.py
+++ b/common/djangoapps/third_party_auth/tests/specs/base.py
@@ -8,7 +8,6 @@ import json
 import mock
 
 from contextlib import contextmanager
-import django
 from django import test
 from django.contrib import auth
 from django.contrib.auth import models as auth_models
@@ -23,7 +22,6 @@ from social_django import views as social_views
 
 from lms.djangoapps.commerce.tests import TEST_API_URL
 from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory
-from openedx.tests.util import expected_redirect_url
 from student import models as student_models
 from student import views as student_views
 from student.tests.factories import UserFactory
@@ -68,7 +66,7 @@ class IntegrationTestMixin(object):
         provider_response = self.do_provider_login(try_login_response['Location'])
         # We should be redirected to the register screen since this account is not linked to an edX account:
         self.assertEqual(provider_response.status_code, 302)
-        self.assertEqual(provider_response['Location'], expected_redirect_url(self.register_page_url, hostname=self.hostname))
+        self.assertEqual(provider_response['Location'], self.register_page_url)
         register_response = self.client.get(self.register_page_url)
         tpa_context = register_response.context["data"]["third_party_auth"]
         self.assertEqual(tpa_context["errorMessage"], None)
@@ -98,7 +96,7 @@ class IntegrationTestMixin(object):
         continue_response = self.client.get(tpa_context["finishAuthUrl"])
         # And we should be redirected to the dashboard:
         self.assertEqual(continue_response.status_code, 302)
-        self.assertEqual(continue_response['Location'], expected_redirect_url(reverse('dashboard'), hostname=self.hostname))
+        self.assertEqual(continue_response['Location'], reverse('dashboard'))
 
         # Now check that we can login again, whether or not we have yet verified the account:
         self.client.logout()
@@ -119,7 +117,7 @@ class IntegrationTestMixin(object):
         complete_response = self.do_provider_login(try_login_response['Location'])
         # We should be redirected to the login screen since this account is not linked to an edX account:
         self.assertEqual(complete_response.status_code, 302)
-        self.assertEqual(complete_response['Location'], expected_redirect_url(self.login_page_url, hostname=self.hostname))
+        self.assertEqual(complete_response['Location'], self.login_page_url)
         login_response = self.client.get(self.login_page_url)
         tpa_context = login_response.context["data"]["third_party_auth"]
         self.assertEqual(tpa_context["errorMessage"], None)
@@ -136,7 +134,7 @@ class IntegrationTestMixin(object):
         continue_response = self.client.get(tpa_context["finishAuthUrl"])
         # And we should be redirected to the dashboard:
         self.assertEqual(continue_response.status_code, 302)
-        self.assertEqual(continue_response['Location'], expected_redirect_url(reverse('dashboard'), hostname=self.hostname))
+        self.assertEqual(continue_response['Location'], reverse('dashboard'))
 
         # Now check that we can login again:
         self.client.logout()
@@ -165,12 +163,7 @@ class IntegrationTestMixin(object):
         # required to set the login cookie (it sticks around if the main session times out):
         if not previous_session_timed_out:
             self.assertEqual(login_response.status_code, 302)
-            expected_url = expected_redirect_url(self.complete_url, hostname=self.hostname)
-            # TODO: Remove Django 1.11 upgrade shim
-            # SHIM: Get rid of this logic post-upgrade
-            if django.VERSION >= (1, 9):
-                expected_url = "{}?".format(expected_url)
-            self.assertEqual(login_response['Location'], expected_url)
+            self.assertEqual(login_response['Location'], self.complete_url + "?")
             # And then we should be redirected to the dashboard:
             login_response = self.client.get(login_response['Location'])
             self.assertEqual(login_response.status_code, 302)
@@ -178,7 +171,7 @@ class IntegrationTestMixin(object):
             url_expected = reverse('dashboard')
         else:
             url_expected = reverse('third_party_inactive_redirect') + '?next=' + reverse('dashboard')
-        self.assertEqual(login_response['Location'], expected_redirect_url(url_expected, hostname=self.hostname))
+        self.assertEqual(login_response['Location'], url_expected)
         # Now we are logged in:
         dashboard_response = self.client.get(reverse('dashboard'))
         self.assertEqual(dashboard_response.status_code, 200)
diff --git a/common/djangoapps/third_party_auth/tests/specs/test_google.py b/common/djangoapps/third_party_auth/tests/specs/test_google.py
index cb66121dc03..00a4e4cb7ba 100644
--- a/common/djangoapps/third_party_auth/tests/specs/test_google.py
+++ b/common/djangoapps/third_party_auth/tests/specs/test_google.py
@@ -6,7 +6,6 @@ from django.conf import settings
 from django.urls import reverse
 import json
 from mock import patch
-from openedx.tests.util import expected_redirect_url
 from social_core.exceptions import AuthException
 from student.tests.factories import UserFactory
 from third_party_auth import pipeline
@@ -73,7 +72,7 @@ class GoogleOauth2IntegrationTest(base.Oauth2IntegrationTest):
             response = self.client.get(complete_url)
         # This should redirect to the custom login/register form:
         self.assertEqual(response.status_code, 302)
-        self.assertEqual(response['Location'], expected_redirect_url('/auth/custom_auth_entry', hostname='example.none'))
+        self.assertEqual(response['Location'], '/auth/custom_auth_entry')
 
         response = self.client.get(response['Location'])
         self.assertEqual(response.status_code, 200)
@@ -107,7 +106,7 @@ class GoogleOauth2IntegrationTest(base.Oauth2IntegrationTest):
         # Now our custom login/registration page must resume the pipeline:
         response = self.client.get(complete_url)
         self.assertEqual(response.status_code, 302)
-        self.assertEqual(response['Location'], expected_redirect_url('/misc/final-destination', hostname='example.none'))
+        self.assertEqual(response['Location'], '/misc/final-destination')
 
         _, strategy = self.get_request_and_strategy()
         self.assert_social_auth_exists_for_user(created_user, strategy)
@@ -134,4 +133,4 @@ class GoogleOauth2IntegrationTest(base.Oauth2IntegrationTest):
             response = self.client.get(complete_url)
         # This should redirect to the custom error URL
         self.assertEqual(response.status_code, 302)
-        self.assertEqual(response['Location'], expected_redirect_url('/misc/my-custom-sso-error-page', hostname='example.none'))
+        self.assertEqual(response['Location'], '/misc/my-custom-sso-error-page')
diff --git a/common/djangoapps/third_party_auth/tests/specs/test_lti.py b/common/djangoapps/third_party_auth/tests/specs/test_lti.py
index 08390afefb7..dc0fe934f7c 100644
--- a/common/djangoapps/third_party_auth/tests/specs/test_lti.py
+++ b/common/djangoapps/third_party_auth/tests/specs/test_lti.py
@@ -2,12 +2,10 @@
 Integration tests for third_party_auth LTI auth providers
 """
 import unittest
-import django
 from django.conf import settings
 from django.contrib.auth.models import User
 from django.urls import reverse
 from oauthlib.oauth1.rfc5849 import Client, SIGNATURE_TYPE_BODY
-from openedx.tests.util import expected_redirect_url
 from third_party_auth.tests import testutil
 
 FORM_ENCODED = 'application/x-www-form-urlencoded'
@@ -94,7 +92,7 @@ class IntegrationTestLTI(testutil.TestCase):
         self.assertEqual(continue_response.status_code, 302)
         self.assertEqual(
             continue_response['Location'],
-            expected_redirect_url('/account/finish_auth/?course_id=my_course_id&enrollment_action=enroll')
+            '/account/finish_auth/?course_id=my_course_id&enrollment_action=enroll'
         )
 
         # Now check that we can login again
@@ -108,12 +106,7 @@ class IntegrationTestLTI(testutil.TestCase):
         login_2_response = self.client.post(path=uri, content_type=FORM_ENCODED, data=body)
         # The user should be redirected to the dashboard
         self.assertEqual(login_2_response.status_code, 302)
-        expected_url = expected_redirect_url(LTI_TPA_COMPLETE_URL)
-        # TODO: Remove Django 1.11 upgrade shim
-        # SHIM: Get rid of this logic post-upgrade
-        if django.VERSION >= (1, 9):
-            expected_url = "{}?".format(expected_url)
-        self.assertEqual(login_2_response['Location'], expected_url)
+        self.assertEqual(login_2_response['Location'], LTI_TPA_COMPLETE_URL + "?")
         continue_2_response = self.client.get(login_2_response['Location'])
         self.assertEqual(continue_2_response.status_code, 302)
         self.assertTrue(continue_2_response['Location'].endswith(reverse('dashboard')))
diff --git a/common/djangoapps/third_party_auth/tests/specs/test_testshib.py b/common/djangoapps/third_party_auth/tests/specs/test_testshib.py
index 7478ab0a009..8691a986189 100644
--- a/common/djangoapps/third_party_auth/tests/specs/test_testshib.py
+++ b/common/djangoapps/third_party_auth/tests/specs/test_testshib.py
@@ -13,7 +13,6 @@ from social_django.models import UserSocialAuth
 from testfixtures import LogCapture
 from unittest import skip
 
-from openedx.tests.util import expected_redirect_url
 from third_party_auth.saml import log as saml_log, SapSuccessFactorsIdentityProvider
 from third_party_auth.tasks import fetch_saml_metadata
 from third_party_auth.tests import testutil
@@ -136,7 +135,7 @@ class TestShibIntegrationTest(SamlIntegrationTestUtilities, IntegrationTestMixin
         try_login_response = self.client.get(testshib_login_url)
         # The user should be redirected to back to the login page:
         self.assertEqual(try_login_response.status_code, 302)
-        self.assertEqual(try_login_response['Location'], expected_redirect_url(self.login_page_url, hostname=self.hostname))
+        self.assertEqual(try_login_response['Location'], self.login_page_url)
         # When loading the login page, the user will see an error message:
         response = self.client.get(self.login_page_url)
         self.assertEqual(response.status_code, 200)
diff --git a/common/djangoapps/util/tests/test_db.py b/common/djangoapps/util/tests/test_db.py
index 372bf6eae12..b3c3b2dd135 100644
--- a/common/djangoapps/util/tests/test_db.py
+++ b/common/djangoapps/util/tests/test_db.py
@@ -5,7 +5,6 @@ import time
 import unittest
 
 import ddt
-import django
 import pytest
 from django.contrib.auth.models import User
 from django.core.management import call_command
@@ -217,7 +216,6 @@ class GenerateIntIdTestCase(TestCase):
             self.assertIn(int_id, list(set(range(minimum, maximum + 1)) - used_ids))
 
 
-@pytest.mark.django111_expected_failure
 class MigrationTests(TestCase):
     """
     Tests for migrations.
@@ -235,11 +233,6 @@ class MigrationTests(TestCase):
         make a migrationless release that'll require a separate migration
         release afterwards, this test doesn't fail.
         """
-        # TODO: Remove Django 1.11 upgrade shim
-        # SHIM: Migrations are known to be needed when actually bumping the Django version number.
-        if django.VERSION >= (1, 9):
-            pytest.skip("Migrations are known to be needed for Django 1.9+!")
-
         out = StringIO()
         call_command('makemigrations', dry_run=True, verbosity=3, stdout=out)
         output = out.getvalue()
diff --git a/common/test/acceptance/pages/lms/admin.py b/common/test/acceptance/pages/lms/admin.py
index 6f86ae89c65..214b7cebc3a 100644
--- a/common/test/acceptance/pages/lms/admin.py
+++ b/common/test/acceptance/pages/lms/admin.py
@@ -1,7 +1,6 @@
 """
 Pages object for the Django's /admin/ views.
 """
-import django
 from bok_choy.page_object import PageObject
 from common.test.acceptance.pages.lms import BASE_URL
 
@@ -30,12 +29,7 @@ class ChangeUserAdminPage(PageObject):
         """
         Reads the read-only username.
         """
-        # TODO: Remove Django 1.11 upgrade shim
-        # SHIM: Get rid of this logic post-upgrade
-        if django.VERSION < (1, 11):
-            return self.q(css='.field-username p').text[0]
-        else:
-            return self.q(css='.field-username .readonly').text[0]
+        return self.q(css='.field-username .readonly').text[0]
 
     @property
     def first_name_element(self):
diff --git a/lms/djangoapps/course_api/blocks/tests/test_api.py b/lms/djangoapps/course_api/blocks/tests/test_api.py
index 324b6a1551b..1c03f26d740 100644
--- a/lms/djangoapps/course_api/blocks/tests/test_api.py
+++ b/lms/djangoapps/course_api/blocks/tests/test_api.py
@@ -5,7 +5,6 @@ Tests for Blocks api.py
 from itertools import product
 
 import ddt
-import django
 from django.test.client import RequestFactory
 from django.test.utils import override_settings
 
@@ -180,13 +179,7 @@ class TestGetBlocksQueryCounts(TestGetBlocksQueryCountsBase):
             clear_course_from_cache(course.id)
 
             if with_storage_backing:
-                # TODO: Remove Django 1.11 upgrade shim
-                # SHIM: Django 1.11 results in a few more SAVEPOINTs due to:
-                # https://github.com/django/django/commit/d44afd88#diff-5b0dda5eb9a242c15879dc9cd2121379L485
-                if django.VERSION >= (1, 11):
-                    num_sql_queries = 16
-                else:
-                    num_sql_queries = 14
+                num_sql_queries = 16
             else:
                 num_sql_queries = 6
 
@@ -235,13 +228,7 @@ class TestQueryCountsWithIndividualOverrideProvider(TestGetBlocksQueryCountsBase
             clear_course_from_cache(course.id)
 
             if with_storage_backing:
-                # TODO: Remove Django 1.11 upgrade shim
-                # SHIM: Django 1.11 results in a few more SAVEPOINTs due to:
-                # https://github.com/django/django/commit/d44afd88#diff-5b0dda5eb9a242c15879dc9cd2121379L485
-                if django.VERSION >= (1, 11):
-                    num_sql_queries = 17
-                else:
-                    num_sql_queries = 15
+                num_sql_queries = 17
             else:
                 num_sql_queries = 7
 
diff --git a/lms/djangoapps/course_wiki/editors.py b/lms/djangoapps/course_wiki/editors.py
index 0b61d035713..ea82427e18e 100644
--- a/lms/djangoapps/course_wiki/editors.py
+++ b/lms/djangoapps/course_wiki/editors.py
@@ -2,7 +2,6 @@
 Support for using the CodeMirror code editor as a wiki content editor.
 """
 
-import django
 from django import forms
 from django.forms.utils import flatatt
 from django.template.loader import render_to_string
@@ -33,14 +32,9 @@ class CodeMirrorWidget(forms.Widget):
         if value is None:
             value = ''
 
-        # TODO: Remove Django 1.11 upgrade shim
-        # SHIM: Compensate for build_attrs() implementation change in 1.11
-        if django.VERSION < (1, 11):
-            final_attrs = self.build_attrs(attrs, name=name)
-        else:
-            extra_attrs = attrs.copy()
-            extra_attrs['name'] = name
-            final_attrs = self.build_attrs(self.attrs, extra_attrs=extra_attrs)  # pylint: disable=redundant-keyword-arg
+        extra_attrs = attrs.copy()
+        extra_attrs['name'] = name
+        final_attrs = self.build_attrs(self.attrs, extra_attrs=extra_attrs)
 
         # TODO use the help_text field of edit form instead of rendering a template
 
diff --git a/lms/djangoapps/course_wiki/tests/tests.py b/lms/djangoapps/course_wiki/tests/tests.py
index 02a79cb7619..6a060edb115 100644
--- a/lms/djangoapps/course_wiki/tests/tests.py
+++ b/lms/djangoapps/course_wiki/tests/tests.py
@@ -9,7 +9,6 @@ from six import text_type
 
 from courseware.tests.tests import LoginEnrollmentTestCase
 from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseTestConsentRequired
-from openedx.tests.util import expected_redirect_url
 from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
 from xmodule.modulestore.tests.factories import CourseFactory
 
@@ -58,7 +57,7 @@ class WikiRedirectTestCase(EnterpriseTestConsentRequired, LoginEnrollmentTestCas
         resp = self.client.get(destination, HTTP_REFERER=referer)
         self.assertEqual(resp.status_code, 302)
 
-        self.assertEqual(resp['Location'], expected_redirect_url(redirected_to))
+        self.assertEqual(resp['Location'], redirected_to)
 
         # Now we test that the student will be redirected away from that page if the course doesn't exist
         # We do this in the same test because we want to make sure the redirected_to is constructed correctly
@@ -67,7 +66,7 @@ class WikiRedirectTestCase(EnterpriseTestConsentRequired, LoginEnrollmentTestCas
 
         resp = self.client.get(bad_course_wiki_page, HTTP_REFERER=referer)
         self.assertEqual(resp.status_code, 302)
-        self.assertEqual(resp['Location'], expected_redirect_url(destination))
+        self.assertEqual(resp['Location'], destination)
 
     @patch.dict("django.conf.settings.FEATURES", {'ALLOW_WIKI_ROOT_ACCESS': False})
     def test_wiki_no_root_access(self):
@@ -100,7 +99,7 @@ class WikiRedirectTestCase(EnterpriseTestConsentRequired, LoginEnrollmentTestCas
 
         ending_location = resp.redirect_chain[-1][0]
 
-        self.assertEquals(ending_location, expected_redirect_url(course_wiki_page))
+        self.assertEquals(ending_location, course_wiki_page)
         self.assertEquals(resp.status_code, 200)
 
         self.has_course_navigator(resp)
diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py
index 64f4f7da267..1e3ecf2294e 100644
--- a/lms/djangoapps/courseware/tests/test_views.py
+++ b/lms/djangoapps/courseware/tests/test_views.py
@@ -63,7 +63,6 @@ from openedx.core.djangolib.testing.utils import get_mock_request
 from openedx.core.lib.gating import api as gating_api
 from openedx.features.course_experience import COURSE_OUTLINE_PAGE_FLAG, UNIFIED_COURSE_TAB_FLAG
 from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseTestConsentRequired
-from openedx.tests.util import expected_redirect_url
 from student.models import CourseEnrollment
 from student.tests.factories import TEST_PASSWORD, AdminFactory, CourseEnrollmentFactory, UserFactory
 from util.tests.test_date_utils import fake_pgettext, fake_ugettext
@@ -106,7 +105,7 @@ class TestJumpTo(ModuleStoreTestCase):
         course = CourseFactory.create()
         chapter = ItemFactory.create(category='chapter', parent_location=course.location)
         section = ItemFactory.create(category='sequential', parent_location=chapter.location)
-        expected = 'courses/{course_id}/courseware/{chapter_id}/{section_id}/?{activate_block_id}'.format(
+        expected = '/courses/{course_id}/courseware/{chapter_id}/{section_id}/?{activate_block_id}'.format(
             course_id=unicode(course.id),
             chapter_id=chapter.url_name,
             section_id=section.url_name,
@@ -118,7 +117,7 @@ class TestJumpTo(ModuleStoreTestCase):
             unicode(section.location),
         )
         response = self.client.get(jumpto_url)
-        self.assertRedirects(response, expected_redirect_url(expected), status_code=302, target_status_code=302)
+        self.assertRedirects(response, expected, status_code=302, target_status_code=302)
 
     def test_jumpto_from_module(self):
         course = CourseFactory.create()
@@ -129,7 +128,7 @@ class TestJumpTo(ModuleStoreTestCase):
         module1 = ItemFactory.create(category='html', parent_location=vertical1.location)
         module2 = ItemFactory.create(category='html', parent_location=vertical2.location)
 
-        expected = 'courses/{course_id}/courseware/{chapter_id}/{section_id}/1?{activate_block_id}'.format(
+        expected = '/courses/{course_id}/courseware/{chapter_id}/{section_id}/1?{activate_block_id}'.format(
             course_id=unicode(course.id),
             chapter_id=chapter.url_name,
             section_id=section.url_name,
@@ -141,9 +140,9 @@ class TestJumpTo(ModuleStoreTestCase):
             unicode(module1.location),
         )
         response = self.client.get(jumpto_url)
-        self.assertRedirects(response, expected_redirect_url(expected), status_code=302, target_status_code=302)
+        self.assertRedirects(response, expected, status_code=302, target_status_code=302)
 
-        expected = 'courses/{course_id}/courseware/{chapter_id}/{section_id}/2?{activate_block_id}'.format(
+        expected = '/courses/{course_id}/courseware/{chapter_id}/{section_id}/2?{activate_block_id}'.format(
             course_id=unicode(course.id),
             chapter_id=chapter.url_name,
             section_id=section.url_name,
@@ -155,7 +154,7 @@ class TestJumpTo(ModuleStoreTestCase):
             unicode(module2.location),
         )
         response = self.client.get(jumpto_url)
-        self.assertRedirects(response, expected_redirect_url(expected), status_code=302, target_status_code=302)
+        self.assertRedirects(response, expected, status_code=302, target_status_code=302)
 
     def test_jumpto_from_nested_module(self):
         course = CourseFactory.create()
@@ -171,7 +170,7 @@ class TestJumpTo(ModuleStoreTestCase):
 
         # internal position of module2 will be 1_2 (2nd item withing 1st item)
 
-        expected = 'courses/{course_id}/courseware/{chapter_id}/{section_id}/1?{activate_block_id}'.format(
+        expected = '/courses/{course_id}/courseware/{chapter_id}/{section_id}/1?{activate_block_id}'.format(
             course_id=unicode(course.id),
             chapter_id=chapter.url_name,
             section_id=section.url_name,
@@ -183,7 +182,7 @@ class TestJumpTo(ModuleStoreTestCase):
             unicode(module2.location),
         )
         response = self.client.get(jumpto_url)
-        self.assertRedirects(response, expected_redirect_url(expected), status_code=302, target_status_code=302)
+        self.assertRedirects(response, expected, status_code=302, target_status_code=302)
 
     def test_jumpto_id_invalid_location(self):
         location = BlockUsageLocator(CourseLocator('edX', 'toy', 'NoSuchPlace', deprecated=True),
diff --git a/lms/djangoapps/grades/tests/test_tasks.py b/lms/djangoapps/grades/tests/test_tasks.py
index 95f799fa603..c59c4618a70 100644
--- a/lms/djangoapps/grades/tests/test_tasks.py
+++ b/lms/djangoapps/grades/tests/test_tasks.py
@@ -10,7 +10,6 @@ from datetime import datetime, timedelta
 import ddt
 import pytz
 import six
-import django
 from django.conf import settings
 from django.db.utils import IntegrityError
 from mock import MagicMock, patch
@@ -111,28 +110,6 @@ class HasCourseWithProblemsMixin(object):
         # pylint: enable=attribute-defined-outside-init,no-member
 
 
-# TODO: Remove Django 1.11 upgrade shim
-# SHIM: Django 1.11 results in a few more SAVEPOINTs due to:
-# https://github.com/django/django/commit/d44afd88#diff-5b0dda5eb9a242c15879dc9cd2121379L485
-# Get rid of this logic post-upgrade.
-def _recalc_expected_query_counts():
-    if django.VERSION >= (1, 11):
-        return 27
-    else:
-        return 23
-
-
-# TODO: Remove Django 1.11 upgrade shim
-# SHIM: Django 1.11 results in a few more SAVEPOINTs due to:
-# https://github.com/django/django/commit/d44afd88#diff-5b0dda5eb9a242c15879dc9cd2121379L485
-# Get rid of this logic post-upgrade.
-def _recalc_persistent_expected_query_counts():
-    if django.VERSION >= (1, 11):
-        return 28
-    else:
-        return 24
-
-
 @patch.dict(settings.FEATURES, {'PERSISTENT_GRADES_ENABLED_FOR_ALL_TESTS': False})
 @ddt.ddt
 class RecalculateSubsectionGradeTest(HasCourseWithProblemsMixin, ModuleStoreTestCase):
@@ -191,10 +168,10 @@ class RecalculateSubsectionGradeTest(HasCourseWithProblemsMixin, ModuleStoreTest
             self.assertEquals(mock_block_structure_create.call_count, 1)
 
     @ddt.data(
-        (ModuleStoreEnum.Type.mongo, 1, _recalc_expected_query_counts(), True),
-        (ModuleStoreEnum.Type.mongo, 1, _recalc_expected_query_counts(), False),
-        (ModuleStoreEnum.Type.split, 3, _recalc_expected_query_counts(), True),
-        (ModuleStoreEnum.Type.split, 3, _recalc_expected_query_counts(), False),
+        (ModuleStoreEnum.Type.mongo, 1, 27, True),
+        (ModuleStoreEnum.Type.mongo, 1, 27, False),
+        (ModuleStoreEnum.Type.split, 3, 27, True),
+        (ModuleStoreEnum.Type.split, 3, 27, False),
     )
     @ddt.unpack
     def test_query_counts(self, default_store, num_mongo_calls, num_sql_calls, create_multiple_subsections):
@@ -206,8 +183,8 @@ class RecalculateSubsectionGradeTest(HasCourseWithProblemsMixin, ModuleStoreTest
                     self._apply_recalculate_subsection_grade()
 
     @ddt.data(
-        (ModuleStoreEnum.Type.mongo, 1, _recalc_expected_query_counts()),
-        (ModuleStoreEnum.Type.split, 3, _recalc_expected_query_counts()),
+        (ModuleStoreEnum.Type.mongo, 1, 27),
+        (ModuleStoreEnum.Type.split, 3, 27),
     )
     @ddt.unpack
     def test_query_counts_dont_change_with_more_content(self, default_store, num_mongo_calls, num_sql_calls):
@@ -267,8 +244,8 @@ class RecalculateSubsectionGradeTest(HasCourseWithProblemsMixin, ModuleStoreTest
             self.assertEqual(len(PersistentSubsectionGrade.bulk_read_grades(self.user.id, self.course.id)), 0)
 
     @ddt.data(
-        (ModuleStoreEnum.Type.mongo, 1, _recalc_persistent_expected_query_counts()),
-        (ModuleStoreEnum.Type.split, 3, _recalc_persistent_expected_query_counts()),
+        (ModuleStoreEnum.Type.mongo, 1, 28),
+        (ModuleStoreEnum.Type.split, 3, 28),
     )
     @ddt.unpack
     def test_persistent_grades_enabled_on_course(self, default_store, num_mongo_queries, num_sql_queries):
diff --git a/lms/djangoapps/student_account/test/test_views.py b/lms/djangoapps/student_account/test/test_views.py
index 30b69f69835..b078ebf208d 100644
--- a/lms/djangoapps/student_account/test/test_views.py
+++ b/lms/djangoapps/student_account/test/test_views.py
@@ -47,7 +47,6 @@ from openedx.core.djangoapps.user_api.accounts.api import activate_account, crea
 from openedx.core.djangolib.js_utils import dump_js_escaped_json
 from openedx.core.djangolib.markup import HTML, Text
 from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
-from openedx.tests.util import expected_redirect_url
 from student.tests.factories import UserFactory
 from student_account.views import account_settings_context, get_user_orders
 from third_party_auth.tests.testutil import ThirdPartyAuthTestMixin, simulate_running_pipeline
@@ -605,9 +604,11 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
         self.google_provider.save()
         params = [("next", "/courses/something/?tpa_hint=oa2-google-oauth2")]
         response = self.client.get(reverse(url_name), params, HTTP_ACCEPT="text/html")
+        expected_url = '/auth/login/google-oauth2/?auth_entry={}&next=%2Fcourses'\
+                       '%2Fsomething%2F%3Ftpa_hint%3Doa2-google-oauth2'.format(auth_entry)
         self.assertRedirects(
             response,
-            expected_redirect_url('auth/login/google-oauth2/?auth_entry={}&next=%2Fcourses%2Fsomething%2F%3Ftpa_hint%3Doa2-google-oauth2'.format(auth_entry)),
+            expected_url,
             target_status_code=302
         )
 
@@ -649,9 +650,11 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
         self.google_provider.save()
         params = [("next", "/courses/something/")]
         response = self.client.get(reverse(url_name), params, HTTP_ACCEPT="text/html")
+        expected_url = '/auth/login/google-oauth2/?auth_entry={}&next=%2Fcourses'\
+                       '%2Fsomething%2F%3Ftpa_hint%3Doa2-google-oauth2'.format(auth_entry)
         self.assertRedirects(
             response,
-            expected_redirect_url('auth/login/google-oauth2/?auth_entry={}&next=%2Fcourses%2Fsomething%2F%3Ftpa_hint%3Doa2-google-oauth2'.format(auth_entry)),
+            expected_url,
             target_status_code=302
         )
 
diff --git a/lms/envs/common.py b/lms/envs/common.py
index 338fbe798b7..86c4a29f665 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -33,7 +33,6 @@ import imp
 import sys
 import os
 
-import django
 from path import Path as path
 from django.utils.translation import ugettext_lazy as _
 
@@ -1216,13 +1215,6 @@ CREDIT_NOTIFICATION_CACHE_TIMEOUT = 5 * 60 * 60
 
 ################################# Middleware ###################################
 
-# TODO: Remove Django 1.11 upgrade shim
-# SHIM: Remove birdcage references post-1.11 upgrade as it is only in place to help during that deployment
-if django.VERSION < (1, 9):
-    _csrf_middleware = 'birdcage.v1_11.csrf.CsrfViewMiddleware'
-else:
-    _csrf_middleware = 'django.middleware.csrf.CsrfViewMiddleware'
-
 MIDDLEWARE_CLASSES = [
     'crum.CurrentRequestUserMiddleware',
 
@@ -1264,7 +1256,7 @@ MIDDLEWARE_CLASSES = [
     'corsheaders.middleware.CorsMiddleware',
     'openedx.core.djangoapps.cors_csrf.middleware.CorsCSRFMiddleware',
     'openedx.core.djangoapps.cors_csrf.middleware.CsrfCrossDomainCookieMiddleware',
-    _csrf_middleware,
+    'django.middleware.csrf.CsrfViewMiddleware',
 
     'splash.middleware.SplashMiddleware',
 
diff --git a/lms/envs/load_test.py b/lms/envs/load_test.py
index cfc90add27d..7b7e7df4051 100644
--- a/lms/envs/load_test.py
+++ b/lms/envs/load_test.py
@@ -8,14 +8,10 @@ Settings for load testing.
 
 from .aws import *
 
-# TODO: Remove Django 1.11 upgrade shim
-# SHIM: Remove birdcage references post-1.11 upgrade as it is only in place to help during that deployment
-
 # Disable CSRF for load testing
 EXCLUDE_CSRF = lambda elem: elem not in [
     'django.template.context_processors.csrf',
-    'django.middleware.csrf.CsrfViewMiddleware',
-    'birdcage.v1_11.csrf.CsrfViewMiddleware'
+    'django.middleware.csrf.CsrfViewMiddleware'
 ]
 DEFAULT_TEMPLATE_ENGINE['OPTIONS']['context_processors'] = filter(
     EXCLUDE_CSRF, DEFAULT_TEMPLATE_ENGINE['OPTIONS']['context_processors']
diff --git a/lms/startup.py b/lms/startup.py
index f1326f0a497..a5255f53798 100644
--- a/lms/startup.py
+++ b/lms/startup.py
@@ -5,8 +5,6 @@ Module for code that should run during LMS startup (deprecated)
 import django
 from django.conf import settings
 
-from openedx.core.djangoapps.monkey_patch import django_db_models_options
-
 # Force settings to run so that the python path is modified
 settings.INSTALLED_APPS  # pylint: disable=pointless-statement
 
@@ -18,9 +16,4 @@ def run():
     NOTE: DO **NOT** add additional code to this method or this file! The Platform Team
           is moving all startup code to more standard locations using Django best practices.
     """
-    # TODO: Remove Django 1.11 upgrade shim
-    # SHIM: We should be able to get rid of this monkey patch post-upgrade
-    if django.VERSION[0] == 1 and django.VERSION[1] < 10:
-        django_db_models_options.patch()
-
     django.setup()
diff --git a/openedx/core/djangoapps/api_admin/widgets.py b/openedx/core/djangoapps/api_admin/widgets.py
index ae50044af41..0bef4dd37a3 100644
--- a/openedx/core/djangoapps/api_admin/widgets.py
+++ b/openedx/core/djangoapps/api_admin/widgets.py
@@ -1,6 +1,5 @@
 """ Form widget classes """
 
-import django
 from django.conf import settings
 from django.urls import reverse
 from django.forms.utils import flatatt
@@ -16,14 +15,9 @@ class TermsOfServiceCheckboxInput(CheckboxInput):
     """ Renders a checkbox with a label linking to the terms of service. """
 
     def render(self, name, value, attrs=None):
-        # TODO: Remove Django 1.11 upgrade shim
-        # SHIM: Compensate for behavior change of default authentication backend in 1.10
-        if django.VERSION < (1, 11):
-            final_attrs = self.build_attrs(attrs, type='checkbox', name=name)
-        else:
-            extra_attrs = attrs.copy()
-            extra_attrs.update({'type': 'checkbox', 'name': name})
-            final_attrs = self.build_attrs(self.attrs, extra_attrs=extra_attrs)  # pylint: disable=redundant-keyword-arg
+        extra_attrs = attrs.copy()
+        extra_attrs.update({'type': 'checkbox', 'name': name})
+        final_attrs = self.build_attrs(self.attrs, extra_attrs=extra_attrs)
 
         if self.check_test(value):
             final_attrs['checked'] = 'checked'
diff --git a/openedx/core/djangoapps/cors_csrf/authentication.py b/openedx/core/djangoapps/cors_csrf/authentication.py
index 91dc5098a2d..f4f2471932c 100644
--- a/openedx/core/djangoapps/cors_csrf/authentication.py
+++ b/openedx/core/djangoapps/cors_csrf/authentication.py
@@ -24,11 +24,8 @@ class SessionAuthenticationCrossDomainCsrf(authentication.SessionAuthentication)
     Since this subclass overrides only the `enforce_csrf()` method,
     it can be mixed in with other `SessionAuthentication` subclasses.
     """
-    # TODO: Remove Django 1.11 upgrade shim
-    # SHIM: Call new process_request in Django 1.11 to process CSRF token in cookie.
     def _process_enforce_csrf(self, request):
-        if django.VERSION >= (1, 11):
-            CsrfViewMiddleware().process_request(request)
+        CsrfViewMiddleware().process_request(request)
         return super(SessionAuthenticationCrossDomainCsrf, self).enforce_csrf(request)
 
     def enforce_csrf(self, request):
diff --git a/openedx/core/djangoapps/cors_csrf/middleware.py b/openedx/core/djangoapps/cors_csrf/middleware.py
index 1f3c8ee3653..7478d542874 100644
--- a/openedx/core/djangoapps/cors_csrf/middleware.py
+++ b/openedx/core/djangoapps/cors_csrf/middleware.py
@@ -44,19 +44,12 @@ CSRF cookie.
 
 import logging
 
-import django
 from django.conf import settings
 from django.core.exceptions import ImproperlyConfigured, MiddlewareNotUsed
+from django.middleware.csrf import CsrfViewMiddleware
 
 from .helpers import is_cross_domain_request_allowed, skip_cross_domain_referer_check
 
-# TODO: Remove Django 1.11 upgrade shim
-# SHIM: Remove birdcage references post-1.11 upgrade as it is only in place to help during that deployment
-if django.VERSION < (1, 9):
-    from birdcage.v1_11.csrf import CsrfViewMiddleware
-else:
-    from django.middleware.csrf import CsrfViewMiddleware
-
 
 log = logging.getLogger(__name__)
 
diff --git a/openedx/core/djangoapps/cors_csrf/tests/test_middleware.py b/openedx/core/djangoapps/cors_csrf/tests/test_middleware.py
index 7c30b8ce334..9c6480ba92c 100644
--- a/openedx/core/djangoapps/cors_csrf/tests/test_middleware.py
+++ b/openedx/core/djangoapps/cors_csrf/tests/test_middleware.py
@@ -5,18 +5,11 @@ Tests for the CORS CSRF middleware
 from mock import patch, Mock
 import ddt
 
-import django
 from django.test import TestCase
 from django.test.utils import override_settings
 from django.core.exceptions import MiddlewareNotUsed, ImproperlyConfigured
 from django.http import HttpResponse
-
-# TODO: Remove Django 1.11 upgrade shim
-# SHIM: Remove birdcage references post-1.11 upgrade as it is only in place to help during that deployment
-if django.VERSION < (1, 9):
-    from birdcage.v1_11.csrf import CsrfViewMiddleware
-else:
-    from django.middleware.csrf import CsrfViewMiddleware
+from django.middleware.csrf import CsrfViewMiddleware
 
 from ..middleware import CorsCSRFMiddleware, CsrfCrossDomainCookieMiddleware
 
diff --git a/openedx/core/djangoapps/external_auth/tests/test_shib.py b/openedx/core/djangoapps/external_auth/tests/test_shib.py
index d9ed8d86203..c89bbfdbc59 100644
--- a/openedx/core/djangoapps/external_auth/tests/test_shib.py
+++ b/openedx/core/djangoapps/external_auth/tests/test_shib.py
@@ -8,7 +8,6 @@ import unittest
 from importlib import import_module
 from urllib import urlencode
 
-import django
 import pytest
 from ddt import ddt, data
 from django.conf import settings
@@ -23,7 +22,6 @@ from openedx.core.djangoapps.external_auth.views import (
     shib_login, course_specific_login, course_specific_register, _flatten_to_ascii
 )
 from openedx.core.djangoapps.user_api import accounts as accounts_settings
-from openedx.tests.util import expected_redirect_url
 from mock import patch
 from nose.plugins.attrib import attr
 from six import text_type
@@ -369,12 +367,7 @@ class ShibSPTest(CacheIsolationTestCase):
             if len(external_name.strip()) < accounts_settings.NAME_MIN_LENGTH:
                 self.assertEqual(profile.name, postvars['name'])
             else:
-                expected_name = external_name
-                # TODO: Remove Django 1.11 upgrade shim
-                # SHIM: form character fields strip leading and trailing whitespace by default in Django 1.9+
-                if django.VERSION >= (1, 9):
-                    expected_name = expected_name.strip()
-                self.assertEqual(profile.name, expected_name)
+                self.assertEqual(profile.name, external_name.strip())
                 self.assertNotIn(u';', profile.name)
         else:
             self.assertEqual(profile.name, self.client.session['ExternalAuthMap'].external_name)
@@ -586,7 +579,7 @@ class ShibSPTestModifiedCourseware(ModuleStoreTestCase):
         # successful login is a redirect to the URL that handles auto-enrollment
         self.assertEqual(response.status_code, 302)
         self.assertEqual(response['location'],
-                         expected_redirect_url('/account/finish_auth?{}'.format(urlencode(params))))
+                         '/account/finish_auth?{}'.format(urlencode(params)))
 
 
 class ShibUtilFnTest(TestCase):
diff --git a/openedx/core/djangoapps/external_auth/tests/test_ssl.py b/openedx/core/djangoapps/external_auth/tests/test_ssl.py
index de5454ee935..056211ed640 100644
--- a/openedx/core/djangoapps/external_auth/tests/test_ssl.py
+++ b/openedx/core/djangoapps/external_auth/tests/test_ssl.py
@@ -20,7 +20,6 @@ from openedx.core.djangoapps.external_auth.models import ExternalAuthMap
 import openedx.core.djangoapps.external_auth.views as external_auth_views
 from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory
 from openedx.core.djangolib.testing.utils import skip_unless_cms, skip_unless_lms
-from openedx.tests.util import expected_redirect_url
 from student.models import CourseEnrollment
 from student.roles import CourseStaffRole
 from student.tests.factories import UserFactory
@@ -183,7 +182,7 @@ class SSLClientTest(ModuleStoreTestCase):
         response = self.client.get(
             reverse('dashboard'), follow=True,
             SSL_CLIENT_S_DN=self.AUTH_DN.format(self.USER_NAME, self.USER_EMAIL))
-        self.assertEquals((expected_redirect_url('/dashboard'), 302),
+        self.assertEquals(('/dashboard', 302),
                           response.redirect_chain[-1])
         self.assertIn(SESSION_KEY, self.client.session)
 
@@ -197,7 +196,7 @@ class SSLClientTest(ModuleStoreTestCase):
         response = self.client.get(
             reverse('register_user'), follow=True,
             SSL_CLIENT_S_DN=self.AUTH_DN.format(self.USER_NAME, self.USER_EMAIL))
-        self.assertEquals((expected_redirect_url('/dashboard'), 302),
+        self.assertEquals(('/dashboard', 302),
                           response.redirect_chain[-1])
         self.assertIn(SESSION_KEY, self.client.session)
 
@@ -237,7 +236,7 @@ class SSLClientTest(ModuleStoreTestCase):
         response = self.client.get(
             reverse('signin_user'), follow=True,
             SSL_CLIENT_S_DN=self.AUTH_DN.format(self.USER_NAME, self.USER_EMAIL))
-        self.assertEquals((expected_redirect_url('/dashboard'), 302),
+        self.assertEquals(('/dashboard', 302),
                           response.redirect_chain[-1])
         self.assertIn(SESSION_KEY, self.client.session)
 
@@ -360,7 +359,7 @@ class SSLClientTest(ModuleStoreTestCase):
             SSL_CLIENT_S_DN=self.AUTH_DN.format(self.USER_NAME, self.USER_EMAIL),
             HTTP_ACCEPT='text/html'
         )
-        self.assertEqual((expected_redirect_url(course_private_url), 302),
+        self.assertEqual((course_private_url, 302),
                          response.redirect_chain[-1])
         self.assertIn(SESSION_KEY, self.client.session)
 
@@ -392,7 +391,7 @@ class SSLClientTest(ModuleStoreTestCase):
             SSL_CLIENT_S_DN=self.AUTH_DN.format(self.USER_NAME, self.USER_EMAIL),
             HTTP_ACCEPT='text/html'
         )
-        self.assertEqual((expected_redirect_url(course_private_url), 302),
+        self.assertEqual((course_private_url, 302),
                          response.redirect_chain[-1])
         self.assertIn(SESSION_KEY, self.client.session)
 
@@ -410,7 +409,7 @@ class SSLClientTest(ModuleStoreTestCase):
         response = self.client.get(
             reverse('dashboard'), follow=True,
             SSL_CLIENT_S_DN=self.AUTH_DN.format(self.USER_NAME, self.USER_EMAIL))
-        self.assertEquals((expected_redirect_url('/dashboard'), 302),
+        self.assertEquals(('/dashboard', 302),
                           response.redirect_chain[-1])
         self.assertIn(SESSION_KEY, self.client.session)
         response = self.client.get(
diff --git a/openedx/core/djangoapps/oauth_dispatch/dot_overrides/validators.py b/openedx/core/djangoapps/oauth_dispatch/dot_overrides/validators.py
index c77d9924a16..a44337a789b 100644
--- a/openedx/core/djangoapps/oauth_dispatch/dot_overrides/validators.py
+++ b/openedx/core/djangoapps/oauth_dispatch/dot_overrides/validators.py
@@ -5,8 +5,8 @@ from __future__ import unicode_literals
 
 from datetime import datetime
 
-import django
 from django.contrib.auth import authenticate, get_user_model
+from django.contrib.auth.backends import AllowAllUsersModelBackend as UserModelBackend
 from django.db.models.signals import pre_save
 from django.dispatch import receiver
 from oauth2_provider.models import AccessToken
@@ -31,17 +31,6 @@ def on_access_token_presave(sender, instance, *args, **kwargs):  # pylint: disab
         RestrictedApplication.set_access_token_as_expired(instance)
 
 
-# TODO: Remove Django 1.11 upgrade shim
-# SHIM: Allow users that are inactive to still authenticate while keeping rate-limiting functionality.
-if django.VERSION < (1, 10):
-    # Old backend which allowed inactive users to authenticate prior to Django 1.10.
-    from django.contrib.auth.backends import ModelBackend as UserModelBackend
-else:
-    # Django 1.10+ ModelBackend disallows inactive users from authenticating, so instead we use
-    # AllowAllUsersModelBackend which is the closest alternative.
-    from django.contrib.auth.backends import AllowAllUsersModelBackend as UserModelBackend
-
-
 class EdxRateLimitedAllowAllUsersModelBackend(RateLimitMixin, UserModelBackend):
     """
     Authentication backend needed to incorporate rate limiting of login attempts - but also
diff --git a/openedx/core/djangoapps/theming/storage.py b/openedx/core/djangoapps/theming/storage.py
index 86bcec667ca..e0b566210a0 100644
--- a/openedx/core/djangoapps/theming/storage.py
+++ b/openedx/core/djangoapps/theming/storage.py
@@ -6,7 +6,6 @@ import os.path
 import posixpath
 import re
 
-import django
 from django.conf import settings
 from django.contrib.staticfiles.finders import find
 from django.contrib.staticfiles.storage import CachedFilesMixin, StaticFilesStorage
@@ -171,24 +170,6 @@ class ThemeCachedFilesMixin(CachedFilesMixin):
 
         return asset_name
 
-    # TODO: Remove Django 1.11 upgrade shim
-    # SHIM: This override method modifies the name argument to contain a theme
-    # prefix when Django < 1.11.  In Django >= 1.11, asset name processing is
-    # done in the _url function, so this method becomes a no-op passthrough.
-    # After the 1.11 upgrade, delete this method.
-    def url(self, name, force=False):
-        """
-        This override method serves a similar function to _url, but this is
-        needed for Django < 1.11.
-        """
-        if django.VERSION < (1, 11):
-            processed_asset_name = self._processed_asset_name(name)
-            return super(ThemeCachedFilesMixin, self).url(processed_asset_name, force)
-        else:
-            # Passthrough directly to the function we are overriding.  _url in
-            # Django 1.11+ will take care of processing the asset name.
-            return super(ThemeCachedFilesMixin, self).url(name, force)
-
     def _url(self, hashed_name_func, name, force=False, hashed_files=None):
         """
         This override method swaps out `name` with a processed version.
@@ -198,153 +179,6 @@ class ThemeCachedFilesMixin(CachedFilesMixin):
         processed_asset_name = self._processed_asset_name(name)
         return super(ThemeCachedFilesMixin, self)._url(hashed_name_func, processed_asset_name, force, hashed_files)
 
-    # TODO: Remove Django 1.11 upgrade shim
-    # SHIM: This method implements url_converter for Django < 1.11.
-    # After the 1.11 upgrade, delete this method.
-    def _url_converter__lt_111(self, name, template=None):
-        """
-        This is an override of url_converter from CachedFilesMixin.
-
-        There are two lines commented out in order to make the converter method
-        return absolute urls instead of relative urls. This behavior is
-        necessary for theme overrides, as we get 404 on assets with relative
-        urls on a themed site.
-        """
-        if template is None:
-            template = self.default_template
-
-        def converter(matchobj):
-            """
-            Converts the matched URL depending on the parent level (`..`)
-            and returns the normalized and hashed URL using the url method
-            of the storage.
-            """
-            matched, url = matchobj.groups()
-            # Completely ignore http(s) prefixed URLs,
-            # fragments and data-uri URLs
-            if url.startswith(('#', 'http:', 'https:', 'data:', '//')):
-                return matched
-            name_parts = name.split(os.sep)
-            # Using posix normpath here to remove duplicates
-            url = posixpath.normpath(url)
-            url_parts = url.split('/')
-            parent_level, sub_level = url.count('..'), url.count('/')
-            if url.startswith('/'):
-                sub_level -= 1
-                url_parts = url_parts[1:]
-            if parent_level or not url.startswith('/'):
-                start, end = parent_level + 1, parent_level
-            else:
-                if sub_level:
-                    if sub_level == 1:
-                        parent_level -= 1
-                    start, end = parent_level, 1
-                else:
-                    start, end = 1, sub_level - 1
-            joined_result = '/'.join(name_parts[:-start] + url_parts[end:])
-            hashed_url = self.url(unquote(joined_result), force=True)
-
-            # NOTE:
-            # following two lines are commented out so that absolute urls are used instead of relative urls
-            # to make themed assets work correctly.
-            #
-            # The lines are commented and not removed to make future django upgrade easier and
-            # show exactly what is changed in this method override
-            #
-            # file_name = hashed_url.split('/')[-1:]
-            # relative_url = '/'.join(url.split('/')[:-1] + file_name)
-
-            # Return the hashed version to the file
-            return template % unquote(hashed_url)
-
-        return converter
-
-    # TODO: Remove Django 1.11 upgrade shim
-    # SHIM: This method implements url_converter for Django >= 1.11.
-    # After the 1.11 upgrade, rename this method to url_converter.
-    def _url_converter__gte_111(self, name, hashed_files, template=None):
-        """
-        This is an override of url_converter from CachedFilesMixin.
-
-        It changes one line near the end of the method (see the NOTE) in order
-        to return absolute urls instead of relative urls.  This behavior is
-        necessary for theme overrides, as we get 404 on assets with relative
-        urls on a themed site.
-        """
-        if template is None:
-            template = self.default_template
-
-        def converter(matchobj):
-            """
-            Convert the matched URL to a normalized and hashed URL.
-            This requires figuring out which files the matched URL resolves
-            to and calling the url() method of the storage.
-            """
-            matched, url = matchobj.groups()
-
-            # Ignore absolute/protocol-relative and data-uri URLs.
-            if re.match(r'^[a-z]+:', url):
-                return matched
-
-            # Ignore absolute URLs that don't point to a static file (dynamic
-            # CSS / JS?). Note that STATIC_URL cannot be empty.
-            if url.startswith('/') and not url.startswith(settings.STATIC_URL):
-                return matched
-
-            # Strip off the fragment so a path-like fragment won't interfere.
-            url_path, fragment = urldefrag(url)
-
-            if url_path.startswith('/'):
-                # Otherwise the condition above would have returned prematurely.
-                assert url_path.startswith(settings.STATIC_URL)
-                target_name = url_path[len(settings.STATIC_URL):]
-            else:
-                # We're using the posixpath module to mix paths and URLs conveniently.
-                source_name = name if os.sep == '/' else name.replace(os.sep, '/')
-                target_name = posixpath.join(posixpath.dirname(source_name), url_path)
-
-            # Determine the hashed name of the target file with the storage backend.
-            hashed_url = self._url(
-                self._stored_name, unquote(target_name),
-                force=True, hashed_files=hashed_files,
-            )
-
-            # NOTE:
-            # The line below was commented out so that absolute urls are used instead of relative urls to make themed
-            # assets work correctly.
-            #
-            # The line is commented and not removed to make future django upgrade easier and show exactly what is
-            # changed in this method override
-            #
-            #transformed_url = '/'.join(url_path.split('/')[:-1] + hashed_url.split('/')[-1:])
-            transformed_url = hashed_url  # This line was added.
-
-            # Restore the fragment that was stripped off earlier.
-            if fragment:
-                transformed_url += ('?#' if '?#' in url else '#') + fragment
-
-            # Return the hashed version to the file
-            return template % unquote(transformed_url)
-
-        return converter
-
-    # TODO: Remove Django 1.11 upgrade shim
-    # SHIM: This method switches the implementation of url_converter according
-    # to the Django version.  After the 1.11 upgrade, do these things:
-    #
-    # 1. delete _url_converter__lt_111.
-    # 2. delete url_converter (below).
-    # 3. rename _url_converter__gte_111 to url_converter.
-    def url_converter(self, *args, **kwargs):
-        """
-        An implementation selector for the url_converter method.  This is in
-        place only for the Django 1.11 upgrade.
-        """
-        if django.VERSION < (1, 11):
-            return self._url_converter__lt_111(*args, **kwargs)
-        else:
-            return self._url_converter__gte_111(*args, **kwargs)
-
 
 class ThemePipelineMixin(PipelineMixin):
     """
diff --git a/openedx/core/djangoapps/user_api/management/commands/email_opt_in_list.py b/openedx/core/djangoapps/user_api/management/commands/email_opt_in_list.py
index 0a2bc14f8fe..c9ed29c998d 100644
--- a/openedx/core/djangoapps/user_api/management/commands/email_opt_in_list.py
+++ b/openedx/core/djangoapps/user_api/management/commands/email_opt_in_list.py
@@ -246,12 +246,7 @@ class Command(BaseCommand):
             user_id, username, email, full_name, course_id, is_opted_in, pref_set_datetime = row
 
             if pref_set_datetime:
-                # TODO: Remove Django 1.11 upgrade shim
-                # SHIM: pref_set_datetime.tzinfo should always be None here after the 1.11 upgrade
-                # As of Django 1.9 datetimes returned from raw sql queries are no longer coerced to being tz aware
-                # so we correct for that here.
-                if pref_set_datetime.tzinfo is None or pref_set_datetime.tzinfo.utcoffset(pref_set_datetime) is None:
-                    pref_set_datetime = timezone.make_aware(pref_set_datetime, timezone.utc)
+                pref_set_datetime = timezone.make_aware(pref_set_datetime, timezone.utc)
             else:
                 pref_set_datetime = self.DEFAULT_DATETIME_STR
 
diff --git a/openedx/tests/util/__init__.py b/openedx/tests/util/__init__.py
index 0db3fb023f8..e69de29bb2d 100644
--- a/openedx/tests/util/__init__.py
+++ b/openedx/tests/util/__init__.py
@@ -1,30 +0,0 @@
-"""
-Utilities for Open edX unit tests.
-"""
-from __future__ import absolute_import, unicode_literals
-
-import django
-
-
-# TODO: Remove Django 1.11 upgrade shim
-# SHIM: We should be able to get rid of this utility post-upgrade
-def expected_redirect_url(relative_url, hostname='testserver'):
-    """
-    Get the expected redirect URL for the current Django version and the
-    given relative URL:
-
-    * Django 1.8 and earlier redirect URLs beginning with a slash to absolute
-      URLs, later versions redirect to relative ones.
-    * Django 1.8 and earlier leave URLs without a leading slash alone, later
-      versions prepend the missing slash.
-    """
-    if django.VERSION < (1, 9):
-        if relative_url.startswith('/'):
-            return 'http://{}{}'.format(hostname, relative_url)
-        else:
-            return relative_url
-    else:
-        if relative_url.startswith('/'):
-            return relative_url
-        else:
-            return '/{}'.format(relative_url)
diff --git a/requirements/edx-sandbox/base.txt b/requirements/edx-sandbox/base.txt
index 43115f056f2..6ec232c40fb 100644
--- a/requirements/edx-sandbox/base.txt
+++ b/requirements/edx-sandbox/base.txt
@@ -4,6 +4,7 @@
 #
 #    make upgrade
 #
+
 common/lib/calc
 common/lib/chem
 common/lib/sandbox-packages
diff --git a/requirements/edx-sandbox/shared.txt b/requirements/edx-sandbox/shared.txt
index 7fb61cce4dc..e9f57b37502 100644
--- a/requirements/edx-sandbox/shared.txt
+++ b/requirements/edx-sandbox/shared.txt
@@ -4,6 +4,7 @@
 #
 #    make upgrade
 #
+
 -e common/lib/calc
 -e common/lib/chem
 -e common/lib/sandbox-packages
diff --git a/requirements/edx/base.in b/requirements/edx/base.in
index 575f37f83c1..d62719ccf55 100644
--- a/requirements/edx/base.in
+++ b/requirements/edx/base.in
@@ -35,7 +35,6 @@ ddt==0.8.0                          # via xblock-drag-and-drop-v2 (which probabl
 defusedxml==0.4.1                   # XML bomb protection for common XML parsers
 Django==1.11.13                     # Web application framework
 django-babel-underscore             # underscore template extractor for django-babel (internationalization utilities)
-django-birdcage                     # CSRF token forwards compatibility for the Django 1.11 upgrade; can be removed after that
 django-config-models==0.1.8         # Configuration models for Django allowing config management with auditing
 django-cors-headers==2.1.0          # Used to allow to configure CORS headers for cross-domain requests
 django-countries==4.6.1             # Country data for Django forms and model fields
diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt
index 595c4756309..41473d9f900 100644
--- a/requirements/edx/base.txt
+++ b/requirements/edx/base.txt
@@ -4,6 +4,7 @@
 #
 #    make upgrade
 #
+
 -e git+https://github.com/edx/acid-block.git@e46f9cda8a03e121a00c7e347084d142d22ebfb7#egg=acid-xblock
 -e common/lib/calc
 -e common/lib/capa
@@ -69,7 +70,6 @@ defusedxml==0.4.1
 django-appconf==1.0.2     # via django-statici18n
 django-babel-underscore==0.5.2
 django-babel==0.6.2       # via django-babel-underscore
-django-birdcage==1.0.0
 django-braces==1.13.0     # via django-oauth-toolkit
 django-classy-tags==0.8.0  # via django-sekizai
 django-config-models==0.1.8
@@ -93,7 +93,7 @@ django-require==1.0.11
 django-rest-swagger==2.2.0
 django-sekizai==0.10.0
 django-ses==0.8.4
-django-simple-history==2.0
+django-simple-history==2.1.0
 django-splash==0.2.2
 django-statici18n==1.4.0
 django-storages==1.4.1
@@ -177,7 +177,7 @@ openapi-codec==1.3.2      # via django-rest-swagger
 path.py==8.2.1
 pathtools==0.1.2
 paver==1.3.4
-pbr==4.0.3
+pbr==4.0.4
 pdfminer==20140328
 piexif==1.0.2
 pillow==3.4.0
@@ -227,12 +227,12 @@ stevedore==1.10.0
 sympy==0.7.1
 unicodecsv==0.14.1
 uritemplate==3.0.0        # via coreapi
-urllib3==1.22             # via elasticsearch
+urllib3==1.23             # via elasticsearch
 user-util==0.1.3
 voluptuous==0.11.1
 watchdog==0.8.3
 web-fragments==0.2.2
-webob==1.8.1              # via xblock
+webob==1.8.2              # via xblock
 wrapt==1.10.5
 xblock-review==1.1.5
 xblock==1.1.1
diff --git a/requirements/edx/coverage.txt b/requirements/edx/coverage.txt
index 2ee11724863..c721171b435 100644
--- a/requirements/edx/coverage.txt
+++ b/requirements/edx/coverage.txt
@@ -4,6 +4,7 @@
 #
 #    make upgrade
 #
+
 coverage==4.2
 diff-cover==0.9.8
 inflect==0.3.1            # via jinja2-pluralize
diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt
index ddf237640d4..85a8bf6ce9f 100644
--- a/requirements/edx/development.txt
+++ b/requirements/edx/development.txt
@@ -4,6 +4,7 @@
 #
 #    make upgrade
 #
+
 -e git+https://github.com/edx/acid-block.git@e46f9cda8a03e121a00c7e347084d142d22ebfb7#egg=acid-xblock
 -e common/lib/calc
 -e common/lib/capa
@@ -88,7 +89,6 @@ diff-cover==0.9.8
 django-appconf==1.0.2
 django-babel-underscore==0.5.2
 django-babel==0.6.2
-django-birdcage==1.0.0
 django-braces==1.13.0
 django-classy-tags==0.8.0
 django-config-models==0.1.8
@@ -113,7 +113,7 @@ django-require==1.0.11
 django-rest-swagger==2.2.0
 django-sekizai==0.10.0
 django-ses==0.8.4
-django-simple-history==2.0
+django-simple-history==2.1.0
 django-splash==0.2.2
 django-statici18n==1.4.0
 django-storages==1.4.1
@@ -210,7 +210,7 @@ markupsafe==1.0
 mccabe==0.6.1
 mock==1.0.1
 mongoengine==0.10.0
-more-itertools==4.1.0
+more-itertools==4.2.0
 moto==0.3.1
 mysql-python==1.2.5
 needle==0.5.0
@@ -229,7 +229,7 @@ parsel==1.4.0
 path.py==8.2.1
 pathtools==0.1.2
 paver==1.3.4
-pbr==4.0.3
+pbr==4.0.4
 pdfminer==20140328
 piexif==1.0.2
 pillow==3.4.0
@@ -240,7 +240,7 @@ psutil==1.2.1
 py2neo==3.1.2
 py==1.5.3
 pyasn1-modules==0.2.1
-pyasn1==0.4.2
+pyasn1==0.4.3
 pycodestyle==2.3.1
 pycontracts==1.7.1
 pycountry==1.20
@@ -270,7 +270,7 @@ pytest-django==3.1.2
 pytest-forked==0.2
 pytest-randomly==1.2.3
 pytest-xdist==1.22.2
-pytest==3.6.0
+pytest==3.6.1
 python-dateutil==2.4.0
 python-levenshtein==0.12.0
 python-memcached==1.48
@@ -309,8 +309,8 @@ social-auth-app-django==1.2.0
 social-auth-core==1.4.0
 sorl-thumbnail==12.3
 sortedcontainers==0.9.2
-sphinx==1.7.4
-sphinxcontrib-websupport==1.0.1  # via sphinx
+sphinx==1.7.5
+sphinxcontrib-websupport==1.1.0  # via sphinx
 splinter==0.8.0
 sqlparse==0.2.4           # via django-debug-toolbar
 stevedore==1.10.0
@@ -329,7 +329,7 @@ unicodecsv==0.14.1
 unidecode==1.0.22
 unittest2==1.1.0
 uritemplate==3.0.0
-urllib3==1.22
+urllib3==1.23
 urlobject==2.4.3
 user-util==0.1.3
 virtualenv==16.0.0
@@ -337,7 +337,7 @@ voluptuous==0.11.1
 w3lib==1.19.0
 watchdog==0.8.3
 web-fragments==0.2.2
-webob==1.8.1
+webob==1.8.2
 werkzeug==0.14.1
 wrapt==1.10.5
 xblock-review==1.1.5
diff --git a/requirements/edx/paver.txt b/requirements/edx/paver.txt
index f00bbe947b7..697c1669401 100644
--- a/requirements/edx/paver.txt
+++ b/requirements/edx/paver.txt
@@ -4,6 +4,7 @@
 #
 #    make upgrade
 #
+
 argh==0.26.2              # via watchdog
 argparse==1.4.0           # via stevedore
 edx-opaque-keys==0.4.4
@@ -14,7 +15,7 @@ mock==1.0.1
 path.py==8.2.1
 pathtools==0.1.2          # via watchdog
 paver==1.3.4
-pbr==4.0.3                # via stevedore
+pbr==4.0.4                # via stevedore
 psutil==1.2.1
 pymongo==2.9.1
 python-memcached==1.48
diff --git a/requirements/edx/pip-tools.txt b/requirements/edx/pip-tools.txt
index e7027f3fb21..44aa65b2a3d 100644
--- a/requirements/edx/pip-tools.txt
+++ b/requirements/edx/pip-tools.txt
@@ -4,6 +4,7 @@
 #
 #    make upgrade
 #
+
 click==6.7                # via pip-tools
 first==2.0.1              # via pip-tools
 pip-tools==2.0.2
diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt
index 219e5cba8f4..5ae1e32a5db 100644
--- a/requirements/edx/testing.txt
+++ b/requirements/edx/testing.txt
@@ -4,6 +4,7 @@
 #
 #    make upgrade
 #
+
 -e git+https://github.com/edx/acid-block.git@e46f9cda8a03e121a00c7e347084d142d22ebfb7#egg=acid-xblock
 -e common/lib/calc
 -e common/lib/capa
@@ -41,7 +42,6 @@ git+https://github.com/edx-solutions/xblock-drag-and-drop-v2@v2.1.6#egg=xblock-d
 git+https://github.com/open-craft/xblock-poll@7ba819b968fe8faddb78bb22e1fe7637005eb414#egg=xblock-poll==1.2.7
 git+https://github.com/edx/xblock-utils.git@v1.1.1#egg=xblock-utils==1.1.1
 -e common/lib/xmodule
-
 amqp==1.4.9
 analytics-python==1.1.0
 anyjson==0.3.3
@@ -86,7 +86,6 @@ diff-cover==0.9.8
 django-appconf==1.0.2
 django-babel-underscore==0.5.2
 django-babel==0.6.2
-django-birdcage==1.0.0
 django-braces==1.13.0
 django-classy-tags==0.8.0
 django-config-models==0.1.8
@@ -110,7 +109,7 @@ django-require==1.0.11
 django-rest-swagger==2.2.0
 django-sekizai==0.10.0
 django-ses==0.8.4
-django-simple-history==2.0
+django-simple-history==2.1.0
 django-splash==0.2.2
 django-statici18n==1.4.0
 django-storages==1.4.1
@@ -203,7 +202,7 @@ markupsafe==1.0
 mccabe==0.6.1             # via flake8, pylint
 mock==1.0.1
 mongoengine==0.10.0
-more-itertools==4.1.0     # via pytest
+more-itertools==4.2.0     # via pytest
 moto==0.3.1
 mysql-python==1.2.5
 needle==0.5.0             # via bok-choy
@@ -221,7 +220,7 @@ parsel==1.4.0             # via scrapy
 path.py==8.2.1
 pathtools==0.1.2
 paver==1.3.4
-pbr==4.0.3
+pbr==4.0.4
 pdfminer==20140328
 piexif==1.0.2
 pillow==3.4.0
@@ -231,7 +230,7 @@ psutil==1.2.1
 py2neo==3.1.2
 py==1.5.3                 # via pytest, tox
 pyasn1-modules==0.2.1     # via service-identity
-pyasn1==0.4.2             # via pyasn1-modules, service-identity
+pyasn1==0.4.3             # via pyasn1-modules, service-identity
 pycodestyle==2.3.1
 pycontracts==1.7.1
 pycountry==1.20
@@ -260,7 +259,7 @@ pytest-django==3.1.2
 pytest-forked==0.2        # via pytest-xdist
 pytest-randomly==1.2.3
 pytest-xdist==1.22.2
-pytest==3.6.0
+pytest==3.6.1
 python-dateutil==2.4.0
 python-levenshtein==0.12.0
 python-memcached==1.48
@@ -313,7 +312,7 @@ unicodecsv==0.14.1
 unidecode==1.0.22         # via python-slugify
 unittest2==1.1.0          # via testtools
 uritemplate==3.0.0
-urllib3==1.22
+urllib3==1.23
 urlobject==2.4.3          # via pa11ycrawler
 user-util==0.1.3
 virtualenv==16.0.0        # via tox
@@ -321,7 +320,7 @@ voluptuous==0.11.1
 w3lib==1.19.0             # via parsel, scrapy
 watchdog==0.8.3
 web-fragments==0.2.2
-webob==1.8.1
+webob==1.8.2
 werkzeug==0.14.1          # via flask
 wrapt==1.10.5
 xblock-review==1.1.5
-- 
GitLab