Edwin Harly
Edwin Harly

Reputation: 439

How to count the distinct of model_set in Django?

I've looked into this question, but I think it's different. Let me explain a bit further. I have a serializer called DetailTrackSerializer to serialize my Track model, and I've nested a TaggedSerializer in DetailTrackSerializer.

class DetailTrackSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(max_length=120)
    link = serializers.URLField(max_length=120)
    tagged_set = TaggedSerializer(many=True)
    artist = ArtistSerializer()
    class Meta:
        model = Track
        fields = ('id', 'artist', 'title', 'link', 'tagged_set',)

class TaggedSerializer(serializers.ModelSerializer):
    tag = TagSerializer()
    class Meta:
        model = Tagged
        fields = ('tag',)

class TagSerializer(serializers.ModelSerializer):
    class Meta:
        model = Tag
        fields = ('name',)

Currently, this DetailTrackSerializer is returning a json like this

{
    "tracks": [
        {
            "id": 168,
            "artist": {
                "id": 163,
                "name": "Gob"
            },
            "title": "Face the Ashes",
            "link": "",
            "tagged_set": [
                {
                    "tag": {
                        "id": 1356,
                        "name": "punk rock"
                    }
                },
                {
                    "tag": {
                        "id": 1356,
                        "name": "punk rock"
                    }
                },
                {
                    "tag": {
                        "id": 1356,
                        "name": "punk rock"
                    }
                },
                ...

The list goes on, if there are 100 "punk rock" tag in this track, it will shows up 100 times and there may be another tag also not only "punk rock". What I need is something like this

{
    "tracks": [
        {
            "id": 168,
            "artist": {
                "id": 163,
                "name": "Gob"
            },
            "title": "Face the Ashes",
            "link": "",
            "tagged_set": [
                {
                    "tag": {
                        "id": 1356,
                        "name": "punk rock"
                    },
                    "frequency": 100,
                },
                {
                    "tag": {
                        "id": 546,
                        "name": "pop"
                    },
                    "frequency": 236,
                },
                ...

Each tag only appears once, and has its frequency. Note: I'm using Django Rest Framework as well

Edit: models.py

class Tagged(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
    track = models.ForeignKey(Track, on_delete=models.CASCADE)
    tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
class Tag(models.Model):
    name = models.CharField(max_length=255, unique=True)
class Track(models.Model):
    artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
    title = models.CharField(max_length=255)
    link = models.URLField(max_length=255, blank=True)
    tags = models.ManyToManyField(Tag, through='Tagged', blank=True)

Upvotes: 1

Views: 618

Answers (3)

Edwin Harly
Edwin Harly

Reputation: 439

After reading through Django's docs about querysets, this is the solution I came up with

class DetailTrackSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(max_length=120)
    link = serializers.URLField(max_length=120)
    tags_frequency = serializers.SerializerMethodField()
    artist = ArtistSerializer()

    def get_tags_frequency(self, track):
        tags = track.tags.all()
        return tags.values('id', 'name').annotate(Count('id'))

    class Meta:
        model = Track
        fields = ('id', 'artist', 'title', 'link', 'tags_frequency',)

which will give me json representation like this

{
    "tracks": [
        {
            "id": 168,
            "artist": {
                "id": 163,
                "name": "Gob"
            },
            "title": "Face the Ashes",
            "link": "",
            "tags_frequency": [
                {
                    "name": "punk rock",
                    "id": 1356,
                    "id__count": 100
                },
                {
                    "name": "punk",
                    "id": 1357,
                    "id__count": 60
                }
            ]
        },
        {
            "id": 169,
            "artist": {
                "id": 164,
                "name": "Jeff And Sheri Easter"
            },
            "title": "The Moon And I (Ordinary Day Album Version)",
            "link": "",
            "tags_frequency": []
        },

Upvotes: 1

JPG
JPG

Reputation: 88579

From your Tagged I understood that there are big chances of Data Redundancy, that's why your tagged_set is showing multiple times.

What I'm trying to say is, this is not a Representation Problem with your serializer, rather than it's an Implementation Problem with your Models.

So, unique_together attribute will solve the problem, as

class Tagged(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
    track = models.ForeignKey(Track, on_delete=models.CASCADE)
    tag = models.ForeignKey(Tag, on_delete=models.CASCADE)

    class Meta:
        unique_together = ('track', 'tag')

After changing the models, please do makemigrations and migration.

Note: While doing migration you may come acrross django.db.utils.IntegrityError: UNIQUE constraint failed exception. So, delete all entries in the Tagged model

Upvotes: 1

Ali
Ali

Reputation: 2591

Edwin Harly, you have some data overlap between Track, Tag and Tagged model. If you accept, I suggest you remove Tagged model. and if you want to save which user create tag, add user field in Tag model.

class Tag(models.Model):
name = models.CharField(max_length=255, unique=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, default=1)

class Track(models.Model):
    artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
    title = models.CharField(max_length=255)
    link = models.URLField(max_length=255, blank=True)
    tags = models.ManyToManyField(Tag, through='Tagged', blank=True)

Then, you can your serializers like this:

class DetailTrackSerializer(serializers.ModelSerializer):

    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(max_length=120)
    link = serializers.URLField(max_length=120)
    tags = TagSerializer(many=True)
    artist = ArtistSerializer()

    class Meta:
        model = Track
        fields = ('id', 'artist', 'title', 'link', 'tags',)



class TagSerializer(serializers.ModelSerializer):
    class Meta:
        model = Tag
        fields = ('name',)

Upvotes: 0

Related Questions