diff --git a/cms/djangoapps/contentstore/config/waffle.py b/cms/djangoapps/contentstore/config/waffle.py index f86154053c4ce9b64d6353cc6217c82223ec4fae..d5707c9eaebb2b531d285ba9cf640810dc01c61a 100644 --- a/cms/djangoapps/contentstore/config/waffle.py +++ b/cms/djangoapps/contentstore/config/waffle.py @@ -9,6 +9,7 @@ WAFFLE_NAMESPACE = u'studio' # Switches ENABLE_ACCESSIBILITY_POLICY_PAGE = u'enable_policy_page' +ENABLE_CHECKLISTS_PAGE = u'enable_checklists_page' def waffle(): @@ -31,3 +32,9 @@ ENABLE_IN_CONTEXT_IMAGE_SELECTION = CourseWaffleFlag( flag_name=u'enable_in_context_image_selection', flag_undefined_default=False ) + +ENABLE_CHECKLISTS_QUALITY = CourseWaffleFlag( + waffle_namespace=waffle_flags(), + flag_name=u'enable_checklists_quality', + flag_undefined_default=False +) diff --git a/cms/djangoapps/contentstore/config/waffle_utils.py b/cms/djangoapps/contentstore/config/waffle_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..33d2959dc78acd1fac386ae801c2a107a092eb78 --- /dev/null +++ b/cms/djangoapps/contentstore/config/waffle_utils.py @@ -0,0 +1,30 @@ +"""Util methods for Waffle checks""" + +from cms.djangoapps.contentstore.config.waffle import waffle, ENABLE_CHECKLISTS_PAGE, ENABLE_CHECKLISTS_QUALITY +from student.roles import GlobalStaff + + +def should_show_checklists_page(requesting_user): + """ + Determine if the ENABLE_CHECKLISTS_PAGE waffle switch is set + and if the user is able to see it + """ + + if waffle().is_enabled(ENABLE_CHECKLISTS_PAGE): + if GlobalStaff().has_user(requesting_user): + return True + + return False + + +def should_show_checklists_quality(requesting_user, course_key): + """ + Determine if the ENABLE_CHECKLISTS_QUALITY waffle flag is set + and if the user is able to see it + """ + + if ENABLE_CHECKLISTS_QUALITY.is_enabled(course_key): + if GlobalStaff().has_user(requesting_user): + return True + + return False diff --git a/cms/djangoapps/contentstore/views/__init__.py b/cms/djangoapps/contentstore/views/__init__.py index 0242ac5723cd52cd38f96f2da6eea801f3ff8bd3..e90628aef46970e0db8946bbc05721c2b06a39e0 100644 --- a/cms/djangoapps/contentstore/views/__init__.py +++ b/cms/djangoapps/contentstore/views/__init__.py @@ -7,6 +7,7 @@ from .assets import * from .component import * from .course import * +from .checklists import * from .entrance_exam import * from .error import * from .helpers import * diff --git a/cms/djangoapps/contentstore/views/checklists.py b/cms/djangoapps/contentstore/views/checklists.py new file mode 100644 index 0000000000000000000000000000000000000000..f3fb7cffba156ec67c8a342af6b86e8c26477973 --- /dev/null +++ b/cms/djangoapps/contentstore/views/checklists.py @@ -0,0 +1,33 @@ +# pylint: disable=missing-docstring +from django.contrib.auth.decorators import login_required +from django.core.exceptions import PermissionDenied +from django.views.decorators.csrf import ensure_csrf_cookie +from opaque_keys.edx.keys import CourseKey +from xmodule.modulestore.django import modulestore + +from edxmako.shortcuts import render_to_response +from student.auth import has_course_author_access + +__all__ = ['checklists_handler'] + + +@login_required +@ensure_csrf_cookie +def checklists_handler(request, course_key_string=None): + ''' + The restful handler for course checklists. + It allows retrieval of the checklists (as an HTML page). + + GET + html: return an html page which will show course checklists. Note that only the checklists container + is returned and that the actual data is determined with a client-side request. + ''' + course_key = CourseKey.from_string(course_key_string) + if not has_course_author_access(request.user, course_key): + raise PermissionDenied() + + course_module = modulestore().get_course(course_key) + return render_to_response('checklists.html', { + 'language_code': request.LANGUAGE_CODE, + 'context_course': course_module, + }) diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index 6e7833f992408492183b5055da9a093c2bf4c3f3..1e8cef3022c5eb4e7d0caf4d7688cfb9411005de 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -637,6 +637,7 @@ def course_index(request, course_key): deprecated_blocks_info = _deprecated_blocks_info(course_module, deprecated_block_names) return render_to_response('course_outline.html', { + 'language_code': request.LANGUAGE_CODE, 'context_course': course_module, 'lms_link': lms_link, 'sections': sections, diff --git a/cms/static/sass/views/_outline.scss b/cms/static/sass/views/_outline.scss index ac06a835b927538532e08d7354c5fe3c3316de75..1a996c04bf65ac7280151362c0acd1ca4d938d17 100644 --- a/cms/static/sass/views/_outline.scss +++ b/cms/static/sass/views/_outline.scss @@ -185,12 +185,13 @@ .status-release, .status-pacing, - .status-highlights-enabled { + .status-highlights-enabled, + .status-checklist { @extend %t-copy-base; display: inline-block; color: $color-copy-base; - margin-right: $baseline; + margin-right: ($baseline/2); // STATE: hover &:hover { diff --git a/cms/templates/checklists.html b/cms/templates/checklists.html new file mode 100644 index 0000000000000000000000000000000000000000..af36cc6e31fe50b8ce30dbc5afcd5bf384a99212 --- /dev/null +++ b/cms/templates/checklists.html @@ -0,0 +1,55 @@ +<%page expression_filter="h"/> +<%inherit file="base.html" /> +<%def name="online_help_token()"><% return "files" %></%def> +<%! + from cms.djangoapps.contentstore.config.waffle_utils import should_show_checklists_quality + from django.core.urlresolvers import reverse + from django.utils.translation import ugettext as _ + from openedx.core.djangolib.markup import HTML, Text + from openedx.core.djangolib.js_utils import js_escaped_string, dump_js_escaped_json +%> +<%block name="title">${_("Checklists")}</%block> +<%block name="bodyclass">is-signedin course uploads view-uploads</%block> + +<%namespace name='static' file='static_content.html'/> + +<%block name="header_extras"> + <link rel="stylesheet" type="text/css" href="${static.url('common/css/vendor/common.min.css')}" /> +</%block> + +<%block name="content"> + +<div class="wrapper-mast wrapper"> + <header class="mast has-actions has-subtitle"> + <h3 class="page-header"> + <small class="subtitle">${_("Content")}</small> + <span class="sr">- </span>${_("Checklists")} + </h3> + </header> +</div> + +<div class="wrapper-content wrapper"> + <div class="content"> + <%static:studiofrontend entry="courseHealthCheck"> + { + "lang": "${language_code | n, js_escaped_string}", + "course": { + "id": "${context_course.id | n, js_escaped_string}", + "name": "${context_course.display_name_with_default | n, js_escaped_string}", + "is_course_self_paced": ${context_course.self_paced | n, dump_js_escaped_json}, + "url_name": "${context_course.location.name | n, js_escaped_string}", + "org": "${context_course.location.org | n, js_escaped_string}", + "num": "${context_course.location.course | n, js_escaped_string}", + "display_course_number": "${context_course.display_coursenumber | n, js_escaped_string}", + "revision": "${context_course.location.revision | n, js_escaped_string}" + }, + "help_tokens": { + "files": "${get_online_help_info(online_help_token())['doc_url'] | n, js_escaped_string}" + }, + "enable_quality": ${should_show_checklists_quality(request.user, context_course.id) | n, dump_js_escaped_json} + } + </%static:studiofrontend> + </div> +</div> + +</%block> diff --git a/cms/templates/course_outline.html b/cms/templates/course_outline.html index 9b5c3f3824320d4726b655d9a4e5d2362962bdce..fb7a97e19caf5bcfbd96e52c6ec075c8212465ab 100644 --- a/cms/templates/course_outline.html +++ b/cms/templates/course_outline.html @@ -3,9 +3,10 @@ <%def name="online_help_token()"><% return "develop_course" %></%def> <%! import logging +from cms.djangoapps.contentstore.config.waffle_utils import should_show_checklists_page, should_show_checklists_quality from util.date_utils import get_default_time_display from django.utils.translation import ugettext as _ -from openedx.core.djangolib.js_utils import dump_js_escaped_json +from openedx.core.djangolib.js_utils import js_escaped_string, dump_js_escaped_json from contentstore.utils import reverse_usage_url from openedx.core.djangolib.markup import HTML, Text %> @@ -155,7 +156,6 @@ from openedx.core.djangolib.markup import HTML, Text <h2 class="status-release-label">${_("Course Start Date")}</h2> <br> <p class="status-release-value">${course_release_date}</p> - <ul class="status-actions"> <li class="action-item action-edit"> <a href="${settings_url}" class="edit-button action-button" data-tooltip="${_("Edit Start Date")}"> @@ -175,6 +175,31 @@ from openedx.core.djangolib.markup import HTML, Text % endif </div> <div class="status-highlights-enabled"></div> + % if should_show_checklists_page(request.user): + <div class="status-checklist"> + <%static:studiofrontend entry="courseOutlineHealthCheck"> + { + "lang": "${language_code | n, js_escaped_string}", + "course": { + "id": "${context_course.id | n, js_escaped_string}", + "name": "${context_course.display_name_with_default | n, js_escaped_string}", + "course_release_date": "${course_release_date | n, js_escaped_string}", + "is_course_self_paced": ${context_course.self_paced | n, dump_js_escaped_json}, + "url_name": "${context_course.location.name | n, js_escaped_string}", + "org": "${context_course.location.org | n, js_escaped_string}", + "num": "${context_course.location.course | n, js_escaped_string}", + "display_course_number": "${context_course.display_coursenumber | n, js_escaped_string}", + "revision": "${context_course.location.revision | n, js_escaped_string}" + }, + "help_tokens": { + "files": "${get_online_help_info(online_help_token())['doc_url'] | n, js_escaped_string}" + }, + "enable_quality": ${should_show_checklists_quality(request.user, context_course.id) | n, dump_js_escaped_json} + } + </%static:studiofrontend> + + </div> + % endif </div> <div class="wrapper-dnd" % if getattr(context_course, 'language'): diff --git a/cms/urls.py b/cms/urls.py index 44d9282a247025689e8176d6a74ca2e605130769..d2771b357c63c53e4f352845135ccf0539990e4b 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -92,6 +92,11 @@ urlpatterns = [ name='course_search_index_handler' ), url(r'^course/{}?$'.format(settings.COURSE_KEY_PATTERN), contentstore.views.course_handler, name='course_handler'), + + url(r'^checklists/{}?$'.format(settings.COURSE_KEY_PATTERN), + contentstore.views.checklists_handler, + name='checklists_handler'), + url(r'^course_notifications/{}/(?P<action_state_id>\d+)?$'.format(settings.COURSE_KEY_PATTERN), contentstore.views.course_notifications_handler, name='course_notifications_handler'), diff --git a/package-lock.json b/package-lock.json index 9c2c458bd34472741269681c5c83971bfef17a5b..b63882c4f930c6aa2b44da9d4793b1f4331e01ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6751,7 +6751,7 @@ }, "onetime": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", "dev": true },