Reputation: 779
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:
/products/
/products/<product_pk>/
/products/<product_pk>/reviews/
/products/<product_pk>/reviews/<reviews-pk>/
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
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 aQuerySet
..all()
clones the queryset, but since you construct one each time, cloning only generates more CPU cycles.
Upvotes: 0