简体   繁体   中英

Send a field from ListAPIView to Serializer in Django Rest Framework

I'm building a leaderboard view for a REST API I'm designing in DRF. I need a bit of help in reducing an inefficiency.

views.py

class LeaderboardAPIView(ListAPIView):
    serializer_class = UserSerializerLeaderboard
    permission_classes = (IsAuthenticated,)

    def get_queryset(self):
        queryset = User.objects.all()
        queryset = list(queryset)
        queryset.sort(key=operator.attrgetter("total_karma"), reverse=True)
        queryset = queryset[:10]
        return queryset

serializers.py

class UserSerializerLeaderboard(serializers.ModelSerializer):
    score = serializers.SerializerMethodField(read_only=True)
    place = serializers.SerializerMethodField(read_only=True)

    def get_score(self, obj):
        return obj.total_karma

    def get_place(self, obj):
        return "1"

    class Meta:
        model = User
        fields = ("score", "place")

models.py

@property
def total_karma(self):
        return self.total_post_score() + self.total_comment_score()

def total_post_score(self):
        relevant_votes = PostVote.objects.filter(post__user=self)
        total = 0
        for vote in relevant_votes:
            total += vote.vote
        return total

def total_comment_score(self):
        relevant_votes = CommentVote.objects.filter(comment__user=self)
        total = 0
        for vote in relevant_votes:
            total += vote.vote
        return total

...

class PostVote(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
    post = models.ForeignKey(Post, on_delete=models.SET_NULL, null=True)
    vote = models.IntegerField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        unique_together = ("user", "post")

    def __str__(self):
        return str(self.id) + " " + str(self.vote)

...

class CommentVote(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
    comment = models.ForeignKey(Comment, on_delete=models.SET_NULL, null=True)
    vote = models.IntegerField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        unique_together = ("user", "comment")

    def __str__(self):
        return str(self.id) + " " + str(self.vote)

The get_place method currently returns a placeholder of 1. I'd like it to return the actual place the user is in, sorted by score. Now, I know I'm already calculating this in the queryset, so I don't want to repeat this inefficiently. How can I send the place of the user to the serializer directly, rather than repeating the calculation in the method?

You can use a window function to annotate each row in your queryset with it's position/row number

Change the total_karma property and the methods used to annotations in your queryset so that you can order by that field and access the calculated result from the returned objects without having to calculate it again

from django.db.models import Window, F, Sum
from django.db.models.functions import RowNumber


class LeaderboardAPIView(ListAPIView):
    serializer_class = UserSerializerLeaderboard
    permission_classes = (IsAuthenticated,)

    def get_queryset(self):
        queryset = User.objects.all()
        queryset = queryset.annotate(
            post_score=Sum('postvote__vote'),
            comment_score=Sum('commentvote__vote')
        )
        queryset = queryset.annotate(
            total_karma=F('post_score') + F('comment_score')
        )
        queryset = queryset.order_by(
            '-total_karma'
        ).annotate(row_num=Window(
            expression=RowNumber(),
            order_by=F('total_karma').desc()
        ))[:10]
        return queryset

Serializer

class UserSerializerLeaderboard(serializers.ModelSerializer):
    score = serializers.SerializerMethodField(read_only=True)
    place = serializers.SerializerMethodField(read_only=True)

    def get_score(self, obj):
        return obj.total_karma

    def get_place(self, obj):
        return obj.row_num

    class Meta:
        model = User
        fields = ("score", "place")

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM