asa
asa

Reputation: 779

Nested Routes in Django with Generic Views (not ViewSets)

All the tutorials and online resources I find teach how to build nested routes in DRF with ViewSets. How could one build nested routes in Django with generic class views?

Example:

I would like to have these endpoints:

So my code looks like this:

# urls.py

from django.urls import path, include
from . import views

urlpatterns = [
    path("products/", views.ProductList.as_view()),
    path(
        "products/<int:product_pk>/",
        include(
            [
                path("", views.ProductDetail.as_view()),
                path("reviews/", views.ReviewList.as_view()),
                path("reviews/<int:review_pk>/", views.ReviewDetail.as_view()),
            ]
        ),
    )
]

The views are all generic views:

# views.py

class ProductList(ListCreateAPIView):
    queryset = Product.objects.all()
    serializer = ProductSerializer


class ProductDetail(RetrieveUpdateDestroyAPIView):
    queryset = Product.objects.all()
    serializer = ProductSerializer
    lookup_field = "product_pk"


class ReviewList(ListCreateAPIView):
    serializer_class = ReviewSerializer

    def get_queryset(self):
        return Review.objects.filter(product_id=self.kwargs["product_pk"]).all()

    def get_serializer_context(self):
        return {"product_id": self.kwargs["product_pk"]}


class ReviewDetail(RetrieveUpdateDestroyAPIView):
    serializer_class = ReviewSerializer
    lookup_field = "review_pk"

    def get_queryset(self):
        return Review.objects.filter(product_id=self.kwargs["product_pk"]).all()

The first problem

When I hit the endpoint /products/1/reviews/1 (assuming there is data). Django throws this error:

FieldError at /products/1/reviews/1/

Cannot resolve keyword 'review_pk' into field. 
Choices are: created_at, description, id, name, product, product_id

Understood, I will do exactly what the error is saying and change my URL lookup name to id. Once the change is made, this endpoint works as expected.

The second problem

When I hit the endpoint /products/1, Django throws this error:

FieldError at /products/1/

Cannot resolve keyword 'product_pk' into field. 
Choices are: description, id, review, title, unit_price

Thsi time I cannot change the lookup field name to id, as suggested. If I do so, Django cannot differentiate the reviews lookup field from the product one.

What is the solution in this case?

Upvotes: 1

Views: 22

Answers (1)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476594

I think you misunderstand the function of the lookup_field=… [drf-doc], this is not the value from the path, it is how Django is supposed to filter on the model.

You are looking for the lookup_url_kwarg=… [drf-doc], which specifies what part of the path to use as lookup field:

class ProductDetail(RetrieveUpdateDestroyAPIView):
    queryset = Product.objects.all()
    serializer = ProductSerializer
    lookup_url_kwarg = 'product_pk'


class ReviewDetail(RetrieveUpdateDestroyAPIView):
    serializer_class = ReviewSerializer
    lookup_url_kwarg = 'review_pk'

    def get_queryset(self):
        return Review.objects.filter(product_id=self.kwargs['product_pk'])

Note: There is no need to add an .all() [Django-doc] at the end of a QuerySet. .all() clones the queryset, but since you construct one each time, cloning only generates more CPU cycles.

Upvotes: 0

Related Questions