Commit 8fedc08c by cahrens Committed by Andy Armstrong

Support web fragments for tabs.

parent d439da44
...@@ -7,6 +7,8 @@ import logging ...@@ -7,6 +7,8 @@ import logging
from xblock.fields import List from xblock.fields import List
from openedx.core.lib.api.plugins import PluginError from openedx.core.lib.api.plugins import PluginError
from django.core.files.storage import get_storage_class
log = logging.getLogger("edx.courseware") log = logging.getLogger("edx.courseware")
# Make '_' a no-op so we can scrape strings. Using lambda instead of # Make '_' a no-op so we can scrape strings. Using lambda instead of
...@@ -71,11 +73,15 @@ class CourseTab(object): ...@@ -71,11 +73,15 @@ class CourseTab(object):
self.name = tab_dict.get('name', self.title) self.name = tab_dict.get('name', self.title)
self.tab_id = tab_dict.get('tab_id', getattr(self, 'tab_id', self.type)) self.tab_id = tab_dict.get('tab_id', getattr(self, 'tab_id', self.type))
self.link_func = tab_dict.get('link_func', link_reverse_func(self.view_name))
self.course_staff_only = tab_dict.get('course_staff_only', False) self.course_staff_only = tab_dict.get('course_staff_only', False)
self.is_hidden = tab_dict.get('is_hidden', False) self.is_hidden = tab_dict.get('is_hidden', False)
self.tab_dict = tab_dict
@property
def link_func(self):
return self.tab_dict.get('link_func', link_reverse_func(self.view_name))
@classmethod @classmethod
def is_enabled(cls, course, user=None): def is_enabled(cls, course, user=None):
"""Returns true if this course tab is enabled in the course. """Returns true if this course tab is enabled in the course.
...@@ -230,6 +236,30 @@ class CourseTab(object): ...@@ -230,6 +236,30 @@ class CourseTab(object):
return tab_type(tab_dict=tab_dict) return tab_type(tab_dict=tab_dict)
class ComponentTabMixin(object):
"""
A mixin for tabs that meet the component API (and can be rendered via Fragments).
"""
class_name = None
@property
def link_func(self):
def link_func(course, reverse_func):
""" Returns a url for a given course and reverse function. """
return reverse_func("content_tab", args=[course.id.to_deprecated_string(), self.type])
return link_func
@property
def url_slug(self):
return "content_tab/"+self.type
def render_fragment(self, request, course):
component = get_storage_class(self.class_name)()
fragment = component.render_component(request, course_id=course.id.to_deprecated_string())
return fragment
class StaticTab(CourseTab): class StaticTab(CourseTab):
""" """
A custom tab. A custom tab.
......
...@@ -9,8 +9,7 @@ from courseware.access import has_access ...@@ -9,8 +9,7 @@ from courseware.access import has_access
from courseware.entrance_exams import user_must_complete_entrance_exam from courseware.entrance_exams import user_must_complete_entrance_exam
from openedx.core.lib.course_tabs import CourseTabPluginManager from openedx.core.lib.course_tabs import CourseTabPluginManager
from student.models import CourseEnrollment from student.models import CourseEnrollment
from student.roles import CourseStaffRole from xmodule.tabs import ComponentTabMixin, CourseTab, CourseTabList, key_checker
from xmodule.tabs import CourseTab, CourseTabList, key_checker
class EnrolledTab(CourseTab): class EnrolledTab(CourseTab):
...@@ -71,14 +70,14 @@ class SyllabusTab(EnrolledTab): ...@@ -71,14 +70,14 @@ class SyllabusTab(EnrolledTab):
return getattr(course, 'syllabus_present', False) return getattr(course, 'syllabus_present', False)
class ProgressTab(EnrolledTab): class ProgressTab(ComponentTabMixin, EnrolledTab):
""" """
The course progress view. The course progress view.
""" """
type = 'progress' type = 'progress'
title = ugettext_noop('Progress') title = ugettext_noop('Progress')
priority = 40 priority = 40
view_name = 'progress' class_name="courseware.views.views.ProgressComponentView"
is_hideable = True is_hideable = True
is_default = False is_default = False
......
...@@ -13,6 +13,7 @@ from django.conf import settings ...@@ -13,6 +13,7 @@ from django.conf import settings
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User, AnonymousUser from django.contrib.auth.models import User, AnonymousUser
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.context_processors import csrf from django.core.context_processors import csrf
from django.db import transaction from django.db import transaction
...@@ -98,6 +99,9 @@ from xmodule.x_module import STUDENT_VIEW ...@@ -98,6 +99,9 @@ from xmodule.x_module import STUDENT_VIEW
from ..entrance_exams import user_must_complete_entrance_exam from ..entrance_exams import user_must_complete_entrance_exam
from ..module_render import get_module_for_descriptor, get_module, get_module_by_usage_id from ..module_render import get_module_for_descriptor, get_module, get_module_by_usage_id
from web_fragments.views import FragmentView
from web_fragments.fragment import Fragment
log = logging.getLogger("edx.courseware") log = logging.getLogger("edx.courseware")
...@@ -456,18 +460,45 @@ def static_tab(request, course_id, tab_slug): ...@@ -456,18 +460,45 @@ def static_tab(request, course_id, tab_slug):
if tab is None: if tab is None:
raise Http404 raise Http404
contents = get_static_tab_contents( fragment = get_static_tab_fragment(
request, request,
course, course,
tab tab
) )
if contents is None:
raise Http404
return render_to_response('courseware/static_tab.html', { return render_to_response('courseware/static_tab.html', {
'course': course, 'course': course,
'active_page': 'static_tab_{0}'.format(tab['url_slug']),
'tab': tab, 'tab': tab,
'tab_contents': contents, 'fragment': fragment,
'uses_pattern_library': False,
'disable_courseware_js': True
})
@ensure_csrf_cookie
@ensure_valid_course_key
def content_tab(request, course_id, tab_type):
"""
Display a content tab based on type name.
Assumes the course_id is in a valid format.
"""
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
course = get_course_with_access(request.user, 'load', course_key)
content_tab = [tab for tab in course.tabs if tab.type == tab_type][0]
fragment = content_tab.render_fragment(request, course)
return render_to_response('courseware/static_tab.html', {
'course': course,
'active_page': content_tab['type'],
'tab': content_tab,
'fragment': fragment,
'uses_pattern_library': True,
'disable_courseware_js': True
}) })
...@@ -718,6 +749,65 @@ def course_about(request, course_id): ...@@ -718,6 +749,65 @@ def course_about(request, course_id):
return render_to_response('courseware/course_about.html', context) return render_to_response('courseware/course_about.html', context)
class ProgressComponentView(FragmentView):
"""
Component implementation of the discussion board.
"""
def render_fragment(self, request, course_id=None):
"""
Render the component
"""
# nr_transaction = newrelic.agent.current_transaction()
#
course_key = CourseKey.from_string(course_id)
context = _create_progress_context(request, course_key)
html = render_to_string('discussion/discussion_board_component.html', context)
# # inline_js = render_to_string('discussion/discussion_board_js.template', context)
#
# fragment = Fragment(html)
# # fragment.add_javascript(inline_js)
fragment = Fragment()
fragment.content = "Hello World"
return fragment
def _create_progress_context(request, course_key):
course = get_course_with_access(request.user, 'load', course_key, depth=None, check_if_enrolled=True)
prep_course_for_grading(course, request)
staff_access = bool(has_access(request.user, 'staff', course))
student = request.user
# NOTE: To make sure impersonation by instructor works, use
# student instead of request.user in the rest of the function.
# The pre-fetching of groups is done to make auth checks not require an
# additional DB lookup (this kills the Progress page in particular).
student = User.objects.prefetch_related("groups").get(id=student.id)
course_grade = CourseGradeFactory().create(student, course)
courseware_summary = course_grade.chapter_grades
grade_summary = course_grade.summary
studio_url = get_studio_url(course, 'settings/grading')
# checking certificate generation configuration
enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user(student, course_key)
context = {
'course': course,
'courseware_summary': courseware_summary,
'studio_url': studio_url,
'grade_summary': grade_summary,
'staff_access': staff_access,
'student': student,
'passed': is_course_passed(course, grade_summary),
'credit_course_requirements': _credit_course_requirements(course_key, student),
'certificate_data': _get_cert_data(student, course, course_key, is_active, enrollment_mode)
}
return context
@transaction.non_atomic_requests @transaction.non_atomic_requests
@login_required @login_required
@cache_control(no_cache=True, no_store=True, must_revalidate=True) @cache_control(no_cache=True, no_store=True, must_revalidate=True)
...@@ -1056,9 +1146,9 @@ def submission_history(request, course_id, student_username, location): ...@@ -1056,9 +1146,9 @@ def submission_history(request, course_id, student_username, location):
return render_to_response('courseware/submission_history.html', context) return render_to_response('courseware/submission_history.html', context)
def get_static_tab_contents(request, course, tab): def get_static_tab_fragment(request, course, tab):
""" """
Returns the contents for the given static tab Returns the fragment for the given static tab
""" """
loc = course.id.make_usage_key( loc = course.id.make_usage_key(
tab.type, tab.type,
...@@ -1073,17 +1163,17 @@ def get_static_tab_contents(request, course, tab): ...@@ -1073,17 +1163,17 @@ def get_static_tab_contents(request, course, tab):
logging.debug('course_module = %s', tab_module) logging.debug('course_module = %s', tab_module)
html = '' fragment = Fragment()
if tab_module is not None: if tab_module is not None:
try: try:
html = tab_module.render(STUDENT_VIEW).content fragment = tab_module.render(STUDENT_VIEW, {})
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
html = render_to_string('courseware/error-message.html', None) fragment.content = render_to_string('courseware/error-message.html', None)
log.exception( log.exception(
u"Error rendering course=%s, tab=%s", course, tab['url_slug'] u"Error rendering course=%s, tab=%s", course, tab['url_slug']
) )
return html return fragment
@require_GET @require_GET
......
...@@ -7,9 +7,10 @@ from django.utils.translation import ugettext_noop ...@@ -7,9 +7,10 @@ from django.utils.translation import ugettext_noop
from courseware.tabs import EnrolledTab from courseware.tabs import EnrolledTab
import django_comment_client.utils as utils import django_comment_client.utils as utils
from xmodule.tabs import ComponentTabMixin
class DiscussionTab(EnrolledTab): class DiscussionTab(ComponentTabMixin, EnrolledTab):
""" """
A tab for the cs_comments_service forums. A tab for the cs_comments_service forums.
""" """
...@@ -17,7 +18,7 @@ class DiscussionTab(EnrolledTab): ...@@ -17,7 +18,7 @@ class DiscussionTab(EnrolledTab):
type = 'discussion' type = 'discussion'
title = ugettext_noop('Discussion') title = ugettext_noop('Discussion')
priority = None priority = None
view_name = 'discussion.views.forum_form_discussion' class_name = 'discussion.views.DiscussionBoardComponentView'
is_hideable = settings.FEATURES.get('ALLOW_HIDING_DISCUSSION_TAB', False) is_hideable = settings.FEATURES.get('ALLOW_HIDING_DISCUSSION_TAB', False)
is_default = False is_default = False
......
## mako
<%page expression_filter="h"/>
<%!
from openedx.core.djangolib.markup import HTML
%>
<%inherit file="/main.html" /> <%inherit file="/main.html" />
<%block name="bodyclass">view-in-course view-statictab ${course.css_class or ''}</%block> <%block name="bodyclass">view-in-course view-statictab ${course.css_class or ''}</%block>
<%namespace name='static' file='/static_content.html'/> <%namespace name='static' file='/static_content.html'/>
...@@ -5,21 +14,23 @@ ...@@ -5,21 +14,23 @@
<%block name="headextra"> <%block name="headextra">
<%static:css group='style-course-vendor'/> <%static:css group='style-course-vendor'/>
<%static:css group='style-course'/> <%static:css group='style-course'/>
${HTML(fragment.head_html())}
</%block> </%block>
<%block name="js_extra"> <%block name="js_extra">
<%include file="/mathjax_include.html" args="disable_fast_preview=True"/> <%include file="/mathjax_include.html" args="disable_fast_preview=True"/>
${HTML(fragment.foot_html())}
</%block> </%block>
<%block name="pagetitle">${tab['name']} | ${course.display_number_with_default | h}</%block> <%block name="pagetitle">${tab['name']} | ${course.display_number_with_default | h}</%block>
<%include file="/courseware/course_navigation.html" args="active_page='static_tab_{0}'.format(tab['url_slug'])" /> <%include file="/courseware/course_navigation.html" args="active_page=active_page" />
<main id="main" aria-label="Content" tabindex="-1"> <main id="main" aria-label="Content" tabindex="-1">
<section class="container"> <section class="container">
<div class="static_tab_wrapper"> <div class="static_tab_wrapper">
${tab_contents} ${HTML(fragment.body_html())}
</div> </div>
</section> </section>
</main> </main>
...@@ -685,6 +685,17 @@ if settings.FEATURES.get('ENABLE_DISCUSSION_SERVICE'): ...@@ -685,6 +685,17 @@ if settings.FEATURES.get('ENABLE_DISCUSSION_SERVICE'):
name='resubscribe_forum_update', name='resubscribe_forum_update',
), ),
) )
urlpatterns += (
url(
r'^courses/{}/tab/(?P<tab_type>[^/]+)/$'.format(
settings.COURSE_ID_PATTERN,
),
'courseware.views.views.content_tab',
name='content_tab',
),
)
urlpatterns += ( urlpatterns += (
# This MUST be the last view in the courseware--it's a catch-all for custom tabs. # This MUST be the last view in the courseware--it's a catch-all for custom tabs.
url( url(
......
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