Skip to content
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

Fork functionality #385

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from
20 changes: 20 additions & 0 deletions PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## Purpose
_Describe the problem or feature in addition to a link to the issues._

Example:
Fixes # .

## Approach
_How does this change address the problem?_

#### Open Questions and Pre-Merge TODOs
- [ ] Use github checklists. When solved, check the box and explain the answer.

## Learning
_Describe the research stage_

_Links to blog posts, patterns, libraries or addons used to solve this problem_

#### Blog Posts
- [How to Pull Request](https://github.com/flexyford/pull-request) Github Repo with Learning focused Pull Request Template.

10 changes: 9 additions & 1 deletion applications/graphs/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,10 +333,11 @@ def delete_graph_to_group(request, group_id, graph_id):


def search_graphs1(request, owner_email=None, names=None, nodes=None, edges=None, tags=None, member_email=None,
is_public=None, query=None, limit=20, offset=0, order='desc', sort='name'):
is_public=None, is_forked=None, query=None, limit=20, offset=0, order='desc', sort='name'):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make sure you are using pycharm editor and running auto formatting before committing the code.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking into this issue.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Followed PEP8 conventions for all python codes.

sort_attr = getattr(db.Graph, sort if sort is not None else 'name')
orber_by = getattr(db, order if order is not None else 'desc')(sort_attr)
is_public = int(is_public) if is_public is not None else None
is_forked = int(is_forked) if is_forked is not None else None

