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>
144 lines
5.1 KiB
Python
144 lines
5.1 KiB
Python
from rest_framework import serializers
|
|
from .models import Contact, Relation, NetworkMap, NetworkMapMembership, NetworkMapType
|
|
from .map_type_validation import validate_map_type_payload
|
|
|
|
|
|
class ContactSerializer(serializers.ModelSerializer):
|
|
relations_count = serializers.SerializerMethodField()
|
|
|
|
class Meta:
|
|
model = Contact
|
|
fields = [
|
|
'id', 'name', 'email', 'phone',
|
|
'organization', 'position', 'notes',
|
|
'created_at', 'updated_at', 'relations_count',
|
|
]
|
|
read_only_fields = ['id', 'created_at', 'updated_at', 'relations_count']
|
|
|
|
def get_relations_count(self, obj):
|
|
return (
|
|
obj.relations_as_source.count() +
|
|
obj.relations_as_target.count()
|
|
)
|
|
|
|
|
|
class RelationSerializer(serializers.ModelSerializer):
|
|
source_name = serializers.CharField(source='source.name', read_only=True)
|
|
target_name = serializers.CharField(source='target.name', read_only=True)
|
|
|
|
class Meta:
|
|
model = Relation
|
|
fields = [
|
|
'id', 'source', 'source_name',
|
|
'target', 'target_name',
|
|
'relation_type', 'description', 'interaction_intensity',
|
|
'created_at',
|
|
]
|
|
read_only_fields = ['id', 'created_at', 'source_name', 'target_name']
|
|
|
|
def validate(self, data):
|
|
if data.get('source') == data.get('target'):
|
|
raise serializers.ValidationError(
|
|
'Нельзя создать связь контакта с самим собой.'
|
|
)
|
|
return data
|
|
|
|
|
|
class NetworkMapTypeSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = NetworkMapType
|
|
fields = [
|
|
'id', 'name', 'sectors', 'circles', 'is_default', 'conflictology_enabled',
|
|
'created_at', 'updated_at',
|
|
]
|
|
read_only_fields = ['id', 'is_default', 'created_at', 'updated_at']
|
|
|
|
def validate(self, data):
|
|
sectors = data.get('sectors', getattr(self.instance, 'sectors', None))
|
|
circles = data.get('circles', getattr(self.instance, 'circles', None))
|
|
errors = validate_map_type_payload(sectors, circles)
|
|
if errors:
|
|
raise serializers.ValidationError(errors)
|
|
return data
|
|
|
|
def create(self, validated_data):
|
|
if NetworkMapType.objects.filter(is_default=True).exists():
|
|
validated_data['is_default'] = False
|
|
return super().create(validated_data)
|
|
|
|
|
|
class NetworkMapSerializer(serializers.ModelSerializer):
|
|
memberships_count = serializers.SerializerMethodField()
|
|
map_type = serializers.PrimaryKeyRelatedField(
|
|
queryset=NetworkMapType.objects.all(),
|
|
required=False,
|
|
)
|
|
|
|
class Meta:
|
|
model = NetworkMap
|
|
fields = [
|
|
'id', 'name', 'description', 'map_type', 'conflict_subject',
|
|
'created_at', 'updated_at', 'memberships_count',
|
|
]
|
|
read_only_fields = ['id', 'created_at', 'updated_at', 'memberships_count']
|
|
|
|
def validate(self, data):
|
|
if not data.get('map_type') and not getattr(self.instance, 'map_type_id', None):
|
|
default_type = NetworkMapType.objects.filter(is_default=True).first()
|
|
if not default_type:
|
|
raise serializers.ValidationError({'map_type': 'Нет типа карты по умолчанию.'})
|
|
data['map_type'] = default_type
|
|
return data
|
|
|
|
def get_memberships_count(self, obj):
|
|
return obj.memberships.count()
|
|
|
|
|
|
class NetworkMapMembershipSerializer(serializers.ModelSerializer):
|
|
contact_name = serializers.CharField(source='contact.name', read_only=True)
|
|
map_name = serializers.CharField(source='map.name', read_only=True)
|
|
|
|
class Meta:
|
|
model = NetworkMapMembership
|
|
fields = [
|
|
'id', 'map', 'map_name', 'contact', 'contact_name',
|
|
'life_sphere', 'network_circle', 'importance', 'conflict_involvement',
|
|
'map_angle', 'map_radius_ratio',
|
|
'created_at', 'updated_at',
|
|
]
|
|
read_only_fields = ['id', 'created_at', 'updated_at', 'contact_name', 'map_name']
|
|
|
|
|
|
class GraphSerializer(serializers.Serializer):
|
|
"""Граф для vis.js: nodes + edges."""
|
|
|
|
nodes = serializers.SerializerMethodField()
|
|
edges = serializers.SerializerMethodField()
|
|
|
|
def get_nodes(self, obj):
|
|
contacts = Contact.objects.all()
|
|
return [
|
|
{
|
|
'id': c.id,
|
|
'label': c.name,
|
|
'title': f'{c.organization}\n{c.position}'.strip() or c.name,
|
|
'group': c.organization or 'default',
|
|
}
|
|
for c in contacts
|
|
]
|
|
|
|
def get_edges(self, obj):
|
|
relations = Relation.objects.select_related('source', 'target').all()
|
|
return [
|
|
{
|
|
'id': r.id,
|
|
'from': r.source_id,
|
|
'to': r.target_id,
|
|
'label': r.get_relation_type_display(),
|
|
'title': r.description or r.get_relation_type_display(),
|
|
'relation_type': r.relation_type,
|
|
'interaction_intensity': r.interaction_intensity,
|
|
}
|
|
for r in relations
|
|
]
|