コード例 #1
0
    def test_bad_provider_config(self):
        """
        Make sure we are throwing exceptions on poor configuration
        """

        with self.assertRaises(ImproperlyConfigured):
            notification_store()
コード例 #2
0
    def test_bad_provider_config(self):
        """
        Make sure we are throwing exceptions on poor configuration
        """

        with self.assertRaises(ImproperlyConfigured):
            notification_store()
コード例 #3
0
 def setUp(self):
     """
     start up stuff
     """
     startup.initialize()
     self.purge_notifications_timer_name = 'purge-notifications-timer'
     self.store = notification_store()
コード例 #4
0
    def setUp(self):
        """
        start up stuff
        """

        register_user_scope_resolver('list_scope', TestListScopeResolver())

        self.store = notification_store()
        self.msg_type = self.store.save_notification_type(
            NotificationType(
                name='foo.bar',
                renderer='foo',
            )
        )

        msg = NotificationMessage(
            msg_type=self.msg_type,
            payload={'foo': 'bar'},
        )
        msg.add_payload(
            {
                'extra': 'stuff'
            },
            channel_name='other_channel'
        )
        self.msg = self.store.save_notification_message(msg)
コード例 #5
0
def get_notification_for_user(user_id, msg_id):
    """
    Returns a single UserNotification object for the user_id/msg_id paid.

    If it is not found a ItemNotFoundError is raised
    """
    return notification_store().get_notification_for_user(user_id, msg_id)
コード例 #6
0
def send_notifications_digest(from_timestamp, to_timestamp, preference_name, subject,
                              from_email, unread_only=True):
    """
    This will generate and send a digest of all notifications over all namespaces to all
    resolvable users subscribing to digests for that namespace
    """

    digests_sent = 0

    # Get a collection of all namespaces
    namespaces = notification_store().get_all_namespaces(from_timestamp, to_timestamp)

    # Loop over all namespaces
    for namespace in namespaces:
        digests_sent += send_notifications_namespace_digest(
            namespace,
            from_timestamp,
            to_timestamp,
            preference_name,
            subject,
            from_email,
            unread_only=unread_only
        )

    return digests_sent
コード例 #7
0
    def setUp(self):
        """
        start up stuff
        """

        register_user_scope_resolver('list_scope', TestListScopeResolver())

        self.store = notification_store()
        self.msg_type = self.store.save_notification_type(
            NotificationType(
                name='foo.bar',
                renderer='foo',
            )
        )

        msg = NotificationMessage(
            msg_type=self.msg_type,
            payload={'foo': 'bar'},
        )
        msg.add_payload(
            {
                'extra': 'stuff'
            },
            channel_name='other_channel'
        )
        self.msg = self.store.save_notification_message(msg)
コード例 #8
0
 def setUp(self):
     """
     start up stuff
     """
     startup.initialize()
     self.purge_notifications_timer_name = 'purge-notifications-timer'
     self.store = notification_store()
コード例 #9
0
def get_notification_for_user(user_id, msg_id):
    """
    Returns a single UserNotification object for the user_id/msg_id paid.

    If it is not found a ItemNotFoundError is raised
    """
    return notification_store().get_notification_for_user(user_id, msg_id)
コード例 #10
0
def register_purge_notifications_timer(sender, **kwargs):  # pylint: disable=unused-argument
    """
    Register PurgeNotificationsCallbackHandler.
    This will be called automatically on the Notification subsystem startup (because we are
    receiving the 'perform_timer_registrations' signal)
    """
    store = notification_store()

    try:
        store.get_notification_timer(PURGE_NOTIFICATIONS_TIMER_NAME)
    except ItemNotFoundError:
        # Set first execution time at upcoming 1:00 AM (1 hour after midnight).
        first_execution_at = (datetime.now(pytz.UTC) +
                              timedelta(days=1)).replace(hour=1,
                                                         minute=0,
                                                         second=0)

        purge_notifications_timer = NotificationCallbackTimer(
            name=PURGE_NOTIFICATIONS_TIMER_NAME,
            callback_at=first_execution_at,
            class_name=
            'edx_notifications.callbacks.PurgeNotificationsCallbackHandler',
            is_active=True,
            periodicity_min=const.MINUTES_IN_A_DAY)
        store.save_notification_timer(purge_notifications_timer)
コード例 #11
0
def send_notifications_digest(from_timestamp, to_timestamp, preference_name, subject,
                              from_email, unread_only=True):
    """
    This will generate and send a digest of all notifications over all namespaces to all
    resolvable users subscribing to digests for that namespace
    """

    digests_sent = 0

    # Get a collection of all namespaces
    namespaces = notification_store().get_all_namespaces()

    # Loop over all namespaces
    for namespace in namespaces:
        digests_sent += send_notifications_namespace_digest(
            namespace,
            from_timestamp,
            to_timestamp,
            preference_name,
            subject,
            from_email,
            unread_only=unread_only
        )

    return digests_sent
