Files
social-graph/backend/contacts/models.py
T
gitrusprusandCursor 5155b3a37a 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>
2026-06-24 17:54:51 +03:00

163 lines
5.3 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
LIFE_SPHERES = [
('work', 'Работа'),
('study', 'Учёба'),
('hobby', 'Хобби'),
('family', 'Семья'),
('health', 'Здоровье'),
('other', 'Другое'),
]
NETWORK_CIRCLES = [
('support', 'Круг поддержки'),
('productivity', 'Круг продуктивности'),
('development', 'Круг развития'),
]
INTERACTION_INTENSITY = [
('intense', 'Интенсивные контакты'),
('periodic', 'Периодические контакты'),
('sparse', 'Редкие контакты'),
]
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 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,
default='other',
verbose_name='Сфера жизни',
)
network_circle = models.CharField(
max_length=32,
choices=NETWORK_CIRCLES,
default='productivity',
verbose_name='Круг сети',
)
importance = models.PositiveSmallIntegerField(
default=3,
validators=[MinValueValidator(1), MaxValueValidator(5)],
verbose_name='Важность (15)',
)
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}'
RELATION_TYPES = [
('colleague', 'Коллега'),
('friend', 'Друг'),
('family', 'Родственник'),
('acquaintance', 'Знакомый'),
('business', 'Деловой партнёр'),
('other', 'Другое'),
]
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=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})'