Introducing notifications

This commit is contained in:
Francesco Siddi 2016-03-05 23:22:57 +01:00
parent 20ba507095
commit 65f8bdc6c0
3 changed files with 177 additions and 21 deletions

View File

@ -15,6 +15,7 @@ from eve import Eve
from eve.auth import TokenAuth from eve.auth import TokenAuth
from eve.io.mongo import Validator from eve.io.mongo import Validator
RFC1123_DATE_FORMAT = '%a, %d %b %Y %H:%M:%S GMT' RFC1123_DATE_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
@ -118,6 +119,9 @@ from application.utils.authorization import check_permissions
from application.utils.gcs import update_file_name from application.utils.gcs import update_file_name
from application.utils.algolia import algolia_index_user_save from application.utils.algolia import algolia_index_user_save
from application.utils.algolia import algolia_index_node_save from application.utils.algolia import algolia_index_node_save
from application.utils.activities import activity_create
from application.utils.activities import activity_subscribe
# from application.utils.activities import notification_parse
from modules.file_storage import process_file from modules.file_storage import process_file
from modules.file_storage import delete_file from modules.file_storage import delete_file
from modules.file_storage import generate_link from modules.file_storage import generate_link
@ -164,6 +168,22 @@ def before_inserting_nodes(items):
if project: if project:
item['project'] = project['_id'] item['project'] = project['_id']
def after_inserting_nodes(items):
for item in items:
activity_create(item['user'], 'node', item['_id'])
if item['node_type'] == 'comment':
verb = 'commented'
else:
verb = 'posted'
activity_subscribe(
item['user'],
verb,
'node',
item['_id'],
'node',
item['parent']
)
def item_parse_attachments(response): def item_parse_attachments(response):
"""Before returning a response, check if the 'attachments' property is """Before returning a response, check if the 'attachments' property is
defined. If yes, load the file (for the moment only images) in the required defined. If yes, load the file (for the moment only images) in the required
@ -229,16 +249,21 @@ def project_node_type_has_method(response):
if not check_permissions(node_type, 'GET', append_allowed_methods=True): if not check_permissions(node_type, 'GET', append_allowed_methods=True):
return abort(403) return abort(403)
# def before_returning_notifications(response):
# for item in response['_items']:
# notification_parse(item)
app.on_fetched_item_nodes += before_returning_item_permissions app.on_fetched_item_nodes += before_returning_item_permissions
app.on_fetched_item_nodes += item_parse_attachments app.on_fetched_item_nodes += item_parse_attachments
app.on_fetched_resource_nodes += before_returning_resource_permissions app.on_fetched_resource_nodes += before_returning_resource_permissions
app.on_fetched_resource_nodes += resource_parse_attachments app.on_fetched_resource_nodes += resource_parse_attachments
app.on_fetched_item_node_types += before_returning_item_permissions app.on_fetched_item_node_types += before_returning_item_permissions
# app.on_fetched_resource_notifications += before_returning_notifications
app.on_fetched_resource_node_types += before_returning_resource_permissions app.on_fetched_resource_node_types += before_returning_resource_permissions
app.on_replace_nodes += before_replacing_node app.on_replace_nodes += before_replacing_node
app.on_replaced_nodes += after_replacing_node app.on_replaced_nodes += after_replacing_node
app.on_insert_nodes += before_inserting_nodes app.on_insert_nodes += before_inserting_nodes
app.on_inserted_nodes += after_inserting_nodes
app.on_fetched_item_projects += before_returning_item_permissions app.on_fetched_item_projects += before_returning_item_permissions
app.on_fetched_item_projects += project_node_type_has_method app.on_fetched_item_projects += project_node_type_has_method
app.on_fetched_resource_projects += before_returning_resource_permissions app.on_fetched_resource_projects += before_returning_resource_permissions

View File

