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>
This commit is contained in:
2026-06-28 21:10:16 +03:00
co-authored by Cursor
parent 85b8b2e9b8
commit 4eb4c145b6
40 changed files with 2301 additions and 117 deletions
+39 -3
View File
@@ -1,8 +1,17 @@
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()
@@ -37,10 +46,22 @@ class RelationSerializer(serializers.ModelSerializer):
read_only_fields = ['id', 'created_at', 'source_name', 'target_name']
def validate(self, data):
if data.get('source') == data.get('target'):
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
@@ -61,8 +82,13 @@ class NetworkMapTypeSerializer(serializers.ModelSerializer):
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 NetworkMapType.objects.filter(is_default=True).exists():
if self._default_exists():
validated_data['is_default'] = False
return super().create(validated_data)
@@ -82,12 +108,22 @@ class NetworkMapSerializer(serializers.ModelSerializer):
]
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 = NetworkMapType.objects.filter(is_default=True).first()
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):