Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
a65bed1
Update requirements.txt to fix security risk in Jinja2
adbharadwaj Apr 10, 2019
52b0312
Update requirements.txt to fix security risk in SQLAlchemy
adbharadwaj Apr 19, 2019
82cb1ff
Added the configuration to show appropriate maintenance message
adbharadwaj Apr 26, 2019
a0924ca
Merge branch 'master' of github.com:Murali-group/GraphSpace
adbharadwaj Apr 26, 2019
d12d954
adding maintenance message
adbharadwaj Apr 26, 2019
6dcfffa
adding maintenance message
adbharadwaj Apr 26, 2019
5c42953
adding maintenance message
adbharadwaj Apr 26, 2019
9960100
updating maintenance message
adbharadwaj Apr 27, 2019
80b32ce
removingmaintenance message
adbharadwaj Apr 27, 2019
b8d9be3
DIFF-01 [ADD] Implements basic diff for graph functionalities
jahandaniyal Jun 20, 2019
200522f
Add template and assets for DIFF-01
jahandaniyal Jun 21, 2019
18438cf
Update graph_page.js to handle basic diff functions
jahandaniyal Jun 21, 2019
ea44961
Add template and CSS for GUI
jahandaniyal Jun 28, 2019
3758498
Update graph_page.js for GUI
jahandaniyal Jun 28, 2019
9fd7df2
Update comparison logic in middleware
jahandaniyal Jun 28, 2019
02426a9
Add additional information in Nodes/Edges Table
jahandaniyal Jul 3, 2019
9634d04
Fix edge table for difference operation
jahandaniyal Jul 3, 2019
cd58c18
Disable buttons on submit and give proper error message.
HitRam Jan 31, 2018
a529abe
Added the name of the groups with which a graph is shared to the "Gra…
lohani2280 Feb 5, 2018
feb99ec
Added code to validate the email address during signup
lohani2280 Feb 11, 2018
639e40d
README.md: Added GraphSpace documentation link
lohani2280 Feb 15, 2018
c50af3e
partially done rest-api-doc
sanket0211 Feb 15, 2018
4b94961
images checked
sanket0211 Feb 15, 2018
09c9b42
issue:332-Add a documentation for RESTful APIs fixed
sanket0211 Feb 15, 2018
0ecd57d
issue:332-Add a documentation for RESTful APIs fixed
sanket0211 Feb 15, 2018
eeca2d7
Corrected Image Name
sanket0211 Feb 15, 2018
21f3c32
Credits given to Sandeep Mahapatra
sanket0211 Feb 15, 2018
ed4529b
Added functionality for default automatic layout and layout settings
lohani2280 Feb 2, 2018
5b5ab24
README.md: Added License and Maintainers section
lohani2280 Feb 22, 2018
561adeb
Update footer.html
JingVT May 22, 2018
b11c11f
Update Cytoscape.js
JingVT May 22, 2018
fdc00db
Update Cytoscape.js
JingVT May 22, 2018
d49c6c3
Update Cytoscape.js
JingVT May 22, 2018
2fceda8
Update Cytoscape.js
JingVT May 22, 2018
0c5213c
Update bower.json
JingVT Oct 23, 2018
58c2bcb
Update error.html
JingVT Oct 26, 2018
6513f63
Update reset.html
JingVT Oct 26, 2018
2968712
Update index.html
JingVT Oct 26, 2018
6da6b97
Update footer.html
JingVT Oct 26, 2018
c4b20df
Fix issue 396 -- select layout algorithm does not work for upgraded c…
JingVT Oct 26, 2018
b859717
Fix issue 398 (#3) -- Edit selected nodes or edges function
JingVT Oct 26, 2018
bd82570
Fix issue 402 (#4) -- cyTarget to Target
JingVT Oct 26, 2018
d2bfc6d
Fix issue 400 (#5) -- cyTarget to Target
JingVT Oct 26, 2018
43c891c
Fix issue 405 (#6) -- Remove applyMax funstions
JingVT Oct 26, 2018
8ad76c5
Added a Save Layout button on the Layouts pane
Jan 10, 2019
1325c60
Refactor headers in templates
jahandaniyal Jul 7, 2019
7c346bf
Refine UI for V1.1
jahandaniyal Jul 10, 2019
837e668
Utility function for V1.1
jahandaniyal Jul 10, 2019
36edbd6
Update template to accept query params
jahandaniyal Jul 17, 2019
d8541f0
Remove compare page functionalities from graph_page.js
jahandaniyal Jul 17, 2019
a6f5fca
Update js for query params & fix node styling
jahandaniyal Jul 17, 2019
f1b6d32
Set edge color same as graphs
jahandaniyal Jul 17, 2019
8990a97
Refactor variables in js
jahandaniyal Jul 17, 2019
67a80a5
Fix Duplicate node issue and use ID/Name
jahandaniyal Jul 17, 2019
45f1fc8
Remove redudant code for duplicate nodes
jahandaniyal Jul 17, 2019
daa0336
Integrate query params support
jahandaniyal Jul 17, 2019
371ec95
Fix sharedgraph dropdown
jahandaniyal Jul 23, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,28 @@ WSGIScriptAlias / /path_to_GraphSpace/graphspace/wsgi.py
Refer to https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/modwsgi/ if any problems occur with the setup.


Documentation
=================

GraphSpace has extensive documentation on the [user interface](http://docs.graphspace.org/en/latest/Quick_Tour_of_GraphSpace.html#welcome-screen), the [REST API](http://docs.graphspace.org/en/latest/Programmers_Guide.html#graphspace-rest-api) and a [Python package for programmatic interaction](http://manual.graphspace.org/projects/graphspace-python/en/latest/tutorial/index.html).


Contributing
=================

Feel free to fork and send us pull requests. Here are the [guidelines for contribution](https://github.com/Murali-group/GraphSpace/blob/master/CONTRIBUTING.md) in GraphSpace.


Contact
=================

If you have questions or suggestions about GraphSpace, please contact

- **T.M. Murali ([@tmmurali](https://github.com/tmmurali))**
- **Aditya Bharadwaj ([@adbharadwaj](https://github.com/adbharadwaj))**


License
=================

GraphSpace is available under the GNU General Public License v2.0 license. See [LICENSE.md](https://github.com/Murali-group/GraphSpace/blob/master/LICENSE.md) for more information.
22 changes: 22 additions & 0 deletions applications/graphs/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -586,3 +586,25 @@ 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 get_graph_comparison(request, graph_1, graph_2, operation):
if operation == 'intersection':
return get_graphs_intersection(request, graph_1, graph_2)
else:
return get_graphs_difference(request, graph_1, graph_2)


def get_graphs_intersection(request, graph_1, graph_2):
# calling nodes_comparison function for testing purpose only
# db.nodes_comparison(request.db_session,)

node_data = db.nodes_intersection(request.db_session, graph_1, graph_2)
edge_data = db.edges_intersection(request.db_session, graph_1, graph_2)
return node_data, edge_data


def get_graphs_difference(request, graph_1, graph_2):
node_data = db.nodes_difference(request.db_session, graph_1, graph_2)
edge_data = db.edges_difference(request.db_session, graph_1, graph_2)
return node_data, edge_data
120 changes: 119 additions & 1 deletion applications/graphs/dal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from applications.users.models import *
from graphspace.wrappers import with_session
from sqlalchemy.orm import defer, undefer
from sqlalchemy.orm import defer, undefer, aliased


@with_session
Expand Down Expand Up @@ -458,3 +458,121 @@ 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 nodes_intersection(db_session, graph_1_id=None, graph_2_id=None):
alias_node = aliased(Node)
query = db_session.query(Node, alias_node)

if graph_1_id is not None and graph_2_id is not None:
query = query.filter(Node.graph_id == graph_1_id).\
join(alias_node, Node.name == alias_node.name).\
filter(alias_node.graph_id == graph_2_id)
total = query.count()
return total, query.all()


@with_session
def nodes_difference(db_session, graph_1_id=None, graph_2_id=None):
graph1_query = db_session.query(Node).filter(Node.graph_id == graph_1_id)
graph2_query = db_session.query(Node).filter(Node.graph_id == graph_2_id)
sub_q = graph2_query.subquery()

query = graph1_query.outerjoin(sub_q, sub_q.c.name == Node.name).\
filter(sub_q.c.name == None)
total = query.count()
return total, query.all()


@with_session
def edges_intersection(db_session, graph_1_id=None, graph_2_id=None):
alias_edge = aliased(Edge)
query = db_session.query(Edge, alias_edge)

if graph_1_id is not None and graph_2_id is not None:
query = query.filter(Edge.graph_id == graph_1_id).\
join(alias_edge, Edge.head_node_name == alias_edge.head_node_name).\
filter(alias_edge.graph_id == graph_2_id).\
filter(Edge.tail_node_name == alias_edge.tail_node_name)
total = query.count()
return total, query.all()


@with_session
def edges_difference(db_session, graph_1_id=None, graph_2_id=None):
graph1_query = db_session.query(Edge).filter(Edge.graph_id == graph_1_id)
graph2_query = db_session.query(Edge).filter(Edge.graph_id == graph_2_id)
sub_q = graph2_query.subquery()

query = graph1_query.outerjoin(sub_q, sub_q.c.name == Edge.name). \
filter(sub_q.c.name == None)
total = query.count()
return total, query.all()


@with_session
def nodes_subquery(db_session, graph_1_id=None, graph_2_id=None, operation=None):
alias_node = aliased(Node)
if operation == 'i':
query = db_session.query(Node, alias_node)
if graph_1_id is not None and graph_2_id is not None:
query = query.filter(Node.graph_id == graph_1_id). \
join(alias_node, Node.name == alias_node.name). \
filter(alias_node.graph_id == graph_2_id)
return query.subquery()
else:
graph1_query = db_session.query(Node).filter(Node.graph_id == graph_1_id)
graph2_query = db_session.query(Node).filter(Node.graph_id == graph_2_id)
sub_q = graph2_query.subquery()

query = graph1_query.outerjoin(sub_q, sub_q.c.name == Node.name). \
filter(sub_q.c.name == None)
return query.subquery()

@with_session
def edges_subquery(db_session, graph_1_id=None, graph_2_id=None, operation=None):
alias_edge = aliased(Edge)
if operation == 'intersection':
query = db_session.query(Edge, alias_edge)

if graph_1_id is not None and graph_2_id is not None:
query = query.filter(Edge.graph_id == graph_1_id). \
join(alias_edge, Edge.head_node_name == alias_edge.head_node_name). \
filter(alias_edge.graph_id == graph_2_id). \
filter(Edge.tail_node_name == alias_edge.tail_node_name)
return query.subquery
else:
graph1_query = db_session.query(Edge).filter(Edge.graph_id == graph_1_id)
graph2_query = db_session.query(Edge).filter(Edge.graph_id == graph_2_id)
sub_q = graph2_query.subquery()

query = graph1_query.outerjoin(sub_q, sub_q.c.name == Edge.name). \
filter(sub_q.c.name == None)
return query.subquery


@with_session
def nodes_comparison(db_session, comp_expression=None):
# Infix -> a 'i' ( b - c )
# Postfix -> a b c - 'i'

comp_expression = [1, 2, 5, 'd', 'i']
temp_stack = []
for item in comp_expression:
if item not in ['d','i']:
temp_stack.append(item)
elif item in ['d','i']:
query1 = temp_stack.pop()
query2 = temp_stack.pop()
if type(query1) != int:
query = db_session.query(Node).filter(Node.graph_id == query2)
sub_query = query.outerjoin(query1, query1.c.name == Node.name). \
filter(query1.c.name == None)
else:
sub_query = nodes_subquery(db_session, query1, query2, item)
temp_stack.append(sub_query)
query = temp_stack.pop()
count = query.count()

return count, query.all()
2 changes: 2 additions & 0 deletions applications/graphs/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
url(r'^graphs/(?P<graph_id>[^/]+)$', views.graph_page, name='graph'),
url(r'^graphs/(?P<email>[^/]+)/(?P<graph_name>[^/]+)$', views.graph_page_by_name, name='graph_by_name'),
url(r'^upload$', views.upload_graph_page, name='upload_graph'),
url(r'^compare$', views.compare_graph_page, name='compare_graph_page'),

# AJAX APIs Endpoints

# Graphs
url(r'^ajax/graphs/$', views.graphs_ajax_api, name='graphs_ajax_api'),
url(r'^ajax/graphs/advanced_search$', views.graphs_advanced_search_ajax_api, name='graphs_advanced_search_ajax_api'),
url(r'^ajax/graphs/(?P<graph_id>[^/]+)$', views.graphs_ajax_api, name='graph_ajax_api'),
url(r'^ajax/compare/$', views.compare_graphs, name='compare_graph'),
# Graphs Groups
url(r'^ajax/graphs/(?P<graph_id>[^/]+)/groups$', views.graph_groups_ajax_api, name='graph_groups_ajax_api'),
url(r'^ajax/graphs/(?P<graph_id>[^/]+)/groups/(?P<group_id>[^/]+)$', views.graph_groups_ajax_api, name='graph_group_ajax_api'),
Expand Down
68 changes: 68 additions & 0 deletions applications/graphs/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,74 @@
from graphspace.wrappers import is_authenticated


def compare_graph_page(request):
context = RequestContext(request, {})
if request.GET.get('graph_1') is None and request.GET.get('graph_2') is None \
and request.GET.get('operation') is None:
return render(request, 'graphs/../../templates/compare_graph/compare_graphs.html', context)

if request.GET.get('graph_1') and request.GET.get('graph_2') \
and request.GET.get('operation'):
if request.GET.get('operation') == 'intersection' or request.GET.get('operation') == 'difference':
context['graph_1_id'] = json.dumps(request.GET.get('graph_1'))
context['graph_2_id'] = json.dumps(request.GET.get('graph_2'))
context['operation'] = json.dumps(request.GET.get('operation'))
return render(request, 'graphs/../../templates/compare_graph/compare_graphs.html', context)
else:
raise MethodNotAllowed(request)


def compare_graphs(request):
context = RequestContext(request, {})

if request.META.get('HTTP_ACCEPT', None) == 'application/json':
if request.method == "GET":
return HttpResponse(json.dumps(_compare_graph(request, request.GET['graph_1_id'],
request.GET['graph_2_id'], request.GET['operation'])),
content_type="application/json", status=200)
else:
raise MethodNotAllowed(request) # Handle other type of request methods like OPTIONS etc.
else:
raise MethodNotAllowed(request)


def _compare_graph(request, graph_1_id, graph_2_id, operation):
"""

Parameters
----------
request : object
HTTP GET Request.
graph_1_id : string
Unique ID of the 1st graph. Required.
graph_2_id : string
Unique ID of the 2nd graph. Required.
operation : string
Comparison operation difference or intersection. Required.

Returns
-------
nodes & edges: object

Raises
------

Notes
------

"""
authorization.validate(request, permission='GRAPH_READ', graph_id=graph_1_id)
authorization.validate(request, permission='GRAPH_READ', graph_id=graph_2_id)
nodes, edges = graphs.get_graph_comparison(request, graph_1_id, graph_2_id, operation)
if operation == 'intersection':
edges = [[utils.serializer(edge) for edge in item] for item in edges[1]]
nodes = [[utils.serializer(node) for node in item] for item in nodes[1]]
else:
edges = [utils.serializer(edge) for edge in edges[1]]
nodes = [utils.serializer(node) for node in nodes[1]]
return {'edges': edges, 'nodes': nodes}


def upload_graph_page(request):
context = RequestContext(request, {})

Expand Down
4 changes: 2 additions & 2 deletions applications/home/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,9 @@ def forgot_password_page(request):

if password_reset_code is not None:
users.send_password_reset_email(request, password_reset_code)
context["success_message"] = "Email has been sent!"
context["success_message"] = "You will receive an email with link to update the password!"
else:
context["error_message"] = "Email does not exist!"
context["error_message"] = "You will receive an email with link to update the password!"
return render(request, 'forgot_password/index.html', context) # Handle POST request to forgot password page.
else:
raise MethodNotAllowed(request) # Handle other type of request methods like PUT, UPDATE.
Expand Down
4 changes: 2 additions & 2 deletions bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"moment": "^2.17.0",
"remarkable-bootstrap-notify": "^3.1.3",
"animate.css": "^3.5.2",
"cytoscape": "^2.7.11",
"cytoscape": "^3.2.17",
"webcola": "^3.3.0",
"bootstrap": "^3.3.7",
"cytoscape-cola": "^1.6.0",
Expand All @@ -30,7 +30,7 @@
"bootstrap-table": "^1.11.0",
"cytoscape-panzoom": "^2.4.0",
"select2": "select2-dist#^4.0.3",
"cytoscape-context-menus": "^2.1.1",
"cytoscape-context-menus": "^3.0.6",
"bootstrap-colorpicker": "^2.5.1"
}
}
54 changes: 54 additions & 0 deletions docs/Programmers_Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,57 @@ Install graphspace_python from PyPI using:
### Usage

Please refer to ``graphspace_python`` package's [documentation](http://manual.graphspace.org/projects/graphspace-python/) to learn how to use it.


## GraphSpace REST APIs using the Postman app

### This documentation is based on [Sandeep Mahapatra's blog post](https://summerofcode17.wordpress.com/2017/05/30/using-the-graphspace-restful-api/) in the 2017 GSoc.


```
Note: In order to fully utilize the features of GraphSpace REST API, you must have an account on GraphSpace.
```

Postman is a Google Chrome app for interacting with HTTP APIs. It provides a friendly GUI for constructing requests and reading responses. Postman makes it easy to test, develop and document APIs by allowing users to quickly put together both simple and complex HTTP requests.

### Postman Installation

Postman is available as a [native app](https://www.getpostman.com/docs/install_native) (recommended) for Mac / Windows / Linux, and as a Chrome App. The Postman Chrome app can only run on the Chrome browser. To use the Postman Chrome app, you need to:
- Install Google Chrome: [Install Chrome](https://www.google.com/chrome/).
- If you already have Chrome installed, head over to Postman’s page on the [Chrome Webstore](https://chrome.google.com/webstore/detail/postman-rest-client-packa/fhbjgbiflinjbdggehcddcbncdddomop?hl=en), and click ‘Add to Chrome’.
- After the download is complete, launch the app.

### Using Postman for GraphSpace REST API

The GraphSpace REST APIs have the base URL http://www.graphspace.org/api/v1/. There are many endpoints defined under this base URL (the documentation of which can be found here), but to learn and understand the usage of GraphSpace REST APIs through Postman, we would be considering only the /graphs endpoint for GET and POST request.
- The GET /graphs request fetches a list of graphs from GraphSpace matching the query parameters.
- The POST /graphs request creates a graph in GraphSpace.

### GET /graphs
- The URL is the first thing that we would be setting for a request. We will set the URL to http://www.graphspace.org/api/v1/graphs.
![Rest API get](_static/images/rest-api/gs_rest_get_1.jpg)
- Provide Authorization: Select ‘Basic Auth’ from Authorization type drop-down. Enter the username and password and click on ‘Update Request’.
![Rest API get 2](_static/images/rest-api/gs_rest_get_2.jpg)
- Set Header: Add the following key value pairs, ```Content-Type:application/json and Accept:application/json.```
![REST API get 3](_static/images/rest-api/gs_rest_get_3.jpg)
- Select Method: Changing the method is straightforward. Just select the method from the select control. We will use GET method here.
- Add URL Params: Clicking on the URL Params button will open up the key-value editor for entering URL parameters. The details of the URL Params for /graphs endpoint can be found in the [documentation](http://manual.graphspace.org/en/latest/Programmers_Guide.html#api-reference).
- Click on the Send button to the send the request. A list of graphs matching the query parameters will be received in the response.

### POST /graphs
- The initial steps of setting URL, Authorization and Header are performed.
- Change Method to POST.
- Set Request Body: Click on Body to open the request body editor. Select raw request from the choices and JSON(application/json) from the drop-down. Enter the json data for the graph to be created in the editor. The details regarding the properties of the json graph body can be found in the [documentation](http://manual.graphspace.org/en/latest/Programmers_Guide.html#api-reference).
![REST API post 1](_static/images/rest-api/gs_rest_post_1.jpg)
- Click on the Send button to the send the request. A new graph object will be created and returned in the response.

### Postman Collection

A collection lets you group individual requests together. These requests can be further organized into folders to accurately mirror our API. Requests can also store sample responses when saved in a collection. You can add metadata like name and description too so that all the information that a developer needs to use your API is available easily. Collections can be exported as JSON files. Exporting a collection also saves the Authorization details. Hence, it is advised to remove the Authorization details from the Header before exporting.

For quick use of the GraphSpace REST APIs or if you are stuck somewhere and you want reference, you can [download the collection of the APIs here](https://gist.github.com/sandeepm96/a824a6d0e643811389a6bf212e30a381). The collection has details regarding the API endpoints like params and body properties. Importing steps:
- Click Import button in the top menu.
- Choose the Import File in the pop up window.
![post man collection](_static/images/rest-api/post_man_collection.jpg)
- Provide the Authorization details for the imported requests (as Authorization details have been removed for security concern)

Binary file added docs/_static/images/rest-api/gs_rest_get_1.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_static/images/rest-api/gs_rest_get_2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_static/images/rest-api/gs_rest_get_3.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_static/images/rest-api/gs_rest_post_1.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading