Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple lookup_fields for django rest framework

I have multiple API which historically work using id as the lookup field:

/api/organization/10

I have a frontend consuming those api.

I'm building a new interface and for some reasons, I would like to use a slug instead an id:

/api/organization/my-orga

The API is built with Django Rest Framework. Except the change of lookup field, the api behavior should stay the same.

Is there a solution to allow my API to work with both a slug and a pk ? Those two path should give them same results:

/api/organization/10
/api/organization/my-orga

Here is my API definition:

# urls.py
router = DefaultRouter()
router.register(r'organization', Organization)
urlpatterns = router.urls

#view.py
class Organization(viewsets.ModelViewSet):
    queryset = OrganisationGroup.objects.all()
    serializer_class = OrganizationSerializer

# serializer.py
class OrganizationSerializer(PermissionsSerializer):
    class Meta:
        model = Organization
like image 296
Alex Grs Avatar asked Jul 19 '16 14:07

Alex Grs


People also ask

What is Lookup_field in Django?

lookup_field - The model field that should be used to for performing object lookup of individual model instances. Defaults to 'pk' . Note that when using hyperlinked APIs you'll need to ensure that both the API views and the serializer classes set the lookup fields if you need to use a custom value.

What is mixin in Django REST framework?

mixins are classes that generally inherit from object (unless you are django core developer) mixins are narrow in scope as in they have single responsibility. They do one thing and do it really well. mixins provide plug-in functionality. although mixins work through inheritence, they DONT create a subtyping relation.

Can I use Django and Django REST framework together?

Django Rest Framework makes it easy to use your Django Server as an REST API. REST stands for "representational state transfer" and API stands for application programming interface. Note that with DRF you easily have list and create views as well as authentication.


7 Answers

Try this

from django.db.models import Q
import operator
from functools import reduce
from django.shortcuts import get_object_or_404

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')
like image 92
itzMEonTV Avatar answered Sep 17 '22 17:09

itzMEonTV


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.
like image 24
Hikmat G. Avatar answered Sep 21 '22 17:09

Hikmat G.


I know you asked this question quite a time ago, but here is the complete solution i got from all answers, considering both views and urls:

Put this in your views.py: (With a little edit from drf)

class MultipleFieldLookupMixin(object):

def get_object(self):
    queryset = self.get_queryset()             
    queryset = self.filter_queryset(queryset)  
    filter = {}
    for field in self.lookup_fields:
        if self.kwargs.get(field, None):  
            filter[field] = self.kwargs[field]
    obj = get_object_or_404(queryset, **filter)  # Lookup the object
    self.check_object_permissions(self.request, obj)
    return obj

Then inherit your view from this Mixin and add fields you want to lookup_fields. Like this:

class YourDetailView(MultipleFieldLookupMixin, RetrieveUpdateAPIView):
    ...
    lookup_fields = ['pk', 'slug','code']

And in urls.py:

re_path(r'^organization/(?P<pk>[0-9]+)/$',
        YourDetailView),
re_path(r'^organization/(?P<slug>[-a-zA-Z0-9_]+)/$',
        YourDetailView),
re_path(r'^organization/sth_else/(?P<code>[0-9]+)/$',
        YourDetailView),
like image 36
Mohamad Malekzahedi Avatar answered Sep 21 '22 17:09

Mohamad Malekzahedi


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')
like image 31
ahprosim Avatar answered Sep 17 '22 17:09

ahprosim


I think best way is to override the get_object(self) method

class Organization(generics.RetrieveAPIView):
    serializer_class = OrganizationSerializer
    queryset = Organization.objects.all()
    multiple_lookup_fields = ['pk', 'slug']

    def get_object(self):
        queryset = self.get_queryset()
        filter = {}
        for field in self.multiple_lookup_fields:
            filter[field] = self.kwargs[field]

        obj = get_object_or_404(queryset, **filter)
        self.check_object_permissions(self.request, obj)
        return obj
like image 33
giveJob Avatar answered Sep 19 '22 17:09

giveJob


I think the fundamental answer is that this would not be good REST/API design and just isn't something DRF would enable.

like image 44
Evan Zamir Avatar answered Sep 19 '22 17:09

Evan Zamir


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'),
like image 38
Hielke Walinga Avatar answered Sep 19 '22 17:09

Hielke Walinga