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_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_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_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_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_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_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