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

hi, happy to find you’re question , could you help me ?

i have this simple viewset and serializer

class UploadSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = ('image',)

class UploadViewSet(viewsets.ModelViewSet):
    queryset = Account.objects.all()
    serializer_class = UploadSerializer

i want to access the id that passed in the url , how could I ?

I tried a lot lot lot of solution but nothing help me… :smiling_face_with_tear: :sob:
sorry for my English ,

Hi!

Could you share the url pattern that maps with this viewset?
Also, is that all the code for the viewset?

Take in mind that ModelViewSet makes a lot of things for you under the hood.