Drf url parameter lookup
I'm creating an endpoint api/users/
with Django REST framework which should return some data about the user by id. It's all good, but I want to obtain a lookup parameter for my User
model from the url's parameters. There are lookup_field
and lookup_url_kwarg
attributes of GenericAPIView
, but they seem to be useless here, because they work with parameters specified in URLConf
, if I understand it right. So, my question is how to get object from the db when I go to like /api/users?uid=123
?
We can surely override get_object()
or get_queryset()
or any other stuff like this:
class UserAPIView(RetrieveUpdateDestroyAPIView,
CreateAPIView):
serializer_class = UserSerializer
queryset = User.objects.all()
def get_object(self) -> User:
uid = self.request.query_params.get('uid', None)
if uid is None or not uid.isdigit():
raise Http404
self.kwargs.update({self.lookup_field: int(uid)})
return super().get_object()
but it looks kind of ugly, isn't it? I believe there should be a better way of dealing with it..
but they seem to be useless here, because they work with parameters specified in URLConf, if I understand it right.
That's right, although Django's REST framework actually takes into account the querystring. This for the format (JSON, CSV, etc.), filtering, ordering, searching, and pagination. But the main reason why is because those are optional parameters.
but it looks kind of ugly, isn't it? I believe there should be a better way of dealing with it..
You could work with a small mixin:
class PassQueryToKwarg:
query_to_kwarg = None
def dispatch(self, request, *args, **kwargs):
query_to_kwargs = self.query_to_kwargs
if isinstance(query_to_kwargs, None):
query_to_kwargs = ()
elif not isinstance(query_to_kwargs, (list, tuple)):
query_to_kwargs = (query_to_kwargs,)
for qtk in query_to_kwargs:
v = request.query_params.get(qtk, None)
if v is not None:
kwargs[qtk] = v
return super().dispatch(self, request, *args, **kwargs)
and then thus mix it in with:
class UserAPIView(PassQueryToKwarg, RetrieveUpdateDestroyAPIView, CreateAPIView):
serializer_class = UserSerializer
queryset = User.objects.all()
query_to_kwarg = GenericAPIView.lookup_field
and that is it.
But actually querystrings for mandatory parameters is just wrong. Indeed, thee fact that these parameters appear in the path has a good reason: Django matches on the path, so that means it will only fire the view, if the parameter is there. So this actually makes the logic more elegant. So using it as ?uuid=…
only generates more trouble. As a rule of thumb, put required parameters in the path, and optional ones in the querystring.
Although it was perhaps not as designed by the Django REST framework, it is probably better not to look in the querystring for the primary key of the object to retrieve. Especially since that makes it harder to disambiguate between retrieving a list of items, and retrieving a single item.