From e30eed4fbc053e4f79918c040db15c91282e9f9e Mon Sep 17 00:00:00 2001 From: Aditya Bharadwaj Date: Sat, 3 Mar 2018 20:36:51 -0500 Subject: [PATCH 01/11] Create PULL_REQUEST_TEMPLATE.md Adding a pull request template to standardize the description of the proposed changes from contributors. Project contributors will automatically see the template's contents in the pull request body. More details can be found [here](https://help.github.com/articles/creating-a-pull-request-template-for-your-repository/). --- PULL_REQUEST_TEMPLATE.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 PULL_REQUEST_TEMPLATE.md diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..9de17e17 --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -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. + From e895be9141c84fd281db1fa215f9cda4ff9e90d7 Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Sat, 19 May 2018 16:53:29 +0200 Subject: [PATCH 02/11] Initial Commit --- applications/graphs/controllers.py | 10 +- applications/graphs/dal.py | 13 +- applications/graphs/models.py | 20 +++ applications/graphs/urls.py | 4 + applications/graphs/views.py | 138 +++++++++++++++++- graphspace/settings/local.py | 6 +- .../bdce7c016932_add_graph_fork_table.py | 24 +++ static/js/graphs_page.js | 102 +++++++++++++ templates/graph/index.html | 29 +++- templates/graphs/forked_graphs_table.html | 35 +++++ templates/graphs/index.html | 9 ++ 11 files changed, 381 insertions(+), 9 deletions(-) create mode 100644 migration/versions/bdce7c016932_add_graph_fork_table.py create mode 100644 templates/graphs/forked_graphs_table.html diff --git a/applications/graphs/controllers.py b/applications/graphs/controllers.py index 7f012a05..be053bb8 100644 --- a/applications/graphs/controllers.py +++ b/applications/graphs/controllers.py @@ -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'): 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) @@ -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, group_ids=group_ids, names=names, nodes=nodes, @@ -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!") diff --git a/applications/graphs/dal.py b/applications/graphs/dal.py index 388b8141..0e004eea 100644 --- a/applications/graphs/dal.py +++ b/applications/graphs/dal.py @@ -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) @@ -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: @@ -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 @@ -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(name='-', graph_id=forked_graph_id, parent_graph_id=parent_graph_id, owner_email=owner_email) + db_session.add(fork) + return 1 diff --git a/applications/graphs/models.py b/applications/graphs/models.py index f0e3e554..d22f8726 100644 --- a/applications/graphs/models.py +++ b/applications/graphs/models.py @@ -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') @@ -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 \ No newline at end of file diff --git a/applications/graphs/urls.py b/applications/graphs/urls.py index 297f92ab..440bd0b2 100644 --- a/applications/graphs/urls.py +++ b/applications/graphs/urls.py @@ -27,6 +27,8 @@ # Graph Layouts url(r'^ajax/graphs/(?P[^/]+)/layouts/$', views.graph_layouts_ajax_api, name='graph_layouts_ajax_api'), url(r'^ajax/graphs/(?P[^/]+)/layouts/(?P[^/]+)$', views.graph_layouts_ajax_api, name='graph_layouts_ajax_api'), + # Graph Fork + url(r'^ajax/graphs/(?P[^/]+)/fork/$', views.graph_fork_ajax_api, name='graph_fork_ajax_api'), # REST APIs Endpoints @@ -45,5 +47,7 @@ # Graph Layouts url(r'^api/v1/graphs/(?P[^/]+)/layouts/$', views.graph_layouts_rest_api, name='graph_layouts_rest_api'), url(r'^api/v1/graphs/(?P[^/]+)/layouts/(?P[^/]+)$', views.graph_layouts_rest_api, name='graph_layouts_rest_api'), + # Graph Fork + url(r'^api/v1/graphs/(?P[^/]+)/fork/$', views.graph_fork_rest_api, name='graph_fork_rest_api'), ] diff --git a/applications/graphs/views.py b/applications/graphs/views.py index 772639c5..20076e96 100644 --- a/applications/graphs/views.py +++ b/applications/graphs/views.py @@ -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, @@ -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[]', []))), @@ -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/ + + 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/ + + 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//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))) \ No newline at end of file diff --git a/graphspace/settings/local.py b/graphspace/settings/local.py index 9b2fa36d..8fce1642 100644 --- a/graphspace/settings/local.py +++ b/graphspace/settings/local.py @@ -39,9 +39,9 @@ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': 'test_database', - 'USER': 'adb', - 'PASSWORD': '', + 'NAME': 'nrnb', + 'USER': 'postgres', + 'PASSWORD': '123', 'HOST': 'localhost', 'PORT': '5432' } diff --git a/migration/versions/bdce7c016932_add_graph_fork_table.py b/migration/versions/bdce7c016932_add_graph_fork_table.py new file mode 100644 index 00000000..57900f51 --- /dev/null +++ b/migration/versions/bdce7c016932_add_graph_fork_table.py @@ -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 + + +def downgrade(): + op.drop_table('graph_fork') diff --git a/static/js/graphs_page.js b/static/js/graphs_page.js index a7f4c57a..838848b5 100644 --- a/static/js/graphs_page.js +++ b/static/js/graphs_page.js @@ -39,6 +39,12 @@ var apis = { apis.jsonRequest('GET', apis.nodes.ENDPOINT({'graph_id': graph_id}), data, successCallback, errorCallback) }, }, + fork: { + ENDPOINT: _.template('/ajax/graphs/<%= graph_id %>/fork/'), + add: function(graph_id, data, successCallback, errorCallback) { + apis.jsonRequest('POST', apis.fork.ENDPOINT({'graph_id': graph_id}), data, successCallback, errorCallback) + } + }, edges: { ENDPOINT: _.template('/ajax/graphs/<%= graph_id %>/edges/'), get: function (graph_id, data, successCallback, errorCallback) { @@ -382,6 +388,71 @@ var graphsPage = { ); } }, + forkedGraphsTable: { + getForkedGraphs: function(params) { + /** + * This is the custom ajax request used to load graphs in forkedGraphsTable. + * + * params - query parameters for the ajax request. + * It contains parameters like limit, offset, search, sort, order. + */ + $('#ForkedGraphsTable').bootstrapTable('showLoading'); + $('#forkedGraphsTotal').html(''); + + query = params.data['query'] + delete params.data.query; + + params.data["is_forked"] = 1; + params.data["owner_email"] = $('#UserEmail').val(); + + apis.graphs.search(params.data, query, + successCallback = function (response) { + // This method is called when graphs are successfully fetched. + $('#ForkedGraphsTable').bootstrapTable('hideLoading'); + params.success(response); + $('#forkedGraphsTotal').text(response.total); + }, + errorCallback = function () { + // This method is called when error occurs while fetching graphs. + $('#ForkedGraphsTable').bootstrapTable('hideLoading'); + params.error('Error'); + } + ); + }, + }, + forkGraphs: { + forkGraph: function (params) { + /** + * This is the custom ajax request used to fork graphs. + * + * params - query parameters for the ajax request. + * It contains parameters like graph_id, graph_json, style_json and other few parameters. + */ + graph_meta_data = cytoscapeGraph.getNetworkAndViewJSON(graphPage.cyGraph); + graph_meta_data.data['parent_id'] = params.graph_id; + graph_meta_data.data['parent_email'] = params.owner_email; + + var graphData = { + 'name':data.graph_name, + 'is_public':0, + 'owner_email':data.uid, + 'graph_json':JSON.stringify(graph_meta_data, null, 4), + 'style_json':JSON.stringify(cytoscapeGraph.getStyleJSON(graphPage.cyGraph), null, 4) + } + + apis.fork.add(params.graph_id, graphData, + successCallback = function (response) { + alert('Graph Forked Successfully'); + $("#ForkGraph").addClass('disabled'); + }, + errorCallback = function (response) { + alert('Could not fork the Graph due to the following error : ' + data.result); + params.error('Error'); + } + ); + } + } + }; //var graph ends var uploadGraphPage = { @@ -441,6 +512,13 @@ var graphPage = { utils.initializeTabs(); + $("#ForkGraph").click(function (e) { + e.preventDefault(); + if (!$(this).hasClass('disabled')){ + graphPage.fork($(this).data()); // Passing data about parent graph to fork() as argument + } + }); + $('#saveOnExitLayoutBtn').click(function () { graphPage.cyGraph.contextMenus('get').destroy(); // Destroys the cytocscape context menu extension instance. @@ -516,6 +594,30 @@ var graphPage = { graphPage.defaultLayoutWidget.init(); }, + fork: function (data) { + //Read the meta_data object and add 'parent_id' & 'parent_email' to the data field. + graph_meta_data = cytoscapeGraph.getNetworkAndViewJSON(graphPage.cyGraph); + //graph_meta_data.data['parent_id'] = data.graph_id; + //graph_meta_data.data['parent_email'] = data.owner_email; + var graphData = { + 'name':data.graph_name, + 'is_public':0, + 'parent_id':data.graph_id, + 'owner_email':data.uid, + 'graph_json':graph_meta_data,//JSON.stringify(graph_meta_data, null, 4), + 'style_json':cytoscapeGraph.getStyleJSON(graphPage.cyGraph)//JSON.stringify(cytoscapeGraph.getStyleJSON(graphPage.cyGraph), null, 4) + } + apis.fork.add(data.graph_id, graphData, + successCallback = function (response) { + alert('Graph Forked Successfully'); + $("#ForkGraph").addClass('disabled'); + }, + errorCallback = function (response) { + alert('Could not fork the Graph due to the following error : ' + data.result); + params.error('Error'); + } + ); + }, export: function (format) { cytoscapeGraph.export(graphPage.cyGraph, format, $('#GraphName').val()); }, diff --git a/templates/graph/index.html b/templates/graph/index.html index 830d44f3..011fe040 100644 --- a/templates/graph/index.html +++ b/templates/graph/index.html @@ -144,7 +144,7 @@
-
+

