From 7a88223ef7958cfffe8df2dcd67c6620f105d647 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 20 Sep 2012 00:51:23 -0400 Subject: [PATCH] custom tabs * still needs better error checking and testing --- doc/xml-format.md | 5 +++- lms/djangoapps/courseware/tabs.py | 37 ++++++++++++++++++++++++ lms/djangoapps/courseware/views.py | 26 +++++++++++++++++ lms/templates/courseware/static_tab.html | 16 ++++++++++ lms/urls.py | 4 +++ 5 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 lms/templates/courseware/static_tab.html diff --git a/doc/xml-format.md b/doc/xml-format.md index e7919b85d5..9d07f432c9 100644 --- a/doc/xml-format.md +++ b/doc/xml-format.md @@ -365,12 +365,15 @@ If you want to customize the courseware tabs displayed for your course, specify {"type": "external_link", "name": "My Discussion", "link": "http://www.mydiscussion.org/blah"}, {"type": "progress", "name": "Progress"}, {"type": "wiki", "name": "Wonderwiki"}, + {"type": "static_tab", "url_slug": "news", "name": "Exciting news"}, {"type": "textbooks"} # generates one tab per textbook, taking names from the textbook titles ] * If you specify any tabs, you must specify all tabs. They will appear in the order given. -* The first two tabs must have types "courseware" and "course_info", in that order. Otherwise, we'll refuse to load the course. +* The first two tabs must have types `"courseware"` and `"course_info"`, in that order. Otherwise, we'll refuse to load the course. +* for static tabs, the url_slug will be the url that points to the tab. It can not be one of the existing courseware url types (even if those aren't used in your course). The static content will come from `tabs/{course_url_name}/{url_slug}.html`, or `tabs/{url_slug}.html` if that doesn't exist. + * An Instructor tab will be automatically added at the end for course staff users. ## Supported tab types: diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index 8ecd8137df..2dfa7afed3 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -16,6 +16,7 @@ from django.conf import settings from django.core.urlresolvers import reverse from courseware.access import has_access +from static_replace import replace_urls log = logging.getLogger(__name__) @@ -77,6 +78,11 @@ def _external_link(tab, user, course, active_page): # external links are never active return [CourseTab(tab['name'], tab['link'], False)] +def _static_tab(tab, user, course, active_page): + link = reverse('static_tab', args=[course.id, tab['url_slug']]) + active_str = 'static_tab_{0}'.format(tab['url_slug']) + return [CourseTab(tab['name'], link, active_page==active_str)] + def _textbooks(tab, user, course, active_page): """ @@ -123,6 +129,7 @@ VALID_TAB_TYPES = { 'external_link': TabImpl(key_checker(['name', 'link']), _external_link), 'textbooks': TabImpl(null_validator, _textbooks), 'progress': TabImpl(need_name, _progress), + 'static_tab': TabImpl(key_checker(['name', 'url_slug']), _static_tab), } @@ -227,3 +234,33 @@ def get_default_tabs(user, course, active_page): tabs.append(CourseTab('Instructor', link, active_page=='instructor')) return tabs + +def get_static_tab_by_slug(tabs, tab_slug): + """ + Look for a tab with type 'static_tab' and the specified 'tab_slug'. Returns + the tab (a config dict), or None if not found. + """ + for tab in tabs: + # if the tab is misconfigured, this will blow up. The validation code should check... + if tab['type'] == 'static_tab' and tab['url_slug'] == tab_slug: + return tab + + return None + + +def get_static_tab_contents(course, tab): + """ + Given a course and a static tab config dict, load the tab contents, + returning None if not found. + + Looks in tabs/{course_url_name}/{tab_slug}.html first, then tabs/{tab_slug}.html. + """ + slug = tab['url_slug'] + paths = ['tabs/{0}/{1}.html'.format(course.url_name, slug), 'tabs/{0}.html'.format(slug)] + fs = course.system.resources_fs + for p in paths: + if fs.exists(p): + with fs.open(p) as tabfile: + # TODO: redundant with module_render.py. Want to be helper methods in static_replace or something. + contents = replace_urls(tabfile.read(), course.metadata['data_dir']) + return replace_urls(contents, staticfiles_prefix='/courses/'+course.id, replace_prefix='/course/') diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index 7da5d06741..b03c1c932e 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -22,6 +22,7 @@ from django.views.decorators.cache import cache_control from courseware import grades from courseware.access import has_access from courseware.courses import (get_course_with_access, get_courses_by_university) +import courseware.tabs as tabs from models import StudentModuleCache from module_render import toc_for_course, get_module, get_instance_module from student.models import UserProfile @@ -343,6 +344,30 @@ def course_info(request, course_id): return render_to_response('courseware/info.html', {'course': course, 'staff_access': staff_access,}) +@ensure_csrf_cookie +def static_tab(request, course_id, tab_slug): + """ + Display the courses tab with the given name. + + Assumes the course_id is in a valid format. + """ + course = get_course_with_access(request.user, course_id, 'load') + + tab = tabs.get_static_tab_by_slug(course.tabs, tab_slug) + if tab is None: + raise Http404 + + contents = tabs.get_static_tab_contents(course, tab) + if contents is None: + raise Http404 + + staff_access = has_access(request.user, course, 'staff') + return render_to_response('courseware/static_tab.html', + {'course': course, + 'tab': tab, + 'tab_contents': contents, + 'staff_access': staff_access,}) + # TODO arjun: remove when custom tabs in place, see courseware/syllabus.py @ensure_csrf_cookie def syllabus(request, course_id): @@ -357,6 +382,7 @@ def syllabus(request, course_id): return render_to_response('courseware/syllabus.html', {'course': course, 'staff_access': staff_access,}) + def registered_for_course(course, user): '''Return CourseEnrollment if user is registered for course, else False''' if user is None: diff --git a/lms/templates/courseware/static_tab.html b/lms/templates/courseware/static_tab.html new file mode 100644 index 0000000000..7a832d6a4c --- /dev/null +++ b/lms/templates/courseware/static_tab.html @@ -0,0 +1,16 @@ +<%inherit file="/main.html" /> +<%namespace name='static' file='/static_content.html'/> + +<%block name="headextra"> + <%static:css group='course'/> + + +<%block name="title">${course.number} ${tab['name']} + +<%include file="/courseware/course_navigation.html" args="active_page='static_tab_{0}'.format(tab['url_slug'])" /> + +
+
+ ${tab_contents} +
+
diff --git a/lms/urls.py b/lms/urls.py index 49febaf84e..6741113507 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -164,6 +164,10 @@ if settings.COURSEWARE_ENABLED: 'instructor.views.grade_summary', name='grade_summary'), url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/enroll_students$', 'instructor.views.enroll_students', name='enroll_students'), + + # This MUST be the last view in the courseware--it's a catch-all for custom tabs. + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/(?P.*)$', + 'courseware.views.static_tab', name="static_tab"), ) # discussion forums live within courseware, so courseware must be enabled first