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='Важность (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}' 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})'