Пример #1
0
    def mark_item_seen(self, resource_key):
        now = utc.now_as_datetime()

        # First, add/update a record to indicate that the student has just now
        # seen the newsworthy thing.
        # Note: Using OrderedDict's here because they permit deletion during
        # iteration.
        seen_items = collections.OrderedDict(
            {i.resource_key: i
             for i in self.get_seen_items()})
        seen_items[resource_key] = SeenItem(resource_key, now)

        # As long as we're here, also take this opportunity to clean up:
        # Remove pairs of items where we have a 'seen' record and a 'news'
        # record for the same key and where the item was seen more than
        # NEWSWORTHINESS_SECONDS ago.  We retain things that are only
        # slightly-old so that students can still use the News feature to
        # re-find stuff they've already seen but may still want to re-visit.
        news_items = collections.OrderedDict(
            {n.resource_key: n
             for n in self.get_news_items()})
        for resource_key, seen_item in seen_items.iteritems():
            if (now - seen_item.when).total_seconds() > NEWSWORTHINESS_SECONDS:
                if resource_key in news_items:
                    del news_items[resource_key]
                    del seen_items[resource_key]
                    break

        self._set_seen_items(seen_items.values())
        self._set_news_items(news_items.values())
Пример #2
0
    def mark_item_seen(self, resource_key):
        now = utc.now_as_datetime()

        # First, add/update a record to indicate that the student has just now
        # seen the newsworthy thing.
        # Note: Using OrderedDict's here because they permit deletion during
        # iteration.
        seen_items = collections.OrderedDict(
            {i.resource_key: i for i in self.get_seen_items()})
        seen_items[resource_key] = SeenItem(resource_key, now)

        # As long as we're here, also take this opportunity to clean up:
        # Remove pairs of items where we have a 'seen' record and a 'news'
        # record for the same key and where the item was seen more than
        # NEWSWORTHINESS_SECONDS ago.  We retain things that are only
        # slightly-old so that students can still use the News feature to
        # re-find stuff they've already seen but may still want to re-visit.
        news_items = collections.OrderedDict(
            {n.resource_key: n for n in self.get_news_items()})
        for resource_key, seen_item in seen_items.iteritems():
            if (now - seen_item.when).total_seconds() > NEWSWORTHINESS_SECONDS:
                if resource_key in news_items:
                    del news_items[resource_key]
                    del seen_items[resource_key]
                    break

        self._set_seen_items(seen_items.values())
        self._set_news_items(news_items.values())
Пример #3
0
    def __init__(self, resource_key, url, when=None, labels=None):
        # String version of common.resource.Key
        self.resource_key = resource_key

        # The time when this item became news.
        self.when = when or utc.now_as_datetime()

        # URL to the page showing the item.
        self.url = url

        # Single string giving IDs of labels, whitespace separated.  Same as
        # labels field on Student, Announcement, Unit and so on.  Used to
        # restrict news on items that are labelled to only students with
        # matching labels.  Follows usual label-match rules: if either Student
        # or NewsItem does not have labels in a category, category does not
        # filter.  If both have labels, at least one label must exist in
        # common for match.
        self.labels = labels or ''

        # --------------------------------------------------------------------
        # Below here is transient data - not persisted.  Overwritten only for
        # UX display.  Note that since the serialization library ignores
        # transient items based on leading-underscore, we also provide
        # getter/setter properties to avoid warnings about touching private
        # members.

        # Distinguish news items that are likely interesting versus items that
        # are likely old news for the student.
        self._is_new_news = None

        # Title, suitably i18n'd for the current display locale.
        self._i18n_title = None
