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)) |