1

I would like to specify a custom lookup field on the action (different from the viewset default "pk"), i.e.

@action(
        methods=["GET"],
        detail=True,
        url_name="something",
        url_path="something",
        lookup_field="uuid",  # this does not work unfortunately
    )
    def get_something(self, request, uuid=None):
         pass

But the router does not generate the correct urls:

router = DefaultRouter()
router.register(r"test", TestViewSet)
router.urls

yields url:

'^test/(?P<pk>[^/.]+)/something/$'

instead of

'^test/(?P<uuid>[^/.]+)/something/$'

I do not want to change the lookup field for the whole viewset though and have been unsuccessful in finding a way to do this for the action itself after debugging through the router url generation. I did notice that model viewsets have this method:

get_extra_action_url_map(self)

but am unsure how to get it to be called to generate custom urls or if it is even relevant. Any help would be great thanks!

nefrob
  • 80
  • 7

2 Answers2

1

I think it will create much confusion for your API consumers if you have 2 different resource identification on the same resource.

You can name that action query_by_uuid or just allow them to use list_view to filter by uuid if you only want to represent the object tho. (so consumers can use /test/?uuid= to retrieve data)

But if you really want to do it, you can simply override get_object method to filter for your custom action tho:

def get_object(self):
    if self.action == 'do_something':
        return get_object_or_404(self.get_queryset(), uuid=self.kwargs['pk'])
    return super().get_object()

Here is a bit hacky solution for generate uuid in router with detail=False.

@action(detail=False, url_path=r'(?P<uuid>[^/.]+)/do_something')
def do_something(self, request, uuid=None):
    pass
Đào Minh Hạt
  • 2,742
  • 16
  • 20
  • Yes ideally I would not have two lookup fields. For the purpose of this problem however, your suggestion is what I am currently doing to bypass the malformed url issue (i.e. putting a uuid in the pk field for lookups). Ideally the url would show that a uuid is expected though. – nefrob Aug 29 '22 at 14:26
  • @Rob check the updated answer if it's suitable for you – Đào Minh Hạt Aug 30 '22 at 02:33
  • 1
    That works. It's a hacky problem so a hacky solution makes sense. Thanks! – nefrob Aug 31 '22 at 13:25
1

According to their docs you could use a regex lookup field. Their example uses a CBV instead of a request based view.

class MyModelViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
    lookup_field = 'uuid'
    lookup_value_regex = '[0-9a-f]{32}'

This could work:

@action(
        methods=["GET"],
        detail=True,
        url_name="something",
        url_path="something",
        lookup_field = 'uuid'
        lookup_value_regex = '[0-9a-f]{32}'
    )
    def get_something(self, request, uuid=None):
         pass
hendrikschneider
  • 1,696
  • 1
  • 14
  • 26
  • If I specify the uuid as the lookup field on the viewset as you have done above it works. However, I was trying to only specify it on the action (which doesn't work). Here I don't care about the lookup value formatting but rather the lookup field itself (being pk by default or uuid). Thanks for the suggestion though! – nefrob Aug 29 '22 at 14:21