{{ title }}

@@ -169,9 +169,34 @@

{% endfor %}

{% endif %} + {% if 'data' in graph.graph_json and 'parent_email' in graph.graph_json.data%} +

+ forked from {{ graph.graph_json.data.parent_email }}/ {{ graph.name }} +

+ {% endif %}
-
+ +
+ {% include 'graphs/forked_graphs_table.html' %} +
+
Upload Graph
From 276a22291edd76e5a1cb7c2180c954f7a0b63cf0 Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Sat, 19 May 2018 17:48:52 +0200 Subject: [PATCH 03/11] Initial Commit --- applications/graphs/dal.py | 2 +- graphspace/settings/local.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/applications/graphs/dal.py b/applications/graphs/dal.py index 0e004eea..ed1a38b1 100644 --- a/applications/graphs/dal.py +++ b/applications/graphs/dal.py @@ -466,6 +466,6 @@ def find_edges(db_session, is_directed=None, names=None, edges=None, graph_id=No @with_session def add_fork(db_session, forked_graph_id, parent_graph_id, owner_email): - fork = GraphFork(name='-', graph_id=forked_graph_id, parent_graph_id=parent_graph_id, owner_email=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 diff --git a/graphspace/settings/local.py b/graphspace/settings/local.py index 8fce1642..9b2fa36d 100644 --- a/graphspace/settings/local.py +++ b/graphspace/settings/local.py @@ -39,9 +39,9 @@ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': 'nrnb', - 'USER': 'postgres', - 'PASSWORD': '123', + 'NAME': 'test_database', + 'USER': 'adb', + 'PASSWORD': '', 'HOST': 'localhost', 'PORT': '5432' } From 4fd6b6683690590ba0b17a3aba0d754166da5b85 Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Sun, 27 May 2018 19:52:08 +0200 Subject: [PATCH 04/11] Added Notification and API Endpoints for Fork Functionality --- applications/graphs/controllers.py | 17 ++++ applications/graphs/dal.py | 12 +++ applications/graphs/models.py | 11 ++- applications/graphs/views.py | 113 +++++++++++++++++++++---- static/js/graphs_page.js | 64 ++++++-------- templates/graph/index.html | 21 ++--- templates/graphs/fork_graph_modal.html | 21 +++++ templates/graphs/index.html | 1 + 8 files changed, 193 insertions(+), 67 deletions(-) create mode 100644 templates/graphs/fork_graph_modal.html diff --git a/applications/graphs/controllers.py b/applications/graphs/controllers.py index be053bb8..0688d001 100644 --- a/applications/graphs/controllers.py +++ b/applications/graphs/controllers.py @@ -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 diff --git a/applications/graphs/dal.py b/applications/graphs/dal.py index ed1a38b1..4d5239fe 100644 --- a/applications/graphs/dal.py +++ b/applications/graphs/dal.py @@ -124,6 +124,8 @@ 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, is_forked=None, names=None, nodes=None, @@ -469,3 +471,13 @@ 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 + +@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() \ No newline at end of file diff --git a/applications/graphs/models.py b/applications/graphs/models.py index d22f8726..8ac16fff 100644 --- a/applications/graphs/models.py +++ b/applications/graphs/models.py @@ -298,4 +298,13 @@ class GraphFork(IDMixin, TimeStampMixin, Base): @declared_attr def __table_args__(cls): args = cls.constraints + cls.indices - return args \ No newline at end of file + return args + + def serialize(cls, **kwargs): + return { + 'graph_id': cls.graph_id, + 'parent_graph_id': cls.parent_graph_id, + 'owner_email': cls.owner_email, + 'created_at': cls.created_at.isoformat(), + 'updated_at': cls.updated_at.isoformat() + } \ No newline at end of file diff --git a/applications/graphs/views.py b/applications/graphs/views.py index 20076e96..b68b7619 100644 --- a/applications/graphs/views.py +++ b/applications/graphs/views.py @@ -5,7 +5,7 @@ import graphspace.authorization as authorization import graphspace.utils as utils from django.conf import settings -from django.http import HttpResponse, QueryDict +from django.http import HttpResponse, QueryDict, HttpResponseForbidden from django.shortcuts import render, redirect from django.template import RequestContext from django.views.decorators.csrf import csrf_exempt @@ -403,8 +403,11 @@ def _get_graph(request, graph_id): """ authorization.validate(request, permission='GRAPH_READ', graph_id=graph_id) - - return utils.serializer(graphs.get_graph_by_id(request, graph_id)) + graph_by_id = utils.serializer(graphs.get_graph_by_id(request, graph_id)) + graph_fork = graphs.get_fork_by_id(request, graph_id) + if graph_fork: + graph_by_id = graphs.get_forked_graph_data(request, graph_by_id, graph_fork) + return graph_by_id def _add_graph(request, graph={}): @@ -1587,10 +1590,10 @@ def _fork_api(request, graph_id=None): """ 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") + if request.method == "GET" and graph_id is not None: + return HttpResponse(json.dumps(_get_forks(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", + return HttpResponse(json.dumps(_get_fork(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))), @@ -1644,15 +1647,95 @@ def _add_fork(request, graph={}): 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)) + try: + new_graph = graphs.add_graph(request, + name=graph.get('name', None), + 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)) + except Exception as error: + return error 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))) \ No newline at end of file + owner_email=graph.get('owner_email', None))) + +def _get_forks(request, query=dict()): + """ + Query Parameters + ---------- + parent_graph_id : integer + ID of the Parent graph from which Graph was forked. + + Parameters + ---------- + query : dict + Dictionary of query parameters. + request : object + HTTP GET Request. + parent_graph_id : Integer + ID of the Parent graph from which Graph was forked. + + Returns + ------- + total : integer + Number of groups matching the request. + forks : List of Forked Graphs. + List of Forked Graphs for the given parent graph ID. + + Raises + ------ + + BadRequest - `User is not authorized to access private graphs created by given owner. This means either the graph belongs to a different owner + or graph is not shared with the user. + + Notes + ------ + """ + + querydict = QueryDict('', mutable=True) + querydict.update(query) + query = querydict + + # Validate search graphs API request + user_role = authorization.user_role(request) + if user_role == authorization.UserRole.LOGGED_IN: + if query.get('parent_graph_id', None) is None: + raise BadRequest(request, error_code=ErrorCodes.Validation.GraphIDMissing) + + total, forks_list = graphs.find_forks(request, + parent_graph_id=query.get('parent_graph_id', None), + forked_graph_id=query.get('forked_graph_id', None) + ) + + return { + 'total': total, + 'graphs': [utils.serializer(fork, summary=True) for fork in forks_list] + } + +def _get_fork(request, graph_id): + """ + + Parameters + ---------- + request : object + HTTP GET Request. + graph_id : string + Unique ID of the graph. + + Returns + ------- + graph: object + + Raises + ------ + + Notes + ------ + + """ + authorization.validate(request, permission='GRAPH_READ', graph_id=graph_id) + + return utils.serializer(graphs.get_fork_by_id(request, graph_id)) \ No newline at end of file diff --git a/static/js/graphs_page.js b/static/js/graphs_page.js index 838848b5..91e90622 100644 --- a/static/js/graphs_page.js +++ b/static/js/graphs_page.js @@ -43,6 +43,12 @@ var apis = { ENDPOINT: _.template('/ajax/graphs/<%= graph_id %>/fork/'), add: function(graph_id, data, successCallback, errorCallback) { apis.jsonRequest('POST', apis.fork.ENDPOINT({'graph_id': graph_id}), data, successCallback, errorCallback) + }, + getByID: function (graph_id, successCallback, errorCallback) { + apis.jsonRequest('GET', apis.fork.ENDPOINT({'graph_id': graph_id}), undefined, successCallback, errorCallback) + }, + get: function (graph_id, data, successCallback, errorCallback) { + apis.jsonRequest('GET', apis.fork.ENDPOINT({'graph_id': graph_id}), data, successCallback, errorCallback) } }, edges: { @@ -420,38 +426,7 @@ var graphsPage = { ); }, }, - forkGraphs: { - forkGraph: function (params) { - /** - * This is the custom ajax request used to fork graphs. - * - * params - query parameters for the ajax request. - * It contains parameters like graph_id, graph_json, style_json and other few parameters. - */ - graph_meta_data = cytoscapeGraph.getNetworkAndViewJSON(graphPage.cyGraph); - graph_meta_data.data['parent_id'] = params.graph_id; - graph_meta_data.data['parent_email'] = params.owner_email; - var graphData = { - 'name':data.graph_name, - 'is_public':0, - 'owner_email':data.uid, - 'graph_json':JSON.stringify(graph_meta_data, null, 4), - 'style_json':JSON.stringify(cytoscapeGraph.getStyleJSON(graphPage.cyGraph), null, 4) - } - - apis.fork.add(params.graph_id, graphData, - successCallback = function (response) { - alert('Graph Forked Successfully'); - $("#ForkGraph").addClass('disabled'); - }, - errorCallback = function (response) { - alert('Could not fork the Graph due to the following error : ' + data.result); - params.error('Error'); - } - ); - } - } }; //var graph ends @@ -515,7 +490,7 @@ var graphPage = { $("#ForkGraph").click(function (e) { e.preventDefault(); if (!$(this).hasClass('disabled')){ - graphPage.fork($(this).data()); // Passing data about parent graph to fork() as argument + $('#forkGraphModal').data('data', $(this).data()).modal('show');//graphPage.fork($(this).data()); // Passing data about parent graph to fork() as argument } }); @@ -560,7 +535,7 @@ var graphPage = { $('#ConfirmRemoveLayoutBtn').click(graphPage.layoutsTable.onConfirmRemoveGraph); $('#ConfirmUpdateLayoutBtn').click(graphPage.layoutsTable.onConfirmUpdateGraph); - + $('#ConfirmForkGraphBtn').click(graphPage.fork); this.filterNodesEdges.init(); this.colaLayoutWidget.init(); @@ -594,11 +569,14 @@ var graphPage = { graphPage.defaultLayoutWidget.init(); }, - fork: function (data) { + fork: function () { //Read the meta_data object and add 'parent_id' & 'parent_email' to the data field. + data = $('#forkGraphModal').data('data') + graph_name = $('#forkGraphName').val() + if (graph_name!=null) data.graph_name = graph_name; + $('#forkGraphModal').modal('hide'); + $.notify({message: 'Request to fork the graph has been submitted'}, {type: 'info'}); graph_meta_data = cytoscapeGraph.getNetworkAndViewJSON(graphPage.cyGraph); - //graph_meta_data.data['parent_id'] = data.graph_id; - //graph_meta_data.data['parent_email'] = data.owner_email; var graphData = { 'name':data.graph_name, 'is_public':0, @@ -607,14 +585,22 @@ var graphPage = { 'graph_json':graph_meta_data,//JSON.stringify(graph_meta_data, null, 4), 'style_json':cytoscapeGraph.getStyleJSON(graphPage.cyGraph)//JSON.stringify(cytoscapeGraph.getStyleJSON(graphPage.cyGraph), null, 4) } + apis.fork.get(data.graph_id,{'parent_graph_id':data.graph_id}, + successCallback = function (response) { + $.notify({message: 'The Graph has been forked successfully'}, {type: 'success'}); + $("#ForkGraph").addClass('disabled'); + }, + errorCallback = function (response) { + $.notify({message: 'Could not fork the Graph due to the following error : ' + response.responseJSON.error_message.split("'")[1] + ''}, {type: 'danger'}); + } + ); apis.fork.add(data.graph_id, graphData, successCallback = function (response) { - alert('Graph Forked Successfully'); + $.notify({message: 'The Graph has been forked successfully'}, {type: 'success'}); $("#ForkGraph").addClass('disabled'); }, errorCallback = function (response) { - alert('Could not fork the Graph due to the following error : ' + data.result); - params.error('Error'); + $.notify({message: 'Could not fork the Graph due to the following error : ' + response.responseJSON.error_message.split("'")[1] + ''}, {type: 'danger'}); } ); }, diff --git a/templates/graph/index.html b/templates/graph/index.html index 011fe040..778206e5 100644 --- a/templates/graph/index.html +++ b/templates/graph/index.html @@ -139,7 +139,7 @@
- + {% include 'graphs/fork_graph_modal.html' %}
@@ -175,26 +175,23 @@

href="{% url 'graphs' %}{{ graph.graph_json.data.parent_id }} "> {{ graph.name }}

{% endif %} + {% if graph.is_fork %} +

+ Forked from {{ graph.parent_email }}/ {{ graph.parent_graph_name }} +

+ {% endif %}
- {% if isForked %} -
- - -
- - {% elif uid != graph.owner_email %} + {% if uid != graph.owner_email %}
- + 00
{% endif %} {% if uid and uid == graph.owner_email %} diff --git a/templates/graphs/fork_graph_modal.html b/templates/graphs/fork_graph_modal.html new file mode 100644 index 00000000..575d7146 --- /dev/null +++ b/templates/graphs/fork_graph_modal.html @@ -0,0 +1,21 @@ + \ No newline at end of file diff --git a/templates/graphs/index.html b/templates/graphs/index.html index b4c2ed98..c1c651e3 100644 --- a/templates/graphs/index.html +++ b/templates/graphs/index.html @@ -86,6 +86,7 @@ {% include 'graphs/delete_graph_modal.html' %} +
From 7397b65ea72c32b70a61a24f82ce409e29facada Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Thu, 31 May 2018 13:56:13 +0200 Subject: [PATCH 05/11] Fix Index Table and other minor changes --- applications/graphs/dal.py | 2 +- applications/graphs/models.py | 3 +-- static/js/graphs_page.js | 12 ++---------- templates/graphs/index.html | 3 ++- 4 files changed, 6 insertions(+), 14 deletions(-) diff --git a/applications/graphs/dal.py b/applications/graphs/dal.py index 4d5239fe..de8f795f 100644 --- a/applications/graphs/dal.py +++ b/applications/graphs/dal.py @@ -468,7 +468,7 @@ def find_edges(db_session, is_directed=None, names=None, edges=None, graph_id=No @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) + fork = GraphFork( graph_id=forked_graph_id, parent_graph_id=parent_graph_id) db_session.add(fork) return 1 diff --git a/applications/graphs/models.py b/applications/graphs/models.py index 8ac16fff..702684b9 100644 --- a/applications/graphs/models.py +++ b/applications/graphs/models.py @@ -286,7 +286,7 @@ def __table_args__(cls): 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) + #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) @@ -304,7 +304,6 @@ def serialize(cls, **kwargs): return { 'graph_id': cls.graph_id, 'parent_graph_id': cls.parent_graph_id, - 'owner_email': cls.owner_email, 'created_at': cls.created_at.isoformat(), 'updated_at': cls.updated_at.isoformat() } \ No newline at end of file diff --git a/static/js/graphs_page.js b/static/js/graphs_page.js index 91e90622..d4e58272 100644 --- a/static/js/graphs_page.js +++ b/static/js/graphs_page.js @@ -573,7 +573,7 @@ var graphPage = { //Read the meta_data object and add 'parent_id' & 'parent_email' to the data field. data = $('#forkGraphModal').data('data') graph_name = $('#forkGraphName').val() - if (graph_name!=null) data.graph_name = graph_name; + if (graph_name!="") data.graph_name = graph_name; $('#forkGraphModal').modal('hide'); $.notify({message: 'Request to fork the graph has been submitted'}, {type: 'info'}); graph_meta_data = cytoscapeGraph.getNetworkAndViewJSON(graphPage.cyGraph); @@ -585,15 +585,7 @@ var graphPage = { 'graph_json':graph_meta_data,//JSON.stringify(graph_meta_data, null, 4), 'style_json':cytoscapeGraph.getStyleJSON(graphPage.cyGraph)//JSON.stringify(cytoscapeGraph.getStyleJSON(graphPage.cyGraph), null, 4) } - apis.fork.get(data.graph_id,{'parent_graph_id':data.graph_id}, - successCallback = function (response) { - $.notify({message: 'The Graph has been forked successfully'}, {type: 'success'}); - $("#ForkGraph").addClass('disabled'); - }, - errorCallback = function (response) { - $.notify({message: 'Could not fork the Graph due to the following error : ' + response.responseJSON.error_message.split("'")[1] + ''}, {type: 'danger'}); - } - ); + apis.fork.add(data.graph_id, graphData, successCallback = function (response) { $.notify({message: 'The Graph has been forked successfully'}, {type: 'success'}); diff --git a/templates/graphs/index.html b/templates/graphs/index.html index c1c651e3..244743d8 100644 --- a/templates/graphs/index.html +++ b/templates/graphs/index.html @@ -22,11 +22,12 @@ Public Graphs {{ public_graphs.total }} - + {% if uid %}
  • Forked Graphs {{ forked_graphs.total }}
  • + {% endif %}
  • Upload New Graph From 375a2c5653fb780239a72bf3f12e5983ccb031fa Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Mon, 11 Jun 2018 18:14:53 +0200 Subject: [PATCH 06/11] Added API Specifications for Graph Fork --- api_specifications/api.raml | 110 ++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/api_specifications/api.raml b/api_specifications/api.raml index 1ca96ef4..512b840c 100644 --- a/api_specifications/api.raml +++ b/api_specifications/api.raml @@ -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: | @@ -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. From 520fbafe7d12621060d380b2d22c80cd557f828d Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Mon, 11 Jun 2018 23:02:27 +0200 Subject: [PATCH 07/11] Updated migration script for graph_fork table --- .../bdce7c016932_add_graph_fork_table.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/migration/versions/bdce7c016932_add_graph_fork_table.py b/migration/versions/bdce7c016932_add_graph_fork_table.py index 57900f51..9d249146 100644 --- a/migration/versions/bdce7c016932_add_graph_fork_table.py +++ b/migration/versions/bdce7c016932_add_graph_fork_table.py @@ -8,7 +8,6 @@ from alembic import op import sqlalchemy as sa - # revision identifiers, used by Alembic. revision = 'bdce7c016932' down_revision = 'bb9a45e2ee5e' @@ -17,8 +16,19 @@ def upgrade(): - pass + op.create_table( + 'graph_fork', + sa.Column('id', sa.Integer, primary_key=True), + sa.Column('graph_id', sa.Integer, nullable=False), + sa.Column('parent_graph_id', sa.Integer, nullable=False), + ) + op.add_column('graph_fork', sa.Column('created_at', sa.TIMESTAMP, server_default=sa.func.current_timestamp())) + op.add_column('graph_fork', sa.Column('updated_at', sa.TIMESTAMP, server_default=sa.func.current_timestamp())) + # Create New Index + op.create_index('graph2fork_idx_graph_id_parent_id', 'graph_fork', ['graph_id', 'parent_graph_id'], unique=True) + # Add new foreign key reference + op.execute('ALTER TABLE graph_fork ADD CONSTRAINT fork_graph_id_fkey FOREIGN KEY (graph_id) REFERENCES "graph" (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE;') def downgrade(): - op.drop_table('graph_fork') + op.drop_table('graph_fork') From e97671f01ac2881593a7b70c90068710c4ad40a7 Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Mon, 11 Jun 2018 23:12:12 +0200 Subject: [PATCH 08/11] Reformated indentation of controllers --- applications/graphs/controllers.py | 130 +++++++++++++++-------------- 1 file changed, 69 insertions(+), 61 deletions(-) diff --git a/applications/graphs/controllers.py b/applications/graphs/controllers.py index 0688d001..ec3820e2 100644 --- a/applications/graphs/controllers.py +++ b/applications/graphs/controllers.py @@ -17,10 +17,10 @@ from graphspace.data_type import DataType AUTOMATIC_LAYOUT_ALGORITHMS = ['default_breadthfirst', 'default_concentric', 'default_circle', 'default_cose', - 'default_grid'] + 'default_grid'] -def map_attributes(attributes): +def map_attributes(attributes): mapped_attributes = {} if attributes and isinstance(attributes, dict) and DataType.forValue(attributes) == DataType.DICT: for key, value in attributes.items(): @@ -36,17 +36,21 @@ def map_attributes(attributes): return mapped_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: @@ -57,6 +61,7 @@ def get_forked_graph_data(request, forked_graph, graph_fork): forked_graph['parent_id'] = parent_graph.id return forked_graph + def is_user_authorized_to_view_graph(request, username, graph_id): is_authorized = False @@ -189,7 +194,7 @@ def uploadJSONFile(request, username, graphJSON, title): @atomic_transaction def add_graph(request, name=None, tags=None, is_public=None, graph_json=None, style_json=None, owner_email=None, - default_layout_id=None): + default_layout_id=None): # If graph already exists for user, alert them if db.get_graph(request.db_session, owner_email, name) is not None: raise Exception('Graph ' + name + ' already exists for ' + owner_email + '!') @@ -208,8 +213,8 @@ def add_graph(request, name=None, tags=None, is_public=None, graph_json=None, st # Construct new graph to add to database new_graph = db.add_graph(request.db_session, name=name, owner_email=owner_email, - graph_json=json.dumps(G.get_graph_json()), style_json=json.dumps(G.get_style_json()), - is_public=is_public, default_layout_id=default_layout_id) + graph_json=json.dumps(G.get_graph_json()), style_json=json.dumps(G.get_style_json()), + is_public=is_public, default_layout_id=default_layout_id) # Add graph tags for tag in G.get_tags(): add_graph_tag(request, new_graph.id, tag) @@ -218,14 +223,15 @@ def add_graph(request, name=None, tags=None, is_public=None, graph_json=None, st # Add graph edges edge_name_to_id_map = add_graph_edges(request, new_graph.id, G.edges(data=True), node_name_to_id_map) - settings.ELASTIC_CLIENT.index(index="graphs", doc_type='json', id=new_graph.id, body=map_attributes(json.loads(new_graph.graph_json)), refresh=True) + settings.ELASTIC_CLIENT.index(index="graphs", doc_type='json', id=new_graph.id, + body=map_attributes(json.loads(new_graph.graph_json)), refresh=True) return new_graph @atomic_transaction def update_graph(request, graph_id, name=None, is_public=None, graph_json=None, style_json=None, owner_email=None, - default_layout_id=None): + default_layout_id=None): graph = {} if name is not None: graph['name'] = name @@ -254,7 +260,8 @@ def update_graph(request, graph_id, name=None, is_public=None, graph_json=None, graph['graph_json'] = json.dumps(G.get_graph_json()) - settings.ELASTIC_CLIENT.index(index="graphs", doc_type='json', id=graph_id, body=map_attributes(G.get_graph_json()), refresh=True) + settings.ELASTIC_CLIENT.index(index="graphs", doc_type='json', id=graph_id, + body=map_attributes(G.get_graph_json()), refresh=True) return db.update_graph(request.db_session, id=graph_id, updated_graph=graph) @@ -277,8 +284,8 @@ def add_graph_edges(request, graph_id, edges, node_name_to_id_map): # To make sure int and floats are also accepted as source and target nodes of an edge new_edge = db.add_edge(request.db_session, graph_id=graph_id, head_node_id=str(node_name_to_id_map[edge[1]]), - tail_node_id=str(node_name_to_id_map[edge[0]]), name=str(edge[2]['name']), - is_directed=is_directed) + tail_node_id=str(node_name_to_id_map[edge[0]]), name=str(edge[2]['name']), + is_directed=is_directed) edge_name_to_id_map[(edge[0], edge[1])] = new_edge.id return edge_name_to_id_map @@ -326,11 +333,11 @@ def _convert_order_query_term_to_database_order_object(order_query): def search_graphs_by_group_ids(request, group_ids=None, owner_email=None, names=None, nodes=None, edges=None, tags=None, - limit=None, offset=None): + limit=None, offset=None): if group_ids is None: raise Exception("Atleast one group id is required.") return db.find_graphs(request.db_session, group_ids=group_ids, owner_email=owner_email, names=names, nodes=nodes, - edges=edges, tags=tags, limit=limit, offset=offset) + edges=edges, tags=tags, limit=limit, offset=offset) def add_graph_to_group(request, group_id, graph_id): @@ -350,7 +357,7 @@ 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, is_forked=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'): 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 @@ -377,24 +384,24 @@ def search_graphs1(request, owner_email=None, names=None, nodes=None, edges=None graph_ids = None total, graphs_list = db.find_graphs(request.db_session, - owner_email=owner_email, - graph_ids=graph_ids, - is_public=is_public, + owner_email=owner_email, + graph_ids=graph_ids, + is_public=is_public, is_forked=is_forked, - group_ids=group_ids, - names=names, - nodes=nodes, - edges=edges, - tags=tags, - limit=limit, - offset=offset, - order_by=orber_by) + group_ids=group_ids, + names=names, + nodes=nodes, + edges=edges, + tags=tags, + limit=limit, + offset=offset, + order_by=orber_by) return total, graphs_list def search_graphs(request, owner_email=None, member_email=None, names=None, is_public=None, nodes=None, edges=None, - tags=None, limit=20, offset=0, order='desc', sort='name'): + tags=None, limit=20, offset=0, order='desc', sort='name'): 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 @@ -433,22 +440,22 @@ def search_graphs(request, owner_email=None, member_email=None, names=None, is_p # graph_ids=None total, graphs_list = db.find_graphs(request.db_session, - owner_email=owner_email, - names=names, - is_public=is_public, - group_ids=group_ids, - nodes=nodes, - edges=edges, - tags=tags, - limit=limit, - offset=offset, - order_by=orber_by) + owner_email=owner_email, + names=names, + is_public=is_public, + group_ids=group_ids, + nodes=nodes, + edges=edges, + tags=tags, + limit=limit, + offset=offset, + order_by=orber_by) return total, graphs_list def search_layouts(request, owner_email=None, is_shared=None, name=None, graph_id=None, limit=20, offset=0, - order='desc', sort='name'): + order='desc', sort='name'): if sort == 'name': sort_attr = db.Layout.name elif sort == 'update_at': @@ -464,13 +471,13 @@ def search_layouts(request, owner_email=None, is_shared=None, name=None, graph_i orber_by = db.asc(sort_attr) total, layouts = db.find_layouts(request.db_session, - owner_email=owner_email, - is_shared=is_shared, - name=name, - graph_id=graph_id, - limit=limit, - offset=offset, - order_by=orber_by) + owner_email=owner_email, + is_shared=is_shared, + name=name, + graph_id=graph_id, + limit=limit, + offset=offset, + order_by=orber_by) return total, layouts @@ -480,18 +487,18 @@ def get_layout_by_id(request, layout_id): def add_layout(request, owner_email=None, name=None, graph_id=None, is_shared=None, style_json=None, - positions_json=None): + positions_json=None): if name is None or owner_email is None or graph_id is None: raise Exception("Required Parameter is missing!") try: return db.add_layout(request.db_session, owner_email=owner_email, name=name, graph_id=graph_id, - is_shared=is_shared, style_json=dumps(style_json), positions_json=dumps(positions_json)) + is_shared=is_shared, style_json=dumps(style_json), positions_json=dumps(positions_json)) except IntegrityError as e: raise BadRequest(request, error_code=ErrorCodes.Validation.LayoutNameAlreadyExists, args=name) def update_layout(request, layout_id, owner_email=None, name=None, graph_id=None, is_shared=None, style_json=None, - positions_json=None): + positions_json=None): if layout_id is None: raise Exception("Required Parameter is missing!") @@ -535,12 +542,12 @@ def search_nodes(request, graph_id=None, names=None, labels=None, limit=20, offs ## TODO: create a util function to relpace the code parse sort and order parameters. This code is repeated again and again. total, nodes = db.find_nodes(request.db_session, - names=names, - labels=labels, - graph_id=graph_id, - limit=limit, - offset=offset, - order_by=orber_by) + names=names, + labels=labels, + graph_id=graph_id, + limit=limit, + offset=offset, + order_by=orber_by) return total, nodes @@ -561,7 +568,7 @@ def delete_node_by_id(request, node_id): def search_edges(request, is_directed=None, names=None, edges=None, graph_id=None, limit=20, offset=0, order='desc', - sort='name'): + sort='name'): if sort == 'name': sort_attr = db.Edge.name elif sort == 'update_at': @@ -580,13 +587,13 @@ def search_edges(request, is_directed=None, names=None, edges=None, graph_id=Non ## TODO: create a util function to relpace the code parse sort and order parameters. This code is repeated again and again. total, edges = db.find_edges(request.db_session, - names=names, - edges=edges, - is_directed=is_directed, - graph_id=graph_id, - limit=limit, - offset=offset, - order_by=orber_by) + names=names, + edges=edges, + is_directed=is_directed, + graph_id=graph_id, + limit=limit, + offset=offset, + order_by=orber_by) return total, edges @@ -599,13 +606,14 @@ def add_edge(request, name=None, head_node_id=None, tail_node_id=None, is_direct if name is None or graph_id is None or head_node_id is None or tail_node_id is None: raise Exception("Required Parameter is missing!") return db.add_edge(request.db_session, name=name, head_node_id=head_node_id, tail_node_id=tail_node_id, - is_directed=is_directed, graph_id=graph_id) + is_directed=is_directed, graph_id=graph_id) 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) From 8ad78e55c028a6d6c54eae1974c04c71d6e3edb7 Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Mon, 11 Jun 2018 23:17:37 +0200 Subject: [PATCH 09/11] Remove notification request_submitted for fork --- static/js/graphs_page.js | 2 +- templates/graph/index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/static/js/graphs_page.js b/static/js/graphs_page.js index d4e58272..c2d52836 100644 --- a/static/js/graphs_page.js +++ b/static/js/graphs_page.js @@ -575,7 +575,7 @@ var graphPage = { graph_name = $('#forkGraphName').val() if (graph_name!="") data.graph_name = graph_name; $('#forkGraphModal').modal('hide'); - $.notify({message: 'Request to fork the graph has been submitted'}, {type: 'info'}); + //$.notify({message: 'Request to fork the graph has been submitted'}, {type: 'info'}); graph_meta_data = cytoscapeGraph.getNetworkAndViewJSON(graphPage.cyGraph); var graphData = { 'name':data.graph_name, diff --git a/templates/graph/index.html b/templates/graph/index.html index 778206e5..e0dfe11a 100644 --- a/templates/graph/index.html +++ b/templates/graph/index.html @@ -191,7 +191,7 @@

    data-graph_id={{ graph.id }} data-owner_email={{ graph.owner_email }}> Fork - 00 + 0 {% endif %} {% if uid and uid == graph.owner_email %} From 29de21cc013736b588eba9f661eb345cb9bdef92 Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Mon, 11 Jun 2018 23:37:04 +0200 Subject: [PATCH 10/11] Return Graph Fork Response object instead of 1 --- applications/graphs/controllers.py | 131 ++++++++++++++--------------- applications/graphs/dal.py | 2 +- 2 files changed, 63 insertions(+), 70 deletions(-) diff --git a/applications/graphs/controllers.py b/applications/graphs/controllers.py index ec3820e2..3671c680 100644 --- a/applications/graphs/controllers.py +++ b/applications/graphs/controllers.py @@ -17,10 +17,10 @@ from graphspace.data_type import DataType AUTOMATIC_LAYOUT_ALGORITHMS = ['default_breadthfirst', 'default_concentric', 'default_circle', 'default_cose', - 'default_grid'] - + 'default_grid'] def map_attributes(attributes): + mapped_attributes = {} if attributes and isinstance(attributes, dict) and DataType.forValue(attributes) == DataType.DICT: for key, value in attributes.items(): @@ -36,21 +36,17 @@ def map_attributes(attributes): return mapped_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: @@ -61,7 +57,6 @@ def get_forked_graph_data(request, forked_graph, graph_fork): forked_graph['parent_id'] = parent_graph.id return forked_graph - def is_user_authorized_to_view_graph(request, username, graph_id): is_authorized = False @@ -194,7 +189,7 @@ def uploadJSONFile(request, username, graphJSON, title): @atomic_transaction def add_graph(request, name=None, tags=None, is_public=None, graph_json=None, style_json=None, owner_email=None, - default_layout_id=None): + default_layout_id=None): # If graph already exists for user, alert them if db.get_graph(request.db_session, owner_email, name) is not None: raise Exception('Graph ' + name + ' already exists for ' + owner_email + '!') @@ -213,8 +208,8 @@ def add_graph(request, name=None, tags=None, is_public=None, graph_json=None, st # Construct new graph to add to database new_graph = db.add_graph(request.db_session, name=name, owner_email=owner_email, - graph_json=json.dumps(G.get_graph_json()), style_json=json.dumps(G.get_style_json()), - is_public=is_public, default_layout_id=default_layout_id) + graph_json=json.dumps(G.get_graph_json()), style_json=json.dumps(G.get_style_json()), + is_public=is_public, default_layout_id=default_layout_id) # Add graph tags for tag in G.get_tags(): add_graph_tag(request, new_graph.id, tag) @@ -223,15 +218,14 @@ def add_graph(request, name=None, tags=None, is_public=None, graph_json=None, st # Add graph edges edge_name_to_id_map = add_graph_edges(request, new_graph.id, G.edges(data=True), node_name_to_id_map) - settings.ELASTIC_CLIENT.index(index="graphs", doc_type='json', id=new_graph.id, - body=map_attributes(json.loads(new_graph.graph_json)), refresh=True) + settings.ELASTIC_CLIENT.index(index="graphs", doc_type='json', id=new_graph.id, body=map_attributes(json.loads(new_graph.graph_json)), refresh=True) return new_graph @atomic_transaction def update_graph(request, graph_id, name=None, is_public=None, graph_json=None, style_json=None, owner_email=None, - default_layout_id=None): + default_layout_id=None): graph = {} if name is not None: graph['name'] = name @@ -260,8 +254,7 @@ def update_graph(request, graph_id, name=None, is_public=None, graph_json=None, graph['graph_json'] = json.dumps(G.get_graph_json()) - settings.ELASTIC_CLIENT.index(index="graphs", doc_type='json', id=graph_id, - body=map_attributes(G.get_graph_json()), refresh=True) + settings.ELASTIC_CLIENT.index(index="graphs", doc_type='json', id=graph_id, body=map_attributes(G.get_graph_json()), refresh=True) return db.update_graph(request.db_session, id=graph_id, updated_graph=graph) @@ -284,8 +277,8 @@ def add_graph_edges(request, graph_id, edges, node_name_to_id_map): # To make sure int and floats are also accepted as source and target nodes of an edge new_edge = db.add_edge(request.db_session, graph_id=graph_id, head_node_id=str(node_name_to_id_map[edge[1]]), - tail_node_id=str(node_name_to_id_map[edge[0]]), name=str(edge[2]['name']), - is_directed=is_directed) + tail_node_id=str(node_name_to_id_map[edge[0]]), name=str(edge[2]['name']), + is_directed=is_directed) edge_name_to_id_map[(edge[0], edge[1])] = new_edge.id return edge_name_to_id_map @@ -333,11 +326,11 @@ def _convert_order_query_term_to_database_order_object(order_query): def search_graphs_by_group_ids(request, group_ids=None, owner_email=None, names=None, nodes=None, edges=None, tags=None, - limit=None, offset=None): + limit=None, offset=None): if group_ids is None: raise Exception("Atleast one group id is required.") return db.find_graphs(request.db_session, group_ids=group_ids, owner_email=owner_email, names=names, nodes=nodes, - edges=edges, tags=tags, limit=limit, offset=offset) + edges=edges, tags=tags, limit=limit, offset=offset) def add_graph_to_group(request, group_id, graph_id): @@ -384,24 +377,24 @@ def search_graphs1(request, owner_email=None, names=None, nodes=None, edges=None graph_ids = None total, graphs_list = db.find_graphs(request.db_session, - owner_email=owner_email, - graph_ids=graph_ids, - is_public=is_public, + owner_email=owner_email, + graph_ids=graph_ids, + is_public=is_public, is_forked=is_forked, - group_ids=group_ids, - names=names, - nodes=nodes, - edges=edges, - tags=tags, - limit=limit, - offset=offset, - order_by=orber_by) + group_ids=group_ids, + names=names, + nodes=nodes, + edges=edges, + tags=tags, + limit=limit, + offset=offset, + order_by=orber_by) return total, graphs_list def search_graphs(request, owner_email=None, member_email=None, names=None, is_public=None, nodes=None, edges=None, - tags=None, limit=20, offset=0, order='desc', sort='name'): + tags=None, limit=20, offset=0, order='desc', sort='name'): 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 @@ -440,22 +433,22 @@ def search_graphs(request, owner_email=None, member_email=None, names=None, is_p # graph_ids=None total, graphs_list = db.find_graphs(request.db_session, - owner_email=owner_email, - names=names, - is_public=is_public, - group_ids=group_ids, - nodes=nodes, - edges=edges, - tags=tags, - limit=limit, - offset=offset, - order_by=orber_by) + owner_email=owner_email, + names=names, + is_public=is_public, + group_ids=group_ids, + nodes=nodes, + edges=edges, + tags=tags, + limit=limit, + offset=offset, + order_by=orber_by) return total, graphs_list def search_layouts(request, owner_email=None, is_shared=None, name=None, graph_id=None, limit=20, offset=0, - order='desc', sort='name'): + order='desc', sort='name'): if sort == 'name': sort_attr = db.Layout.name elif sort == 'update_at': @@ -471,13 +464,13 @@ def search_layouts(request, owner_email=None, is_shared=None, name=None, graph_i orber_by = db.asc(sort_attr) total, layouts = db.find_layouts(request.db_session, - owner_email=owner_email, - is_shared=is_shared, - name=name, - graph_id=graph_id, - limit=limit, - offset=offset, - order_by=orber_by) + owner_email=owner_email, + is_shared=is_shared, + name=name, + graph_id=graph_id, + limit=limit, + offset=offset, + order_by=orber_by) return total, layouts @@ -487,18 +480,18 @@ def get_layout_by_id(request, layout_id): def add_layout(request, owner_email=None, name=None, graph_id=None, is_shared=None, style_json=None, - positions_json=None): + positions_json=None): if name is None or owner_email is None or graph_id is None: raise Exception("Required Parameter is missing!") try: return db.add_layout(request.db_session, owner_email=owner_email, name=name, graph_id=graph_id, - is_shared=is_shared, style_json=dumps(style_json), positions_json=dumps(positions_json)) + is_shared=is_shared, style_json=dumps(style_json), positions_json=dumps(positions_json)) except IntegrityError as e: raise BadRequest(request, error_code=ErrorCodes.Validation.LayoutNameAlreadyExists, args=name) def update_layout(request, layout_id, owner_email=None, name=None, graph_id=None, is_shared=None, style_json=None, - positions_json=None): + positions_json=None): if layout_id is None: raise Exception("Required Parameter is missing!") @@ -542,12 +535,12 @@ def search_nodes(request, graph_id=None, names=None, labels=None, limit=20, offs ## TODO: create a util function to relpace the code parse sort and order parameters. This code is repeated again and again. total, nodes = db.find_nodes(request.db_session, - names=names, - labels=labels, - graph_id=graph_id, - limit=limit, - offset=offset, - order_by=orber_by) + names=names, + labels=labels, + graph_id=graph_id, + limit=limit, + offset=offset, + order_by=orber_by) return total, nodes @@ -568,7 +561,7 @@ def delete_node_by_id(request, node_id): def search_edges(request, is_directed=None, names=None, edges=None, graph_id=None, limit=20, offset=0, order='desc', - sort='name'): + sort='name'): if sort == 'name': sort_attr = db.Edge.name elif sort == 'update_at': @@ -587,13 +580,13 @@ def search_edges(request, is_directed=None, names=None, edges=None, graph_id=Non ## TODO: create a util function to relpace the code parse sort and order parameters. This code is repeated again and again. total, edges = db.find_edges(request.db_session, - names=names, - edges=edges, - is_directed=is_directed, - graph_id=graph_id, - limit=limit, - offset=offset, - order_by=orber_by) + names=names, + edges=edges, + is_directed=is_directed, + graph_id=graph_id, + limit=limit, + offset=offset, + order_by=orber_by) return total, edges @@ -606,16 +599,16 @@ def add_edge(request, name=None, head_node_id=None, tail_node_id=None, is_direct if name is None or graph_id is None or head_node_id is None or tail_node_id is None: raise Exception("Required Parameter is missing!") return db.add_edge(request.db_session, name=name, head_node_id=head_node_id, tail_node_id=tail_node_id, - is_directed=is_directed, graph_id=graph_id) + is_directed=is_directed, graph_id=graph_id) 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) + 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 \ No newline at end of file diff --git a/applications/graphs/dal.py b/applications/graphs/dal.py index de8f795f..fd790847 100644 --- a/applications/graphs/dal.py +++ b/applications/graphs/dal.py @@ -470,7 +470,7 @@ def find_edges(db_session, is_directed=None, names=None, edges=None, graph_id=No 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 1 + return fork @with_session def get_fork(db_session, parent_graph_id=None, forked_graph_id=None): From 159f0a6985aa6a829d69e95bc83ff89a771c192d Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Tue, 12 Jun 2018 00:41:14 +0200 Subject: [PATCH 11/11] Format code according to PEP8 --- applications/graphs/controllers.py | 7 ++++--- applications/graphs/dal.py | 4 +++- applications/graphs/models.py | 2 +- applications/graphs/views.py | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/applications/graphs/controllers.py b/applications/graphs/controllers.py index 3671c680..74dc3c92 100644 --- a/applications/graphs/controllers.py +++ b/applications/graphs/controllers.py @@ -350,7 +350,7 @@ 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, is_forked=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'): 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 @@ -380,7 +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, + is_forked=is_forked, group_ids=group_ids, names=names, nodes=nodes, @@ -606,9 +606,10 @@ 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 \ No newline at end of file + return new_fork diff --git a/applications/graphs/dal.py b/applications/graphs/dal.py index fd790847..65bd7189 100644 --- a/applications/graphs/dal.py +++ b/applications/graphs/dal.py @@ -466,12 +466,14 @@ def find_edges(db_session, is_directed=None, names=None, edges=None, graph_id=No 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) @@ -480,4 +482,4 @@ def get_fork(db_session, parent_graph_id=None, forked_graph_id=None): if forked_graph_id is not None: query = query.filter(GraphFork.graph_id == forked_graph_id) total = query.count() - return total, query.all() \ No newline at end of file + return total, query.all() diff --git a/applications/graphs/models.py b/applications/graphs/models.py index 702684b9..1af5468b 100644 --- a/applications/graphs/models.py +++ b/applications/graphs/models.py @@ -306,4 +306,4 @@ def serialize(cls, **kwargs): 'parent_graph_id': cls.parent_graph_id, 'created_at': cls.created_at.isoformat(), 'updated_at': cls.updated_at.isoformat() - } \ No newline at end of file + } diff --git a/applications/graphs/views.py b/applications/graphs/views.py index b68b7619..6b2a851d 100644 --- a/applications/graphs/views.py +++ b/applications/graphs/views.py @@ -1738,4 +1738,4 @@ def _get_fork(request, graph_id): """ authorization.validate(request, permission='GRAPH_READ', graph_id=graph_id) - return utils.serializer(graphs.get_fork_by_id(request, graph_id)) \ No newline at end of file + return utils.serializer(graphs.get_fork_by_id(request, graph_id))