if member_email is not None:
member_user = users.controllers.get_user(request, member_email)
Expand All @@ -362,6 +363,7 @@ def search_graphs1(request, owner_email=None, names=None, nodes=None, edges=None
owner_email=owner_email,
graph_ids=graph_ids,
is_public=is_public,
is_forked=is_forked,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatting doesnt look right.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixing it now.

group_ids=group_ids,
names=names,
nodes=nodes,
Expand Down Expand Up @@ -586,3 +588,9 @@ def add_edge(request, name=None, head_node_id=None, tail_node_id=None, is_direct
def delete_edge_by_id(request, edge_id):
db.delete_edge(request.db_session, id=edge_id)
return

def add_graph_to_fork(request, forked_graph_id, parent_graph_id, owner_email):
if forked_graph_id is not None and parent_graph_id is not None:
db.add_fork(request.db_session, forked_graph_id, parent_graph_id, owner_email)
else:
raise Exception("Required Parameter is missing!")
13 changes: 12 additions & 1 deletion applications/graphs/dal.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ def get_graph_by_id(db_session, id):


@with_session
def find_graphs(db_session, owner_email=None, group_ids=None, graph_ids=None, is_public=None, names=None, nodes=None,
def find_graphs(db_session, owner_email=None, group_ids=None, graph_ids=None, is_public=None, is_forked=None, names=None, nodes=None,
edges=None,
tags=None, limit=None, offset=None, order_by=desc(Graph.updated_at)):
query = db_session.query(Graph)
Expand All @@ -151,6 +151,8 @@ def find_graphs(db_session, owner_email=None, group_ids=None, graph_ids=None, is
options_group.append(subqueryload('nodes'))
if edges is not None and len(edges) > 0:
options_group.append(subqueryload(Graph.edges))
if is_forked is not None:
options_group.append(joinedload(Graph.forked_graphs))
if group_ids is not None and len(group_ids) > 0:
options_group.append(joinedload('shared_with_groups'))
if len(options_group) > 0:
Expand All @@ -159,6 +161,9 @@ def find_graphs(db_session, owner_email=None, group_ids=None, graph_ids=None, is
if group_ids is not None:
query = query.filter(Graph.groups.any(Group.id.in_(group_ids)))

if is_forked is not None:
query = query.filter(Graph.forked_graphs.any())

edges = [] if edges is None else edges
nodes = [] if nodes is None else nodes
names = [] if names is None else names
Expand Down Expand Up @@ -458,3 +463,9 @@ def find_edges(db_session, is_directed=None, names=None, edges=None, graph_id=No
query = query.limit(limit).offset(offset)

return total, query.all()

@with_session
def add_fork(db_session, forked_graph_id, parent_graph_id, owner_email):
fork = GraphFork( graph_id=forked_graph_id, parent_graph_id=parent_graph_id, owner_email=owner_email)
db_session.add(fork)
return 1
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return fork object. Refer to other functions.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also add python docs. Refer to other functions.

20 changes: 20 additions & 0 deletions applications/graphs/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class Graph(IDMixin, TimeStampMixin, Base):
edges = relationship("Edge", back_populates="graph", cascade="all, delete-orphan")
nodes = relationship("Node", back_populates="graph", cascade="all, delete-orphan")

forked_graphs = relationship("GraphFork", foreign_keys="GraphFork.graph_id", back_populates="graph", cascade="all, delete-orphan")

groups = association_proxy('shared_with_groups', 'group')
tags = association_proxy('graph_tags', 'tag')

Expand Down Expand Up @@ -279,3 +281,21 @@ class GraphToTag(TimeStampMixin, Base):
def __table_args__(cls):
args = cls.constraints + cls.indices
return args


class GraphFork(IDMixin, TimeStampMixin, Base):
__tablename__ = 'graph_fork'
#name = Column(String, nullable=False)
owner_email = Column(String, ForeignKey('user.email', ondelete="CASCADE", onupdate="CASCADE"), nullable=False)
graph_id = Column(Integer, ForeignKey('graph.id', ondelete="CASCADE", onupdate="CASCADE"), nullable=False)
parent_graph_id = Column(Integer, ForeignKey('graph.id', ondelete="CASCADE", onupdate="CASCADE"), nullable=False)

graph = relationship("Graph", foreign_keys=[graph_id], back_populates="forked_graphs", uselist=False)

indices = (Index('graph2fork_idx_graph_id_parent_id', 'graph_id', 'parent_graph_id'),)
constraints = ()

@declared_attr
def __table_args__(cls):
args = cls.constraints + cls.indices
return args
4 changes: 4 additions & 0 deletions applications/graphs/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
# Graph Layouts
url(r'^ajax/graphs/(?P<graph_id>[^/]+)/layouts/$', views.graph_layouts_ajax_api, name='graph_layouts_ajax_api'),
url(r'^ajax/graphs/(?P<graph_id>[^/]+)/layouts/(?P<layout_id>[^/]+)$', views.graph_layouts_ajax_api, name='graph_layouts_ajax_api'),
# Graph Fork
url(r'^ajax/graphs/(?P<graph_id>[^/]+)/fork/$', views.graph_fork_ajax_api, name='graph_fork_ajax_api'),

# REST APIs Endpoints

Expand All @@ -45,5 +47,7 @@
# Graph Layouts
url(r'^api/v1/graphs/(?P<graph_id>[^/]+)/layouts/$', views.graph_layouts_rest_api, name='graph_layouts_rest_api'),
url(r'^api/v1/graphs/(?P<graph_id>[^/]+)/layouts/(?P<layout_id>[^/]+)$', views.graph_layouts_rest_api, name='graph_layouts_rest_api'),
# Graph Fork
url(r'^api/v1/graphs/(?P<graph_id>[^/]+)/fork/$', views.graph_fork_rest_api, name='graph_fork_rest_api'),

]
138 changes: 136 additions & 2 deletions applications/graphs/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,9 +212,10 @@ def graphs_advanced_search_ajax_api(request):
if user_role == authorization.UserRole.LOGGED_IN:
if queryparams.get('owner_email', None) is None \
and queryparams.get('member_email', None) is None \
and queryparams.get('is_public', None) != '1':
and queryparams.get('is_public', None) != '1' \
and queryparams.get('is_forked', None) != '1' :
raise BadRequest(request, error_code=ErrorCodes.Validation.IsPublicNotSet)
if queryparams.get('is_public', None) != '1':
if queryparams.get('is_public', None) != '1' and queryparams.get('is_forked', None) != '1':
if get_request_user(request) != queryparams.get('member_email', None) \
and get_request_user(request) != queryparams.get('owner_email', None):
raise BadRequest(request, error_code=ErrorCodes.Validation.NotAllowedGraphAccess,
Expand All @@ -225,6 +226,7 @@ def graphs_advanced_search_ajax_api(request):
member_email=queryparams.get('member_email', None),
names=list(filter(None, queryparams.getlist('names[]', []))),
is_public=queryparams.get('is_public', None),
is_forked=queryparams.get('is_forked', None),
nodes=list(filter(None, queryparams.getlist('nodes[]', []))),
edges=list(filter(None, queryparams.getlist('edges[]', []))),
tags=list(filter(None, queryparams.getlist('tags[]', []))),
Expand Down Expand Up @@ -1522,3 +1524,135 @@ def _delete_edge(request, graph_id, edge_id):
authorization.validate(request, permission='GRAPH_UPDATE', graph_id=graph_id)

graphs.delete_edge_by_id(request, edge_id)


'''
Graph Fork APIs
'''

@csrf_exempt
@is_authenticated()
def graph_fork_rest_api(request, graph_id=None):
"""
Handles any request sent to following urls:
/api/v1/graphs
/api/v1/graphs/<graph_id>

Parameters
----------
request - HTTP Request

Returns
-------
response : JSON Response

"""
return _fork_api(request, graph_id=graph_id)


def graph_fork_ajax_api(request, graph_id=None):
"""
Handles any request sent to following urls:
/ajax/graphs
/ajax/graphs/<graph_id>

Parameters
----------
request - HTTP Request

Returns
-------
response : JSON Response

"""
return _fork_api(request, graph_id=graph_id)

def _fork_api(request, graph_id=None):
"""
Handles any request sent to following urls:
/graphs/<graph_id>/fork

Parameters
----------
request - HTTP Request

Returns
-------
response : JSON Response

Raises
------
MethodNotAllowed: If a user tries to send requests other than GET or POST.
BadRequest: If HTTP_ACCEPT header is not set to application/json.

"""
if request.META.get('HTTP_ACCEPT', None) == 'application/json':
if request.method == "GET" and graph_id is None:
return HttpResponse(json.dumps(_get_graphs(request, query=request.GET)), content_type="application/json")
elif request.method == "GET" and graph_id is not None:
return HttpResponse(json.dumps(_get_graph(request, graph_id)), content_type="application/json",
status=200)
elif request.method == "POST" and graph_id is not None:
return HttpResponse(json.dumps(_add_fork(request, graph=json.loads(request.body))),
content_type="application/json", status=201)
return HttpResponse(json.dumps({
"message": "Successfully deleted graph with id=%s" % graph_id
}), content_type="application/json", status=200)
else:
raise MethodNotAllowed(request) # Handle other type of request methods like OPTIONS etc.
else:
raise BadRequest(request)

def _add_fork(request, graph={}):
"""
Graph Parameters
----------
name : string
Name of group. Required
owner_email : string
Email of the Owner of the graph. Required
tags: list of strings
List of tags to be attached with the graph. Optional


Parameters
----------
graph : dict
Dictionary containing the data of the graph being added.
request : object
HTTP POST Request.

Returns
-------
Confirmation Message

Raises
------
BadRequest - Cannot create graph for user other than the requesting user.

Notes
------

"""

# Validate add graph API request
user_role = authorization.user_role(request)
if user_role == authorization.UserRole.LOGGED_IN:
if get_request_user(request) != graph.get('owner_email', None):
raise BadRequest(request, error_code=ErrorCodes.Validation.CannotCreateGraphForOtherUser,
args=graph.get('owner_email', None))
elif user_role == authorization.UserRole.LOGGED_OFF and graph.get('owner_email', None) is not None:
raise BadRequest(request, error_code=ErrorCodes.Validation.CannotCreateGraphForOtherUser,
args=graph.get('owner_email', None))

new_graph = graphs.add_graph(request,
name=graph.get('name', None)+'_fork',
is_public=graph.get('is_public', None),
graph_json=graph.get('graph_json', None),
style_json=graph.get('style_json', None),
tags=graph.get('tags', None),
owner_email=graph.get('owner_email', None))
return utils.serializer(graphs.add_graph_to_fork(request,
forked_graph_id=new_graph.id,
parent_graph_id=graph.get('parent_id', None),
owner_email=graph.get('owner_email', None)))
24 changes: 24 additions & 0 deletions migration/versions/bdce7c016932_add_graph_fork_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""add_graph_fork_table

Revision ID: bdce7c016932
Revises: bb9a45e2ee5e
Create Date: 2018-05-19 16:15:09.911000

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'bdce7c016932'
down_revision = 'bb9a45e2ee5e'
branch_labels = None
depends_on = None


def upgrade():
pass
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add table.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixing it.



def downgrade():
op.drop_table('graph_fork')
Loading