diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 2670254fc9a8..3917ce4c8964 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -681,10 +681,10 @@ class CourseFields: # lint-amnesty, pylint: disable=missing-class-docstring # until we get grade integration set up. # Explicit comparison to True because we always want to return a bool. hide_progress_tab = Boolean( - display_name=_("Hide Progress Tab"), - help=_("Allows hiding of the progress tab."), + display_name=_("Hide Score Tab"), + help=_("Allows hiding of the score tab."), scope=Scope.settings, - deprecated=True + default=True ) display_organization = String( diff --git a/openedx/features/wikimedia_features/progress_tab/apps.py b/openedx/features/wikimedia_features/progress_tab/apps.py new file mode 100644 index 000000000000..075c97ddff1d --- /dev/null +++ b/openedx/features/wikimedia_features/progress_tab/apps.py @@ -0,0 +1,25 @@ +""" +Progress Tab App Config +""" +from django.apps import AppConfig +from edx_django_utils.plugins import PluginURLs, PluginSettings +from openedx.core.djangoapps.plugins.constants import ProjectType, SettingsType + + +class ProgressTabConfig(AppConfig): + name = 'openedx.features.wikimedia_features.progress_tab' + + plugin_app = { + PluginURLs.CONFIG: { + ProjectType.LMS: { + PluginURLs.NAMESPACE: 'progress_tab', + PluginURLs.REGEX: '^progress_tab/', + PluginURLs.RELATIVE_PATH: 'urls', + }, + }, + PluginSettings.CONFIG: { + ProjectType.LMS: { + SettingsType.COMMON: {PluginSettings.RELATIVE_PATH: 'settings.common'}, + }, + } + } diff --git a/openedx/features/wikimedia_features/progress_tab/settings/__init__.py b/openedx/features/wikimedia_features/progress_tab/settings/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/openedx/features/wikimedia_features/progress_tab/settings/common.py b/openedx/features/wikimedia_features/progress_tab/settings/common.py new file mode 100644 index 000000000000..ec3c69fb59d7 --- /dev/null +++ b/openedx/features/wikimedia_features/progress_tab/settings/common.py @@ -0,0 +1,8 @@ + +"""Settings""" + + +def plugin_settings(settings): + """ + Required Common settings + """ diff --git a/openedx/features/wikimedia_features/wikimedia_general/tab.py b/openedx/features/wikimedia_features/progress_tab/tab.py similarity index 73% rename from openedx/features/wikimedia_features/wikimedia_general/tab.py rename to openedx/features/wikimedia_features/progress_tab/tab.py index fc590211cadf..ca093f26bf0f 100644 --- a/openedx/features/wikimedia_features/wikimedia_general/tab.py +++ b/openedx/features/wikimedia_features/progress_tab/tab.py @@ -1,4 +1,3 @@ -from django.conf import settings from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_noop @@ -10,11 +9,13 @@ class WikimediaProgressTab(TabFragmentViewMixin, EnrolledTab): type = 'wikimedia_progress_tab' title = ugettext_noop('Progress') priority = None - fragment_view_name = 'openedx.features.wikimedia_features.wikimedia_general.views.WikimediaProgressFragmentView' + fragment_view_name = 'openedx.features.wikimedia_features.progress_tab.views.WikimediaProgressFragmentView' is_hideable = False is_default = True body_class = 'wikimedia_progress' @classmethod def is_enabled(cls, course, user=None): + if not super().is_enabled(course, user=user): + return False return True diff --git a/openedx/features/wikimedia_features/progress_tab/urls.py b/openedx/features/wikimedia_features/progress_tab/urls.py new file mode 100644 index 000000000000..794b52dedb03 --- /dev/null +++ b/openedx/features/wikimedia_features/progress_tab/urls.py @@ -0,0 +1,13 @@ +from django.conf.urls import url, include + +from .views import WikimediaProgressFragmentView + +app_name = 'progress_tab' + +urlpatterns = [ + url( + r'progress_fragment_view$', + WikimediaProgressFragmentView.as_view(), + name='progress_fragment_view' + ), +] diff --git a/openedx/features/wikimedia_features/progress_tab/views.py b/openedx/features/wikimedia_features/progress_tab/views.py new file mode 100644 index 000000000000..b76c6e433a4e --- /dev/null +++ b/openedx/features/wikimedia_features/progress_tab/views.py @@ -0,0 +1,139 @@ +from opaque_keys.edx.keys import CourseKey +from django.template.loader import render_to_string +from web_fragments.fragment import Fragment +from openedx.core.djangoapps.plugin_api.views import EdxFragmentView +from completion.models import BlockCompletion +from openedx.features.course_experience.utils import get_course_outline_block_tree +from lms.djangoapps.courseware.courses import get_course_blocks_completion_summary, get_course_with_access +from lms.djangoapps.courseware.views.views import get_cert_data +from lms.djangoapps.grades.api import CourseGradeFactory +from common.djangoapps.student.models import CourseEnrollment + +class WikimediaProgressFragmentView(EdxFragmentView): + """ + View to render Wikimedia course progress tab + """ + def _get_grade_dict(self, course_grade): + """ + Extract grade dict in following format from edX grade classes for subsections and problem components. + { + "subsection1_block_id": { + "score": 1/2, + "graded": True + }, + "problem1_block_id": { + "score": 1/1, + "graded": True + }, + "problem2_block_id": { + "score": 0/1, + "graded": True + } + } + """ + data = {} + if not course_grade: + return data + + for chapter in course_grade.chapter_grades.values(): + subsections = chapter.get('sections', []) + for subsection in subsections: + subsection_grade = subsection.graded_total + subsection_score = "-" + if subsection_grade and subsection_grade.possible: + subsection_score = "{}/{}".format(round(subsection_grade.earned), round(subsection_grade.possible)) + data.update({str(subsection.location): { + 'score': subsection_score, + 'graded': subsection_grade.graded + }}) + for problem_location, problem_grade in subsection.problem_scores.items(): + data.update({str(problem_location): { + 'score': "{}/{}".format(round(problem_grade.earned), round(problem_grade.possible)), + 'graded': problem_grade.graded, + }}) + return data + + def _update_context_with_score_and_progress(self, block, grade_dict): + """ + Update given block dict with grade and progress percentage info. + """ + if not block: + return + + block_grade_data = grade_dict.get(block.get('id')) + if block_grade_data: + block.update(block_grade_data) + + progress = 0 + unit_count = 0 + children = block.get('children', []) + if not len(children): + # Mark Excluded xblocks as completed + if block.get('type') in ['discussion']: + block.update({'complete': True, 'is_excluded_block': True}) + + if block.get('complete'): + progress = 100 + + # handle case of empty sections/subsections/units + if block.get('type') not in ['course', 'chapter', 'vertical', 'sequential']: + unit_count = 1 + + block.update({ + 'progress': progress, + 'unit_count': unit_count, + }) + + for child in children: + self._update_context_with_score_and_progress(child, grade_dict) + if not child.get('is_excluded_block', False): + progress += child.get('progress', 0) * child.get('unit_count', 0) + unit_count += child.get('unit_count', 0) + + if children and unit_count: + block.update({ + 'progress': round(progress/unit_count, 1), + 'unit_count': unit_count + }) + + def render_to_fragment(self, request, course_id=None, **kwargs): + """ + Render wikimedia course progress to a fragment. + Args: + request: The Django request. + course_id: The id of the course in question. + + Returns: + Fragment: The fragment representing the Wikimedia course progress + """ + course_key = CourseKey.from_string(course_id) + user = request.user + show_score_tab = False + cert_data = None + enrollment_mode, _ = CourseEnrollment.enrollment_mode_for_user(user, course_key) + + try: + course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) + if hasattr(course, 'enable_score_tab_on_progress_page'): + show_score_tab = course.enable_score_tab_on_progress_page + course_grade = CourseGradeFactory().read(user, course) + grade_dict = self._get_grade_dict(course_grade) + cert_data = get_cert_data(user, course, enrollment_mode, course_grade) + except Exception as err: + grade_dict = {} + + course_outline_context = get_course_outline_block_tree(request, course_id, user) + self._update_context_with_score_and_progress(course_outline_context, grade_dict) + html = render_to_string( + "wikimedia_general/wikimedia_progress_fragment.html", + { + "course_id": course_id, + "data": course_outline_context, + "show_score_tab": show_score_tab, + "certificate_data": cert_data, + }, + ) + + fragment = Fragment(html) + self.add_fragment_resource_urls(fragment) + return fragment diff --git a/openedx/features/wikimedia_features/wikimedia_general/templates/wikimedia_general/wikimedia_progress_fragment.html b/openedx/features/wikimedia_features/wikimedia_general/templates/wikimedia_general/wikimedia_progress_fragment.html index 1326f80889f5..0f24986aac34 100644 --- a/openedx/features/wikimedia_features/wikimedia_general/templates/wikimedia_general/wikimedia_progress_fragment.html +++ b/openedx/features/wikimedia_features/wikimedia_general/templates/wikimedia_general/wikimedia_progress_fragment.html @@ -5,12 +5,50 @@ <%namespace name='static' file='../static_content.html'/> <%! +from django.urls import reverse from django.utils.translation import ugettext as _ +from lms.djangoapps.certificates.data import CertificateStatuses %> + +<%block name="js_extra"> + + +
+
+
+ <%block name="certificate_block"> + %if certificate_data: +
+
+ <% post_url = reverse('generate_user_cert', args=[course_id]) %> +
+
+
+