コード例 #12
0
 def setUp(self):
     """
     start up stuff
     """
     startup.initialize()
     self.daily_digest_timer_name = 'daily-digest-timer'
     self.weekly_digest_timer_name = 'weekly-digest-timer'
     self.store = notification_store()
コード例 #13
0
def register_notification_type(msg_type):
    """
    Registers a new notification type
    """

    log.info('Registering NotificationType: {msg_type}'.format(msg_type=str(msg_type)))

    # do validation
    msg_type.validate()

    notification_store().save_notification_type(msg_type)

    # also register the Renderer associated with this
    # type, note that the multiple msg types can have
    # the same renderer, but only one entry will
    # get placed in the registry
    register_renderer(msg_type.renderer)
コード例 #14
0
 def setUp(self):
     """
     start up stuff
     """
     startup.initialize()
     self.daily_digest_timer_name = 'daily-digest-timer'
     self.weekly_digest_timer_name = 'weekly-digest-timer'
     self.store = notification_store()
コード例 #15
0
def register_notification_type(msg_type):
    """
    Registers a new notification type
    """

    log.info('Registering NotificationType: %s', str(msg_type))

    # do validation
    msg_type.validate()

    notification_store().save_notification_type(msg_type)

    # also register the Renderer associated with this
    # type, note that the multiple msg types can have
    # the same renderer, but only one entry will
    # get placed in the registry
    register_renderer(msg_type.renderer)
コード例 #16
0
    def test_get_provider(self):
        """
        Makes sure we get an instance of the registered store provider
        """

        provider = notification_store()

        self.assertIsNotNone(provider)
        self.assertTrue(isinstance(provider, SQLNotificationStoreProvider))
コード例 #17
0
    def test_get_provider(self):
        """
        Makes sure we get an instance of the registered store provider
        """

        provider = notification_store()

        self.assertIsNotNone(provider)
        self.assertTrue(isinstance(provider, SQLNotificationStoreProvider))
コード例 #18
0
def set_user_notification_preference(user_id, name, value):
    """
    Create or Update the user preference
    """
    store = notification_store()
    notification_preference = get_notification_preference(name)
    user_notification_preference = UserNotificationPreferences(
        user_id=user_id, preference=notification_preference, value=value)

    return store.set_user_preference(user_notification_preference)
コード例 #19
0
def set_user_notification_preference(user_id, name, value):
    """
    Create or Update the user preference
    """
    store = notification_store()
    notification_preference = get_notification_preference(name)
    user_notification_preference = UserNotificationPreferences(
        user_id=user_id, preference=notification_preference, value=value
    )

    return store.set_user_preference(user_notification_preference)
コード例 #20
0
    def test_timer_execution(self):
        """
        Make sure that Django management command runs through the timers
        """

        timer = NotificationCallbackTimer(
            name='foo',
            class_name='edx_notifications.tests.test_timer.NullNotificationCallbackTimerHandler',
            callback_at=datetime.now(pytz.UTC) - timedelta(days=1),
            context={},
            is_active=True,
        )

        notification_store().save_notification_timer(timer)

        background_notification_check.Command().handle()

        readback_timer = notification_store().get_notification_timer(timer.name)

        self.assertIsNotNone(readback_timer.executed_at)
        self.assertIsNone(readback_timer.err_msg)
コード例 #21
0
    def setUp(self):
        """
        Initialize tests, by creating users and populating some
        unread notifications
        """
        create_default_notification_preferences()
        self.store = notification_store()
        self.test_user_id = 1001
        self.from_timestamp = datetime.datetime.now(
            pytz.UTC) - datetime.timedelta(days=1)
        self.weekly_from_timestamp = datetime.datetime.now(
            pytz.UTC) - datetime.timedelta(days=7)
        self.to_timestamp = datetime.datetime.now(pytz.UTC)

        self.msg_type = self.store.save_notification_type(
            NotificationType(
                name='foo.bar',
                renderer=
                'edx_notifications.renderers.basic.BasicSubjectBodyRenderer',
            ))

        self.msg_type_no_renderer = self.store.save_notification_type(
            NotificationType(
                name='foo.baz',
                renderer='foo',
            ))

        # create two notifications
        with freeze_time(self.to_timestamp):
            msg = self.store.save_notification_message(
                NotificationMessage(
                    msg_type=self.msg_type,
                    namespace='foo',
                    payload={
                        'subject': 'foo',
                        'body': 'bar'
                    },
                ))
            self.notification1 = publish_notification_to_user(
                self.test_user_id, msg)

        with freeze_time(self.to_timestamp):
            msg = self.store.save_notification_message(
                NotificationMessage(
                    msg_type=self.msg_type_no_renderer,
                    namespace='bar',
                    payload={
                        'subject': 'foo',
                        'body': 'bar'
                    },
                ))
            self.notification2 = publish_notification_to_user(
                self.test_user_id, msg)
