pythondjangodjango-rest-frameworkcore-api

Django Rest Framework Copeapi LinkLookupError


I am getting a LinkLookupError when trying to get a endpoint served by Django Rest Framework. This error only occurs after a single successfully request. My DRF view.py looks like:

class RecordViewSet(viewsets.ReadOnlyModelViewSet):
    """
    API endpoint that allows Records to be viewed or edited.

    """
    filter_backends = (SimpleRecordFilterBackend,)
    serializer_class = RecordSerializer
    permission_classes = (IsAuthenticated,)

    @action(detail=False, url_path='tagsum/(?P<account>[^/.]+)/(?P<tag>[^/.]+)')
    def tagsum(self, request, account, tag):
        if not account or not tag:
            return Response({'status': "need accoutn and tag"})
        sum =  Records.objects.filter(linkedaccount_id=account).filter(user_project=tag).aggregate(total=Sum('unblendedcost'))
        return Response({'sum': sum['total']})

    def get_queryset(self):
        q = Q()
        linkedaccount_id = self.request.query_params.get("linkedaccount_id") or ''
        user_project = self.request.query_params.get("user_project") or ''
        if linkedaccount_id:
            q &= Q(linkedaccount_id=linkedaccount_id)
        if user_project:
            q &= Q(user_project=user_project)
        return Records.objects.filter(q).annotate(Sum('unblendedcost'))

After modifying the view file, I call $coreapi get http://localhost:8000/api/docs/ and see:

...
    records: {
        list([page], [linkedaccount_id], [productname], [user_project])
        tagsum(account, tag)
        read(id, [linkedaccount_id], [productname], [user_project])
    }
...

and after one page load that calls that endpoint, when I run the same command I see this output:

...
    records: {
        tagsum: {
            read(account, tag)
        }
        list([page], [linkedaccount_id], [productname], [user_project])
        read(id, [linkedaccount_id], [productname], [user_project])
    }
...

Note that the tagsum method now has the read method nested. Calls to the endpoint now return the following error.

errors.js:10 Uncaught LinkLookupError: Invalid link lookup: ["records","tagsum"]
    at new LinkLookupError (errors.js:10)
    at lookupLink (client.js:19)
    at Client.action (client.js:38)
    at eval (ProjectTagBilling.js:40)
    at invokePassiveEffectCreate (react-dom.development.js:23482)
    at HTMLUnknownElement.callCallback (react-dom.development.js:3945)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:3994)
    at invokeGuardedCallback (react-dom.development.js:4056)
    at flushPassiveEffectsImpl (react-dom.development.js:23569)
    at unstable_runWithPriority (scheduler.development.js:468)

Any advice would be appreciated.


Solution

  • I had the same problem and spent time to understand what was going wrong.

    What is going wrong When you get the schema.js for the first time (before accessing "tagsum"), it only associates the "get" method to "tagsum". When you access "tagsum", DRF adds the mehod "head" associated to "tagsum". So when you get the schema.js again, it is different.

    Workaround In your Django WiewSet view.py add the parameter "methods" to @action

    class RecordViewSet(viewsets.ReadOnlyModelViewSet):
        """
        API endpoint that allows Records to be viewed or edited.
    
        """
        filter_backends = (SimpleRecordFilterBackend,)
        serializer_class = RecordSerializer
        permission_classes = (IsAuthenticated,)
    
        @action(
          detail=False,
          methods=['get','head'], # 'head' is required in order to workaround a bug of coreapi/DRF
          url_path='tagsum/(?P<account>[^/.]+)/(?P<tag>[^/.]+)'
        )
        def tagsum(self, request, account, tag):
            if not account or not tag:
                return Response({'status': "need accoutn and tag"})
            sum =  Records.objects.filter(linkedaccount_id=account).filter(user_project=tag).aggregate(total=Sum('unblendedcost'))
            return Response({'sum': sum['total']})
    

    With this, you should always get the same schema.js and always the same result when calling $coreapi get http://localhost:8000/api/docs/

    ...
        records: {
            tagsum: {
                read(account, tag)
            }
            list([page], [linkedaccount_id], [productname], [user_project])
            read(id, [linkedaccount_id], [productname], [user_project])
        }
    ...