Search code examples
pythondjango-rest-frameworkmany-to-many

django-rest-framework: how to represent many-to-many relationship?


I'm building a django application, with a rest api, where users can subscribe to feeds.

My model looks like this:

class Profile(models.Model):
    user = models.OneToOneField(
        User, # standard django user
        on_delete=models.CASCADE,
        primary_key=True
    )

    subscriptions = models.ManyToManyField(Feed, related_name="subscribers")

class Feed(models.Model):
    # These feeds are read-only for the subscribers
    pass

Explained in English:

Each user has a profile. These profile can subscribe to feeds. The feeds can only be subscribed to, they can't be created or updated by the users.

I want to expose a rest endpoint to create, read, update and delete subscriptions for the current user. Of course, I'd love to use the generic views, a ListCreateAPIView and a RetrieveUpdateDeleteAPIView, but I can't figure out how to make the correct serializers and querysets to make this easy to use.

I ended up with /me/subscriptions/ returning a list of Feeds directly, but then the POST is weird because it would create a feed instead of creating a subscription.

I'm confused... Any hint is appreciated!


Solution

  • You could use a ModelViewSet and override create method:

    views.py:

    from rest_framework import status
    from rest_framework import viewsets
    
    
    class FeedViewSet(serializers.ModelViewSet):  
    
        serializer_class = FeedSerializer
    
        def get_queryset(self):
            # show an only related Feed objects to a request.user
            profile = request.user.profile
            return Feed.objects.filter(subscribers=profile)
    
        def create(self, request, *args, **kwargs):
            pk = request.data.get('pk')
            profile = request.user.profile
            feed = Feed.objects.get(pk=pk)
            profile.subscriptions.add(feed)
            return Response('subscription feed updated!', status=status.HTTP_201_CREATED) 
    
        # list will work by default
        # other methods like update, delete, retrieve 
        # you could set with NOT_IMPLEMENTED status
        def update(self, request, *args, **kwargs):
            return Response(status=status.HTTP_501_NOT_IMPLEMENTED)
    

    urls.py:

    from rest_framework import routers
    
    router = routers.SimpleRouter()
    router.register('subscriptions', FeedViewSet, base_name='subscription')
    

    Now you'll have an endpoint for updating subscriptions of Profile based on a request.user. GET to /subscriptions/ will show all Feed objects filtered by request.user and POST with data {'pk': <Feed_pk>} to /subscriptions/ will create a link between Feed and Profile.