Commit 26a63850 by Victor Shnayder

Merge pull request #733 from MITx/feature/victor/static-tabs

Feature/victor/static tabs
parents fc61a456 621acd20
......@@ -247,7 +247,7 @@ def load_preview_module(request, preview_id, descriptor, instance_state, shared_
module = descriptor.xmodule_constructor(system)(instance_state, shared_state)
module.get_html = replace_static_urls(
wrap_xmodule(module.get_html, module, "xmodule_display.html"),
module.metadata['data_dir'], module
module.metadata['data_dir']
)
save_preview_state(request, preview_id, descriptor.location.url(),
module.get_instance_state(), module.get_shared_state())
......
......@@ -35,7 +35,7 @@ def wrap_xmodule(get_html, module, template):
return _get_html
def replace_course_urls(get_html, course_id, module):
def replace_course_urls(get_html, course_id):
"""
Updates the supplied module with a new get_html function that wraps
the old get_html function and substitutes urls of the form /course/...
......@@ -46,7 +46,7 @@ def replace_course_urls(get_html, course_id, module):
return replace_urls(get_html(), staticfiles_prefix='/courses/'+course_id, replace_prefix='/course/')
return _get_html
def replace_static_urls(get_html, prefix, module):
def replace_static_urls(get_html, prefix):
"""
Updates the supplied module with a new get_html function that wraps
the old get_html function and substitutes urls of the form /static/...
......
......@@ -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:
......
......@@ -254,12 +254,11 @@ def _get_module(user, request, location, student_module_cache, course_id, positi
module.get_html = replace_static_urls(
wrap_xmodule(module.get_html, module, 'xmodule_display.html'),
module.metadata['data_dir'], module
)
module.metadata['data_dir'])
# Allow URLs of the form '/course/' refer to the root of multicourse directory
# hierarchy of this course
module.get_html = replace_course_urls(module.get_html, course_id, module)
module.get_html = replace_course_urls(module.get_html, course_id)
if settings.MITX_FEATURES.get('DISPLAY_HISTOGRAMS_TO_STAFF'):
if has_access(user, module, 'staff'):
......
......@@ -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,41 @@ def get_default_tabs(user, course, active_page):
tabs.append(CourseTab('Instructor', link, active_page=='instructor'))
return tabs
def get_static_tab_by_slug(course, 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.
"""
if course.tabs is None:
return None
for tab in course.tabs:
# The validation code checks that these exist.
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):
try:
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/')
except (ResourceNotFoundError) as err:
log.exception("Couldn't load tab contents from '{0}': {1}".format(p, err))
return None
return None
......@@ -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, 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:
......
<%inherit file="/main.html" />
<%namespace name='static' file='/static_content.html'/>
<%block name="headextra">
<%static:css group='course'/>
</%block>
<%block name="title"><title>${course.number} ${tab['name']}</title></%block>
<%include file="/courseware/course_navigation.html" args="active_page='static_tab_{0}'.format(tab['url_slug'])" />
<section class="container">
<div class="static_tab_wrapper">
${tab_contents}
</div>
</section>
......@@ -97,6 +97,33 @@ urlpatterns = ('',
if settings.PERFSTATS:
urlpatterns += (url(r'^reprofile$','perfstats.views.end_profile'),)
# Multicourse wiki (Note: wiki urls must be above the courseware ones because of
# the custom tab catch-all)
if settings.WIKI_ENABLED:
from wiki.urls import get_pattern as wiki_pattern
from django_notify.urls import get_pattern as notify_pattern
# Note that some of these urls are repeated in course_wiki.course_nav. Make sure to update
# them together.
urlpatterns += (
# First we include views from course_wiki that we use to override the default views.
# They come first in the urlpatterns so they get resolved first
url('^wiki/create-root/$', 'course_wiki.views.root_create', name='root_create'),
url(r'^wiki/', include(wiki_pattern())),
url(r'^notify/', include(notify_pattern())),
# 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_id>[^/]+/[^/]+/[^/]+)/course_wiki/?$',
'course_wiki.views.course_wiki_redirect', name="course_wiki"),
url(r'^courses/(?:[^/]+/[^/]+/[^/]+)/wiki/', include(wiki_pattern())),
)
if settings.COURSEWARE_ENABLED:
urlpatterns += (
# Hook django-masquerade, allowing staff to view site as other users
......@@ -164,6 +191,10 @@ if settings.COURSEWARE_ENABLED:
'instructor.views.grade_summary', name='grade_summary'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/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<course_id>[^/]+/[^/]+/[^/]+)/(?P<tab_slug>.*)$',
'courseware.views.static_tab', name="static_tab"),
)
# discussion forums live within courseware, so courseware must be enabled first
......@@ -176,29 +207,6 @@ if settings.COURSEWARE_ENABLED:
include('django_comment_client.urls'))
)
# Multicourse wiki
if settings.WIKI_ENABLED:
from wiki.urls import get_pattern as wiki_pattern
from django_notify.urls import get_pattern as notify_pattern
# Note that some of these urls are repeated in course_wiki.course_nav. Make sure to update
# them together.
urlpatterns += (
# First we include views from course_wiki that we use to override the default views.
# They come first in the urlpatterns so they get resolved first
url('^wiki/create-root/$', 'course_wiki.views.root_create', name='root_create'),
url(r'^wiki/', include(wiki_pattern())),
url(r'^notify/', include(notify_pattern())),
# 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_id>[^/]+/[^/]+/[^/]+)/course_wiki/?$',
'course_wiki.views.course_wiki_redirect', name="course_wiki"),
url(r'^courses/(?:[^/]+/[^/]+/[^/]+)/wiki/', include(wiki_pattern())),
)
if settings.QUICKEDIT:
urlpatterns += (url(r'^quickedit/(?P<id>[^/]*)$', 'dogfood.views.quickedit'),)
urlpatterns += (url(r'^dogfood/(?P<id>[^/]*)$', 'dogfood.views.df_capa_problem'),)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment