From d9dd45662ec96d465897f1e0d8e16e681d1db067 Mon Sep 17 00:00:00 2001 From: Julia Hansbrough Date: Tue, 8 Jul 2014 19:22:55 +0000 Subject: [PATCH] Enables LMS to handle deprecated URLs LMS-2651 --- .../views/tests/test_course_index.py | 4 +- cms/envs/common.py | 6 + cms/urls.py | 48 ++++---- common/djangoapps/course_modes/urls.py | 5 +- common/djangoapps/student/views.py | 6 +- common/djangoapps/util/request.py | 3 +- lms/djangoapps/class_dashboard/urls.py | 8 +- lms/djangoapps/courseware/urls.py | 0 lms/djangoapps/instructor/views/legacy.py | 2 +- lms/djangoapps/shoppingcart/urls.py | 2 +- lms/djangoapps/verify_student/urls.py | 18 ++- lms/envs/common.py | 8 ++ lms/urls.py | 109 +++++++++--------- 13 files changed, 122 insertions(+), 97 deletions(-) delete mode 100644 lms/djangoapps/courseware/urls.py diff --git a/cms/djangoapps/contentstore/views/tests/test_course_index.py b/cms/djangoapps/contentstore/views/tests/test_course_index.py index 0d24ed0b94..ed08d42387 100644 --- a/cms/djangoapps/contentstore/views/tests/test_course_index.py +++ b/cms/djangoapps/contentstore/views/tests/test_course_index.py @@ -4,11 +4,11 @@ Unit tests for getting the list of courses and the course outline. import json import lxml -from cms.urls import COURSE_KEY_PATTERN from contentstore.tests.utils import CourseTestCase from contentstore.utils import reverse_course_url from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from opaque_keys.edx.locator import Locator +from django.conf import settings class TestCourseIndex(CourseTestCase): @@ -39,7 +39,7 @@ class TestCourseIndex(CourseTestCase): for link in course_link_eles: self.assertRegexpMatches( link.get("href"), - 'course/{}'.format(COURSE_KEY_PATTERN) + 'course/{}'.format(settings.COURSE_KEY_PATTERN) ) # now test that url outline_response = authed_client.get(link.get("href"), {}, HTTP_ACCEPT='text/html') diff --git a/cms/envs/common.py b/cms/envs/common.py index 4bd842da47..127550d4be 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -168,6 +168,12 @@ AUTHENTICATION_BACKENDS = ( LMS_BASE = None +# These are standard regexes for pulling out info like course_ids, usage_ids, etc. +# They are used so that URLs with deprecated-format strings still work. +from lms.envs.common import ( + COURSE_KEY_PATTERN, COURSE_ID_PATTERN, USAGE_KEY_PATTERN, ASSET_KEY_PATTERN +) + #################### CAPA External Code Evaluation ############################# XQUEUE_INTERFACE = { 'url': 'http://localhost:8888', diff --git a/cms/urls.py b/cms/urls.py index 01563b8921..3d09b00e57 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -5,10 +5,6 @@ from django.conf.urls import patterns, include, url from ratelimitbackend import admin admin.autodiscover() -COURSE_KEY_PATTERN = r'(?P(?:[^/]+/[^/]+/[^/]+)|(?:[^/]+))' -USAGE_KEY_PATTERN = r'(?P(?:i4x://?[^/]+/[^/]+/[^/]+/[^@]+(?:@[^/]+)?)|(?:[^/]+))' -ASSET_KEY_PATTERN = r'(?P(?:/?c4x(:/)?/[^/]+/[^/]+/[^/]+/[^@]+(?:@[^/]+)?)|(?:[^/]+))' - urlpatterns = patterns('', # nopep8 url(r'^transcripts/upload$', 'contentstore.views.upload_transcripts', name='upload_transcripts'), @@ -70,30 +66,30 @@ urlpatterns += patterns( url(r'^signin$', 'login_page', name='login'), url(r'^request_course_creator$', 'request_course_creator'), - url(r'^course_team/{}/(?P.+)?$'.format(COURSE_KEY_PATTERN), 'course_team_handler'), - url(r'^course_info/{}$'.format(COURSE_KEY_PATTERN), 'course_info_handler'), + url(r'^course_team/{}/(?P.+)?$'.format(settings.COURSE_KEY_PATTERN), 'course_team_handler'), + url(r'^course_info/{}$'.format(settings.COURSE_KEY_PATTERN), 'course_info_handler'), url( - r'^course_info_update/{}/(?P\d+)?$'.format(COURSE_KEY_PATTERN), + r'^course_info_update/{}/(?P\d+)?$'.format(settings.COURSE_KEY_PATTERN), 'course_info_update_handler' ), - url(r'^course/{}?$'.format(COURSE_KEY_PATTERN), 'course_handler', name='course_handler'), - url(r'^subsection/{}$'.format(USAGE_KEY_PATTERN), 'subsection_handler'), - url(r'^unit/{}$'.format(USAGE_KEY_PATTERN), 'unit_handler'), - url(r'^container/{}$'.format(USAGE_KEY_PATTERN), 'container_handler'), - url(r'^checklists/{}/(?P\d+)?$'.format(COURSE_KEY_PATTERN), 'checklists_handler'), - url(r'^orphan/{}$'.format(COURSE_KEY_PATTERN), 'orphan_handler'), - url(r'^assets/{}/{}?$'.format(COURSE_KEY_PATTERN, ASSET_KEY_PATTERN), 'assets_handler'), - url(r'^import/{}$'.format(COURSE_KEY_PATTERN), 'import_handler'), - url(r'^import_status/{}/(?P.+)$'.format(COURSE_KEY_PATTERN), 'import_status_handler'), - url(r'^export/{}$'.format(COURSE_KEY_PATTERN), 'export_handler'), - url(r'^xblock/{}/(?P[^/]+)$'.format(USAGE_KEY_PATTERN), 'xblock_view_handler'), - url(r'^xblock/{}?$'.format(USAGE_KEY_PATTERN), 'xblock_handler'), - url(r'^tabs/{}$'.format(COURSE_KEY_PATTERN), 'tabs_handler'), - url(r'^settings/details/{}$'.format(COURSE_KEY_PATTERN), 'settings_handler'), - url(r'^settings/grading/{}(/)?(?P\d+)?$'.format(COURSE_KEY_PATTERN), 'grading_handler'), - url(r'^settings/advanced/{}$'.format(COURSE_KEY_PATTERN), 'advanced_settings_handler'), - url(r'^textbooks/{}$'.format(COURSE_KEY_PATTERN), 'textbooks_list_handler'), - url(r'^textbooks/{}/(?P\d[^/]*)$'.format(COURSE_KEY_PATTERN), 'textbooks_detail_handler'), + url(r'^course/{}?$'.format(settings.COURSE_KEY_PATTERN), 'course_handler', name='course_handler'), + url(r'^subsection/{}$'.format(settings.USAGE_KEY_PATTERN), 'subsection_handler'), + url(r'^unit/{}$'.format(settings.USAGE_KEY_PATTERN), 'unit_handler'), + url(r'^container/{}$'.format(settings.USAGE_KEY_PATTERN), 'container_handler'), + url(r'^checklists/{}/(?P\d+)?$'.format(settings.COURSE_KEY_PATTERN), 'checklists_handler'), + url(r'^orphan/{}$'.format(settings.COURSE_KEY_PATTERN), 'orphan_handler'), + url(r'^assets/{}/{}?$'.format(settings.COURSE_KEY_PATTERN, settings.ASSET_KEY_PATTERN), 'assets_handler'), + url(r'^import/{}$'.format(settings.COURSE_KEY_PATTERN), 'import_handler'), + url(r'^import_status/{}/(?P.+)$'.format(settings.COURSE_KEY_PATTERN), 'import_status_handler'), + url(r'^export/{}$'.format(settings.COURSE_KEY_PATTERN), 'export_handler'), + url(r'^xblock/{}/(?P[^/]+)$'.format(settings.USAGE_KEY_PATTERN), 'xblock_view_handler'), + url(r'^xblock/{}?$'.format(settings.USAGE_KEY_PATTERN), 'xblock_handler'), + url(r'^tabs/{}$'.format(settings.COURSE_KEY_PATTERN), 'tabs_handler'), + url(r'^settings/details/{}$'.format(settings.COURSE_KEY_PATTERN), 'settings_handler'), + url(r'^settings/grading/{}(/)?(?P\d+)?$'.format(settings.COURSE_KEY_PATTERN), 'grading_handler'), + url(r'^settings/advanced/{}$'.format(settings.COURSE_KEY_PATTERN), 'advanced_settings_handler'), + url(r'^textbooks/{}$'.format(settings.COURSE_KEY_PATTERN), 'textbooks_list_handler'), + url(r'^textbooks/{}/(?P\d[^/]*)$'.format(settings.COURSE_KEY_PATTERN), 'textbooks_detail_handler'), ) if settings.FEATURES.get('ENABLE_GROUP_CONFIGURATIONS'): @@ -113,7 +109,7 @@ urlpatterns += patterns('', if settings.FEATURES.get('ENABLE_EXPORT_GIT'): - urlpatterns += (url(r'^export_git/{}$'.format(COURSE_KEY_PATTERN), + urlpatterns += (url(r'^export_git/{}$'.format(settings.COURSE_KEY_PATTERN), 'contentstore.views.export_git', name='export_git'),) if settings.FEATURES.get('ENABLE_SERVICE_STATUS'): diff --git a/common/djangoapps/course_modes/urls.py b/common/djangoapps/course_modes/urls.py index 917f97ff31..6e4b24ac17 100644 --- a/common/djangoapps/course_modes/urls.py +++ b/common/djangoapps/course_modes/urls.py @@ -1,9 +1,12 @@ from django.conf.urls import include, patterns, url +from django.conf import settings + from django.views.generic import TemplateView from course_modes import views urlpatterns = patterns( '', - url(r'^choose/(?P[^/]+/[^/]+/[^/]+)/$', views.ChooseModeView.as_view(), name="course_modes_choose"), + # pylint seems to dislike as_view() calls because it's a `classonlymethod` instead of `classmethod`, so we disable the warning + url(r'^choose/{}/$'.format(settings.COURSE_ID_PATTERN), views.ChooseModeView.as_view(), name="course_modes_choose"), # pylint: disable=no-value-for-parameter ) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 6c5c311dd8..cd69ebc046 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -87,6 +87,7 @@ from util.password_policy_validators import ( from third_party_auth import pipeline, provider from xmodule.error_module import ErrorDescriptor + log = logging.getLogger("edx.student") AUDIT_LOG = logging.getLogger("audit") @@ -657,13 +658,16 @@ def change_enrollment(request): return HttpResponseBadRequest(_("Enrollment action is invalid")) +# TODO: This function is kind of gnarly/hackish/etc and is only used in one location. +# It'd be awesome if we could get rid of it; manually parsing course_id strings form larger strings +# seems Probably Incorrect def _parse_course_id_from_string(input_str): """ Helper function to determine if input_str (typically the queryparam 'next') contains a course_id. @param input_str: @return: the course_id if found, None if not """ - m_obj = re.match(r'^/courses/(?P[^/]+/[^/]+/[^/]+)', input_str) + m_obj = re.match(r'^/courses/{}'.format(settings.COURSE_ID_PATTERN), input_str) if m_obj: return SlashSeparatedCourseKey.from_deprecated_string(m_obj.group('course_id')) return None diff --git a/common/djangoapps/util/request.py b/common/djangoapps/util/request.py index 3192261bfa..d99962a76d 100644 --- a/common/djangoapps/util/request.py +++ b/common/djangoapps/util/request.py @@ -5,7 +5,8 @@ from django.conf import settings from microsite_configuration import microsite from opaque_keys.edx.locations import SlashSeparatedCourseKey -COURSE_REGEX = re.compile(r'^.*?/courses/(?P[^/]+/[^/]+/[^/]+)') + +COURSE_REGEX = re.compile(r'^.*?/courses/{}'.format(settings.COURSE_ID_PATTERN)) def safe_get_host(request): diff --git a/lms/djangoapps/class_dashboard/urls.py b/lms/djangoapps/class_dashboard/urls.py index 24198260e3..fbb3eaf207 100644 --- a/lms/djangoapps/class_dashboard/urls.py +++ b/lms/djangoapps/class_dashboard/urls.py @@ -3,17 +3,19 @@ Class Dashboard API endpoint urls. """ from django.conf.urls import patterns, url +from django.conf import settings +COURSE_ID_PATTERN = settings.COURSE_ID_PATTERN urlpatterns = patterns('', # nopep8 # Json request data for metrics for entire course - url(r'^(?P[^/]+/[^/]+/[^/]+)/all_sequential_open_distrib$', + url(r'^{}/all_sequential_open_distrib$'.format(settings.COURSE_ID_PATTERN), 'class_dashboard.views.all_sequential_open_distrib', name="all_sequential_open_distrib"), - url(r'^(?P[^/]+/[^/]+/[^/]+)/all_problem_grade_distribution$', + url(r'^{}/all_problem_grade_distribution$'.format(settings.COURSE_ID_PATTERN), 'class_dashboard.views.all_problem_grade_distribution', name="all_problem_grade_distribution"), # Json request data for metrics for particular section - url(r'^(?P[^/]+/[^/]+/[^/]+)/problem_grade_distribution/(?P
\d+)$', + url(r'^{}/problem_grade_distribution/(?P
\d+)$'.format(settings.COURSE_ID_PATTERN), 'class_dashboard.views.section_problem_grade_distrib', name="section_problem_grade_distrib"), # For listing students that opened a sub-section diff --git a/lms/djangoapps/courseware/urls.py b/lms/djangoapps/courseware/urls.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lms/djangoapps/instructor/views/legacy.py b/lms/djangoapps/instructor/views/legacy.py index 4dd7d355af..5228f020f7 100644 --- a/lms/djangoapps/instructor/views/legacy.py +++ b/lms/djangoapps/instructor/views/legacy.py @@ -1033,7 +1033,7 @@ def instructor_dashboard(request, course_id): 'course_errors': modulestore().get_course_errors(course.id), 'instructor_tasks': instructor_tasks, 'offline_grade_log': offline_grades_available(course_key), - 'cohorts_ajax_url': reverse('cohorts', kwargs={'course_key': course_key.to_deprecated_string()}), + 'cohorts_ajax_url': reverse('cohorts', kwargs={'course_key_string': course_key.to_deprecated_string()}), 'analytics_results': analytics_results, 'disable_buttons': disable_buttons, diff --git a/lms/djangoapps/shoppingcart/urls.py b/lms/djangoapps/shoppingcart/urls.py index b9eb7eb7a5..2dd083c88c 100644 --- a/lms/djangoapps/shoppingcart/urls.py +++ b/lms/djangoapps/shoppingcart/urls.py @@ -13,7 +13,7 @@ if settings.FEATURES['ENABLE_SHOPPING_CART']: url(r'^$', 'show_cart'), url(r'^clear/$', 'clear_cart'), url(r'^remove_item/$', 'remove_item'), - url(r'^add/course/(?P[^/]+/[^/]+/[^/]+)/$', 'add_course_to_cart', name='add_course_to_cart'), + url(r'^add/course/{}/$'.format(settings.COURSE_ID_PATTERN), 'add_course_to_cart', name='add_course_to_cart'), ) if settings.FEATURES.get('ENABLE_PAYMENT_FAKE'): diff --git a/lms/djangoapps/verify_student/urls.py b/lms/djangoapps/verify_student/urls.py index 22f779fcd7..9ee8e939bf 100644 --- a/lms/djangoapps/verify_student/urls.py +++ b/lms/djangoapps/verify_student/urls.py @@ -2,22 +2,28 @@ from django.conf.urls import patterns, url from verify_student import views +from django.conf import settings + + urlpatterns = patterns( '', url( - r'^show_requirements/(?P[^/]+/[^/]+/[^/]+)/$', + r'^show_requirements/{}/$'.format(settings.COURSE_ID_PATTERN), views.show_requirements, name="verify_student_show_requirements" ), + # pylint sometimes seems to dislike the as_view() function because as_view() is + # decorated with `classonlymethod` instead of `classmethod`. It's inconsistent + # about *which* as_view() calls it grumbles about, but we disable those warnings url( - r'^verify/(?P[^/]+/[^/]+/[^/]+)/$', - views.VerifyView.as_view(), # pylint: disable=E1120 + r'^verify/{}/$'.format(settings.COURSE_ID_PATTERN), + views.VerifyView.as_view(), # pylint: disable=no-value-for-parameter name="verify_student_verify" ), url( - r'^verified/(?P[^/]+/[^/]+/[^/]+)/$', + r'^verified/{}/$'.format(settings.COURSE_ID_PATTERN), views.VerifiedView.as_view(), name="verify_student_verified" ), @@ -41,8 +47,8 @@ urlpatterns = patterns( ), url( - r'^midcourse_reverify/(?P[^/]+/[^/]+/[^/]+)/$', - views.MidCourseReverifyView.as_view(), # pylint: disable=E1120 + r'^midcourse_reverify/{}/$'.format(settings.COURSE_ID_PATTERN), + views.MidCourseReverifyView.as_view(), # pylint: disable=no-value-for-parameter name="verify_student_midcourse_reverify" ), diff --git a/lms/envs/common.py b/lms/envs/common.py index 7718ada4ca..533337ffd3 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -399,6 +399,14 @@ COURSE_SETTINGS = { # TODO (vshnayder): Will probably need to change as we get real access control in. LMS_MIGRATION_ALLOWED_IPS = [] +# These are standard regexes for pulling out info like course_ids, usage_ids, etc. +# They are used so that URLs with deprecated-format strings still work. +COURSE_ID_PATTERN = r'(?P(?:[^/]+/[^/]+/[^/]+)|(?:[^/]+))' +COURSE_KEY_PATTERN = r'(?P(?:[^/]+/[^/]+/[^/]+)|(?:[^/]+))' +USAGE_KEY_PATTERN = r'(?P(?:i4x://?[^/]+/[^/]+/[^/]+/[^@]+(?:@[^/]+)?)|(?:[^/]+))' +ASSET_KEY_PATTERN = r'(?P(?:/?c4x(:/)?/[^/]+/[^/]+/[^/]+/[^@]+(?:@[^/]+)?)|(?:[^/]+))' +USAGE_ID_PATTERN = r'(?P(?:i4x://?[^/]+/[^/]+/[^/]+/[^@]+(?:@[^/]+)?)|(?:[^/]+))' + ############################## EVENT TRACKING ################################# diff --git a/lms/urls.py b/lms/urls.py index 063be31b55..a112daea5b 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -179,22 +179,21 @@ if settings.WIKI_ENABLED: # These urls are for viewing the wiki in the context of a course. They should # never be returned by a reverse() so they come after the other url patterns - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/course_wiki/?$', + url(r'^courses/{}/course_wiki/?$'.format(settings.COURSE_ID_PATTERN), 'course_wiki.views.course_wiki_redirect', name="course_wiki"), url(r'^courses/(?:[^/]+/[^/]+/[^/]+)/wiki/', include(wiki_pattern())), ) - if settings.COURSEWARE_ENABLED: urlpatterns += ( - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/jump_to/(?P.*)$', + url(r'^courses/{}/jump_to/(?P.*)$'.format(settings.COURSE_ID_PATTERN), 'courseware.views.jump_to', name="jump_to"), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/jump_to_id/(?P.*)$', + url(r'^courses/{}/jump_to_id/(?P.*)$'.format(settings.COURSE_ID_PATTERN), 'courseware.views.jump_to_id', name="jump_to_id"), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/xblock/(?P[^/]*)/handler/(?P[^/]*)(?:/(?P.*))?$', + url(r'^courses/{course_key}/xblock/{usage_key}/handler/(?P[^/]*)(?:/(?P.*))?$'.format(course_key=settings.COURSE_ID_PATTERN, usage_key=settings.USAGE_ID_PATTERN), 'courseware.module_render.handle_xblock_callback', name='xblock_handler'), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/xblock/(?P[^/]*)/handler_noauth/(?P[^/]*)(?:/(?P.*))?$', + url(r'^courses/{course_key}/xblock/{usage_key}/handler_noauth/(?P[^/]*)(?:/(?P.*))?$'.format(course_key=settings.COURSE_ID_PATTERN, usage_key=settings.USAGE_ID_PATTERN), 'courseware.module_render.handle_xblock_callback_noauth', name='xblock_handler_noauth'), url(r'xblock/resource/(?P[^/]+)/(?P.*)$', @@ -209,7 +208,7 @@ if settings.COURSEWARE_ENABLED: # into the database. url(r'^software-licenses$', 'licenses.views.user_software_license', name="user_software_license"), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/xqueue/(?P[^/]*)/(?P.*?)/(?P[^/]*)$', + url(r'^courses/{}/xqueue/(?P[^/]*)/(?P.*?)/(?P[^/]*)$'.format(settings.COURSE_ID_PATTERN), 'courseware.module_render.xqueue_callback', name='xqueue_callback'), url(r'^change_setting$', 'student.views.change_setting', @@ -227,117 +226,117 @@ if settings.COURSEWARE_ENABLED: url(r'^change_email_settings$', 'student.views.change_email_settings', name="change_email_settings"), #About the course - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/about$', + url(r'^courses/{}/about$'.format(settings.COURSE_ID_PATTERN), 'courseware.views.course_about', name="about_course"), #View for mktg site (kept for backwards compatibility TODO - remove before merge to master) - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/mktg-about$', + url(r'^courses/{}/mktg-about$'.format(settings.COURSE_ID_PATTERN), 'courseware.views.mktg_course_about', name="mktg_about_course"), #View for mktg site - url(r'^mktg/(?P[^/]+/[^/]+/[^/]+)/?$', + url(r'^mktg/{}/?$'.format(settings.COURSE_ID_PATTERN), 'courseware.views.mktg_course_about', name="mktg_about_course"), #Inside the course - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/$', + url(r'^courses/{}/$'.format(settings.COURSE_ID_PATTERN), 'courseware.views.course_info', name="course_root"), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/info$', + url(r'^courses/{}/info$'.format(settings.COURSE_ID_PATTERN), 'courseware.views.course_info', name="info"), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/syllabus$', + url(r'^courses/{}/syllabus$'.format(settings.COURSE_ID_PATTERN), 'courseware.views.syllabus', name="syllabus"), # TODO arjun remove when custom tabs in place, see courseware/courses.py - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/book/(?P\d+)/$', + url(r'^courses/{}/book/(?P\d+)/$'.format(settings.COURSE_ID_PATTERN), 'staticbook.views.index', name="book"), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/book/(?P\d+)/(?P\d+)$', + url(r'^courses/{}/book/(?P\d+)/(?P\d+)$'.format(settings.COURSE_ID_PATTERN), 'staticbook.views.index', name="book"), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/pdfbook/(?P\d+)/$', + url(r'^courses/{}/pdfbook/(?P\d+)/$'.format(settings.COURSE_ID_PATTERN), 'staticbook.views.pdf_index', name="pdf_book"), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/pdfbook/(?P\d+)/(?P\d+)$', + url(r'^courses/{}/pdfbook/(?P\d+)/(?P\d+)$'.format(settings.COURSE_ID_PATTERN), 'staticbook.views.pdf_index', name="pdf_book"), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/pdfbook/(?P\d+)/chapter/(?P\d+)/$', + url(r'^courses/{}/pdfbook/(?P\d+)/chapter/(?P\d+)/$'.format(settings.COURSE_ID_PATTERN), 'staticbook.views.pdf_index', name="pdf_book"), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/pdfbook/(?P\d+)/chapter/(?P\d+)/(?P\d+)$', + url(r'^courses/{}/pdfbook/(?P\d+)/chapter/(?P\d+)/(?P\d+)$'.format(settings.COURSE_ID_PATTERN), 'staticbook.views.pdf_index', name="pdf_book"), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/htmlbook/(?P\d+)/$', + url(r'^courses/{}/htmlbook/(?P\d+)/$'.format(settings.COURSE_ID_PATTERN), 'staticbook.views.html_index', name="html_book"), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/htmlbook/(?P\d+)/chapter/(?P\d+)/$', + url(r'^courses/{}/htmlbook/(?P\d+)/chapter/(?P\d+)/$'.format(settings.COURSE_ID_PATTERN), 'staticbook.views.html_index', name="html_book"), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/courseware/?$', + url(r'^courses/{}/courseware/?$'.format(settings.COURSE_ID_PATTERN), 'courseware.views.index', name="courseware"), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/courseware/(?P[^/]*)/$', + url(r'^courses/{}/courseware/(?P[^/]*)/$'.format(settings.COURSE_ID_PATTERN), 'courseware.views.index', name="courseware_chapter"), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/courseware/(?P[^/]*)/(?P
[^/]*)/$', + url(r'^courses/{}/courseware/(?P[^/]*)/(?P
[^/]*)/$'.format(settings.COURSE_ID_PATTERN), 'courseware.views.index', name="courseware_section"), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/courseware/(?P[^/]*)/(?P
[^/]*)/(?P[^/]*)/?$', + url(r'^courses/{}/courseware/(?P[^/]*)/(?P
[^/]*)/(?P[^/]*)/?$'.format(settings.COURSE_ID_PATTERN), 'courseware.views.index', name="courseware_position"), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/progress$', + url(r'^courses/{}/progress$'.format(settings.COURSE_ID_PATTERN), 'courseware.views.progress', name="progress"), # Takes optional student_id for instructor use--shows profile as that student sees it. - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/progress/(?P[^/]*)/$', + url(r'^courses/{}/progress/(?P[^/]*)/$'.format(settings.COURSE_ID_PATTERN), 'courseware.views.progress', name="student_progress"), # For the instructor - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/instructor$', + url(r'^courses/{}/instructor$'.format(settings.COURSE_ID_PATTERN), 'instructor.views.instructor_dashboard.instructor_dashboard_2', name="instructor_dashboard"), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/instructor/api/', + url(r'^courses/{}/instructor/api/'.format(settings.COURSE_ID_PATTERN), include('instructor.views.api_urls')), # see ENABLE_INSTRUCTOR_LEGACY_DASHBOARD section for legacy dash urls # Open Ended grading views - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/staff_grading$', + url(r'^courses/{}/staff_grading$'.format(settings.COURSE_ID_PATTERN), 'open_ended_grading.views.staff_grading', name='staff_grading'), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/staff_grading/get_next$', + url(r'^courses/{}/staff_grading/get_next$'.format(settings.COURSE_ID_PATTERN), 'open_ended_grading.staff_grading_service.get_next', name='staff_grading_get_next'), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/staff_grading/save_grade$', + url(r'^courses/{}/staff_grading/save_grade$'.format(settings.COURSE_ID_PATTERN), 'open_ended_grading.staff_grading_service.save_grade', name='staff_grading_save_grade'), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/staff_grading/get_problem_list$', + url(r'^courses/{}/staff_grading/get_problem_list$'.format(settings.COURSE_ID_PATTERN), 'open_ended_grading.staff_grading_service.get_problem_list', name='staff_grading_get_problem_list'), # Open Ended problem list - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/open_ended_problems$', + url(r'^courses/{}/open_ended_problems$'.format(settings.COURSE_ID_PATTERN), 'open_ended_grading.views.student_problem_list', name='open_ended_problems'), # Open Ended flagged problem list - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/open_ended_flagged_problems$', + url(r'^courses/{}/open_ended_flagged_problems$'.format(settings.COURSE_ID_PATTERN), 'open_ended_grading.views.flagged_problem_list', name='open_ended_flagged_problems'), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/open_ended_flagged_problems/take_action_on_flags$', + url(r'^courses/{}/open_ended_flagged_problems/take_action_on_flags$'.format(settings.COURSE_ID_PATTERN), 'open_ended_grading.views.take_action_on_flags', name='open_ended_flagged_problems_take_action'), # Cohorts management - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/cohorts$', + url(r'^courses/{}/cohorts$'.format(settings.COURSE_KEY_PATTERN), 'course_groups.views.list_cohorts', name="cohorts"), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/cohorts/add$', + url(r'^courses/{}/cohorts/add$'.format(settings.COURSE_KEY_PATTERN), 'course_groups.views.add_cohort', name="add_cohort"), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/cohorts/(?P[0-9]+)$', + url(r'^courses/{}/cohorts/(?P[0-9]+)$'.format(settings.COURSE_KEY_PATTERN), 'course_groups.views.users_in_cohort', name="list_cohort"), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/cohorts/(?P[0-9]+)/add$', + url(r'^courses/{}/cohorts/(?P[0-9]+)/add$'.format(settings.COURSE_KEY_PATTERN), 'course_groups.views.add_users_to_cohort', name="add_to_cohort"), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/cohorts/(?P[0-9]+)/delete$', + url(r'^courses/{}/cohorts/(?P[0-9]+)/delete$'.format(settings.COURSE_KEY_PATTERN), 'course_groups.views.remove_user_from_cohort', name="remove_from_cohort"), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/cohorts/debug$', + url(r'^courses/{}/cohorts/debug$'.format(settings.COURSE_KEY_PATTERN), 'course_groups.views.debug_cohort_mgmt', name="debug_cohort_mgmt"), # Open Ended Notifications - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/open_ended_notifications$', + url(r'^courses/{}/open_ended_notifications$'.format(settings.COURSE_ID_PATTERN), 'open_ended_grading.views.combined_notifications', name='open_ended_notifications'), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/peer_grading$', + url(r'^courses/{}/peer_grading$'.format(settings.COURSE_ID_PATTERN), 'open_ended_grading.views.peer_grading', name='peer_grading'), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/notes$', 'notes.views.notes', name='notes'), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/notes/', include('notes.urls')), + url(r'^courses/{}/notes$'.format(settings.COURSE_ID_PATTERN), 'notes.views.notes', name='notes'), + url(r'^courses/{}/notes/'.format(settings.COURSE_ID_PATTERN), include('notes.urls')), # LTI endpoints listing - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/lti_rest_endpoints/', + url(r'^courses/{}/lti_rest_endpoints/'.format(settings.COURSE_ID_PATTERN), 'courseware.views.get_course_lti_endpoints', name='lti_rest_endpoints'), ) @@ -350,7 +349,7 @@ if settings.COURSEWARE_ENABLED: # discussion forums live within courseware, so courseware must be enabled first if settings.FEATURES.get('ENABLE_DISCUSSION_SERVICE'): urlpatterns += ( - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/discussion/', + url(r'^courses/{}/discussion/'.format(settings.COURSE_ID_PATTERN), include('django_comment_client.urls')), url(r'^notification_prefs/enable/', 'notification_prefs.views.ajax_enable'), url(r'^notification_prefs/disable/', 'notification_prefs.views.ajax_disable'), @@ -362,13 +361,13 @@ if settings.COURSEWARE_ENABLED: ) urlpatterns += ( # This MUST be the last view in the courseware--it's a catch-all for custom tabs. - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/(?P[^/]+)/$', + url(r'^courses/{}/(?P[^/]+)/$'.format(settings.COURSE_ID_PATTERN), 'courseware.views.static_tab', name="static_tab"), ) if settings.FEATURES.get('ENABLE_STUDENT_HISTORY_VIEW'): urlpatterns += ( - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/submission_history/(?P[^/]*)/(?P.*?)$', + url(r'^courses/{}/submission_history/(?P[^/]*)/(?P.*?)$'.format(settings.COURSE_ID_PATTERN), 'courseware.views.submission_history', name='submission_history'), ) @@ -376,9 +375,9 @@ if settings.COURSEWARE_ENABLED: if settings.COURSEWARE_ENABLED and settings.FEATURES.get('ENABLE_INSTRUCTOR_LEGACY_DASHBOARD'): urlpatterns += ( - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/legacy_grade_summary$', + url(r'^courses/{}/legacy_grade_summary$'.format(settings.COURSE_ID_PATTERN), 'instructor.views.legacy.grade_summary', name='grade_summary_legacy'), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/legacy_instructor_dash$', + url(r'^courses/{}/legacy_instructor_dash$'.format(settings.COURSE_ID_PATTERN), 'instructor.views.legacy.instructor_dashboard', name="instructor_dashboard_legacy"), ) @@ -411,9 +410,9 @@ if settings.FEATURES.get('AUTH_USE_CAS'): if settings.FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD'): urlpatterns += ( - url(r'^course_specific_login/(?P[^/]+/[^/]+/[^/]+)/$', + url(r'^course_specific_login/{}/$'.format(settings.COURSE_ID_PATTERN), 'external_auth.views.course_specific_login', name='course-specific-login'), - url(r'^course_specific_register/(?P[^/]+/[^/]+/[^/]+)/$', + url(r'^course_specific_register/{}/$'.format(settings.COURSE_ID_PATTERN), 'external_auth.views.course_specific_register', name='course-specific-register'), ) @@ -477,7 +476,7 @@ if settings.FEATURES.get('ENABLE_DEBUG_RUN_PYTHON'): # Crowdsourced hinting instructor manager. if settings.FEATURES.get('ENABLE_HINTER_INSTRUCTOR_VIEW'): urlpatterns += ( - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/hint_manager$', + url(r'^courses/{}/hint_manager$'.format(settings.COURSE_ID_PATTERN), 'instructor.hint_manager.hint_manager', name="hint_manager"), )