I have multiple API which historically work using id
as the lookup field:
/api/organization/10
I have a frontend consuming tho
Try this
from django.db.models import Q
import operator
class MultipleFieldLookupMixin(object):
def get_object(self):
queryset = self.get_queryset() # Get the base queryset
queryset = self.filter_queryset(queryset) # Apply any filter backends
filter = {}
for field in self.lookup_fields:
filter[field] = self.kwargs[field]
q = reduce(operator.or_, (Q(x) for x in filter.items()))
return get_object_or_404(queryset, q)
Then in View
class Organization(MultipleFieldLookupMixin, viewsets.ModelViewSet):
queryset = OrganisationGroup.objects.all()
serializer_class = OrganizationSerializer
lookup_fields = ('pk', 'another field')
Hope this helps.
I think the fundamental answer is that this would not be good REST/API design and just isn't something DRF would enable.
I solved the similar problem by overriding retrieve
method and check pk
field's value against any pattern. For example if it consists of only numbers.
def retrieve(self, request, *args, **kwargs):
if kwargs['pk'].isdigit():
return super(Organization, self).retrieve(request, *args, **kwargs)
else:
# get and return object however you want here.
class MultipleFieldLookupMixin(object):
"""
Apply this mixin to any view or viewset to get multiple field filtering
based on a `lookup_fields` attribute, instead of the default single field filtering.
"""
def get_object(self):
queryset = self.get_queryset() # Get the base queryset
queryset = self.filter_queryset(queryset)
filter = {}
for field in self.lookup_fields:
if self.kwargs[field]: # Ignore empty fields.
filter[field] = self.kwargs[field]
return get_object_or_404(queryset, **filter) # Lookup the object
class RetrieveUserView(MultipleFieldLookupMixin, generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
lookup_fields = ('account', 'username')
The official docs have an example for this at https://www.django-rest-framework.org/api-guide/generic-views/#creating-custom-mixins
Also, you need to modify the urls.py
adding a new route for the same view, but with the new field name.
There are a lot of answers here already, but none provide a full description including the mixin, view, and url configuration. This answer does.
This is the mixin that works best, it is slightly modified from https://www.django-rest-framework.org/api-guide/generic-views/#creating-custom-mixins to not error out on non-existing fields.
class MultipleFieldLookupMixin:
"""
Apply this mixin to any view or viewset to get multiple field filtering
based on a `lookup_fields` attribute, instead of the default single field filtering.
Source: https://www.django-rest-framework.org/api-guide/generic-views/#creating-custom-mixins
Modified to not error out for not providing all fields in the url.
"""
def get_object(self):
queryset = self.get_queryset() # Get the base queryset
queryset = self.filter_queryset(queryset) # Apply any filter backends
filter = {}
for field in self.lookup_fields:
if self.kwargs.get(field): # Ignore empty fields.
filter[field] = self.kwargs[field]
obj = get_object_or_404(queryset, **filter) # Lookup the object
self.check_object_permissions(self.request, obj)
return obj
Now add the view as follows, it is important to have the Mixin first, otherwise the get_object method is not overwritten:
class RudAPIView(MultipleFieldLookupMixin, generics.RetrieveUpdateDestroyAPIView):
...
lookup_fields = ['pk', 'other_field']
Now, for the urls, we use default converters. It is important int comes first as that one will actually check if it is an int, and if not fallback to str. If you have more complex fields, you need to resort to regex.
path('efficiency/<int:pk>/', views.RudAPIView.as_view(), name='something-rud'),
path('efficiency/<string:other_field>/', views.RudAPIView.as_view(), name='something-rud'),