Пример #4
0
    def __init__(self, resource_key, url, when=None, labels=None):
        # String version of common.resource.Key
        self.resource_key = resource_key

        # The time when this item became news.
        self.when = when or utc.now_as_datetime()

        # URL to the page showing the item.
        self.url = url

        # Single string giving IDs of labels, whitespace separated.  Same as
        # labels field on Student, Announcement, Unit and so on.  Used to
        # restrict news on items that are labelled to only students with
        # matching labels.  Follows usual label-match rules: if either Student
        # or NewsItem does not have labels in a category, category does not
        # filter.  If both have labels, at least one label must exist in
        # common for match.
        self.labels = labels or ''

        # --------------------------------------------------------------------
        # Below here is transient data - not persisted.  Overwritten only for
        # UX display.  Note that since the serialization library ignores
        # transient items based on leading-underscore, we also provide
        # getter/setter properties to avoid warnings about touching private
        # members.

        # Distinguish news items that are likely interesting versus items that
        # are likely old news for the student.
        self._is_new_news = None

        # Title, suitably i18n'd for the current display locale.
        self._i18n_title = None
Пример #5
0
 def make(cls, title, html, is_draft):
     entity = cls()
     entity.title = title
     entity.date = utc.now_as_datetime().date()
     entity.html = html
     entity.is_draft = is_draft
     return entity
    def run(self):
        now = utc.now_as_datetime()
        namespace = namespace_manager.get_namespace()
        app_context = sites.get_app_context_for_namespace(namespace)
        course = courses.Course.get(app_context)
        env = app_context.get_environ()

        tct = triggers.ContentTrigger
        content_acts = tct.act_on_settings(course, env, now)

        tmt = triggers.MilestoneTrigger
        course_acts = tmt.act_on_settings(course, env, now)

        save_settings = content_acts.num_consumed or course_acts.num_consumed
        if save_settings:
            # At least one of the settings['publish'] triggers was consumed
            # or discarded, so save changes to triggers into the settings.
            settings_saved = course.save_settings(env)
        else:
            settings_saved = False

        save_course = content_acts.num_changed or course_acts.num_changed
        if save_course:
            course.save()

        tct.log_acted_on(
            namespace, content_acts, save_course, settings_saved)
        tmt.log_acted_on(
            namespace, course_acts, save_course, settings_saved)

        common_utils.run_hooks(self.RUN_HOOKS.itervalues(), course)
