We seen in our another post “Implementing filtering against query parameters for viewsets in DRF” , how to implement filters, but why these filters are actually required ? When we were trying to implement separating JSON objects based on “userid” and “username” using the object filters from views, it created following problem.
We wanted the API name to remain same for filtering objects based on userid and username, something like below,
- http://192.168.0.106:8001/user/userid
- http://192.168.0.106:8001/user/username
And our JSON was like below,
{
"id": 7,
"userid": "my_userid",
"username": "lynxbee4",
"email": "social(at)lynxbee.com",
"age": "452",
"useraddress": {
"id": 7,
"home_no": "123",
"street": "my home street",
"city": "my city",
"pincode": 123456
}
}
So, we implemented urls.py as below,
urlpatterns = [
path('user/<str:user_id>/', views.user_by_uuid.as_view()),
path('user/<str:user_name>/', views.user_by_name.as_view()),
]
And related views.py code looks as below,
class user_by_name(APIView):
def get(self, request, user_name, format=None):
users = UserInfo.objects.filter(username=user_name)
serializer = UserInfoSerializer(users, many=True)
return JsonResponse(serializer.data, safe=False)
class user_by_uuid(APIView):
def get(self, request, user_id, format=None):
users = UserInfo.objects.filter(userid=user_id)
serializer = UserInfoSerializer(users, many=True)
return JsonResponse(serializer.data, safe=False)
So, ideally if we use API “http://192.168.0.106:8001/user/my_userid/” and “http://192.168.0.106:8001/user/lynxbee4/” API to access the above object, we should have got the proper results, but this didn’t worked as expected.
When we accessed, http://192.168.0.106:8001/user/my_userid/ to filter based on “userid” we got proper result and seen the JSON object in return, but when we accessed using “username” like “http://192.168.0.106:8001/user/lynxbee4/” we just received empty JSON like [] … WHY ?
So question is why we received empty JSON when we tried to access using userid ??
We tried to identify what went wrong in this implementation, although our all code was correct in implementation of get functions.
Here is what happened…
We wanted our API URL to remain same for both filter using “userid” and “username” , so obviously we written urls.py as,
urlpatterns = [
path('user/<str:user_id>/', views.user_by_uuid.as_view()),
path('user/<str:user_name>/', views.user_by_name.as_view()),
]
When we tried to access using “userid” it worked fine because, Django matches urls from top to bottom in urlpatterns, so “http://192.168.0.106:8001/user/my_userid/” matched with view “user_by_uuid” in first attempt , since “my_userid” is a string and since implementation of “user_by_uuid” class was fine, it returned proper results.
But now when we tried to filter using “username” using url “http://192.168.0.106:8001/user/lynxbee4/” , Django has no way to differentiate whether “lynxbee4” was “userid” or “username” while matching urlpatterns.. since both are string and our API is also same i.e. “http://192.168.0.106:8001/user/” for both URL’s
hence in case when we tried to get json for username using “http://192.168.0.106:8001/user/lynxbee4/” , django will encounter first urlpattern for string i.e. user<str:user_id> , and it will consider “lynxbee4” as userid and call for “user_by_uuid” view, but since “user_by_uuid” view only filters based on userid, and there is no JSON object with userid = lynxbee4, we will get an empty JSON.. which we seen in actual result.
So, since Django matched urlpatten from username with userid as both being string, the control never reached to next view “user_by_name” thus we got incorrect results.
So, what is the solution for this ?
There are two solutions for this,
- use different API for each filter pattern like for userid, write API like “http://192.168.0.106:8001/user_by_id/my_userid/” and “http://192.168.0.106:8001/user_by_name/lynxbee4/” , and access seperate URL for filtering userid and username.
- But above solution 1 becomes complicated if we have lot of paramaters in JSON say for ex. 100, in that case we can’t go on writing seperate API for all 100 parameters. So use “query based filters”
So, as mentioned in second option, by using query based filters, we can use same API and filter the objects using userid and username like below,
- http://192.168.0.106:8001/user/?userid=my_userid
- http://192.168.0.106:8001/user/?username=lynxbee4