Patrick
Patrick

Reputation: 2709

In Django Rest Framework how to update FK in serializer

I'm trying to add a vote functionality to the code found in tutorial of Django Rest Framework. On top of Snippet model, I added a Vote model:

class Vote(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    voter = models.ForeignKey(User, on_delete=models.CASCADE)
    snippet = models.ForeignKey(Snippet, related_name='votes', on_delete=models.CASCADE)

    class Meta:
        ordering = ('created',)

After posting and validating a user vote to a snippet, I now want to update the number of votes received by the snippet (I added to the Snippet model a number_of_votes field).

I'm doing it in the create method of my VoteSerializer like that:

class VoteSerializer(serializers.HyperlinkedModelSerializer):
    voter = serializers.ReadOnlyField(source='voter.username',validators=[UniqueValidator(queryset=VoteUp.objects.all(), message=already_voted)])
    snippet = serializers.PrimaryKeyRelatedField(queryset=Snippet.objects.all())

    def validate(self, data):
        # ... my validation function

    def create(self, validated_data):
        obj = Vote.objects.create(**validated_data)
        obj.snippet.number_of_votes += 1
        obj.snippet.save()
        return obj

It works well but I'm not sure if it's the good way or not to do it. Is there a better way?

Upvotes: 0

Views: 214

Answers (1)

MCBama
MCBama

Reputation: 1480

Couple of possible "better" ways to try:

Overwrite Save Method

class Vote(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    voter = models.ForeignKey(User, on_delete=models.CASCADE)
    snippet = models.ForeignKey(Snippet, related_name='votes', on_delete=models.CASCADE)

  class Meta:
    ordering = ('created',)

  def save(self, *args, **kwargs):
    # ensure model is being created and not just modified:
    if not self.pk:
      # increment snippet vote counter
      self.snippet.number_of_votes += 1
      self.snippet.save()

    # call base save method to ensure proper handling
    super().save(*args, **kwargs)

Use a Post Save signal

Once you have signals setup for your Django project following the documentation you can create a signal that will look something like this:

from django.dispatch import receiver
from django.db.models.signals import post_save
# make sure you import Vote here. I'm not sure of your project 
# setup so I can't write the import for you.

@receiver(post_save, sender=Vote)
def update(sender, instance, created, **kwargs):
  # only increment on model creation
  if created:
    instance.snippet.number_of_votes += 1
    instance.snippet.save()

Although in all honesty I'm pretty sure snippet.vote_set.count() is a pretty fast call and shouldn't interfere with your run time any if you wanted to start by using that instead of a counter. Because remember if you increment the counter you're going to have to decrement it too or your count will be off if someone deletes a record.

Upvotes: 1

Related Questions