def subscribe_user_to_global_notifications(user): notification_type = 'email_transactional' user_events = constants.USER_SUBSCRIPTIONS_AVAILABLE for user_event in user_events: user_event_id = to_subscription_key(user._id, user_event) subscription = NotificationSubscription(_id=user_event_id, owner=user, event_name=user_event) subscription.add_user_to_subscription(user, notification_type) subscription.save()
def test_adopt_parent_subscription_default(self): payload = { 'id': self.node._id, 'event': 'comments', 'notification_type': 'adopt_parent' } url = api_url_for('configure_subscription') self.app.post_json(url, payload, auth=self.node.creator.auth) event_id = self.node._id + '_' + 'comments' # confirm subscription was not created with assert_raises(NoResultsFound): NotificationSubscription.find_one(Q('_id', 'eq', event_id))
def subscribe_user_to_notifications(node, user): """ Update the notification settings for the creator or contributors :param user: User to subscribe to notifications """ if node.institution_id: raise InvalidSubscriptionError( 'Institutions are invalid targets for subscriptions') if node.is_collection: raise InvalidSubscriptionError( 'Collections are invalid targets for subscriptions') if node.is_deleted: raise InvalidSubscriptionError( 'Deleted Nodes are invalid targets for subscriptions') events = constants.NODE_SUBSCRIPTIONS_AVAILABLE notification_type = 'email_transactional' target_id = node._id if user.is_registered: for event in events: event_id = to_subscription_key(target_id, event) global_event_id = to_subscription_key(user._id, 'global_' + event) global_subscription = NotificationSubscription.load( global_event_id) subscription = NotificationSubscription.load(event_id) # If no subscription for component and creator is the user, do not create subscription # If no subscription exists for the component, this means that it should adopt its # parent's settings if not (node and node.parent_node and not subscription and node.creator == user): if not subscription: subscription = NotificationSubscription(_id=event_id, owner=node, event_name=event) if global_subscription: global_notification_type = get_global_notification_type( global_subscription, user) subscription.add_user_to_subscription( user, global_notification_type) else: subscription.add_user_to_subscription( user, notification_type) subscription.save()
def notify(uid, event, user, node, timestamp, **context): """ :param uid: node's id :param event: type of notification :param user: user "sending" notification :param node: the node :param timestamp: time :param context: optional variables specific to templates target_user: used with comment_replies :return: """ node_subscribers = [] subscription = NotificationSubscription.load(utils.to_subscription_key(uid, event)) if subscription: for notification_type in constants.NOTIFICATION_TYPES: subscribed_users = getattr(subscription, notification_type, []) node_subscribers.extend(subscribed_users) if subscribed_users and notification_type != 'none': for recipient in subscribed_users: event = 'comment_replies' if context.get('target_user') == recipient else event send([recipient._id], notification_type, uid, event, user, node, timestamp, **context) return check_parent(uid, event, node_subscribers, user, node, timestamp, **context)
def notify(uid, event, user, node, timestamp, **context): """ :param uid: node's id :param event: type of notification :param user: user "sending" notification :param node: the node :param timestamp: time :param context: optional variables specific to templates target_user: used with comment_replies :return: """ node_subscribers = [] subscription = NotificationSubscription.load( utils.to_subscription_key(uid, event)) if subscription: for notification_type in constants.NOTIFICATION_TYPES: subscribed_users = getattr(subscription, notification_type, []) node_subscribers.extend(subscribed_users) if subscribed_users and notification_type != 'none': for recipient in subscribed_users: event = 'comment_replies' if context.get( 'target_user') == recipient else event send([recipient._id], notification_type, uid, event, user, node, timestamp, **context) return check_parent(uid, event, node_subscribers, user, node, timestamp, **context)
def get_all_user_subscriptions(user): """ Get all Subscription objects that the user is subscribed to""" for notification_type in constants.NOTIFICATION_TYPES: query = NotificationSubscription.find( Q(notification_type, 'eq', user._id)) for subscription in query: yield subscription
def test_create_new_subscription(self): payload = { 'id': self.node._id, 'event': 'comments', 'notification_type': 'email_transactional' } url = api_url_for('configure_subscription') self.app.post_json(url, payload, auth=self.node.creator.auth) # check that subscription was created event_id = self.node._id + '_' + 'comments' s = NotificationSubscription.find_one(Q('_id', 'eq', event_id)) # check that user was added to notification_type field assert_equal(payload['id'], s.owner._id) assert_equal(payload['event'], s.event_name) assert_in(self.node.creator, getattr(s, payload['notification_type'])) # change subscription new_payload = { 'id': self.node._id, 'event': 'comments', 'notification_type': 'email_digest' } url = api_url_for('configure_subscription') self.app.post_json(url, new_payload, auth=self.node.creator.auth) s.reload() assert_false(self.node.creator in getattr(s, payload['notification_type'])) assert_in(self.node.creator, getattr(s, new_payload['notification_type']))
def check_parent(uid, event, node_subscribers, **context): """ Check subscription object for the event on the parent project and send transactional email to indirect subscribers. """ node = website_models.Node.load(uid) target_user = context.get('target_user', None) if node and node.parent_id: key = utils.to_subscription_key(node.parent_id, event) subscription = NotificationSubscription.load(key) if not subscription: return check_parent(node.parent_id, event, node_subscribers, **context) for notification_type in constants.NOTIFICATION_TYPES: subscribed_users = getattr(subscription, notification_type, []) for u in subscribed_users: if u not in node_subscribers and node.has_permission( u, 'read'): if notification_type != 'none': event = 'comment_replies' if target_user == u else event send([u._id], notification_type, uid, event, **context) node_subscribers.append(u) return check_parent(node.parent_id, event, node_subscribers, **context) return node_subscribers
def test_change_subscription_to_adopt_parent_subscription_removes_user(self): payload = { 'id': self.node._id, 'event': 'comments', 'notification_type': 'email_transactional' } url = api_url_for('configure_subscription') self.app.post_json(url, payload, auth=self.node.creator.auth) # check that subscription was created event_id = self.node._id + '_' + 'comments' s = NotificationSubscription.find_one(Q('_id', 'eq', event_id)) # change subscription to adopt_parent new_payload = { 'id': self.node._id, 'event': 'comments', 'notification_type': 'adopt_parent' } url = api_url_for('configure_subscription') self.app.post_json(url, new_payload, auth=self.node.creator.auth) s.reload() # assert that user is removed from the subscription entirely for n in constants.NOTIFICATION_TYPES: assert_false(self.node.creator in getattr(s, n))
def get_user_subscriptions(user, event): user_subscription = NotificationSubscription.load( utils.to_subscription_key(user._id, event)) return { key: getattr(user_subscription, key, []) for key in constants.NOTIFICATION_TYPES }
def check_parent(uid, event, node_subscribers, user, orig_node, timestamp, **context): """ Check subscription object for the event on the parent project and send transactional email to indirect subscribers. """ node = website_models.Node.load(uid) target_user = context.get('target_user', None) if node and node.parent_id: key = utils.to_subscription_key(node.parent_id, event) subscription = NotificationSubscription.load(key) if not subscription: return check_parent(node.parent_id, event, node_subscribers, user, orig_node, timestamp, **context) for notification_type in constants.NOTIFICATION_TYPES: subscribed_users = getattr(subscription, notification_type, []) for u in subscribed_users: if u not in node_subscribers and node.has_permission(u, 'read'): if notification_type != 'none': event = 'comment_replies' if target_user == u else event send([u._id], notification_type, uid, event, user, orig_node, timestamp, **context) node_subscribers.append(u) return check_parent(node.parent_id, event, node_subscribers, user, orig_node, timestamp, **context) return node_subscribers
def move_subscription(remove_users, source_event, source_node, new_event, new_node): """Moves subscription from old_node to new_node :param remove_users: dictionary of lists of users to remove from the subscription :param source_event: A specific guid event <guid>_file_updated :param source_node: Instance of Node :param new_event: A specific guid event :param new_node: Instance of Node :return: Returns a NOTIFICATION_TYPES list of removed users without permissions """ if source_node == new_node: return old_sub = NotificationSubscription.load(to_subscription_key(source_node._id, source_event)) if not old_sub: return elif old_sub: old_sub.update_fields(_id=to_subscription_key(new_node._id, new_event), event_name=new_event, owner=new_node) new_sub = old_sub # Remove users that don't have permission on the new node. for notification_type in constants.NOTIFICATION_TYPES: if new_sub: for user_id in remove_users[notification_type]: if user_id in getattr(new_sub, notification_type, []): user = User.load(user_id) new_sub.remove_user_from_subscription(user)
def move_subscription(remove_users, source_event, source_node, new_event, new_node): """Moves subscription from old_node to new_node :param remove_users: dictionary of lists of users to remove from the subscription :param source_event: A specific guid event <guid>_file_updated :param source_node: Instance of Node :param new_event: A specific guid event :param new_node: Instance of Node :return: Returns a NOTIFICATION_TYPES list of removed users without permissions """ if source_node == new_node: return old_sub = NotificationSubscription.load( to_subscription_key(source_node._id, source_event)) if not old_sub: return elif old_sub: old_sub.update_fields(_id=to_subscription_key(new_node._id, new_event), event_name=new_event, owner=new_node) new_sub = old_sub # Remove users that don't have permission on the new node. for notification_type in constants.NOTIFICATION_TYPES: if new_sub: for user_id in remove_users[notification_type]: if user_id in getattr(new_sub, notification_type, []): user = User.load(user_id) new_sub.remove_user_from_subscription(user)
def users_to_remove(source_event, source_node, new_node): """Find users that do not have permissions on new_node. :param source_event: such as _file_updated :param source_node: Node instance where a subscription currently resides :param new_node: Node instance where a sub or new sub will be. :return: Dict of notification type lists with user_ids """ removed_users = {key: [] for key in constants.NOTIFICATION_TYPES} if source_node == new_node: return removed_users old_sub = NotificationSubscription.load(to_subscription_key(source_node._id, source_event)) old_node_sub = NotificationSubscription.load(to_subscription_key(source_node._id, '_'.join(source_event.split('_')[-2:]))) if not old_sub and not old_node_sub: return removed_users for notification_type in constants.NOTIFICATION_TYPES: users = getattr(old_sub, notification_type, []) + getattr(old_node_sub, notification_type, []) subbed, removed_users[notification_type] = separate_users(new_node, users) return removed_users
def check_node(node, event): """Return subscription for a particular node and event.""" node_subscriptions = {key: [] for key in constants.NOTIFICATION_TYPES} if node: subscription = NotificationSubscription.load(utils.to_subscription_key(node._id, event)) for notification_type in node_subscriptions: users = getattr(subscription, notification_type, []) for user in users: if node.has_permission(user, 'read'): node_subscriptions[notification_type].append(user._id) return node_subscriptions
def test_node_subscriptions_and_backrefs_removed_when_node_is_deleted(self): project = factories.ProjectFactory() subscription = factories.NotificationSubscriptionFactory( _id=project._id + '_comments', owner=project ) subscription.save() subscription.email_transactional.append(project.creator) subscription.save() s = getattr(project.creator, 'email_transactional', []) assert_equal(len(s), 1) with capture_signals() as mock_signals: project.remove_node(auth=Auth(project.creator)) assert_true(project.is_deleted) assert_equal(mock_signals.signals_sent(), set([node_deleted])) s = getattr(project.creator, 'email_transactional', []) assert_equal(len(s), 0) with assert_raises(NoResultsFound): NotificationSubscription.find_one(Q('owner', 'eq', project))
def notify(uid, event, **context): node_subscribers = [] subscription = NotificationSubscription.load(utils.to_subscription_key(uid, event)) if subscription: for notification_type in constants.NOTIFICATION_TYPES: subscribed_users = getattr(subscription, notification_type, []) node_subscribers.extend(subscribed_users) if subscribed_users and notification_type != 'none': event = 'comment_replies' if context.get('target_user') else event send([u._id for u in subscribed_users], notification_type, uid, event, **context) return check_parent(uid, event, node_subscribers, **context)
def subscribe_user_to_notifications(node, user): """ Update the notification settings for the creator or contributors :param user: User to subscribe to notifications """ if node.institution_id: raise InvalidSubscriptionError('Institutions are invalid targets for subscriptions') if node.is_collection: raise InvalidSubscriptionError('Collections are invalid targets for subscriptions') if node.is_deleted: raise InvalidSubscriptionError('Deleted Nodes are invalid targets for subscriptions') events = constants.NODE_SUBSCRIPTIONS_AVAILABLE notification_type = 'email_transactional' target_id = node._id if user.is_registered: for event in events: event_id = to_subscription_key(target_id, event) global_event_id = to_subscription_key(user._id, 'global_' + event) global_subscription = NotificationSubscription.load(global_event_id) subscription = NotificationSubscription.load(event_id) # If no subscription for component and creator is the user, do not create subscription # If no subscription exists for the component, this means that it should adopt its # parent's settings if not(node and node.parent_node and not subscription and node.creator == user): if not subscription: subscription = NotificationSubscription(_id=event_id, owner=node, event_name=event) if global_subscription: global_notification_type = get_global_notification_type(global_subscription, user) subscription.add_user_to_subscription(user, global_notification_type) else: subscription.add_user_to_subscription(user, notification_type) subscription.save()
def add_global_subscriptions(dry=True): OSFUser = apps.get_model('osf.OSFUser') notification_type = 'email_transactional' user_events = constants.USER_SUBSCRIPTIONS_AVAILABLE count = 0 with transaction.atomic(): for user in OSFUser.objects.filter(is_registered=True, date_confirmed__isnull=False): changed = False if not user.is_active: continue for user_event in user_events: user_event_id = to_subscription_key(user._id, user_event) subscription = NotificationSubscription.load(user_event_id) if not subscription: logger.info( 'No {} subscription found for user {}. Subscribing...'. format(user_event, user._id)) subscription = NotificationSubscription( _id=user_event_id, owner=user, event_name=user_event) subscription.save( ) # Need to save in order to access m2m fields subscription.add_user_to_subscription( user, notification_type) subscription.save() changed = True else: logger.info('User {} already has a {} subscription'.format( user._id, user_event)) if changed: count += 1 logger.info('Added subscriptions for {} users'.format(count)) if dry: raise RuntimeError('Dry mode -- rolling back transaction')
def notify(uid, event, **context): node_subscribers = [] subscription = NotificationSubscription.load( utils.to_subscription_key(uid, event)) if subscription: for notification_type in constants.NOTIFICATION_TYPES: subscribed_users = getattr(subscription, notification_type, []) node_subscribers.extend(subscribed_users) if subscribed_users and notification_type != 'none': for user in subscribed_users: event = 'comment_replies' if context.get( 'target_user') == user else event send([user._id], notification_type, uid, event, **context) return check_parent(uid, event, node_subscribers, **context)
def add_global_subscriptions(): notification_type = 'email_transactional' user_events = constants.USER_SUBSCRIPTIONS_AVAILABLE for user in models.User.find(): if user.is_active and user.is_registered: for user_event in user_events: user_event_id = to_subscription_key(user._id, user_event) subscription = NotificationSubscription.load(user_event_id) if not subscription: subscription = NotificationSubscription( _id=user_event_id, owner=user, event_name=user_event) subscription.add_user_to_subscription( user, notification_type) subscription.save() logger.info('No subscription found. {} created.'.format( subscription)) else: logger.info('Subscription {} found.'.format(subscription))
def add_global_subscriptions(): notification_type = 'email_transactional' user_events = constants.USER_SUBSCRIPTIONS_AVAILABLE for user in models.User.find(): if user.is_active and user.is_registered: for user_event in user_events: user_event_id = to_subscription_key(user._id, user_event) subscription = NotificationSubscription.load(user_event_id) if not subscription: subscription = NotificationSubscription(_id=user_event_id, owner=user, event_name=user_event) subscription.add_user_to_subscription(user, notification_type) subscription.save() logger.info('No subscription found. {} created.'.format(subscription)) else: logger.info('Subscription {} found.'.format(subscription))
def get_user_subscriptions(user, event): user_subscription = NotificationSubscription.load(utils.to_subscription_key(user._id, event)) return {key: getattr(user_subscription, key, []) for key in constants.NOTIFICATION_TYPES}
def configure_subscription(auth): user = auth.user json_data = request.get_json() target_id = json_data.get('id') event = json_data.get('event') notification_type = json_data.get('notification_type') if not event or (notification_type not in NOTIFICATION_TYPES and notification_type != 'adopt_parent'): raise HTTPError(http.BAD_REQUEST, data=dict( message_long="Must provide an event and notification type for subscription.") ) node = Node.load(target_id) event_id = utils.to_subscription_key(target_id, event) if not node: # if target_id is not a node it currently must be the current user if not target_id == user._id: sentry.log_message( '{!r} attempted to subscribe to either a bad ' 'id or non-node non-self id, {}'.format(user, target_id) ) raise HTTPError(http.NOT_FOUND) if notification_type == 'adopt_parent': sentry.log_message( '{!r} attempted to adopt_parent of a none node id, {}'.format(user, target_id) ) raise HTTPError(http.BAD_REQUEST) owner = user else: if not node.has_permission(user, 'read'): sentry.log_message('{!r} attempted to subscribe to private node, {}'.format(user, target_id)) raise HTTPError(http.FORBIDDEN) if notification_type != 'adopt_parent': owner = node else: parent = node.parent_node if not parent: sentry.log_message( '{!r} attempted to adopt_parent of ' 'the parentless project, {!r}'.format(user, node) ) raise HTTPError(http.BAD_REQUEST) # If adopt_parent make sure that this subscription is None for the current User subscription = NotificationSubscription.load(event_id) if not subscription: return {} # We're done here subscription.remove_user_from_subscription(user) return {} subscription = NotificationSubscription.load(event_id) if not subscription: subscription = NotificationSubscription(_id=event_id, owner=owner, event_name=event) subscription.add_user_to_subscription(user, notification_type) subscription.save() return {'message': 'Successfully subscribed to {} list on {}'.format(notification_type, event_id)}
def configure_subscription(auth): user = auth.user json_data = request.get_json() target_id = json_data.get('id') event = json_data.get('event') notification_type = json_data.get('notification_type') if not event or (notification_type not in NOTIFICATION_TYPES and notification_type != 'adopt_parent'): raise HTTPError( http.BAD_REQUEST, data=dict( message_long= "Must provide an event and notification type for subscription." )) node = Node.load(target_id) event_id = utils.to_subscription_key(target_id, event) if not node: # if target_id is not a node it currently must be the current user if not target_id == user._id: sentry.log_message('{!r} attempted to subscribe to either a bad ' 'id or non-node non-self id, {}'.format( user, target_id)) raise HTTPError(http.NOT_FOUND) if notification_type == 'adopt_parent': sentry.log_message( '{!r} attempted to adopt_parent of a none node id, {}'.format( user, target_id)) raise HTTPError(http.BAD_REQUEST) owner = user else: if not node.has_permission(user, 'read'): sentry.log_message( '{!r} attempted to subscribe to private node, {}'.format( user, target_id)) raise HTTPError(http.FORBIDDEN) if notification_type != 'adopt_parent': owner = node else: parent = node.parent_node if not parent: sentry.log_message('{!r} attempted to adopt_parent of ' 'the parentless project, {!r}'.format( user, node)) raise HTTPError(http.BAD_REQUEST) # If adopt_parent make sure that this subscription is None for the current User subscription = NotificationSubscription.load(event_id) if not subscription: return {} # We're done here subscription.remove_user_from_subscription(user) return {} subscription = NotificationSubscription.load(event_id) if not subscription: subscription = NotificationSubscription(_id=event_id, owner=owner, event_name=event) subscription.add_user_to_subscription(user, notification_type) subscription.save() return { 'message': 'Successfully subscribed to {} list on {}'.format( notification_type, event_id) }
def get_all_user_subscriptions(user): """ Get all Subscription objects that the user is subscribed to""" for notification_type in constants.NOTIFICATION_TYPES: query = NotificationSubscription.find(Q(notification_type, 'eq', user._id)) for subscription in query: yield subscription