コード例 #22
0
    def test_timer_execution(self):
        """
        Make sure that Django management command runs through the timers
        """

        timer = NotificationCallbackTimer(
            name="foo",
            class_name="edx_notifications.tests.test_timer.NullNotificationCallbackTimerHandler",
            callback_at=datetime.now(pytz.UTC) - timedelta(days=1),
            context={},
            is_active=True,
        )

        notification_store().save_notification_timer(timer)

        background_notification_check.Command().handle()

        readback_timer = notification_store().get_notification_timer(timer.name)

        self.assertIsNotNone(readback_timer.executed_at)
        self.assertIsNone(readback_timer.err_msg)
コード例 #23
0
def mark_all_user_notification_as_read(user_id, filters=None):
    """
    Will mark a given user notifications as 'read'

    ARGS:
        - user_id: The user that wishes to mark the msg as read/unread

    NOTE: If the corresponding user_id cannot be found, then this will raise
    a ItemNotFoundError().
    """

    store = notification_store()
    store.mark_user_notifications_read(user_id, filters=filters)
コード例 #24
0
def mark_all_user_notification_as_read(user_id, filters=None):
    """
    Will mark a given user notifications as 'read'

    ARGS:
        - user_id: The user that wishes to mark the msg as read/unread

    NOTE: If the corresponding user_id cannot be found, then this will raise
    a ItemNotFoundError().
    """

    store = notification_store()
    store.mark_user_notifications_read(user_id, filters=filters)
コード例 #25
0
    def test_preserve_execute_at(self):
        """
        Make sure the execute at timestamps don't get reset
        across system restarts
        """

        register_digest_timers(None)

        timer = notification_store().get_notification_timer(const.DAILY_DIGEST_TIMER_NAME)

        callback_at = timer.callback_at - datetime.timedelta(days=1)

        timer.callback_at = callback_at
        notification_store().save_notification_timer(timer)

        # restart
        register_digest_timers(None)

        timer = notification_store().get_notification_timer(const.DAILY_DIGEST_TIMER_NAME)

        # make sure the timestamp did not get reset
        self.assertEqual(timer.callback_at, callback_at)
コード例 #26
0
    def bulk_dispatch_notification(self, user_ids, msg, exclude_user_ids=None, channel_context=None):
        """
        Perform a bulk dispatch of the notification message to
        all user_ids that will be enumerated over in user_ids.

        NOTE: We will chunk together up to NOTIFICATION_BULK_PUBLISH_CHUNK_SIZE

        user_ids should be a list, a generator function, or a
        django.db.models.query.ValuesQuerySet/ValuesListQuerySet
        when directly feeding in a Django ORM queryset, where we select just the id column of the user
        """

        store = notification_store()

        # get a msg (cloned from original) with resolved links
        msg = self._get_linked_resolved_msg(msg)

        # persist the message in our Store Provide
        _msg = store.save_notification_message(msg)

        user_msgs = []

        exclude_user_ids = exclude_user_ids if exclude_user_ids else []

        cnt = 0
        total = 0

        # enumerate through the list of user_ids and chunk them
        # up. Be sure not to include any user_id in the exclude list
        for user_id in user_ids:
            if user_id not in exclude_user_ids:
                user_msgs.append(
                    UserNotification(
                        user_id=user_id,
                        msg=_msg
                    )
                )
                cnt = cnt + 1
                total = total + 1
                if cnt == const.NOTIFICATION_BULK_PUBLISH_CHUNK_SIZE:
                    store.bulk_create_user_notification(user_msgs)
                    user_msgs = []
                    cnt = 0

        if user_msgs:
            store.bulk_create_user_notification(user_msgs)

        return total
コード例 #27
0
    def bulk_dispatch_notification(self, user_ids, msg, exclude_user_ids=None, channel_context=None):
        """
        Perform a bulk dispatch of the notification message to
        all user_ids that will be enumerated over in user_ids.

        NOTE: We will chunk together up to NOTIFICATION_BULK_PUBLISH_CHUNK_SIZE

        user_ids should be a list, a generator function, or a
        django.db.models.query.ValuesQuerySet/ValuesListQuerySet
        when directly feeding in a Django ORM queryset, where we select just the id column of the user
        """

        store = notification_store()

        # get a msg (cloned from original) with resolved links
        msg = self._get_linked_resolved_msg(msg)

        # persist the message in our Store Provide
        _msg = store.save_notification_message(msg)

        user_msgs = []

        exclude_user_ids = exclude_user_ids if exclude_user_ids else []

        cnt = 0
        total = 0

        # enumerate through the list of user_ids and chunk them
        # up. Be sure not to include any user_id in the exclude list
        for user_id in user_ids:
            if user_id not in exclude_user_ids:
                user_msgs.append(
                    UserNotification(
                        user_id=user_id,
                        msg=_msg
                    )
                )
                cnt = cnt + 1
                total = total + 1
                if cnt == const.NOTIFICATION_BULK_PUBLISH_CHUNK_SIZE:
                    store.bulk_create_user_notification(user_msgs)
                    user_msgs = []
                    cnt = 0

        if user_msgs:
            store.bulk_create_user_notification(user_msgs)

        return total
