Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
8fedc08c
Commit
8fedc08c
authored
Dec 08, 2016
by
cahrens
Committed by
Andy Armstrong
Feb 24, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Support web fragments for tabs.
parent
d439da44
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
162 additions
and
20 deletions
+162
-20
common/lib/xmodule/xmodule/tabs.py
+32
-2
lms/djangoapps/courseware/tabs.py
+3
-4
lms/djangoapps/courseware/views/views.py
+100
-10
lms/djangoapps/discussion/plugins.py
+3
-2
lms/templates/courseware/static_tab.html
+13
-2
lms/urls.py
+11
-0
No files found.
common/lib/xmodule/xmodule/tabs.py
View file @
8fedc08c
...
@@ -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.
...
...
lms/djangoapps/courseware/tabs.py
View file @
8fedc08c
...
@@ -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
...
...
lms/djangoapps/courseware/views/views.py
View file @
8fedc08c
...
@@ -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
...
...
lms/djangoapps/discussion/plugins.py
View file @
8fedc08c
...
@@ -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
...
...
lms/templates/courseware/static_tab.html
View file @
8fedc08c
## 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>
lms/urls.py
View file @
8fedc08c
...
@@ -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
(
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment