feat(people): Add network view

Network view shows people and relationships between them

Resolves #21
Resolves #20
Part of #14
This commit is contained in:
James Graham
2020-03-06 15:09:40 +00:00
parent 9b3b759254
commit b2fd9eeaf9
6 changed files with 198 additions and 2 deletions

View File

@@ -71,6 +71,7 @@ THIRD_PARTY_APPS = [
'dbbackup',
'django_countries',
'django_select2',
'rest_framework',
]
FIRST_PARTY_APPS = [

26
people/serializers.py Normal file
View File

@@ -0,0 +1,26 @@
"""
Serialize models to and deserialize from JSON.
"""
from rest_framework import serializers
from . import models
class PersonSerializer(serializers.ModelSerializer):
class Meta:
model = models.Person
fields = [
'pk',
'name',
]
class RelationshipSerializer(serializers.ModelSerializer):
class Meta:
model = models.Relationship
fields = [
'pk',
'source',
'target',
]

View File

@@ -0,0 +1,120 @@
{% extends 'base.html' %}
{% block content %}
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item active" aria-current="page">Network</li>
</ol>
</nav>
<hr>
<div id="cy"
style="width: 100%; min-height: 1000px; flex-grow: 1; border: 2px solid black"></div>
{% endblock %}
{% block extra_script %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.14.0/cytoscape.min.js"
integrity="sha256-rI7zH7xDqO306nxvXUw9gqkeBpvvmddDdlXJjJM7rEM="
crossorigin="anonymous"></script>
<script type="application/javascript">
var cy;
/**
* Get all :class:`Person` records from people:person.api.list endpoint.
*
* @returns JQuery Promise from AJAX
*/
function get_people_ajax(){
return $.ajax({
url: '{% url "people:person.api.list" %}',
success: success_people_ajax,
error: function (xhr, status, error) {
console.error(error);
}
});
}
/**
* Add nodes to Cytoscape network from :class:`Person` JSON.
*
* @param data: JSON representation of people
* @param status: unused
* @param xhr: unused
*/
function success_people_ajax(data, status, xhr) {
for (var person of data) {
cy.add({
group: 'nodes',
data: {
id: 'person-' + person.pk.toString(),
name: person.name
}
})
}
}
/**
* Get all :class:`Relationship` records from people:relationship.api.list endpoint.
*
* @returns JQuery Promise from AJAX
*/
function get_relationships_ajax() {
return $.ajax({
url: '{% url "people:relationship.api.list" %}',
success: success_relationships_ajax,
error: function (xhr, status, error) {
console.error(error);
}
});
}
/**
* Add edges to Cytoscape network from :class:`Relationship` JSON.
*
* @param data: JSON representation of relationships
* @param status: unused
* @param xhr: unused
*/
function success_relationships_ajax(data, status, xhr) {
for (var relationship of data) {
cy.add({
group: 'edges',
data: {
id: 'relationship-' + relationship.pk.toString(),
source: 'person-' + relationship.source.toString(),
target: 'person-' + relationship.target.toString()
}
})
}
}
/**
* Populate a Cytoscape network from :class:`Person` and :class:`Relationship` API.
*/
function get_network() {
cy = cytoscape({
container: document.getElementById('cy')
});
$.when(get_people_ajax()).then(function() {
get_relationships_ajax()
}).then(function() {
var layout = cy.layout({
name: 'circle',
randomize: true,
animate: false
});
layout.run();
});
}
$( window ).on('load', get_network());
</script>
{% endblock %}

View File

@@ -37,4 +37,16 @@ urlpatterns = [
path('relationships/<int:relationship_pk>/update',
views.RelationshipUpdateView.as_view(),
name='relationship.update'),
path('api/people',
views.PersonApiView.as_view(),
name='person.api.list'),
path('api/relationships',
views.RelationshipApiView.as_view(),
name='relationship.api.list'),
path('network',
views.NetworkView.as_view(),
name='network'),
]

View File

@@ -4,9 +4,11 @@ Views for displaying or manipulating models in the 'people' app.
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.views.generic import CreateView, DetailView, FormView, ListView, UpdateView
from django.views.generic import CreateView, DetailView, ListView, TemplateView, UpdateView
from . import forms, models, permissions
from rest_framework.views import APIView, Response
from . import forms, models, permissions, serializers
class PersonCreateView(CreateView):
@@ -176,3 +178,36 @@ class RelationshipUpdateView(permissions.UserIsLinkedPersonMixin, CreateView):
form.save()
return HttpResponseRedirect(self.relationship.get_absolute_url())
class PersonApiView(APIView):
"""
List all :class:`Person` instances.
"""
def get(self, request, format=None):
"""
List all :class:`Person` instances.
"""
serializer = serializers.PersonSerializer(models.Person.objects.all(),
many=True)
return Response(serializer.data)
class RelationshipApiView(APIView):
"""
List all :class:`Relationship` instances.
"""
def get(self, request, format=None):
"""
List all :class:`Relationship` instances.
"""
serializer = serializers.RelationshipSerializer(models.Relationship.objects.all(),
many=True)
return Response(serializer.data)
class NetworkView(TemplateView):
"""
View to display relationship network.
"""
template_name = 'people/network.html'

View File

@@ -7,9 +7,11 @@ django-bootstrap4==1.1.1
django-constance==2.6.0
django-countries==5.5
django-dbbackup==3.2.0
django-filter==2.2.0
django-picklefield==2.1.1
django-select2==7.2.0
django-settings-export==1.2.1
djangorestframework==3.11.0
dodgy==0.2.1
isort==4.3.21
lazy-object-proxy==1.4.3