Search code examples
djangodjango-modelsdjango-rest-frameworkdjango-viewsets

Django Rest Framework - Annotate duplicates values


I encounter a really weird bug. When annotating 3 values (AVG, COUNT and SUM) I notice that COUNT and SUM are multiplying each other.

For instance:

enter image description here

The duration should be half the time and there are only 2 ratings (number_of_ratings - COUNT, album_duration - SUM).

I have these annotations in queryset in ViewSet class

class AlbumViewSet(ModelViewSet):
    queryset = Album.objects \
        .prefetch_related("tracks", "album_genres", "album_genres__genre_id", "album_links", "reviews") \
        .select_related("aoty", "artist_id") \
        .annotate(overall_score=Avg("reviews__rating", output_field=IntegerField()),
                  number_of_ratings=Count("reviews__rating", output_field=IntegerField()),
                  album_duration=Sum("tracks__duration", output_field=DurationField()))

Here are other files

models.py

class Album(models.Model):

    RELEASE_TYPE_ALBUM_CHOICES = [
        ("LP", "LP"),
        ("EP", "EP"),
        ("Single", "Single"),
        ("Live", "Live"),
    ]

    title = models.CharField(max_length=255)
    slug = models.SlugField(max_length=255)
    release_date = models.DateField()
    artist_id = models.ForeignKey(
        Artist, on_delete=models.PROTECT, related_name="albums"
    )
    art_cover = ResizedImageField(
        size=[750, 750], null=True, blank=True, upload_to=rename_album_art_cover)
    release_type = models.CharField(max_length=10,
                                    choices=RELEASE_TYPE_ALBUM_CHOICES, default="LP")
    created_at = models.DateTimeField(auto_now_add=True)


class Track(models.Model):
    title = models.CharField(max_length=255)
    position = models.PositiveIntegerField()
    album_id = models.ForeignKey(
        Album, on_delete=models.PROTECT, related_name="tracks")
    duration = models.DurationField(null=True)


class Review(models.Model):
    reviewer_id = models.ForeignKey(Reviewer, on_delete=models.PROTECT)
    rating = models.IntegerField(
        validators=[MaxValueValidator(100), MinValueValidator(0)]
    )
    review_text = models.TextField(null=True, blank=True)
    album_id = models.ForeignKey(
        Album, on_delete=models.PROTECT, related_name="reviews")
    created_at = models.DateTimeField(auto_now_add=True)

serializer.py

class AlbumSerializer(serializers.ModelSerializer):
    tracks = TrackSerializer(many=True)
    genres = StringRelatedField(
        source="album_genres", many=True)
    aoty = StringRelatedField(read_only=True)
    links = AlbumLinkSerializer(
        source="album_links", many=True, read_only=True)
    artist = SimpleArtistSerializer(source="artist_id", read_only=True)
    overall_score = serializers.IntegerField(read_only=True)
    number_of_ratings = serializers.IntegerField(read_only=True)
    album_duration = serializers.DurationField(read_only=True)

    class Meta:
        model = Album
        fields = [
            "id",
            "title",
            "slug",
            "created_at",
            "artist",
            "art_cover",
            "genres",
            "overall_score",
            "number_of_ratings",
            "release_date",
            "release_type",
            "tracks",
            "album_duration",
            "links",
            "aoty"
        ]

class TrackSerializer(serializers.ModelSerializer):
    class Meta:
        model = Track
        fields = ["position", "title",  "duration", "album_id"]

class ReviewSerializer(serializers.ModelSerializer):
    reviewer = SimpleReviewerSerializer(source="reviewer_id", read_only=True)
    album = ReviewAlbumSerializer(source="album_id", read_only=True)

    class Meta:
        model = Review
        fields = [
            "id",
            "reviewer_id",
            "reviewer",
            "rating",
            "review_text",
            "album_id",
            "album",
            "created_at"
        ]

Solution

  • For anyone that would encounter the same problem, here's how I worked it out. There should be distinct property set in every annotate function.

    .annotate(overall_score=Avg("reviews__rating", output_field=IntegerField()),
                      number_of_ratings=Count("reviews__rating", output_field=IntegerField(), distinct=True),
                      album_duration=Sum("tracks__duration", output_field=DurationField(), distinct=True))