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

110 changes: 110 additions & 0 deletions api_specifications/api.raml
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,32 @@ types:
graph_id: number
positions_json: string
style_json: string
Fork:
description: Fork Graph object
example: |
{
"graph_id": 21,
"parent_graph_id": 25
}
properties:
graph_id: number
parent_graph_id: number
ForkResponse:
description: Fork Graph Response object
example: |
{
"graph_id": 21,
"parent_graph_id": 25,
"updated_at": "2017-03-27T07:59:35.416245",
"created_at": "2017-03-27T07:59:35.416245",
"id": 1235
}
properties:
id: number
graph_id: number
parent_graph_id: number
created_at: string
updated_at: string
Member:
description: Member Response object
example: |
Expand Down Expand Up @@ -808,6 +834,90 @@ types:
body:
application/json:
type: Error
/fork:
description: APIs to access graph forks on GraphSpace.
displayName: Graph Forks
get:
description: List all Graph Forks matching query criteria, if provided; otherwise list all Graph Forks.
queryParameters:
parent_graph_id?:
description: Search for Graph Forks with given parent_graph_id.
type: number
responses:
200:
description: SUCCESS
body:
application/json:
type: object
properties:
total: number
layouts: ForkResponse[]
400:
description: BAD REQUEST
body:
application/json:
type: Error
403:
description: FORBIDDEN
body:
application/json:
type: Error
post:
description: Create a new Fork of a Graph.
body:
application/json:
type: Fork
responses:
201:
description: SUCCESS
body:
application/json:
type: ForkResponse
400:
description: BAD REQUEST
body:
application/json:
type: Error
403:
description: FORBIDDEN
body:
application/json:
type: Error
/{graph_id}:
description: APIs to access a specific Fork on GraphSpace.
displayName: Graph Fork
get:
description: Get a Fork by forked graph id
responses:
200:
description: SUCCESS
body:
application/json:
type: ForkResponse
403:
description: FORBIDDEN
body:
application/json:
type: Error
delete:
description: Delete a Fork by forked graph id
responses:
200:
body:
application/json:
type: object
properties:
message: string
example: |
{
"message": "Successfully deleted Fork with id=1068"
}
403:
description: FORBIDDEN
body:
application/json:
type: Error


/groups:
description: APIs to access groups on GraphSpace.
Expand Down
29 changes: 28 additions & 1 deletion applications/graphs/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,23 @@ def map_attributes(attributes):
def get_graph_by_id(request, graph_id):
return db.get_graph_by_id(request.db_session, graph_id)

def get_fork_by_id(request, graph_id):
return db.get_fork_by_id(request.db_session, graph_id)

def find_forks(request, parent_graph_id=None, forked_graph_id=None):
if parent_graph_id is None:
raise Exception("The Parent graph ID is required.")
return db.get_fork(request.db_session, parent_graph_id, forked_graph_id)

def get_forked_graph_data(request, forked_graph, graph_fork):
'''Set information of parent graph into Graph object. The modified graph object is send in the HTTP Response'''
if graph_fork:
parent_graph = get_graph_by_id(request, graph_fork.parent_graph_id)
forked_graph['parent_email'] = parent_graph.owner_email
forked_graph['parent_graph_name'] = parent_graph.name
forked_graph['is_fork'] = 1
forked_graph['parent_id'] = parent_graph.id
return forked_graph

def is_user_authorized_to_view_graph(request, username, graph_id):
is_authorized = False
Expand Down Expand Up @@ -333,10 +350,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 +380,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,
group_ids=group_ids,
names=names,
nodes=nodes,
Expand Down Expand Up @@ -586,3 +605,11 @@ 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:
new_fork = db.add_fork(request.db_session, forked_graph_id, parent_graph_id, owner_email)
else:
raise Exception("Required Parameter is missing!")
return new_fork
27 changes: 26 additions & 1 deletion applications/graphs/dal.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,11 @@ def delete_graph(db_session, id):
def get_graph_by_id(db_session, id):
return db_session.query(Graph).filter(Graph.id == id).one_or_none()

def get_fork_by_id(db_session, id):
return db_session.query(GraphFork).filter(GraphFork.graph_id == id).one_or_none()

@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 +153,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 +163,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 +465,21 @@ 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)
db_session.add(fork)
return fork


@with_session
def get_fork(db_session, parent_graph_id=None, forked_graph_id=None):
query = db_session.query(GraphFork)
if parent_graph_id is not None:
query = query.filter(GraphFork.parent_graph_id == parent_graph_id)
if forked_graph_id is not None:
query = query.filter(GraphFork.graph_id == forked_graph_id)
total = query.count()
return total, query.all()
28 changes: 28 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,29 @@ 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

def serialize(cls, **kwargs):
return {
'graph_id': cls.graph_id,
'parent_graph_id': cls.parent_graph_id,
'created_at': cls.created_at.isoformat(),
'updated_at': cls.updated_at.isoformat()
}
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'),

]
Loading