コード例 #28
0
    def setUp(self):
        """
        start up stuff
        """

        register_user_scope_resolver('list_scope', TestListScopeResolver())

        self.store = notification_store()
        self.callback = NotificationDispatchMessageCallback()

        self.msg_type = self.store.save_notification_type(
            NotificationType(
                name='foo.bar',
                renderer='foo',
            )
        )

        self.msg = self.store.save_notification_message(
            NotificationMessage(
                msg_type=self.msg_type,
                payload={'foo': 'bar'},
            )
        )

        self.timer_for_user = NotificationCallbackTimer(
            context={
                'msg_id': self.msg.id,
                'distribution_scope': {
                    'scope_name': 'user',
                    'scope_context': {
                        'user_id': 1
                    }
                }
            }
        )

        self.timer_for_group = NotificationCallbackTimer(
            context={
                'msg_id': self.msg.id,
                'distribution_scope': {
                    'scope_name': 'list_scope',
                    'scope_context': {
                        'range': 5
                    }
                }
            }
        )
コード例 #29
0
def get_notifications_count_for_user(user_id, filters=None):
    """
    Returns the number of notifications this user has

    ARGS:
        user_id: The id of the user to query
        filters: A dict containing the following optional keys
            - 'namespace': what namespace to search (defuault None)
            - 'read': Whether to return read notifications (default True)
            - 'unread': Whether to return unread notifications (default True)
            - 'type_name': what msg_types to count
    """

    # make sure user_id is an integer
    if not isinstance(user_id, int):
        raise TypeError("user_id must be an integer")

    return notification_store().get_num_notifications_for_user(user_id, filters=filters)
コード例 #30
0
    def setUp(self):
        """
        Initialize tests, by creating users and populating some
        unread notifications
        """
        create_default_notification_preferences()
        self.store = notification_store()
        self.test_user_id = 1001
        self.from_timestamp = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=1)
        self.weekly_from_timestamp = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=7)
        self.to_timestamp = datetime.datetime.now(pytz.UTC)

        self.msg_type = self.store.save_notification_type(
            NotificationType(
                name='foo.bar',
                renderer='edx_notifications.renderers.basic.BasicSubjectBodyRenderer',
            )
        )

        self.msg_type_no_renderer = self.store.save_notification_type(
            NotificationType(
                name='foo.baz',
                renderer='foo',
            )
        )

        # create two notifications
        msg = self.store.save_notification_message(
            NotificationMessage(
                msg_type=self.msg_type,
                namespace='foo',
                payload={'subject': 'foo', 'body': 'bar'},
            )
        )
        self.notification1 = publish_notification_to_user(self.test_user_id, msg)

        msg = self.store.save_notification_message(
            NotificationMessage(
                msg_type=self.msg_type_no_renderer,
                namespace='bar',
                payload={'subject': 'foo', 'body': 'bar'},
            )
        )
        self.notification2 = publish_notification_to_user(self.test_user_id, msg)
コード例 #31
0
    def setUp(self):
        """
        start up stuff
        """

        register_user_scope_resolver('list_scope', TestListScopeResolver())

        self.store = notification_store()
        self.callback = NotificationDispatchMessageCallback()

        self.msg_type = self.store.save_notification_type(
            NotificationType(
                name='foo.bar',
                renderer='foo',
            ))

        self.msg = self.store.save_notification_message(
            NotificationMessage(
                msg_type=self.msg_type,
                payload={'foo': 'bar'},
            ))

        self.timer_for_user = NotificationCallbackTimer(
            context={
                'msg_id': self.msg.id,
                'distribution_scope': {
                    'scope_name': 'user',
                    'scope_context': {
                        'user_id': 1
                    }
                }
            })

        self.timer_for_group = NotificationCallbackTimer(
            context={
                'msg_id': self.msg.id,
                'distribution_scope': {
                    'scope_name': 'list_scope',
                    'scope_context': {
                        'range': 5
                    }
                }
            })
コード例 #32
0
def get_notifications_count_for_user(user_id, filters=None):
    """
    Returns the number of notifications this user has

    ARGS:
        user_id: The id of the user to query
        filters: A dict containing the following optional keys
            - 'namespace': what namespace to search (defuault None)
            - 'read': Whether to return read notifications (default True)
            - 'unread': Whether to return unread notifications (default True)
            - 'type_name': what msg_types to count
    """

    # make sure user_id is an integer
    if not isinstance(user_id, int):
        raise TypeError("user_id must be an integer")

    return notification_store().get_num_notifications_for_user(
        user_id,
        filters=filters,
    )
コード例 #33
0
    def setUp(self):
        """
        Test setup
        """
        startup.initialize()
        register_user_scope_resolver("user_email_resolver", TestUserResolver())
        self.store = notification_store()

        self.msg_type = self.store.get_notification_type(name="open-edx.lms.discussions.reply-to-thread")

        self.msg = NotificationMessage(
            namespace="test-runner",
            msg_type=self.msg_type,
            payload={
                "_schema_version": 1,
                "_click_link": "http://localhost",
                "original_poster_id": 1,
                "action_username": "******",
                "thread_title": "A demo posting to the discussion forums",
            },
        )
