diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 247127a2f3595fd9e099a21c9faff51648dffa71..10fbfec4369e4e70bbc24fd46bf1ba596c93dfe3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,7 +15,7 @@ jobs:
       matrix:
         os: [ubuntu-20.04]
         python-version: ['3.8']
-        toxenv: [py38-django22, py38-django30, py38-django31, py38-django32, quality]
+        toxenv: [py38-django32, py38-django40, quality]
 
     steps:
     - uses: actions/checkout@v2
@@ -36,7 +36,7 @@ jobs:
       run: tox
 
     - name: Run Coverage
-      if: matrix.python-version == '3.8' && matrix.toxenv=='py38-django22'
+      if: matrix.python-version == '3.8' && matrix.toxenv=='py38-django32'
       uses: codecov/codecov-action@v1
       with:
         flags: unittests
diff --git a/lti_consumer/__init__.py b/lti_consumer/__init__.py
index 06a949dcd7b850dab33052b9fd18d7bcfb814701..10ea9b742c31236a0a666f71d17b881429139e01 100644
--- a/lti_consumer/__init__.py
+++ b/lti_consumer/__init__.py
@@ -1,7 +1,7 @@
 """
 Runtime will load the XBlock class from here.
 """
-from .lti_xblock import LtiConsumerXBlock
 from .apps import LTIConsumerApp
+from .lti_xblock import LtiConsumerXBlock
 
 __version__ = '3.3.0'
diff --git a/lti_consumer/plugin/urls.py b/lti_consumer/plugin/urls.py
index 3cb07326320a6f97f162d7097e1f9880ec65107e..ff37bc23a7c21fbeb4b0d66b978a3851a18815ef 100644
--- a/lti_consumer/plugin/urls.py
+++ b/lti_consumer/plugin/urls.py
@@ -4,22 +4,13 @@ URL mappings for LTI Consumer plugin.
 
 
 from django.conf import settings
-from django.conf.urls import url, include
-
+from django.urls import include, re_path
 from rest_framework import routers
 
-from lti_consumer.plugin.views import (
-    public_keyset_endpoint,
-    launch_gate_endpoint,
-    access_token_endpoint,
-    # LTI Advantage URLs
-    LtiAgsLineItemViewset,
-    deep_linking_response_endpoint,
-    deep_linking_content_endpoint,
-    # LTI NRPS URLs
-    LtiNrpsContextMembershipViewSet,
-)
-
+from lti_consumer.plugin.views import (LtiAgsLineItemViewset,  # LTI Advantage URLs; LTI NRPS URLs
+                                       LtiNrpsContextMembershipViewSet, access_token_endpoint,
+                                       deep_linking_content_endpoint, deep_linking_response_endpoint,
+                                       launch_gate_endpoint, public_keyset_endpoint)
 
 # LTI 1.3 APIs router
 router = routers.SimpleRouter(trailing_slash=False)
