Add conflictology map mode and fix graph rendering reliability.
Support conflict types, involvement sizing, and map-type toggles while keeping polar sector layout; improve GraphView init retries and edge matching on network maps. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('contacts', '0008_alter_relation_interaction_intensity'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='networkmap',
|
||||
name='conflictology_enabled',
|
||||
field=models.BooleanField(default=False, verbose_name='Режим конфликтологии'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='networkmap',
|
||||
name='conflict_subject',
|
||||
field=models.CharField(blank=True, max_length=255, verbose_name='Предмет конфликта (центр карты)'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='networkmapmembership',
|
||||
name='conflict_involvement',
|
||||
field=models.PositiveSmallIntegerField(
|
||||
default=3,
|
||||
validators=[MinValueValidator(1), MaxValueValidator(5)],
|
||||
verbose_name='Вовлечённость в конфликт (1–5)',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='relation',
|
||||
name='relation_type',
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
('colleague', 'Коллега'),
|
||||
('friend', 'Друг'),
|
||||
('family', 'Родственник'),
|
||||
('acquaintance', 'Знакомый'),
|
||||
('business', 'Деловой партнёр'),
|
||||
('other', 'Другое'),
|
||||
('conflict_open', 'Открытый конфликт'),
|
||||
('conflict_tension', 'Напряжение'),
|
||||
('conflict_alliance', 'Союз / поддержка'),
|
||||
('conflict_neutral', 'Нейтральная связь'),
|
||||
],
|
||||
default='acquaintance',
|
||||
max_length=50,
|
||||
verbose_name='Тип связи',
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,32 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def migrate_conflictology_to_map_type(apps, schema_editor):
|
||||
NetworkMap = apps.get_model('contacts', 'NetworkMap')
|
||||
NetworkMapType = apps.get_model('contacts', 'NetworkMapType')
|
||||
|
||||
for network_map in NetworkMap.objects.filter(conflictology_enabled=True).select_related('map_type'):
|
||||
map_type = network_map.map_type
|
||||
if map_type and not map_type.conflictology_enabled:
|
||||
map_type.conflictology_enabled = True
|
||||
map_type.save(update_fields=['conflictology_enabled'])
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('contacts', '0009_conflictology'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='networkmaptype',
|
||||
name='conflictology_enabled',
|
||||
field=models.BooleanField(default=False, verbose_name='Режим конфликтологии'),
|
||||
),
|
||||
migrations.RunPython(migrate_conflictology_to_map_type, migrations.RunPython.noop),
|
||||
migrations.RemoveField(
|
||||
model_name='networkmap',
|
||||
name='conflictology_enabled',
|
||||
),
|
||||
]
|
||||
@@ -2,8 +2,8 @@ from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db import models
|
||||
|
||||
from core.choices import (
|
||||
ALL_RELATION_TYPES,
|
||||
INTERACTION_INTENSITY,
|
||||
RELATION_TYPES,
|
||||
)
|
||||
|
||||
|
||||
@@ -35,6 +35,10 @@ class NetworkMapType(models.Model):
|
||||
sectors = models.JSONField(default=list, verbose_name='Секторы')
|
||||
circles = models.JSONField(default=list, verbose_name='Круги')
|
||||
is_default = models.BooleanField(default=False, verbose_name='Тип по умолчанию')
|
||||
conflictology_enabled = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name='Режим конфликтологии',
|
||||
)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
@@ -58,6 +62,11 @@ class NetworkMap(models.Model):
|
||||
related_name='maps',
|
||||
verbose_name='Тип карты',
|
||||
)
|
||||
conflict_subject = models.CharField(
|
||||
max_length=255,
|
||||
blank=True,
|
||||
verbose_name='Предмет конфликта (центр карты)',
|
||||
)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
@@ -100,6 +109,11 @@ class NetworkMapMembership(models.Model):
|
||||
validators=[MinValueValidator(1), MaxValueValidator(5)],
|
||||
verbose_name='Важность (1–5)',
|
||||
)
|
||||
conflict_involvement = models.PositiveSmallIntegerField(
|
||||
default=3,
|
||||
validators=[MinValueValidator(1), MaxValueValidator(5)],
|
||||
verbose_name='Вовлечённость в конфликт (1–5)',
|
||||
)
|
||||
map_angle = models.FloatField(null=True, blank=True, verbose_name='Угол позиции на карте')
|
||||
map_radius_ratio = models.FloatField(
|
||||
null=True,
|
||||
@@ -136,7 +150,7 @@ class Relation(models.Model):
|
||||
)
|
||||
relation_type = models.CharField(
|
||||
max_length=50,
|
||||
choices=RELATION_TYPES,
|
||||
choices=ALL_RELATION_TYPES,
|
||||
default='acquaintance',
|
||||
verbose_name='Тип связи',
|
||||
)
|
||||
|
||||
@@ -48,7 +48,7 @@ class NetworkMapTypeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = NetworkMapType
|
||||
fields = [
|
||||
'id', 'name', 'sectors', 'circles', 'is_default',
|
||||
'id', 'name', 'sectors', 'circles', 'is_default', 'conflictology_enabled',
|
||||
'created_at', 'updated_at',
|
||||
]
|
||||
read_only_fields = ['id', 'is_default', 'created_at', 'updated_at']
|
||||
@@ -77,7 +77,7 @@ class NetworkMapSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = NetworkMap
|
||||
fields = [
|
||||
'id', 'name', 'description', 'map_type',
|
||||
'id', 'name', 'description', 'map_type', 'conflict_subject',
|
||||
'created_at', 'updated_at', 'memberships_count',
|
||||
]
|
||||
read_only_fields = ['id', 'created_at', 'updated_at', 'memberships_count']
|
||||
@@ -102,7 +102,7 @@ class NetworkMapMembershipSerializer(serializers.ModelSerializer):
|
||||
model = NetworkMapMembership
|
||||
fields = [
|
||||
'id', 'map', 'map_name', 'contact', 'contact_name',
|
||||
'life_sphere', 'network_circle', 'importance',
|
||||
'life_sphere', 'network_circle', 'importance', 'conflict_involvement',
|
||||
'map_angle', 'map_radius_ratio',
|
||||
'created_at', 'updated_at',
|
||||
]
|
||||
|
||||
@@ -30,10 +30,20 @@ RELATION_TYPES = [
|
||||
('other', 'Другое'),
|
||||
]
|
||||
|
||||
CONFLICT_RELATION_TYPES = [
|
||||
('conflict_open', 'Открытый конфликт'),
|
||||
('conflict_tension', 'Напряжение'),
|
||||
('conflict_alliance', 'Союз / поддержка'),
|
||||
('conflict_neutral', 'Нейтральная связь'),
|
||||
]
|
||||
|
||||
ALL_RELATION_TYPES = RELATION_TYPES + CONFLICT_RELATION_TYPES
|
||||
|
||||
|
||||
def choices_payload():
|
||||
return {
|
||||
'relation_types': [{'value': v, 'label': l} for v, l in RELATION_TYPES],
|
||||
'conflict_relation_types': [{'value': v, 'label': l} for v, l in CONFLICT_RELATION_TYPES],
|
||||
'life_spheres': [{'value': v, 'label': l} for v, l in LIFE_SPHERES],
|
||||
'network_circles': [{'value': v, 'label': l} for v, l in NETWORK_CIRCLES],
|
||||
'interaction_intensities': [{'value': v, 'label': l} for v, l in INTERACTION_INTENSITY],
|
||||
|
||||
@@ -17,6 +17,7 @@ def node_from_membership(membership):
|
||||
'life_sphere': membership.life_sphere,
|
||||
'network_circle': membership.network_circle,
|
||||
'importance': membership.importance,
|
||||
'conflict_involvement': membership.conflict_involvement,
|
||||
'map_angle': membership.map_angle,
|
||||
'map_radius_ratio': membership.map_radius_ratio,
|
||||
'membership_id': membership.id,
|
||||
@@ -45,11 +46,15 @@ def build_full_graph():
|
||||
|
||||
def build_network_map_graph(map_id=None):
|
||||
if not map_id:
|
||||
default_map = NetworkMap.objects.order_by('id').first()
|
||||
default_map = NetworkMap.objects.select_related('map_type').order_by('id').first()
|
||||
if not default_map:
|
||||
return {'nodes': [], 'edges': []}
|
||||
return {'nodes': [], 'edges': [], 'conflictology': False, 'conflict_subject': ''}
|
||||
map_id = default_map.id
|
||||
|
||||
network_map = NetworkMap.objects.select_related('map_type').filter(pk=map_id).first()
|
||||
if not network_map:
|
||||
return {'nodes': [], 'edges': [], 'conflictology': False, 'conflict_subject': ''}
|
||||
|
||||
memberships = list(
|
||||
NetworkMapMembership.objects.filter(map_id=map_id)
|
||||
.select_related('contact')
|
||||
@@ -63,4 +68,9 @@ def build_network_map_graph(map_id=None):
|
||||
for r in relations
|
||||
if r.source_id in allowed_ids and r.target_id in allowed_ids
|
||||
]
|
||||
return {'nodes': nodes, 'edges': edges}
|
||||
return {
|
||||
'nodes': nodes,
|
||||
'edges': edges,
|
||||
'conflictology': network_map.map_type.conflictology_enabled,
|
||||
'conflict_subject': network_map.conflict_subject or network_map.name,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user