Include a location header in Django Rest Framework
Asked Answered
C

3

9

I read in a post that it is ideal to put that it is a good standard to include a Location header that points to the URL of the new resource (newly created via POST). My problem is I do not know how to include it.

I am using a class-based views using the APIView and my code in the view is:

class ListArtists(APIView):
    serializer_class = ArtistSerializer
    def get(self, request, format=None):
        _array = Artist.objects.filter()
        serializer = self.serializer_class(_array, many=True)
        if serializer.data:
            _status = status.HTTP_200_OK
        else:
            _status = status.HTTP_204_NO_CONTENT
        return Response(standardResponse(data=serializer.data), status=_status)

    def post(self, request, format=None):
        serializer = self.serializer_class(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(standardResponse(data=serializer.data), status=status.HTTP_201_CREATED)
        else:
            return Response(standardResponse(errors=serializer.errors))
artist = ListArtists.as_view()

urls.py

from django.conf.urls import url, include

from store import views

urlpatterns = [
    url(r'^artists/', views.artist, name='artists-list'),
]

P.S.

Every time I throw a request using my Advanced REST Client this is the response that I receive:

Date: Sat, 23 Jul 2016 10:54:23 GMT
Server: WSGIServer/0.1 Python/2.7.10
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN
Content-Type: application/json
Allow: GET, POST, HEAD, OPTIONS
Crosspiece answered 23/7, 2016 at 10:58 Comment(2)
You can add headers to the response object before returning it.Lowrance
Could you provide a code please..Crosspiece
L
11

You can add arbitrary headers to a Response object, like so:

def post(self, request, format=None):
    serializer = self.serializer_class(data=request.data)
    if serializer.is_valid():
        obj = serializer.save()
        response = Response(standardResponse(data=serializer.data), 
                            status=status.HTTP_201_CREATED)
        # If you have defined a get_absolute_url method on your model, then
        # you can use that to get a URL for the new object
        response['Location'] = obj.get_absolute_url()
        return response
Lowrance answered 23/7, 2016 at 13:3 Comment(2)
Now this is the answer t hat I am looking for! Thanks!Crosspiece
Hi, Can we also remove any header field manually, in DRF, If yes can you please throw some light here:- #68707489Mogador
F
1

The default of the create-function in a CreateModelMixin calls self.get_success_headers(serializer.data) (implemented here).

We can see that by default api_settings.URL_FIELD_NAME would be included. URL_FIELD_NAME is by default url.

When changing this value in your settings.py to, for example, id will now include the id in the Location header:

REST_FRAMEWORK = {
    "URL_FIELD_NAME": "id",  # This field is used in the Location header on a created resource
}

Resulting POST request:

HTTP 201 Created
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Location: fe36f285-ab38-4ef9-b707-425f03a7cdc5
Vary: Accept

{
    "id": "fe36f285-ab38-4ef9-b707-425f03a7cdc5",
}
Freestyle answered 17/11, 2023 at 10:11 Comment(0)
E
0

For those using the drf CreateModelMixin the view has to look something like that:

In case you do want an empty body in the response:

class MyView(mixins.CreateModelMixin, generics.GenericAPIView):

    serializer_class = MySerializer

    def post(self, request, *args, **kwargs):
        default_response = self.create(request, *args, **kwargs)
        return Response(headers={'Location': reverse("get_enpoint_name", args=[default_response.data['id']], request=self.request)})

In case the response body has to include the objects data as specified in the serializer:

class MyView(mixins.CreateModelMixin, generics.GenericAPIView):

    serializer_class = MySerializer

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

    def get_success_headers(self, data):
        try:
            return {'Location': reverse("customer", args=[data['id']], request=self.request)}
        except (TypeError, KeyError):
            return {}

Always open for improvements!

Emulate answered 16/8, 2021 at 7:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.