Пример #7
0
    def run(self):
        now = utc.now_as_datetime()
        namespace = namespace_manager.get_namespace()
        app_context = sites.get_app_context_for_namespace(namespace)
        course = courses.Course.get(app_context)
        env = app_context.get_environ()

        tct = triggers.ContentTrigger
        content_acts = tct.act_on_settings(course, env, now)

        tmt = triggers.MilestoneTrigger
        course_acts = tmt.act_on_settings(course, env, now)

        save_settings = content_acts.num_consumed or course_acts.num_consumed
        if save_settings:
            # At least one of the settings['publish'] triggers was consumed
            # or discarded, so save changes to triggers into the settings.
            settings_saved = course.save_settings(env)
        else:
            settings_saved = False

        save_course = content_acts.num_changed or course_acts.num_changed
        if save_course:
            course.save()

        tct.log_acted_on(namespace, content_acts, save_course, settings_saved)
        tmt.log_acted_on(namespace, course_acts, save_course, settings_saved)

        common_utils.run_hooks(self.RUN_HOOKS.itervalues(), course)
 def _test_add_duplicate_news_item(self, dao_class):
     now = utc.now_as_datetime()
     news_item = news.NewsItem('test:key', 'test:url', when=now)
     dao_class.add_news_item(news_item)
     dao_class.add_news_item(news_item)
     dao_class.add_news_item(news_item)
     dao_class.add_news_item(news_item)
     self.assertEquals([news_item], dao_class.get_news_items())
    def _test_mark_item_seen(self, dao_class):
        actions.login(self.STUDENT_EMAIL)
        actions.register(self, 'John Smith')

        now = utc.now_as_datetime()
        news_item = news.NewsItem('test:key', 'test:url', when=now)
        dao_class.add_news_item(news_item)
        self.assertEquals([news_item], dao_class.get_news_items())
        news.StudentNewsDao.mark_item_seen(news_item.resource_key)

        seen_item = news.SeenItem(news_item.resource_key, now)
        self.assertEquals([seen_item], news.StudentNewsDao.get_seen_items())

        # Move time forward one tick, and verify that the timestamp *does*
        # move forward on the 'seen' record.
        time.sleep(1)
        seen_item.when = utc.now_as_datetime()
        news.StudentNewsDao.mark_item_seen(news_item.resource_key)
        self.assertEquals([seen_item], news.StudentNewsDao.get_seen_items())
    def test_old_student_news_removed_when_seen_far_in_the_past(self):
        actions.login(self.STUDENT_EMAIL)
        actions.register(self, 'John Smith')
        now = utc.now_as_datetime()
        item_one = news.NewsItem('key_one', 'test:url', when=now)
        item_two = news.NewsItem('key_two', 'test:url', when=now)
        news.StudentNewsDao.add_news_item(item_one)
        news.StudentNewsDao.add_news_item(item_two)

        # Nothing seen; should have both news items still.
        news_items = news.StudentNewsDao.get_news_items()
        self.assertEquals(2, len(news_items))
        self.assertIn(item_one, news_items)
        self.assertIn(item_two, news_items)

        # Now we mark item_one as visited.  Still should retain both items,
        # since we're within the newsworthiness time limit.
        news.StudentNewsDao.mark_item_seen(item_one.resource_key)
        seen_one = news.SeenItem(item_one.resource_key, now)
        self.assertEquals([seen_one], news.StudentNewsDao.get_seen_items())
        news_items = news.StudentNewsDao.get_news_items()
        self.assertEquals(2, len(news_items))
        self.assertIn(item_one, news_items)
        self.assertIn(item_two, news_items)

        # Set the newsworthiness timeout to one second so we can get this
        # done in a sane amount of time.
        try:
            save_newsworthiness_seconds = news.NEWSWORTHINESS_SECONDS
            news.NEWSWORTHINESS_SECONDS = 1
            time.sleep(2)
            now = utc.now_as_datetime()

            # Marking item two as seen should have the side effect of
            # removing the seen and news items for 'key_one'.
            news.StudentNewsDao.mark_item_seen(item_two.resource_key)
            self.assertEquals([item_two], news.StudentNewsDao.get_news_items())
            seen_two = news.SeenItem(item_two.resource_key, now)
            self.assertEquals([seen_two], news.StudentNewsDao.get_seen_items())

        finally:
            news.NEWSWORTHINESS_SECONDS = save_newsworthiness_seconds
    def test_news_before_user_registration_is_not_news(self):
        news_item = news.NewsItem(
            'test:before', 'before_url', utc.now_as_datetime())
        news.CourseNewsDao.add_news_item(news_item)
        time.sleep(1)
        user = actions.login(self.STUDENT_EMAIL)
        actions.register(self, 'John Smith')
        now = utc.now_as_datetime()
        news_item = news.NewsItem('test:at', 'at_url', now)
        self._set_student_enroll_date(user, now)
        news.CourseNewsDao.add_news_item(news_item)
        time.sleep(1)
        news_item = news.NewsItem(
            'test:after', 'after_url', utc.now_as_datetime())
        news.CourseNewsDao.add_news_item(news_item)

        # Expect to not see news item from before student registration.
        response = self.get('course')
        soup = self.parse_html_string_to_soup(response.body)
        self.assertEquals(
            [news_tests_lib.NewsItem('Test Item after', 'after_url', True),
             news_tests_lib.NewsItem('Test Item at', 'at_url', True)],
            news_tests_lib.extract_news_items_from_soup(soup))
    def _test_remove_news_item(self, dao_class):
        # Just need to see no explosions.
        dao_class.remove_news_item('no_such_item_key')

        # Add an item so that we can remove it next.
        now = utc.now_as_datetime()
        news_item = news.NewsItem('test:key', 'test:url', when=now)
        dao_class.add_news_item(news_item)
        self.assertEquals([news_item], dao_class.get_news_items())

        # Remove the item; verify empty set of items.
        dao_class.remove_news_item(news_item.resource_key)
        self.assertEquals([], dao_class.get_news_items())

        # Remove again an item that had existed; just want no explosions.
        dao_class.remove_news_item(news_item.resource_key)
        self.assertEquals([], dao_class.get_news_items())