コード例 #34
0
    def setUp(self):
        """
        Test setup
        """
        startup.initialize()
        register_user_scope_resolver('user_email_resolver', TestUserResolver())
        self.store = notification_store()

        self.msg_type = self.store.get_notification_type(name='open-edx.lms.discussions.reply-to-thread')

        self.msg = NotificationMessage(
            namespace='test-runner',
            msg_type=self.msg_type,
            payload={
                '_schema_version': 1,
                '_click_link': 'http://localhost',
                'original_poster_id': 1,
                'action_username': '******',
                'thread_title': 'A demo posting to the discussion forums',
            }
        )
コード例 #35
0
ファイル: timer.py プロジェクト: muhhshoaib/edx-notifications
def register_purge_notifications_timer(sender, **kwargs):  # pylint: disable=unused-argument
    """
    Register PurgeNotificationsCallbackHandler.
    This will be called automatically on the Notification subsystem startup (because we are
    receiving the 'perform_timer_registrations' signal)
    """
    store = notification_store()

    try:
        store.get_notification_timer(PURGE_NOTIFICATIONS_TIMER_NAME)
    except ItemNotFoundError:
        # Set first execution time at upcoming 1:00 AM (1 hour after midnight).
        first_execution_at = (datetime.now(pytz.UTC) + timedelta(days=1)).replace(hour=1, minute=0, second=0)

        purge_notifications_timer = NotificationCallbackTimer(
            name=PURGE_NOTIFICATIONS_TIMER_NAME,
            callback_at=first_execution_at,
            class_name='edx_notifications.callbacks.PurgeNotificationsCallbackHandler',
            is_active=True,
            periodicity_min=const.MINUTES_IN_A_DAY
        )
        store.save_notification_timer(purge_notifications_timer)
コード例 #36
0
def cancel_timed_notification(timer_name, exception_on_not_found=True):
    """
    Cancels a previously published timed notification
    """

    log_msg = ('Cancelling timed Notification named "{timer_name}"').format(
        timer_name=timer_name)
    log.info(log_msg)

    store = notification_store()
    try:
        timer = store.get_notification_timer(timer_name)
        timer.is_active = False  # simply make is_active=False
        store.save_notification_timer(timer)
    except ItemNotFoundError:
        if not exception_on_not_found:
            return

        err_msg = (
            'Tried to call cancel_timed_notification for timer_name "{name}" '
            'but it does not exist. Skipping...').format(name=timer_name)
        log.error(err_msg)
コード例 #37
0
def get_notifications_for_user(user_id, filters=None, options=None):
    """
    Returns a list of UserNotification for the passed in user. The list will be
    ordered by most recent first

    ARGS:
        - user_id: The id of the user
        - filters: a dict containing
            - namespace: what namespace to search (defuault None)
            - read: Whether to return read notifications (default True)
            - unread: Whether to return unread notifications (default True)
            - type_name: which type to return
        - options: a dict containing some optional parameters
            - limit: max number to return (up to some system defined max)
            - offset: offset into the list, to implement paging
    """

    # make sure user_id is an integer
    if not isinstance(user_id, int):
        raise TypeError("user_id must be an integer")

    return notification_store().get_notifications_for_user(user_id, filters=filters, options=options)
コード例 #38
0
def purge_expired_notifications():
    """
    This method reads from the configuration how long (in days) old notifications (read and unread separately)
    can remain in the system before being purged. Lack of configuration (or None) means "don't purge ever"
    and calls into the store provider's purge_expired_notifications() method.
    """

    store = notification_store()
    now = datetime.datetime.now(pytz.UTC)

    purge_read_older_than = None
    if const.NOTIFICATION_PURGE_READ_OLDER_THAN_DAYS:
        purge_read_older_than = now - datetime.timedelta(days=const.NOTIFICATION_PURGE_READ_OLDER_THAN_DAYS)

    purge_unread_older_than = None
    if const.NOTIFICATION_PURGE_UNREAD_OLDER_THAN_DAYS:
        purge_unread_older_than = now - datetime.timedelta(days=const.NOTIFICATION_PURGE_UNREAD_OLDER_THAN_DAYS)

    store.purge_expired_notifications(
        purge_read_messages_older_than=purge_read_older_than,
        purge_unread_messages_older_than=purge_unread_older_than
    )
