def test_remove_user_override(self, initial_date, override_date, expected_date): items = make_items() first = items[0] block_id = first[0] items[0][1]['due'] = initial_date api.set_dates_for_course(six.text_type(block_id.course_key), items) api.set_date_for_block(block_id.course_key, block_id, 'due', override_date, user=self.user) DEFAULT_REQUEST_CACHE.clear() retrieved = api.get_dates_for_course(block_id.course_key, user=self.user.id) assert len(retrieved) == NUM_OVERRIDES assert retrieved[block_id, 'due'] == expected_date api.set_date_for_block(block_id.course_key, block_id, 'due', None, user=self.user) DEFAULT_REQUEST_CACHE.clear() retrieved = api.get_dates_for_course(block_id.course_key, user=self.user.id) assert len(retrieved) == NUM_OVERRIDES if isinstance(initial_date, timedelta): user_initial_date = self.schedule.start_date + initial_date else: user_initial_date = initial_date assert retrieved[block_id, 'due'] == user_initial_date
def test_set_user_override(self, initial_date, override_date, expected_date): items = make_items() first = items[0] block_id = first[0] items[0][1]['due'] = initial_date api.set_dates_for_course(str(block_id.course_key), items) api.set_date_for_block(block_id.course_key, block_id, 'due', override_date, user=self.user) DEFAULT_REQUEST_CACHE.clear() cache.clear() retrieved = api.get_dates_for_course(block_id.course_key, user=self.user.id) assert len(retrieved) == NUM_OVERRIDES assert retrieved[block_id, 'due'] == expected_date overrides = api.get_overrides_for_block(block_id.course_key, block_id) assert len(overrides) == 1 assert overrides[0][2] == expected_date overrides = list( api.get_overrides_for_user(block_id.course_key, self.user)) assert len(overrides) == 1 assert overrides[0] == { 'location': block_id, 'actual_date': expected_date }
def set_due_date_extension(course, unit, student, due_date, actor=None): """ Sets a due date extension. Raises DashboardError if the unit or extended due date is invalid. """ if due_date: try: api.set_date_for_block(course.id, unit.location, 'due', due_date, user=student, reason=None, actor=actor) except api.MissingDateError: raise DashboardError( _(u"Unit {0} has no due date to extend.").format( unit.location)) except api.InvalidDateError: raise DashboardError( _("An extended due date must be later than the original due date." )) else: api.set_date_for_block(course.id, unit.location, 'due', None, user=student, reason=None, actor=actor)
def test_transform(self, _mock): override = datetime.datetime(2020, 1, 1) api.set_date_for_block(self.items[0][0].course_key, self.items[0][0], 'due', override, user=self.user) usage_info = mock.MagicMock() usage_info.course_key = self.course_id block_structure = mock.MagicMock() transformer = field_data.DateOverrideTransformer(self.user) transformer.transform(usage_info, block_structure) assert block_structure.override_xblock_field.call_count == NUM_OVERRIDES args = block_structure.override_xblock_field.call_args_list for arg in args: call_args = arg[0] if call_args[0] == self.items[0][0]: assert call_args[2] == override # now make it raise exceptions # attributeerror is swallowed # in the case where a block does not exist for some reason block_structure.override_xblock_field.side_effect = AttributeError() transformer.transform(usage_info, block_structure) # other exceptions should bubble up block_structure.override_xblock_field.side_effect = ValueError() with self.assertRaises(ValueError): transformer.transform(usage_info, block_structure)
def test_hidden_content_with_transformer_override(self): """ Tests content is hidden if the date changes after collection and during the transform phase (for example, by the DateOverrideTransformer). """ with mock_registered_transformers( [DateOverrideTransformer, self.TRANSFORMER_CLASS_TO_TEST]): transformers = BlockStructureTransformers([ DateOverrideTransformer(self.student), self.TRANSFORMER_CLASS_TO_TEST() ]) block = self.get_block(1) block.hide_after_due = True update_block(block) set_date_for_block(self.course.id, block.location, 'due', self.DueDateType.PAST_DATE) # Due date is in the past so some blocks are hidden self.assert_transform_results( self.student, self.ALL_BLOCKS - {1, 3, 4}, blocks_with_differing_access=None, transformers=transformers, ) # Set an override for the due date to be in the future set_date_for_block(self.course.id, block.location, 'due', self.DueDateType.FUTURE_DATE, user=self.student) # this line is just to bust the cache for the user so it returns the updated date. get_dates_for_course(self.course.id, user=self.student, use_cached=False) # Now all blocks are returned for the student self.assert_transform_results( self.student, self.ALL_BLOCKS, blocks_with_differing_access=None, transformers=transformers, ) # But not for a different user different_user = UserFactory() with mock_registered_transformers( [DateOverrideTransformer, self.TRANSFORMER_CLASS_TO_TEST]): transformers = BlockStructureTransformers([ DateOverrideTransformer(different_user), self.TRANSFORMER_CLASS_TO_TEST() ]) self.assert_transform_results( different_user, self.ALL_BLOCKS - {1, 3, 4}, blocks_with_differing_access=None, transformers=transformers, )
def test_set_user_override_invalid_date(self, initial_date, override_date): items = make_items() first = items[0] block_id = first[0] items[0][1]['due'] = initial_date api.set_dates_for_course(str(block_id.course_key), items) with self.assertRaises(api.InvalidDateError): api.set_date_for_block(block_id.course_key, block_id, 'due', override_date, user=self.user)
def test_set_user_override_invalid_block(self): items = make_items() first = items[0] block_id = first[0] api.set_dates_for_course(str(block_id.course_key), items) with self.assertRaises(api.MissingDateError): # can't set a user override for content without a date bad_block_id = make_block_id() api.set_date_for_block(bad_block_id.course_key, bad_block_id, 'due', datetime(2019, 4, 6), user=self.user)
def test_get_schedules_with_due_date_for_abs_date(self): self.schedule.start_date = datetime(2019, 3, 22) items = make_items(with_relative=False) assignment_date = items[0][1].get('due') api.set_date_for_block(items[0][0].course_key, items[0][0], 'due', assignment_date) # Specify the actual assignment due date so this will return true schedules = api.get_schedules_with_due_date(items[0][0].course_key, datetime.date(assignment_date)) assert len(schedules) > 0 for schedule in schedules: assert schedule.enrollment.course_id == items[0][0].course_key assert schedule.enrollment.user.id == self.user.id
def test_set_date_for_block_query_counts(self): args = (self.course.id, make_block_id(self.course.id), 'due', datetime(2019, 3, 22)) # Each date we make has: # 1 get & 1 create for the date itself # 1 get & 1 create for the sub-policy with self.assertNumQueries(4): api.set_date_for_block(*args) # When setting same items, we should only do initial read with self.assertNumQueries(1): api.set_date_for_block(*args)
def test_set_date_for_block(self, initial_date, override_date, expected_date): items = make_items() first = items[0] block_id = first[0] items[0][1]['due'] = initial_date api.set_dates_for_course(str(block_id.course_key), items) api.set_date_for_block(block_id.course_key, block_id, 'due', override_date) TieredCache.dangerous_clear_all_tiers() retrieved = api.get_dates_for_course(block_id.course_key, user=self.user.id) assert len(retrieved) == NUM_OVERRIDES assert retrieved[block_id, 'due'] == expected_date
def set_due_date_extension(course, unit, student, due_date, actor=None, reason=''): """ Sets a due date extension. Raises DashboardError if the unit or extended due date is invalid. """ if due_date: try: api.set_date_for_block(course.id, unit.location, 'due', due_date, user=student, reason=reason, actor=actor) except api.MissingDateError: raise DashboardError(_(u"Unit {0} has no due date to extend.").format(unit.location)) except api.InvalidDateError: raise DashboardError(_("An extended due date must be later than the original due date.")) else: api.set_date_for_block(course.id, unit.location, 'due', None, user=student, reason=reason, actor=actor)
def test_get_user_date_no_schedule(self): items = make_items() course_key = items[0][0].course_key api.set_dates_for_course(course_key, items) before_override = api.get_dates_for_course(course_key, user=self.user) assert len(before_override) == 3 # Override a date for the user with a relative date, but remove the schedule # so that the override can't be applied api.set_date_for_block(course_key, items[0][0], 'due', timedelta(days=2), user=self.user) self.schedule.delete() after_override = api.get_dates_for_course(course_key, user=self.user, use_cached=False) assert before_override == after_override
def test_get_schedules_with_due_date_for_rel_date(self): items = make_items(with_relative=False) api.set_dates_for_course(items[0][0].course_key, items) relative_date = timedelta(days=2) api.set_date_for_block(items[0][0].course_key, items[0][0], 'due', relative_date) assignment_date = items[0][1].get('due') + relative_date # Move the schedule's start to the first assignment's original due since it's now offset self.schedule.start_date = items[0][1].get('due') self.schedule.save() # Specify the actual assignment due date so this will return true schedules = api.get_schedules_with_due_date(items[0][0].course_key, assignment_date.date()) assert len(schedules) > 0 for schedule in schedules: assert schedule.enrollment.course_id == items[0][0].course_key assert schedule.enrollment.user.id == self.user.id
def test_get_schedules_with_due_date_for_abs_user_dates(self): items = make_items(with_relative=True) api.set_dates_for_course(items[0][0].course_key, items) assignment_date = items[0][1].get('due') api.set_date_for_block(items[0][0].course_key, items[0][0], 'due', assignment_date, user=self.user) models.UserDate.objects.create( abs_date=assignment_date, user=self.user, content_date=models.ContentDate.objects.first(), ) # Specify the actual assignment due date so this will return true schedules = api.get_schedules_with_due_date(items[0][0].course_key, assignment_date.date()) assert len(schedules) == 1 # Make sure there's only one schedule, we should not have duplicates assert schedules[0].enrollment.course_id == items[0][0].course_key assert schedules[0].enrollment.user.id == self.user.id
def move_overrides_to_edx_when(apps, schema_editor): from xmodule.fields import Date from edx_when import api date_field = Date() StudentFieldOverride = apps.get_model('courseware', 'StudentFieldOverride') log = logging.getLogger(__name__) for override in StudentFieldOverride.objects.filter(field='due'): try: abs_date = date_field.from_json(json.loads(override.value)) api.set_date_for_block( override.course_id, override.location, 'due', abs_date, user=override.student) except Exception: # pylint: disable=broad-except log.exception("migrating %d %r: %r", override.id, override.location, override.value)
def test_set_date_for_block(self, initial_date, override_date, expected_date): items = make_items() first = items[0] block_id = first[0] items[0][1]['due'] = initial_date api.set_dates_for_course(str(block_id.course_key), items) api.set_date_for_block(block_id.course_key, block_id, 'due', override_date) DEFAULT_REQUEST_CACHE.clear() cache.clear() retrieved = api.get_dates_for_course(block_id.course_key, user=self.user.id) assert len(retrieved) == NUM_OVERRIDES assert retrieved[block_id, 'due'] == expected_date
def set_due_date_extension(course, unit, student, due_date, actor=None, reason=''): """ Sets a due date extension. Raises: DashboardError if the unit or extended, due date is invalid or user is not enrolled in the course. """ mode, __ = CourseEnrollment.enrollment_mode_for_user(user=student, course_id=six.text_type(course.id)) if not mode: raise DashboardError(_("Could not find student enrollment in the course.")) if due_date: try: api.set_date_for_block(course.id, unit.location, 'due', due_date, user=student, reason=reason, actor=actor) except api.MissingDateError: raise DashboardError(_(u"Unit {0} has no due date to extend.").format(unit.location)) except api.InvalidDateError: raise DashboardError(_("An extended due date must be later than the original due date.")) else: api.set_date_for_block(course.id, unit.location, 'due', None, user=student, reason=reason, actor=actor)
def test_remove_user_override(self, initial_date, override_date, expected_date): items = make_items() first = items[0] block_id = first[0] items[0][1]['due'] = initial_date api.set_dates_for_course(str(block_id.course_key), items) api.set_date_for_block(block_id.course_key, block_id, 'due', override_date, user=self.user) TieredCache.dangerous_clear_all_tiers() retrieved = api.get_dates_for_course(block_id.course_key, user=self.user.id) assert len(retrieved) == NUM_OVERRIDES assert retrieved[block_id, 'due'] == expected_date api.set_date_for_block(block_id.course_key, block_id, 'due', None, user=self.user) TieredCache.dangerous_clear_all_tiers() retrieved = api.get_dates_for_course(block_id.course_key, user=self.user.id) assert len(retrieved) == NUM_OVERRIDES if isinstance(initial_date, timedelta): user_initial_date = self.schedule.start_date + initial_date else: user_initial_date = initial_date assert retrieved[block_id, 'due'] == user_initial_date
def set_due_date_extension(course, unit, student, due_date, actor=None, reason=''): """ Sets a due date extension. Raises: DashboardError if the unit or extended, due date is invalid or user is not enrolled in the course. """ mode, __ = CourseEnrollment.enrollment_mode_for_user( user=student, course_id=six.text_type(course.id)) if not mode: raise DashboardError( _("Could not find student enrollment in the course.")) # We normally set dates at the subsection level. But technically dates can be anywhere down the tree (and # usually are in self paced courses, where the subsection date gets propagated down). # So find all children that we need to set the date on, then set those dates. course_dates = api.get_dates_for_course(course.id, user=student) blocks_to_set = { unit } # always include the requested unit, even if it doesn't appear to have a due date now def visit(node): """ Visit a node. Checks to see if node has a due date and appends to `blocks_to_set` if it does. And recurses into children to search for nodes with due dates. """ if (node.location, 'due') in course_dates: blocks_to_set.add(node) for child in node.get_children(): visit(child) visit(unit) for block in blocks_to_set: if due_date: try: api.set_date_for_block(course.id, block.location, 'due', due_date, user=student, reason=reason, actor=actor) except api.MissingDateError: raise DashboardError( _(u"Unit {0} has no due date to extend.").format( unit.location)) except api.InvalidDateError: raise DashboardError( _("An extended due date must be later than the original due date." )) else: api.set_date_for_block(course.id, block.location, 'due', None, user=student, reason=reason, actor=actor)
def test_allow_relative_dates(self): course_key = CourseLocator('testX', 'tt101', '2019') block1 = make_block_id(course_key) date1 = datetime(2019, 3, 22) block2 = make_block_id(course_key) date2 = datetime(2019, 3, 23) date2_override_delta = timedelta(days=10) date2_override = date2 + date2_override_delta block3 = make_block_id(course_key) date3_delta = timedelta(days=1) date3 = self.schedule.start_date + date3_delta block4 = make_block_id(course_key) date4_delta = timedelta(days=2) date4 = self.schedule.start_date + date4_delta date4_override = datetime(2019, 4, 24) items = [ (block1, { 'due': date1 }), # absolute (block2, { 'due': date2 }), # absolute, to be overwritten by relative date (block3, { 'due': date3_delta }), # relative (block4, { 'due': date4_delta }), # relative, to be overwritten by absolute date ] api.set_dates_for_course(course_key, items) api.set_date_for_block(course_key, block2, 'due', date2_override_delta, user=self.user) api.set_date_for_block(course_key, block4, 'due', date4_override, user=self.user) # get_dates_for_course dates = [ ((block1, 'due'), date1), ((block2, 'due'), date2), ((block3, 'due'), date3), ((block4, 'due'), date4), ] user_dates = [ ((block1, 'due'), date1), ((block2, 'due'), date2_override), ] assert api.get_dates_for_course(course_key, schedule=self.schedule) == dict(dates) with patch('edx_when.api._are_relative_dates_enabled', return_value=False): assert api.get_dates_for_course( course_key, schedule=self.schedule) == dict(dates[0:2]) assert api.get_dates_for_course(course_key, schedule=self.schedule, user=self.user) == dict(user_dates) # get_date_for_block assert api.get_date_for_block(course_key, block2) == date2 assert api.get_date_for_block(course_key, block4, user=self.user) == date4_override with patch('edx_when.api._are_relative_dates_enabled', return_value=False): assert api.get_date_for_block(course_key, block2) == date2 assert api.get_date_for_block(course_key, block1, user=self.user) == date1 assert api.get_date_for_block(course_key, block2, user=self.user) == date2_override assert api.get_date_for_block(course_key, block4, user=self.user) is None # get_overrides_for_block block2_overrides = [(self.user.username, 'unknown', date2_override)] assert api.get_overrides_for_block(course_key, block2) == block2_overrides with patch('edx_when.api._are_relative_dates_enabled', return_value=False): assert api.get_overrides_for_block(course_key, block2) == [ (self.user.username, 'unknown', date2_override) ] # get_overrides_for_user user_overrides = [ { 'location': block4, 'actual_date': date4_override }, { 'location': block2, 'actual_date': date2_override }, ] assert list(api.get_overrides_for_user(course_key, self.user)) == user_overrides with patch('edx_when.api._are_relative_dates_enabled', return_value=False): assert list(api.get_overrides_for_user( course_key, self.user)) == user_overrides