Files
social-graph/backend/contacts/models.py
T
gitrusprusandCursor 85b8b2e9b8 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>
2026-06-25 11:51:06 +03:00

173 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from core.choices import (
ALL_RELATION_TYPES,
INTERACTION_INTENSITY,
)
class Contact(models.Model):
"""Контакт в социальном графе."""
name = models.CharField(max_length=255, verbose_name='Имя')
email = models.EmailField(blank=True, verbose_name='Email')
phone = models.CharField(max_length=50, blank=True, verbose_name='Телефон')
organization = models.CharField(max_length=255, blank=True, verbose_name='Организация')
position = models.CharField(max_length=255, blank=True, verbose_name='Должность')
notes = models.TextField(blank=True, verbose_name='Заметки')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['name']
verbose_name = 'Контакт'
verbose_name_plural = 'Контакты'
def __str__(self):
return self.name
class NetworkMapType(models.Model):
"""Тип карты сети: настраиваемые секторы и концентрические круги."""
name = models.CharField(max_length=255, verbose_name='Название типа')
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)
class Meta:
ordering = ['name']
verbose_name = 'Тип карты сети'
verbose_name_plural = 'Типы карт сети'
def __str__(self):
return self.name
class NetworkMap(models.Model):
"""Карта сети — отдельный контекст для визуализации подмножества контактов."""
name = models.CharField(max_length=255, verbose_name='Название')
description = models.TextField(blank=True, verbose_name='Описание')
map_type = models.ForeignKey(
NetworkMapType,
on_delete=models.PROTECT,
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)
class Meta:
ordering = ['name']
verbose_name = 'Карта сети'
verbose_name_plural = 'Карты сети'
def __str__(self):
return self.name
class NetworkMapMembership(models.Model):
"""Участие контакта на конкретной карте сети."""
map = models.ForeignKey(
NetworkMap,
on_delete=models.CASCADE,
related_name='memberships',
verbose_name='Карта',
)
contact = models.ForeignKey(
Contact,
on_delete=models.CASCADE,
related_name='map_memberships',
verbose_name='Контакт',
)
life_sphere = models.CharField(
max_length=32,
default='other',
verbose_name='Сфера жизни',
)
network_circle = models.CharField(
max_length=32,
default='productivity',
verbose_name='Круг сети',
)
importance = models.PositiveSmallIntegerField(
default=3,
validators=[MinValueValidator(1), MaxValueValidator(5)],
verbose_name='Важность (15)',
)
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,
blank=True,
validators=[MinValueValidator(0), MaxValueValidator(1)],
verbose_name='Радиус позиции на карте (доля)',
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
unique_together = ('map', 'contact')
verbose_name = 'Участие на карте'
verbose_name_plural = 'Участия на картах'
def __str__(self):
return f'{self.contact} на {self.map}'
class Relation(models.Model):
"""Связь между двумя контактами."""
source = models.ForeignKey(
Contact,
on_delete=models.CASCADE,
related_name='relations_as_source',
verbose_name='Источник',
)
target = models.ForeignKey(
Contact,
on_delete=models.CASCADE,
related_name='relations_as_target',
verbose_name='Цель',
)
relation_type = models.CharField(
max_length=50,
choices=ALL_RELATION_TYPES,
default='acquaintance',
verbose_name='Тип связи',
)
description = models.CharField(max_length=255, blank=True, verbose_name='Описание')
interaction_intensity = models.CharField(
max_length=32,
choices=INTERACTION_INTENSITY,
default='intense',
verbose_name='Интенсивность общения',
)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = ('source', 'target')
verbose_name = 'Связь'
verbose_name_plural = 'Связи'
def __str__(self):
return f'{self.source}{self.target} ({self.relation_type})'