コード例 #39
0
    def dispatch_notification_to_user(self,
                                      user_id,
                                      msg,
                                      channel_context=None):
        """
        Send a notification to a user, which - in a durable Notification -
        is simply store it in the database, and - soon in the future -
        raise some signal to a waiting client that a message is available
        """

        store = notification_store()

        # get a msg (cloned from original) with resolved links
        msg = self._get_linked_resolved_msg(msg)

        # persist the message in our Store Provide
        _msg = store.save_notification_message(msg)

        # create a new UserNotification and point to the new message
        # this new mapping will have the message in an unread state
        # NOTE: We need to set this up after msg is saved otherwise
        # we won't have it's primary key (id)
        user_msg = UserNotification(user_id=user_id, msg=_msg)

        _user_msg = store.save_user_notification(user_msg)

        #
        # When we support in-broswer push notifications
        # such as Comet/WebSockets, this is where we should
        # signal the client to come fetch the
        # notification that has just been dispatched
        #

        #
        # Here is where we will tie into the Analytics pipeline
        #

        return _user_msg
コード例 #40
0
    def dispatch_notification_to_user(self, user_id, msg, channel_context=None):
        """
        Send a notification to a user, which - in a durable Notification -
        is simply store it in the database, and - soon in the future -
        raise some signal to a waiting client that a message is available
        """

        store = notification_store()

        # get a msg (cloned from original) with resolved links
        msg = self._get_linked_resolved_msg(msg)

        # persist the message in our Store Provide
        _msg = store.save_notification_message(msg)

        # create a new UserNotification and point to the new message
        # this new mapping will have the message in an unread state
        # NOTE: We need to set this up after msg is saved otherwise
        # we won't have it's primary key (id)
        user_msg = UserNotification(
            user_id=user_id,
            msg=_msg
        )

        _user_msg = store.save_user_notification(user_msg)

        #
        # When we support in-broswer push notifications
        # such as Comet/WebSockets, this is where we should
        # signal the client to come fetch the
        # notification that has just been dispatched
        #

        #
        # Here is where we will tie into the Analytics pipeline
        #

        return _user_msg
コード例 #41
0
def purge_expired_notifications():
    """
    This method reads from the configuration how long (in days) old notifications (read and unread separately)
    can remain in the system before being purged. Lack of configuration (or None) means "don't purge ever"
    and calls into the store provider's purge_expired_notifications() method.
    """

    store = notification_store()
    now = datetime.datetime.now(pytz.UTC)

    purge_read_older_than = None
    if const.NOTIFICATION_PURGE_READ_OLDER_THAN_DAYS:
        purge_read_older_than = now - datetime.timedelta(
            days=const.NOTIFICATION_PURGE_READ_OLDER_THAN_DAYS)

    purge_unread_older_than = None
    if const.NOTIFICATION_PURGE_UNREAD_OLDER_THAN_DAYS:
        purge_unread_older_than = now - datetime.timedelta(
            days=const.NOTIFICATION_PURGE_UNREAD_OLDER_THAN_DAYS)

    store.purge_expired_notifications(
        purge_read_messages_older_than=purge_read_older_than,
        purge_unread_messages_older_than=purge_unread_older_than)
コード例 #42
0
def mark_notification_read(user_id, msg_id, read=True):
    """
    Will mark a given UserNotification as 'read' (or unread is 'unread' argument is passed in)

    ARGS:
        - user_id: The user that wishes to mark the msg as read/unread
        - msg_id: The corresponding message that is being marked
        - read: (Optional) indicate whether message should be marked as read or unread

    NOTE: If the corresponding user_id/msg_id cannot be found, then this will raise
    a ItemNotFoundError().
    """

    store = notification_store()
    user_msg = store.get_notification_for_user(user_id, msg_id)

    if read:
        # marking as read
        user_msg.read_at = datetime.now(pytz.UTC)
    else:
        # marking as unread
        user_msg.read_at = None

    store.save_user_notification(user_msg)
コード例 #43
0
def cancel_timed_notification(timer_name, exception_on_not_found=True):
    """
    Cancels a previously published timed notification
    """

    log_msg = (
        'Cancelling timed Notification named "{timer_name}"'
    ).format(timer_name=timer_name)
    log.info(log_msg)

    store = notification_store()
    try:
        timer = store.get_notification_timer(timer_name)
        timer.is_active = False  # simply make is_active=False
        store.save_notification_timer(timer)
    except ItemNotFoundError:
        if not exception_on_not_found:
            return

        err_msg = (
            'Tried to call cancel_timed_notification for timer_name "{name}" '
            'but it does not exist. Skipping...'
        ).format(name=timer_name)
        log.error(err_msg)
コード例 #44
0
def mark_notification_read(user_id, msg_id, read=True):
    """
    Will mark a given UserNotification as 'read' (or unread is 'unread' argument is passed in)

    ARGS:
        - user_id: The user that wishes to mark the msg as read/unread
        - msg_id: The corresponding message that is being marked
        - read: (Optional) indicate whether message should be marked as read or unread

    NOTE: If the corresponding user_id/msg_id cannot be found, then this will raise
    a ItemNotFoundError().
    """

    store = notification_store()
    user_msg = store.get_notification_for_user(user_id, msg_id)

    if read:
        # marking as read
        user_msg.read_at = datetime.now(pytz.UTC)
    else:
        # marking as unread
        user_msg.read_at = None

    store.save_user_notification(user_msg)
