diff options
| -rw-r--r-- | .gitlab-ci.yml | 2 | ||||
| -rw-r--r-- | archaeological_context_records/migrations/0048_auto_20190704_1526.py | 96 | ||||
| -rw-r--r-- | archaeological_context_records/tests.py | 97 | ||||
| -rw-r--r-- | archaeological_operations/migrations/0057_auto_20190704_1526.py | 96 | ||||
| -rw-r--r-- | example_project/settings.py | 4 | ||||
| -rwxr-xr-x | install/ishtar-install | 4 | ||||
| -rw-r--r-- | ishtar_common/models.py | 32 | ||||
| -rw-r--r-- | ishtar_common/utils.py | 166 | 
8 files changed, 431 insertions, 66 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 65f39ebb3..ec1e9a845 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,6 @@  before_script:    - apt-get update -  - apt-get install -q -y git sed python3-pip libpq-dev python3-dev libjpeg-dev zlib1g-dev libxml2-dev libxslt1-dev libgeos-dev python3-cairocffi tidy libtidy-dev binutils libproj-dev gdal-bin libpangocairo-1.0-0 pandoc +  - apt-get install -q -y git sed python3-pip libpq-dev python3-dev libjpeg-dev zlib1g-dev libxml2-dev libxslt1-dev libgeos-dev python3-cairocffi tidy libtidy-dev binutils libproj-dev gdal-bin libpangocairo-1.0-0 pandoc graphviz    - apt-get install -q -y locales    - echo "fr_FR.UTF-8 UTF-8" >> /etc/locale.gen    - dpkg-reconfigure --frontend=noninteractive locales && update-locale LANG=$LANG diff --git a/archaeological_context_records/migrations/0048_auto_20190704_1526.py b/archaeological_context_records/migrations/0048_auto_20190704_1526.py new file mode 100644 index 000000000..9e65a4b31 --- /dev/null +++ b/archaeological_context_records/migrations/0048_auto_20190704_1526.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.18 on 2019-07-04 15:26 +from __future__ import unicode_literals + +from django.db import migrations, models +import ishtar_common.models + + +class Migration(migrations.Migration): + +    dependencies = [ +        ('archaeological_context_records', '0047_auto_20190628_1257'), +    ] + +    operations = [ +        migrations.AddField( +            model_name='contextrecord', +            name='relation_bitmap_image', +            field=models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', null=True, upload_to=ishtar_common.models.get_image_path, verbose_name='Generated relation image (PNG)'), +        ), +        migrations.AddField( +            model_name='contextrecord', +            name='relation_bitmap_image_above', +            field=models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', null=True, upload_to=ishtar_common.models.get_image_path, verbose_name='Generated above relation image (PNG)'), +        ), +        migrations.AddField( +            model_name='contextrecord', +            name='relation_bitmap_image_bellow', +            field=models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', null=True, upload_to=ishtar_common.models.get_image_path, verbose_name='Generated bellow relation image (PNG)'), +        ), +        migrations.AddField( +            model_name='contextrecord', +            name='relation_dot', +            field=models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', null=True, upload_to=ishtar_common.models.get_image_path, verbose_name='Generated relation image (DOT)'), +        ), +        migrations.AddField( +            model_name='contextrecord', +            name='relation_dot_above', +            field=models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', null=True, upload_to=ishtar_common.models.get_image_path, verbose_name='Generated above relation image (DOT)'), +        ), +        migrations.AddField( +            model_name='contextrecord', +            name='relation_dot_bellow', +            field=models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', null=True, upload_to=ishtar_common.models.get_image_path, verbose_name='Generated bellow relation image (DOT)'), +        ), +        migrations.AddField( +            model_name='contextrecord', +            name='relation_image_above', +            field=models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', null=True, upload_to=ishtar_common.models.get_image_path, verbose_name='Generated above relation image (SVG)'), +        ), +        migrations.AddField( +            model_name='contextrecord', +            name='relation_image_bellow', +            field=models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', null=True, upload_to=ishtar_common.models.get_image_path, verbose_name='Generated bellow relation image (SVG)'), +        ), +        migrations.AddField( +            model_name='historicalcontextrecord', +            name='relation_bitmap_image', +            field=models.TextField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', max_length=100, null=True, verbose_name='Generated relation image (PNG)'), +        ), +        migrations.AddField( +            model_name='historicalcontextrecord', +            name='relation_bitmap_image_above', +            field=models.TextField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', max_length=100, null=True, verbose_name='Generated above relation image (PNG)'), +        ), +        migrations.AddField( +            model_name='historicalcontextrecord', +            name='relation_bitmap_image_bellow', +            field=models.TextField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', max_length=100, null=True, verbose_name='Generated bellow relation image (PNG)'), +        ), +        migrations.AddField( +            model_name='historicalcontextrecord', +            name='relation_dot', +            field=models.TextField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', max_length=100, null=True, verbose_name='Generated relation image (DOT)'), +        ), +        migrations.AddField( +            model_name='historicalcontextrecord', +            name='relation_dot_above', +            field=models.TextField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', max_length=100, null=True, verbose_name='Generated above relation image (DOT)'), +        ), +        migrations.AddField( +            model_name='historicalcontextrecord', +            name='relation_dot_bellow', +            field=models.TextField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', max_length=100, null=True, verbose_name='Generated bellow relation image (DOT)'), +        ), +        migrations.AddField( +            model_name='historicalcontextrecord', +            name='relation_image_above', +            field=models.TextField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', max_length=100, null=True, verbose_name='Generated above relation image (SVG)'), +        ), +        migrations.AddField( +            model_name='historicalcontextrecord', +            name='relation_image_bellow', +            field=models.TextField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', max_length=100, null=True, verbose_name='Generated bellow relation image (SVG)'), +        ), +    ] diff --git a/archaeological_context_records/tests.py b/archaeological_context_records/tests.py index 79d60bf09..a0a76ba78 100644 --- a/archaeological_context_records/tests.py +++ b/archaeological_context_records/tests.py @@ -37,6 +37,8 @@ from archaeological_operations.tests import OperationInitTest, \  from archaeological_operations import models as models_ope  from archaeological_context_records import models +from ishtar_common.utils import generate_relation_graph +  from ishtar_common.tests import WizardTest, WizardTestFormData as FormData, \      create_superuser, create_user, TestCase, AutocompleteTestBase, AcItem @@ -865,3 +867,98 @@ class AutocompleteTest(AutocompleteTestBase, TestCase):              label=base_name          )          return item, None + + +class GraphGenerationTest(ContextRecordInit, TestCase): +    fixtures = CONTEXT_RECORD_TOWNS_FIXTURES + +    def setUp(self): +        self.sym_rel_type, __ = models.RelationType.objects.get_or_create( +            symmetrical=True, txt_idx='sym', logical_relation='equal') +        self.rel_type_bellow, __ = models.RelationType.objects.get_or_create( +            symmetrical=False, txt_idx='bellow', logical_relation='bellow') +        self.rel_type_above, __ = models.RelationType.objects.get_or_create( +            symmetrical=False, txt_idx='above', logical_relation='above') +        self.create_context_record({"label": u"CR 1"}) +        self.create_context_record({"label": u"CR 2"}) +        self.create_context_record({"label": u"CR 3"}) +        self.create_context_record({"label": u"CR 4"}) +        self.create_context_record({"label": u"CR 1B"}) +        self.create_context_record({"label": u"CR 2B"}) +        self.create_context_record({"label": u"CR 3B"}) + +        cr_1 = self.context_records[0] +        self.cr_2 = cr_2 = self.context_records[1] +        cr_3 = self.context_records[2] +        cr_4 = self.context_records[3] +        cr_1B = self.context_records[4] +        cr_2B = self.context_records[5] +        cr_3B = self.context_records[6] + +        models.RecordRelations.objects.create( +            left_record=cr_2, right_record=cr_2B, +            relation_type=self.sym_rel_type) + +        models.RecordRelations.objects.create( +            left_record=cr_1, right_record=cr_2, +            relation_type=self.rel_type_bellow) +        models.RecordRelations.objects.create( +            left_record=cr_3, right_record=cr_2, +            relation_type=self.rel_type_above) +        models.RecordRelations.objects.create( +            left_record=cr_3, right_record=cr_4, +            relation_type=self.rel_type_bellow) + +        models.RecordRelations.objects.create( +            left_record=cr_1B, right_record=cr_2B, +            relation_type=self.rel_type_bellow) +        models.RecordRelations.objects.create( +            left_record=cr_3B, right_record=cr_2B, +            relation_type=self.rel_type_above) + +    def test_gen_relation_full(self): +        generate_relation_graph(self.cr_2) +        cr_2 = models.ContextRecord.objects.get(pk=self.cr_2.pk) +        self.assertIsNotNone(cr_2.relation_image) +        self.assertIsNotNone(cr_2.relation_bitmap_image) +        self.assertIsNotNone(cr_2.relation_dot) +        content = open(cr_2.relation_dot.path).read() +        self.assertIn('"CR 1"', content) +        self.assertIn('"CR 1B"', content) +        self.assertIn('"CR 2B"', content) +        self.assertIn('"CR 2",style=filled,fillcolor="#C6C0C0"', content) +        self.assertIn('"CR 3"', content) +        self.assertIn('"CR 3B"', content) +        self.assertIn('"CR 4"', content) + +    def test_gen_relation_above(self): +        generate_relation_graph(self.cr_2, render_above=False) +        cr_2 = models.ContextRecord.objects.get(pk=self.cr_2.pk) +        self.assertIsNotNone(cr_2.relation_image_above) +        self.assertIsNotNone(cr_2.relation_bitmap_image_above) +        self.assertIsNotNone(cr_2.relation_dot_above) +        content = open(cr_2.relation_dot_above.path).read() +        self.assertIn('"CR 1"', content) +        self.assertIn('"CR 1B"', content) +        self.assertIn('"CR 2B"', content) +        self.assertIn('"CR 2",style=filled,fillcolor="#C6C0C0"', content) +        self.assertNotIn('"CR 3B"', content) +        self.assertNotIn('"CR 3"', content) +        self.assertNotIn('"CR 4"', content) + +    def test_gen_relation_bellow(self): +        generate_relation_graph(self.cr_2, render_bellow=False) +        cr_2 = models.ContextRecord.objects.get(pk=self.cr_2.pk) +        self.assertIsNotNone(cr_2.relation_image_bellow) +        self.assertIsNotNone(cr_2.relation_bitmap_image_bellow) +        self.assertIsNotNone(cr_2.relation_dot_bellow) +        content = open(cr_2.relation_dot_bellow.path).read() +        self.assertNotIn('"CR 1"', content) +        self.assertNotIn('"CR 1B"', content) +        self.assertIn('"CR 2B"', content) +        self.assertIn('"CR 2",style=filled,fillcolor="#C6C0C0"', content) +        self.assertIn('"CR 3"', content) +        self.assertIn('"CR 3B"', content) +        self.assertIn('"CR 4"', content) + + diff --git a/archaeological_operations/migrations/0057_auto_20190704_1526.py b/archaeological_operations/migrations/0057_auto_20190704_1526.py new file mode 100644 index 000000000..291821d36 --- /dev/null +++ b/archaeological_operations/migrations/0057_auto_20190704_1526.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.18 on 2019-07-04 15:26 +from __future__ import unicode_literals + +from django.db import migrations, models +import ishtar_common.models + + +class Migration(migrations.Migration): + +    dependencies = [ +        ('archaeological_operations', '0056_auto_20190628_1257'), +    ] + +    operations = [ +        migrations.AddField( +            model_name='historicaloperation', +            name='relation_bitmap_image', +            field=models.TextField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', max_length=100, null=True, verbose_name='Generated relation image (PNG)'), +        ), +        migrations.AddField( +            model_name='historicaloperation', +            name='relation_bitmap_image_above', +            field=models.TextField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', max_length=100, null=True, verbose_name='Generated above relation image (PNG)'), +        ), +        migrations.AddField( +            model_name='historicaloperation', +            name='relation_bitmap_image_bellow', +            field=models.TextField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', max_length=100, null=True, verbose_name='Generated bellow relation image (PNG)'), +        ), +        migrations.AddField( +            model_name='historicaloperation', +            name='relation_dot', +            field=models.TextField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', max_length=100, null=True, verbose_name='Generated relation image (DOT)'), +        ), +        migrations.AddField( +            model_name='historicaloperation', +            name='relation_dot_above', +            field=models.TextField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', max_length=100, null=True, verbose_name='Generated above relation image (DOT)'), +        ), +        migrations.AddField( +            model_name='historicaloperation', +            name='relation_dot_bellow', +            field=models.TextField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', max_length=100, null=True, verbose_name='Generated bellow relation image (DOT)'), +        ), +        migrations.AddField( +            model_name='historicaloperation', +            name='relation_image_above', +            field=models.TextField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', max_length=100, null=True, verbose_name='Generated above relation image (SVG)'), +        ), +        migrations.AddField( +            model_name='historicaloperation', +            name='relation_image_bellow', +            field=models.TextField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', max_length=100, null=True, verbose_name='Generated bellow relation image (SVG)'), +        ), +        migrations.AddField( +            model_name='operation', +            name='relation_bitmap_image', +            field=models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', null=True, upload_to=ishtar_common.models.get_image_path, verbose_name='Generated relation image (PNG)'), +        ), +        migrations.AddField( +            model_name='operation', +            name='relation_bitmap_image_above', +            field=models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', null=True, upload_to=ishtar_common.models.get_image_path, verbose_name='Generated above relation image (PNG)'), +        ), +        migrations.AddField( +            model_name='operation', +            name='relation_bitmap_image_bellow', +            field=models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', null=True, upload_to=ishtar_common.models.get_image_path, verbose_name='Generated bellow relation image (PNG)'), +        ), +        migrations.AddField( +            model_name='operation', +            name='relation_dot', +            field=models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', null=True, upload_to=ishtar_common.models.get_image_path, verbose_name='Generated relation image (DOT)'), +        ), +        migrations.AddField( +            model_name='operation', +            name='relation_dot_above', +            field=models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', null=True, upload_to=ishtar_common.models.get_image_path, verbose_name='Generated above relation image (DOT)'), +        ), +        migrations.AddField( +            model_name='operation', +            name='relation_dot_bellow', +            field=models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', null=True, upload_to=ishtar_common.models.get_image_path, verbose_name='Generated bellow relation image (DOT)'), +        ), +        migrations.AddField( +            model_name='operation', +            name='relation_image_above', +            field=models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', null=True, upload_to=ishtar_common.models.get_image_path, verbose_name='Generated above relation image (SVG)'), +        ), +        migrations.AddField( +            model_name='operation', +            name='relation_image_bellow', +            field=models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', null=True, upload_to=ishtar_common.models.get_image_path, verbose_name='Generated bellow relation image (SVG)'), +        ), +    ] diff --git a/example_project/settings.py b/example_project/settings.py index 267370945..15ed14559 100644 --- a/example_project/settings.py +++ b/example_project/settings.py @@ -273,8 +273,8 @@ MAX_ATTEMPTS = 1  # django background tasks  MAX_UPLOAD_SIZE = 100  # in Mo -# if you want to generate relation graph provide the path to the "dot" program -DOT_BINARY = "" +# path to the "dot" program to generate graph +DOT_BINARY = "/usr/bin/dot"  TEST_RUNNER = 'ishtar_common.tests.ManagedModelTestRunner' diff --git a/install/ishtar-install b/install/ishtar-install index fd1934db5..f3dbfe189 100755 --- a/install/ishtar-install +++ b/install/ishtar-install @@ -455,7 +455,7 @@ EOF                            python3-django-registration libpangocairo-1.0-0 python3-requests \                            python3-bs4 python3-cffi python3-django-compressor pandoc libjs-jquery\                            python3-tidylib python3-lxml python3-pil python3-html5lib \ -                          python3-psycopg2 python3-gdal gettext memcached \ +                          python3-psycopg2 python3-gdal gettext memcached graphviz \                            python3-memcache python3-dbf python3-markdown \                            python3-reportlab python3-django-extensions python3-unidecode' )                  fi @@ -467,7 +467,7 @@ EOF                                       django-extensions==1.7.4' )                      ( set -x; $sh_c 'sleep 3; apt-get install -y -q \                            libpangocairo-1.0-0 python3-requests \ -                          python3-bs4 python3-cffi pandoc libjs-jquery \ +                          python3-bs4 python3-cffi pandoc libjs-jquery graphviz \                            python3-tidylib python3-lxml python3-imaging python3-html5lib \                            python3-psycopg2 python3-gdal gettext memcached \                            python3-memcache python3-dbf python3-markdown \ diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 5e7f6e9b3..e8b6fc518 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -1205,6 +1205,38 @@ class RelationItem(models.Model):          _("Generated relation image (SVG)"), null=True, blank=True,          upload_to=get_image_path, help_text=max_size_help()      ) +    relation_bitmap_image = models.FileField( +        _("Generated relation image (PNG)"), null=True, blank=True, +        upload_to=get_image_path, help_text=max_size_help() +    ) +    relation_dot = models.FileField( +        _("Generated relation image (DOT)"), null=True, blank=True, +        upload_to=get_image_path, help_text=max_size_help() +    ) +    relation_image_above = models.FileField( +        _("Generated above relation image (SVG)"), null=True, blank=True, +        upload_to=get_image_path, help_text=max_size_help() +    ) +    relation_dot_above = models.FileField( +        _("Generated above relation image (DOT)"), null=True, blank=True, +        upload_to=get_image_path, help_text=max_size_help() +    ) +    relation_bitmap_image_above = models.FileField( +        _("Generated above relation image (PNG)"), null=True, blank=True, +        upload_to=get_image_path, help_text=max_size_help() +    ) +    relation_image_bellow = models.FileField( +        _("Generated bellow relation image (SVG)"), null=True, blank=True, +        upload_to=get_image_path, help_text=max_size_help() +    ) +    relation_dot_bellow = models.FileField( +        _("Generated bellow relation image (DOT)"), null=True, blank=True, +        upload_to=get_image_path, help_text=max_size_help() +    ) +    relation_bitmap_image_bellow = models.FileField( +        _("Generated bellow relation image (PNG)"), null=True, blank=True, +        upload_to=get_image_path, help_text=max_size_help() +    )      class Meta:          abstract = True diff --git a/ishtar_common/utils.py b/ishtar_common/utils.py index 83bc56279..4f8dce853 100644 --- a/ishtar_common/utils.py +++ b/ishtar_common/utils.py @@ -17,6 +17,7 @@  # See the file COPYING for details. +from cairosvg import svg2png  from csv import QUOTE_ALL  import datetime  from functools import wraps @@ -1089,7 +1090,8 @@ def create_default_areas(models=None, verbose=False):  def get_relations_for_graph(rel_model, obj_pk, above_relations=None, -                            equal_relations=None, treated=None, styles=None): +                            equal_relations=None, treated=None, styles=None, +                            render_above=True, render_bellow=True):      """      Get all above and equal relations of an object (get all child and parent      relations) @@ -1099,6 +1101,8 @@ def get_relations_for_graph(rel_model, obj_pk, above_relations=None,      :param equal_relations: list of current equal_relations      :param treated: treated relation list to prevent circular call      :param styles: current styles +    :param render_above: render relation above the current object +    :param render_bellow: render relation bellow the current object      :return: above and equal relations list (each containing lists of two      members)      """ @@ -1114,63 +1118,81 @@ def get_relations_for_graph(rel_model, obj_pk, above_relations=None,          return above_relations, equal_relations, styles      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, substyles = get_relations_for_graph( -            rel_model, right_record, above_relations, equal_relations, treated, -            styles -        ) -        styles.update(substyles) -        error_style = "color=red" -        for r in ar: -            if r not in above_relations: -                above_relations.append(r) -            reverse_rel = tuple(reversed(r)) -            if reverse_rel in above_relations: -                # circular -                if r not in styles: -                    styles[r] = [] -                if reverse_rel not in styles: -                    styles[reverse_rel] = [] - -                if error_style not in styles[r]: -                    styles[r].append(error_style) -                if error_style not in styles[reverse_rel]: -                    styles[reverse_rel].append(error_style) -            if r[0] == r[1]: -                # same entity -                if r not in styles: -                    styles[r] = [] -                if error_style not in styles[r]: -                    styles[r].append("color=red") -        for r in er: -            if r not in equal_relations: -                equal_relations.append(r) + +    for q, inverse in ( +            (rel_model.objects.filter( +                left_record_id=obj_pk, +                relation_type__logical_relation__isnull=False), False), +            (rel_model.objects.filter( +                right_record_id=obj_pk, +                relation_type__logical_relation__isnull=False), True)): +        q = q.values("left_record_id",'right_record_id', +                     'relation_type__logical_relation') +        get_above, get_bellow = render_above, render_bellow +        if inverse and (not render_above or not render_bellow): +            get_above, get_bellow = not render_above, not render_bellow + +        for relation in q.all(): +            logical_relation = relation['relation_type__logical_relation'] +            left_record = relation['left_record_id'] +            right_record = relation['right_record_id'] +            if not logical_relation: +                continue +            elif get_bellow and logical_relation == 'above' and \ +                    (left_record, right_record) not in above_relations: +                above_relations.append((left_record, right_record)) +            elif get_above and logical_relation == 'bellow' and \ +                    (right_record, left_record) not in above_relations: +                above_relations.append((right_record, left_record)) +            elif logical_relation == 'equal' and \ +                    (right_record, left_record) not in equal_relations and \ +                    (left_record, right_record) not in equal_relations: +                equal_relations.append((left_record, right_record)) +            else: +                continue + +            if right_record == obj_pk: +                other_record = left_record +            else: +                other_record = right_record + +            ar, er, substyles = get_relations_for_graph( +                rel_model, other_record, above_relations, equal_relations, +                treated, styles, render_above=render_above, +                render_bellow=render_bellow +            ) +            styles.update(substyles) +            error_style = "color=red" +            for r in ar: +                if r not in above_relations: +                    above_relations.append(r) +                reverse_rel = tuple(reversed(r)) +                if reverse_rel in above_relations: +                    # circular +                    if r not in styles: +                        styles[r] = [] +                    if reverse_rel not in styles: +                        styles[reverse_rel] = [] + +                    if error_style not in styles[r]: +                        styles[r].append(error_style) +                    if error_style not in styles[reverse_rel]: +                        styles[reverse_rel].append(error_style) +                if r[0] == r[1]: +                    # same entity +                    if r not in styles: +                        styles[r] = [] +                    if error_style not in styles[r]: +                        styles[r].append("color=red") +            for r in er: +                if r not in equal_relations: +                    equal_relations.append(r)      return above_relations, equal_relations, styles -def generate_relation_graph(obj, debug=False): +def generate_relation_graph(obj, highlight_current=True, +                            render_above=True, render_bellow=True, +                            debug=False):      if not settings.DOT_BINARY:          return @@ -1179,8 +1201,8 @@ def generate_relation_graph(obj, debug=False):      # get relations      above_relations, equal_relations, styles = get_relations_for_graph( -        rel_model, obj.pk) -    print(styles) +        rel_model, obj.pk, render_above=render_above, +        render_bellow=render_bellow)      if not above_relations and not equal_relations:          obj.relation_image = None          obj.save() @@ -1201,14 +1223,14 @@ def generate_relation_graph(obj, debug=False):                  described.append(left_pk)                  left = model.objects.get(pk=left_pk)                  style = 'label="{}"'.format(left.relation_label) -                if left.pk == obj.pk: +                if left.pk == obj.pk and highlight_current:                      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: +                if right.pk == obj.pk and highlight_current:                      style += ',style=filled,fillcolor="#C6C0C0"'                  dot_str += u'item{}[{}];\n'.format(right.pk, style)              if not directed:  # on the same level @@ -1226,6 +1248,18 @@ def generate_relation_graph(obj, debug=False):      with open(dot_name, 'w') as dot_file:          dot_file.write(dot_str) +    if not render_above: +        suffix = "_above" +    elif not render_bellow: +        suffix = "_bellow" +    else: +        suffix = "" + +    with open(dot_name, "r") as dot_file: +        django_file = File(dot_file) +        attr = "relation_dot" + suffix +        getattr(obj, attr).save("relations.dot", django_file, save=True) +      # execute dot program      args = (settings.DOT_BINARY, "-Tsvg", dot_name) @@ -1234,10 +1268,20 @@ def generate_relation_graph(obj, debug=False):      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) +        attr = "relation_image" + suffix +        getattr(obj, attr).save("relations.svg", django_file, save=True) + +    png_name = tempdir + os.path.sep + "relations.png" + +    with open(png_name, "wb") as png_file: +        svg2png(open(svg_tmp_name, 'rb').read(), +                write_to=png_file) +    with open(png_name, "rb") as png_file: +        django_file = File(png_file) +        attr = "relation_bitmap_image" + suffix +        getattr(obj, attr).save("relations.png", django_file, save=True)      if debug:          print(u"DOT file: {}. Tmp SVG file: {}.".format(dot_name, svg_tmp_name))  | 