@ -0,0 +1,132 @@
from eve.methods.post import post_internal
from application import app
# def notification_parse(notification):
# # TODO: finish fixing this
# activities_collection = app.data.driver.db['activities']
# users_collection = app.data.driver.db['users']
# nodes_collection = app.data.driver.db['nodes']
# activity = activities_collection.find_one({'_id': notification['_id']})
# actor = users_collection.find_one({'_id': activity['actor_user']})
# # Context is optional
# context_object_type = None
# context_object_name = None
# context_object_url = None
# if activity['object_type'] == 'node':
# node = nodes_collection.find_one({'_id': activity['object']})
# # project = Project.find(node.project, {
# # 'projection': '{"name":1, "url":1}'}, api=api)
# # Initial support only for node_type comments
# if node['node_type'] == 'comment':
# # comment = Comment.query.get_or_404(notification_object.object_id)
# node['parent'] = nodes_collection.find_one({'_id': node['parent']})
# object_type = 'comment'
# object_name = ''
# object_url = url_for('nodes.view', node_id=node._id, redir=1)
# if node.parent.user == current_user.objectid:
# owner = "your {0}".format(node.parent.node_type)
# else:
# parent_comment_user = User.find(node.parent.user, api=api)
# owner = "{0}'s {1}".format(parent_comment_user.username,
# node.parent.node_type)
# context_object_type = node.parent.node_type
# context_object_name = owner
# context_object_url = url_for('nodes.view', node_id=node.parent._id, redir=1)
# if activity.verb == 'replied':
# action = 'replied to'
# elif activity.verb == 'commented':
# action = 'left a comment on'
# else:
# action = activity.verb
# else:
# return None
# else:
# return None
# return dict(
# _id=notification._id,
# username=actor.username,
# username_avatar=actor.gravatar(),
# action=action,
# object_type=object_type,
# object_name=object_name,
# object_url=object_url,
# context_object_type=context_object_type,
# context_object_name=context_object_name,
# context_object_url=context_object_url,
# date=pretty_date(activity._created),
# is_read=notification.is_read,
# # is_subscribed=notification.is_subscribed
# )
def notification_get_subscriptions(context_object_type, context_object_id, actor_user_id):
subscriptions_collection = app.data.driver.db['activities-subscriptions']
lookup = {
'user': {"$ne": actor_user_id},
'context_object_type': context_object_type,
'context_object': context_object_id,
'is_subscribed': True,
}
print lookup
return subscriptions_collection.find(lookup)
def activity_create(user_id, context_object_type, context_object_id):
"""Subscribe a user to changes for a specific context. We create a subscription
if none is found.
:param user_id: id of the user we are going to subscribe
:param context_object_type: hardcoded index, check the notifications/model.py
:param context_object_id: object id, to be traced with context_object_type_id
"""
subscriptions_collection = app.data.driver.db['activities-subscriptions']
lookup = {
'user': user_id,
'context_object_type': context_object_type,
'context_object': context_object_id
}
subscription = subscriptions_collection.find_one(lookup)
# If no subscription exists, we create one
if not subscription:
post_internal('activities-subscriptions', lookup)
def activity_subscribe(actor_user_id, verb, object_type, object_id,
context_object_type, context_object_id):
"""Add a notification object and creates a notification for each user that
- is not the original author of the post
- is actively subscribed to the object
This works using the following pattern:
ACTOR -> VERB -> OBJECT -> CONTEXT
:param actor_user_id: id of the user who is changing the object
:param verb: the action on the object ('commented', 'replied')
:param object_type: hardcoded name
:param object_id: object id, to be traced with object_type_id
"""
subscriptions = notification_get_subscriptions(
context_object_type, context_object_id, actor_user_id)
if subscriptions.count():
activity = dict(
actor_user=actor_user_id,
verb=verb,
object_type=object_type,
object=object_id,
context_object_type=context_object_type,
context_object=context_object_id
)
activity = post_internal('activities', activity)
for subscription in subscriptions:
notification = dict(
user=subscription['user'],
activity=activity[0]['_id'])
post_internal('notifications', notification)

View File

@ -20,6 +20,16 @@ _file_embedded_schema = {
} }
} }
_required_user_embedded_schema = {
'type': 'objectid',
'required': True,
'data_relation': {
'resource': 'users',
'field': '_id',
'embeddable': True
},
}
_activity_object_type = { _activity_object_type = {
'type': 'string', 'type': 'string',
'required': True, 'required': True,
@ -251,7 +261,6 @@ nodes_schema = {
}, },
'picture': { 'picture': {
'type': 'objectid', 'type': 'objectid',
'nullable': True,
'data_relation': { 'data_relation': {
'resource': 'files', 'resource': 'files',
'field': '_id', 'field': '_id',
@ -629,10 +638,7 @@ projects_schema = {
} }
activities_subscriptions_schema = { activities_subscriptions_schema = {
'user': { 'user': _required_user_embedded_schema,
'type': 'objectid',
'required': True
},
'context_object_type': _activity_object_type, 'context_object_type': _activity_object_type,
'context_object': { 'context_object': {
'type': 'objectid', 'type': 'objectid',
@ -646,16 +652,18 @@ activities_subscriptions_schema = {
}, },
'web': { 'web': {
'type': 'boolean', 'type': 'boolean',
'default': True
}, },
} }
},
'is_subscribed': {
'type': 'boolean',
'default': True
} }
} }
activities_schema = { activities_schema = {
'actor_user': { 'actor_user': _required_user_embedded_schema,
'type': 'objectid',
'required': True
},
'verb': { 'verb': {
'type': 'string', 'type': 'string',
'required': True 'required': True
@ -673,13 +681,10 @@ activities_schema = {
} }
notifications_schema = { notifications_schema = {
'user': { 'user': _required_user_embedded_schema,
'type': 'objectid',
'required': True
},
'activity': { 'activity': {
'type': 'objectid', 'type': 'objectid',
'required': True 'required': True,
}, },
'is_read': { 'is_read': {
'type': 'boolean', 'type': 'boolean',
@ -752,20 +757,14 @@ projects = {
activities = { activities = {
'schema': activities_schema, 'schema': activities_schema,
'public_item_methods': None,
'public_methods': None
} }
activities_subscriptions = { activities_subscriptions = {
'schema': activities_subscriptions_schema, 'schema': activities_subscriptions_schema,
'public_item_methods': None,
'public_methods': None
} }
notifications = { notifications = {
'schema': activities_subscriptions_schema, 'schema': notifications_schema,
'public_item_methods': None,
'public_methods': None
} }