You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在DRF中实现仅活动参与者可评论活动的逻辑?

DRF活动管理系统:评论权限校验与统计功能实现

问题概述

作为DRF新手,开发活动管理系统时遇到以下需求与问题:

  • 统计每个活动的参与者数量和评论数量
  • 仅允许活动参与者对该活动发表评论,当前Review模型的权限校验逻辑存在问题

以下是现有代码:

Models代码

class Event(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="event_user")
    attendees = models.ManyToManyField(User, blank=True, related_name='attendees_user')
    venue = models.ForeignKey(Venue, on_delete=models.CASCADE, related_name='venue_user')
    event_type = models.CharField(max_length=50, choices=event_type)
    event_name = models.CharField(max_length=25)
    start_time = models.DateTimeField(blank=False, null=True)
    end_time = models.DateTimeField(blank=False, null=True)

    def __str__(self):
        return f'{self.venue} - {self.event_name}'

class Reviews(models.Model):
    reviewer = models.ForeignKey(User, on_delete=models.CASCADE, related_name="reviewer_user")
    event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="event")
    review = models.TextField()

    class Meta:
        verbose_name_plural = "Reviews"

    def __str__(self):
        return f'{self.reviewer} -- {self.review} -- {self.event}'

Serializers代码

class EventSerializer(serializers.ModelSerializer):
    images = serializers.ListField(
        child=serializers.FileField(allow_empty_file=True, use_url=True, ), required=False)

    class Meta:
        model = Event
        fields = (
            'id', 'user', 'attendees', 'venue', 'event_type', 'event_name', 'start_time', 'end_time',
            'images')

    def create(self, validated_data):
        uploaded_data = validated_data.pop('images')
        attendees = validated_data.pop('attendees')
        instance = Event.objects.create(**validated_data)
        instance.attendees.set(attendees)
        instance.save()
        for pics in uploaded_data:
            EventImages.objects.create(event=instance, event_pics=pics)
        return instance

    def to_representation(self, instance):
        response = super(EventSerializer, self).to_representation(instance)
        response["venue_name"] = instance.venue.venue_name
        response["start_time"] = instance.start_time.strftime("%d/%m/%Y, %H:%M:%S")
        response["end_time"] = instance.end_time.strftime("%d/%m/%Y, %H:%M:%S")
        return response

    def validate(self, data):
        if data.get("start_time") > data.get("end_time"):
            raise serializers.ValidationError("'End time' must be after 'Start time'.")
        
        if Event.objects.filter(
                (Q(venue=data['venue']) &
                 Q(start_time__range=(data['start_time'], data['end_time']))) |
                (Q(venue=data['venue']) &
                 Q(end_time__range=(data['start_time'], data['end_time'])))
        ):
            raise serializers.ValidationError("Venue is booked for this time slot")
        return data

class ReviewSerializer(serializers.ModelSerializer):
    class Meta:
        model = Reviews
        fields = ('id', 'reviewer', 'event', 'review')

    def validate(self, request):
        data = request.data
        event = Event.objects.filter(id=request.get("event").id).first()
        user = User.objects.filter(id=request.get("user").id).first()
        if user and event and user in event.attendees:
            return data
        else:
            raise serializers.ValidationError("only participants can review !!")

ViewSets代码

class EventViewSet(viewsets.ModelViewSet):
    queryset = Event.objects.all()
    serializer_class = EventSerializer

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            instance = serializer.save()
            return_serializer = EventSerializer(instance)
            headers = self.get_success_headers(return_serializer.data)
            return Response(return_serializer.data, status=status.HTTP_201_CREATED, headers=headers)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def partial_update(self, request, *args, **kwargs):
        serializer = EventSerializer(instance=Event(), data=request.data, partial=True)
        if serializer.is_valid():
            instance = serializer.save()
            return_serializer = VenueSerializer(instance)
            headers = self.get_success_headers(return_serializer.data)
            return Response(return_serializer.data, headers=headers, status=status.HTTP_202_ACCEPTED)

class ReviewViewSet(viewsets.ModelViewSet):
    queryset = Reviews.objects.all()
    serializer_class = ReviewSerializer

解决方案

1. 修复评论权限校验逻辑

原ReviewSerializer的validate方法存在参数错误、用户获取方式不当的问题,修改如下:

class ReviewSerializer(serializers.ModelSerializer):
    # 将评论者设为只读,自动从当前请求获取用户
    reviewer = serializers.PrimaryKeyRelatedField(read_only=True)

    class Meta:
        model = Reviews
        fields = ('id', 'reviewer', 'event', 'review')

    def validate(self, data):
        # 获取当前请求用户
        current_user = self.context['request'].user
        event = data.get('event')
        
        # 校验用户是否为活动参与者
        if not event.attendees.filter(id=current_user.id).exists():
            raise serializers.ValidationError("仅活动参与者可发表评论!")
        return data

    def create(self, validated_data):
        # 自动设置评论者为当前用户
        validated_data['reviewer'] = self.context['request'].user
        return super().create(validated_data)

2. 实现活动参与者数与评论数统计

方案一:Serializer中动态添加统计字段(简单直观)

修改EventSerializer的to_representation方法,添加统计字段:

def to_representation(self, instance):
    response = super(EventSerializer, self).to_representation(instance)
    response["venue_name"] = instance.venue.venue_name
    response["start_time"] = instance.start_time.strftime("%d/%m/%Y, %H:%M:%S")
    response["end_time"] = instance.end_time.strftime("%d/%m/%Y, %H:%M:%S")
    # 添加参与者数量
    response["attendees_count"] = instance.attendees.count()
    # 添加评论数量(建议先修改Reviews模型的event字段related_name为"reviews",避免歧义)
    response["reviews_count"] = instance.event.count()
    return response

方案二:数据库层面聚合统计(性能更优)

先修改Reviews模型的event字段related_name(可选,但更清晰):

class Reviews(models.Model):
    # ... 其他字段不变
    event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="reviews")

然后在EventViewSet中用annotate预聚合统计数据:

from django.db.models import Count

class EventViewSet(viewsets.ModelViewSet):
    # 预聚合参与者数和评论数
    queryset = Event.objects.annotate(
        attendees_count=Count('attendees', distinct=True),  # 多对多需加distinct避免重复计数
        reviews_count=Count('reviews')
    )
    serializer_class = EventSerializer

最后在EventSerializer中添加对应字段:

class EventSerializer(serializers.ModelSerializer):
    images = serializers.ListField(
        child=serializers.FileField(allow_empty_file=True, use_url=True, ), required=False)
    # 添加统计字段,设为只读
    attendees_count = serializers.IntegerField(read_only=True)
    reviews_count = serializers.IntegerField(read_only=True)

    class Meta:
        model = Event
        fields = (
            'id', 'user', 'attendees', 'venue', 'event_type', 'event_name', 'start_time', 'end_time',
            'images', 'attendees_count', 'reviews_count')

3. 修复EventViewSet的partial_update方法

原方法错误创建新实例,改为获取当前请求的活动实例:

def partial_update(self, request, *args, **kwargs):
    instance = self.get_object()
    serializer = EventSerializer(instance=instance, data=request.data, partial=True)
    serializer.is_valid(raise_exception=True)
    instance = serializer.save()
    return_serializer = EventSerializer(instance)
    headers = self.get_success_headers(return_serializer.data)
    return Response(return_serializer.data, headers=headers, status=status.HTTP_202_ACCEPTED)

内容的提问来源于stack exchange,提问作者karan vyas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 06:10:27