Search code examples
pythondjangodjango-rest-frameworkcruduuid

Django Rest Framework with shortuuid, generics.RetrieveUpdateDestroyAPIView returning 404 {"detail": "Not found."}


I was remaking a social media site as a revision of Django and the rest framework, I didn't want to use the django default linear id count and didn't like how long the uuid library's ids was, so I used the shortuuid library. I've used them on the posts and the comments just to keep the anonymity of the count of both posts and comments. On the posts side everything works for the CRUD stuff (which should be proof that the issue isn't from the shortuuid library, as far as I know), although with the comments the Create Retrieve works perfectly but the Update Destroy doesn't. so here is the code we are working with:

starting with the models to know what kind of data we are working with (models.py):

from shortuuid.django_fields import ShortUUIDField

... # posts likes etc


class Comment(models.Model):
    id = ShortUUIDField(primary_key=True, length=8, max_length=10)
    user    = models.ForeignKey(User, on_delete=models.CASCADE)
    post    = models.ForeignKey(Post, on_delete=models.CASCADE)
    body    = models.TextField(max_length=350)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    active  = models.BooleanField(default=True)

    class Meta:
        ordering = ['created']

    def __str__(self):
        return f'on {self.post} by {self.user}'

    objects = models.Manager()

serializers.py:

class CommentSerializer(ModelSerializer):
    username = SerializerMethodField()

    def get_username(self, comment):
        return str(comment.user)

    class Meta:
        model = Comment
        fields = ['id', 'user', 'post', 'username', 'body', 'created', 'updated']
        read_only_fields = ['id', 'post', 'user', 'username']

now with the routing (urls.py):

from django.urls import path
from .views import *


urlpatterns = [
    ...

    path('<str:pk>/comments/'       , Comments),
    path('<str:pk>/comments/create/', CreateComment),
    path('<str:pk>/comments/<str:cm>/', ModifyComment),
    # pk = post ID
    # cm = comment ID
]

views.py:

class ModifyComment(generics.RetrieveUpdateDestroyAPIView):
    serializer_class = CommentSerializer
    permission_classes = [permissions.AllowAny]

    def get_queryset(self):
        post = Post.objects.get(pk=self.kwargs['pk'])
        comment = Comment.objects.get(post=post, pk=self.kwargs['cm'])
        return comment


    def perform_update(self, serializer):
        print(Post.objects.all())
        post = Post.objects.get(pk=self.kwargs['pk'])
        comment = Comment.objects.filter(pk=self.kwargs['cm'], post=post)
        if self.request.user != comment.user:
            raise ValidationError('you can\'t edit another user\'s post')
        if comment.exists():
            serializer.save(user=self.request.user, comment=comment)
        else:
            raise ValidationError('the comment doesnt exist lol')

    def delete(self, request, *args, **kwargs):
        comment = Comment.objects.filter(user=self.request.user, pk=self.kwargs['cm'])
        if comment.exists():
            return self.destroy(request, *args, **kwargs)
        else:
            raise ValidationError("you can\'t delete another user\'s post")
ModifyComment = ModifyComment.as_view()

and the response to going to the url '<str:pk>/comments/<str:cm>/' comment of some post we get this: example of the response

side note, the perform_update function doesn't seem to be called ever, even putting a print statement at the beginning of the function doesn't get printed so the issue may have to do with the get_queryset even though I've tried using the normal queryset=Comment.object.all() and making the get_queryset function return the comment with the correct params but I couldn't make it work


Solution

  • For individual objects you need to overwrite the get_object method.

    You are performing the request GET /str:pk/comments/str:cm/, this calls the retrieve method on the view, which in turn calls get_object. The default behaviour is trying to find a Comment object with id equal to pk since it's the first argument, since you need to filter through a different model you need to overwrite it.

    classy drf is a good website for seing how the internals of the clases work.