Add multiple network maps, graph UX improvements, and full backup import.
Support per-map contact membership with scoped graph views, relation editing on edges, layout caching, and automatic detection of full JSON backups so contacts and relations import together. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -19,6 +19,7 @@ NETWORK_CIRCLES = [
|
||||
|
||||
INTERACTION_INTENSITY = [
|
||||
('intense', 'Интенсивные контакты'),
|
||||
('periodic', 'Периодические контакты'),
|
||||
('sparse', 'Редкие контакты'),
|
||||
]
|
||||
|
||||
@@ -32,6 +33,50 @@ class Contact(models.Model):
|
||||
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 NetworkMap(models.Model):
|
||||
"""Карта сети — отдельный контекст для визуализации подмножества контактов."""
|
||||
|
||||
name = models.CharField(max_length=255, verbose_name='Название')
|
||||
description = 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 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,
|
||||
choices=LIFE_SPHERES,
|
||||
@@ -49,10 +94,6 @@ class Contact(models.Model):
|
||||
validators=[MinValueValidator(1), MaxValueValidator(5)],
|
||||
verbose_name='Важность (1–5)',
|
||||
)
|
||||
include_on_network_map = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name='Показывать на карте сети',
|
||||
)
|
||||
map_angle = models.FloatField(null=True, blank=True, verbose_name='Угол позиции на карте')
|
||||
map_radius_ratio = models.FloatField(
|
||||
null=True,
|
||||
@@ -64,12 +105,12 @@ class Contact(models.Model):
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
verbose_name = 'Контакт'
|
||||
verbose_name_plural = 'Контакты'
|
||||
unique_together = ('map', 'contact')
|
||||
verbose_name = 'Участие на карте'
|
||||
verbose_name_plural = 'Участия на картах'
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
return f'{self.contact} на {self.map}'
|
||||
|
||||
|
||||
RELATION_TYPES = [
|
||||
|
||||
Reference in New Issue
Block a user