from django.db import transaction from rest_framework import serializers from apps.accounts.serializers import UserSerializer from apps.library.models import LibraryItem from apps.library.serializers import LibraryItemSerializer from .models import Comment, Moment, MomentItem, Reaction class MomentItemSerializer(serializers.ModelSerializer): library_item = LibraryItemSerializer(read_only=True) class Meta: model = MomentItem fields = ["id", "library_item", "order"] class CommentSerializer(serializers.ModelSerializer): author = UserSerializer(read_only=True) class Meta: model = Comment fields = ["id", "moment", "author", "parent", "body", "created_at"] read_only_fields = ["id", "moment", "author", "created_at"] def validate_parent(self, value): if value is None: return value if value.parent_id is not None: raise serializers.ValidationError("Cannot reply to a reply; reply to the original comment instead.") moment = self.context.get("moment") if moment is not None and value.moment_id != moment.id: raise serializers.ValidationError("Reply must target a comment on the same Moment.") return value class ReactionSerializer(serializers.ModelSerializer): user = UserSerializer(read_only=True) class Meta: model = Reaction fields = ["id", "moment", "user", "reaction_type", "created_at"] read_only_fields = ["id", "moment", "user", "created_at"] class MomentSerializer(serializers.ModelSerializer): owner = UserSerializer(read_only=True) items = MomentItemSerializer(many=True, read_only=True) library_item_ids = serializers.PrimaryKeyRelatedField( queryset=LibraryItem.objects.all(), many=True, write_only=True, source="library_items", required=False ) comment_count = serializers.IntegerField(source="comments.count", read_only=True) reaction_count = serializers.IntegerField(source="reactions.count", read_only=True) viewer_reaction_id = serializers.SerializerMethodField() class Meta: model = Moment fields = [ "id", "owner", "caption", "location", "audience", "items", "library_item_ids", "comment_count", "reaction_count", "viewer_reaction_id", "created_at", "updated_at", ] read_only_fields = [ "id", "owner", "items", "comment_count", "reaction_count", "viewer_reaction_id", "created_at", "updated_at", ] def get_viewer_reaction_id(self, obj): request = self.context.get("request") if not request or not request.user.is_authenticated: return None # obj.reactions is prefetched by the view, so this doesn't re-query per moment. reaction = next((r for r in obj.reactions.all() if r.user_id == request.user.id), None) return reaction.id if reaction else None def validate_library_item_ids(self, library_items): request = self.context["request"] seen_ids = set() for item in library_items: if item.owner_id != request.user.id: raise serializers.ValidationError("Can only share items from your own library.") if item.id in seen_ids: raise serializers.ValidationError("The same photo was selected more than once.") seen_ids.add(item.id) return library_items def validate(self, attrs): library_items = attrs.get("library_items", []) caption = attrs.get("caption", "") if not library_items and not caption.strip(): raise serializers.ValidationError("A Moment needs a caption or at least one photo.") return attrs def create(self, validated_data): library_items = validated_data.pop("library_items", []) validated_data["owner"] = self.context["request"].user with transaction.atomic(): moment = Moment.objects.create(**validated_data) MomentItem.objects.bulk_create( [MomentItem(moment=moment, library_item=item, order=i) for i, item in enumerate(library_items)] ) return moment