Files
social-graph/backend/contacts/serializers.py
T
gitrusprusandCursor 4eb4c145b6 Add JWT auth with per-user data isolation and account settings.
Users can register, log in, and manage profile/password in a personal account page; server data is scoped by owner across contacts, maps, tags, and import.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-28 21:10:16 +03:00

180 lines
6.7 KiB
Python

from rest_framework import serializers
from core.access import use_jwt_auth
from .models import Contact, Relation, NetworkMap, NetworkMapMembership, NetworkMapType
from .map_type_validation import validate_map_type_payload
def scoped_map_types_queryset(request):
qs = NetworkMapType.objects.all()
if use_jwt_auth() and request and request.user.is_authenticated:
return qs.filter(owner=request.user)
return qs
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):
request = self.context.get('request')
source = data.get('source') or getattr(self.instance, 'source', None)
target = data.get('target') or getattr(self.instance, 'target', None)
if source and target and source == target:
raise serializers.ValidationError(
'Нельзя создать связь контакта с самим собой.'
)
if use_jwt_auth() and request and request.user.is_authenticated:
user = request.user
for contact in (source, target):
if contact and contact.owner_id != user.id:
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 _default_exists(self):
request = self.context.get('request')
qs = scoped_map_types_queryset(request)
return qs.filter(is_default=True).exists()
def create(self, validated_data):
if self._default_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 __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
request = self.context.get('request')
self.fields['map_type'].queryset = scoped_map_types_queryset(request)
def validate(self, data):
request = self.context.get('request')
if not data.get('map_type') and not getattr(self.instance, 'map_type_id', None):
default_type = scoped_map_types_queryset(request).filter(is_default=True).first()
if not default_type:
raise serializers.ValidationError({'map_type': 'Нет типа карты по умолчанию.'})
data['map_type'] = default_type
map_type = data.get('map_type') or getattr(self.instance, 'map_type', None)
if use_jwt_auth() and request and request.user.is_authenticated and map_type:
if map_type.owner_id != request.user.id:
raise serializers.ValidationError({'map_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
]