コード例 #45
0
def get_notifications_for_user(user_id, filters=None, options=None):
    """
    Returns a list of UserNotification for the passed in user. The list will be
    ordered by most recent first

    ARGS:
        - user_id: The id of the user
        - filters: a dict containing
            - namespace: what namespace to search (defuault None)
            - read: Whether to return read notifications (default True)
            - unread: Whether to return unread notifications (default True)
            - type_name: which type to return
        - options: a dict containing some optional parameters
            - limit: max number to return (up to some system defined max)
            - offset: offset into the list, to implement paging
    """

    # make sure user_id is an integer
    if not isinstance(user_id, int):
        raise TypeError("user_id must be an integer")

    return notification_store().get_notifications_for_user(user_id,
                                                           filters=filters,
                                                           options=options)
コード例 #46
0
def get_all_notification_types():
    """
    Returns all know Notification types
    """

    return notification_store().get_all_notification_types()
コード例 #47
0
def set_notification_preference(notification_preference):
    """
    Create or Update the notification preferences
    """
    store = notification_store()
    return store.save_notification_preference(notification_preference)
コード例 #48
0
def get_user_preference_by_name(user_id, name):
    """
    Returns a single UserNotificationPreference
    """
    store = notification_store()
    return store.get_user_preference(user_id=user_id, name=name)
コード例 #49
0
def set_notification_preference(notification_preference):
    """
    Create or Update the notification preferences
    """
    store = notification_store()
    return store.save_notification_preference(notification_preference)
コード例 #50
0
    def notification_timer_callback(self, timer):
        num_dispatched = 0
        err_msgs = []

        try:
            context = timer.context

            # make sure we have all information we need
            # in our various contexts
            check_keys = (
                'distribution_scope' in context and
                'msg_id' in context and
                'scope_name' in context['distribution_scope'] and
                'scope_context' in context['distribution_scope']
            )
            if not check_keys:
                err_msg = (
                    'Malformed timer "{name}" context! Expected a keys of '
                    '"msg_id", "distribution_scope.name", and '
                    '"distribution_scope.scope_context" in '
                    'timer context but could not find it in {context}.'
                ).format(name=timer.name, context=context)
                raise KeyError(err_msg)

            msg_id = int(context['msg_id'])
            scope_name = context['distribution_scope']['scope_name']
            scope_context = context['distribution_scope']['scope_context']

            log_msg = (
                'Firing timed Notification to scope name "{scope_name}" and scope '
                'context {scope_context} with message_id: {msg_id}'
            ).format(scope_name=scope_name, scope_context=scope_context, msg_id=msg_id)
            log.info(log_msg)

            channel_context = context.get('channel_context')
            preferred_channel = context.get('preferred_channel')

            try:
                notification_msg = notification_store().get_notification_message_by_id(msg_id)
            except ItemNotFoundError:
                err_msg = (
                    'Could not find msg_id {msg_id} associated '
                    'with timer "{name}". Message was not sent!'
                ).format(msg_id=msg_id, name=timer.name)

            if scope_name == 'user':
                num_dispatched = _send_to_single_user(
                    notification_msg,
                    scope_context,
                    preferred_channel=preferred_channel,
                    channel_context=channel_context
                )
                num_dispatched = 1
            else:
                num_dispatched = _send_to_scoped_users(
                    notification_msg,
                    scope_name,
                    scope_context,
                    preferred_channel=preferred_channel,
                    channel_context=channel_context
                )

        except Exception, ex:  # pylint: disable=broad-except
            log.exception(ex)
            err_msgs.append(str(ex))
コード例 #51
0
def get_user_preferences(user_id):
    """
    Returns a list of Notification Preferences
    """
    store = notification_store()
    return store.get_all_user_preferences_for_user(user_id=user_id)
コード例 #52
0
def get_user_preference_by_name(user_id, name):
    """
    Returns a single UserNotificationPreference
    """
    store = notification_store()
    return store.get_user_preference(user_id=user_id, name=name)
コード例 #53
0
def get_notification_preferences():
    """
    Returns a list of Notification Preferences
    """
    store = notification_store()
    return store.get_all_notification_preferences()
コード例 #54
0
def get_notification_preference(name):
    """
    Returns the notification preference
    """
    store = notification_store()
    return store.get_notification_preference(name=name)
