diff options
| -rw-r--r-- | example_project/settings.py | 3 | ||||
| -rw-r--r-- | ishtar_common/utils.py | 134 | 
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) + | 