@@ -29,32 +20,32 @@ router.register(r'memberships', LtiNrpsContextMembershipViewSet, basename='lti-n
 
 app_name = 'lti_consumer'
 urlpatterns = [
-    url(
+    re_path(
         f'lti_consumer/v1/public_keysets/{settings.USAGE_ID_PATTERN}$',
         public_keyset_endpoint,
         name='lti_consumer.public_keyset_endpoint'
     ),
-    url(
+    re_path(
         'lti_consumer/v1/launch/(?:/(?P<suffix>.*))?$',
         launch_gate_endpoint,
         name='lti_consumer.launch_gate'
     ),
-    url(
+    re_path(
         f'lti_consumer/v1/token/{settings.USAGE_ID_PATTERN}$',
         access_token_endpoint,
         name='lti_consumer.access_token'
     ),
-    url(
+    re_path(
         r'lti_consumer/v1/lti/(?P<lti_config_id>[-\w]+)/lti-dl/response',
         deep_linking_response_endpoint,
         name='lti_consumer.deep_linking_response_endpoint'
     ),
-    url(
+    re_path(
         r'lti_consumer/v1/lti/(?P<lti_config_id>[-\w]+)/lti-dl/content',
         deep_linking_content_endpoint,
         name='lti_consumer.deep_linking_content_endpoint'
     ),
-    url(
+    re_path(
         r'lti_consumer/v1/lti/(?P<lti_config_id>[-\w]+)/',
         include(router.urls)
     ),
diff --git a/requirements/base.txt b/requirements/base.txt
index e4a0b5bd1349fdc97312121c273f024d9d0811da..4c4621fe28e18c9b909059436bbe48d311097c11 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -6,13 +6,13 @@
 #
 appdirs==1.4.4
     # via fs
-asgiref==3.4.1
+asgiref==3.5.0
     # via django
 bleach==4.1.0
     # via -r requirements/base.in
 certifi==2021.10.8
     # via requests
-charset-normalizer==2.0.10
+charset-normalizer==2.0.11
     # via requests
 django==3.2.11
     # via
@@ -24,7 +24,7 @@ django==3.2.11
     #   djangorestframework
     #   edx-django-utils
     #   jsonfield
-django-config-models==2.2.2
+django-config-models==2.3.0
     # via
     #   -c requirements/constraints.txt
     #   -r requirements/base.in
@@ -38,9 +38,9 @@ djangorestframework==3.13.1
     # via
     #   -c requirements/constraints.txt
     #   django-config-models
-edx-django-utils==4.4.1
+edx-django-utils==4.4.2
     # via django-config-models
-edx-opaque-keys[django]==2.2.2
+edx-opaque-keys[django]==2.3.0
     # via -r requirements/base.in
 fs==2.4.14
     # via xblock
@@ -64,9 +64,9 @@ markupsafe==2.0.1
     # via
     #   mako
     #   xblock
-newrelic==7.2.4.171
+newrelic==7.4.0.172
     # via edx-django-utils
-oauthlib==3.1.1
+oauthlib==3.2.0
     # via -r requirements/base.in
 packaging==21.3
     # via bleach
@@ -74,15 +74,15 @@ pbr==5.8.0
     # via stevedore
 psutil==5.9.0
     # via edx-django-utils
-pycryptodomex==3.12.0
+pycryptodomex==3.14.0
     # via
     #   -r requirements/base.in
     #   pyjwkest
 pyjwkest==1.4.2
     # via -r requirements/base.in
-pymongo==4.0.1
+pymongo==3.12.3
     # via edx-opaque-keys
-pyparsing==3.0.6
+pyparsing==3.0.7
     # via packaging
 python-dateutil==2.8.2
     # via xblock
diff --git a/requirements/ci.txt b/requirements/ci.txt
index 3fa9ff757e94db220b3596eb45c9bbd432654332..2ccfed49f6762430e427301f6a4eec891ceec91c 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -8,24 +8,36 @@ appdirs==1.4.4
     # via
     #   -r requirements/test.txt
     #   fs
-asgiref==3.4.1
+arrow==1.2.2
+    # via
+    #   -r requirements/test.txt
+    #   jinja2-time
+asgiref==3.5.0
     # via
     #   -r requirements/test.txt
     #   django
-astroid==2.8.4
+astroid==2.9.3
     # via
     #   -r requirements/test.txt
     #   pylint
     #   pylint-celery
+binaryornot==0.4.4
+    # via
+    #   -r requirements/test.txt
+    #   cookiecutter
 bleach==4.1.0
     # via
     #   -r requirements/test.txt
     #   readme-renderer
-boto3==1.20.37
+boto==2.49.0
+    # via
+    #   -r requirements/test.txt
+    #   xblock-sdk
+boto3==1.20.46
     # via
     #   -r requirements/test.txt
     #   fs-s3fs
-botocore==1.23.37
+botocore==1.23.46
     # via
     #   -r requirements/test.txt
     #   boto3
@@ -34,7 +46,11 @@ certifi==2021.10.8
     # via
     #   -r requirements/test.txt
     #   requests
-charset-normalizer==2.0.10
+chardet==4.0.0
+    # via
+    #   -r requirements/test.txt
+    #   binaryornot
+charset-normalizer==2.0.11
     # via
     #   -r requirements/test.txt
     #   requests
@@ -43,6 +59,7 @@ click==8.0.3
     #   -r requirements/test.txt
     #   click-log
     #   code-annotations
+    #   cookiecutter
     #   edx-lint
 click-log==0.3.2
     # via
@@ -56,7 +73,11 @@ colorama==0.4.4
     # via
     #   -r requirements/test.txt
     #   twine
-coverage==6.2
+cookiecutter==1.7.3
+    # via
+    #   -r requirements/test.txt
+    #   xblock-sdk
+coverage==6.3
     # via
     #   -r requirements/test.txt
     #   coveralls
@@ -80,7 +101,7 @@ django==3.2.11
     #   edx-django-utils
     #   jsonfield
     #   xblock-sdk
-django-config-models==2.2.2
+django-config-models==2.3.0
     # via
     #   -c requirements/constraints.txt
     #   -r requirements/test.txt
@@ -90,8 +111,10 @@ django-crum==0.7.9
     #   edx-django-utils
 django-filter==21.1
     # via -r requirements/test.txt
-django-pyfs==3.1.0
-    # via -r requirements/test.txt
+django-pyfs==3.2.0
+    # via
+    #   -r requirements/test.txt
+    #   xblock-sdk
 django-waffle==2.3.0
     # via
     #   -r requirements/test.txt
@@ -109,13 +132,13 @@ docutils==0.18.1
     # via
     #   -r requirements/test.txt
     #   readme-renderer
-edx-django-utils==4.4.1
+edx-django-utils==4.4.2
     # via
     #   -r requirements/test.txt
     #   django-config-models
 edx-lint==5.2.1
     # via -r requirements/test.txt
-edx-opaque-keys[django]==2.2.2
+edx-opaque-keys[django]==2.3.0
     # via -r requirements/test.txt
 filelock==3.4.2
     # via
@@ -132,6 +155,7 @@ fs-s3fs==1.1.1
     # via
     #   -r requirements/test.txt
     #   django-pyfs
+    #   xblock-sdk
 future==0.18.2
     # via
     #   -r requirements/test.txt
@@ -153,6 +177,12 @@ jinja2==3.0.3
     # via
     #   -r requirements/test.txt
     #   code-annotations
+    #   cookiecutter
+    #   jinja2-time
+jinja2-time==0.2.0
+    # via
+    #   -r requirements/test.txt
+    #   cookiecutter
 jmespath==0.10.0
     # via
     #   -r requirements/test.txt
@@ -165,7 +195,9 @@ keyring==23.5.0
     #   -r requirements/test.txt
     #   twine
 lazy==1.4
-    # via -r requirements/test.txt
+    # via
+    #   -r requirements/test.txt
+    #   xblock-sdk
 lazy-object-proxy==1.7.1
     # via
     #   -r requirements/test.txt
@@ -174,6 +206,7 @@ lxml==4.7.1
     # via
     #   -r requirements/test.txt
     #   xblock
+    #   xblock-sdk
 mako==1.1.6
     # via
     #   -r requirements/test.txt
@@ -190,11 +223,11 @@ mccabe==0.6.1
     #   pylint
 mock==4.0.3
     # via -r requirements/test.txt
-newrelic==7.2.4.171
+newrelic==7.4.0.172
     # via
     #   -r requirements/test.txt
     #   edx-django-utils
-oauthlib==3.1.1
+oauthlib==3.2.0
     # via -r requirements/test.txt
 packaging==21.3
     # via
@@ -220,6 +253,10 @@ pluggy==1.0.0
     # via
     #   -r requirements/tox.txt
     #   tox
+poyo==0.5.0
+    # via
+    #   -r requirements/test.txt
+    #   cookiecutter
 psutil==5.9.0
     # via
     #   -r requirements/test.txt
@@ -230,7 +267,7 @@ py==1.11.0
     #   tox
 pycodestyle==2.8.0
     # via -r requirements/test.txt
-pycryptodomex==3.12.0
+pycryptodomex==3.14.0
     # via
     #   -r requirements/test.txt
     #   pyjwkest
@@ -240,7 +277,7 @@ pygments==2.11.2
     #   readme-renderer
 pyjwkest==1.4.2
     # via -r requirements/test.txt
-pylint==2.11.1
+pylint==2.12.2
     # via
     #   -r requirements/test.txt
     #   edx-lint
@@ -260,24 +297,30 @@ pylint-plugin-utils==0.7
     #   -r requirements/test.txt
     #   pylint-celery
     #   pylint-django
-pymongo==4.0.1
+pymongo==3.12.3
     # via
     #   -r requirements/test.txt
     #   edx-opaque-keys
-pyparsing==3.0.6
+pyparsing==3.0.7
     # via
     #   -r requirements/test.txt
     #   -r requirements/tox.txt
     #   packaging
+pypng==0.0.21
+    # via
+    #   -r requirements/test.txt
+    #   xblock-sdk
 python-dateutil==2.8.2
     # via
     #   -r requirements/test.txt
+    #   arrow
     #   botocore
     #   xblock
 python-slugify==5.0.2
     # via
     #   -r requirements/test.txt
     #   code-annotations
+    #   cookiecutter
 pytz==2021.3
     # via
     #   -r requirements/test.txt
@@ -297,10 +340,12 @@ readme-renderer==32.0
 requests==2.27.1
     # via
     #   -r requirements/test.txt
+    #   cookiecutter
     #   coveralls
     #   pyjwkest
     #   requests-toolbelt
     #   twine
+    #   xblock-sdk
 requests-toolbelt==0.9.1
     # via
     #   -r requirements/test.txt
@@ -316,12 +361,14 @@ s3transfer==0.5.0
 simplejson==3.17.6
     # via
     #   -r requirements/test.txt
+    #   xblock-sdk
     #   xblock-utils
 six==1.16.0
     # via
     #   -r requirements/test.txt
     #   -r requirements/tox.txt
     #   bleach
+    #   cookiecutter
     #   edx-lint
     #   fs
     #   fs-s3fs
@@ -375,6 +422,7 @@ web-fragments==2.0.0
     # via
     #   -r requirements/test.txt
     #   xblock
+    #   xblock-sdk
     #   xblock-utils
 webencodings==0.5.1
     # via
@@ -384,6 +432,7 @@ webob==1.8.7
     # via
     #   -r requirements/test.txt
     #   xblock
+    #   xblock-sdk
 wrapt==1.13.3
     # via
     #   -r requirements/test.txt
@@ -391,8 +440,9 @@ wrapt==1.13.3
 xblock==1.5.1
     # via
     #   -r requirements/test.txt
+    #   xblock-sdk
     #   xblock-utils
-xblock-sdk==0.4.0
+xblock-sdk==0.5.1
     # via -r requirements/test.txt
 xblock-utils==2.2.0
     # via -r requirements/test.txt
diff --git a/requirements/common_constraints.txt b/requirements/common_constraints.txt
index e8b5d79f920babc1cd6c84dc3b82617ba7d9d5a7..2c80f843257132da31b09d9a2e23d64db6af932a 100644
--- a/requirements/common_constraints.txt
+++ b/requirements/common_constraints.txt
@@ -18,3 +18,8 @@ Django<4.0
 # elasticsearch>=7.14.0 includes breaking changes in it which caused issues in discovery upgrade process.
 # elastic search changelog: https://www.elastic.co/guide/en/enterprise-search/master/release-notes-7.14.0.html
 elasticsearch<7.14.0
+
+setuptools<60
+
+# redis 4 client doesn't play nicely with redis 3 server
+redis<4
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 098affa1cee113f6fdfbd7fe44e82f933ab6c18d..d2fa367f7deb1827d5464cdca5d4faa201bdbe4f 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -8,7 +8,7 @@ appdirs==1.4.4
     # via
     #   -r requirements/base.txt
     #   fs
-asgiref==3.4.1
+asgiref==3.5.0
     # via
     #   -r requirements/base.txt
     #   django
@@ -18,7 +18,7 @@ certifi==2021.10.8
     # via
     #   -r requirements/base.txt
     #   requests
-charset-normalizer==2.0.10
+charset-normalizer==2.0.11
     # via
     #   -r requirements/base.txt
     #   requests
@@ -32,7 +32,7 @@ django==3.2.11
     #   edx-django-utils
     #   edx-i18n-tools
     #   jsonfield
-django-config-models==2.2.2
+django-config-models==2.3.0
     # via -r requirements/base.txt
 django-crum==0.7.9
     # via
@@ -48,13 +48,13 @@ djangorestframework==3.13.1
     # via
     #   -r requirements/base.txt
     #   django-config-models
-edx-django-utils==4.4.1
+edx-django-utils==4.4.2
     # via
     #   -r requirements/base.txt
     #   django-config-models
 edx-i18n-tools==0.8.1
     # via -r requirements/dev.in
-edx-opaque-keys[django]==2.2.2
+edx-opaque-keys[django]==2.3.0
     # via -r requirements/base.txt
 fs==2.4.14
     # via
@@ -85,11 +85,11 @@ markupsafe==2.0.1
     #   -r requirements/base.txt
     #   mako
     #   xblock
-newrelic==7.2.4.171
+newrelic==7.4.0.172
     # via
     #   -r requirements/base.txt
     #   edx-django-utils
-oauthlib==3.1.1
+oauthlib==3.2.0
     # via -r requirements/base.txt
 packaging==21.3
     # via
@@ -107,17 +107,17 @@ psutil==5.9.0
     # via
     #   -r requirements/base.txt
     #   edx-django-utils
-pycryptodomex==3.12.0
+pycryptodomex==3.14.0
     # via
     #   -r requirements/base.txt
     #   pyjwkest
 pyjwkest==1.4.2
     # via -r requirements/base.txt
-pymongo==4.0.1
+pymongo==3.12.3
     # via
     #   -r requirements/base.txt
     #   edx-opaque-keys
-pyparsing==3.0.6
+pyparsing==3.0.7
     # via
     #   -r requirements/base.txt
     #   packaging
diff --git a/requirements/pip.in b/requirements/pip.in
index 21ce8e9d24660b11418e7a2f87b101258c7d6c0c..716c6f28377d648dd534fbb0bae8948785d2b075 100644
--- a/requirements/pip.in
+++ b/requirements/pip.in
@@ -1,4 +1,5 @@
 # Core dependencies for installing other packages
+-c constraints.txt
 
 pip
 setuptools
diff --git a/requirements/pip.txt b/requirements/pip.txt
index de37a34a0ed54ca32eebe19fc89f7b6b75ef4162..6d2ee0a98496659dd2e23e05e5991557de22d95a 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -8,7 +8,9 @@ wheel==0.37.1
     # via -r requirements/pip.in
 
 # The following packages are considered to be unsafe in a requirements file:
-pip==21.3.1
-    # via -r requirements/pip.in
-setuptools==60.5.0
+pip==22.0.2
     # via -r requirements/pip.in
+setuptools==59.8.0
+    # via
+    #   -c requirements/common_constraints.txt
+    #   -r requirements/pip.in
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 1366ad66d567ccfa2ab7de3a2bf3b8f8caf6a3ea..47f7647c99eef3dcb11a1eab9c5416cdc80e8924 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -2,13 +2,15 @@
 # This file is autogenerated by pip-compile with python 3.8
 # To update, run:
 #
-#    pip-compile --output-file=requirements/quality.txt requirements/quality.in
+#    make upgrade
 #
 appdirs==1.4.4
     # via
     #   -r requirements/base.txt
     #   fs
-asgiref==3.4.1
+arrow==1.2.2
+    # via jinja2-time
+asgiref==3.5.0
     # via
     #   -r requirements/base.txt
     #   django
@@ -16,13 +18,25 @@ astroid==2.9.3
     # via
     #   pylint
     #   pylint-celery
+binaryornot==0.4.4
+    # via cookiecutter
 bleach==4.1.0
     # via -r requirements/base.txt
+boto==2.49.0
+    # via xblock-sdk
+boto3==1.20.46
+    # via fs-s3fs
+botocore==1.23.46
+    # via
+    #   boto3
+    #   s3transfer
 certifi==2021.10.8
     # via
     #   -r requirements/base.txt
     #   requests
-charset-normalizer==2.0.10
+chardet==4.0.0
+    # via binaryornot
+charset-normalizer==2.0.11
     # via
     #   -r requirements/base.txt
     #   requests
@@ -30,11 +44,14 @@ click==8.0.3
     # via
     #   click-log
     #   code-annotations
+    #   cookiecutter
     #   edx-lint
 click-log==0.3.2
     # via edx-lint
 code-annotations==1.2.0
     # via edx-lint
+cookiecutter==1.7.3
+    # via xblock-sdk
 ddt==1.4.4
     # via -r requirements/quality.in
 django==3.2.11
@@ -44,11 +61,12 @@ django==3.2.11
     #   django-config-models
     #   django-crum
     #   django-filter
+    #   django-pyfs
     #   djangorestframework
     #   edx-django-utils
     #   jsonfield
     #   xblock-sdk
-django-config-models==2.2.2
+django-config-models==2.3.0
     # via
     #   -c requirements/constraints.txt
     #   -r requirements/base.txt
@@ -58,6 +76,8 @@ django-crum==0.7.9
     #   edx-django-utils
 django-filter==21.1
     # via -r requirements/base.txt
+django-pyfs==3.2.0
+    # via xblock-sdk
 django-waffle==2.3.0
     # via
     #   -r requirements/base.txt
@@ -67,18 +87,24 @@ djangorestframework==3.13.1
     #   -c requirements/constraints.txt
     #   -r requirements/base.txt
     #   django-config-models
-edx-django-utils==4.4.1
+edx-django-utils==4.4.2
     # via
     #   -r requirements/base.txt
     #   django-config-models
 edx-lint==5.2.1
     # via -r requirements/quality.in
-edx-opaque-keys[django]==2.2.2
+edx-opaque-keys[django]==2.3.0
     # via -r requirements/base.txt
 fs==2.4.14
     # via
     #   -r requirements/base.txt
+    #   django-pyfs
+    #   fs-s3fs
     #   xblock
+fs-s3fs==1.1.1
+    # via
+    #   django-pyfs
+    #   xblock-sdk
 future==0.18.2
     # via
     #   -r requirements/base.txt
@@ -90,17 +116,29 @@ idna==3.3
 isort==5.10.1
     # via pylint
 jinja2==3.0.3
-    # via code-annotations
+    # via
+    #   code-annotations
+    #   cookiecutter
+    #   jinja2-time
+jinja2-time==0.2.0
+    # via cookiecutter
+jmespath==0.10.0
+    # via
+    #   boto3
+    #   botocore
 jsonfield==3.1.0
     # via -r requirements/base.txt
 lazy==1.4
-    # via -r requirements/base.txt
+    # via
+    #   -r requirements/base.txt
+    #   xblock-sdk
 lazy-object-proxy==1.7.1
     # via astroid
 lxml==4.7.1
     # via
     #   -r requirements/base.txt
     #   xblock
+    #   xblock-sdk
 mako==1.1.6
     # via
     #   -r requirements/base.txt
@@ -113,11 +151,11 @@ markupsafe==2.0.1
     #   xblock
 mccabe==0.6.1
     # via pylint
-newrelic==7.2.4.171
+newrelic==7.4.0.172
     # via
     #   -r requirements/base.txt
     #   edx-django-utils
-oauthlib==3.1.1
+oauthlib==3.2.0
     # via -r requirements/base.txt
 packaging==21.3
     # via
@@ -129,13 +167,15 @@ pbr==5.8.0
     #   stevedore
 platformdirs==2.4.1
     # via pylint
+poyo==0.5.0
+    # via cookiecutter
 psutil==5.9.0
     # via
     #   -r requirements/base.txt
     #   edx-django-utils
 pycodestyle==2.8.0
     # via -r requirements/quality.in
-pycryptodomex==3.12.0
+pycryptodomex==3.14.0
     # via
     #   -r requirements/base.txt
     #   pyjwkest
@@ -156,20 +196,26 @@ pylint-plugin-utils==0.7
     # via
     #   pylint-celery
     #   pylint-django
-pymongo==4.0.1
+pymongo==3.12.3
     # via
     #   -r requirements/base.txt
     #   edx-opaque-keys
-pyparsing==3.0.6
+pyparsing==3.0.7
     # via
     #   -r requirements/base.txt
     #   packaging
+pypng==0.0.21
+    # via xblock-sdk
 python-dateutil==2.8.2
     # via
     #   -r requirements/base.txt
+    #   arrow
+    #   botocore
     #   xblock
 python-slugify==5.0.2
-    # via code-annotations
+    # via
+    #   code-annotations
+    #   cookiecutter
 pytz==2021.3
     # via
     #   -r requirements/base.txt
@@ -185,17 +231,24 @@ pyyaml==6.0
 requests==2.27.1
     # via
     #   -r requirements/base.txt
+    #   cookiecutter
     #   pyjwkest
+    #   xblock-sdk
+s3transfer==0.5.0
+    # via boto3
 simplejson==3.17.6
     # via
     #   -r requirements/base.txt
+    #   xblock-sdk
     #   xblock-utils
 six==1.16.0
     # via
     #   -r requirements/base.txt
     #   bleach
+    #   cookiecutter
     #   edx-lint
     #   fs
+    #   fs-s3fs
     #   pyjwkest
     #   python-dateutil
 sqlparse==0.4.2
@@ -219,11 +272,13 @@ typing-extensions==4.0.1
 urllib3==1.26.8
     # via
     #   -r requirements/base.txt
+    #   botocore
     #   requests
 web-fragments==2.0.0
     # via
     #   -r requirements/base.txt
     #   xblock
+    #   xblock-sdk
     #   xblock-utils
 webencodings==0.5.1
     # via
@@ -233,13 +288,15 @@ webob==1.8.7
     # via
     #   -r requirements/base.txt
     #   xblock
+    #   xblock-sdk
 wrapt==1.13.3
     # via astroid
 xblock==1.5.1
     # via
     #   -r requirements/base.txt
+    #   xblock-sdk
     #   xblock-utils
-xblock-sdk==0.4.0
+xblock-sdk==0.5.1
     # via -r requirements/quality.in
 xblock-utils==2.2.0
     # via -r requirements/base.txt
diff --git a/requirements/test.txt b/requirements/test.txt
index 8ba21350d1cbcd89278e8b058a366c5c5cfc5eab..57595f04564e808cd5cbf0fc9ed86ff87a1f6e95 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -8,21 +8,27 @@ appdirs==1.4.4
     # via
     #   -r requirements/base.txt
     #   fs
-asgiref==3.4.1
+arrow==1.2.2
+    # via jinja2-time
+asgiref==3.5.0
     # via
     #   -r requirements/base.txt
     #   django
-astroid==2.8.4
+astroid==2.9.3
     # via
     #   pylint
     #   pylint-celery
+binaryornot==0.4.4
+    # via cookiecutter
 bleach==4.1.0
     # via
     #   -r requirements/base.txt
     #   readme-renderer
-boto3==1.20.37
+boto==2.49.0
+    # via xblock-sdk
+boto3==1.20.46
     # via fs-s3fs
-botocore==1.23.37
+botocore==1.23.46
     # via
     #   boto3
     #   s3transfer
@@ -30,7 +36,9 @@ certifi==2021.10.8
     # via
     #   -r requirements/base.txt
     #   requests
-charset-normalizer==2.0.10
+chardet==4.0.0
+    # via binaryornot
+charset-normalizer==2.0.11
     # via
     #   -r requirements/base.txt
     #   requests
@@ -38,6 +46,7 @@ click==8.0.3
     # via
     #   click-log
     #   code-annotations
+    #   cookiecutter
     #   edx-lint
 click-log==0.3.2
     # via edx-lint
@@ -45,7 +54,9 @@ code-annotations==1.2.0
     # via edx-lint
 colorama==0.4.4
     # via twine
-coverage==6.2
+cookiecutter==1.7.3
+    # via xblock-sdk
+coverage==6.3
     # via coveralls
 coveralls==3.3.1
     # via -r requirements/test.in
@@ -54,7 +65,6 @@ ddt==1.4.4
     # via
     #   -c requirements/common_constraints.txt
     #   -r requirements/base.txt
-    #   -r requirements/test.in
     #   django-config-models
     #   django-crum
     #   django-filter
@@ -63,7 +73,7 @@ ddt==1.4.4
     #   edx-django-utils
     #   jsonfield
     #   xblock-sdk
-django-config-models==2.2.2
+django-config-models==2.3.0
     # via
     #   -c requirements/constraints.txt
     #   -r requirements/base.txt
@@ -73,8 +83,10 @@ django-crum==0.7.9
     #   edx-django-utils
 django-filter==21.1
     # via -r requirements/base.txt
-django-pyfs==3.1.0
-    # via -r requirements/test.in
+django-pyfs==3.2.0
+    # via
+    #   -r requirements/test.in
+    #   xblock-sdk
 django-waffle==2.3.0
     # via
     #   -r requirements/base.txt
@@ -89,13 +101,13 @@ docopt==0.6.2
     # via coveralls
 docutils==0.18.1
     # via readme-renderer
-edx-django-utils==4.4.1
+edx-django-utils==4.4.2
     # via
     #   -r requirements/base.txt
     #   django-config-models
 edx-lint==5.2.1
     # via -r requirements/test.in
-edx-opaque-keys[django]==2.2.2
+edx-opaque-keys[django]==2.3.0
     # via -r requirements/base.txt
 fs==2.4.14
     # via
@@ -104,7 +116,9 @@ fs==2.4.14
     #   fs-s3fs
     #   xblock
 fs-s3fs==1.1.1
-    # via django-pyfs
+    # via
+    #   django-pyfs
+    #   xblock-sdk
 future==0.18.2
     # via
     #   -r requirements/base.txt
@@ -120,7 +134,12 @@ importlib-metadata==4.10.1
 isort==5.10.1
     # via pylint
 jinja2==3.0.3
-    # via code-annotations
+    # via
+    #   code-annotations
+    #   cookiecutter
+    #   jinja2-time
+jinja2-time==0.2.0
+    # via cookiecutter
 jmespath==0.10.0
     # via
     #   boto3
@@ -130,13 +149,16 @@ jsonfield==3.1.0
 keyring==23.5.0
     # via twine
 lazy==1.4
-    # via -r requirements/base.txt
+    # via
+    #   -r requirements/base.txt
+    #   xblock-sdk
 lazy-object-proxy==1.7.1
     # via astroid
 lxml==4.7.1
     # via
     #   -r requirements/base.txt
     #   xblock
+    #   xblock-sdk
 mako==1.1.6
     # via
     #   -r requirements/base.txt
@@ -151,11 +173,11 @@ mccabe==0.6.1
     # via pylint
 mock==4.0.3
     # via -r requirements/test.in
-newrelic==7.2.4.171
+newrelic==7.4.0.172
     # via
     #   -r requirements/base.txt
     #   edx-django-utils
-oauthlib==3.1.1
+oauthlib==3.2.0
     # via -r requirements/base.txt
 packaging==21.3
     # via
@@ -169,13 +191,15 @@ pkginfo==1.8.2
     # via twine
 platformdirs==2.4.1
     # via pylint
+poyo==0.5.0
+    # via cookiecutter
 psutil==5.9.0
     # via
     #   -r requirements/base.txt
     #   edx-django-utils
 pycodestyle==2.8.0
     # via -r requirements/test.in
-pycryptodomex==3.12.0
+pycryptodomex==3.14.0
     # via
     #   -r requirements/base.txt
     #   pyjwkest
@@ -183,7 +207,7 @@ pygments==2.11.2
     # via readme-renderer
 pyjwkest==1.4.2
     # via -r requirements/base.txt
-pylint==2.11.1
+pylint==2.12.2
     # via
     #   edx-lint
     #   pylint-celery
@@ -197,21 +221,26 @@ pylint-plugin-utils==0.7
     # via
     #   pylint-celery
     #   pylint-django
-pymongo==4.0.1
+pymongo==3.12.3
     # via
     #   -r requirements/base.txt
     #   edx-opaque-keys
-pyparsing==3.0.6
+pyparsing==3.0.7
     # via
     #   -r requirements/base.txt
     #   packaging
+pypng==0.0.21
+    # via xblock-sdk
 python-dateutil==2.8.2
     # via
     #   -r requirements/base.txt
+    #   arrow
     #   botocore
     #   xblock
 python-slugify==5.0.2
-    # via code-annotations
+    # via
+    #   code-annotations
+    #   cookiecutter
 pytz==2021.3
     # via
     #   -r requirements/base.txt
@@ -231,10 +260,12 @@ readme-renderer==32.0
 requests==2.27.1
     # via
     #   -r requirements/base.txt
+    #   cookiecutter
     #   coveralls
     #   pyjwkest
     #   requests-toolbelt
     #   twine
+    #   xblock-sdk
 requests-toolbelt==0.9.1
     # via twine
 rfc3986==2.0.0
@@ -244,11 +275,13 @@ s3transfer==0.5.0
 simplejson==3.17.6
     # via
     #   -r requirements/base.txt
+    #   xblock-sdk
     #   xblock-utils
 six==1.16.0
     # via
     #   -r requirements/base.txt
     #   bleach
+    #   cookiecutter
     #   edx-lint
     #   fs
     #   fs-s3fs
@@ -285,6 +318,7 @@ web-fragments==2.0.0
     # via
     #   -r requirements/base.txt
     #   xblock
+    #   xblock-sdk
     #   xblock-utils
 webencodings==0.5.1
     # via
@@ -294,13 +328,15 @@ webob==1.8.7
     # via
     #   -r requirements/base.txt
     #   xblock
+    #   xblock-sdk
 wrapt==1.13.3
     # via astroid
 xblock==1.5.1
     # via
     #   -r requirements/base.txt
+    #   xblock-sdk
     #   xblock-utils
-xblock-sdk==0.4.0
+xblock-sdk==0.5.1
     # via -r requirements/test.in
 xblock-utils==2.2.0
     # via -r requirements/base.txt
diff --git a/requirements/tox.txt b/requirements/tox.txt
index 3e47a84f2ab174e9d337d4a5c22e4a7772a4896f..678cee1dc13133d6dc7513557e0fb5068c6b7439 100644
--- a/requirements/tox.txt
+++ b/requirements/tox.txt
@@ -18,7 +18,7 @@ pluggy==1.0.0
     # via tox
 py==1.11.0
     # via tox
-pyparsing==3.0.6
+pyparsing==3.0.7
     # via packaging
 six==1.16.0
     # via
diff --git a/setup.py b/setup.py
index 7d1a77f6cab25f9d3206794483e745e0a9c3265f..e46cb530b516d3e6334e0968eb4cd0aec7dcb0c9 100644
--- a/setup.py
+++ b/setup.py
@@ -138,10 +138,8 @@ setup(
     classifiers=[
         'Development Status :: 5 - Production/Stable',
         'Framework :: Django',
-        'Framework :: Django :: 2.2',
-        'Framework :: Django :: 3.0',
-        'Framework :: Django :: 3.1',
         'Framework :: Django :: 3.2',
+        'Framework :: Django :: 4.0',
         'Intended Audience :: Developers',
         'License :: OSI Approved :: GNU Affero General Public License v3',
         'Natural Language :: English',
diff --git a/test_urls.py b/test_urls.py
index b250558f9d91a01ed36ab599a33aa1b64bea5a33..2d5041ce587c512aa568e77b304cf01270d56617 100644
--- a/test_urls.py
+++ b/test_urls.py
@@ -1,7 +1,7 @@
 """
 Custom URL patterns for testing
 """
-from django.conf.urls import include, re_path
+from django.urls import include, re_path
 
 urlpatterns = [
     re_path(r'^', include('workbench.urls')),
diff --git a/tox.ini b/tox.ini
index 3939b051acdaed344663aed53ac6293501eff993..aacadd3de8b469144b0fa465c5e793f4628d8aa4 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,23 +1,21 @@
 [tox]
-envlist = py38-django{22,30,31,32}, quality
+envlist = py38-django{32,40}, quality
 
 [testenv]
-whitelist_externals =
-	make
-deps =
-	django22: Django>=2.2,<2.3
-	django30: Django>=3.0,<3.1
-	django31: Django>=3.1,<3.2
-	django32: Django>=3.2,<4.0
-	-r{toxinidir}/requirements/test.txt
-commands =
-	make test
-
+whitelist_externals = 
+    make
+deps = 
+    django32: Django>=3.2,<4.0
+    django40: Django>=4.0,<4.1
+    -r{toxinidir}/requirements/test.txt
+commands = 
+    make test
 
 [testenv:quality]
-whitelist_externals =
-	make
-deps =
-	-r{toxinidir}/requirements/quality.txt
-commands =
-	make quality
+whitelist_externals = 
+    make
+deps = 
+    -r{toxinidir}/requirements/quality.txt
+commands = 
+    make quality
+