summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorÉtienne Loks <etienne.loks@iggdrasil.net>2018-04-30 15:36:20 +0200
committerÉtienne Loks <etienne.loks@iggdrasil.net>2018-06-12 08:43:58 +0200
commit16fa62f8fd560d09eb339df372416653d6eb916d (patch)
tree3a9af0df0ec10457521b51c05b0ecb96783937a6
parent962b8fd05606c8187b33e9c2bf1e88985f548802 (diff)
downloadIshtar-16fa62f8fd560d09eb339df372416653d6eb916d.tar.bz2
Ishtar-16fa62f8fd560d09eb339df372416653d6eb916d.zip
Generate an SVG file displaying relations using DOT file format and "dot" program
-rw-r--r--example_project/settings.py3
-rw-r--r--ishtar_common/utils.py134
2 files changed, 137 insertions, 0 deletions
diff --git a/example_project/settings.py b/example_project/settings.py
index 46fee04d8..d5efd2891 100644
--- a/example_project/settings.py
+++ b/example_project/settings.py
@@ -253,6 +253,9 @@ ISHTAR_DPTS = []
MAX_ATTEMPTS = 1 # django background tasks
+# if you want to generate relation graph provide the path to the "dot" program
+DOT_BINARY = ""
+
TEST_RUNNER = 'ishtar_common.tests.ManagedModelTestRunner'
try:
diff --git a/ishtar_common/utils.py b/ishtar_common/utils.py
index 01b35dcef..1613b389d 100644
--- a/ishtar_common/utils.py
+++ b/ishtar_common/utils.py
@@ -21,7 +21,11 @@ import datetime
from functools import wraps
from itertools import chain
import hashlib
+import os
import random
+import shutil
+import subprocess
+import tempfile
from django import forms
from django.conf import settings
@@ -29,6 +33,7 @@ from django.contrib.gis.geos import GEOSGeometry
from django.contrib.sessions.backends.db import SessionStore
from django.core.cache import cache
from django.core.exceptions import FieldDoesNotExist
+from django.core.files import File
from django.core.urlresolvers import reverse
from django.utils.datastructures import MultiValueDict as BaseMultiValueDict
from django.utils.safestring import mark_safe
@@ -480,3 +485,132 @@ def create_default_areas(models=None):
idx += 1
print("* {} town associated to department area".format(idx))
+
+
+def get_relations_for_graph(rel_model, obj_pk, above_relations=None,
+ equal_relations=None, treated=None):
+ """
+ Get all above and equal relations of an object (get all child and parent
+ relations)
+ :param rel_model: the relation model concerned
+ :param obj_pk: id of an object with relations
+ :param above_relations: list of current above_relations
+ :param equal_relations: list of current equal_relations
+ :param treated: treated relation list to prevent circular call
+ :return: above and equal relations list (each containing lists of two
+ members)
+ """
+ if not above_relations:
+ above_relations = []
+ if not equal_relations:
+ equal_relations = []
+ if not treated:
+ treated = []
+ if obj_pk in treated:
+ return above_relations, equal_relations
+
+ treated.append(obj_pk)
+ q = rel_model.objects.filter(
+ left_record_id=obj_pk,
+ relation_type__logical_relation__isnull=False
+ ).values('right_record_id', 'relation_type__logical_relation')
+ if not q.count():
+ return [], []
+ for relation in q.all():
+ logical_relation = relation['relation_type__logical_relation']
+ right_record = relation['right_record_id']
+ if not logical_relation:
+ continue
+ elif logical_relation == 'above'and \
+ (obj_pk, right_record) not in above_relations:
+ above_relations.append((obj_pk, right_record))
+ elif logical_relation == 'bellow' and \
+ (right_record, obj_pk) not in above_relations:
+ above_relations.append((right_record, obj_pk))
+ elif logical_relation == 'equal' and \
+ (right_record, obj_pk) not in equal_relations and \
+ (obj_pk, right_record) not in equal_relations:
+ equal_relations.append((obj_pk, right_record))
+ else:
+ continue
+ ar, er = get_relations_for_graph(
+ rel_model, right_record, above_relations, equal_relations, treated)
+ for r in ar:
+ if r not in above_relations:
+ above_relations.append(r)
+ for r in er:
+ if r not in equal_relations:
+ equal_relations.append(r)
+ return above_relations, equal_relations
+
+
+def generate_relation_graph(obj, debug=False):
+ if not settings.DOT_BINARY:
+ return
+
+ model = obj.__class__
+ rel_model = model._meta.get_field('right_relations').related_model
+
+ # get relations
+ above_relations, equal_relations = get_relations_for_graph(rel_model,
+ obj.pk)
+ if not above_relations and not equal_relations:
+ obj.relation_image = None
+ obj.save()
+ return
+
+ # generate dotfile
+ dot_str = "digraph relations {\nnode [shape=box];\n"
+ rel_str = ""
+ described = []
+ for list, directed in ((above_relations, True),
+ (equal_relations, False)):
+ if directed:
+ rel_str += "subgraph Dir {\n"
+ else:
+ rel_str += "subgraph NoDir {\nedge [dir=none,style=dashed];\n"
+ for left_pk, right_pk in list:
+ if left_pk not in described:
+ described.append(left_pk)
+ left = model.objects.get(pk=left_pk)
+ style = 'label="{}"'.format(left.relation_label)
+ if left.pk == obj.pk:
+ style += ',style=filled,fillcolor="#C6C0C0"'
+ dot_str += u'item{}[{}];\n'.format(left.pk, style)
+ if right_pk not in described:
+ described.append(right_pk)
+ right = model.objects.get(pk=right_pk)
+ style = 'label="{}"'.format(right.relation_label)
+ if right.pk == obj.pk:
+ style += ',style=filled,fillcolor="#C6C0C0"'
+ dot_str += u'item{}[{}];\n'.format(right.pk, style)
+ if not directed: # on sthe same level
+ rel_str += u"{{rank = same; item{}; item{};}}\n".format(
+ left_pk, right_pk)
+ rel_str += u'item{} -> item{};\n'.format(left_pk, right_pk)
+ rel_str += "}\n"
+ dot_str += rel_str + "\n}"
+
+ tempdir = tempfile.mkdtemp("-ishtardot")
+ dot_name = tempdir + os.path.sep + "relations.dot"
+ with open(dot_name, 'w') as dot_file:
+ dot_file.write(dot_str)
+
+ # execute dot program
+ args = (settings.DOT_BINARY, "-Tsvg", dot_name)
+
+ svg_tmp_name = tempdir + os.path.sep + "relations.svg"
+
+ with open(svg_tmp_name, "w") as svg_file:
+ popen = subprocess.Popen(args, stdout=svg_file)
+ popen.wait()
+
+ with open(svg_tmp_name, "r") as svg_file:
+ django_file = File(svg_file)
+ obj.relation_image.save("relations.svg", django_file, save=True)
+
+ if debug:
+ print(u"DOT file: {}. Tmp SVG file: {}.".format(dot_name, svg_tmp_name))
+ return
+ shutil.rmtree(tempdir)
+