From e30eed4fbc053e4f79918c040db15c91282e9f9e Mon Sep 17 00:00:00 2001 From: Aditya Bharadwaj Date: Sat, 3 Mar 2018 20:36:51 -0500 Subject: [PATCH 01/21] 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 652195503e50d2ee095ce86b2bbf751a024da63c Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Sat, 9 Jun 2018 17:11:27 +0200 Subject: [PATCH 02/21] Added GET Api for Graph Versions --- applications/graphs/controllers.py | 27 ++++ applications/graphs/dal.py | 27 ++++ applications/graphs/models.py | 25 ++++ applications/graphs/urls.py | 6 + applications/graphs/views.py | 162 +++++++++++++++++++++++ static/js/graphs_page.js | 38 ++++++ templates/graph/graph_version_table.html | 36 +++++ templates/graph/index.html | 8 ++ 8 files changed, 329 insertions(+) create mode 100644 templates/graph/graph_version_table.html diff --git a/applications/graphs/controllers.py b/applications/graphs/controllers.py index 7f012a05..4e3b96c7 100644 --- a/applications/graphs/controllers.py +++ b/applications/graphs/controllers.py @@ -586,3 +586,30 @@ 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 search_graph_versions(request, graph_id=None, names=None, limit=20, offset=0, order='desc', sort='name'): + if sort == 'name': + sort_attr = db.GraphVersion.name + elif sort == 'update_at': + sort_attr = db.GraphVersion.updated_at + else: + sort_attr = db.GraphVersion.name + + if order == 'desc': + orber_by = db.desc(sort_attr) + else: + orber_by = db.asc(sort_attr) + + ## TODO: create a util function to relpace the code parse sort and order parameters. This code is repeated again and again. + + total, graph_versions = db.find_graph_versions(request.db_session, + names=names, + graph_id=graph_id, + limit=limit, + offset=offset, + order_by=orber_by) + + return total, graph_versions + +def get_graph_version_by_id(request, version_id): + return db.get_graph_version_by_id(request.db_session, version_id) \ No newline at end of file diff --git a/applications/graphs/dal.py b/applications/graphs/dal.py index 388b8141..0efd9e0a 100644 --- a/applications/graphs/dal.py +++ b/applications/graphs/dal.py @@ -458,3 +458,30 @@ 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 find_graph_versions(db_session, names=None, graph_id=None, limit=None, offset=None, + order_by=desc(GraphVersion.updated_at)): + query = db_session.query(GraphVersion) + + if graph_id is not None: + query = query.filter(GraphVersion.graph_id == graph_id) + + names = [] if names is None else names + if len(names) > 0: + query = query.filter( + or_(*([GraphVersion.name.ilike(name) for name in names]))) + + total = query.count() + + if order_by is not None: + query = query.order_by(order_by) + + if offset is not None and limit is not None: + query = query.limit(limit).offset(offset) + + return total, query.all() + +@with_session +def get_graph_version_by_id(db_session, id): + return db_session.query(GraphVersion).filter(GraphVersion.id == id).one_or_none() \ No newline at end of file diff --git a/applications/graphs/models.py b/applications/graphs/models.py index f0e3e554..c58a15ba 100644 --- a/applications/graphs/models.py +++ b/applications/graphs/models.py @@ -32,6 +32,9 @@ 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") + graph_version = relationship("GraphVersion", foreign_keys="GraphVersion.graph_id", back_populates="graph", + cascade="all, delete-orphan") + groups = association_proxy('shared_with_groups', 'group') tags = association_proxy('graph_tags', 'tag') @@ -279,3 +282,25 @@ class GraphToTag(TimeStampMixin, Base): def __table_args__(cls): args = cls.constraints + cls.indices return args + +class GraphVersion(IDMixin, TimeStampMixin, Base): + __tablename__ = 'graph_version' + + name = Column(String, nullable=False, unique=True) + graph_id = Column(Integer, ForeignKey('graph.id', ondelete="CASCADE", onupdate="CASCADE"), primary_key=True) + owner_email = Column(String, ForeignKey('user.email', ondelete="CASCADE", onupdate="CASCADE"), nullable=False) + graph_json = Column(String, nullable=False) + description = Column(String, nullable=True) + + graph = relationship("Graph", foreign_keys=[graph_id], back_populates="graph_version", uselist=False) + + def serialize(cls, **kwargs): + return { + 'id': cls.id, + 'name': cls.name, + 'description': cls.description, + 'graph_json' : cls.graph_json, + 'creator': 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/urls.py b/applications/graphs/urls.py index 297f92ab..7c59705c 100644 --- a/applications/graphs/urls.py +++ b/applications/graphs/urls.py @@ -27,6 +27,9 @@ # 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 Versions + url(r'^ajax/graphs/(?P[^/]+)/version/$', views.graph_versions_ajax_api, name='graph_versions_ajax_api'), + url(r'^ajax/graphs/(?P[^/]+)/version/(?P[^/]+)$', views.graph_versions_ajax_api, name='graph_versions_ajax_api'), # REST APIs Endpoints @@ -45,5 +48,8 @@ # 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 Nodes + url(r'^api/v1/graphs/(?P[^/]+)/version/$', views.graph_versions_rest_api, name='graph_versions_rest_api'), + url(r'^api/v1/graphs/(?P[^/]+)/version/(?P[^/]+)$', views.graph_versions_rest_api, name='graph_versions_rest_api'), ] diff --git a/applications/graphs/views.py b/applications/graphs/views.py index 772639c5..bed0384d 100644 --- a/applications/graphs/views.py +++ b/applications/graphs/views.py @@ -1522,3 +1522,165 @@ 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 Version APIs +''' + +@csrf_exempt +@is_authenticated() +def graph_versions_rest_api(request, graph_id, version_id=None): + """ + Handles any request sent to following urls: + /api/v1/graphs//version + /api/v1/graphs//version/ + + Parameters + ---------- + request - HTTP Request + + Returns + ------- + response : JSON Response + + """ + return _graph_versions_api(request, graph_id, version_id=version_id) + + +def graph_versions_ajax_api(request, graph_id, version_id=None): + """ + Handles any request sent to following urls: + /javascript/graphs//version + /javascript/graphs//version/ + + Parameters + ---------- + request - HTTP Request + + Returns + ------- + response : JSON Response + + """ + return _graph_versions_api(request, graph_id, version_id=version_id) + + +def _graph_versions_api(request, graph_id, version_id=None): + """ + Handles any request (GET/POST) sent to version/ or version/. + + Parameters + ---------- + request - HTTP Request + graph_id : string + Unique ID of the graph. + version_id : string + Unique ID of the version. + + Returns + ------- + + """ + if request.META.get('HTTP_ACCEPT', None) == 'application/json': + if request.method == "GET" and version_id is None: + return HttpResponse(json.dumps(_get_graph_versions(request, graph_id, query=request.GET)), + content_type="application/json") + elif request.method == "GET" and version_id is not None: + return HttpResponse(json.dumps(_get_graph_version(request, graph_id, version_id)), + content_type="application/json") + elif request.method == "POST" and version_id is None: + return HttpResponse(json.dumps(_add_node(request, graph_id, node=json.loads(request.body))), + content_type="application/json", + status=201) + elif request.method == "DELETE" and version_id is not None: + _delete_node(request, graph_id, version_id) + return HttpResponse(json.dumps({ + "message": "Successfully deleted node with id=%s" % (version_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 _get_graph_versions(request, graph_id, query={}): + """ + + Query Parameters + ---------- + graph_id : string + Unique ID of the graph. + limit : integer + Number of entities to return. Default value is 20. + offset : integer + Offset the list of returned entities by this number. Default value is 0. + names : list of strings + Search for versions with given names. In order to search for versions with given name as a substring, wrap the name with percentage symbol. For example, %xyz% will search for all versions with xyz in their name. + order : string + Defines the column sort order, can only be 'asc' or 'desc'. + sort : string + Defines which column will be sorted. + + + Parameters + ---------- + request : object + HTTP GET Request. + + Returns + ------- + total : integer + Number of graph versions matching the request. + versions : List of versions. + List of Version Objects with given limit and offset. + + Raises + ------ + + Notes + ------ + + """ + + authorization.validate(request, permission='GRAPH_READ', graph_id=graph_id) + + querydict = QueryDict('', mutable=True) + querydict.update(query) + query = querydict + + total, versions_list = graphs.search_graph_versions(request, + graph_id=graph_id, + names=query.getlist('names[]', None), + limit=query.get('limit', 20), + offset=query.get('offset', 0), + order=query.get('order', 'desc'), + sort=query.get('sort', 'name')) + + return { + 'total': total, + 'versions': [utils.serializer(version) for version in versions_list] + } + +def _get_graph_version(request, graph_id, version_id): + """ + + Parameters + ---------- + request : object + HTTP GET Request. + version_id : string + Unique ID of the version. + + Returns + ------- + version: object + + Raises + ------ + + Notes + ------ + + """ + authorization.validate(request, permission='GRAPH_READ', graph_id=graph_id) + return utils.serializer(graphs.get_graph_version_by_id(request, version_id)) \ No newline at end of file diff --git a/static/js/graphs_page.js b/static/js/graphs_page.js index a7f4c57a..306d0800 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) }, }, + version: { + ENDPOINT: _.template('/ajax/graphs/<%= graph_id %>/version/'), + get: function (graph_id, data, successCallback, errorCallback) { + apis.jsonRequest('GET', apis.version.ENDPOINT({'graph_id': graph_id}), data, successCallback, errorCallback) + }, + }, edges: { ENDPOINT: _.template('/ajax/graphs/<%= graph_id %>/edges/'), get: function (graph_id, data, successCallback, errorCallback) { @@ -1127,6 +1133,38 @@ var graphPage = { ); } }, + graphVersionTable: { + getVersionByGraphID: function (params) { + /** + * This is the custom ajax request used to load version in graphVersionTable. + * + * params - query parameters for the ajax request. + * It contains parameters like limit, offset, search, sort, order. + */ + + if (params.data["search"]) { + params.data["names"] = _.map(_.filter(_.split(params.data["search"], ','), function (s) { + return s.indexOf(':') === -1; + }), function (str) { + return '%' + str + '%'; + }); + params.data["labels"] = params.data["names"]; + } + + params.data["graph_id"] = $('#GraphID').val(); + + apis.version.get($('#GraphID').val(), params.data, + successCallback = function (response) { + // This method is called when nodes are successfully fetched. + params.success(response); + }, + errorCallback = function () { + // This method is called when error occurs while fetching nodes. + params.error('Error'); + } + ); + } + }, layoutsTable: { getPrivateLayoutsByGraphID: function (params) { /** diff --git a/templates/graph/graph_version_table.html b/templates/graph/graph_version_table.html new file mode 100644 index 00000000..b8a28a3b --- /dev/null +++ b/templates/graph/graph_version_table.html @@ -0,0 +1,36 @@ + +
+ Currently Viewing : +
+ Default Version : +
+ + + + + + + {# #} + + {% if uid %} + + {% endif %} + + +
Version NameDescriptionLast#} + {# Modified#} + {# Created on + Operations +
diff --git a/templates/graph/index.html b/templates/graph/index.html index 830d44f3..50b681c9 100644 --- a/templates/graph/index.html +++ b/templates/graph/index.html @@ -220,6 +220,11 @@

Layouts {% endif %} + {% if uid %} +
  • + Graph Version +
  • + {% endif %}
    @@ -274,6 +279,9 @@

    {% include 'graph/layouts_table.html' %}
    +
    + {% include 'graph/graph_version_table.html' %} +

    From 240fd20cf36a945e7bf52f89f5c7af8c0b8a0d0a Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Sun, 10 Jun 2018 07:58:26 +0200 Subject: [PATCH 03/21] Added POST Api for graph_version --- applications/graphs/controllers.py | 7 ++++- applications/graphs/dal.py | 8 +++++- applications/graphs/views.py | 45 ++++++++++++++++++++++++++++-- 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/applications/graphs/controllers.py b/applications/graphs/controllers.py index 4e3b96c7..c877869b 100644 --- a/applications/graphs/controllers.py +++ b/applications/graphs/controllers.py @@ -612,4 +612,9 @@ def search_graph_versions(request, graph_id=None, names=None, limit=20, offset=0 return total, graph_versions def get_graph_version_by_id(request, version_id): - return db.get_graph_version_by_id(request.db_session, version_id) \ No newline at end of file + return db.get_graph_version_by_id(request.db_session, version_id) + +def add_graph_version(request, name=None, description=None, owner_email=None, graph_json=None, graph_id=None): + if name is None or graph_id is None or graph_json is None: + raise Exception("Required Parameter is missing!") + return db.add_graph_version(request.db_session, name=name, description=description, owner_email=owner_email, graph_json=graph_json, graph_id=graph_id) \ No newline at end of file diff --git a/applications/graphs/dal.py b/applications/graphs/dal.py index 0efd9e0a..986fec0f 100644 --- a/applications/graphs/dal.py +++ b/applications/graphs/dal.py @@ -484,4 +484,10 @@ def find_graph_versions(db_session, names=None, graph_id=None, limit=None, offse @with_session def get_graph_version_by_id(db_session, id): - return db_session.query(GraphVersion).filter(GraphVersion.id == id).one_or_none() \ No newline at end of file + return db_session.query(GraphVersion).filter(GraphVersion.id == id).one_or_none() + +@with_session +def add_graph_version(db_session, graph_id, name, graph_json, owner_email, description=None): + graph_version = GraphVersion(name=name, graph_id=graph_id, graph_json=graph_json, owner_email=owner_email, description=description) + db_session.add(graph_version) + return graph_version \ No newline at end of file diff --git a/applications/graphs/views.py b/applications/graphs/views.py index bed0384d..1e5a5826 100644 --- a/applications/graphs/views.py +++ b/applications/graphs/views.py @@ -1590,7 +1590,7 @@ def _graph_versions_api(request, graph_id, version_id=None): return HttpResponse(json.dumps(_get_graph_version(request, graph_id, version_id)), content_type="application/json") elif request.method == "POST" and version_id is None: - return HttpResponse(json.dumps(_add_node(request, graph_id, node=json.loads(request.body))), + return HttpResponse(json.dumps(_add_graph_version(request, graph_id, graph_version=json.loads(request.body))), content_type="application/json", status=201) elif request.method == "DELETE" and version_id is not None: @@ -1683,4 +1683,45 @@ def _get_graph_version(request, graph_id, version_id): """ authorization.validate(request, permission='GRAPH_READ', graph_id=graph_id) - return utils.serializer(graphs.get_graph_version_by_id(request, version_id)) \ No newline at end of file + return utils.serializer(graphs.get_graph_version_by_id(request, version_id)) + +@is_authenticated() +def _add_graph_version(request, graph_id, graph_version={}): + """ + Node Parameters + ---------- + name : string + Name of the node. Required + owner_email : string + Email of the Owner of the graph. Required + graph_id : string + Unique ID of the graph. Required + + + Parameters + ---------- + graph_json : dict + Dictionary containing the graph_json data of the graph being added. + request : object + HTTP POST Request. + + Returns + ------- + graph_version : object + Newly created graph_version object. + + Raises + ------ + + Notes + ------ + + """ + # + + return utils.serializer(graphs.add_graph_version(request, + name=graph_version.get('name', None), + description=graph_version.get('description', None), + owner_email=graph_version.get('owner_email', None), + graph_json=graph_version.get('graph_json', None), + graph_id=graph_id)) \ No newline at end of file From 64921a35922a435343ea20be6c98aafadcb72e6e Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Sun, 10 Jun 2018 08:17:00 +0200 Subject: [PATCH 04/21] Added DELETE API for graph_version --- applications/graphs/controllers.py | 6 +++++- applications/graphs/dal.py | 8 +++++++- applications/graphs/views.py | 32 +++++++++++++++++++++++++++--- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/applications/graphs/controllers.py b/applications/graphs/controllers.py index c877869b..9169cde4 100644 --- a/applications/graphs/controllers.py +++ b/applications/graphs/controllers.py @@ -617,4 +617,8 @@ def get_graph_version_by_id(request, version_id): def add_graph_version(request, name=None, description=None, owner_email=None, graph_json=None, graph_id=None): if name is None or graph_id is None or graph_json is None: raise Exception("Required Parameter is missing!") - return db.add_graph_version(request.db_session, name=name, description=description, owner_email=owner_email, graph_json=graph_json, graph_id=graph_id) \ No newline at end of file + return db.add_graph_version(request.db_session, name=name, description=description, owner_email=owner_email, graph_json=graph_json, graph_id=graph_id) + +def delete_graph_version_by_id(request, graph_version_id): + db.delete_graph_version(request.db_session, id=graph_version_id) + return \ No newline at end of file diff --git a/applications/graphs/dal.py b/applications/graphs/dal.py index 986fec0f..7ca67453 100644 --- a/applications/graphs/dal.py +++ b/applications/graphs/dal.py @@ -490,4 +490,10 @@ def get_graph_version_by_id(db_session, id): def add_graph_version(db_session, graph_id, name, graph_json, owner_email, description=None): graph_version = GraphVersion(name=name, graph_id=graph_id, graph_json=graph_json, owner_email=owner_email, description=description) db_session.add(graph_version) - return graph_version \ No newline at end of file + return graph_version + +@with_session +def delete_graph_version(db_session, id): + graph_version = db_session.query(GraphVersion).filter(GraphVersion.id == id).one_or_none() + db_session.delete(graph_version) + return graph_version \ No newline at end of file diff --git a/applications/graphs/views.py b/applications/graphs/views.py index 1e5a5826..e7b0e94a 100644 --- a/applications/graphs/views.py +++ b/applications/graphs/views.py @@ -1594,9 +1594,9 @@ def _graph_versions_api(request, graph_id, version_id=None): content_type="application/json", status=201) elif request.method == "DELETE" and version_id is not None: - _delete_node(request, graph_id, version_id) + _delete_graph_version(request, graph_id, version_id) return HttpResponse(json.dumps({ - "message": "Successfully deleted node with id=%s" % (version_id) + "message": "Successfully deleted Graph Version with id=%s" % (version_id) }), content_type="application/json", status=200) else: raise MethodNotAllowed(request) # Handle other type of request methods like OPTIONS etc. @@ -1724,4 +1724,30 @@ def _add_graph_version(request, graph_id, graph_version={}): description=graph_version.get('description', None), owner_email=graph_version.get('owner_email', None), graph_json=graph_version.get('graph_json', None), - graph_id=graph_id)) \ No newline at end of file + graph_id=graph_id)) + +@is_authenticated() +def _delete_graph_version(request, graph_id, graph_version_id): + """ + + Parameters + ---------- + request : object + HTTP GET Request. + graph_version_id : string + Unique ID of the Graph Version. + + Returns + ------- + None + + Raises + ------ + + Notes + ------ + + """ + #authorization.validate(request, permission='GRAPH_UPDATE', graph_id=graph_id) + + graphs.delete_graph_version_by_id(request, graph_version_id) \ No newline at end of file From 4d307e38b5a70ea5bec1fc7d97c1b48d5b7c8f9b Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Sun, 10 Jun 2018 14:44:07 +0200 Subject: [PATCH 05/21] Added Version selector dropdown --- applications/graphs/models.py | 28 ++++++++++++++++-------- applications/graphs/views.py | 2 +- static/css/graphspace.css | 9 ++++++++ static/js/graphs_page.js | 25 ++++++++++++++++++++- templates/graph/graph_version_table.html | 8 +++---- templates/graph/index.html | 8 +++++++ 6 files changed, 65 insertions(+), 15 deletions(-) diff --git a/applications/graphs/models.py b/applications/graphs/models.py index c58a15ba..3f2121ec 100644 --- a/applications/graphs/models.py +++ b/applications/graphs/models.py @@ -295,12 +295,22 @@ class GraphVersion(IDMixin, TimeStampMixin, Base): graph = relationship("Graph", foreign_keys=[graph_id], back_populates="graph_version", uselist=False) def serialize(cls, **kwargs): - return { - 'id': cls.id, - 'name': cls.name, - 'description': cls.description, - 'graph_json' : cls.graph_json, - 'creator': cls.owner_email, - 'created_at': cls.created_at.isoformat(), - 'updated_at': cls.updated_at.isoformat() - } \ No newline at end of file + if 'summary' in kwargs and kwargs['summary']: + return { + 'id': cls.id, + 'name': cls.name, + 'description': cls.description, + 'creator': cls.owner_email, + 'created_at': cls.created_at.isoformat(), + 'updated_at': cls.updated_at.isoformat() + } + else : + return { + 'id': cls.id, + 'name': cls.name, + 'description': cls.description, + 'graph_json': cls.graph_json, + 'creator': 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 e7b0e94a..f8ea2609 100644 --- a/applications/graphs/views.py +++ b/applications/graphs/views.py @@ -1658,7 +1658,7 @@ def _get_graph_versions(request, graph_id, query={}): return { 'total': total, - 'versions': [utils.serializer(version) for version in versions_list] + 'versions': [utils.serializer(version, summary=True) for version in versions_list] } def _get_graph_version(request, graph_id, version_id): diff --git a/static/css/graphspace.css b/static/css/graphspace.css index ff68bde3..daf77688 100644 --- a/static/css/graphspace.css +++ b/static/css/graphspace.css @@ -438,4 +438,13 @@ p.lead { position: relative; margin-left: 0; } +} +.graph_version_span { + cursor:pointer; + color:#337ab7; + +} + +.graph_version_span:hover { + text-decoration:underline; } \ No newline at end of file diff --git a/static/js/graphs_page.js b/static/js/graphs_page.js index 306d0800..1f38ccc0 100644 --- a/static/js/graphs_page.js +++ b/static/js/graphs_page.js @@ -44,6 +44,9 @@ var apis = { get: function (graph_id, data, successCallback, errorCallback) { apis.jsonRequest('GET', apis.version.ENDPOINT({'graph_id': graph_id}), data, successCallback, errorCallback) }, + getByID: function (graph_id, version_id, successCallback, errorCallback) { + apis.jsonRequest('GET', apis.version.ENDPOINT({'graph_id': graph_id}) + version_id, undefined, successCallback, errorCallback) + }, }, edges: { ENDPOINT: _.template('/ajax/graphs/<%= graph_id %>/edges/'), @@ -525,6 +528,22 @@ var graphPage = { export: function (format) { cytoscapeGraph.export(graphPage.cyGraph, format, $('#GraphName').val()); }, + selectGraphVersion: function (row) { + label = $("#version_selector_dropdown").find('a[row_id=' + row + ']').attr('data') + + apis.version.getByID($('#GraphID').val(), row, + successCallback = function (response) { + graph_json = JSON.parse(response.graph_json); + graphPage.contructCytoscapeGraph(); + console.log("Success"); + }, + errorCallback = function (xhr, status, errorThrown) { + // This method is called when error occurs while deleting group_to_graph relationship. + $.notify({message: "You are not authorized to access this Version."}, {type: 'danger'}); + }); + $('#version_selector > bold').text(label); + console.log("abc"); + }, applyAutoLayout: function (layout_id) { graphPage.applyLayout(cytoscapeGraph.getAutomaticLayoutSettings(layout_id)); console.log("after applying layout"); @@ -1163,7 +1182,11 @@ var graphPage = { params.error('Error'); } ); - } + }, + versionFormatter: function (value, row, index) { + $("#version_selector_dropdown").append('
  • ' + value + '
  • ') + return (''+ value +'') + } }, layoutsTable: { getPrivateLayoutsByGraphID: function (params) { diff --git a/templates/graph/graph_version_table.html b/templates/graph/graph_version_table.html index b8a28a3b..2e42c9cd 100644 --- a/templates/graph/graph_version_table.html +++ b/templates/graph/graph_version_table.html @@ -13,19 +13,19 @@ data-ajax="graphPage.graphVersionTable.getVersionByGraphID" data-search="true" data-side-pagination="server" - data-data-field="nodes" + data-data-field="versions" data-pagination="true" data-sort-name="name" data-id-field="id" data-sort-order="asc"> - Version Name - Description + Version Name + Description {# Last#} {# Modified#} {# #} - Created on + Created on {% if uid %} Operations diff --git a/templates/graph/index.html b/templates/graph/index.html index 50b681c9..15db3677 100644 --- a/templates/graph/index.html +++ b/templates/graph/index.html @@ -225,6 +225,14 @@

    Graph Version {% endif %} +
    + + +
    From 321a432e26ff8e930d80c5c9254e136d82753aa4 Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Mon, 11 Jun 2018 17:29:33 +0200 Subject: [PATCH 06/21] Added API Specifications for Graph Version --- api_specifications/api.raml | 261 ++++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) diff --git a/api_specifications/api.raml b/api_specifications/api.raml index 1ca96ef4..fc254f80 100644 --- a/api_specifications/api.raml +++ b/api_specifications/api.raml @@ -543,6 +543,156 @@ types: graph_id: number positions_json: string style_json: string + GraphVersion: + description: Graph Version object + example: | + { + "name": "Graph Version 1.0", + "owner_email": "user1@example.com", + "description": "This is Version 1.0 of Sample Graph", + "graph_id": 1, + "graph_json": { + "elements": { + "nodes": [ + { + "position": { + "y": 277.5, + "x": 297.5 + }, + "data": { + "k": 0, + "id": "P4314611", + "name": "P4314611", + "label": "" + } + }, + { + "position": { + "y": 277.5, + "x": 892.5 + }, + "data": { + "k": 0, + "id": "P0810711", + "name": "P0810711", + "label": "" + } + } + ], + "edges": [ + { + "data": { + "target": "P0810711", + "k": 0, + "source": "P4314611", + "is_directed": 1, + "id": "P4314611-P0810711", + "name": "P4314611-P0810711" + }, + "style": { + "line-color": "blue", + "target-arrow-shape": "triangle", + "source-arrow-color": "yellow", + "width": "12px", + "curve-style": "bezier", + "line-style": "dotted" + } + } + ] + }, + "data": { + "description": "Description of graph.. can also point to an image hosted elsewhere", + "name": "My first graph", + "tags": [ + "tutorial", "sample" + ] + } + } + } + properties: + name: string + owner_email: string + description: string + graph_id: number + graph_json: GraphJSON + GraphVersionResponse: + description: Graph Version Response object + example: | + { + "name": "Graph Version 1.0", + "updated_at": "2017-03-25T15:37:20.728954", + "graph_id": 25, + "created_at": "2017-03-25T15:37:20.728954", + "owner_email": "user1@example.com", + "description": "This is Version 1.0 of Sample Graph", + "id": 21384, + "graph_json": { + "elements": { + "nodes": [ + { + "position": { + "y": 277.5, + "x": 297.5 + }, + "data": { + "k": 0, + "id": "P4314611", + "name": "P4314611", + "label": "" + } + }, + { + "position": { + "y": 277.5, + "x": 892.5 + }, + "data": { + "k": 0, + "id": "P0810711", + "name": "P0810711", + "label": "" + } + } + ], + "edges": [ + { + "data": { + "target": "P0810711", + "k": 0, + "source": "P4314611", + "is_directed": 1, + "id": "P4314611-P0810711", + "name": "P4314611-P0810711" + }, + "style": { + "line-color": "blue", + "target-arrow-shape": "triangle", + "source-arrow-color": "yellow", + "width": "12px", + "curve-style": "bezier", + "line-style": "dotted" + } + } + ] + }, + "data": { + "description": "Description of graph.. can also point to an image hosted elsewhere", + "name": "My first graph", + "tags": [ + "tutorial", "sample" + ] + } + } + } + properties: + id: number + name: string + owner_email: string + graph_id: number + graph_json: GraphJSON + created_at: string + updated_at: string + description: string Member: description: Member Response object example: | @@ -808,6 +958,117 @@ types: body: application/json: type: Error + /versions: + description: APIs to access versions of a specific graph on GraphSpace. + displayName: Graph Versions + get: + description: List all Graph Versions matching query criteria, if provided; otherwise list all Graph Versions. + queryParameters: + owner_email?: string + name?: + description: Search for Graph Versions with given name. In order to search for versions with given name as a substring, wrap the name with percentage symbol. For example, %xyz% will search for all versions with xyz in their name. + type: string + limit?: + description: Number of entities to return. + default: 20 + type: number + offset?: + description: Offset the list of returned entities by this number. + default: 0 + type: number + order?: + description: Defines the column sort order, can only be 'asc' or 'desc'. + type: string + sort?: + description: Defines which column will be sorted. + type: string + example: "name" + responses: + 200: + description: SUCCESS + body: + application/json: + type: object + properties: + total: number + versions: GraphVersionResponse[] + 400: + description: BAD REQUEST + body: + application/json: + type: Error + 403: + description: FORBIDDEN + body: + application/json: + type: Error + post: + description: Create a new Graph Version. + body: + application/json: + type: GraphVersion + responses: + 201: + description: SUCCESS + body: + application/json: + type: GraphVersionResponse + 400: + description: BAD REQUEST + body: + application/json: + type: Error + /{version_id}: + description: APIs to access a specific graph version on GraphSpace. + displayName: Graph Version + get: + description: Get a Graph Version by id + responses: + 200: + description: SUCCESS + body: + application/json: + type: GraphVersionResponse + 403: + description: FORBIDDEN + body: + application/json: + type: Error + put: + description: Update a Graph Version by id + body: + application/json: + type: GraphVersion + responses: + 200: + description: SUCCESS + body: + application/json: + type: GraphVersionResponse + 403: + description: FORBIDDEN + body: + application/json: + type: Error + delete: + description: Delete a Graph Version by id + responses: + 200: + description: SUCCESS + body: + application/json: + type: object + properties: + message: string + example: | + { + "message": "Successfully deleted graph version with id=21341" + } + 403: + description: FORBIDDEN + body: + application/json: + type: Error /groups: description: APIs to access groups on GraphSpace. From f99e6682bbe90f9bba47096178201bb308355dea Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Mon, 18 Jun 2018 18:39:13 +0200 Subject: [PATCH 07/21] Changes in model for version feature --- applications/graphs/models.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/applications/graphs/models.py b/applications/graphs/models.py index 3f2121ec..2a47d491 100644 --- a/applications/graphs/models.py +++ b/applications/graphs/models.py @@ -17,8 +17,10 @@ class Graph(IDMixin, TimeStampMixin, Base): name = Column(String, nullable=False) owner_email = Column(String, ForeignKey('user.email', ondelete="CASCADE", onupdate="CASCADE"), nullable=False) - graph_json = Column(String, nullable=False) - style_json = Column(String, nullable=False) + #graph_json = Column(String, nullable=False) + #style_json = Column(String, nullable=False) + default_version_id = Column(Integer, ForeignKey('graph_version.id', ondelete="CASCADE", onupdate="CASCADE"), + nullable=True) is_public = Column(Integer, nullable=False, default=0) default_layout_id = Column(Integer, ForeignKey('layout.id', ondelete="CASCADE", onupdate="CASCADE"), nullable=True) @@ -290,6 +292,7 @@ class GraphVersion(IDMixin, TimeStampMixin, Base): graph_id = Column(Integer, ForeignKey('graph.id', ondelete="CASCADE", onupdate="CASCADE"), primary_key=True) owner_email = Column(String, ForeignKey('user.email', ondelete="CASCADE", onupdate="CASCADE"), nullable=False) graph_json = Column(String, nullable=False) + style_json = Column(String, nullable=False) description = Column(String, nullable=True) graph = relationship("Graph", foreign_keys=[graph_id], back_populates="graph_version", uselist=False) From 76b4acc39db482b524bec0556ea5dbe260b72cae Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Mon, 18 Jun 2018 22:17:33 +0200 Subject: [PATCH 08/21] Added changes for upload, add graph --- applications/graphs/controllers.py | 14 +++++++++++++- applications/graphs/dal.py | 16 +++++++++++----- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/applications/graphs/controllers.py b/applications/graphs/controllers.py index 9169cde4..ab108c76 100644 --- a/applications/graphs/controllers.py +++ b/applications/graphs/controllers.py @@ -191,8 +191,20 @@ 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) + default_version = db.add_graph_version(request.db_session, name=name, description='Default Version', + owner_email=owner_email, graph_json=json.dumps(G.get_graph_json()), + style_json=json.dumps(G.get_style_json()), graph_id=new_graph.id) + + # Add graph_json to new_graph + new_graph.__setattr__('graph_json', default_version.graph_json) + + # Add style_json to new_graph + new_graph.__setattr__('style_json', default_version.style_json) + # Store the index of default version in the graph table (new_graph entry) + new_graph.__setattr__('default_version_id',default_version.id) + db.set_default_version(request.db_session, new_graph.id, default_version.id) + # Add graph tags for tag in G.get_tags(): add_graph_tag(request, new_graph.id, tag) diff --git a/applications/graphs/dal.py b/applications/graphs/dal.py index 7ca67453..c848ddfe 100644 --- a/applications/graphs/dal.py +++ b/applications/graphs/dal.py @@ -80,8 +80,8 @@ def get_graphs_by_edges_and_nodes_and_names(db_session, group_ids=None, names=No @with_session -def add_graph(db_session, name, owner_email, graph_json, style_json, is_public=0, default_layout_id=None): - graph = Graph(name=name, owner_email=owner_email, graph_json=graph_json, style_json=style_json, is_public=is_public, +def add_graph(db_session, name, owner_email, is_public=0, default_layout_id=None): + graph = Graph(name=name, owner_email=owner_email, is_public=is_public, default_layout_id=default_layout_id) db_session.add(graph) return graph @@ -487,8 +487,8 @@ def get_graph_version_by_id(db_session, id): return db_session.query(GraphVersion).filter(GraphVersion.id == id).one_or_none() @with_session -def add_graph_version(db_session, graph_id, name, graph_json, owner_email, description=None): - graph_version = GraphVersion(name=name, graph_id=graph_id, graph_json=graph_json, owner_email=owner_email, description=description) +def add_graph_version(db_session, graph_id, name, graph_json, owner_email, style_json=None, description=None, is_default=0): + graph_version = GraphVersion(name=name, graph_id=graph_id, graph_json=graph_json, style_json=style_json, owner_email=owner_email, description=description) db_session.add(graph_version) return graph_version @@ -496,4 +496,10 @@ def add_graph_version(db_session, graph_id, name, graph_json, owner_email, descr def delete_graph_version(db_session, id): graph_version = db_session.query(GraphVersion).filter(GraphVersion.id == id).one_or_none() db_session.delete(graph_version) - return graph_version \ No newline at end of file + return graph_version + +@with_session +def set_default_version(db_session, graph_id, default_version_id): + graph = db_session.query(Graph).filter(Graph.id == graph_id).one_or_none() + setattr(graph, 'default_version_id', default_version_id) + return graph \ No newline at end of file From 50b9c7e2c2bf74bc958d41087bbc7a423242e23a Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Sat, 23 Jun 2018 02:45:18 +0200 Subject: [PATCH 09/21] Update models - migrate json from graph to graph_version table --- applications/graphs/models.py | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/applications/graphs/models.py b/applications/graphs/models.py index 2a47d491..2e9c73c6 100644 --- a/applications/graphs/models.py +++ b/applications/graphs/models.py @@ -34,8 +34,10 @@ 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") - graph_version = relationship("GraphVersion", foreign_keys="GraphVersion.graph_id", back_populates="graph", - cascade="all, delete-orphan") + default_version = relationship("GraphVersion", foreign_keys=[default_version_id], back_populates="default_version_graph", + uselist=False) + graph_versions = relationship("GraphVersion", foreign_keys="GraphVersion.graph_id", back_populates="graph", + passive_deletes=True) groups = association_proxy('shared_with_groups', 'group') tags = association_proxy('graph_tags', 'tag') @@ -61,6 +63,7 @@ def serialize(cls, **kwargs): 'is_public': cls.is_public, 'tags': [tag.name for tag in cls.tags], 'default_layout_id': cls.default_layout_id, + 'default_version_id': cls.default_version_id, 'created_at': cls.created_at.isoformat(), 'updated_at': cls.updated_at.isoformat() } @@ -69,11 +72,12 @@ def serialize(cls, **kwargs): 'id': cls.id, 'owner_email': cls.owner_email, 'name': cls.name, - 'graph_json': json.loads(cls.graph_json), - 'style_json': json.loads(cls.style_json), + 'graph_json': json.loads(cls.default_version.graph_json), + 'style_json': json.loads(cls.default_version.style_json), 'is_public': cls.is_public, 'tags': [tag.name for tag in cls.tags], 'default_layout_id': cls.default_layout_id, + 'default_version_id': cls.default_version_id, 'created_at': cls.created_at.isoformat(), 'updated_at': cls.updated_at.isoformat() } @@ -288,14 +292,29 @@ def __table_args__(cls): class GraphVersion(IDMixin, TimeStampMixin, Base): __tablename__ = 'graph_version' - name = Column(String, nullable=False, unique=True) - graph_id = Column(Integer, ForeignKey('graph.id', ondelete="CASCADE", onupdate="CASCADE"), primary_key=True) + name = Column(String, nullable=False) + graph_id = Column(Integer, ForeignKey('graph.id', ondelete="CASCADE", onupdate="CASCADE"), nullable=False) owner_email = Column(String, ForeignKey('user.email', ondelete="CASCADE", onupdate="CASCADE"), nullable=False) graph_json = Column(String, nullable=False) style_json = Column(String, nullable=False) description = Column(String, nullable=True) + graph = relationship("Graph", foreign_keys=[graph_id], back_populates="graph_versions", uselist=False) + default_version_graph = relationship("Graph", foreign_keys="Graph.default_version_id", + back_populates="default_version", cascade="all, delete-orphan", + uselist=False) + constraints = ( + UniqueConstraint('graph_id', 'name', name='_graph_version_uc_graph_id_name'), + UniqueConstraint('id', 'name', name='_graph_version_uc_id_name'), + ) - graph = relationship("Graph", foreign_keys=[graph_id], back_populates="graph_version", uselist=False) + indices = ( + Index('graph_version_idx_name', text("name gin_trgm_ops"), postgresql_using="gin"), + ) + + @declared_attr + def __table_args__(cls): + args = cls.constraints + cls.indices + return args def serialize(cls, **kwargs): if 'summary' in kwargs and kwargs['summary']: From 1cdcb570f2d3ec5b987eea7b897d4d3b822c1084 Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Sat, 23 Jun 2018 02:45:52 +0200 Subject: [PATCH 10/21] Added migration files for model changes --- ...bce_update_graph_table_migrate_json_to_.py | 41 ++++++++++++++++++ ...8f6ba9712df_create_graph_versions_table.py | 42 +++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 migration/versions/840db85c5bce_update_graph_table_migrate_json_to_.py create mode 100644 migration/versions/f8f6ba9712df_create_graph_versions_table.py diff --git a/migration/versions/840db85c5bce_update_graph_table_migrate_json_to_.py b/migration/versions/840db85c5bce_update_graph_table_migrate_json_to_.py new file mode 100644 index 00000000..4adf545e --- /dev/null +++ b/migration/versions/840db85c5bce_update_graph_table_migrate_json_to_.py @@ -0,0 +1,41 @@ +"""update_graph_table_migrate_json_to_graph_version + +Revision ID: 840db85c5bce +Revises: f8f6ba9712df +Create Date: 2018-06-23 02:27:29.434000 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '840db85c5bce' +down_revision = 'f8f6ba9712df' +branch_labels = None +depends_on = None + + +def upgrade(): + + # Drop columns which have been migrated to graph_version table + op.drop_column('graph', 'style_json') + op.drop_column('graph', 'graph_json') + + # Add new column for default_graph_version_id + op.add_column('graph', sa.Column('default_version_id', sa.Integer)) + + # Add new foreign key reference + op.execute('ALTER TABLE graph ADD CONSTRAINT graph_default_version_id_fkey FOREIGN KEY (default_version_id) REFERENCES "graph_version" (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE;') + +def downgrade(): + + # Add columns which have been migrated to graph_version table + op.add_column('graph', sa.Column('style_json', sa.String)) + op.add_column('graph', sa.Column('graph_json', sa.String)) + + # Remove foreign key reference + op.drop_constraint('graph_default_version_id_fkey', 'graph', type_='foreignkey') + + # Drop column for default_graph_version_id + op.drop_column('graph', 'default_version_id') diff --git a/migration/versions/f8f6ba9712df_create_graph_versions_table.py b/migration/versions/f8f6ba9712df_create_graph_versions_table.py new file mode 100644 index 00000000..e5cd5005 --- /dev/null +++ b/migration/versions/f8f6ba9712df_create_graph_versions_table.py @@ -0,0 +1,42 @@ +"""create_graph_versions_table + +Revision ID: f8f6ba9712df +Revises: bb9a45e2ee5e +Create Date: 2018-06-22 23:08:39.459000 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'f8f6ba9712df' +down_revision = 'bb9a45e2ee5e' +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table( + 'graph_version', + sa.Column('id', sa.Integer, primary_key=True), + sa.Column('name', sa.String, nullable=False, unique=True), + sa.Column('graph_id', sa.Integer, nullable=False), + sa.Column('owner_email', sa.String, nullable=False), + sa.Column('graph_json', sa.String, nullable=False), + sa.Column('style_json', sa.String, nullable=False), + sa.Column('description', sa.String, nullable=False), + ) + op.add_column('graph_version', sa.Column('created_at', sa.TIMESTAMP, server_default=sa.func.current_timestamp())) + op.add_column('graph_version', sa.Column('updated_at', sa.TIMESTAMP, server_default=sa.func.current_timestamp())) + # Create New Index + op.create_index('graph_version_idx_name', 'graph_version', ['name'], unique=True) + # Add new foreign key reference + op.execute('ALTER TABLE graph_version ADD CONSTRAINT graph_version_graph_id_fkey FOREIGN KEY (graph_id) REFERENCES "graph" (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE;') + op.execute('ALTER TABLE graph_version ADD CONSTRAINT graph_version_owner_email_fkey FOREIGN KEY (owner_email) REFERENCES "user" (email) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE;') + + + + +def downgrade(): + op.drop_table('graph_version') From e6b59cfd69a25fcecb3ca7a1a69a7b0ef8290f77 Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Sat, 23 Jun 2018 02:47:17 +0200 Subject: [PATCH 11/21] Added graph_version specific code to views --- applications/graphs/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/applications/graphs/views.py b/applications/graphs/views.py index f8ea2609..99eece9e 100644 --- a/applications/graphs/views.py +++ b/applications/graphs/views.py @@ -114,6 +114,7 @@ def graph_page(request, graph_id): else: return redirect(request.get_full_path() + '?user_layout=' + context["default_layout_id"]) + context['default_version_id'] = context['graph']['default_version_id'] context['graph_json_string'] = json.dumps(context['graph']['graph_json']) context['data'] = {k: json.dumps(v, encoding='ascii') for k,v in context['graph']['graph_json']['data'].items()} context['style_json_string'] = json.dumps(context['graph']['style_json']) From 9bd157301b45046c5583859720775b0085227844 Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Sat, 23 Jun 2018 02:48:34 +0200 Subject: [PATCH 12/21] Added graph_version specific code to controllers --- applications/graphs/controllers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/graphs/controllers.py b/applications/graphs/controllers.py index ab108c76..71383a8d 100644 --- a/applications/graphs/controllers.py +++ b/applications/graphs/controllers.py @@ -203,7 +203,7 @@ def add_graph(request, name=None, tags=None, is_public=None, graph_json=None, st new_graph.__setattr__('style_json', default_version.style_json) # Store the index of default version in the graph table (new_graph entry) new_graph.__setattr__('default_version_id',default_version.id) - db.set_default_version(request.db_session, new_graph.id, default_version.id) + #db.set_default_version(request.db_session, new_graph.id, default_version.id) # Add graph tags for tag in G.get_tags(): From aed358b4a1fdfdeb6ac0af8a66073b5dbb19d9ef Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Sat, 23 Jun 2018 02:50:39 +0200 Subject: [PATCH 13/21] dal changes - moved json from graph to graph_version --- applications/graphs/dal.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/applications/graphs/dal.py b/applications/graphs/dal.py index c848ddfe..2fe311d6 100644 --- a/applications/graphs/dal.py +++ b/applications/graphs/dal.py @@ -97,7 +97,12 @@ def update_graph(db_session, id, updated_graph): :return: Graph if id exists else None """ graph = db_session.query(Graph).filter(Graph.id == id).one_or_none() + version_id = update_graph if 'version_id' in updated_graph else graph.default_version_id + graph_version = db_session.query(GraphVersion).filter(GraphVersion.id == version_id) + for (key, value) in updated_graph.items(): + if key == 'graph_json' or key == 'style_json': + setattr(graph_version, key, value) setattr(graph, key, value) return graph @@ -122,7 +127,8 @@ def delete_graph(db_session, id): @with_session def get_graph_by_id(db_session, id): - return db_session.query(Graph).filter(Graph.id == id).one_or_none() + query = db_session.query(Graph).filter(Graph.id == id) + return query.one_or_none() @with_session @@ -130,7 +136,7 @@ def find_graphs(db_session, owner_email=None, group_ids=None, graph_ids=None, is edges=None, tags=None, limit=None, offset=None, order_by=desc(Graph.updated_at)): query = db_session.query(Graph) - query = query.options(defer("graph_json")).options(defer("style_json")) + #query = query.options(defer("graph_json")).options(defer("style_json")) graph_filter_group = [] if is_public is not None: @@ -145,6 +151,7 @@ def find_graphs(db_session, owner_email=None, group_ids=None, graph_ids=None, is query = query.filter(Graph.id.in_(graph_ids)) options_group = [] + options_group.append(joinedload('graph_versions')) if tags is not None and len(tags) > 0: options_group.append(joinedload('graph_tags')) if nodes is not None and len(nodes) > 0: @@ -158,6 +165,7 @@ 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))) + query = query.filter(GraphVersion.id==Graph.default_version_id) edges = [] if edges is None else edges nodes = [] if nodes is None else nodes From 9141f5747888c26bf122e2f66ff8976e37274dd0 Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Sat, 23 Jun 2018 02:52:13 +0200 Subject: [PATCH 14/21] UI changes for graph_version feature --- static/js/graphs_page.js | 20 ++++++++++++++------ templates/graph/graph_version_table.html | 2 -- templates/graph/index.html | 5 +++-- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/static/js/graphs_page.js b/static/js/graphs_page.js index 1f38ccc0..8dea5fd3 100644 --- a/static/js/graphs_page.js +++ b/static/js/graphs_page.js @@ -529,20 +529,23 @@ var graphPage = { cytoscapeGraph.export(graphPage.cyGraph, format, $('#GraphName').val()); }, selectGraphVersion: function (row) { - label = $("#version_selector_dropdown").find('a[row_id=' + row + ']').attr('data') + label = $("#version_selector_dropdown").find('a[row_id=' + row + ']').attr('data'); + $("#GraphVersionTable").find('tr').removeClass('success'); apis.version.getByID($('#GraphID').val(), row, successCallback = function (response) { graph_json = JSON.parse(response.graph_json); - graphPage.contructCytoscapeGraph(); - console.log("Success"); + //graphPage.contructCytoscapeGraph(); + graphPage.init(); + $("#version_selector_dropdown").attr('current_version_id', row); + $("#GraphVersionTable").find('span[row_id=' + row + ']').parent().parent().addClass('success'); + //console.log("Success"); }, errorCallback = function (xhr, status, errorThrown) { // This method is called when error occurs while deleting group_to_graph relationship. $.notify({message: "You are not authorized to access this Version."}, {type: 'danger'}); }); $('#version_selector > bold').text(label); - console.log("abc"); }, applyAutoLayout: function (layout_id) { graphPage.applyLayout(cytoscapeGraph.getAutomaticLayoutSettings(layout_id)); @@ -714,7 +717,9 @@ var graphPage = { onShareGraphWithPublicBtn: function (e, graph_id) { apis.graphs.update(graph_id, { - 'is_public': 1 + 'is_public': 1, + 'version_id': parseInt( $("#version_selector_dropdown").attr('current_version_id')), + 'style_json': style_json //JSON.stringify(style_json) }, successCallback = function (response) { // This method is called when group_to_graph relationship is successfully deleted. @@ -1176,6 +1181,9 @@ var graphPage = { successCallback = function (response) { // This method is called when nodes are successfully fetched. params.success(response); + default_version = response.versions.find(x => x.id === default_version_id); + default_version ? $('#current_version_label').text(default_version.name) : $('#current_version_label').text('Default'); + $("#GraphVersionTable").find('span[row_id=' + default_version_id + ']').parent().parent().addClass('success'); }, errorCallback = function () { // This method is called when error occurs while fetching nodes. @@ -1185,7 +1193,7 @@ var graphPage = { }, versionFormatter: function (value, row, index) { $("#version_selector_dropdown").append('
  • ' + value + '
  • ') - return (''+ value +'') + return (''+ value +'') } }, layoutsTable: { diff --git a/templates/graph/graph_version_table.html b/templates/graph/graph_version_table.html index 2e42c9cd..44b89cb7 100644 --- a/templates/graph/graph_version_table.html +++ b/templates/graph/graph_version_table.html @@ -1,7 +1,5 @@
    - Currently Viewing : -
    Default Version :
    diff --git a/templates/graph/index.html b/templates/graph/index.html index 15db3677..4bea8a72 100644 --- a/templates/graph/index.html +++ b/templates/graph/index.html @@ -6,6 +6,7 @@ var graph_json = {{ graph_json_string|safe }}; var style_json = {{ style_json_string|safe }}; var default_layout_id = {% if default_layout_id %}{{ default_layout_id|safe }}{% else %}null{% endif %}; + var default_version_id = {% if default_version_id %}{{ default_version_id|safe }}{% else %}null{% endif %}; {% endif %} @@ -228,9 +229,9 @@

    -
    From 5ba4276014360986a8dd8f7af000161b27a31eb3 Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Sat, 30 Jun 2018 03:39:36 +0200 Subject: [PATCH 15/21] Added Python Documentation for Version Feature --- applications/graphs/controllers.py | 41 +++++++++++--- applications/graphs/dal.py | 85 +++++++++++++++++++++++------- 2 files changed, 99 insertions(+), 27 deletions(-) diff --git a/applications/graphs/controllers.py b/applications/graphs/controllers.py index 71383a8d..2805309e 100644 --- a/applications/graphs/controllers.py +++ b/applications/graphs/controllers.py @@ -194,16 +194,14 @@ def add_graph(request, name=None, tags=None, is_public=None, graph_json=None, st is_public=is_public, default_layout_id=default_layout_id) default_version = db.add_graph_version(request.db_session, name=name, description='Default Version', owner_email=owner_email, graph_json=json.dumps(G.get_graph_json()), - style_json=json.dumps(G.get_style_json()), graph_id=new_graph.id) + style_json=json.dumps(G.get_style_json()), graph_id=new_graph.id, is_default = True) + # Add default_version to new_graph + new_graph.__setattr__('default_version', default_version) # Add graph_json to new_graph new_graph.__setattr__('graph_json', default_version.graph_json) - # Add style_json to new_graph new_graph.__setattr__('style_json', default_version.style_json) - # Store the index of default version in the graph table (new_graph entry) - new_graph.__setattr__('default_version_id',default_version.id) - #db.set_default_version(request.db_session, new_graph.id, default_version.id) # Add graph tags for tag in G.get_tags(): @@ -600,6 +598,37 @@ def delete_edge_by_id(request, edge_id): return def search_graph_versions(request, graph_id=None, names=None, limit=20, offset=0, order='desc', sort='name'): + """ + Parameters + ---------- + request : object + HTTP GET Request. + graph_id : string + Unique ID of the graph. + names : list of strings + Search for graphs with given list of names. In order to search for graphs with given name as a substring, wrap the name with percentage symbol. For example, %xyz% will search for all graphs with xyz in their name. + limit : integer + Number of entities to return. Default value is 20. + offset : integer + Offset the list of returned entities by this number. Default value is 0. + order : string + Defines the column sort order, can only be 'asc' or 'desc'. + sort : string + Defines which column will be sorted. + + Returns + ------- + total : integer + Number of groups matching the request. + graph_versions : List of Graph Versions. + List of Graph Version Objects with given limit and offset. + + Raises + ------ + + Notes + ------ + """ if sort == 'name': sort_attr = db.GraphVersion.name elif sort == 'update_at': @@ -612,8 +641,6 @@ def search_graph_versions(request, graph_id=None, names=None, limit=20, offset=0 else: orber_by = db.asc(sort_attr) - ## TODO: create a util function to relpace the code parse sort and order parameters. This code is repeated again and again. - total, graph_versions = db.find_graph_versions(request.db_session, names=names, graph_id=graph_id, diff --git a/applications/graphs/dal.py b/applications/graphs/dal.py index 2fe311d6..a3d8eb18 100644 --- a/applications/graphs/dal.py +++ b/applications/graphs/dal.py @@ -18,8 +18,8 @@ def get_edges(db_session, edges, order=desc(Edge.updated_at), page=0, page_size= @with_session def get_graphs_by_edges_and_nodes_and_names(db_session, group_ids=None, names=None, nodes=None, edges=None, tags=None, - order=desc(Graph.updated_at), page=0, page_size=10, partial_matching=False, - owner_email=None, is_public=None): + order=desc(Graph.updated_at), page=0, page_size=10, partial_matching=False, + owner_email=None, is_public=None): query = db_session.query(Graph) edges = [] if edges is None else edges @@ -47,7 +47,7 @@ def get_graphs_by_edges_and_nodes_and_names(db_session, group_ids=None, names=No nodes_filter_group = [Node.label.ilike(node) for node in nodes] nodes_filter_group.extend([Node.name.ilike(node) for node in nodes]) edges_filter_group = [and_(Edge.head_node.has(Node.name.ilike(u)), Edge.tail_node.has(Node.name.ilike(v))) for u, v - in edges] + in edges] edges_filter_group.extend( [and_(Edge.tail_node.has(Node.name.ilike(u)), Edge.head_node.has(Node.name.ilike(v))) for u, v in edges]) edges_filter_group.extend( @@ -82,7 +82,7 @@ def get_graphs_by_edges_and_nodes_and_names(db_session, group_ids=None, names=No @with_session def add_graph(db_session, name, owner_email, is_public=0, default_layout_id=None): graph = Graph(name=name, owner_email=owner_email, is_public=is_public, - default_layout_id=default_layout_id) + default_layout_id=default_layout_id) db_session.add(graph) return graph @@ -128,13 +128,14 @@ def delete_graph(db_session, id): @with_session def get_graph_by_id(db_session, id): query = db_session.query(Graph).filter(Graph.id == id) + query.options(joinedload('graph_versions')).filter(GraphVersion.id==Graph.default_version_id) return query.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, - edges=None, - tags=None, limit=None, offset=None, order_by=desc(Graph.updated_at)): + edges=None, + tags=None, limit=None, offset=None, order_by=desc(Graph.updated_at)): query = db_session.query(Graph) #query = query.options(defer("graph_json")).options(defer("style_json")) @@ -225,10 +226,10 @@ def add_edge(db_session, graph_id, head_node_id, tail_node_id, name, is_directed tail_node = get_node_by_id(db_session, tail_node_id) edge = Edge(name=name, graph_id=graph_id, - head_node_id=head_node_id, tail_node_id=tail_node_id, - head_node_name=head_node.name, tail_node_name=tail_node.name, - head_node_label=head_node.label, tail_node_label=tail_node.label, - is_directed=is_directed) + head_node_id=head_node_id, tail_node_id=tail_node_id, + head_node_name=head_node.name, tail_node_name=tail_node.name, + head_node_label=head_node.label, tail_node_label=tail_node.label, + is_directed=is_directed) db_session.add(edge) return edge @@ -322,7 +323,7 @@ def delete_graph_to_group(db_session, group_id, graph_id): @with_session def find_layouts(db_session, owner_email=None, is_shared=None, name=None, graph_id=None, limit=None, offset=None, - order_by=desc(Layout.updated_at)): + order_by=desc(Layout.updated_at)): query = db_session.query(Layout) if order_by is not None: @@ -372,7 +373,7 @@ def add_layout(db_session, owner_email, name, graph_id, is_shared, style_json, p """ layout = Layout(owner_email=owner_email, name=name, graph_id=graph_id, is_shared=is_shared, style_json=style_json, - positions_json=positions_json) + positions_json=positions_json) db_session.add(layout) return layout @@ -413,7 +414,7 @@ def delete_layout(db_session, id): @with_session def find_nodes(db_session, labels=None, names=None, graph_id=None, limit=None, offset=None, - order_by=desc(Node.updated_at)): + order_by=desc(Node.updated_at)): query = db_session.query(Node) if graph_id is not None: @@ -438,7 +439,7 @@ def find_nodes(db_session, labels=None, names=None, graph_id=None, limit=None, o @with_session def find_edges(db_session, is_directed=None, names=None, edges=None, graph_id=None, limit=None, offset=None, - order_by=desc(Node.updated_at)): + order_by=desc(Node.updated_at)): query = db_session.query(Edge) if graph_id is not None: @@ -469,7 +470,18 @@ def find_edges(db_session, is_directed=None, names=None, edges=None, graph_id=No @with_session def find_graph_versions(db_session, names=None, graph_id=None, limit=None, offset=None, - order_by=desc(GraphVersion.updated_at)): + order_by=desc(GraphVersion.updated_at)): + """ + Find graph version by Graph ID. + :param db_session: Database session. + :param graph_id: Unique ID of the graph + :param name - Name of the graph version + :param limit - Number of entities to return. Default value is 20. + :param offset - Offset the list of returned entities by this number. Default value is 0. + :param order_by - Defines which column the results will be sorted by. + :return: Total, Graph Versions + """ + query = db_session.query(GraphVersion) if graph_id is not None: @@ -492,22 +504,55 @@ def find_graph_versions(db_session, names=None, graph_id=None, limit=None, offse @with_session def get_graph_version_by_id(db_session, id): + """ + Get graph version by ID. + :param db_session: Database session. + :param id: Unique ID of the graph version + :return: Graph Version if id exists else None + """ return db_session.query(GraphVersion).filter(GraphVersion.id == id).one_or_none() @with_session -def add_graph_version(db_session, graph_id, name, graph_json, owner_email, style_json=None, description=None, is_default=0): +def add_graph_version(db_session, graph_id, name, graph_json, owner_email, style_json=None, description=None, is_default=None): + """ + Get graph version by ID. + :param db_session: Database session. + :param graph_id: Unique ID of the graph + :param name - Name of the graph version + :param graph_json - positions_json of the layouts. + :param owner_email - ID of user who owns the graph + :param style_json - style_json of the layouts. + :param description - ID of the graph the layout belongs to. + :param is_default - Set this graph_version as the default version of the graph + :return: Graph Version if id exists else None + """ graph_version = GraphVersion(name=name, graph_id=graph_id, graph_json=graph_json, style_json=style_json, owner_email=owner_email, description=description) + if is_default is not None: + set_default_version(db_session, graph_id, graph_version.id) db_session.add(graph_version) return graph_version @with_session def delete_graph_version(db_session, id): - graph_version = db_session.query(GraphVersion).filter(GraphVersion.id == id).one_or_none() - db_session.delete(graph_version) - return graph_version + """ + Delete graph version. + :param db_session: Database session. + :param id: Unique ID of the graph version + :return: None + """ + graph_version = db_session.query(GraphVersion).filter(GraphVersion.id == id).one_or_none() + db_session.delete(graph_version) + return @with_session def set_default_version(db_session, graph_id, default_version_id): + """ + Set the default graph version. + :param db_session: Database session. + :param graph_id: Unique ID of the graph + :param default_version_id: Unique ID of the graph version + :return: None + """ graph = db_session.query(Graph).filter(Graph.id == graph_id).one_or_none() setattr(graph, 'default_version_id', default_version_id) - return graph \ No newline at end of file + return From 3e1dffcbf2941f3b281ea1c5a740e06b16199a7e Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Sat, 30 Jun 2018 03:41:31 +0200 Subject: [PATCH 16/21] Fix indentations and remove unnecessary commented code --- applications/graphs/models.py | 4 ++-- applications/graphs/views.py | 20 ++++++++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/applications/graphs/models.py b/applications/graphs/models.py index 2e9c73c6..ad9aeae8 100644 --- a/applications/graphs/models.py +++ b/applications/graphs/models.py @@ -300,8 +300,8 @@ class GraphVersion(IDMixin, TimeStampMixin, Base): description = Column(String, nullable=True) graph = relationship("Graph", foreign_keys=[graph_id], back_populates="graph_versions", uselist=False) default_version_graph = relationship("Graph", foreign_keys="Graph.default_version_id", - back_populates="default_version", cascade="all, delete-orphan", - uselist=False) + back_populates="default_version", cascade="all, delete-orphan", + uselist=False) constraints = ( UniqueConstraint('graph_id', 'name', name='_graph_version_uc_graph_id_name'), UniqueConstraint('id', 'name', name='_graph_version_uc_id_name'), diff --git a/applications/graphs/views.py b/applications/graphs/views.py index 99eece9e..416dd235 100644 --- a/applications/graphs/views.py +++ b/applications/graphs/views.py @@ -1604,7 +1604,7 @@ def _graph_versions_api(request, graph_id, version_id=None): else: raise BadRequest(request) -def _get_graph_versions(request, graph_id, query={}): +def _get_graph_versions(request, graph_id, query=dict()): """ Query Parameters @@ -1627,6 +1627,8 @@ def _get_graph_versions(request, graph_id, query={}): ---------- request : object HTTP GET Request. + graph_id : string + Unique ID of the graph. Returns ------- @@ -1637,6 +1639,8 @@ def _get_graph_versions(request, graph_id, query={}): 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 ------ @@ -1649,6 +1653,15 @@ def _get_graph_versions(request, graph_id, query={}): 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('is_public', None) != '1': + if get_request_user(request) != query.get('member_email', None) \ + and get_request_user(request) != query.get('owner_email', None): + raise BadRequest(request, error_code=ErrorCodes.Validation.NotAllowedGraphAccess, + args=query.get('owner_email', None)) + total, versions_list = graphs.search_graph_versions(request, graph_id=graph_id, names=query.getlist('names[]', None), @@ -1718,7 +1731,7 @@ def _add_graph_version(request, graph_id, graph_version={}): ------ """ - # + return utils.serializer(graphs.add_graph_version(request, name=graph_version.get('name', None), @@ -1749,6 +1762,5 @@ def _delete_graph_version(request, graph_id, graph_version_id): ------ """ - #authorization.validate(request, permission='GRAPH_UPDATE', graph_id=graph_id) - + authorization.validate(request, permission='GRAPH_UPDATE', graph_id=graph_id) graphs.delete_graph_version_by_id(request, graph_version_id) \ No newline at end of file From 5f7e22471f6e7b3d6bc6eee25dc35278f7e64036 Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Sun, 1 Jul 2018 09:30:52 +0200 Subject: [PATCH 17/21] Added Tests for GraphSpace models --- applications/graphs/tests.py | 310 ++++++++++++++++++++++++++--------- graphspace/database.py | 5 + 2 files changed, 241 insertions(+), 74 deletions(-) diff --git a/applications/graphs/tests.py b/applications/graphs/tests.py index f05abfe7..e4330cd2 100644 --- a/applications/graphs/tests.py +++ b/applications/graphs/tests.py @@ -37,7 +37,7 @@ def test_crud_operation(self): # Create self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) self.session.commit() graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.assertEqual(graph1.name, 'graph1') @@ -61,23 +61,23 @@ def test_crud_operation(self): def test_owner_email_fkey_constraint(self): with self.assertRaises(IntegrityError): - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) self.session.commit() def test_default_layout_id_fkey_constraint(self): with self.assertRaises(IntegrityError): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0, default_layout_id=1)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0, default_layout_id=1)) self.session.commit() def test_name_owner_email_uc_constraint(self): with self.assertRaises(IntegrityError): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) self.session.commit() - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=1)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=1)) self.session.commit() def test_cascade_on_user_delete(self): @@ -85,7 +85,7 @@ def test_cascade_on_user_delete(self): On deleting user row, the corresponding row in graph table should also be deleted. """ self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) self.session.commit() owner = self.session.query(User).filter(User.email == 'owner@example.com').one_or_none() @@ -101,7 +101,7 @@ def test_cascade_on_user_update(self): On deleting user row, the corresponding row in graph table should also be updated """ self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) self.session.commit() graph1 = self.session.query(Graph).filter(and_(Graph.owner_email == 'owner@example.com', Graph.name == 'graph1')).one_or_none() @@ -115,7 +115,7 @@ def test_cascade_on_user_update(self): def test_owner_relationship(self): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) self.session.commit() graph1 = self.session.query(Graph).filter(and_(Graph.owner_email == 'owner@example.com', Graph.name == 'graph1')).one_or_none() @@ -125,7 +125,7 @@ def test_owner_relationship(self): def test_nodes_relationship(self): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(Node(graph_id=graph1.id, name='source', label='source1')) self.session.commit() @@ -140,7 +140,7 @@ def test_nodes_relationship(self): def test_edges_relationship(self): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(Node(graph_id=graph1.id, name='source', label='source')) self.session.add(Node(graph_id=graph1.id, name='target', label='target')) @@ -154,9 +154,9 @@ def test_edges_relationship(self): def test_layouts_relationship(self): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() - self.session.add(Layout(graph_id=graph1.id, name='layout1', owner_email='owner@example.com', json='{}', is_public=0, is_shared=0, original_json='{}')) + self.session.add(Layout(graph_id=graph1.id, name='layout1', owner_email='owner@example.com', is_shared=0, original_json='{}')) self.session.commit() layout1 = self.session.query(Layout).filter(Layout.name == 'layout1').one_or_none() @@ -165,9 +165,9 @@ def test_layouts_relationship(self): def test_default_layout_relationship(self): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() - self.session.add(Layout(graph_id=graph1.id, name='layout1', owner_email='owner@example.com', json='{}', is_public=0, is_shared=0, original_json='{}')) + self.session.add(Layout(graph_id=graph1.id, name='layout1', owner_email='owner@example.com', is_shared=0, original_json='{}', positions_json='{}', style_json='{}')) self.session.commit() layout1 = self.session.query(Layout).filter(Layout.name == 'layout1').one_or_none() @@ -188,7 +188,7 @@ def test_groups_relationship(self): Group.owner_email == 'owner@example.com', Group.name == 'group1')).one_or_none() self.session.add(GroupToUser(user_id=member.id, group_id=group1.id)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(GroupToGraph(graph_id=graph1.id, group_id=group1.id)) self.session.commit() @@ -197,7 +197,7 @@ def test_groups_relationship(self): def test_tags_relationship(self): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(GraphTag(name='tag1')) @@ -235,7 +235,7 @@ def test_crud_operation(self): # Create self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(Node(graph_id=graph1.id, name='source', label='source')) self.session.add(Node(graph_id=graph1.id, name='target', label='target')) @@ -272,7 +272,7 @@ def test_graph_id_name_uc_constraint(self): with self.assertRaises(IntegrityError): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(Node(graph_id=graph1.id, name='source', label='source1')) self.session.add(Node(graph_id=graph1.id, name='source', label='source2')) @@ -283,7 +283,7 @@ def test_cascade_on_user_delete(self): On deleting user row, the corresponding row in node table should also be deleted. """ self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(Node(graph_id=graph1.id, name='source', label='source1')) self.session.commit() @@ -302,7 +302,7 @@ def test_cascade_on_graph_delete(self): On deleting graph row, the corresponding row in node table should also be deleted. """ self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(Node(graph_id=graph1.id, name='source', label='source1')) self.session.commit() @@ -317,7 +317,7 @@ def test_cascade_on_graph_delete(self): def test_graph_relationship(self): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(Node(graph_id=graph1.id, name='source', label='source1')) self.session.commit() @@ -329,7 +329,7 @@ def test_graph_relationship(self): def test_source_edges_relationship(self): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(Node(graph_id=graph1.id, name='source', label='source')) self.session.add(Node(graph_id=graph1.id, name='target', label='target')) @@ -345,7 +345,7 @@ def test_source_edges_relationship(self): def test_target_edges_relationship(self): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(Node(graph_id=graph1.id, name='source', label='source')) self.session.add(Node(graph_id=graph1.id, name='target', label='target')) @@ -388,7 +388,7 @@ def test_crud_operation(self): # Create self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(Node(graph_id=graph1.id, name='source', label='source')) self.session.add(Node(graph_id=graph1.id, name='target', label='target')) @@ -419,7 +419,7 @@ def test_graph_id_fkey_constraint(self): with self.assertRaises(IntegrityError): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(Node(graph_id=graph1.id, name='source', label='source')) self.session.add(Node(graph_id=graph1.id, name='target', label='target')) @@ -435,7 +435,7 @@ def test_head_node_id_fkey_constraint(self): with self.assertRaises(IntegrityError): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(Node(graph_id=graph1.id, name='source', label='source')) self.session.add(Node(graph_id=graph1.id, name='target', label='target')) @@ -451,7 +451,7 @@ def test_tail_node_id_fkey_constraint(self): with self.assertRaises(IntegrityError): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(Node(graph_id=graph1.id, name='source', label='source')) self.session.add(Node(graph_id=graph1.id, name='target', label='target')) @@ -467,7 +467,7 @@ def test_graph_id_name_uc_constraint(self): with self.assertRaises(IntegrityError): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(Node(graph_id=graph1.id, name='source', label='source')) self.session.add(Node(graph_id=graph1.id, name='target', label='target')) @@ -481,7 +481,7 @@ def test_graph_id_head_node_id_tail_node_id_uc_constraint(self): with self.assertRaises(IntegrityError): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(Node(graph_id=graph1.id, name='source', label='source')) self.session.add(Node(graph_id=graph1.id, name='target', label='target')) @@ -496,7 +496,7 @@ def test_cascade_on_user_delete(self): On deleting user row, the corresponding row in edge table should also be deleted. """ self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(Node(graph_id=graph1.id, name='source', label='source')) self.session.add(Node(graph_id=graph1.id, name='target', label='target')) @@ -519,7 +519,7 @@ def test_cascade_on_graph_delete(self): On deleting graph row, the corresponding row in edge table should also be deleted. """ self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(Node(graph_id=graph1.id, name='source', label='source')) self.session.add(Node(graph_id=graph1.id, name='target', label='target')) @@ -541,7 +541,7 @@ def test_cascade_on_node_delete(self): On deleting node row, the corresponding row in edge table should also be deleted. """ self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(Node(graph_id=graph1.id, name='source', label='source')) self.session.add(Node(graph_id=graph1.id, name='target', label='target')) @@ -560,7 +560,7 @@ def test_cascade_on_node_delete(self): def test_graph_relationship(self): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(Node(graph_id=graph1.id, name='source', label='source')) self.session.add(Node(graph_id=graph1.id, name='target', label='target')) @@ -574,7 +574,7 @@ def test_graph_relationship(self): def test_head_node_relationship(self): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(Node(graph_id=graph1.id, name='source', label='source')) self.session.add(Node(graph_id=graph1.id, name='target', label='target')) @@ -591,7 +591,7 @@ def test_head_node_relationship(self): def test_tail_node_relationship(self): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(Node(graph_id=graph1.id, name='source', label='source')) self.session.add(Node(graph_id=graph1.id, name='target', label='target')) @@ -644,7 +644,7 @@ def test_add_delete_operation(self): self.session.add(GroupToUser(user_id=member.id, group_id=group1.id)) group2user1 = self.session.query(GroupToUser).filter(and_(GroupToUser.user_id == member.id, GroupToUser.group_id == group1.id)).one_or_none() - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(GroupToGraph(graph_id=graph1.id, group_id=group1.id)) @@ -676,7 +676,7 @@ def test_graph_id_fkey_constraint(self): self.session.add(GroupToUser(user_id=member.id, group_id=group1.id)) group2user1 = self.session.query(GroupToUser).filter(and_(GroupToUser.user_id == member.id, GroupToUser.group_id == group1.id)).one_or_none() - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.delete(graph1) self.session.commit() @@ -700,7 +700,7 @@ def test_group_id_fkey_constraint(self): self.session.add(GroupToUser(user_id=member.id, group_id=group1.id)) group2user1 = self.session.query(GroupToUser).filter(and_(GroupToUser.user_id == member.id, GroupToUser.group_id == group1.id)).one_or_none() - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.delete(group1) self.session.commit() @@ -723,7 +723,7 @@ def test_graph_id_group_id_unique_constraint(self): self.session.add(GroupToUser(user_id=member.id, group_id=group1.id)) group2user1 = self.session.query(GroupToUser).filter(and_(GroupToUser.user_id == member.id, GroupToUser.group_id == group1.id)).one_or_none() - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(GroupToGraph(graph_id=graph1.id, group_id=group1.id)) self.session.commit() @@ -745,7 +745,7 @@ def test_cascade_on_group_member_delete(self): self.session.add(GroupToUser(user_id=member.id, group_id=group1.id)) group2user1 = self.session.query(GroupToUser).filter(and_(GroupToUser.user_id == member.id, GroupToUser.group_id == group1.id)).one_or_none() - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(GroupToGraph(graph_id=graph1.id, group_id=group1.id)) self.session.commit() @@ -777,7 +777,7 @@ def test_cascade_on_group_owner_delete(self): self.session.add(GroupToUser(user_id=member.id, group_id=group1.id)) group2user1 = self.session.query(GroupToUser).filter(and_(GroupToUser.user_id == member.id, GroupToUser.group_id == group1.id)).one_or_none() - self.session.add(Graph(name='graph1', owner_email='member@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='member@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'member@example.com').one_or_none() self.session.add(GroupToGraph(graph_id=graph1.id, group_id=group1.id)) self.session.commit() @@ -808,7 +808,7 @@ def test_cascade_on_graph_owner_delete(self): self.session.add(GroupToUser(user_id=member.id, group_id=group1.id)) self.session.add(GroupToUser(user_id=graph_owner.id, group_id=group1.id)) - self.session.add(Graph(name='graph1', owner_email='graph_owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='graph_owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'graph_owner@example.com').one_or_none() self.session.add(GroupToGraph(graph_id=graph1.id, group_id=group1.id)) self.session.commit() @@ -836,7 +836,7 @@ def test_cascade_on_graph_delete(self): self.session.add(GroupToUser(user_id=member.id, group_id=group1.id)) group2user1 = self.session.query(GroupToUser).filter(and_(GroupToUser.user_id == member.id, GroupToUser.group_id == group1.id)).one_or_none() - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(GroupToGraph(graph_id=graph1.id, group_id=group1.id)) self.session.commit() @@ -865,7 +865,7 @@ def test_cascade_on_group_delete(self): self.session.add(GroupToUser(user_id=member.id, group_id=group1.id)) group2user1 = self.session.query(GroupToUser).filter(and_(GroupToUser.user_id == member.id, GroupToUser.group_id == group1.id)).one_or_none() - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(GroupToGraph(graph_id=graph1.id, group_id=group1.id)) self.session.commit() @@ -891,7 +891,7 @@ def test_graph_relationship(self): Group.owner_email == 'owner@example.com', Group.name == 'group1')).one_or_none() self.session.add(GroupToUser(user_id=member.id, group_id=group1.id)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(GroupToGraph(graph_id=graph1.id, group_id=group1.id)) self.session.commit() @@ -909,7 +909,7 @@ def test_group_relationship(self): Group.owner_email == 'owner@example.com', Group.name == 'group1')).one_or_none() self.session.add(GroupToUser(user_id=member.id, group_id=group1.id)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(GroupToGraph(graph_id=graph1.id, group_id=group1.id)) self.session.commit() @@ -977,7 +977,7 @@ def test_name_uc_constraint(self): def test_graphs_relationship(self): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(GraphTag(name='tag1')) @@ -1016,7 +1016,7 @@ def test_add_delete_operation(self): # Create self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(GraphTag(name='tag1')) @@ -1040,7 +1040,7 @@ def test_graph_id_fkey_constraint(self): with self.assertRaises(IntegrityError): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(GraphTag(name='tag1')) @@ -1058,7 +1058,7 @@ def test_tag_id_fkey_constraint(self): with self.assertRaises(IntegrityError): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(GraphTag(name='tag1')) @@ -1074,7 +1074,7 @@ def test_graph_id_tag_id_unique_constraint(self): with self.assertRaises(IntegrityError): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(GraphTag(name='tag1')) @@ -1093,7 +1093,7 @@ def test_cascade_on_graph_delete(self): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(GraphTag(name='tag1')) @@ -1117,7 +1117,7 @@ def test_cascade_on_graph_delete(self): def test_cascade_on_tag_delete(self): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(GraphTag(name='tag1')) @@ -1142,7 +1142,7 @@ def test_cascade_on_graph_owner_delete(self): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) owner = self.session.query(User).filter(User.email == 'owner@example.com').one_or_none() - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(GraphTag(name='tag1')) @@ -1166,7 +1166,7 @@ def test_cascade_on_graph_owner_delete(self): def test_graph_relationship(self): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(GraphTag(name='tag1')) @@ -1179,7 +1179,7 @@ def test_graph_relationship(self): def test_tag_relationship(self): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.add(GraphTag(name='tag1')) @@ -1219,9 +1219,9 @@ def test_crud_operation(self): # Create self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() - self.session.add(Layout(graph_id=graph1.id, name='layout1', owner_email='owner@example.com', json='{}', is_public=0, is_shared=0, original_json='{}')) + self.session.add(Layout(graph_id=graph1.id, name='layout1', owner_email='owner@example.com', is_shared=0, original_json='{}', positions_json='{}', style_json='{}')) self.session.commit() layout1 = self.session.query(Layout).filter(Layout.owner_email == 'owner@example.com').one_or_none() @@ -1247,36 +1247,36 @@ def test_graph_id_fkey_constraint(self): with self.assertRaises(IntegrityError): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.delete(graph1) self.session.commit() - self.session.add(Layout(graph_id=graph1.id, name='layout1', owner_email='owner@example.com', json='{}', is_public=0, is_shared=0, original_json='{}')) + self.session.add(Layout(graph_id=graph1.id, name='layout1', owner_email='owner@example.com', is_shared=0, original_json='{}', positions_json='{}', style_json='{}')) self.session.commit() def test_owner_email_fkey_constraint(self): with self.assertRaises(IntegrityError): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() owner = self.session.query(User).filter(User.email == 'owner@example.com').one_or_none() self.session.delete(owner) self.session.commit() - self.session.add(Layout(graph_id=graph1.id, name='layout1', owner_email='owner@example.com', json='{}', is_public=0, is_shared=0, original_json='{}')) + self.session.add(Layout(graph_id=graph1.id, name='layout1', owner_email='owner@example.com', is_shared=0, original_json='{}', positions_json='{}', style_json='{}')) self.session.commit() def test_name_graph_id_owner_email_uc_constraint(self): with self.assertRaises(IntegrityError): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() self.session.commit() - self.session.add(Layout(graph_id=graph1.id, name='layout1', owner_email='owner@example.com', json='{}', is_public=0, is_shared=0, original_json='{}')) + self.session.add(Layout(graph_id=graph1.id, name='layout1', owner_email='owner@example.com', is_shared=0, original_json='{}', positions_json='{}', style_json='{}')) self.session.add(Layout(graph_id=graph1.id, name='layout1', owner_email='owner@example.com', json='{"a": "a"}', is_public=1, is_shared=1, original_json='{"a": "a"}')) self.session.commit() @@ -1285,9 +1285,9 @@ def test_cascade_on_user_delete(self): On deleting user row, the corresponding row in node table should also be deleted. """ self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() - self.session.add(Layout(graph_id=graph1.id, name='layout1', owner_email='owner@example.com', json='{}', is_public=0, is_shared=0, original_json='{}')) + self.session.add(Layout(graph_id=graph1.id, name='layout1', owner_email='owner@example.com', is_shared=0, original_json='{}', positions_json='{}', style_json='{}')) self.session.commit() owner = self.session.query(User).filter(User.email == 'owner@example.com').one_or_none() @@ -1304,9 +1304,9 @@ def test_cascade_on_graph_delete(self): On deleting graph row, the corresponding row in node table should also be deleted. """ self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() - self.session.add(Layout(graph_id=graph1.id, name='layout1', owner_email='owner@example.com', json='{}', is_public=0, is_shared=0, original_json='{}')) + self.session.add(Layout(graph_id=graph1.id, name='layout1', owner_email='owner@example.com', is_shared=0, original_json='{}', positions_json='{}', style_json='{}')) self.session.commit() self.session.delete(graph1) @@ -1319,9 +1319,9 @@ def test_cascade_on_graph_delete(self): def test_graph_relationship(self): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() - self.session.add(Layout(graph_id=graph1.id, name='layout1', owner_email='owner@example.com', json='{}', is_public=0, is_shared=0, original_json='{}')) + self.session.add(Layout(graph_id=graph1.id, name='layout1', owner_email='owner@example.com', is_shared=0, original_json='{}', positions_json='{}', style_json='{}')) self.session.commit() layout1 = self.session.query(Layout).filter(Layout.name == 'layout1').one_or_none() @@ -1330,9 +1330,9 @@ def test_graph_relationship(self): def test_owner_relationship(self): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() - self.session.add(Layout(graph_id=graph1.id, name='layout1', owner_email='owner@example.com', json='{}', is_public=0, is_shared=0, original_json='{}')) + self.session.add(Layout(graph_id=graph1.id, name='layout1', owner_email='owner@example.com', is_shared=0, original_json='{}', positions_json='{}', style_json='{}')) self.session.commit() owner = self.session.query(User).filter(User.email == 'owner@example.com').one_or_none() @@ -1342,9 +1342,9 @@ def test_owner_relationship(self): def test_default_layout_graph_relationship(self): self.session.add(User(email='owner@example.com', password="password", is_admin=0)) - self.session.add(Graph(name='graph1', owner_email='owner@example.com', json='{}', is_public=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() - self.session.add(Layout(graph_id=graph1.id, name='layout1', owner_email='owner@example.com', json='{}', is_public=0, is_shared=0, original_json='{}')) + self.session.add(Layout(graph_id=graph1.id, name='layout1', owner_email='owner@example.com', is_shared=0, original_json='{}', positions_json='{}', style_json='{}')) self.session.commit() layout1 = self.session.query(Layout).filter(Layout.name == 'layout1').one_or_none() @@ -1356,6 +1356,168 @@ def test_default_layout_graph_relationship(self): self.assertEqual(layout1.default_layout_graph.id, graph1.id) +class GraphVersionModelTestCase(TestCase): + def setUp(self): + db = Database() + # connect to the database + self.connection = db.engine.connect() + # begin a non-ORM transaction + self.trans = self.connection.begin() + # bind an individual Session to the connection + self.session = Session(bind=self.connection) + + def tearDown(self): + self.session.close() + + # rollback - everything that happened with the + # Session above (including calls to commit()) + # is rolled back. + self.trans.rollback() + + # return connection to the Engine + self.connection.close() + + def test_crud_operation(self): + """ + Basic CRUD (Create, Retrieve, Update, Delete) operation should work properly. + """ + + # Create + self.session.add(User(email='owner@example.com', password="password", is_admin=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) + self.session.commit() + graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() + self.assertEqual(graph1.name, 'graph1') + self.session.add(GraphVersion(name='graphversion1', owner_email='owner@example.com', graph_id=graph1.id, + graph_json='{}', style_json='{}', description='{}')) + self.session.commit() + graphversion1 = self.session.query(GraphVersion).filter(Graph.owner_email == 'owner@example.com').one_or_none() + self.assertEqual(graphversion1.name, 'graphversion1') + # + # Update + graphversion1.name = 'updated_graph_version' + self.session.commit() + graphversion1 = self.session.query(GraphVersion).filter(Graph.owner_email == 'owner@example.com').one_or_none() + self.assertEqual(graphversion1.name, 'updated_graph_version') + # + # Delete + self.session.delete(graphversion1) + self.session.commit() + graphversion1 = self.session.query(GraphVersion).filter(Graph.owner_email == 'owner@example.com').one_or_none() + self.assertIsNone(graphversion1) + + # Retrieve + num_graph_versions = self.session.query(GraphVersion).count() + self.assertEqual(num_graph_versions, 0) + + def test_owner_email_fkey_constraint(self): + + with self.assertRaises(IntegrityError): + self.session.add(GraphVersion(name='graphversion1', owner_email='owner@example.com', graph_id=0, + graph_json='{}', style_json='{}', description='{}')) + self.session.commit() + + def test_graph_id_fkey_constraint(self): + + with self.assertRaises(IntegrityError): + self.session.add(User(email='owner@example.com', password="password", is_admin=0)) + self.session.add(GraphVersion(name='graphversion1', owner_email='owner@example.com', graph_id=0, + graph_json='{}', style_json='{}', description='{}')) + self.session.commit() + + def test_cascade_on_user_delete(self): + """ + On deleting user row, the corresponding row in graph version table should also be deleted. + """ + self.session.add(User(email='owner@example.com', password="password", is_admin=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) + self.session.commit() + graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() + self.session.add(GraphVersion(name='graphversion1', owner_email='owner@example.com', graph_id=graph1.id, + graph_json='{}', style_json='{}', description='{}')) + self.session.commit() + + owner = self.session.query(User).filter(User.email == 'owner@example.com').one_or_none() + self.session.delete(owner) + self.session.commit() + self.assertIsNone(self.session.query(User).filter(User.email == 'owner@example.com').one_or_none()) + + self.assertIsNone(self.session.query(GraphVersion).filter(and_(GraphVersion.owner_email == 'owner@example.com', GraphVersion.name == 'graphversion1')).one_or_none()) + self.session.commit() + + def test_cascade_on_user_update(self): + """ + On deleting user row, the corresponding row in graph version table should also be updated + """ + self.session.add(User(email='owner@example.com', password="password", is_admin=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) + self.session.commit() + graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() + self.session.add(GraphVersion(name='graphversion1', owner_email='owner@example.com', graph_id=graph1.id, + graph_json='{}', style_json='{}', description='{}')) + self.session.commit() + graphversion1 = self.session.query(GraphVersion).filter(and_(GraphVersion.owner_email == 'owner@example.com', GraphVersion.name == 'graphversion1')).one_or_none() + + owner = self.session.query(User).filter(User.email == 'owner@example.com').one_or_none() + owner.email = 'owner_updated@example.com' + self.session.commit() + + graphversion1 = self.session.query(GraphVersion).filter(GraphVersion.id == graphversion1.id).one_or_none() + self.assertEqual(graphversion1.owner_email, 'owner_updated@example.com') + self.session.commit() + + def test_cascade_on_graph_delete(self): + """ + On deleting graph row, the corresponding row in graph version table should also be deleted. + """ + self.session.add(User(email='owner@example.com', password="password", is_admin=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) + self.session.commit() + graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() + self.session.add(GraphVersion(name='graphversion1', owner_email='owner@example.com', graph_id=graph1.id, + graph_json='{}', style_json='{}', description='{}')) + self.session.commit() + + self.session.delete(graph1) + self.session.commit() + self.assertIsNone(self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none()) + + self.assertIsNone(self.session.query(GraphVersion).filter(and_(GraphVersion.owner_email == 'owner@example.com', GraphVersion.name == 'graphversion1')).one_or_none()) + self.session.commit() + + def test_cascade_on_graph_update(self): + """ + On deleting graph row, the corresponding row in graph version table should also be updated + """ + self.session.add(User(email='owner@example.com', password="password", is_admin=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) + self.session.commit() + graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() + self.session.add(GraphVersion(name='graphversion1', owner_email='owner@example.com', graph_id=graph1.id, + graph_json='{}', style_json='{}', description='{}')) + self.session.commit() + graphversion1 = self.session.query(GraphVersion).filter(and_(GraphVersion.owner_email == 'owner@example.com', GraphVersion.name == 'graphversion1')).one_or_none() + + graph1.id = 10 + self.session.commit() + + graphversion1 = self.session.query(GraphVersion).filter(GraphVersion.id == graphversion1.id).one_or_none() + self.assertEqual(graphversion1.graph_id, graph1.id) + self.session.commit() + + def test_graph_relationship(self): + self.session.add(User(email='owner@example.com', password="password", is_admin=0)) + self.session.add(Graph(name='graph1', owner_email='owner@example.com', is_public=0)) + self.session.commit() + graph1 = self.session.query(Graph).filter(Graph.owner_email == 'owner@example.com').one_or_none() + self.session.add(GraphVersion(name='graphversion1', owner_email='owner@example.com', graph_id=graph1.id, + graph_json='{}', style_json='{}', description='{}')) + self.session.commit() + + graph1 = self.session.query(Graph).filter(and_(Graph.owner_email == 'owner@example.com', Graph.name == 'graph1')).one_or_none() + graphversion1 = self.session.query(GraphVersion).filter(and_(GraphVersion.owner_email == 'owner@example.com', GraphVersion.name == 'graphversion1')).one_or_none() + self.assertEqual(graphversion1.graph.id, graph1.id) + diff --git a/graphspace/database.py b/graphspace/database.py index b750a367..f92025bf 100644 --- a/graphspace/database.py +++ b/graphspace/database.py @@ -20,6 +20,11 @@ def __init__(self): self.engine = create_engine(''.join( ['postgresql://', config['USER'], ':', config['PASSWORD'], '@', config['HOST'], ':', config['PORT'], '/', config['NAME']]), echo=False) # TODO: Find out what is the use of metadata and reflection. + self.connection = self.engine.connect() + result = self.connection.execute("SELECT * FROM pg_extension where extname like 'pg_trgm'") + if result.rowcount==0: + self.connection.execute("create extension btree_gin") + self.connection.execute("create extension pg_trgm") settings.BASE.metadata.create_all(self.engine) self.meta = sqlalchemy.schema.MetaData() self.meta.reflect(bind=self.engine) From c2ed1d8f0d7ec439bec0430870cf32f4fa633cff Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Warsi Date: Sun, 1 Jul 2018 09:42:46 +0200 Subject: [PATCH 18/21] Fix indentation --- applications/graphs/dal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/graphs/dal.py b/applications/graphs/dal.py index a3d8eb18..dc8833f1 100644 --- a/applications/graphs/dal.py +++ b/applications/graphs/dal.py @@ -18,8 +18,8 @@ def get_edges(db_session, edges, order=desc(Edge.updated_at), page=0, page_size= @with_session def get_graphs_by_edges_and_nodes_and_names(db_session, group_ids=None, names=None, nodes=None, edges=None, tags=None, - order=desc(Graph.updated_at), page=0, page_size=10, partial_matching=False, - owner_email=None, is_public=None): + order=desc(Graph.updated_at), page=0, page_size=10, partial_matching=False, + owner_email=None, is_public=None): query = db_session.query(Graph) edges = [] if edges is None else edges From 4135efe913f9941d691cf58c02abd1ef09ccbb76 Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Mon, 2 Jul 2018 11:14:37 +0200 Subject: [PATCH 19/21] Graph Version minor UI Fixes --- static/js/graphs_page.js | 4 +++- templates/graph/graph_version_table.html | 5 ----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/static/js/graphs_page.js b/static/js/graphs_page.js index 8dea5fd3..b401447d 100644 --- a/static/js/graphs_page.js +++ b/static/js/graphs_page.js @@ -536,6 +536,8 @@ var graphPage = { successCallback = function (response) { graph_json = JSON.parse(response.graph_json); //graphPage.contructCytoscapeGraph(); + $("#graphVisualizationTabBtn.link-reset").click(); + $(location).attr('href', '#graph_visualization_tab'); graphPage.init(); $("#version_selector_dropdown").attr('current_version_id', row); $("#GraphVersionTable").find('span[row_id=' + row + ']').parent().parent().addClass('success'); @@ -1176,7 +1178,7 @@ var graphPage = { } params.data["graph_id"] = $('#GraphID').val(); - + params.data["owner_email"] = $('#UserEmail').val(); apis.version.get($('#GraphID').val(), params.data, successCallback = function (response) { // This method is called when nodes are successfully fetched. diff --git a/templates/graph/graph_version_table.html b/templates/graph/graph_version_table.html index 44b89cb7..2af96c3b 100644 --- a/templates/graph/graph_version_table.html +++ b/templates/graph/graph_version_table.html @@ -1,8 +1,3 @@ - -
    - Default Version : -
    - Date: Sun, 15 Jul 2018 10:46:03 +0200 Subject: [PATCH 20/21] Fix Graph Fit and Layout issues for Graph Versions --- static/js/graphs_page.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/static/js/graphs_page.js b/static/js/graphs_page.js index b401447d..1281cf8f 100644 --- a/static/js/graphs_page.js +++ b/static/js/graphs_page.js @@ -535,12 +535,17 @@ var graphPage = { apis.version.getByID($('#GraphID').val(), row, successCallback = function (response) { graph_json = JSON.parse(response.graph_json); - //graphPage.contructCytoscapeGraph(); + + window.history.pushState('auto-layout', 'Graph Page', window.location.origin + window.location.pathname); $("#graphVisualizationTabBtn.link-reset").click(); $(location).attr('href', '#graph_visualization_tab'); graphPage.init(); $("#version_selector_dropdown").attr('current_version_id', row); $("#GraphVersionTable").find('span[row_id=' + row + ']').parent().parent().addClass('success'); + /* Pan and zooms the graph to fit to a collection. */ + window.setTimeout(function () { + graphPage.cyGraph.fit(); + }, 300); //console.log("Success"); }, errorCallback = function (xhr, status, errorThrown) { From 19e319d218a15cdf29488a3138f71c3acbab4a2b Mon Sep 17 00:00:00 2001 From: Daniyal Jahan Date: Sun, 15 Jul 2018 11:12:37 +0200 Subject: [PATCH 21/21] Set correct z-index for Export button & version dropdown --- templates/graph/index.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/templates/graph/index.html b/templates/graph/index.html index 4bea8a72..e3d1bde8 100644 --- a/templates/graph/index.html +++ b/templates/graph/index.html @@ -179,7 +179,7 @@

    Share {% endif %} -
    +