def cron_action(self, app_context, global_state): job = ComputeCounts(app_context) ns = app_context.get_namespace_name() dto = TotalEnrollmentDAO.load_or_default(ns) if job.is_active(): # Weekly re-computation of enrollments counters, so forcibly stop # any already-running job and start over. if not dto.is_empty: logging.warning( 'CANCELING periodic "%s" enrollments total refresh found' ' unexpectedly still running.', dto.id) elif dto.is_pending: logging.warning( 'INTERRUPTING missing "%s" enrollments total' ' initialization started on %s.', dto.id, utc.to_text(seconds=dto.last_modified)) job.cancel() else: when = dto.last_modified if not dto.is_empty: logging.info( 'REFRESHING existing "%s" enrollments total, %d as of %s.', dto.id, dto.get(), utc.to_text(seconds=when)) elif dto.is_pending: since = dto.seconds_since_last_modified() logging.warning( 'COMPLETING "%s" enrollments total initialization started ' 'on %s stalled for %s.', dto.id, utc.to_text(seconds=when), datetime.timedelta(seconds=since)) job.submit()
def init_missing_total(enrolled_total_dto, app_context): """Returns True if a ComputeCounts MapReduceJob was submitted.""" name = enrolled_total_dto.id when = enrolled_total_dto.last_modified if not enrolled_total_dto.is_empty: logging.warning(StartInitMissingCounts.LOG_SKIPPING_FMT, name, enrolled_total_dto.get(), utc.to_text(seconds=when)) return False delta = enrolled_total_dto.seconds_since_last_modified() if enrolled_total_dto.is_pending: if not enrolled_total_dto.is_stalled: logging.info( 'PENDING "%s" enrollments total initialization in progress' ' for %s, since %s.', name, datetime.timedelta(seconds=delta), utc.to_text(seconds=when)) return False logging.warning( 'STALLED "%s" enrollments total initialization for %s, since %s.', name, datetime.timedelta(seconds=delta), utc.to_text(seconds=when)) # enrolled_total_dto is either *completely* missing (not *just* lacking its # counter property, and thus not indicating initialization of that count # is in progress), or pending initialization has stalled (taken more than # MAX_PENDING_SEC to complete), so store "now" as a last_modified value to # indicate that a MapReduce update has been requested. marked_dto = TotalEnrollmentDAO.mark_pending(dto=enrolled_total_dto, namespace_name=app_context.get_namespace_name()) logging.info('SCHEDULING "%s" enrollments total update at %s.', marked_dto.id, utc.to_text(seconds=marked_dto.last_modified)) ComputeCounts(app_context).submit() return True
def cron_action(self, app_context, global_state): job = ComputeCounts(app_context) ns = app_context.get_namespace_name() dto = TotalEnrollmentDAO.load_or_default(ns) if job.is_active(): # Weekly re-computation of enrollments counters, so forcibly stop # any already-running job and start over. if not dto.is_empty: logging.warning( 'CANCELING periodic "%s" enrollments total refresh found' ' unexpectedly still running.', dto.id) elif dto.is_pending: logging.warning( 'INTERRUPTING missing "%s" enrollments total' ' initialization started on %s.', dto.id, utc.to_text(seconds=dto.last_modified)) job.cancel() else: when = dto.last_modified if not dto.is_empty: logging.info( 'REFRESHING existing "%s" enrollments total, %d as of %s.', dto.id, dto.get(), utc.to_text(seconds=when)) elif dto.is_pending: since = dto.seconds_since_last_modified() logging.warning( 'COMPLETING "%s" enrollments total initialization started ' 'on %s stalled for %s.', dto.id, utc.to_text(seconds=when), datetime.timedelta(seconds=since)) job.submit()
def init_missing_total(enrolled_total_dto, app_context): """Returns True if a ComputeCounts MapReduceJob was submitted.""" name = enrolled_total_dto.id when = enrolled_total_dto.last_modified if not enrolled_total_dto.is_empty: logging.warning(StartInitMissingCounts.LOG_SKIPPING_FMT, name, enrolled_total_dto.get(), utc.to_text(seconds=when)) return False delta = enrolled_total_dto.seconds_since_last_modified() if enrolled_total_dto.is_pending: if not enrolled_total_dto.is_stalled: logging.info( 'PENDING "%s" enrollments total initialization in progress' ' for %s, since %s.', name, datetime.timedelta(seconds=delta), utc.to_text(seconds=when)) return False logging.warning( 'STALLED "%s" enrollments total initialization for %s, since %s.', name, datetime.timedelta(seconds=delta), utc.to_text(seconds=when)) # enrolled_total_dto is either *completely* missing (not *just* lacking its # counter property, and thus not indicating initialization of that count # is in progress), or pending initialization has stalled (taken more than # MAX_PENDING_SEC to complete), so store "now" as a last_modified value to # indicate that a MapReduce update has been requested. marked_dto = TotalEnrollmentDAO.mark_pending( dto=enrolled_total_dto, namespace_name=app_context.get_namespace_name()) logging.info('SCHEDULING "%s" enrollments total update at %s.', marked_dto.id, utc.to_text(seconds=marked_dto.last_modified)) ComputeCounts(app_context).submit() return True
def test_announcement_draft_status(self): key = self._add_announcement() data = { 'key': key, 'date': utc.to_text(seconds=0), 'html': 'Twas brillig, and the slithy toves', 'title': 'Jabberwocky', 'is_draft': True, } self._put_announcement(data) # Admin sees announcement on course page self._verify_announcements([data['title'] + ' (Private)'], [data['html']]) # Should appear to student as though there are no announcements. actions.login('*****@*****.**') self._verify_announcements([], ['Currently, there are no announcements.']) # Make announcement public actions.login(self.ADMIN_EMAIL) data['is_draft'] = False self._put_announcement(data) # Should now appear to both admin and student. self._verify_announcements([data['title']], [data['html']]) actions.login('*****@*****.**') self._verify_announcements([data['title']], [data['html']])
def test_announcement_draft_status(self): key = self._add_announcement() data = { 'key': key, 'date': utc.to_text(seconds=0), 'html': 'Twas brillig, and the slithy toves', 'title': 'Jabberwocky', 'is_draft': True, } self._put_announcement(data) # Admin sees announcement on course page self._verify_announcements([data['title'] + ' (Private)'], [data['html']]) # Should appear to student as though there are no announcements. actions.login('*****@*****.**') self._verify_announcements([], ['Currently, there are no announcements.']) # Make announcement public actions.login(self.ADMIN_EMAIL) data['is_draft'] = False self._put_announcement(data) # Should now appear to both admin and student. self._verify_announcements([data['title']], [data['html']]) actions.login('*****@*****.**') self._verify_announcements([data['title']], [data['html']])
def test_announcement_news(self): actions.login('*****@*****.**') actions.register(self, 'John Doe') time.sleep(1) locale = 'de' announcement = self._add_announcement_and_translation( locale, is_draft=True) sent_data = { 'key': str(announcement.key()), 'title': 'Test Announcement', 'date': utc.to_text(seconds=utc.now_as_timestamp()), 'is_draft': False, } actions.login(self.ADMIN_EMAIL) response = self._put_announcement(sent_data) actions.login('*****@*****.**') # Verify announcement news item using news API directly news_items = news.CourseNewsDao.get_news_items() self.assertEquals(1, len(news_items)) item = news_items[0] now_timestamp = utc.now_as_timestamp() self.assertEquals( announcements.AnnouncementsStudentHandler.URL.lstrip('/'), item.url) self.assertEquals( str(announcements.TranslatableResourceAnnouncement.key_for_entity( announcement)), item.resource_key) self.assertAlmostEqual( now_timestamp, utc.datetime_to_timestamp(item.when), delta=10) # Verify announcement news item looking at HTTP response to /course response = self.get('course') soup = self.parse_html_string_to_soup(response.body) self.assertEquals( [news_tests_lib.NewsItem( 'Test Announcement', announcements.AnnouncementsStudentHandler.URL.lstrip('/'), True)], news_tests_lib.extract_news_items_from_soup(soup)) # Verify announcement news item translated title. self._set_prefs_locale(locale) response = self.get('course') soup = self.parse_html_string_to_soup(response.body) self.assertEquals( [news_tests_lib.NewsItem( 'TEST ANNOUNCEMENT', announcements.AnnouncementsStudentHandler.URL.lstrip('/'), True)], news_tests_lib.extract_news_items_from_soup(soup)) # Delete the announcement; news item should also go away. actions.login(self.ADMIN_EMAIL) self._delete_announcement(str(announcement.key())) actions.login('*****@*****.**') response = self.get('course') soup = self.parse_html_string_to_soup(response.body) self.assertEquals([], news_tests_lib.extract_news_items_from_soup(soup))
def test_to_text(self): """Confirm precedence of seconds, dt, and st in to_text().""" # Select now_seconds value, because seconds= was supplied. self.assertEquals( utc.to_text(seconds=self.now_seconds, dt=self.day_dt, st=self.month_st), time.strftime(self.ISO_8601_STRUCT_TIME_FORMAT, self.now_st), ) # Select day_st value, because seconds= was not supplied, and # dt was supplied. self.assertEquals( utc.to_text(dt=self.day_dt, st=self.month_st), time.strftime(self.ISO_8601_STRUCT_TIME_FORMAT, self.day_st) ) # Select month value, because seconds= and dt= were not supplied, and # st was supplied. self.assertEquals(utc.to_text(st=self.month_st), time.strftime(self.ISO_8601_STRUCT_TIME_FORMAT, self.month_st))
def cron_action(self, app_context, global_state): total_dto = TotalEnrollmentDAO.load_or_default( app_context.get_namespace_name()) if total_dto.is_missing or total_dto.is_stalled: init_missing_total(total_dto, app_context) else: # Debug-level log message only for tests examining logs. logging.debug(self.LOG_SKIPPING_FMT, total_dto.id, total_dto.get(), utc.to_text(seconds=total_dto.last_modified))
def cron_action(self, app_context, global_state): total_dto = TotalEnrollmentDAO.load_or_default( app_context.get_namespace_name()) if total_dto.is_missing or total_dto.is_stalled: init_missing_total(total_dto, app_context) else: # Debug-level log message only for tests examining logs. logging.debug(self.LOG_SKIPPING_FMT, total_dto.id, total_dto.get(), utc.to_text(seconds=total_dto.last_modified))
def get_course_enrolled(enrolled_dto, course_name): if enrolled_dto.is_empty: # 'count' property is not present, so exit early. return CourseEnrolled( 0, NONE_ENROLLED, _NONE_RECENT_FMT.format(course_name)) count = enrolled_dto.get() lm_dt = utc.timestamp_to_datetime(enrolled_dto.last_modified) lm_text = utc.to_text(dt=lm_dt, fmt=utc.ISO_8601_UTC_HUMAN_FMT) most_recent_enroll = _MOST_RECENT_FMT.format(lm_text, course_name) return CourseEnrolled(count, count, most_recent_enroll)
def test_to_text(self): """Confirm precedence of seconds, dt, and st in to_text().""" # Select now_seconds value, because seconds= was supplied. self.assertEquals( utc.to_text(seconds=self.now_seconds, dt=self.day_dt, st=self.month_st), time.strftime(self.ISO_8601_STRUCT_TIME_FORMAT, self.now_st)) # Select day_st value, because seconds= was not supplied, and # dt was supplied. self.assertEquals( utc.to_text(dt=self.day_dt, st=self.month_st), time.strftime(self.ISO_8601_STRUCT_TIME_FORMAT, self.day_st)) # Select month value, because seconds= and dt= were not supplied, and # st was supplied. self.assertEquals( utc.to_text(st=self.month_st), time.strftime(self.ISO_8601_STRUCT_TIME_FORMAT, self.month_st))
def get_course_enrolled(enrolled_dto, course_name): if enrolled_dto.is_empty: # 'count' property is not present, so exit early. return CourseEnrolled(0, NONE_ENROLLED, _NONE_RECENT_FMT.format(course_name)) count = enrolled_dto.get() lm_dt = utc.timestamp_to_datetime(enrolled_dto.last_modified) lm_text = utc.to_text(dt=lm_dt, fmt=utc.ISO_8601_UTC_HUMAN_FMT) most_recent_enroll = _MOST_RECENT_FMT.format(lm_text, course_name) return CourseEnrolled(count, count, most_recent_enroll)
def test_put_unknown_key(self): key = str(db.Key.from_path(announcements.AnnouncementEntity.kind(), 1)) sent_data = { 'key': key, 'date': utc.to_text(seconds=0), 'html': 'Twas brillig, and the slithy toves', 'title': 'Jabberwocky', 'is_draft': False, } response = self._put_announcement(sent_data, expect_errors=True) self.assertEquals(404, response['status']) self.assertEquals('Object not found.', response['message'])
def test_put_unknown_key(self): key = str(db.Key.from_path(announcements.AnnouncementEntity.kind(), 1)) sent_data = { 'key': key, 'date': utc.to_text(seconds=0), 'html': 'Twas brillig, and the slithy toves', 'title': 'Jabberwocky', 'is_draft': False, } response = self._put_announcement(sent_data, expect_errors=True) self.assertEquals(404, response['status']) self.assertEquals('Object not found.', response['message'])
def marshal(self, the_dict): binned = the_dict.get('binned') if binned: # A copy (to avoid mutating the original) is only necessary if # there are actually int seconds since epoch bin keys that need # to be made compatible with JSON. the_dict = copy.copy(the_dict) the_dict['binned'] = dict( [(utc.to_text(seconds=seconds, fmt=self._BIN_FORMAT), count) for seconds, count in binned.iteritems()]) return super(BinnedEnrollmentsDTO, self).marshal(the_dict)
def marshal(self, the_dict): binned = the_dict.get('binned') if binned: # A copy (to avoid mutating the original) is only necessary if # there are actually int seconds since epoch bin keys that need # to be made compatible with JSON. the_dict = copy.copy(the_dict) the_dict['binned'] = dict( [(utc.to_text(seconds=seconds, fmt=self._BIN_FORMAT), count) for seconds, count in binned.iteritems()]) return super(BinnedEnrollmentsDTO, self).marshal(the_dict)
def test_put_as_student(self): key = self._add_announcement() sent_data = { 'key': key, 'date': utc.to_text(seconds=0), 'html': 'Twas brillig, and the slithy toves', 'title': 'Jabberwocky', 'is_draft': False, } actions.login('*****@*****.**') response = self._put_announcement(sent_data, expect_errors=True) self.assertEquals(401, response['status']) self.assertEquals('Access denied.', response['message'])
def test_put_as_student(self): key = self._add_announcement() sent_data = { 'key': key, 'date': utc.to_text(seconds=0), 'html': 'Twas brillig, and the slithy toves', 'title': 'Jabberwocky', 'is_draft': False, } actions.login('*****@*****.**') response = self._put_announcement(sent_data, expect_errors=True) self.assertEquals(401, response['status']) self.assertEquals('Access denied.', response['message'])
def test_create_announcement_defaults(self): key = self._add_announcement() data = self._get_announcement(key) expected_date = utc.to_text( seconds=utc.day_start(utc.now_as_timestamp())) self.assertEquals(data['date'], expected_date) expected_key = str( db.Key.from_path(announcements.AnnouncementEntity.kind(), 1)) self.assertEquals(data['key'], expected_key) self.assertEquals(data['html'], '') self.assertEquals(data['is_draft'], True) self.assertEquals( data['title'], announcements.AnnouncementsDashboardHandler.DEFAULT_TITLE_TEXT)
def test_put_bad_xsrf(self): key = self._add_announcement() sent_data = { 'key': key, 'date': utc.to_text(seconds=0), 'html': 'Twas brillig, and the slithy toves', 'title': 'Jabberwocky', 'is_draft': False, } response = self._put_announcement(sent_data, expect_errors=True, xsrf_token='gibberish') self.assertEquals(403, response['status']) self.assertEquals( 'Bad XSRF token. Please reload the page and try again', response['message'])
def test_create_announcement_defaults(self): key = self._add_announcement() data = self._get_announcement(key) expected_date = utc.to_text( seconds=utc.day_start(utc.now_as_timestamp())) self.assertEquals(data['date'], expected_date) expected_key = str(db.Key.from_path( announcements.AnnouncementEntity.kind(), 1)) self.assertEquals(data['key'], expected_key) self.assertEquals(data['html'], '') self.assertEquals(data['is_draft'], True) self.assertEquals( data['title'], announcements.AnnouncementsDashboardHandler.DEFAULT_TITLE_TEXT)
def test_announcement_translation_caching(self): LOCALE = 'de' with actions.OverriddenConfig(models.CAN_USE_MEMCACHE.name, True): with actions.OverriddenEnvironment({ 'i18n': { 'course:locale': 'en_US', 'extra_locales': [{ 'locale': LOCALE, 'availability': 'true' }] } }): key = self._add_announcement() data = { 'key': key, 'date': utc.to_text(seconds=0), 'html': 'Unsafe for operation', 'title': 'Attention', 'is_draft': False, } self._put_announcement(data) self._put_translation(data, LOCALE, 'Achtung', 'Gefahrlich!') actions.login('*****@*****.**') actions.register(self, 'John Doe') self._set_prefs_locale(None) self._verify_announcements([data['title']], [data['html']]) self._set_prefs_locale(LOCALE) self._verify_announcements(['Achtung'], ['Gefahrlich!']) # Verify that we have data added to the cache. cached = models.MemcacheManager.get( announcements.AnnouncementEntity._cache_key(LOCALE)) self.assertIsNotNone(cached) # Modify the translated version. actions.login(self.ADMIN_EMAIL) self._put_translation(data, LOCALE, 'Foo', 'Bar') # Verify that the cache has been purged cached = models.MemcacheManager.get( announcements.AnnouncementEntity._cache_key(LOCALE)) self.assertIsNone(cached) # And that the changed translations show up on the page. actions.login('*****@*****.**') self._verify_announcements(['Foo'], ['Bar'])
def test_put_announcement(self): key = self._add_announcement() sent_data = { 'key': key, 'date': utc.to_text(seconds=0), 'html': 'Twas brillig, and the slithy toves', 'title': 'Jabberwocky', 'is_draft': False, } self._put_announcement(sent_data) data = self._get_announcement(key) self.assertEquals(sent_data, data) self._verify_announcements([sent_data['title']], [sent_data['html']])
def test_put_announcement(self): key = self._add_announcement() sent_data = { 'key': key, 'date': utc.to_text(seconds=0), 'html': 'Twas brillig, and the slithy toves', 'title': 'Jabberwocky', 'is_draft': False, } self._put_announcement(sent_data) data = self._get_announcement(key) self.assertEquals(sent_data, data) self._verify_announcements([sent_data['title']], [sent_data['html']])
def test_put_bad_xsrf(self): key = self._add_announcement() sent_data = { 'key': key, 'date': utc.to_text(seconds=0), 'html': 'Twas brillig, and the slithy toves', 'title': 'Jabberwocky', 'is_draft': False, } response = self._put_announcement(sent_data, expect_errors=True, xsrf_token='gibberish') self.assertEquals(403, response['status']) self.assertEquals( 'Bad XSRF token. Please reload the page and try again', response['message'])
def test_announcement_translation_caching(self): LOCALE = 'de' with actions.OverriddenConfig(models.CAN_USE_MEMCACHE.name, True): with actions.OverriddenEnvironment({ 'i18n': { 'course:locale': 'en_US', 'extra_locales': [ {'locale': LOCALE, 'availability': 'true'}] }}): key = self._add_announcement() data = { 'key': key, 'date': utc.to_text(seconds=0), 'html': 'Unsafe for operation', 'title': 'Attention', 'is_draft': False, } self._put_announcement(data) self._put_translation(data, LOCALE, 'Achtung', 'Gefahrlich!') actions.login('*****@*****.**') actions.register(self, 'John Doe') self._set_prefs_locale(None) self._verify_announcements([data['title']], [data['html']]) self._set_prefs_locale(LOCALE) self._verify_announcements(['Achtung'], ['Gefahrlich!']) # Verify that we have data added to the cache. cached = models.MemcacheManager.get( announcements.AnnouncementEntity._cache_key(LOCALE)) self.assertIsNotNone(cached) # Modify the translated version. actions.login(self.ADMIN_EMAIL) self._put_translation(data, LOCALE, 'Foo', 'Bar') # Verify that the cache has been purged cached = models.MemcacheManager.get( announcements.AnnouncementEntity._cache_key(LOCALE)) self.assertIsNone(cached) # And that the changed translations show up on the page. actions.login('*****@*****.**') self._verify_announcements(['Foo'], ['Bar'])
def test_change_base_announcment_updates_i18n_progress(self): LOCALE = 'de' with actions.OverriddenConfig(models.CAN_USE_MEMCACHE.name, True): with actions.OverriddenEnvironment({ 'course': { 'locale': 'en_US', }, 'extra_locales': [{ 'locale': LOCALE, 'availability': 'true' }] }): key = self._add_announcement() data = { 'key': key, 'date': utc.to_text(seconds=0), 'html': 'Unsafe for operation', 'title': 'Attention', 'is_draft': False, } self._put_announcement(data) self._put_translation(data, LOCALE, 'Achtung', 'Gefahrlich!') # Verify that having saved the translation, we are in progress # state DONE. resource_key = str( resource.Key( announcements.ResourceHandlerAnnouncement.TYPE, db.Key(encoded=data['key']).id())) progress = i18n_dashboard.I18nProgressDAO.load(resource_key) self.assertEquals(progress.get_progress(LOCALE), i18n_dashboard.I18nProgressDTO.DONE) # Modify the announcement in the base language. data['title'] = 'Informational' data['html'] = 'Now safe for operation again' self._put_announcement(data) self.execute_all_deferred_tasks() # Verify that saving the base version of the announcement # moves the progress state back. progress = i18n_dashboard.I18nProgressDAO.load(resource_key) self.assertEquals(progress.get_progress(LOCALE), i18n_dashboard.I18nProgressDTO.IN_PROGRESS)
def test_announcement_ordering(self): items = [] for x in xrange(5): key = self._add_announcement() data = { 'key': key, 'date': utc.to_text(seconds=86400 * x), 'html': 'content %d' % x, 'title': 'title %d' % x, 'is_draft': False, } self._put_announcement(data) items.append(data) # Since we added items in increasing timestamp order, we now # reverse, since announcements are listed newest-first. items.reverse() self._verify_announcements( [i['title'] for i in items], [i['html'] for i in items])
def test_announcement_ordering(self): items = [] for x in xrange(5): key = self._add_announcement() data = { 'key': key, 'date': utc.to_text(seconds=86400 * x), 'html': 'content %d' % x, 'title': 'title %d' % x, 'is_draft': False, } self._put_announcement(data) items.append(data) # Since we added items in increasing timestamp order, we now # reverse, since announcements are listed newest-first. items.reverse() self._verify_announcements([i['title'] for i in items], [i['html'] for i in items])
def test_change_base_announcment_updates_i18n_progress(self): LOCALE = 'de' with actions.OverriddenConfig(models.CAN_USE_MEMCACHE.name, True): with actions.OverriddenEnvironment({ 'course': { 'locale': 'en_US', }, 'extra_locales': [{'locale': LOCALE, 'availability': 'true'}] }): key = self._add_announcement() data = { 'key': key, 'date': utc.to_text(seconds=0), 'html': 'Unsafe for operation', 'title': 'Attention', 'is_draft': False, } self._put_announcement(data) self._put_translation(data, LOCALE, 'Achtung', 'Gefahrlich!') # Verify that having saved the translation, we are in progress # state DONE. resource_key = str(resource.Key( announcements.ResourceHandlerAnnouncement.TYPE, db.Key(encoded=data['key']).id())) progress = i18n_dashboard.I18nProgressDAO.load(resource_key) self.assertEquals(progress.get_progress(LOCALE), i18n_dashboard.I18nProgressDTO.DONE) # Modify the announcement in the base language. data['title'] = 'Informational' data['html'] = 'Now safe for operation again' self._put_announcement(data) self.execute_all_deferred_tasks() # Verify that saving the base version of the announcement # moves the progress state back. progress = i18n_dashboard.I18nProgressDAO.load(resource_key) self.assertEquals(progress.get_progress(LOCALE), i18n_dashboard.I18nProgressDTO.IN_PROGRESS)
def test_search_index_translated_announcements(self): LOCALE = 'de' with actions.OverriddenConfig(models.CAN_USE_MEMCACHE.name, True): with actions.OverriddenEnvironment({ 'course': { 'locale': 'en_US', }, 'extra_locales': [{ 'locale': LOCALE, 'availability': 'true' }] }): key = self._add_announcement() data = { 'key': key, 'date': utc.to_text(seconds=0), 'html': 'Unsafe for operation', 'title': 'Attention', 'is_draft': False, } self._put_announcement(data) self._put_translation(data, LOCALE, 'Achtung', 'Gefahrlich!') response = self.post( self.base + '/dashboard?action=index_course', { 'xsrf_token': crypto.XsrfTokenManager.create_xsrf_token( 'index_course') }) self.assertEquals(302, response.status_int) self.execute_all_deferred_tasks() actions.login('*****@*****.**') actions.register(self, 'John Doe') self._set_prefs_locale(LOCALE) response = self.get('search?query=Achtung') soup = self.parse_html_string_to_soup(response.body) snippets = soup.select('.gcb-search-result-snippet') self.assertEquals(snippets[0].text.strip(), 'Gefahrlich!...')
def test_announcement_caching(self): with actions.OverriddenConfig(models.CAN_USE_MEMCACHE.name, True): # Get the fact that there are no announcements into the cache. self._verify_announcements( [], ['Currently, there are no announcements.']) # Add an announcement key = self._add_announcement() data = { 'key': key, 'date': utc.to_text(seconds=0), 'html': 'Twas brillig, and the slithy toves', 'title': 'Jabberwocky', 'is_draft': True, } self._put_announcement(data) # Admin sees announcement on course page. self._verify_announcements([data['title'] + ' (Private)'], [data['html']]) # Capture cache content for later. cache_content = models.MemcacheManager.get( announcements.AnnouncementEntity._MEMCACHE_KEY) # Delete announcement. self._delete_announcement(key) # Check that we see no announcements. self._verify_announcements( [], ['Currently, there are no announcements.']) # Put cache content back and verify we see cache content on page. models.MemcacheManager.set( announcements.AnnouncementEntity._MEMCACHE_KEY, cache_content) self._verify_announcements([data['title'] + ' (Private)'], [data['html']])
def test_announcement_caching(self): with actions.OverriddenConfig(models.CAN_USE_MEMCACHE.name, True): # Get the fact that there are no announcements into the cache. self._verify_announcements( [], ['Currently, there are no announcements.']) # Add an announcement key = self._add_announcement() data = { 'key': key, 'date': utc.to_text(seconds=0), 'html': 'Twas brillig, and the slithy toves', 'title': 'Jabberwocky', 'is_draft': True, } self._put_announcement(data) # Admin sees announcement on course page. self._verify_announcements([data['title'] + ' (Private)'], [data['html']]) # Capture cache content for later. cache_content = models.MemcacheManager.get( announcements.AnnouncementEntity._MEMCACHE_KEY) # Delete announcement. self._delete_announcement(key) # Check that we see no announcements. self._verify_announcements( [], ['Currently, there are no announcements.']) # Put cache content back and verify we see cache content on page. models.MemcacheManager.set( announcements.AnnouncementEntity._MEMCACHE_KEY, cache_content) self._verify_announcements([data['title'] + ' (Private)'], [data['html']])
def test_search_index_translated_announcements(self): LOCALE = 'de' with actions.OverriddenConfig(models.CAN_USE_MEMCACHE.name, True): with actions.OverriddenEnvironment({ 'course': { 'locale': 'en_US', }, 'extra_locales': [{'locale': LOCALE, 'availability': 'true'}] }): key = self._add_announcement() data = { 'key': key, 'date': utc.to_text(seconds=0), 'html': 'Unsafe for operation', 'title': 'Attention', 'is_draft': False, } self._put_announcement(data) self._put_translation(data, LOCALE, 'Achtung', 'Gefahrlich!') response = self.post( self.base + '/dashboard?action=index_course', {'xsrf_token': crypto.XsrfTokenManager.create_xsrf_token('index_course')}) self.assertEquals(302, response.status_int) self.execute_all_deferred_tasks() actions.login('*****@*****.**') actions.register(self, 'John Doe') self._set_prefs_locale(LOCALE) response = self.get('search?query=Achtung') soup = self.parse_html_string_to_soup(response.body) snippets = soup.select('.gcb-search-result-snippet') self.assertEquals(snippets[0].text.strip(), 'Gefahrlich!...')
def get_courses(self): """Shows a list of all courses available on this site.""" if hasattr(self, 'app_context'): this_namespace = self.app_context.get_namespace_name() else: this_namespace = None # GlobalAdminHandler total_students = 0 all_courses = [] app_contexts = sites.get_all_courses() app_contexts.sort( key=lambda app_context: app_context.get_title().lower()) namespaces = [ app_context.get_namespace_name() for app_context in app_contexts] enrolled_totals = enrollments.TotalEnrollmentDAO.load_many(namespaces) for app_context, enrolled_dto in zip(app_contexts, enrolled_totals): slug = app_context.get_slug() name = app_context.get_title() ns_name = app_context.get_namespace_name() if slug == '/': link = '/dashboard' else: link = '%s/dashboard' % slug is_selected_course = (ns_name == this_namespace) course_availability = ( courses.Course.get_course_availability_from_app_context( app_context)) availability_title = courses.COURSE_AVAILABILITY_POLICIES[ course_availability]['title'] if not enrolled_dto.is_empty: # 'count' property is present, so counter *is* initialized. total_enrolled = enrolled_dto.get() total_students += total_enrolled fmt = 'Most recent activity at %s for %s.' % ( self.ISO_8601_UTC_HUMAN_FMT, name) most_recent_enroll = utc.to_text( seconds=enrolled_dto.last_modified, fmt=fmt) else: total_enrolled = self.NONE_ENROLLED most_recent_enroll = ( '(registration activity for %s is being computed)' % name) all_courses.append({ 'link': link, 'name': name, 'slug': slug, 'namespace_name': ns_name, 'is_selected_course': is_selected_course, 'availability': availability_title, 'total_enrolled': total_enrolled, 'most_recent_enroll': most_recent_enroll }) delete_course_xsrf_token = crypto.XsrfTokenManager.create_xsrf_token( CourseDeleteHandler.XSRF_ACTION) add_course_xsrf_token = crypto.XsrfTokenManager.create_xsrf_token( modules.admin.config.CoursesItemRESTHandler.XSRF_ACTION) edit_course_availability_xsrf_token = ( crypto.XsrfTokenManager.create_xsrf_token( availability.AvailabilityRESTHandler.ACTION)) edit_course_settings_xsrf_token = ( crypto.XsrfTokenManager.create_xsrf_token( settings.CourseSettingsRESTHandler.XSRF_ACTION)) course_availability_options = transforms.dumps( [{'value': k, 'title': v['title']} for k, v in courses.COURSE_AVAILABILITY_POLICIES.iteritems()]) template_values = { 'page_title': self.format_title('Courses'), 'main_content': self.render_template_to_html( {'add_course_link': '%s?action=add_course' % self.LINK_URL, 'delete_course_link': CourseDeleteHandler.URI, 'delete_course_xsrf_token': delete_course_xsrf_token, 'add_course_xsrf_token': add_course_xsrf_token, 'edit_course_settings_xsrf_token': edit_course_settings_xsrf_token, 'edit_course_availability_xsrf_token': edit_course_availability_xsrf_token, 'course_availability_options': course_availability_options, 'courses': all_courses, 'total_students': total_students, 'email': users.get_current_user().email(), 'bundle_lib_files': 'true' if appengine_config.BUNDLE_LIB_FILES else 'false', }, 'courses.html', _TEMPLATE_DIRS) } self.render_page(template_values, in_action='courses')
def test_announcement_news(self): actions.login('*****@*****.**') actions.register(self, 'John Doe') time.sleep(1) locale = 'de' announcement = self._add_announcement_and_translation(locale, is_draft=True) sent_data = { 'key': str(announcement.key()), 'title': 'Test Announcement', 'date': utc.to_text(seconds=utc.now_as_timestamp()), 'is_draft': False, } actions.login(self.ADMIN_EMAIL) response = self._put_announcement(sent_data) actions.login('*****@*****.**') # Verify announcement news item using news API directly news_items = news.CourseNewsDao.get_news_items() self.assertEquals(1, len(news_items)) item = news_items[0] now_timestamp = utc.now_as_timestamp() self.assertEquals( announcements.AnnouncementsStudentHandler.URL.lstrip('/'), item.url) self.assertEquals( str( announcements.TranslatableResourceAnnouncement.key_for_entity( announcement)), item.resource_key) self.assertAlmostEqual(now_timestamp, utc.datetime_to_timestamp(item.when), delta=10) # Verify announcement news item looking at HTTP response to /course response = self.get('course') soup = self.parse_html_string_to_soup(response.body) self.assertEquals([ news_tests_lib.NewsItem( 'Test Announcement', announcements.AnnouncementsStudentHandler.URL.lstrip('/'), True) ], news_tests_lib.extract_news_items_from_soup(soup)) # Verify announcement news item translated title. self._set_prefs_locale(locale) response = self.get('course') soup = self.parse_html_string_to_soup(response.body) self.assertEquals([ news_tests_lib.NewsItem( 'TEST ANNOUNCEMENT', announcements.AnnouncementsStudentHandler.URL.lstrip('/'), True) ], news_tests_lib.extract_news_items_from_soup(soup)) # Delete the announcement; news item should also go away. actions.login(self.ADMIN_EMAIL) self._delete_announcement(str(announcement.key())) actions.login('*****@*****.**') response = self.get('course') soup = self.parse_html_string_to_soup(response.body) self.assertEquals([], news_tests_lib.extract_news_items_from_soup(soup))
def get_courses(self): """Shows a list of all courses available on this site.""" if hasattr(self, 'app_context'): this_namespace = self.app_context.get_namespace_name() else: this_namespace = None # GlobalAdminHandler total_students = 0 all_courses = [] app_contexts = sites.get_all_courses() app_contexts.sort( key=lambda app_context: app_context.get_title().lower()) namespaces = [ app_context.get_namespace_name() for app_context in app_contexts] enrolled_totals = enrollments.TotalEnrollmentDAO.load_many(namespaces) for app_context, enrolled_dto in zip(app_contexts, enrolled_totals): slug = app_context.get_slug() name = app_context.get_title() ns_name = app_context.get_namespace_name() if slug == '/': link = '/dashboard' else: link = '%s/dashboard' % slug is_selected_course = (ns_name == this_namespace) course_availability = ( courses.Course.get_course_availability_from_app_context( app_context)) availability_title = courses.COURSE_AVAILABILITY_POLICIES[ course_availability]['title'] if not enrolled_dto.is_empty: # 'count' property is present, so counter *is* initialized. total_enrolled = enrolled_dto.get() total_students += total_enrolled fmt = 'Most recent activity at %s for %s.' % ( self.ISO_8601_UTC_HUMAN_FMT, name) most_recent_enroll = utc.to_text( seconds=enrolled_dto.last_modified, fmt=fmt) else: total_enrolled = self.NONE_ENROLLED most_recent_enroll = ( '(registration activity for %s is being computed)' % name) all_courses.append({ 'link': link, 'name': name, 'slug': slug, 'namespace_name': ns_name, 'is_selected_course': is_selected_course, 'availability': availability_title, 'total_enrolled': total_enrolled, 'most_recent_enroll': most_recent_enroll }) delete_course_xsrf_token = crypto.XsrfTokenManager.create_xsrf_token( CourseDeleteHandler.XSRF_ACTION) add_course_xsrf_token = crypto.XsrfTokenManager.create_xsrf_token( modules.admin.config.CoursesItemRESTHandler.XSRF_ACTION) edit_course_availability_xsrf_token = ( crypto.XsrfTokenManager.create_xsrf_token( availability.AvailabilityRESTHandler.ACTION)) edit_course_settings_xsrf_token = ( crypto.XsrfTokenManager.create_xsrf_token( settings.CourseSettingsRESTHandler.XSRF_ACTION)) course_availability_options = transforms.dumps( [{'value': k, 'title': v['title']} for k, v in courses.COURSE_AVAILABILITY_POLICIES.iteritems()]) template_values = { 'page_title': self.format_title('Courses'), 'main_content': self.render_template_to_html( {'add_course_link': '%s?action=add_course' % self.LINK_URL, 'delete_course_link': CourseDeleteHandler.URI, 'delete_course_xsrf_token': delete_course_xsrf_token, 'add_course_xsrf_token': add_course_xsrf_token, 'edit_course_settings_xsrf_token': edit_course_settings_xsrf_token, 'edit_course_availability_xsrf_token': edit_course_availability_xsrf_token, 'course_availability_options': course_availability_options, 'courses': all_courses, 'total_students': total_students, 'email': users.get_current_user().email(), 'bundle_lib_files': 'true' if appengine_config.BUNDLE_LIB_FILES else 'false', }, 'courses.html', _TEMPLATE_DIRS) } self.render_page(template_values, in_action='courses')