diff --git a/cms/djangoapps/contentstore/config/waffle.py b/cms/djangoapps/contentstore/config/waffle.py index e9c1bd90cd..b81f731f28 100644 --- a/cms/djangoapps/contentstore/config/waffle.py +++ b/cms/djangoapps/contentstore/config/waffle.py @@ -67,3 +67,33 @@ REDIRECT_TO_LIBRARY_AUTHORING_MICROFRONTEND = LegacyWaffleFlag( flag_name='library_authoring_mfe', module_name=__name__, ) + + +# .. toggle_name: studio.pages_and_resources_mfe +# .. toggle_implementation: CourseWaffleFlag +# .. toggle_default: False +# .. toggle_description: Waffle flag to link existing studio views to the new Pages and Resources experience. +# .. toggle_use_cases: temporary, open_edx +# .. toggle_creation_date: 2021-05-24 +# .. toggle_target_removal_date: 2021-12-31 +# .. toggle_warnings: Also set settings.COURSE_AUTHORING_MICROFRONTEND_URL. +# .. toggle_tickets: None +ENABLE_PAGES_AND_RESOURCES_MICROFRONTEND = CourseWaffleFlag( + waffle_namespace=waffle_flags(), + flag_name='pages_and_resources_mfe', + module_name=__name__, +) + +# .. toggle_name: studio.custom_pls +# .. toggle_implementation: CourseWaffleFlag +# .. toggle_default: False (except for SuperUsers) +# .. toggle_description: Waffle flag to enable custom pacing for PLS +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2021-06-15 +# .. toggle_target_removal_date: 2021-12-31 +# .. toggle_warnings: None +# .. toggle_tickets: None +CUSTOM_PLS = CourseWaffleFlag(WAFFLE_NAMESPACE, 'custom_pls', module_name=__name__,) + +def custom_pls_is_active(course_key): + return CUSTOM_PLS.is_enabled(course_key) diff --git a/cms/djangoapps/contentstore/views/item.py b/cms/djangoapps/contentstore/views/item.py index 2e65e83532..c429bccee9 100644 --- a/cms/djangoapps/contentstore/views/item.py +++ b/cms/djangoapps/contentstore/views/item.py @@ -2,7 +2,7 @@ import logging from collections import OrderedDict -from datetime import datetime +from datetime import datetime, timedelta from functools import partial from uuid import uuid4 diff --git a/cms/static/js/spec/views/pages/course_outline_spec.js b/cms/static/js/spec/views/pages/course_outline_spec.js index afb5d5cfd3..93d245a5df 100644 --- a/cms/static/js/spec/views/pages/course_outline_spec.js +++ b/cms/static/js/spec/views/pages/course_outline_spec.js @@ -294,7 +294,7 @@ describe('CourseOutlinePage', function() { TemplateHelpers.installTemplates([ 'course-outline', 'xblock-string-field-editor', 'modal-button', 'basic-modal', 'course-outline-modal', 'release-date-editor', - 'due-date-editor', 'grading-editor', 'publish-editor', + 'due-date-editor', 'self-paced-due-date-editor', 'grading-editor', 'publish-editor', 'staff-lock-editor', 'unit-access-editor', 'content-visibility-editor', 'settings-modal-tabs', 'timed-examination-preference-editor', 'access-editor', 'show-correctness-editor', 'highlights-editor', 'highlights-enable-editor', diff --git a/cms/static/js/views/modals/course_outline_modals.js b/cms/static/js/views/modals/course_outline_modals.js index 7419a0ae10..eded671fa7 100644 --- a/cms/static/js/views/modals/course_outline_modals.js +++ b/cms/static/js/views/modals/course_outline_modals.js @@ -17,7 +17,8 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', AbstractEditor, BaseDateEditor, ReleaseDateEditor, DueDateEditor, GradingEditor, PublishEditor, AbstractVisibilityEditor, StaffLockEditor, UnitAccessEditor, ContentVisibilityEditor, TimedExaminationPreferenceEditor, - AccessEditor, ShowCorrectnessEditor, HighlightsEditor, HighlightsEnableXBlockModal, HighlightsEnableEditor; + AccessEditor, ShowCorrectnessEditor, HighlightsEditor, HighlightsEnableXBlockModal, HighlightsEnableEditor, + SelfPacedDueDateEditor; CourseOutlineXBlockModal = BaseModal.extend({ events: _.extend({}, BaseModal.prototype.events, { @@ -74,6 +75,7 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', event.preventDefault(); requestData = this.getRequestData(); + console.log(requestData) if (!_.isEqual(requestData, {metadata: {}})) { XBlockViewUtils.updateXBlockFields(this.model, requestData, { success: this.options.onSave @@ -389,6 +391,35 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', } }); + SelfPacedDueDateEditor = BaseDateEditor.extend({ + fieldName: 'due', + templateName: 'self-paced-due-date-editor', + className: 'modal-section-content has-actions due-date-input grading-due-date', + + getValue: function() { + return this.$('#due_date').val(); + }, + + clearValue: function(event) { + event.preventDefault(); + this.$('#due_date').val(''); + }, + + getRequestData: function() { + let currentDate = parseInt(this.getValue()) + if (parseInt(this.getValue())){ + currentDate = new Date() + currentDate.setDate(currentDate.getDate() + parseInt(this.getValue())*7) + }; + // due_num_weeks + return { + metadata: { + due: currentDate + } + }; + } + }); + ReleaseDateEditor = BaseDateEditor.extend({ fieldName: 'start', templateName: 'release-date-editor', @@ -1078,6 +1109,10 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', tabs[0].editors = [ReleaseDateEditor, GradingEditor, DueDateEditor]; tabs[1].editors = [ContentVisibilityEditor, ShowCorrectnessEditor]; + if (course.get('self_paced')) { + tabs[0].editors.push(SelfPacedDueDateEditor) + } + if (options.enable_proctored_exams || options.enable_timed_exams) { advancedTab.editors.push(TimedExaminationPreferenceEditor); } diff --git a/cms/templates/course_outline.html b/cms/templates/course_outline.html index 0bf9ce60d4..e9f6f3ad54 100644 --- a/cms/templates/course_outline.html +++ b/cms/templates/course_outline.html @@ -29,7 +29,7 @@ from django.urls import reverse <%block name="header_extras"> -% for template_name in ['course-outline', 'xblock-string-field-editor', 'basic-modal', 'modal-button', 'course-outline-modal', 'due-date-editor', 'release-date-editor', 'grading-editor', 'publish-editor', 'staff-lock-editor', 'unit-access-editor', 'content-visibility-editor', 'verification-access-editor', 'timed-examination-preference-editor', 'access-editor', 'settings-modal-tabs', 'show-correctness-editor', 'highlights-editor', 'highlights-enable-editor', 'course-highlights-enable']: +% for template_name in ['course-outline', 'xblock-string-field-editor', 'basic-modal', 'modal-button', 'course-outline-modal', 'due-date-editor', 'self-paced-due-date-editor', 'release-date-editor', 'grading-editor', 'publish-editor', 'staff-lock-editor', 'unit-access-editor', 'content-visibility-editor', 'verification-access-editor', 'timed-examination-preference-editor', 'access-editor', 'settings-modal-tabs', 'show-correctness-editor', 'highlights-editor', 'highlights-enable-editor', 'course-highlights-enable']: diff --git a/cms/templates/js/self-paced-due-date-editor.underscore b/cms/templates/js/self-paced-due-date-editor.underscore new file mode 100644 index 0000000000..77ed3fb65c --- /dev/null +++ b/cms/templates/js/self-paced-due-date-editor.underscore @@ -0,0 +1,16 @@ + + + diff --git a/common/lib/xmodule/xmodule/modulestore/xml_importer.py b/common/lib/xmodule/xmodule/modulestore/xml_importer.py index 07b184cf6c..57db76be52 100644 --- a/common/lib/xmodule/xmodule/modulestore/xml_importer.py +++ b/common/lib/xmodule/xmodule/modulestore/xml_importer.py @@ -1051,7 +1051,7 @@ def allowed_metadata_by_category(category): return { 'vertical': [], 'chapter': ['start'], - 'sequential': ['due', 'format', 'start', 'graded'] + 'sequential': ['due', 'due_num_weeks', 'format', 'start', 'graded'] }.get(category, ['*']) diff --git a/common/lib/xmodule/xmodule/seq_module.py b/common/lib/xmodule/xmodule/seq_module.py index 3cd2fd4833..71da55d3bd 100644 --- a/common/lib/xmodule/xmodule/seq_module.py +++ b/common/lib/xmodule/xmodule/seq_module.py @@ -79,6 +79,12 @@ class SequenceFields: # lint-amnesty, pylint: disable=missing-class-docstring scope=Scope.settings, ) + due_num_weeks = Integer( + display_name = _("Number of Weeks Due By"), + help=_("Enter the number of weeks the problems are due by"), + scope = Scope.settings, + ) + hide_after_due = Boolean( display_name=_("Hide sequence content After Due Date"), help=_( @@ -195,6 +201,7 @@ class ProctoringFields: default=False, scope=Scope.settings, ) + def _get_course(self): """ diff --git a/lms/djangoapps/course_api/blocks/serializers.py b/lms/djangoapps/course_api/blocks/serializers.py index 277bf2a668..870f834888 100644 --- a/lms/djangoapps/course_api/blocks/serializers.py +++ b/lms/djangoapps/course_api/blocks/serializers.py @@ -51,6 +51,7 @@ SUPPORTED_FIELDS = [ SupportedFieldType('format'), SupportedFieldType('start'), SupportedFieldType('due'), + SupportedFieldType('due_num_weeks'), SupportedFieldType('contains_gated_content'), SupportedFieldType('has_score'), SupportedFieldType('has_scheduled_content'), diff --git a/openedx/core/djangoapps/course_date_signals/handlers.py b/openedx/core/djangoapps/course_date_signals/handlers.py index 263916c538..e44b130adc 100644 --- a/openedx/core/djangoapps/course_date_signals/handlers.py +++ b/openedx/core/djangoapps/course_date_signals/handlers.py @@ -1,8 +1,11 @@ """Signal handlers for writing course dates into edx_when.""" +from datetime import timedelta, datetime +import datetime import logging +from cms.djangoapps.contentstore.config.waffle import custom_pls_is_active from django.dispatch import receiver from edx_when.api import FIELDS_TO_EXTRACT, set_dates_for_course @@ -27,6 +30,11 @@ def _field_values(fields, xblock): if field_name not in xblock.fields: continue field = xblock.fields[field_name] + if field_name == 'due': + print("THIS IS THE FIELD ", field) + print(xblock) + result[field.name] = field.read_from(xblock) + continue if field.scope == Scope.settings and field.is_set_on(xblock): try: result[field.name] = field.read_from(xblock) @@ -83,7 +91,34 @@ def extract_dates_from_course(course): Extract all dates from the supplied course. """ log.info('Extracting course dates for %s', course.id) - if course.self_paced: + + if course.self_paced and custom_pls_is_active(course.id): + print("This is self paced ") + date_items = [] + store = modulestore() + with store.branch_setting(ModuleStoreEnum.Branch.published_only, course.id): + items = store.get_items(course.id) + log.info('Extracting dates from %d items in %s', len(items), course.id) + print("B4 the items sections") + # new_fields_to_extract = FIELDS_TO_EXTRACT + ('due_num_weeks',) + # print("THe new fields to extract ", new_fields_to_extract) + for item in items: + metadata = _field_values(FIELDS_TO_EXTRACT, item) + print("THIS IS THE METADATA ", metadata) + metadata['due'] = datetime.datetime.now() - metadata['due'] + + # print("TYPE OF DATES: ", metadata) + # print("RIGHT NOW, ", datetime.datetime.now()) + # print(metadata['due']) + # metadata['due'] = datetime.datetime.now() - metadata['due'] + # print('metadata due: ', metadata['due']) + metadata.pop('due_num_weeks',None) + # print("THIS IS THE DUE DATE: ", metadata['due']) + date_items.append((item.location, metadata)) + # date_items.append((item.location, _field_values(FIELDS_TO_EXTRACT, item))) + print("Here are the date items: ", date_items) + + elif course.self_paced and not custom_pls_is_active(course.id): metadata = _field_values(FIELDS_TO_EXTRACT, course) # self-paced courses may accidentally have a course due date metadata.pop('due', None) @@ -94,6 +129,7 @@ def extract_dates_from_course(course): # unless that item already has a relative date set for _, section, weeks_to_complete in spaced_out_sections(course): section_date_items = [] + print("THESE IS THE WEEKS TO COMPLETE ,", weeks_to_complete) for subsection in section.get_children(): section_date_items.extend(_gather_graded_items(subsection, weeks_to_complete)) @@ -108,6 +144,7 @@ def extract_dates_from_course(course): log.info('Extracting dates from %d items in %s', len(items), course.id) for item in items: date_items.append((item.location, _field_values(FIELDS_TO_EXTRACT, item))) + return date_items