I am using Django Rest Framework 3.14 with ModelViewsets
and a settings-wide DjangoModelOrAnonReadOnly
permission class.
Given this config, out of the box my JSON API seems to respond to OPTIONS requests in a misleading way, i.e. sending unauthenticated OPTIONS requests to /api/collections/collectionA/items
is replied with Allow: GET, POST, HEAD, OPTIONS
in the headers (correct would be: GET, HEAD, OPTIONS
). However if I define my own metadataclass
and do something like:
def options(self, request, *args, **kwargs) -> response.Response:
allowed_actions = self.metadata_class().determine_actions(request, self)
allowed_actions = ", ".join(allowed_actions.keys())
# ^ allowed_actions is correct
data = self.metadata_class().determine_metadata(request, self)
return response.Response(data, headers={"Allow": allowed_actions})
I am able to get the correct allowed_actions
(GET, OPTIONS, HEAD). However, and that is my issue, headers are unmodified by the last statement in the snipper above.
How can I update my headers to ensure that the Allow
headers correctly reflect the state of my API?
Context: this is required while implementing an OGC API Features endpoint. It's an OpenAPI defintion for Geospatial data. Details can be found here.
And from the part 4 (CRUD operations):
A server is not required to implement every method described in this specification (i.e. POST, PUT, PATCH or DELETE) for every mutable resource that it offers. Furthermore, a server that supports the ability to add, modify or remove resources from collections is not likely to be an open server. That is, access to the server, and specifically the operations that allow resource creation, modification and/or removal, will be controlled. Such controls might, for example, take the form of policy requirements (e.g. resources on this server can be inserted or updated but not deleted) or user access control requirements (e.g. user "X" is only allowed to create resources but not update or delete resources). Regardless of the controls the server must be able to advertise, within the control context in place, which methods are available for each resource that it offers. This is accomplished using the HTTP OPTIONS method.
The HTTP OPTIONS method allows the server to explicitly declare which HTTP methods are supported for a particular resource endpoint. This specification deals with the HTTP POST, PUT, PATCH and DELETE methods but any relevant HTTP method may be listed for a particular resource.
It seems to be you need to override the finalize_response(...)
method to patch the Allow
header.
from django.http import Http404
from rest_framework import permissions, viewsets
from rest_framework.exceptions import APIException, PermissionDenied
from rest_framework.request import clone_request
from polls.models import Poll
from .serializers import PollSerializer
def get_allow_list(request, view) -> list[str]:
allowed_methods = []
for method in view.allowed_methods:
view.request = clone_request(request, method)
try:
view.check_permissions(view.request)
allowed_methods.append(method)
except (APIException, PermissionDenied, Http404):
pass
return allowed_methods
class PollViewSet(viewsets.ModelViewSet):
serializer_class = PollSerializer
queryset = Poll.objects.all()
permission_classes = [permissions.DjangoModelPermissionsOrAnonReadOnly]
def finalize_response(self, request, response, *args, **kwargs):
response = super().finalize_response(request, response, *args, **kwargs)
if request.method == "OPTIONS":
allow_str = ", ".join(get_allow_list(request, self))
response.headers["Allow"] = allow_str
return response
I think you missunderstood the concept of Allow
header
The
Allow
header lists the set of methods supported by a resource.
Since you are using a ModelViewsets
, and I assume you are using it with a router, with all default configurations, then, most likely, you will have all the HTTP methods enabled for the client. In other words, the Allow
header returns the value irrespective of the Authorization checks.