Пример #13
0
def course_page_navbar_callback(app_context):
    """Generate HTML for inclusion on tabs bar.

    Thankfully, this function gets called pretty late during page generation,
    so StudentNewsDao should already have been notified when we're on a page
    that was newsworthy, but now is not because the student has seen it.
    """

    # If we don't have a registered student in session, no news for you!
    user = users.get_current_user()
    if not user:
        return []
    student = models.Student.get_enrolled_student_by_user(user)
    if not student or student.is_transient:
        return []
    student_dao = StudentNewsDao.load_or_default()

    # Combine all news items for consideration.
    news = student_dao.get_news_items() + CourseNewsDao.get_news_items()
    seen_times = {s.resource_key: s.when for s in student_dao.get_seen_items()}

    # Filter out items that student can't see due to label matching.  Do
    # this before reducing number of items displayed to a fixed maximum.
    course = courses.Course.get(app_context)
    models.LabelDAO.apply_course_track_labels_to_student_labels(
        course, student, news)

    # Run through news items, categorizing 'new' and 'old' news for display.
    # news is everything else.
    new_news = []
    old_news = []
    now = utc.now_as_datetime()
    enrolled_on = student.enrolled_on.replace(microsecond=0)
    for item in news:
        seen_when = seen_times.get(item.resource_key)
        if seen_when is None:
            # Items not yet seen at all get marked for CSS highlighting.
            # Items prior to student enrollment are not incremental new stuff;
            # we assume that on enroll, the student is on notice that all
            # course content is "new", and we don't need to redundantly bring
            # it to their attention.
            if item.when >= enrolled_on:
                item.is_new_news = True
                new_news.append(item)
        elif (now - seen_when).total_seconds() < NEWSWORTHINESS_SECONDS:
            # Items seen recently are always shown, but with CSS dimming.
            item.is_new_news = False
            new_news.append(item)
        else:
            # Items seen and not recently are put on seprate list for
            # inclusion only if there are few new items.
            item.is_new_news = False
            old_news.append(item)

    # Display setup: Order by time within new, old set.  Show all new
    # news, and if there are few of those, some old news as well.
    new_news.sort(key=lambda n: (n.is_new_news, n.when), reverse=True)
    old_news.sort(key=lambda n: n.when, reverse=True)
    news = new_news + old_news[0:max(0, MIN_NEWS_ITEMS_TO_DISPLAY -
                                     len(new_news))]

    for item in news:
        try:
            key = resource.Key.fromstring(item.resource_key)
            resource_handler = (
                i18n_dashboard.TranslatableResourceRegistry.get_by_type(
                    key.type))
            item.i18n_title = resource_handler.get_i18n_title(key)
        except AssertionError:
            # Not all news things are backed by AbstractResourceHandler types.
            # Fall back to news-specific registry for these.
            resource_handler = I18nTitleRegistry
            key_type, _ = item.resource_key.split(resource.Key.SEPARATOR, 1)
            item.i18n_title = resource_handler.get_i18n_title(
                key_type, item.resource_key)

    # Fill template
    template_environ = app_context.get_template_environ(
        app_context.get_current_locale(), [TEMPLATES_DIR])
    template = template_environ.get_template('news.html', [TEMPLATES_DIR])
    return [
        jinja2.utils.Markup(template.render({'news': news}, autoescape=True))
    ]
Пример #14
0
def _new_course_counts(app_context, unused_errors):
    """Called back from CoursesItemRESTHandler when new course is created."""
    namespace_name = app_context.get_namespace_name()
    TotalEnrollmentDAO.set(namespace_name, 0)
    EnrollmentsAddedDAO.set(namespace_name, utc.now_as_datetime(), 0)
    EnrollmentsDroppedDAO.set(namespace_name, utc.now_as_datetime(), 0)
