Single view for multiple paths via URL kwargs
I'm building a simple API listing various plants. Currently, it's limited to filter via a single field like common name, species, etc.
My urls.py
urlpatterns = [
path('project/family=<str:family>/', views.SpeciesDetail_family.as_view(), name='family'),
path('project/species=<str:species>/', views.SpeciesDetail_species.as_view(), name='species')
]
And views.py
class SpeciesDetail_species(generics.ListAPIView):
serializer_class = SpeciesSerializer
def get_queryset(self):
queryset = Species.objects.filter()
species = self.kwargs['species']
if species is not None:
queryset = queryset.filter(species__iexact=species)
return queryset
class SpeciesDetail_family(generics.ListAPIView):
serializer_class = SpeciesSerializer
def get_queryset(self):
queryset = Species.objects.all()
family = self.kwargs['family']
if family is not None:
queryset = queryset.filter(family__iexact=family)
return queryset
How can I create a single view for these two paths? So, I just need to parameterize the lookup field (can be family, species, etc.) from the URL like /project/species=PlantSpecies/
or /project/family=PlantFamily/
.
If I add kwargs to the path like path('project/family=<str:family>/', views.SpeciesDetail_family.as_view(), {'lu_field':'family'}, name='family')
, how can I access lu_field
in the views?
Take a look at the code below see if it works the way you want :
from rest_framework import generics
from .models import Species
from .serializers import SpeciesSerializer
class SpeciesDetail(generics.ListAPIView):
serializer_class = SpeciesSerializer
def get_queryset(self):
queryset = Species.objects.all()
# Access dynamic lookup field and value from kwargs
lu_field = self.kwargs.get('lu_field')
lu_value = self.kwargs.get('lu_value')
# Filter the queryset dynamically
if lu_field and lu_value:
filter_kwargs = {f"{lu_field}__iexact": lu_value}
queryset = queryset.filter(**filter_kwargs)
return queryset
and here is the urls
:
from django.urls import path
from . import views
urlpatterns = [
path('project/<str:lu_field>=<str:lu_value>/', views.SpeciesDetail.as_view(), name='species_detail'),
]
You can redirect both to the same view, and thus work with:
urlpatterns = [
path(
'project/family=<str:value>/',
views.SpeciesDetailView.as_view(),
kwargs={'search_by': 'family'},
name='family',
),
path(
'project/species=<str:value>/',
views.SpeciesDetailView.as_view(),
kwargs={'search_by': 'species'},
name='species',
),
]
and let the view inspect self.kwargs['search_by']
:
from django.db.models import Q
class SpeciesDetailView(generics.ListAPIView):
serializer_class = SpeciesSerializer
queryset = Species.objects.all()
def get_queryset(self):
return (
super()
.get_queryset()
.filter(
Q((f'{self.kwargs["search_by"]}__iexact', self.kwargs['species']))
)
)
I would strongly advise not to allow filtering on arbitrary fields, since this can expose sensitive data by a hacker performing binary search on the secret.
Another option is, as @Abdul Aziz Barkat says in his comment, you can use an attribute and specify it in the .as_view(…)
[Django-doc]:
from django.db.models import Q
class SpeciesDetailView(generics.ListAPIView):
serializer_class = SpeciesSerializer
queryset = Species.objects.all()
def get_queryset(self):
return (
super()
.get_queryset()
.filter(Q((f'{self.search_by}__iexact', self.kwargs['species'])))
)
and inject this as:
urlpatterns = [
path(
'project/family=<str:value>/',
views.SpeciesDetailView.as_view(search_by='family'),
name='family',
),
path(
'project/species=<str:value>/',
views.SpeciesDetailView.as_view(search_by='species'),
name='species',
),
]