如何在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
相关产品推荐
相关产品推荐

