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
示例#3
0
    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()
示例#4
0
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))
示例#9
0
 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))
示例#10
0
 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))
示例#11
0
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))
示例#13
0
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'])
示例#16
0
    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!...')
示例#36
0
    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))
示例#38
0
    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')