Пример #15
0
def _new_course_counts(app_context, unused_errors):
    """Called back from CoursesItemRESTHandler when new course is created."""
    namespace_name = app_context.get_namespace_name()
    TotalEnrollmentDAO.set(namespace_name, 0)
    EnrollmentsAddedDAO.set(namespace_name, utc.now_as_datetime(), 0)
    EnrollmentsDroppedDAO.set(namespace_name, utc.now_as_datetime(), 0)
Пример #16
0
def course_page_navbar_callback(app_context):
    """Generate HTML for inclusion on tabs bar.

    Thankfully, this function gets called pretty late during page generation,
    so StudentNewsDao should already have been notified when we're on a page
    that was newsworthy, but now is not because the student has seen it.
    """

    # If we don't have a registered student in session, no news for you!
    user = users.get_current_user()
    if not user:
        return []
    student = models.Student.get_enrolled_student_by_user(user)
    if not student or student.is_transient:
        return []
    student_dao = StudentNewsDao.load_or_default()

    # Combine all news items for consideration.
    news = student_dao.get_news_items() + CourseNewsDao.get_news_items()
    seen_times = {s.resource_key: s.when
                  for s in student_dao.get_seen_items()}

    # Filter out items that student can't see due to label matching.  Do
    # this before reducing number of items displayed to a fixed maximum.
    course = courses.Course.get(app_context)
    models.LabelDAO.apply_course_track_labels_to_student_labels(
        course, student, news)

    # Run through news items, categorizing 'new' and 'old' news for display.
    # news is everything else.
    new_news = []
    old_news = []
    now = utc.now_as_datetime()
    enrolled_on = student.enrolled_on.replace(microsecond=0)
    for item in news:
        seen_when = seen_times.get(item.resource_key)
        if seen_when is None:
            # Items not yet seen at all get marked for CSS highlighting.
            # Items prior to student enrollment are not incremental new stuff;
            # we assume that on enroll, the student is on notice that all
            # course content is "new", and we don't need to redundantly bring
            # it to their attention.
            if item.when >= enrolled_on:
                item.is_new_news = True
                new_news.append(item)
        elif (now - seen_when).total_seconds() < NEWSWORTHINESS_SECONDS:
            # Items seen recently are always shown, but with CSS dimming.
            item.is_new_news = False
            new_news.append(item)
        else:
            # Items seen and not recently are put on seprate list for
            # inclusion only if there are few new items.
            item.is_new_news = False
            old_news.append(item)

    # Display setup: Order by time within new, old set.  Show all new
    # news, and if there are few of those, some old news as well.
    new_news.sort(key=lambda n: (n.is_new_news, n.when), reverse=True)
    old_news.sort(key=lambda n: n.when, reverse=True)
    news = new_news + old_news[
        0:max(0, MIN_NEWS_ITEMS_TO_DISPLAY - len(new_news))]

    for item in news:
        try:
            key = resource.Key.fromstring(item.resource_key)
            resource_handler = (
                i18n_dashboard.TranslatableResourceRegistry.get_by_type(
                    key.type))
            item.i18n_title = resource_handler.get_i18n_title(key)
        except AssertionError:
            # Not all news things are backed by AbstractResourceHandler types.
            # Fall back to news-specific registry for these.
            resource_handler = I18nTitleRegistry
            key_type, _ = item.resource_key.split(resource.Key.SEPARATOR, 1)
            item.i18n_title = resource_handler.get_i18n_title(
                key_type, item.resource_key)

    # Fill template
    template_environ = app_context.get_template_environ(
        app_context.get_current_locale(), [TEMPLATES_DIR])
    template = template_environ.get_template('news.html', [TEMPLATES_DIR])
    return [
        jinja2.utils.Markup(template.render({'news': news}, autoescape=True))]