Django DRF中如何判断当前登录用户是否已点赞问题?
问题:判断登录用户是否点赞问题时出现TypeError错误及解决方法
错误原因
你遇到的TypeError: 'RelatedManager' object is not iterable,是因为obj.feelings是Django的RelatedManager对象——它是管理关联数据的接口,并非直接可迭代的QuerySet集合。直接用for feeling in obj.feelings遍历会报错,必须调用.all()、.filter()等方法获取实际的QuerySet后才能进行遍历操作。
正确实现方式
下面提供几种高效的实现方案,避免重复查询数据库:
方案1:优化序列化器方法(直接检查存在性)
无需遍历所有点赞记录,直接通过filter+exists()判断,仅生成1条高效的查询语句:
def get_liked(self, obj): request = self.context.get('request') if request and request.user.is_authenticated: return obj.feelings.filter(user=request.user).exists() return False
方案2:视图层用annotate预注入字段(推荐,避免N+1查询)
如果是批量查询Question列表,用annotate在数据库层面直接计算是否点赞,彻底避免每条Question单独查询点赞记录的N+1性能问题:
视图层代码
from django.db.models import Exists, OuterRef from rest_framework import viewsets class QuestionViewSet(viewsets.ModelViewSet): queryset = Question.objects.all() serializer_class = QuestionSerializer def get_queryset(self): queryset = super().get_queryset() if self.request.user.is_authenticated: # 用OuterRef关联当前Question,Exists判断用户是否有点赞记录 queryset = queryset.annotate( liked=Exists( QuestionFeeling.objects.filter( question=OuterRef('pk'), user=self.request.user ) ) ) return queryset
序列化器代码
直接读取预注入的liked字段,无需额外查询:
from rest_framework import serializers class QuestionSerializer(serializers.ModelSerializer): liked = serializers.BooleanField(read_only=True) # 如果需要返回点赞列表,添加嵌套序列化器 feelings = QuestionFeelingSerializer(many=True, read_only=True) class Meta: model = Question fields = ['id', 'title', 'body', 'user', 'created_at', 'updated_at', 'feelings', 'liked']
方案3:修复原序列化器的遍历逻辑(不推荐,效率低)
如果一定要用遍历的方式,需要先调用.all()获取可迭代的QuerySet,但这种方式会加载所有点赞记录,数据量大时效率很低:
def get_liked(self, obj): request = self.context.get('request') if request and request.user.is_authenticated: logged_user_id = request.user.id # 先调用.all()获取可迭代的QuerySet for feeling in obj.feelings.all(): if feeling.user_id == logged_user_id: return True return False
附相关代码
Question模型
class Question(models.Model): title = models.CharField(max_length=100) body = models.TextField(max_length=10000) user = models.ForeignKey(User, on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now_add=True, verbose_name="created at") updated_at = models.DateTimeField(auto_now=True, verbose_name="updated at") class Meta: verbose_name = "question" verbose_name_plural = "questions" db_table = "questions" def __str__(self): return self.body
QuestionFeeling模型
class QuestionFeeling(models.Model): like = "like" feelings = [ (like , "like"), ] feeling = models.CharField(max_length=15,choices=feelings, default=like) question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name='feelings') user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="question_feelings") created_at = models.DateTimeField(auto_now_add=True, verbose_name="created at") updated_at = models.DateTimeField(auto_now=True, verbose_name="updated at") class Meta: unique_together = ['question', 'user'] verbose_name = "Question feeling" verbose_name_plural = "Question feelings" db_table = "question_feeling" def __str__(self): return str(self.feeling)
前端返回的feelings结构
feelings: Array [ {…} ] 0: Object { id: 1, feeling: "like", created_at: "2023-02-13T04:21:43.323313Z", … } created_at: "2023-02-13T04:21:43.323313Z" feeling: "like" id: 1 question: 14 updated_at: "2023-02-13T04:21:43.323358Z" user: 33
报错的原序列化器方法
def get_liked(self, obj): request = self.context.get('request', None) if request: loggedUser = self.context["request"].user.id for feeling in obj.feelings: if loggedUser == feeling.user: return True
内容的提问来源于stack exchange,提问作者crawlingdev
相关产品推荐
相关产品推荐