コード例 #55
0
def publish_timed_notification(msg, send_at, scope_name, scope_context, timer_name=None,
                               ignore_if_past_due=False, timer_context=None):  # pylint: disable=too-many-arguments
    """
    Registers a new notification message to be dispatched
    at a particular time.

    IMPORTANT: There can only be one timer associated with
    a notification message. If it is called more than once on the
    same msg_id, then the existing one is updated.

    ARGS:
        send_at: datetime when the message should be sent
        msg: An instance of a NotificationMessage
        distribution_scope: enum of three values: 'user', 'course_group', 'course_enrollments'
               which describe the distribution scope of the message
        scope_context:
            if scope='user': then {'user_id': xxxx }
            if scope='course_group' then {'course_id': xxxx, 'group_id': xxxxx}
            if scope='course_enrollments' then {'course_id'}

        timer_name: if we know the name of the timer we want to use rather than auto-generating it.
                    use caution not to mess with other code's timers!!!
        ignore_if_past_due: If the notification should not be put into the timers, if the send date
                    is in the past

    RETURNS: instance of NotificationCallbackTimer
    """

    now = datetime.datetime.now(pytz.UTC)
    if now > send_at and ignore_if_past_due:
        log.info('Timed Notification is past due and the caller said to ignore_if_past_due. Dropping notification...')
        if timer_name:
            # If timer is named and it is past due, it is possibly being updated
            # so, then we should remove any previously stored
            # timed notification
            cancel_timed_notification(timer_name, exception_on_not_found=False)
        return

    # make sure we can resolve the scope_name
    if not has_user_scope_resolver(scope_name):
        err_msg = (
            'There is no registered scope resolver for scope_name "{name}"'
        ).format(name=scope_name)
        raise ValueError(err_msg)

    store = notification_store()

    # make sure we put the delivery timestamp on the message as well
    msg.deliver_no_earlier_than = send_at
    saved_msg = store.save_notification_message(msg)

    _timer_name = timer_name if timer_name else 'notification-dispatch-timer-{_id}'.format(_id=saved_msg.id)

    log_msg = (
        'Publishing timed Notification named "{timer_name}" to scope name "{scope_name}" and scope '
        'context {scope_context} to be sent at "{send_at} with message: {msg}'
    ).format(timer_name=_timer_name, scope_name=scope_name, scope_context=scope_context, send_at=send_at, msg=msg)
    log.info(log_msg)

    _timer_context = copy.deepcopy(timer_context) if timer_context else {}

    # add in the context that is predefined
    _timer_context.update({
        'msg_id': saved_msg.id,
        'distribution_scope': {
            'scope_name': scope_name,
            'scope_context': scope_context,
        }
    })

    timer = NotificationCallbackTimer(
        name=_timer_name,
        callback_at=send_at,
        class_name='edx_notifications.callbacks.NotificationDispatchMessageCallback',
        is_active=True,
        context=_timer_context
    )

    saved_timer = store.save_notification_timer(timer)

    return saved_timer
コード例 #56
0
def get_all_notification_types():
    """
    Returns all know Notification types
    """

    return notification_store().get_all_notification_types()
コード例 #57
0
def get_notification_type(type_name):
    """
    Returns the NotificationType registered by type_name
    """

    return notification_store().get_notification_type(type_name)
コード例 #58
0
def get_notification_type(type_name):
    """
    Returns the NotificationType registered by type_name
    """

    return notification_store().get_notification_type(type_name)
コード例 #59
0
ファイル: timer.py プロジェクト: muhhshoaib/edx-notifications
def poll_and_execute_timers(**kwargs):  # pylint: disable=unused-argument
    """
    Will look in our registry of timers and see which should be executed now. It is not
    advised to call this method on any webservers that are serving HTTP traffic as
    this can take an arbitrary amount of time
    """

    log.info('Starting poll_and_execute_timers()...')
    store = notification_store()

    timers_not_executed = store.get_all_active_timers()

    for timer in timers_not_executed:
        log.info('Executing timer: {timer}...'.format(timer=str(timer)))

        timer.executed_at = datetime.now(pytz.UTC)
        store.save_notification_timer(timer)

        try:
            module_path, _, name = timer.class_name.rpartition('.')
            log.info('Creating TimerCallback at class_name "{class_name}"'.format(class_name=timer.class_name))

            class_ = getattr(import_module(module_path), name)
            handler = class_()

            results = handler.notification_timer_callback(timer)

            # store a copy of the results in the database record
            # for the timer
            timer.results = copy.deepcopy(results)

            # successful, see if we should reschedule
            rerun_delta = results.get('reschedule_in_mins')
            rerun_delta = rerun_delta if rerun_delta else timer.periodicity_min

            if rerun_delta:
                min_delta = const.NOTIFICATION_MINIMUM_PERIODICITY_MINS
                rerun_delta = rerun_delta if rerun_delta >= min_delta else min_delta

                timer.callback_at = timer.callback_at + timedelta(minutes=rerun_delta)

                # is the rescheduling still in the past?
                if timer.callback_at < datetime.now(pytz.UTC):
                    timer.callback_at = datetime.now(pytz.UTC) + timedelta(minutes=rerun_delta)

                timer.executed_at = None  # need to reset this or it won't get picked up again

            if results.get('errors'):
                timer.err_msg = str(results['errors'])

            # see if the callback returned a 'context_update'
            # which means that we should persist this in
            # the timer context
            if 'context_update' in results:
                timer.context.update(results['context_update'])

            store.save_notification_timer(timer)
        except Exception, ex:  # pylint: disable=broad-except
            # generic error (possibly couldn't create class_name instance?)
            timer.err_msg = str(ex)
            timer.is_active = False
            store.save_notification_timer(timer)

            log.exception(ex)