Using reverse relationships with django-rest-frame

2019-05-21 19:08发布

问题:

My model looks like this:

class User(TimestampedModel):
    name = models.CharField(max_length=30, null=False, blank=False)
    device = models.CharField(max_length=255, null=False, blank=False)


class Comment(TimestampedModel):
    user = models.ForeignKey(User, on_delete=models.PROTECT, blank=True, null=True)
    contents = models.CharField(max_length=510)
    rating = models.IntegerField(blank=False, null=False)

And my serializer looks like this:

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('name',)


class CommentListItemSerializer(serializers.ModelSerializer):
    user = UserSerializer()

    class Meta:
        model = Comment
        fields = ('user', 'contents', 'rating')

And the view:

class CommentsList(generics.ListAPIView):
    serializer_class = CommentListItemSerializer
    queryset = Comment.objects.all()

It's almost getting the job done ;). The response I'm getting looks like this:

"results": [
    {
        "user": {
            "name": "Ania"
        },
        "contents": "Very good",
        "rating": 6
    },
    {
        "user": {
            "name": "Anuk"
        },
        "contents": "Not very good",
        "rating": 1
    }
]

There are two problems with that response.

  1. I don't want to have this nested object "user.name". I'd like to receive that as a simple string field, for example "username".
  2. Serializer makes a database query (not a join, but a separate query) for each user, to get his/her name. Since that's unacceptable, how to fix that?

回答1:

Serializer makes a database query (not a join, but a separate query) for each user, to get his/her name.

You can use select_related() on the queryset attribute of your view. Then accessing user.name will not result in further database queries.

class CommentsList(generics.ListAPIView):
    serializer_class = CommentListItemSerializer
    queryset = Comment.objects.all().select_related('user') # use select_related

I don't want to have this nested object "user.name". I'd like to receive that as a simple string field, for example "username"

You can define a read-only username field in your serializer with source argument. This will return a username field in response.

class CommentListItemSerializer(serializers.ModelSerializer):
    # define read-only username field
    username = serializers.CharField(source='user.name', read_only=True) 

    class Meta:
        model = Comment
        fields = ('username', 'contents', 'rating')


回答2:

You can add custom functions as fields

class Comment(models.Model):
    user = models.ForeignKey(User, on_delete=models.PROTECT, blank=True, null=True)
    contents = models.CharField(max_length=510)
    rating = models.IntegerField(blank=False, null=False)

    def username(self):
        return self.user.name


class CommentListItemSerializer(serializers.ModelSerializer):

    class Meta:
        model = Comment
        fields = ('username', 'contents', 'rating')