0

I'm building an API with Applications and Servers. An application can have many servers and a server can have many applications.

These are my models.

class Server(Model):
    name = TextField(unique=True)
    description = TextField(blank=True)
    ip_address = TextField()

    class Meta:
        ordering = ["name"]

    def __str__(self):
        return self.name


class Application(Model):
    name = TextField(unique=True)
    description = TextField()
    is_critical = BooleanField()
    servers = ManyToManyField(Server, related_name="applications")

    class Meta:
        ordering = ["name"]

    def __str__(self):
        return self.name

These are my serializers.

class ServerSerializer(ModelSerializer):

    class Meta:
        model = Server
        fields = "__all__"

class ApplicationSerializer(ModelSerializer):

    servers = ServerSerializer(many=True, read_only=True)    

    class Meta:
        model = Application
        fields = "__all__"

I'm using DRF's Viewsets.

class ServerViewSet(ModelViewSet):
    queryset = Server.objects.all()
    serializer_class = ServerSerializer

class ApplicationViewSet(ModelViewSet):
    queryset = Application.objects.all()
    serializer_class = ApplicationSerializer

    @action(detail=True, methods=["HEAD", "GET", "POST", "DELETE"])
    def servers(self, request, pk=None):
        """Relate a server to the application"""

        application = self.get_object()

        if request.method in ("HEAD", "GET"):
            s_servers = ServerSerializer(application.servers, many=True)
            return Response(s_servers.data)
        else:
            server_id = request.data.get("id")
            if not server_id:
                return Response(
                    "Server details must be provided",
                    status=HTTP_400_BAD_REQUEST)

            server = get_object_or_404(Server, id=server_id)

            if request.method == "POST":
                application.servers.add(server)
                return Response(
                    "Server added to the application",
                    HTTP_201_CREATED)

            elif request.method == "DELETE":
                application.servers.remove(server)
                return Response(
                    "Server removed from the application",
                    HTTP_204_NO_CONTENT)

I would like servers to be unique and applications can have many servers. So, I will allow access to servers using

/api/servers and /api/servers/<int:pk>

I would like users to add existing servers to their applications using

/api/applications/app_id/servers

I'm able to achieve all operations except deleting a server that was already added to an application. How to handle the delete HTTP request using viewsets for the nested resource?

kunaguvarun
  • 703
  • 1
  • 10
  • 29
  • Is there any error raised, or does your viewset return 204 but the server is not removed? – mfrackowiak Jan 29 '19 at 16:13
  • even though I pass id in the body for a DELETE request, all I'm getting is "Server details must be provided" with a 400 Bad Request – kunaguvarun Jan 29 '19 at 16:27
  • Looking around, into documentation https://www.django-rest-framework.org/api-guide/requests/#data and e.g. https://stackoverflow.com/questions/299628/is-an-entity-body-allowed-for-an-http-delete-request I think it is possible that body of DELETE request are *not* parsed into `request.data`. You could try to print out/debug the content or request.data for this case, and I will probably have some time in the evening to play with this, since it's quite interesting issue. – mfrackowiak Jan 29 '19 at 16:51
  • you're right. The body of the request is not read by the DELETE operation. Print statement give an empty dictionary back. I think I've to implement a detail endpoint for delete operation. Came across this quetions https://stackoverflow.com/questions/23476342/django-rest-framework-nested-viewsets-and-routes and this recommendation. https://github.com/alanjds/drf-nested-routers – kunaguvarun Jan 29 '19 at 17:08
  • I've implemented with drf-nested-routers but what it does is, it allows, CRUD on /applications/:id/servers and limits servers only to this particular application. If I want to add a server to another application, I cannot. Not sure how to implement my requirement. In another words, when I delete a server, I just wan to delete the relation not the server object itself. I want to expose another endpoint /servers/:id to handle a server deletion – kunaguvarun Jan 29 '19 at 18:44
  • The "DELETE" HTTP method isn't actually the right choice here, because as you point out, you are not deleting an object, just updating an object's relationships. That is a case for a PUT or a PATCH request on Application. For non-nested M2M, you can simply change [1,2,3] to [1,2] where the numbers are the Server PKs. For nested like above, it's more complicated - this [DjangoCon talk](https://www.youtube.com/watch?v=-9WniUBt0fo) outlines the different ways. – birophilo Feb 01 '19 at 23:24

0 Answers0