Search code examples
djangodjango-rest-frameworkdrf-nested-routers

POST new nested object


I'm trying to implement this scheme:

http://127.0.0.1:8000/api/get_work/

{
    "type": "dns",
    "source_alerts": [
        {
            "source": "alehop.com",
            "alerts": [
                {
                    "dns_server": "8.8.4.4",
                    "ip_addr": "134.211.190.5",
                },
                {
                    "dns_server": "7.7.2.2",
                    "ip_addr": "224.110.70.3",
                }
            ]
        }
    ]
}

And then be able to GET all alerts nested into a source:

** The source will be unique

http://127.0.0.1:8000/api/set_work/dns/alehop.com/

        "alerts": [
            {
                "dns_server": "8.8.4.4",
                "ip_addr": "134.211.190.5",
            },
            {
                "dns_server": "7.7.2.2",
                "ip_addr": "224.110.70.3",
            }

And POST a single alert into that source:

        {
            "dns_server": "7.7.2.2",
            "ip_addr": "224.110.70.3",
        }

My question is: is possible to implement a list/create viewset of a route with parameters?

router.register(r'set_work/(?P<type>.+)/(?P<source>.+)', views.SetWorkViewSet)

In that case, how can I use that parameters in the viewset in order to filter the queryset?

Thank you in advance. Any other approaches will be very welcome, I'm very new to python/django.


Solution

  • Sure you can!

    Django REST Framework (DRF) for model viewsets (which I assume you are using) implements standard get_object and get_queryset methods. Since you added additional parameters to the url regex, you can reference them via self.kwargs inside the viewset:

    def get_queryset(self):
        qs = super(...).get_queryset()
        return qs.filter(type=self.kwargs['type'], source=self.kwargs['source'])
    

    This will do the filtering which will make list work. As for the create, you will probably need to adjust the serializer in order to use the values from the url kwargs. There are a couple of ways to do that:

    1. add the url kwargs to the data being passed to the serializer when instantiating it

      class MyViewSet(ViewSet):
          def get_serializer(self, *args, **kwargs):
              # add the url kwargs to request data so that serializer will see it
              self.request.data.update(self.kwargs)
              return super(...).get_serializer(*args, **kwargs)
      

      This technically should work however feels a bit hackish to me so I would not recommend this approach.

    2. adjust the validation logic within the serializer to add the url parameters

      class MySerializer(ModelSerializer):
          class Meta(object):
              model = MyModel
          def to_internal_value(self, data):
              if hasattr(self, 'internal_data') and 'view' in self.context:
                  data.update(self.context['view'].kwargs)
              return super(...).to_internal_value(data)
      

      I personally feel this approach is cleaner even though it leaks a little of information about the viewset to the serializer.

    Please note the code is not tested so you will need to do some testing but it should get you started.