Search code examples
djangopython-3.xdjango-rest-frameworkapi-design

Django-REST: Proper way to add API to get entity via another field?


I'm super new to Python/Django/Django-REST, but I've managed to follow the tutorial and create my own API similar to the tutorial.

In my app, I have a model called Toggle, with a unique field called feature_key.

Following the tutorial, the default way to get a Toggle is by using the ID, i.e.

http://127.0.0.1:8000/toggles/1/

And it shows up in the browsable API page as a clickable link.


My question is, how do I add another endpoint to directly get a toggle via its feature_key? Maybe something like:

http://127.0.0.1:8000/toggles/key/category.key_1/

(I'm not sure if this is a "correct" way to design API)

How do I make this endpoint to be visible in the browsable API so that other devs are aware that this endpoint exists?


toggle/views.py:

This is almost the same as the tutorial's view.py

# imports here


@api_view(['GET'])
def api_root(request, format=None):
    return Response({
        'users': reverse('user-list', request=request, format=format),
        'toggles': reverse('toggle-list', request=request, format=format)
    })


class ToggleViewSet(viewsets.ModelViewSet):
    queryset = Toggle.objects.all()
    serializer_class = ToggleSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]

    def perform_create(self, serializer):
        serializer.save(created_by=self.request.user)


class UserViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

toggle/serializers.py:

Also similar to the tutorial

class ToggleSerializer(serializers.HyperlinkedModelSerializer):
    created_by = serializers.ReadOnlyField(source='created_by.username')
    is_enabled = serializers.BooleanField(initial=True)

    class Meta:
        model = Toggle
        fields = ['url', 'id', 'feature_key', 'description', 'is_enabled', 'created_at', 'created_by']


class UserSerializer(serializers.HyperlinkedModelSerializer):
    toggles = serializers.HyperlinkedRelatedField(many=True, view_name='toggle-detail', read_only=True)

    class Meta:
        model = User
        fields = ['url', 'id', 'username', 'toggles']

toggle/urls.py:

Same as tutorial

router = DefaultRouter()
router.register(r'toggles', views.ToggleViewSet)
router.register(r'users', views.UserViewSet)

urlpatterns = [
    path('', include(router.urls)),
]

Solution

  • I checked the tutorial...

    You can make following changes: urls.py

    replace this

    path('toggles/<int:pk>/', views.toggles_function),
    

    with this:

    path('toggles/<feature_key>/', views.toggles_function),
    

    views.py

    @csrf_exempt
    def snippet_detail(request, key):
        try:
            snippet = Snippet.objects.get(feature_key=key)
        except Snippet.DoesNotExist:
            return HttpResponse(status=404)
    

    you can get the link http://127.0.0.1:8000/toggles/<feature_key of entry>/

    if you want link like this

    http://127.0.0.1:8000/toggles/key/category.key_1/
    

    in urls.py

    path('toggles/<pk>/<feature_key>/', views.toggles_function),
    

    and in views.py

    @csrf_exempt
        def snippet_detail(request, pk, key):
            try:
                snippet = Snippet.objects.get(feature_key=key) # snippet = Snippet.objects.get(pk=pk)
            except Snippet.DoesNotExist:
                return HttpResponse(status=404)
    

    suggestion

    You don't need use two fields from same model in url (like pk and feature_key both) if both are unique fields. You can get the instance from any of them.