Django Rest Framework: Access item detail by slug instead of ID

DjangoDjango Rest-Framework

Django Problem Overview


Is it possible to use a object's slug (or any other field) to access the details of an item, instead of using the ID?

For example, if I have an item with the slug "lorem" and ID 1. By default the URL is http://localhost:9999/items/1/. I want to access it via http://localhost:9999/items/lorem/ instead.

Adding lookup_field in the serializer's Meta class did nothing to change the automatically generated URL nor did it allow me to access the item by manually writing the slug instead of the ID in the URL.

models.py

class Item(models.Model):
    slug = models.CharField(max_length=100, unique=True)
    title = models.CharField(max_length=100, blank=True, default='')
    # An arbitrary, user provided, URL
    item_url = models.URLField(unique=True)

serializers.py

class ClassItemSerializer(serializers.HyperlinkedModelSerializer):
     class Meta:
        model = Item
        fields = ('url', 'slug', 'title', 'item_url')

views.py

class ItemViewSet(viewsets.ModelViewSet):
    queryset = Item.objects.all()
    serializer_class = ItemSerializer

urls.py

router = DefaultRouter()
router.register(r'items', views.ItemViewSet)

urlpatterns = [
    url(r'^', include(router.urls)),
]

Generated JSON:

[    {        "url": "http://localhost:9999/items/1/",        "slug": "lorem",        "title": "Lorem",        "item_url": "http://example.com"    }]

Django Solutions


Solution 1 - Django

You should set lookup_field in your serializer:

class ItemSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Item
        fields = ('url', 'slug', 'title', 'item_url')
        lookup_field = 'slug'
        extra_kwargs = {
            'url': {'lookup_field': 'slug'}
        }

and in your view:

class ItemViewSet(viewsets.ModelViewSet):
    queryset = Item.objects.all()
    serializer_class = ItemSerializer
    lookup_field = 'slug'

I got this result:

~ curl http://127.0.0.1:8000/items/testslug/ | python -mjson.tool
{
    "item_url": "https://example.com/", 
    "slug": "testslug", 
    "title": "Test Title", 
    "url": "http://127.0.0.1:8000/items/testslug/"
}

Solution 2 - Django

In some scenarios you may want to have both the "low level" pk value and the more semantic slug. I like to have both options personally and do this by setting the lookup_field later in the viewset as_view() method in my urls.py.

> Note: the following defaults to pk with an optional slug lookup. To combine this with previous answer you would change the lookup_field below to be "pk" instead of "slug".

from django.conf.urls import *
from rest_framework.urlpatterns import format_suffix_patterns

from myobjects import views as myviews


# simplify the view definitions by splitting out the options
REQDICT = {
    'get': 'retrieve',
    'put': 'update',
    'patch': 'partial_update',
    'delete': 'destroy'
}


# define the pk detail view
myobject_detail = myviews.MyObjectViewset.as_view(REQDICT)
# define the slug detail view
myobject_slug_detail = myviews.MyObjectViewset.as_view(REQDICT, lookup_field='slug')


urlpatterns = [
    url(r"^myobjects/(?P<pk>\d*)/$",
           myobject_detail,
           name = 'myobject-detail'),
    url(r"^myobjects/(?P<slug>[-\w]+)/$",
           myobject_slug_detail,
           name = 'myobject-slug-detail'),
]

urlpatterns = format_suffix_patterns(urlpatterns)

> This could also be in your views.py - my preference is to see it beside the urlpatterns list.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionVirgiliuView Question on Stackoverflow
Solution 1 - DjangoIlya TikhonovView Answer on Stackoverflow
Solution 2 - DjangorjmoggachView Answer on Stackoverflow