${_(certificate_data.title)}

+

${_(certificate_data.msg)}

+
+
+
+
+ %if certificate_data.cert_web_view_url: + ${_("View Certificate")} ${_("Opens in a new browser window")} + %elif certificate_data.cert_status == CertificateStatuses.downloadable and certificate_data.download_url: + ${_("Download Your Certificate")} ${_("Opens in a new browser window")} + %elif certificate_data.cert_status == CertificateStatuses.requesting: + + %endif +
+
+
+
+
+ %endif + +
% for section in data.get('children', []):
diff --git a/openedx/features/wikimedia_features/wikimedia_general/urls.py b/openedx/features/wikimedia_features/wikimedia_general/urls.py index 14b6556d0484..cb9968e21ed2 100644 --- a/openedx/features/wikimedia_features/wikimedia_general/urls.py +++ b/openedx/features/wikimedia_features/wikimedia_general/urls.py @@ -3,16 +3,9 @@ """ from django.conf.urls import url, include -from .views import WikimediaProgressFragmentView - app_name = 'wikimedia_general' urlpatterns = [ - url( - r'progress_fragment_view$', - WikimediaProgressFragmentView.as_view(), - name='progress_fragment_view' - ), url( r'^api/v0/', include('openedx.features.wikimedia_features.wikimedia_general.api.v0.urls', namespace='general_api_v0') diff --git a/openedx/features/wikimedia_features/wikimedia_general/views.py b/openedx/features/wikimedia_features/wikimedia_general/views.py index 57ecbe748f0b..a12de20d2c3d 100644 --- a/openedx/features/wikimedia_features/wikimedia_general/views.py +++ b/openedx/features/wikimedia_features/wikimedia_general/views.py @@ -1,135 +1,3 @@ -from opaque_keys.edx.keys import CourseKey -from django.template.loader import render_to_string -from web_fragments.fragment import Fragment -from openedx.core.djangoapps.plugin_api.views import EdxFragmentView -from completion.models import BlockCompletion -from openedx.features.course_experience.utils import get_course_outline_block_tree -from lms.djangoapps.courseware.courses import get_course_blocks_completion_summary, get_course_with_access -from lms.djangoapps.grades.api import CourseGradeFactory - - -class WikimediaProgressFragmentView(EdxFragmentView): - """ - View to render Wikimedia course progress tab - """ - def _get_grade_dict(self, course_grade): - """ - Extract grade dict in following format from edX grade classes for subsections and problem components. - { - "subsection1_block_id": { - "score": 1/2, - "graded": True - }, - "problem1_block_id": { - "score": 1/1, - "graded": True - }, - "problem2_block_id": { - "score": 0/1, - "graded": True - } - } - """ - data = {} - if not course_grade: - return data - - for chapter in course_grade.chapter_grades.values(): - subsections = chapter.get('sections', []) - for subsection in subsections: - subsection_grade = subsection.graded_total - subsection_score = "-" - if subsection_grade and subsection_grade.possible: - subsection_score = "{}/{}".format(round(subsection_grade.earned), round(subsection_grade.possible)) - data.update({str(subsection.location): { - 'score': subsection_score, - 'graded': subsection_grade.graded - }}) - for problem_location, problem_grade in subsection.problem_scores.items(): - data.update({str(problem_location): { - 'score': "{}/{}".format(round(problem_grade.earned), round(problem_grade.possible)), - 'graded': problem_grade.graded, - }}) - return data - - def _update_context_with_score_and_progress(self, block, grade_dict): - """ - Update given block dict with grade and progress percentage info. - """ - if not block: - return - - block_grade_data = grade_dict.get(block.get('id')) - if block_grade_data: - block.update(block_grade_data) - - progress = 0 - unit_count = 0 - children = block.get('children', []) - if not len(children): - # Mark Excluded xblocks as completed - if block.get('type') in ['discussion']: - block.update({'complete': True, 'is_excluded_block': True}) - - if block.get('complete'): - progress = 100 - - # handle case of empty sections/subsections/units - if block.get('type') not in ['course', 'chapter', 'vertical', 'sequential']: - unit_count = 1 - - block.update({ - 'progress': progress, - 'unit_count': unit_count, - }) - - for child in children: - self._update_context_with_score_and_progress(child, grade_dict) - if not child.get('is_excluded_block', False): - progress += child.get('progress', 0) * child.get('unit_count', 0) - unit_count += child.get('unit_count', 0) - - if children and unit_count: - block.update({ - 'progress': round(progress/unit_count, 1), - 'unit_count': unit_count - }) - - def render_to_fragment(self, request, course_id=None, **kwargs): - """ - Render wikimedia course progress to a fragment. - Args: - request: The Django request. - course_id: The id of the course in question. - - Returns: - Fragment: The fragment representing the Wikimedia course progress - """ - course_key = CourseKey.from_string(course_id) - user = request.user - show_score_tab = False - - try: - course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) - if hasattr(course, 'enable_score_tab_on_progress_page'): - show_score_tab = course.enable_score_tab_on_progress_page - course_grade = CourseGradeFactory().read(user, course) - grade_dict = self._get_grade_dict(course_grade) - except Exception as err: - grade_dict = {} - - course_outline_context = get_course_outline_block_tree( - request, course_id, request.user - ) - self._update_context_with_score_and_progress(course_outline_context, grade_dict) - html = render_to_string( - 'wikimedia_general/wikimedia_progress_fragment.html', - { - "data": course_outline_context, - "show_score_tab": show_score_tab, - } - ) - - fragment = Fragment(html) - self.add_fragment_resource_urls(fragment) - return fragment +""" +Wikimedia General Views +""" diff --git a/setup.py b/setup.py index 7c4f0787eaa7..b5b34a622870 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,7 @@ "teams = lms.djangoapps.teams.plugins:TeamsTab", "textbooks = lms.djangoapps.courseware.tabs:TextbookTabs", "wiki = lms.djangoapps.course_wiki.tab:WikiTab", - "wikimedia_progress_tab = openedx.features.wikimedia_features.wikimedia_general.tab:WikimediaProgressTab", + "wikimedia_progress_tab = openedx.features.wikimedia_features.progress_tab.tab:WikimediaProgressTab", ], "openedx.course_app": [ "calculator = lms.djangoapps.courseware.plugins:CalculatorCourseApp",