""" Task serializers for KeepItGoing API. """ from rest_framework import serializers from django.contrib.auth import get_user_model from .models import Task, Tag, TimeEntry, TaskShare User = get_user_model() class TagSerializer(serializers.ModelSerializer): """Serializer for tags.""" task_count = serializers.SerializerMethodField() class Meta: model = Tag fields = [ 'id', 'name', 'description', 'color', 'icon', 'sort_order', 'is_archived', 'task_count', 'sync_id', 'created_at', 'updated_at' ] read_only_fields = ['id', 'sync_id', 'created_at', 'updated_at'] def get_task_count(self, obj): return obj.tasks.filter(status__in=['pending', 'in_progress']).count() def create(self, validated_data): validated_data['user'] = self.context['request'].user return super().create(validated_data) class TimeEntrySerializer(serializers.ModelSerializer): """Serializer for time entries.""" is_running = serializers.ReadOnlyField() class Meta: model = TimeEntry fields = [ 'id', 'task', 'started_at', 'ended_at', 'duration_seconds', 'notes', 'is_running', 'sync_id', 'created_at', 'updated_at' ] read_only_fields = ['id', 'sync_id', 'created_at', 'updated_at', 'duration_seconds'] def create(self, validated_data): validated_data['user'] = self.context['request'].user return super().create(validated_data) class TaskSerializer(serializers.ModelSerializer): """Serializer for tasks.""" tags = TagSerializer(many=True, read_only=True) tag_ids = serializers.PrimaryKeyRelatedField( queryset=Tag.objects.all(), many=True, write_only=True, required=False, source='tags' ) tag_sync_ids = serializers.SerializerMethodField() subtasks = serializers.SerializerMethodField() is_overdue = serializers.ReadOnlyField() total_time_spent = serializers.ReadOnlyField() parent_sync_id = serializers.UUIDField(source='parent.sync_id', read_only=True, allow_null=True) class Meta: model = Task fields = [ 'id', 'parent', 'parent_sync_id', 'title', 'description', 'status', 'priority', 'due_date', 'due_time', 'reminder_at', 'completed_at', 'recurrence', 'recurrence_rule', 'recurrence_end_date', 'tags', 'tag_ids', 'tag_sync_ids', 'subtasks', 'sort_order', 'is_overdue', 'total_time_spent', 'sync_id', 'created_at', 'updated_at' ] read_only_fields = ['id', 'sync_id', 'created_at', 'updated_at', 'completed_at'] def get_tag_sync_ids(self, obj): """Return list of tag sync_ids for sync purposes.""" return [str(tag.sync_id) for tag in obj.tags.all()] def get_subtasks(self, obj): # Only include subtasks for top-level tasks to avoid recursion if obj.parent is None: subtasks = obj.subtasks.all() return TaskSerializer(subtasks, many=True, context=self.context).data return [] def create(self, validated_data): tags = validated_data.pop('tags', []) validated_data['user'] = self.context['request'].user task = super().create(validated_data) task.tags.set(tags) return task def update(self, instance, validated_data): tags = validated_data.pop('tags', None) task = super().update(instance, validated_data) if tags is not None: task.tags.set(tags) return task class TaskListSerializer(serializers.ModelSerializer): """Lightweight serializer for task lists.""" tags = TagSerializer(many=True, read_only=True) subtask_count = serializers.SerializerMethodField() is_overdue = serializers.ReadOnlyField() class Meta: model = Task fields = [ 'id', 'title', 'status', 'priority', 'due_date', 'due_time', 'tags', 'subtask_count', 'is_overdue', 'sort_order', 'sync_id', 'updated_at' ] def get_subtask_count(self, obj): return obj.subtasks.count() class TaskSyncSerializer(serializers.ModelSerializer): """Serializer for tasks in sync responses - uses sync_id as id.""" id = serializers.UUIDField(source='sync_id', read_only=True) sync_id = serializers.UUIDField(read_only=True) tag_sync_ids = serializers.SerializerMethodField() parent_sync_id = serializers.UUIDField(source='parent.sync_id', read_only=True, allow_null=True) class Meta: model = Task fields = [ 'id', 'sync_id', 'parent', 'parent_sync_id', 'title', 'description', 'status', 'priority', 'due_date', 'due_time', 'reminder_at', 'completed_at', 'recurrence', 'recurrence_rule', 'recurrence_end_date', 'tag_sync_ids', 'sort_order', 'is_deleted', 'created_at', 'updated_at' ] def get_tag_sync_ids(self, obj): """Return list of tag sync_ids for sync purposes.""" return [str(tag.sync_id) for tag in obj.tags.all()] class TagSyncSerializer(serializers.ModelSerializer): """Serializer for tags in sync responses - uses sync_id as id.""" id = serializers.UUIDField(source='sync_id', read_only=True) sync_id = serializers.UUIDField(read_only=True) class Meta: model = Tag fields = ['id', 'sync_id', 'name', 'description', 'color', 'icon', 'sort_order', 'is_archived', 'is_deleted', 'created_at', 'updated_at'] class TimeEntrySyncSerializer(serializers.ModelSerializer): """Serializer for time entries in sync responses - uses sync_id as id.""" id = serializers.UUIDField(source='sync_id', read_only=True) sync_id = serializers.UUIDField(read_only=True) task = serializers.UUIDField(source='task.sync_id', read_only=True) class Meta: model = TimeEntry fields = ['id', 'sync_id', 'task', 'started_at', 'ended_at', 'notes', 'is_deleted', 'created_at', 'updated_at'] class TaskShareSerializer(serializers.ModelSerializer): """Serializer for task sharing.""" shared_with_email = serializers.EmailField(write_only=True) shared_with_username = serializers.CharField(source='shared_with.username', read_only=True) class Meta: model = TaskShare fields = [ 'id', 'task', 'tag', 'shared_with_email', 'shared_with_username', 'permission', 'sync_id', 'created_at' ] read_only_fields = ['id', 'sync_id', 'created_at'] def validate_shared_with_email(self, value): try: user = User.objects.get(email=value) if user == self.context['request'].user: raise serializers.ValidationError("Cannot share with yourself.") return value except User.DoesNotExist: raise serializers.ValidationError("User with this email does not exist.") def validate(self, attrs): if not attrs.get('task') and not attrs.get('tag'): raise serializers.ValidationError("Must share either a task or a tag.") if attrs.get('task') and attrs.get('tag'): raise serializers.ValidationError("Cannot share both a task and a tag.") return attrs def create(self, validated_data): email = validated_data.pop('shared_with_email') validated_data['shared_with'] = User.objects.get(email=email) validated_data['owner'] = self.context['request'].user return super().create(validated_data)