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
3456c117
Commit
3456c117
authored
Feb 07, 2014
by
Giulio Gratta
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added "Edit in CMS" links throughout courseware
parent
6292e559
Hide whitespace changes
Inline
Side-by-side
Showing
23 changed files
with
362 additions
and
90 deletions
+362
-90
common/djangoapps/xmodule_modifiers.py
+23
-5
common/lib/xmodule/xmodule/course_module.py
+1
-0
common/lib/xmodule/xmodule/modulestore/inheritance.py
+4
-0
lms/djangoapps/courseware/courses.py
+25
-1
lms/djangoapps/courseware/module_render.py
+2
-2
lms/djangoapps/courseware/tests/test_courses.py
+12
-10
lms/djangoapps/courseware/tests/test_masquerade.py
+2
-2
lms/djangoapps/courseware/tests/test_module_render.py
+117
-1
lms/djangoapps/courseware/views.py
+31
-6
lms/djangoapps/instructor/features/bulk_email.py
+1
-1
lms/djangoapps/instructor/features/common.py
+1
-1
lms/djangoapps/instructor/views/instructor_dashboard.py
+1
-2
lms/djangoapps/instructor/views/legacy.py
+0
-1
lms/static/sass/course/courseware/_courseware.scss
+29
-0
lms/static/sass/multicourse/_course_about.scss
+25
-1
lms/templates/courseware/course_about.html
+6
-0
lms/templates/courseware/course_navigation.html
+1
-1
lms/templates/courseware/info.html
+8
-0
lms/templates/courseware/instructor_dashboard.html
+9
-9
lms/templates/courseware/progress.html
+9
-2
lms/templates/edit_unit_link.html
+7
-0
lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html
+30
-29
lms/templates/staff_problem_info.html
+18
-16
No files found.
common/djangoapps/xmodule_modifiers.py
View file @
3456c117
...
...
@@ -17,6 +17,8 @@ from xmodule.seq_module import SequenceModule
from
xmodule.vertical_module
import
VerticalModule
from
xmodule.x_module
import
shim_xmodule_js
,
XModuleDescriptor
,
XModule
from
lms.lib.xblock.runtime
import
quote_slashes
from
xmodule.modulestore
import
MONGO_MODULESTORE_TYPE
from
xmodule.modulestore.django
import
modulestore
,
loc_mapper
log
=
logging
.
getLogger
(
__name__
)
...
...
@@ -152,17 +154,33 @@ def grade_histogram(module_id):
return
grades
def
add_staff_
debug_info
(
user
,
block
,
view
,
frag
,
context
):
# pylint: disable=unused-argument
def
add_staff_
markup
(
user
,
block
,
view
,
frag
,
context
):
# pylint: disable=unused-argument
"""
Updates the supplied module with a new get_html function that wraps
the output of the old get_html function with additional information
for admin users only, including a histogram of student answers and the
definition of the xmodule
for admin users only, including a histogram of student answers, the
definition of the xmodule, and a link to view the module in Studio
if it is a Studio edited, mongo stored course.
Does nothing if module is a SequenceModule
or a VerticalModule
.
Does nothing if module is a SequenceModule.
"""
# TODO: make this more general, eg use an XModule attribute instead
if
isinstance
(
block
,
(
SequenceModule
,
VerticalModule
)):
if
isinstance
(
block
,
VerticalModule
):
# check that the course is a mongo backed Studio course before doing work
is_mongo_course
=
modulestore
()
.
get_modulestore_type
(
block
.
course_id
)
==
MONGO_MODULESTORE_TYPE
is_studio_course
=
block
.
course_edit_method
==
"Studio"
if
is_studio_course
and
is_mongo_course
:
# get relative url/location of unit in Studio
locator
=
loc_mapper
()
.
translate_location
(
block
.
course_id
,
block
.
location
,
False
,
True
)
# build edit link to unit in CMS
edit_link
=
"//"
+
settings
.
CMS_BASE
+
locator
.
url_reverse
(
'unit'
,
''
)
# return edit link in rendered HTML for display
return
wrap_fragment
(
frag
,
render_to_string
(
"edit_unit_link.html"
,
{
'frag_content'
:
frag
.
content
,
'edit_link'
:
edit_link
}))
else
:
return
frag
if
isinstance
(
block
,
SequenceModule
):
return
frag
block_id
=
block
.
id
...
...
common/lib/xmodule/xmodule/course_module.py
View file @
3456c117
...
...
@@ -224,6 +224,7 @@ class CourseFields(object):
scope
=
Scope
.
content
)
show_calculator
=
Boolean
(
help
=
"Whether to show the calculator in this course"
,
default
=
False
,
scope
=
Scope
.
settings
)
display_name
=
String
(
help
=
"Display name for this module"
,
default
=
"Empty"
,
display_name
=
"Display Name"
,
scope
=
Scope
.
settings
)
course_edit_method
=
String
(
help
=
"Method with which this course is edited."
,
default
=
"Studio"
,
scope
=
Scope
.
settings
)
show_chat
=
Boolean
(
help
=
"Whether to show the chat widget in this course"
,
default
=
False
,
scope
=
Scope
.
settings
)
tabs
=
CourseTabList
(
help
=
"List of tabs to enable in this course"
,
scope
=
Scope
.
settings
,
default
=
[])
end_of_course_survey_url
=
String
(
help
=
"Url for the end-of-course survey"
,
scope
=
Scope
.
settings
)
...
...
common/lib/xmodule/xmodule/modulestore/inheritance.py
View file @
3456c117
...
...
@@ -36,6 +36,10 @@ class InheritanceMixin(XBlockMixin):
default
=
None
,
scope
=
Scope
.
user_state
,
)
course_edit_method
=
String
(
help
=
"Method with which this course is edited."
,
default
=
"Studio"
,
scope
=
Scope
.
settings
)
giturl
=
String
(
help
=
"url root for course data git repository"
,
scope
=
Scope
.
settings
,
...
...
lms/djangoapps/courseware/courses.py
View file @
3456c117
...
...
@@ -9,7 +9,7 @@ from django.conf import settings
from
edxmako.shortcuts
import
render_to_string
from
xmodule.course_module
import
CourseDescriptor
from
xmodule.modulestore
import
Location
,
XML_MODULESTORE_TYPE
from
xmodule.modulestore
import
Location
,
XML_MODULESTORE_TYPE
,
MONGO_MODULESTORE_TYPE
from
xmodule.modulestore.django
import
modulestore
,
loc_mapper
from
xmodule.contentstore.content
import
StaticContent
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
,
InvalidLocationError
...
...
@@ -341,3 +341,27 @@ def get_cms_course_link(course):
course
.
location
.
course_id
,
course
.
location
,
False
,
True
)
return
"//"
+
settings
.
CMS_BASE
+
locator
.
url_reverse
(
'course/'
,
''
)
def
get_cms_block_link
(
block
,
page
):
"""
Returns a link to block_index for editing the course in cms,
assuming that the block is actually cms-backed.
"""
locator
=
loc_mapper
()
.
translate_location
(
block
.
location
.
course_id
,
block
.
location
,
False
,
True
)
return
"//"
+
settings
.
CMS_BASE
+
locator
.
url_reverse
(
page
,
''
)
def
get_studio_url
(
course_id
,
page
):
"""
Get the Studio URL of the page that is passed in.
"""
course
=
get_course_by_id
(
course_id
)
is_studio_course
=
course
.
course_edit_method
==
"Studio"
is_mongo_course
=
modulestore
()
.
get_modulestore_type
(
course_id
)
==
MONGO_MODULESTORE_TYPE
studio_link
=
None
if
is_studio_course
and
is_mongo_course
:
studio_link
=
get_cms_block_link
(
course
,
page
)
return
studio_link
lms/djangoapps/courseware/module_render.py
View file @
3456c117
...
...
@@ -37,7 +37,7 @@ from xmodule.modulestore import Location
from
xmodule.modulestore.django
import
modulestore
,
ModuleI18nService
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
from
xmodule.util.duedate
import
get_extended_due_date
from
xmodule_modifiers
import
replace_course_urls
,
replace_jump_to_id_urls
,
replace_static_urls
,
add_staff_
debug_info
,
wrap_xblock
from
xmodule_modifiers
import
replace_course_urls
,
replace_jump_to_id_urls
,
replace_static_urls
,
add_staff_
markup
,
wrap_xblock
from
xmodule.lti_module
import
LTIModule
from
xmodule.x_module
import
XModuleDescriptor
...
...
@@ -371,7 +371,7 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
if
settings
.
FEATURES
.
get
(
'DISPLAY_DEBUG_INFO_TO_STAFF'
):
if
has_access
(
user
,
descriptor
,
'staff'
,
course_id
):
block_wrappers
.
append
(
partial
(
add_staff_
debug_info
,
user
))
block_wrappers
.
append
(
partial
(
add_staff_
markup
,
user
))
# These modules store data using the anonymous_student_id as a key.
# To prevent loss of data, we will continue to provide old modules with
...
...
lms/djangoapps/courseware/tests/test_courses.py
View file @
3456c117
...
...
@@ -14,8 +14,13 @@ from xmodule.tests.xml import factories as xml
from
xmodule.tests.xml
import
XModuleXmlImportTest
from
courseware.courses
import
(
get_course_by_id
,
get_course
,
get_cms_course_link
,
course_image_url
,
get_course_info_section
,
get_course_about_section
get_course_by_id
,
get_course
,
get_cms_course_link
,
get_cms_block_link
,
course_image_url
,
get_course_info_section
,
get_course_about_section
)
from
courseware.tests.helpers
import
get_request_for_user
from
courseware.tests.tests
import
TEST_DATA_MONGO_MODULESTORE
,
TEST_DATA_MIXED_MODULESTORE
...
...
@@ -52,21 +57,18 @@ class CoursesTest(ModuleStoreTestCase):
@override_settings
(
MODULESTORE
=
TEST_DATA_MONGO_MODULESTORE
,
CMS_BASE
=
CMS_BASE_TEST
)
def
test_get_cms_course_link
(
self
):
def
test_get_cms_course_
block_
link
(
self
):
"""
Tests that get_cms_course_link_by_id
returns
the right thing
Tests that get_cms_course_link_by_id
and get_cms_block_link_by_id return
the right thing
"""
cms_url
=
u"//{}/course/org.num.name/branch/draft/block/name"
.
format
(
CMS_BASE_TEST
)
self
.
course
=
CourseFactory
.
create
(
org
=
'org'
,
number
=
'num'
,
display_name
=
'name'
)
self
.
assertEqual
(
u"//{}/course/org.num.name/branch/draft/block/name"
.
format
(
CMS_BASE_TEST
),
get_cms_course_link
(
self
.
course
)
)
self
.
assertEqual
(
cms_url
,
get_cms_course_link
(
self
.
course
))
self
.
assertEqual
(
cms_url
,
get_cms_block_link
(
self
.
course
,
'course'
))
@mock.patch
(
'xmodule.modulestore.django.get_current_request_hostname'
,
...
...
lms/djangoapps/courseware/tests/test_masquerade.py
View file @
3456c117
...
...
@@ -63,7 +63,7 @@ class TestStaffMasqueradeAsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase)
def
test_staff_debug_for_staff
(
self
):
resp
=
self
.
get_cw_section
()
sdebug
=
'
<div aria-hidden="true"><a href="#i4x_edX_graded_problem_H1P1_debug" id="i4x_edX_graded_problem_H1P1_trig">Staff Debug Info</a></div>
'
sdebug
=
'
Staff Debug Info
'
self
.
assertTrue
(
sdebug
in
resp
.
content
)
...
...
@@ -82,7 +82,7 @@ class TestStaffMasqueradeAsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase)
self
.
assertEqual
(
togresp
.
content
,
'{"status": "student"}'
,
''
)
resp
=
self
.
get_cw_section
()
sdebug
=
'
<div><a href="#i4x_edX_graded_problem_H1P1_debug" id="i4x_edX_graded_problem_H1P1_trig">Staff Debug Info</a></div>
'
sdebug
=
'
Staff Debug Info
'
self
.
assertFalse
(
sdebug
in
resp
.
content
)
...
...
lms/djangoapps/courseware/tests/test_module_render.py
View file @
3456c117
...
...
@@ -27,9 +27,12 @@ from xmodule.x_module import XModuleDescriptor
from
courseware
import
module_render
as
render
from
courseware.courses
import
get_course_with_access
,
course_image_url
,
get_course_info_section
from
courseware.model_data
import
FieldDataCache
from
courseware.tests.factories
import
StudentModuleFactory
,
UserFactory
from
courseware.tests.factories
import
StudentModuleFactory
,
UserFactory
,
GlobalStaffFactory
from
courseware.tests.tests
import
LoginEnrollmentTestCase
from
courseware.tests.modulestore_config
import
TEST_DATA_MIXED_MODULESTORE
from
courseware.tests.modulestore_config
import
TEST_DATA_MONGO_MODULESTORE
from
courseware.tests.modulestore_config
import
TEST_DATA_XML_MODULESTORE
from
lms.lib.xblock.runtime
import
quote_slashes
...
...
@@ -509,6 +512,119 @@ class TestHtmlModifiers(ModuleStoreTestCase):
)
class
ViewInStudioTest
(
ModuleStoreTestCase
):
"""Tests for the 'View in Studio' link visiblity."""
def
setUp
(
self
):
""" Set up the user and request that will be used. """
self
.
staff_user
=
GlobalStaffFactory
.
create
()
self
.
request
=
RequestFactory
()
.
get
(
'/'
)
self
.
request
.
user
=
self
.
staff_user
self
.
request
.
session
=
{}
self
.
module
=
None
def
_get_module
(
self
,
course_id
,
descriptor
,
location
):
"""
Get the module from the course from which to pattern match (or not) the 'View in Studio' buttons
"""
field_data_cache
=
FieldDataCache
.
cache_for_descriptor_descendents
(
course_id
,
self
.
staff_user
,
descriptor
)
self
.
module
=
render
.
get_module
(
self
.
staff_user
,
self
.
request
,
location
,
field_data_cache
,
course_id
,
)
def
setup_mongo_course
(
self
,
course_edit_method
=
'Studio'
):
""" Create a mongo backed course. """
course
=
CourseFactory
.
create
(
course_edit_method
=
course_edit_method
)
descriptor
=
ItemFactory
.
create
(
category
=
'vertical'
,
)
self
.
_get_module
(
course
.
id
,
descriptor
,
descriptor
.
location
)
def
setup_xml_course
(
self
):
"""
Define the XML backed course to use.
Toy courses are already loaded in XML and mixed modulestores.
"""
course_id
=
'edX/toy/2012_Fall'
location
=
Location
(
'i4x'
,
'edX'
,
'toy'
,
'chapter'
,
'Overview'
)
descriptor
=
modulestore
()
.
get_instance
(
course_id
,
location
)
self
.
_get_module
(
course_id
,
descriptor
,
location
)
@override_settings
(
MODULESTORE
=
TEST_DATA_MONGO_MODULESTORE
)
class
MongoViewInStudioTest
(
ViewInStudioTest
):
"""Test the 'View in Studio' link visibility in a mongo backed course."""
def
setUp
(
self
):
super
(
MongoViewInStudioTest
,
self
)
.
setUp
()
def
test_view_in_studio_link_studio_course
(
self
):
"""Regular Studio courses should see 'View in Studio' links."""
self
.
setup_mongo_course
()
result_fragment
=
self
.
module
.
render
(
'student_view'
)
self
.
assertIn
(
'View Unit in Studio'
,
result_fragment
.
content
)
def
test_view_in_studio_link_xml_authored
(
self
):
"""Courses that change 'course_edit_method' setting can hide 'View in Studio' links."""
self
.
setup_mongo_course
(
course_edit_method
=
'XML'
)
result_fragment
=
self
.
module
.
render
(
'student_view'
)
self
.
assertNotIn
(
'View Unit in Studio'
,
result_fragment
.
content
)
@override_settings
(
MODULESTORE
=
TEST_DATA_MIXED_MODULESTORE
)
class
MixedViewInStudioTest
(
ViewInStudioTest
):
"""Test the 'View in Studio' link visibility in a mixed mongo backed course."""
def
setUp
(
self
):
super
(
MixedViewInStudioTest
,
self
)
.
setUp
()
def
test_view_in_studio_link_mongo_backed
(
self
):
"""Mixed mongo courses that are mongo backed should see 'View in Studio' links."""
self
.
setup_mongo_course
()
result_fragment
=
self
.
module
.
render
(
'student_view'
)
self
.
assertIn
(
'View Unit in Studio'
,
result_fragment
.
content
)
def
test_view_in_studio_link_xml_authored
(
self
):
"""Courses that change 'course_edit_method' setting can hide 'View in Studio' links."""
self
.
setup_mongo_course
(
course_edit_method
=
'XML'
)
result_fragment
=
self
.
module
.
render
(
'student_view'
)
self
.
assertNotIn
(
'View Unit in Studio'
,
result_fragment
.
content
)
def
test_view_in_studio_link_xml_backed
(
self
):
"""Course in XML only modulestore should not see 'View in Studio' links."""
self
.
setup_xml_course
()
result_fragment
=
self
.
module
.
render
(
'student_view'
)
self
.
assertNotIn
(
'View Unit in Studio'
,
result_fragment
.
content
)
@override_settings
(
MODULESTORE
=
TEST_DATA_XML_MODULESTORE
)
class
XmlViewInStudioTest
(
ViewInStudioTest
):
"""Test the 'View in Studio' link visibility in an xml backed course."""
def
setUp
(
self
):
super
(
XmlViewInStudioTest
,
self
)
.
setUp
()
def
test_view_in_studio_link_xml_backed
(
self
):
"""Course in XML only modulestore should not see 'View in Studio' links."""
self
.
setup_xml_course
()
result_fragment
=
self
.
module
.
render
(
'student_view'
)
self
.
assertNotIn
(
'View Unit in Studio'
,
result_fragment
.
content
)
@override_settings
(
MODULESTORE
=
TEST_DATA_MIXED_MODULESTORE
)
@patch.dict
(
'django.conf.settings.FEATURES'
,
{
'DISPLAY_DEBUG_INFO_TO_STAFF'
:
True
,
'DISPLAY_HISTOGRAMS_TO_STAFF'
:
True
})
@patch
(
'courseware.module_render.has_access'
,
Mock
(
return_value
=
True
))
...
...
lms/djangoapps/courseware/views.py
View file @
3456c117
"""
Courseware views functions
"""
import
logging
import
urllib
...
...
@@ -20,7 +24,8 @@ from markupsafe import escape
from
courseware
import
grades
from
courseware.access
import
has_access
from
courseware.courses
import
get_courses
,
get_course_with_access
,
sort_by_announcement
from
courseware.courses
import
get_courses
,
get_course_with_access
,
get_studio_url
,
sort_by_announcement
from
courseware.masquerade
import
setup_masquerade
from
courseware.model_data
import
FieldDataCache
from
.module_render
import
toc_for_course
,
get_module_for_descriptor
,
get_module
...
...
@@ -46,6 +51,7 @@ log = logging.getLogger("edx.courseware")
template_imports
=
{
'urllib'
:
urllib
}
def
user_groups
(
user
):
"""
TODO (vshnayder): This is not used. When we have a new plan for groups, adjust appropriately.
...
...
@@ -95,7 +101,7 @@ def render_accordion(request, course, chapter, section, field_data_cache):
# grab the table of contents
user
=
User
.
objects
.
prefetch_related
(
"groups"
)
.
get
(
id
=
request
.
user
.
id
)
request
.
user
=
user
# keep just one instance of User
request
.
user
=
user
# keep just one instance of User
toc
=
toc_for_course
(
user
,
request
,
course
,
chapter
,
section
,
field_data_cache
)
context
=
dict
([
...
...
@@ -257,6 +263,8 @@ def index(request, course_id, chapter=None, section=None,
u' far, should have gotten a course module for this user'
)
return
redirect
(
reverse
(
'about_course'
,
args
=
[
course
.
id
]))
studio_url
=
get_studio_url
(
course_id
,
'course'
)
if
chapter
is
None
:
return
redirect_to_course_position
(
course_module
)
...
...
@@ -268,6 +276,7 @@ def index(request, course_id, chapter=None, section=None,
'init'
:
''
,
'fragment'
:
Fragment
(),
'staff_access'
:
staff_access
,
'studio_url'
:
studio_url
,
'masquerade'
:
masq
,
'xqa_server'
:
settings
.
FEATURES
.
get
(
'USE_XQA_SERVER'
,
'http://xqa:server@content-qa.mitx.mit.edu/xqa'
),
'reverifications'
:
fetch_reverify_banner_info
(
request
,
course_id
),
...
...
@@ -294,7 +303,7 @@ def index(request, course_id, chapter=None, section=None,
chapter_module
=
course_module
.
get_child_by
(
lambda
m
:
m
.
url_name
==
chapter
)
if
chapter_module
is
None
:
# User may be trying to access a chapter that isn't live yet
if
masq
==
'student'
:
# if staff is masquerading as student be kinder, don't 404
if
masq
==
'student'
:
# if staff is masquerading as student be kinder, don't 404
log
.
debug
(
'staff masq as student: no chapter
%
s'
%
chapter
)
return
redirect
(
reverse
(
'courseware'
,
args
=
[
course
.
id
]))
raise
Http404
...
...
@@ -303,7 +312,7 @@ def index(request, course_id, chapter=None, section=None,
section_descriptor
=
chapter_descriptor
.
get_child_by
(
lambda
m
:
m
.
url_name
==
section
)
if
section_descriptor
is
None
:
# Specifically asked-for section doesn't exist
if
masq
==
'student'
:
# if staff is masquerading as student be kinder, don't 404
if
masq
==
'student'
:
# if staff is masquerading as student be kinder, don't 404
log
.
debug
(
'staff masq as student: no section
%
s'
%
section
)
return
redirect
(
reverse
(
'courseware'
,
args
=
[
course
.
id
]))
raise
Http404
...
...
@@ -317,7 +326,8 @@ def index(request, course_id, chapter=None, section=None,
section_field_data_cache
=
FieldDataCache
.
cache_for_descriptor_descendents
(
course_id
,
user
,
section_descriptor
,
depth
=
None
)
section_module
=
get_module_for_descriptor
(
request
.
user
,
section_module
=
get_module_for_descriptor
(
request
.
user
,
request
,
section_descriptor
,
section_field_data_cache
,
...
...
@@ -336,6 +346,7 @@ def index(request, course_id, chapter=None, section=None,
context
[
'section_title'
]
=
section_descriptor
.
display_name_with_default
else
:
# section is none, so display a message
studio_url
=
get_studio_url
(
course_id
,
'course'
)
prev_section
=
get_current_child
(
chapter_module
)
if
prev_section
is
None
:
# Something went wrong -- perhaps this chapter has no sections visible to the user
...
...
@@ -347,6 +358,7 @@ def index(request, course_id, chapter=None, section=None,
'courseware/welcome-back.html'
,
{
'course'
:
course
,
'studio_url'
:
studio_url
,
'chapter_module'
:
chapter_module
,
'prev_section'
:
prev_section
,
'prev_section_url'
:
prev_section_url
...
...
@@ -461,6 +473,7 @@ def course_info(request, course_id):
course
=
get_course_with_access
(
request
.
user
,
course_id
,
'load'
)
staff_access
=
has_access
(
request
.
user
,
course
,
'staff'
)
masq
=
setup_masquerade
(
request
,
staff_access
)
# allow staff to toggle masquerade on info page
studio_url
=
get_studio_url
(
course_id
,
'course_info'
)
reverifications
=
fetch_reverify_banner_info
(
request
,
course_id
)
context
=
{
...
...
@@ -470,6 +483,7 @@ def course_info(request, course_id):
'course'
:
course
,
'staff_access'
:
staff_access
,
'masquerade'
:
masq
,
'studio_url'
:
studio_url
,
'reverifications'
:
reverifications
,
}
...
...
@@ -537,6 +551,11 @@ def registered_for_course(course, user):
@ensure_csrf_cookie
@cache_if_anonymous
def
course_about
(
request
,
course_id
):
"""
Display the course's about page.
Assumes the course_id is in a valid format.
"""
if
microsite
.
get_value
(
'ENABLE_MKTG_SITE'
,
...
...
@@ -546,6 +565,8 @@ def course_about(request, course_id):
course
=
get_course_with_access
(
request
.
user
,
course_id
,
'see_exists'
)
registered
=
registered_for_course
(
course
,
request
.
user
)
staff_access
=
has_access
(
request
.
user
,
course
,
'staff'
)
studio_url
=
get_studio_url
(
course_id
,
'settings/details'
)
if
has_access
(
request
.
user
,
course
,
'load'
):
course_target
=
reverse
(
'info'
,
args
=
[
course
.
id
])
...
...
@@ -575,6 +596,8 @@ def course_about(request, course_id):
return
render_to_response
(
'courseware/course_about.html'
,
{
'course'
:
course
,
'staff_access'
:
staff_access
,
'studio_url'
:
studio_url
,
'registered'
:
registered
,
'course_target'
:
course_target
,
'registration_price'
:
registration_price
,
...
...
@@ -664,7 +687,7 @@ def _progress(request, course_id, student_id):
student
=
User
.
objects
.
prefetch_related
(
"groups"
)
.
get
(
id
=
student
.
id
)
courseware_summary
=
grades
.
progress_summary
(
student
,
request
,
course
)
studio_url
=
get_studio_url
(
course_id
,
'settings/grading'
)
grade_summary
=
grades
.
grade
(
student
,
request
,
course
)
if
courseware_summary
is
None
:
...
...
@@ -674,6 +697,7 @@ def _progress(request, course_id, student_id):
context
=
{
'course'
:
course
,
'courseware_summary'
:
courseware_summary
,
'studio_url'
:
studio_url
,
'grade_summary'
:
grade_summary
,
'staff_access'
:
staff_access
,
'student'
:
student
,
...
...
@@ -701,6 +725,7 @@ def fetch_reverify_banner_info(request, course_id):
reverifications
[
info
.
status
]
.
append
(
info
)
return
reverifications
@login_required
def
submission_history
(
request
,
course_id
,
student_username
,
location
):
"""Render an HTML fragment (meant for inclusion elsewhere) that renders a
...
...
lms/djangoapps/instructor/features/bulk_email.py
View file @
3456c117
...
...
@@ -117,7 +117,7 @@ def when_i_send_an_email(step, recipient): # pylint: disable=unused-argument
# Go to the email section of the instructor dash
world
.
visit
(
'/courses/edx/888/Bulk_Email_Test_Course'
)
world
.
css_click
(
'a[href="/courses/edx/888/Bulk_Email_Test_Course/instructor"]'
)
world
.
css_click
(
'div.beta-button-wrapper>a'
)
world
.
css_click
(
'div.beta-button-wrapper>a
.beta-button
'
)
world
.
css_click
(
'a[data-section="send_email"]'
)
# Select the recipient
...
...
lms/djangoapps/instructor/features/common.py
View file @
3456c117
...
...
@@ -77,7 +77,7 @@ def go_to_section(section_name):
# course_info, membership, student_admin, data_download, analytics, send_email
world
.
visit
(
'/courses/edx/999/Test_Course'
)
world
.
css_click
(
'a[href="/courses/edx/999/Test_Course/instructor"]'
)
world
.
css_click
(
'div.beta-button-wrapper>a'
)
world
.
css_click
(
'div.beta-button-wrapper>a
.beta-button
'
)
world
.
css_click
(
'a[data-section="{0}"]'
.
format
(
section_name
))
...
...
lms/djangoapps/instructor/views/instructor_dashboard.py
View file @
3456c117
...
...
@@ -60,8 +60,7 @@ def instructor_dashboard_2(request, course_id):
sections
.
insert
(
3
,
_section_extensions
(
course
))
# Gate access to course email by feature flag & by course-specific authorization
if
settings
.
FEATURES
[
'ENABLE_INSTRUCTOR_EMAIL'
]
and
\
is_studio_course
and
CourseAuthorization
.
instructor_email_enabled
(
course_id
):
if
settings
.
FEATURES
[
'ENABLE_INSTRUCTOR_EMAIL'
]
and
is_studio_course
and
CourseAuthorization
.
instructor_email_enabled
(
course_id
):
sections
.
append
(
_section_send_email
(
course_id
,
access
,
course
))
# Gate access to Metrics tab by featue flag and staff authorization
...
...
lms/djangoapps/instructor/views/legacy.py
View file @
3456c117
...
...
@@ -851,7 +851,6 @@ def instructor_dashboard(request, course_id):
# determine if this is a studio-backed course so we can provide a link to edit this course in studio
is_studio_course
=
modulestore
()
.
get_modulestore_type
(
course_id
)
!=
XML_MODULESTORE_TYPE
studio_url
=
None
if
is_studio_course
:
studio_url
=
get_cms_course_link
(
course
)
...
...
lms/static/sass/course/courseware/_courseware.scss
View file @
3456c117
...
...
@@ -11,7 +11,36 @@ html.video-fullscreen{
}
}
.wrap-instructor-info
{
margin
:
(
$baseline
/
2
)
(
$baseline
/
4
)
0
0
;
overflow
:
hidden
;
&
.studio-view
{
position
:
relative
;
top
:
-
(
$baseline
/
2
);
margin
:
0
;
}
.instructor-info-action
{
@extend
%t-copy-sub2
;
float
:
right
;
margin-left
:
(
$baseline
/
2
);
padding
:
(
$baseline
/
4
)
(
$baseline
/
2
);
border-radius
:
(
$baseline
/
4
);
background-color
:
$shadow-l2
;
text-align
:
right
;
text-transform
:
uppercase
;
color
:
$lighter-base-font-color
;
&
:hover
{
background-color
:
$link-hover
;
color
:
$white
;
}
}
}
div
.course-wrapper
{
position
:
relative
;
section
.course-content
{
@extend
.content
;
...
...
lms/static/sass/multicourse/_course_about.scss
View file @
3456c117
...
...
@@ -233,7 +233,31 @@
.container
{
@include
clearfix
;
.wrap-instructor-info
{
&
.studio-view
{
position
:
relative
;
margin
:
(
$baseline
/
2
)
0
0
0
;
overflow
:
hidden
;
}
.instructor-info-action
{
@extend
%t-copy-sub2
;
float
:
right
;
padding
:
(
$baseline
/
4
)
(
$baseline
/
2
);
border-radius
:
(
$baseline
/
4
);
background-color
:
$shadow-l2
;
text-align
:
right
;
text-transform
:
uppercase
;
color
:
$lighter-base-font-color
;
&
:hover
{
background-color
:
$link-hover
;
color
:
$white
;
}
}
}
nav
{
border-bottom
:
1px
solid
$border-color-2
;
@include
box-sizing
(
border-box
);
...
...
lms/templates/courseware/course_about.html
View file @
3456c117
...
...
@@ -201,6 +201,12 @@
<section
class=
"container"
>
<section
class=
"details"
>
% if staff_access and studio_url is not None:
<div
class=
"wrap-instructor-info studio-view"
>
<a
class=
"instructor-info-action"
href=
"${studio_url}"
>
${_("View About Page in studio")}
</a>
</div>
% endif
<nav>
<a
href=
"#"
class=
"active"
>
${_("Overview")}
</a>
##
<a
href=
"#"
>
${_("FAQ")}
</a>
...
...
lms/templates/courseware/course_navigation.html
View file @
3456c117
...
...
@@ -49,7 +49,7 @@ def url_class(is_active):
<
%
block
name=
"extratabs"
/>
% if masquerade is not UNDEFINED:
% if staff_access and masquerade is not None:
<li
style=
"float:right"
><a
href=
"#"
id=
"staffstatus"
>
${_("Staff view")}
</a></li>
<li
style=
"float:right"
><a
href=
"#"
id=
"staffstatus"
>
${_("Staff view")}
</a></li>
% endif
% endif
</ol>
...
...
lms/templates/courseware/info.html
View file @
3456c117
...
...
@@ -29,6 +29,14 @@ $(document).ready(function(){
<div
class=
"info-wrapper"
>
% if user.is_authenticated():
<section
class=
"updates"
>
% if staff_access and masquerade is not UNDEFINED and studio_url is not None:
% if masquerade == 'staff':
<div
class=
"wrap-instructor-info studio-view"
>
<a
class=
"instructor-info-action"
href=
"${studio_url}"
>
${_("View Updates in Studio")}
</a>
</div>
% endif
% endif
<h1>
${_("Course Updates
&
News")}
</h1>
${get_course_info_section(request, course, 'updates')}
</section>
...
...
lms/templates/courseware/instructor_dashboard.html
View file @
3456c117
...
...
@@ -117,16 +117,16 @@ function goto( mode)
<section
class=
"container"
>
<div
class=
"instructor-dashboard-wrapper"
>
%if settings.FEATURES.get('ENABLE_INSTRUCTOR_BETA_DASHBOARD'):
<div
class=
"beta-button-wrapper"
><a
href=
"${ beta_dashboard_url }"
>
${_("Try New Beta Dashboard")}
</a></div>
%endif
%if studio_url:
## not checking access because if user can see this, they are at least course staff (with studio edit access)
<div
class=
"studio-edit-link"
><a
href=
"${studio_url}"
target=
"_blank"
>
${_('Edit Course In Studio')}
</a></div>
%endif
<section
class=
"instructor-dashboard-content"
id=
"instructor-dashboard-content"
>
<div
class=
"wrap-instructor-info studio-view beta-button-wrapper"
>
%if studio_url:
<a
class=
"instructor-info-action"
href=
"${studio_url}"
>
${_("View Course in Studio")}
</a>
%endif
%if settings.FEATURES.get('ENABLE_INSTRUCTOR_BETA_DASHBOARD'):
<a
class=
"instructor-info-action beta-button"
href=
"${ beta_dashboard_url }"
>
${_("Try New Beta Dashboard")}
</a>
%endif
</div>
<h1>
${_("Instructor Dashboard")}
</h1>
<h2
class=
"navbar"
>
[
<a
href=
"#"
onclick=
"goto('Grades');"
class=
"${modeflag.get('Grades')}"
>
Grades
</a>
|
...
...
lms/templates/courseware/progress.html
View file @
3456c117
...
...
@@ -26,19 +26,26 @@ from django.conf import settings
<script
type=
"text/javascript"
src=
"${static.url('js/vendor/flot/jquery.flot.stack.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/vendor/flot/jquery.flot.symbol.js')}"
></script>
<script>
$
{
progress_graph
.
body
(
grade_summary
,
course
.
grade_cutoffs
,
"grade-detail-graph"
,
not
course
.
no_grade
,
not
course
.
no_grade
)}
$
{
progress_graph
.
body
(
grade_summary
,
course
.
grade_cutoffs
,
"grade-detail-graph"
,
not
course
.
no_grade
,
not
course
.
no_grade
)}
</script>
</
%
block>
<
%
include
file=
"/dashboard/_dashboard_prompt_midcourse_reverify.html"
/>
<
%
include
file=
"/courseware/course_navigation.html"
args=
"active_page='progress'"
/>
<div
class=
"container"
>
<div
class=
"profile-wrapper"
>
<div
class=
"course-info"
id=
"course-info-progress"
aria-label=
"${_('Course Progress')}"
>
% if staff_access and studio_url is not None:
<div
class=
"wrap-instructor-info"
>
<a
class=
"instructor-info-action studio-view"
href=
"${studio_url}"
>
${_("View Grading in studio")}
</a>
</div>
% endif
<header>
<h1>
${_("Course Progress for Student '{username}' ({email})").format(username=student.username, email=student.email)}
</h1>
<h1>
${_("Course Progress for Student '{username}' ({email})").format(username=student.username, email=student.email)}
</h1>
</header>
%if not course.disable_progress_graph:
...
...
lms/templates/edit_unit_link.html
0 → 100644
View file @
3456c117
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
%
>
<div
class=
"wrap-instructor-info studio-view"
>
<a
class=
"instructor-info-action"
href=
"${edit_link}"
>
${_("View Unit in Studio")}
</a>
</div>
${frag_content}
\ No newline at end of file
lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html
View file @
3456c117
...
...
@@ -53,34 +53,35 @@
<script
language=
"JavaScript"
type=
"text/javascript"
></script>
<section
class=
"container"
>
<div
class=
"instructor-dashboard-wrapper-2"
>
<div
class=
"olddash-button-wrapper"
><a
href=
"${ old_dashboard_url }"
>
${_("Back to Standard Dashboard")}
</a></div>
%if studio_url:
## not checking access because if user can see this, they are at least course staff (with studio edit access)
<div
class=
"studio-edit-link"
><a
href=
"${studio_url}"
target=
"_blank"
>
${_('Edit Course In Studio')}
</a></div>
%endif
<section
class=
"instructor-dashboard-content-2"
id=
"instructor-dashboard-content"
>
<h1>
${_("Instructor Dashboard")}
</h1>
<hr
/>
## links which are tied to idash-sections below.
## the links are acativated and handled in instructor_dashboard.coffee
## when the javascript loads, it clicks on the first section
<h2
class=
"instructor-nav"
>
% for section_data in sections:
<a
href=
""
data-section=
"${ section_data['section_key'] }"
>
${_(section_data['section_display_name'])}
</a>
% endfor
</h2>
## each section corresponds to a section_data sub-dictionary provided by the view
## to keep this short, sections can be pulled out into their own files
% for section_data in sections:
<section
id=
"${ section_data['section_key'] }"
class=
"idash-section"
>
<
%
include
file=
"${ section_data['section_key'] }.html"
args=
"section_data=section_data"
/>
<div
class=
"instructor-dashboard-wrapper-2"
>
<section
class=
"instructor-dashboard-content-2"
id=
"instructor-dashboard-content"
>
<div
class=
"wrap-instructor-info studio-view"
>
%if studio_url:
<a
class=
"instructor-info-action"
href=
"${studio_url}"
>
${_("View Course in Studio")}
</a>
%endif
<a
class=
"instructor-info-action"
href=
"${ old_dashboard_url }"
>
${_("Back to Standard Dashboard")}
</a>
</div>
<h1>
${_("Instructor Dashboard")}
</h1>
<hr
/>
## links which are tied to idash-sections below.
## the links are acativated and handled in instructor_dashboard.coffee
## when the javascript loads, it clicks on the first section
<h2
class=
"instructor-nav"
>
% for section_data in sections:
<a
href=
""
data-section=
"${ section_data['section_key'] }"
>
${_(section_data['section_display_name'])}
</a>
% endfor
</h2>
## each section corresponds to a section_data sub-dictionary provided by the view
## to keep this short, sections can be pulled out into their own files
% for section_data in sections:
<section
id=
"${ section_data['section_key'] }"
class=
"idash-section"
>
<
%
include
file=
"${ section_data['section_key'] }.html"
args=
"section_data=section_data"
/>
</section>
% endfor
</section>
% endfor
</section>
</div>
</div>
</section>
lms/templates/staff_problem_info.html
View file @
3456c117
...
...
@@ -4,24 +4,26 @@
${block_content}
%if location.category in ['problem','video','html','combinedopenended','graphical_slider_tool']:
% if edit_link:
<div>
<a
href=
"${edit_link}"
>
Edit
</a>
% if xqa_key:
/
<a
href=
"#${element_id}_xqa-modal"
onclick=
"javascript:getlog('${element_id}', {
'location': '${location}',
'xqa_key': '${xqa_key}',
'category': '${category}',
'user': '${user}'
})"
id=
"${element_id}_xqa_log"
>
QA
</a>
% endif
</div>
<div>
<a
href=
"${edit_link}"
>
Edit
</a>
% if xqa_key:
/
<a
href=
"#${element_id}_xqa-modal"
onclick=
"javascript:getlog('${element_id}', {
'location': '${location}',
'xqa_key': '${xqa_key}',
'category': '${category}',
'user': '${user}'
})"
id=
"${element_id}_xqa_log"
>
QA
</a>
% endif
</div>
% endif
<div
aria-hidden=
"true"
><a
href=
"#${element_id}_debug"
id=
"${element_id}_trig"
>
${_("Staff Debug Info")}
</a></div>
<div
aria-hidden=
"true"
class=
"wrap-instructor-info"
>
<a
class=
"instructor-info-action"
href=
"#${element_id}_debug"
id=
"${element_id}_trig"
>
${_("Staff Debug Info")}
</a>
% if settings.FEATURES.get('ENABLE_STUDENT_HISTORY_VIEW') and \
location.category == 'problem':
<div
aria-hidden=
"true"
><a
href=
"#${element_id}_history"
id=
"${element_id}_history_trig"
>
${_("Submission history")}
</a></div>
% endif
% if settings.FEATURES.get('ENABLE_STUDENT_HISTORY_VIEW') and \
location.category == 'problem':
<a
class=
"instructor-info-action"
href=
"#${element_id}_history"
id=
"${element_id}_history_trig"
>
${_("Submission history")}
</a>
% endif
</div>
<section
aria-hidden=
"true"
id=
"${element_id}_xqa-modal"
class=
"modal xqa-modal"
style=
"width:80%; left:20%; height:80%; overflow:auto"
>
<div
class=
"inner-wrapper"
>
...
...
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