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_get_dates_for_course(self): items = make_items() api.set_dates_for_course(items[0][0].course_key, items) retrieved = api.get_dates_for_course(items[0][0].course_key) assert len(retrieved) == NUM_OVERRIDES first = items[0] assert retrieved[(first[0], 'due')] == first[1]['due'] # second time is cached retrieved = api.get_dates_for_course(items[0][0].course_key) assert len(retrieved) == NUM_OVERRIDES # third time with new course_id course2 = DummyCourse(id='course-v1:testX+tt202+2019') course2.save() new_items = make_items(course2.id) enrollment2 = DummyEnrollment(user=self.user, course=course2) enrollment2.save() schedule2 = DummySchedule(enrollment=enrollment2, start_date=datetime(2019, 4, 1)) schedule2.save() api.set_dates_for_course(new_items[0][0].course_key, new_items) new_retrieved = api.get_dates_for_course(new_items[0][0].course_key) assert len(new_retrieved) == NUM_OVERRIDES first_id = list(new_retrieved.keys())[0][0] last_id = list(retrieved.keys())[0][0] assert first_id.course_key != last_id.course_key return items
def test_get_dates_for_course_outline(self): items = make_items() course_key = items[0][0].course_key items.append((make_block_id(course_key, block_type='video'), {'start': datetime(2019, 3, 21), 'test': '1'})) api.set_dates_for_course(course_key, items) # Ensure the video block *was* returned normally. retrieved = api.get_dates_for_course( course_key, subsection_and_higher_only=False, published_version=self.course_version ) assert len(retrieved) == NUM_OVERRIDES + 1 # Ensure the video block *was not* returned with subsection and higher blocks. retrieved = api.get_dates_for_course( course_key, subsection_and_higher_only=True, published_version=self.course_version ) assert len(retrieved) == NUM_OVERRIDES # Set all the ContentDates for this course's structural blocks to have # None for their block_type to test compatibilty with a half-migrated # state. They should still be returned by get_dates_for_course with # subsection_and_higher_only=True. structural_dates = models.ContentDate.objects.filter( course_id=course_key, block_type__in=['course', 'chapter', 'sequential'] ) structural_dates.update(block_type=None) retrieved = api.get_dates_for_course( course_key, subsection_and_higher_only=True, published_version=self.course_version, use_cached=False ) assert len(retrieved) == NUM_OVERRIDES
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_get_dates_no_schedule(self): items = make_items(with_relative=True) course_key = items[0][0].course_key api.set_dates_for_course(course_key, items) retrieved = api.get_dates_for_course(course_key, user=self.user) assert len(retrieved) == 6 self.schedule.delete() retrieved = api.get_dates_for_course(course_key, user=self.user, use_cached=False) assert len(retrieved) == 3
def test_relative_date_past_cutoff_date(self): course_key = CourseLocator('testX', 'tt101', '2019') start_block = make_block_id(course_key, block_type='course') start_date = datetime(2019, 3, 15) first_block = make_block_id(course_key) first_delta = timedelta(days=1) second_block = make_block_id(course_key) second_delta = timedelta(days=10) end_block = make_block_id(course_key, block_type='course') end_date = datetime(2019, 4, 20) items = [ (start_block, { 'start': start_date }), # start dates are always absolute (first_block, { 'due': first_delta }), # relative (second_block, { 'due': second_delta }), # relative (end_block, { 'end': end_date }), # end dates are always absolute ] api.set_dates_for_course(course_key, items) # Try one with just enough as a sanity check self.schedule.created = end_date - second_delta self.schedule.save() dates = [ ((start_block, 'start'), start_date), ((first_block, 'due'), self.schedule.start_date + first_delta), ((second_block, 'due'), self.schedule.start_date + second_delta), ((end_block, 'end'), end_date), ] assert api.get_dates_for_course(course_key, schedule=self.schedule) == dict(dates) cache.clear() DEFAULT_REQUEST_CACHE.clear() # Now set schedule start date too close to the end date and verify that we no longer get due dates self.schedule.created = datetime(2019, 4, 15) self.schedule.save() dates = [ ((start_block, 'start'), start_date), ((first_block, 'due'), None), ((second_block, 'due'), None), ((end_block, 'end'), end_date), ] assert api.get_dates_for_course(course_key, schedule=self.schedule) == dict(dates)
def test_clear_dates_for_course(self): items = self.test_get_dates_for_course() keep_date = models.ContentDate.objects.get(location=items[1][0]) with self.assertNumQueries(1): api._clear_dates_for_course(items[0][0].course_key, keep=[keep_date.id]) # pylint: disable=protected-access retrieved = api.get_dates_for_course(items[0][0].course_key, use_cached=False) self.assertEqual(len(retrieved), 1) self.assertEqual(list(retrieved.keys())[0][0], items[1][0]) with self.assertNumQueries(1): api._clear_dates_for_course(items[0][0].course_key) # pylint: disable=protected-access self.assertEqual(api.get_dates_for_course(items[0][0].course_key, use_cached=False), {})
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 get_units_with_due_date(course): """ Returns all top level units which have due dates. Does not return descendents of those nodes. """ units = [] # Pass in a schedule here so that we get back any relative dates in the course, but actual value # doesn't matter, since we don't care about the dates themselves, just whether they exist. # Thus we don't save or care about this temporary schedule object. schedule = Schedule(start_date=course.start) course_dates = api.get_dates_for_course(course.id, schedule=schedule) def visit(node): """ Visit a node. Checks to see if node has a due date and appends to `units` if it does. Otherwise recurses into children to search for nodes with due dates. """ if (node.location, 'due') in course_dates: units.append(node) else: for child in node.get_children(): visit(child) visit(course) #units.sort(key=_title_or_url) return units
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 get_course_assignment_due_dates(course, user, request, num_return=None, include_past_dates=False): """ Returns a list of assignment (at the subsection/sequential level) due date blocks for the given course. Will return num_return results or all results if num_return is None in date increasing order. """ store = modulestore() all_course_dates = get_dates_for_course(course.id, user) date_blocks = [] for (block_key, date_type), date in all_course_dates.items(): if date_type == 'due' and block_key.block_type == 'sequential': item = store.get_item(block_key) if item.graded: date_block = CourseAssignmentDate(course, user) date_block.date = date block_url = None now = datetime.now().replace(tzinfo=pytz.UTC) assignment_released = item.start < now if item.start else None if assignment_released: block_url = reverse('jump_to', args=[course.id, block_key]) block_url = request.build_absolute_uri(block_url) if request else None assignment_title = item.display_name if item.display_name else _('Assignment') date_block.set_title(assignment_title, link=block_url) date_blocks.append(date_block) date_blocks = sorted((b for b in date_blocks if b.is_enabled or include_past_dates), key=date_block_key_fn) if num_return: return date_blocks[:num_return] return date_blocks
def test_relative_date_past_end_date(self): course_key = CourseLocator('testX', 'tt101', '2019') start_block = make_block_id(course_key, block_type='course') start_date = datetime(2019, 3, 15) before_end_date_block = make_block_id(course_key) before_end_date_delta = timedelta(days=1) before_end_date = self.schedule.start_date + before_end_date_delta after_end_date_block = make_block_id(course_key) after_end_date_delta = timedelta(days=10) end_block = make_block_id(course_key, block_type='course') end_date = datetime(2019, 4, 4) self.schedule.created = datetime(2019, 3, 20) # set a while back, before the 4/1 start_date self.schedule.save() items = [ (start_block, {'start': start_date}), # start dates are always absolute (before_end_date_block, {'due': before_end_date_delta}), # relative (after_end_date_block, {'due': after_end_date_delta}), # relative (end_block, {'end': end_date}), # end dates are always absolute ] api.set_dates_for_course(course_key, items) dates = [ ((start_block, 'start'), start_date), ((before_end_date_block, 'due'), before_end_date), # Because the end date for this block would have been after the course end date, # the block will have an end date of the course end date ((after_end_date_block, 'due'), end_date), ((end_block, 'end'), end_date), ] assert api.get_dates_for_course(course_key, schedule=self.schedule) == dict(dates)
def test_get_dates_for_course_query_counts(self, has_schedule, pass_user_object, pass_schedule, item_count): if not has_schedule: self.schedule.delete() items = [(make_block_id(self.course.id), { 'due': datetime(2020, 1, 1) + timedelta(days=i) }) for i in range(item_count)] api.set_dates_for_course(self.course.id, items) user = self.user if pass_user_object else self.user.id schedule = self.schedule if pass_schedule and has_schedule else None if has_schedule and pass_schedule: query_count = 2 else: query_count = 3 with self.assertNumQueries(query_count): dates = api.get_dates_for_course(course_id=self.course.id, user=user, schedule=schedule) # Second time, the request cache eliminates all querying... with self.assertNumQueries(0): cached_dates = api.get_dates_for_course(course_id=self.course.id, user=user, schedule=schedule) assert dates == cached_dates # Now wipe the request cache... DEFAULT_REQUEST_CACHE.clear() # This time, test the external cache (which eliminates the one large # query to ContentDates). with self.assertNumQueries(query_count - 1): externally_cached_dates = api.get_dates_for_course( course_id=self.course.id, user=user, schedule=schedule) assert dates == externally_cached_dates # Finally, force uncached behavior with used_cache=False with self.assertNumQueries(query_count): uncached_dates = api.get_dates_for_course(course_id=self.course.id, user=user, schedule=schedule, use_cached=False) assert dates == uncached_dates
def test_get_dates_for_course_query_counts(self, has_schedule, pass_user_object, pass_schedule, item_count): if not has_schedule: self.schedule.delete() items = [ (make_block_id(self.course.id), {'due': datetime(2020, 1, 1) + timedelta(days=i)}) for i in range(item_count) ] api.set_dates_for_course(self.course.id, items) user = self.user if pass_user_object else self.user.id schedule = self.schedule if pass_schedule and has_schedule else None if has_schedule and pass_schedule: query_count = 2 else: query_count = 3 with self.assertNumQueries(query_count): dates = api.get_dates_for_course( course_id=self.course.id, user=user, schedule=schedule ) # Second time, the request cache eliminates all querying (sometimes)... # If a schedule is not provided, we will get the schedule to avoid caching outdated dates with self.assertNumQueries(0 if schedule else 1): cached_dates = api.get_dates_for_course( course_id=self.course.id, user=user, schedule=schedule ) assert dates == cached_dates # Now wipe all cache tiers... TieredCache.dangerous_clear_all_tiers() # No cached values - so will do *all* queries again. with self.assertNumQueries(query_count): externally_cached_dates = api.get_dates_for_course( course_id=self.course.id, user=user, schedule=schedule ) assert dates == externally_cached_dates # Finally, force uncached behavior with used_cache=False with self.assertNumQueries(query_count): uncached_dates = api.get_dates_for_course( course_id=self.course.id, user=user, schedule=schedule, use_cached=False ) assert dates == uncached_dates
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 load_data(self): """Pull dates information from edx-when.""" # (usage_key, 'due'): datetime.datetime(2019, 12, 11, 15, 0, tzinfo=<UTC>) # TODO: Merge https://github.com/edx/edx-when/pull/48 and add `outline_only=True` self.dates = get_dates_for_course(self.course_key, self.user) for (usage_key, field_name), date in self.dates.items(): self.keys_to_schedule_fields[usage_key][field_name] = date course_usage_key = self.course_key.make_usage_key('course', 'course') self._course_start = self.keys_to_schedule_fields[course_usage_key].get('start') self._course_end = self.keys_to_schedule_fields[course_usage_key].get('end') self._is_beta_tester = user_has_role(self.user, CourseBetaTesterRole(self.course_key))
def get_due_dates(request, course_key, user): """ Get due date information for a user for blocks in a course. Arguments: request: the request object course_key (CourseKey): the CourseKey for the course user: the user object for which we want due date information Returns: due_dates (list): a list of dictionaries containing due date information keys: name: the display name of the block url: the deep link to the block date: the due date for the block """ try: outline = get_course_outline(course_key) except (ValueError, CourseOutlineData.DoesNotExist): # Either this course is Old Mongo-backed or doesn't have a generated course outline. course_version = None else: course_version = outline.published_version dates = get_dates_for_course(course_key, user, published_version=course_version) store = modulestore() due_dates = [] for (block_key, date_type), date in dates.items(): if date_type == 'due': try: block_display_name = store.get_item(block_key).display_name except ItemNotFoundError: logger.exception( f'Failed to get block for due date item with key: {block_key}' ) block_display_name = UNKNOWN_BLOCK_DISPLAY_NAME # get url to the block in the course block_url = reverse('jump_to', args=[course_key, block_key]) block_url = request.build_absolute_uri(block_url) due_dates.append({ 'name': block_display_name, 'url': block_url, 'date': date, }) return due_dates
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 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 get_due_dates(request, course_key, user): """ Get due date information for a user for blocks in a course. Arguments: request: the request object course_key (CourseKey): the CourseKey for the course user: the user object for which we want due date information Returns: due_dates (list): a list of dictionaries containing due date information keys: name: the display name of the block url: the deep link to the block date: the due date for the block """ dates = get_dates_for_course( course_key, user, ) store = modulestore() due_dates = [] for (block_key, date_type), date in six.iteritems(dates): if date_type == 'due': try: block_display_name = store.get_item(block_key).display_name except ItemNotFoundError: logger.exception( 'Failed to get block for due date item with key: {}'. format(block_key)) block_display_name = UNKNOWN_BLOCK_DISPLAY_NAME # get url to the block in the course block_url = reverse('jump_to', args=[course_key, block_key]) block_url = request.build_absolute_uri(block_url) due_dates.append({ 'name': block_display_name, 'url': block_url, 'date': date, }) return due_dates
def get_due_dates(request, course_key, user): """ Get due date information for a user for blocks in a course. Arguments: request: the request object course_key (CourseKey): the CourseKey for the course user: the user object for which we want due date information Returns: due_dates (list): a list of dictionaries containing due date information keys: name: the display name of the block url: the deep link to the block date: the due date for the block """ dates = get_dates_for_course( course_key, user, ) store = modulestore() due_dates = [] for (block_key, date_type), date in iteritems(dates): if date_type == 'due': block = store.get_item(block_key) # get url to the block in the course block_url = reverse('jump_to', args=[course_key, block_key]) block_url = request.build_absolute_uri(block_url) due_dates.append({ 'name': block.display_name, 'url': block_url, 'date': date, }) return due_dates
def get_course_assignments(course_key, user, request, include_access=False): """ Returns a list of assignment (at the subsection/sequential level) due dates for the given course. Each returned object is a namedtuple with fields: block_key, title, url, date, requires_full_access """ store = modulestore() all_course_dates = get_dates_for_course(course_key, user) block_data = course_blocks_api.get_course_blocks( user, store.make_course_usage_key(course_key)) assignments = [] for (block_key, date_type), date in all_course_dates.items(): if date_type != 'due' or block_key.block_type != 'sequential': continue if block_key not in block_data: continue block = block_data[block_key] if not block.graded: continue requires_full_access = include_access and _requires_full_access( block_data, block, user) title = block.display_name or _('Assignment') url = None assignment_released = not block.start or block.start < datetime.now( pytz.UTC) if assignment_released: url = reverse('jump_to', args=[course_key, block_key]) url = request and request.build_absolute_uri(url) assignments.append( _Assignment(block_key, title, url, date, requires_full_access)) return assignments
def get_course_assignments(course_key, user, request, include_access=False): """ Returns a list of assignment (at the subsection/sequential level) due dates for the given course. Each returned object is a namedtuple with fields: block_key, title, url, date, requires_full_access """ store = modulestore() all_course_dates = get_dates_for_course(course_key, user) assignments = [] for (block_key, date_type), date in all_course_dates.items(): if date_type != 'due' or block_key.block_type != 'sequential': continue try: item = store.get_item(block_key) except ItemNotFoundError: continue if not item.graded: continue requires_full_access = include_access and _requires_full_access( store, user, block_key) title = item.display_name or _('Assignment') url = None assignment_released = not item.start or item.start < datetime.now( pytz.UTC) if assignment_released: url = reverse('jump_to', args=[course_key, block_key]) url = request and request.build_absolute_uri(url) assignments.append( _Assignment(block_key, title, url, date, requires_full_access)) return assignments
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
def test_clear_dates_for_course(self): items = self.test_get_dates_for_course() api.clear_dates_for_course(items[0][0].course_key) retrieved = api.get_dates_for_course(items[0][0].course_key, use_cached=False) assert not retrieved
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)