def test_purge_command_check(self): """ Invoke the Management Command """ msg_type = self.provider.save_notification_type(self.notification_type) msg1 = self.provider.save_notification_message(NotificationMessage( namespace='namespace1', msg_type=msg_type, payload={ 'foo': 'bar' } )) msg2 = self.provider.save_notification_message(NotificationMessage( namespace='namespace1', msg_type=msg_type, payload={ 'test': 'test' } )) # now reset the time to 66 days ago # in order to save the user notification message in the past. reset_time = datetime.now(pytz.UTC) - timedelta(days=66) with freeze_time(reset_time): self.provider.save_user_notification(UserNotification( user_id=self.test_user_id, msg=msg1 )) self.provider.save_user_notification(UserNotification( user_id=self.test_user_id, msg=msg2 )) # user notifications count self.assertEqual( self.provider.get_num_notifications_for_user( self.test_user_id, filters={ 'namespace': 'namespace1' } ), 2 ) # run the management command for purging notifications. force_purge.Command().handle() # now get the user notification count. # count should be 0 at that moment. because # all the notifications have been deleted. self.assertEqual( self.provider.get_num_notifications_for_user( self.test_user_id, filters={ 'namespace': 'namespace1' } ), 0 )
def test_purge_expired_read_notifications(self): """ Test to check for the older read messages. If exists delete those messages from the database. """ msg_type = self._save_notification_type() msg1 = self.provider.save_notification_message( NotificationMessage(namespace='namespace1', msg_type=msg_type, payload={'foo': 'bar'})) msg2 = self.provider.save_notification_message( NotificationMessage(namespace='namespace1', msg_type=msg_type, payload={'test': 'test'})) # now reset the time to 10 days ago # in order to save the user notification messages in the past. reset_time = datetime.now(pytz.UTC) - timedelta(days=10) with freeze_time(reset_time): self.provider.save_user_notification( UserNotification(user_id=self.test_user_id, msg=msg1)) # mark the user notification as read. self.provider.mark_user_notifications_read(self.test_user_id) # now reset the time to 2 days ago # in order to save the user notification messages in the past. reset_time = datetime.now(pytz.UTC) - timedelta(days=2) with freeze_time(reset_time): self.provider.save_user_notification( UserNotification(user_id=self.test_user_id, msg=msg2)) # mark the user notification as read. self.provider.mark_user_notifications_read(self.test_user_id) # user notifications count self.assertEqual( self.provider.get_num_notifications_for_user( self.test_user_id, filters={'namespace': 'namespace1'}), 2) # purge older read messages. purge_older_read_messages = datetime.now(pytz.UTC) - timedelta(days=6) self.provider.purge_expired_notifications( purge_read_messages_older_than=purge_older_read_messages) # now get the user notification count. # count should be 1 at that moment. because # only 1 notification has been deleted. self.assertEqual( self.provider.get_num_notifications_for_user( self.test_user_id, filters={'namespace': 'namespace1'}), 1)
def _setup_user_notifications(self): """ Helper to build out some """ msg_type = self._save_notification_type() # set up some notifications msg1 = self.provider.save_notification_message(NotificationMessage( namespace='namespace1', msg_type=msg_type, payload={ 'foo': 'bar', 'one': 1, 'none': None, 'datetime': datetime.utcnow(), 'iso8601-fakeout': '--T::', # something to throw off the iso8601 parser heuristic } )) map1 = self.provider.save_user_notification(UserNotification( user_id=self.test_user_id, msg=msg1 )) msg_type2 = self.provider.save_notification_type( NotificationType( name='foo.bar.another', renderer='foo.renderer', ) ) msg2 = self.provider.save_notification_message(NotificationMessage( namespace='namespace2', msg_type=msg_type2, payload={ 'foo': 'baz', 'one': 1, 'none': None, 'datetime': datetime.utcnow(), 'iso8601-fakeout': '--T::', # something to throw off the iso8601 parser heuristic } )) map2 = self.provider.save_user_notification(UserNotification( user_id=self.test_user_id, msg=msg2 )) return map1, msg1, map2, msg2
def test_over_limit_counting(self): """ Verifies that our counting operations will work as expected even when our count is greater that the NOTIFICATION_MAX_LIST_SIZE which is the maximum page size """ self.assertEqual(const.NOTIFICATION_MAX_LIST_SIZE, 1) msg_type = self._save_notification_type() for __ in range(10): msg = self.provider.save_notification_message(NotificationMessage( namespace='namespace1', msg_type=msg_type, payload={ 'foo': 'bar' } )) self.provider.save_user_notification(UserNotification( user_id=self.test_user_id, msg=msg )) self.assertEqual( self.provider.get_num_notifications_for_user( self.test_user_id, filters={ 'namespace': 'namespace1', } ), 10 )
def test_mark_user_notification_read(self): """ """ msg_type = self._save_notification_type() for __ in range(10): msg = self.provider.save_notification_message( NotificationMessage(namespace='namespace1', msg_type=msg_type, payload={'foo': 'bar'})) self.provider.save_user_notification( UserNotification(user_id=self.test_user_id, msg=msg)) self.assertEqual( self.provider.get_num_notifications_for_user(self.test_user_id, filters={ 'namespace': 'namespace1', }), 10) self.provider.mark_user_notifications_read(self.test_user_id) self.assertEqual( self.provider.get_num_notifications_for_user(self.test_user_id, filters={ 'namespace': 'namespace1', 'read': False }), 0)
def test_bulk_user_notification_create(self): """ Test that we can create new UserNotifications using an optimized code path to minimize round trips to the database """ msg_type = self._save_notification_type() # set up some notifications msg = self.provider.save_notification_message(NotificationMessage( namespace='namespace1', msg_type=msg_type, payload={ 'foo': 'bar', 'one': 1, 'none': None, 'datetime': datetime.utcnow(), 'iso8601-fakeout': '--T::', # something to throw off the iso8601 parser heuristic } )) user_msgs = [] for user_id in range(const.NOTIFICATION_BULK_PUBLISH_CHUNK_SIZE): user_msgs.append( UserNotification(user_id=user_id, msg=msg) ) # assert that this only takes one round-trip to the database # to insert all of them with self.assertNumQueries(1): self.provider.bulk_create_user_notification(user_msgs) # now make sure that we can query each one for user_id in range(const.NOTIFICATION_BULK_PUBLISH_CHUNK_SIZE): notifications = self.provider.get_notifications_for_user(user_id) self.assertEqual(len(notifications), 1) self.assertEqual(notifications[0].msg, msg) # now test if we send in a size too large that an exception is raised user_msgs.append( UserNotification(user_id=user_id, msg=msg) ) with self.assertRaises(BulkOperationTooLarge): self.provider.bulk_create_user_notification(user_msgs)
def restore_object(self, attrs, instance=None): """ Instantiate a new object from the deserialized data """ if instance is not None: raise NotImplementedError() user_msg = UserNotification(**attrs) # pylint: disable=star-args return user_msg
def to_data_object(self, options=None): # pylint: disable=unused-argument """ Generate a NotificationType data object """ return UserNotification( id=self.id, user_id=self.user_id, msg=self.msg.to_data_object(), # pylint: disable=no-member read_at=self.read_at, user_context=DictField.from_json(self.user_context), created=self.created)
def _gen_notifications(count, namespace): """ Helper to generate notifications """ for __ in range(count): msg = self.provider.save_notification_message( NotificationMessage(namespace=namespace, msg_type=msg_type, payload={'foo': 'bar'})) self.provider.save_user_notification( UserNotification(user_id=self.test_user_id, msg=msg))
def test_archive_the_purged_notifications(self): """ Test to check that deleting user notification should be archive. """ msg_type = self._save_notification_type() msg = self.provider.save_notification_message(NotificationMessage( namespace='namespace1', msg_type=msg_type, payload={ 'test': 'test' } )) # now reset the time to 7 days ago # in order to save the user notification message in the past. reset_time = datetime.now(pytz.UTC) - timedelta(days=7) with freeze_time(reset_time): user_notification = self.provider.save_user_notification(UserNotification( user_id=self.test_user_id, msg=msg )) # mark the user notification as read. self.provider.mark_user_notifications_read(self.test_user_id) self.assertEqual(SQLUserNotificationArchive.objects.all().count(), 0) # purge older read messages. purge_older_read_messages = datetime.now(pytz.UTC) - timedelta(days=6) self.provider.purge_expired_notifications(purge_read_messages_older_than=purge_older_read_messages) # now get the user notification count. # count should be 0 at that moment. because # 1 notification has been deleted. self.assertEqual( self.provider.get_num_notifications_for_user( self.test_user_id, filters={ 'namespace': 'namespace1' } ), 0 ) # Notification should be archived # count should be increased by 1. self.assertEqual(SQLUserNotificationArchive.objects.all().count(), 1) archived_notification = SQLUserNotificationArchive.objects.all()[0] self.assertEqual(archived_notification.msg_id, user_notification.msg.id) self.assertEqual(archived_notification.user_id, user_notification.user_id)
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
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
def dispatch_notification_to_user(self, user_id, msg, channel_context=None): """ Send a notification to a user, which - in a TriggerEmailChannel Notification """ # call into one of the registered resolvers to get the email for this # user scope_results = resolve_user_scope( 'user_email_resolver', { 'user_id': user_id, 'fields': { 'user_id': True, 'email': True, 'first_name': True, 'last_name': True, } }) msg = self._get_linked_resolved_msg(msg) msg.created = datetime.datetime.now(pytz.UTC) user_msg = UserNotification(user_id=user_id, msg=msg) config = const.NOTIFICATION_DIGEST_GROUP_CONFIG for result in scope_results: # # Do the rendering and the sending of the email # if isinstance(result, dict): email = result['email'] else: email = result renderer = get_renderer_for_type(user_msg.msg.msg_type) notification_html = '' if renderer and renderer.can_render_format( const.RENDER_FORMAT_HTML): notification_html = renderer.render( # pylint: disable=unused-variable user_msg.msg, const.RENDER_FORMAT_HTML, None) # create the image dictionary to store the # img_path, unique id and title for the image. branded_logo = dict(title='Logo', path=const.NOTIFICATION_BRANDED_DEFAULT_LOGO, cid=str(uuid.uuid4())) group_name = get_group_name_for_msg_type( user_msg.msg.msg_type.name) resolve_links = user_msg.msg.resolve_links click_link = user_msg.msg.payload['_click_link'] if resolve_links and not click_link.startswith('http'): click_link = const.NOTIFICATION_EMAIL_CLICK_LINK_URL_FORMAT.format( url_path=click_link, encoded_url_path=six.moves.urllib.parse.quote(click_link), user_msg_id=user_msg.id, msg_id=user_msg.msg.id, hostname=const.NOTIFICATION_APP_HOSTNAME) context = { 'branded_logo': branded_logo['cid'], 'notification_html': notification_html, 'user_first_name': result['first_name'] if isinstance(result, dict) else None, 'user_last_name': result['last_name'] if isinstance(result, dict) else None, 'group_name': group_name, 'group_title': config['groups'][group_name]['display_name'], 'click_link': click_link } # render the notifications html template email_body = with_inline_css( render_to_string( "django/triggered_notification_email/triggered_email.html", context)) html_part = MIMEMultipart(_subtype='related') html_part.attach(MIMEText(email_body, _subtype='html')) logo_image = attach_image(branded_logo, 'Header Logo') if logo_image: html_part.attach(logo_image) log.info('Sending Notification email to %s', email) msg = EmailMessage(const.NOTIFICATION_TRIGGERED_EMAIL_SUBJECT, '', const.NOTIFICATION_EMAIL_FROM_ADDRESS, [email]) msg.attach(html_part) msg.send() return user_msg