Django Rest Framework Viewsets urls

Hi!

Per this code:

# views.py
EmployeeViewSet(GenericViewSet)

    @action(methods=["get"], detail=False)
    def approvedRequests(self, request, employee_id):
    	 # ...

LeaderViewSet(GenericViewSet)

    @action(methods=["get"], detail=False)
    def approvedRequests(self, request, employee_id):
        # ...

# urls.py
router.register(r'<int:employee_id>', views.EmployeeViewSet, basename="employee")
router.register(r'leaders/<int:employee_id>', views.LeaderViewSet, basename="leader")

urlpatters = [
    path('employees/', include(router.urls)),
    # ...
]

# Result
'^employees/{employee_id}/approvedRequests/$'
'^employees/leaders/{employee_id}/approvedRequests/$'

The urls matches what I need, and also Swagger let me provide an employee_id as a path parameter which is what I expect, but Django responds with a 404:

"GET /employees/4/approvedRequests/ HTTP/1.1" 404 7079

When reversing the url, it seems to me that the <int:employee_id> notation on the router it’s not working as if it were inside a path(), therefore the url with the path parameter doesn’t really exist.

>>> reverse("requests:request-leader-approvedRequests")
'/employees/leaders/%3Cint:employee_id%3E/approvedRequests/'

Other approach

Is there a way to “register” a viewset routes inside the path?

# urls.py

urlpatters = [
    path('employees/<int:employee_id>', EmployeeViewSet.urls), # Something like this 
]

I’m relatively new to DRF, so this may be a misconcept I have from routers.
Thanks in advance.

Hi Marco,

Before trying to manipulate the viewset into giving you the urls you want, you should try reworking the path argument to register. It looks like you’re supplying it a regular expression, r'<int:employee_id>', but the contents of that expression are what we’d typically supply to a django.urls.path call.

You need to treat that argument as if it were a re_path call instead.

1 Like

Thanks for answering Tim.

There is no example on the documentation of defining path parameters as an argument of router.register(), that was just me trying to solve my problem. :sweat_smile:

Is it common to define this kind of notation on routers?

That said, I made a simpler (and I think clever) approach: create a separate router for the second viewset.

Right now I can’t see a downside on this approach other than code duplication (3 lines).

Is it common to define this kind of notation on routers?

I would expect this to fall within range of normal use cases, but that’s based on my experiences.

That said, I made a simpler (and I think clever) approach: create a separate router for the second viewset.

Right now I can’t see a downside on this approach other than code duplication (3 lines).

That’s fair. It shouldn’t be necessary, but if it has unstuck you then move forward.

Now I’m intrigued about how to define path parameters as a router argument. Do you have any example repo?

I believe it should be the following.

router.register(r'^(?P<employee_id>\d+)/', views.EmployeeViewSet, basename="employee")
router.register(r'^leaders/(?P<employee_id>\d+)/', views.LeaderViewSet, basename="leader")
1 Like

Hi Marco,

Here is an example of a URL which will return an answer to a question. The models look a little like this.

class Quiz(models.model):
      id

class QuizQuestion(models.model):
      id
      quiz = models.ForeignKey(Quiz)

class QuizAnswer(models.model):
     id
     question = models.ForeignKey(QuizQuestion)

In order to list all questions or get a single question, I use this URL

router.register(
    "quiz/(?P<quiz>.+)/question/(?P<quiz_question>.+)/answers",
    QuizAnswerCreatorViewSet,
    basename="quiz_answer_viewset",
)

This means I can get all answers at:

/quiz/<quiz_id>/question/<question_id>/answers

and I can get a single answer with:

/quiz/<quiz_id>/question/<question_id>/answers/<answer_id>

In your ViewSet, you can define lookup_field = one_of_your_models_fields and use that field instead of the models primary key should you wish.

Hope that was helpful.

Cheers,

Conor

1 Like

I try to use regex as a last resort, but I know they can be pretty useful in certain cases.
Thanks for the tip! @CodenameTim @conor

1 Like