-
Notifications
You must be signed in to change notification settings - Fork 40
Notification framework #191
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 11 commits
dc5e6f8
3d9a85d
c79c1bd
5cd7500
d507c44
1dd3d6b
47feb76
0e9465f
6982837
ec85ee2
f630f8a
4c24e6c
db07aa5
e98ff6f
99c3aff
80124a4
7d5a2c9
24ea0e2
3706261
9c672dd
3542ff8
8cd3d09
d9f31bb
d4f786a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| from django.core.exceptions import ObjectDoesNotExist | ||
|
|
||
| class EventNotFound(ObjectDoesNotExist): | ||
| pass | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,10 +10,10 @@ | |
| ex. 'id' for user table would be 'user_id' | ||
| ''' | ||
|
|
||
| from sqlalchemy import Column, Integer, String, ForeignKey, Table, Index, ForeignKeyConstraint | ||
| from sqlalchemy import Column, Integer, String, ForeignKey, Table, Index, ForeignKeyConstraint, Boolean, UniqueConstraint | ||
| from sqlalchemy.ext.declarative import declarative_base | ||
| from sqlalchemy.orm import relationship, backref | ||
| from sqlalchemy.types import TIMESTAMP | ||
| from sqlalchemy.types import TIMESTAMP, Boolean | ||
| from django.db import models | ||
| from django.conf import settings | ||
| from sqlalchemy import create_engine | ||
|
|
@@ -310,6 +310,33 @@ class Edge(Base): | |
| ForeignKeyConstraint([user_id, graph_id, tail_node_id], [Node.user_id, Node.graph_id, Node.node_id], ondelete="CASCADE", onupdate="CASCADE"), {}) | ||
| #no relationship specified | ||
|
|
||
| class ShareGraphEvent(Base): | ||
| __tablename__ = 'share_graph_event' | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need some docs on the schema. What does the table store? Why do you need each attribute?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Class names should have camel case. |
||
| # unique id for each share graph event | ||
| id = Column(Integer, autoincrement=True, primary_key=True) | ||
| # id of the graph shared | ||
| graph_id = Column(String, nullable=False) | ||
| # id of the owner of the graph which is shared | ||
| owner_id = Column(String, nullable=False) | ||
| # id of the group the graph is shared in | ||
| group_id = Column(String, ForeignKey('group.group_id', ondelete="CASCADE", onupdate="CASCADE"), nullable=False) | ||
| # id of the member of the group. | ||
| # Hence there can be multiple share graph events if a owner shares a grap | ||
| # with a group. A share graph event will be created for all the memebers | ||
| # of the group except the owner of the graph (the one who shared it). | ||
| member_id = Column(String, ForeignKey('user.user_id', ondelete="CASCADE", onupdate="CASCADE"), nullable=False) | ||
| # timestamp at which the share graph event occured | ||
| share_time = Column(TIMESTAMP, nullable = False) | ||
| # Boolean value to track if notifications is read or not. | ||
| # if True then the notification is active, i.e not read | ||
| is_active = Column(Boolean, nullable=False) | ||
| # We use ForeignKeyConstraint for graph_id and owner_id | ||
| # because this is the only to define a composite foreign key | ||
| __table_args__ = ( | ||
| UniqueConstraint('graph_id', 'owner_id', 'group_id', 'member_id'), | ||
| ForeignKeyConstraint([graph_id, owner_id], [Graph.graph_id, Graph.user_id], ondelete="CASCADE", onupdate="CASCADE"), | ||
| ) | ||
|
|
||
| #Create indices | ||
| Index('graph_public_idx', Graph.public) | ||
| Index('graph_owner_idx', Graph.user_id) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| $(document).ready(function() { | ||
|
|
||
|
|
||
| /** | ||
| * Mark notification as read through the UI. | ||
| */ | ||
| $(".read_notification").click(function (e) { | ||
| var uid = $(this).val(); | ||
| var nid = $(this).attr('id'); | ||
|
|
||
| $.post('../../../read_notification/', { | ||
| 'uid': uid, | ||
| 'nid': nid | ||
| }, function (data) { | ||
| if (data.Error) { | ||
| return alert(data.Error); | ||
| } | ||
| window.location.reload(); | ||
| }); | ||
| }); | ||
|
|
||
| /** | ||
| * Mark all notifications as read in a group | ||
| */ | ||
| $(".read_all_notifications").click(function (e) { | ||
| var uid = $(this).val(); | ||
| var nid = $(this).attr('id'); | ||
|
|
||
| $.post('../../../read_all_notifications/', { | ||
| 'uid': uid, | ||
| 'nid': nid | ||
| }, function (data) { | ||
| if (data.Error) { | ||
| return alert(data.Error); | ||
| } | ||
| window.location.reload(); | ||
| }); | ||
| }); | ||
|
|
||
|
|
||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -109,7 +109,10 @@ <h4 class="modal-title" id="myModalLabel">Forgot Information</h4> | |
| {% endif %} | ||
| <li><a href="{% url 'features' %}">Features</a></li> | ||
| <li><a href="{% url 'help_tutorial' %}">Help</a></li> | ||
| <li><a href="{% url 'help_about' %}" style="text-align:center">About Us</a></li> | ||
| <li><a href="{% url 'help_about' %}" style="text-align:center">About Us</a></li> | ||
| {% if uid != None %} | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this check sufficient? Should we not check that the user is indeed logged in and is a "valid" user? I am asking without knowledge of how this check is performed in other parts of GraphSpace.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes this will be enough as
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| <li><a href="{% url 'notifications' %}">Notifications</a></li> | ||
| {% endif %} | ||
| </ul> | ||
|
|
||
| <!-- display login form if not logged in --> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| {% extends 'base.html' %} | ||
| {% block content %} | ||
| {% load staticfiles %} | ||
| <script type="text/javascript" src="{% static 'graphs/js/notifications.js' %}"></script> | ||
|
|
||
| {% if uid != None %} | ||
|
|
||
|
|
||
|
|
||
| <br> | ||
| <div class="row"><button style="position: relative; | ||
| right: 100px; float: right;" class="btn btn-default read_all_notifications" id="{{ group }}" value="{{ event.member_id }}">Mark all notifications as read</button></div> | ||
|
|
||
| <br> | ||
| <div class="row"> | ||
| <div class="col-sm-3"> | ||
| <div class="panel panel-primary"> | ||
| <!-- Default panel contents --> | ||
| <div class="panel-heading">Groups</div> | ||
| <div class="panel-body"> | ||
| <p>All the notifications of the Group you are a part of.</p> | ||
| </div> | ||
|
|
||
| <!-- Table --> | ||
| <table class="table"> | ||
| {% for group in groups_for_user %} | ||
| <tr> | ||
| <td>{{ group.groupId }}</td> | ||
| </tr> | ||
| {% endfor %} | ||
| </table> | ||
| </div> | ||
| </div> | ||
| <div class="col-sm-8"> | ||
|
|
||
|
|
||
| <!-- <div class="column"> --> | ||
| {% for group, events in notifications.items %} | ||
| {% if events != None %} | ||
| <div class="panel panel-primary"> | ||
| <div class="panel-heading"> | ||
| <h3 class="panel-title">{{ group }}</h3> | ||
| </div> | ||
| <table class="table"> | ||
| {% for event in events %} | ||
| {% if event.owner_id != uid %} | ||
| {% if event.is_active == 1 %} | ||
| <tr> | ||
| <td> <a href="{% url 'graphs' %}/{{ event.owner_id }}/{{ event.graph_id}}">{{ event.owner_id }} shared {{ event.graph_id }} with {{ event.group_id }}.</td> | ||
| <td><div class="test"><button class="btn btn-default read_notification" id="{{ event.id }}" value="{{ event.member_id }}">Mark as read</button></div></td> | ||
| </tr> | ||
| {% endif %} | ||
| {% endif %} | ||
| {% endfor %} | ||
|
|
||
| </table> | ||
| <div class="panel-footer"><div class="test"><button class="btn btn-default read_all_notifications" id="{{ group }}" value="{{ event.member_id }}">Mark all as read</button></div></div> | ||
| </div> | ||
| {% endif %} | ||
| {% endfor %} | ||
| </div> | ||
|
|
||
| </div> | ||
| <!-- </div> --> | ||
|
|
||
| {% endif %} | ||
| {% endblock %} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ | |
| from django.conf import settings | ||
| from django.core.mail import send_mail | ||
| from json_validator import validate_json, assign_edge_ids, convert_json, verify_json | ||
| from graphs.exception import EventNotFound | ||
|
|
||
| data_connection = db_init.db | ||
|
|
||
|
|
@@ -3648,7 +3649,11 @@ def share_graph_with_group(owner, graph, groupId, groupOwner): | |
| # If they're an owner or a group member, they can add graph to the group | ||
| if group_owner != None or group_member != None: | ||
| new_shared_graph = models.GroupToGraph(group_id = groupId, group_owner = groupOwner, user_id = owner, graph_id = graph, modified = graph_exists.modified) | ||
|
|
||
| members = get_group_members(groupOwner, groupId) | ||
| for member in members: | ||
| # TODO: use the current db session instead of creating a new db | ||
| # session | ||
| add_share_graph_event(graph, owner, groupId, member.user_id) | ||
| db_session.add(new_shared_graph) | ||
| db_session.commit() | ||
| else: | ||
|
|
@@ -4742,3 +4747,155 @@ def constructResponse(statusCode, message, error): | |
| response['Error'] = error | ||
|
|
||
| return response | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a comment to document the return value for each method. |
||
|
|
||
| def add_share_graph_event(graph_id, owner_id, group_id, member_id): | ||
| ''' | ||
| Add a new share graph event to the table. | ||
| After sharing the graph with a group this function will create | ||
| a share graph event for all the users in that group | ||
|
|
||
| @param graph_id: id of the graph shared | ||
| @param owner_id: owner of the graph which is shared | ||
| @param group_id: id of the grop | ||
| @param member_id: id of the member the graph is shared | ||
| ''' | ||
| # Create database connection | ||
| db_session = data_connection.new_session() | ||
|
|
||
| # Get the current time | ||
| cur_time = datetime.now() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we dont need this variable. you directly assign the value while creating the share event object. |
||
|
|
||
| new_event = models.ShareGraphEvent(graph_id=graph_id, owner_id=owner_id, group_id=group_id, member_id=member_id, share_time=cur_time, is_active=True) | ||
| db_session.add(new_event) | ||
| db_session.commit() | ||
| db_session.close() | ||
|
|
||
|
|
||
| def update_share_graph_event(event_id, active, member_id): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lets make this method more generic. Keep the signature as
And you can write the code as following: |
||
| ''' | ||
| Update the share graph event. Change its active state. | ||
| If active is True then the notification is not read/clicked. | ||
|
|
||
| @param event_id: id of the share graph event | ||
| @param active: Boolean value, update the state of event | ||
| @param member_id: id of the user, the logged in user. | ||
| ''' | ||
| db_session = data_connection.new_session() | ||
|
|
||
| try: | ||
| event = db_session.query(models.ShareGraphEvent).filter(models.ShareGraphEvent.id == event_id).filter(models.ShareGraphEvent.member_id == member_id).one() | ||
| event.is_active = active | ||
| db_session.commit() | ||
| db_session.close() | ||
| except NoResultFound: | ||
| db_session.close() | ||
| raise EventNotFound | ||
| # return 'Event not found' | ||
|
|
||
| # admin function | ||
| def delete_share_graph_event(event_id, member_id): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove the member id from this. event id is unique to a share graph event. |
||
| ''' | ||
| Delete the share graph event from the table for the member | ||
|
|
||
| @param event_id: id of the share graph event | ||
| @param member_id: id of the member | ||
|
|
||
| ''' | ||
| db_session = data_connection.new_session() | ||
| try: | ||
| event = db_session.query(models.ShareGraphEvent).filter(models.ShareGraphEvent.id == event_id).filter(models.ShareGraphEvent.member_id == member_id).one() | ||
| db_session.delete(event) | ||
| db_session.commit() | ||
| db_session.close() | ||
| except NoResultFound: | ||
| db_session.close() | ||
| raise EventNotFound | ||
| # return 'Event not found' | ||
|
|
||
|
|
||
| def get_share_graph_event_by_member_id(member_id): | ||
| ''' | ||
| Return a dictionary of share graph events for a user keyed by group id. | ||
| If all the share graph events for a particular group are inactive, i.e. read | ||
| then the group is keyed by None in the dictionary. | ||
|
|
||
| @param member_id: id of the user | ||
| ''' | ||
| # Create database connection | ||
| db_session = data_connection.new_session() | ||
|
|
||
| try: | ||
| events = db_session.query(models.ShareGraphEvent).filter(models.ShareGraphEvent.member_id == member_id).all() | ||
| db_session.close() | ||
| return events | ||
| except NoResultFound: | ||
| db_session.close() | ||
| # return None | ||
| raise EventNotFound | ||
|
|
||
|
|
||
| def get_share_graph_event_by_id(event_id, member_id): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again no need for member id. |
||
| ''' | ||
| Return share graph event notification | ||
|
|
||
| @param event_id: id of the event | ||
| @param member_id: id of the logged in user | ||
| ''' | ||
| db_session = data_connection.new_session() | ||
| try: | ||
| event = db_session.query(models.ShareGraphEvent).filter(models.ShareGraphEvent.id == event_id).filter(models.ShareGraphEvent.member_id == member_id).one() | ||
| db_session.close() | ||
| return event | ||
| except NoResultFound: | ||
|
||
| db_session.close() | ||
| raise EventNotFound | ||
| # return 'Event not found' | ||
|
|
||
|
|
||
| def get_all_share_graph_event(): | ||
| ''' | ||
| Return all the share graph events. | ||
| ''' | ||
| db_session = data_connection.new_session() | ||
| events = db_session.query(models.ShareGraphEvent).all() | ||
| db_session.close() | ||
| return events | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add another method called
This function should set all events as inactive. Return type should be None. Raise an error if query fails. Try to make it a single db query. You can use the update api in sqlalchemy. |
||
|
|
||
| def set_share_graph_events_inactive(event_ids, member_id): | ||
| ''' | ||
| Set all events in the list event_ids as inactive | ||
|
|
||
| @param events_id: list of event ids | ||
| @param member_id: id of the logged in user | ||
| ''' | ||
| db_session = data_connection.new_session() | ||
| try: | ||
| for event in events_id: | ||
| db_session.query(models.ShareGraphEvent).filter(models.ShareGraphEvent.member_id == member_id).filter(models.ShareGraphEvent.id == event_id).update({"is_active": 0}) | ||
| db_session.commit() | ||
| db_session.close() | ||
| except NoResultFound: | ||
| db_session.close() | ||
| raise EventNotFound | ||
|
|
||
| def set_share_graph_events_inactive_by_group(group_id, member_id): | ||
| ''' | ||
| Set all events in a group for a user as inactive | ||
|
|
||
| @param group_id: id of the group | ||
| @param member_id: id of the logged in user | ||
| ''' | ||
| db_session = data_connection.new_session() | ||
| try: | ||
|
|
||
| events = db_session.query(models.ShareGraphEvent).filter(models.ShareGraphEvent.member_id == member_id).filter(models.ShareGraphEvent.group_id == group_id).all() | ||
|
||
| for event in events: | ||
| event.is_active = 0 | ||
| db_session.commit() | ||
| db_session.close() | ||
| except NoResultFound: | ||
| db_session.close() | ||
| raise EventNotFound | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| from graphs.models import share_graph_event | ||
| from django.test import TestCase | ||
| from db import (get_all_share_graph_event, get_share_graph_event_by_id, | ||
| get_share_graph_event_by_member_id, delete_share_graph_event, | ||
| update_share_graph_event, add_share_graph_event) | ||
|
|
||
|
|
||
| class share_graph_event_test(TestCase): | ||
| def basic_test(self): | ||
| share_event = add_share_graph_event() | ||
| # add tests after adding logic | ||
|
|

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not to be used anymore.