Openapi - duplicate operationID and extra parameter - django-rest-framework

I'm documenting my Django API using Swagger.When I generate my Openapi schema, a second route with an additional parameter {format} is added for each of my paths, with the same operation ID as the "correct" route.
I read that the generator may generate duplicate operationId if I have several views with the same model, but this is not my case I think.
Overall, my views (resource/api/views.py) are all organized like this:
class ResourceList(APIView):
"""
View to list Resources.
"""
def get(self, request, parameter1):
...
class ResourceDetail(APIView):
"""
View to retrieve information of a Resource.
"""
def get(self, request, parameter1, parameter2):
...
For these two views, I have these two paths:
urlpatterns = ([
path('<str:parameter1>', views.ResourceList.as_view()),
path('<str:parameter1>/details/<str:parameter2>', views.ResourceDetail.as_view())
])
And the schema generator generates two routes for each.
For the first path:
Route: /api/resource/{parameter1}, Method: get
Route: /api/resource/{parameter1}{format}, Method: get
For the second path:
Route: /api/resource/{parameter1}/details/{parameter2}, Method: get
Route: /api/resource/{parameter1}/details/{parameter2}{format}, Method: get
A warning like this appears:
Route: /api/resource/{parameter1}, Method: get
Route: /api/resource/{parameter1}{format}, Method: get
An operationId has to be unique across your schema. Your schema may not work in other tools.
The warning obviously makes sense, because both routes have the same operationID,(retrieveResourceList in this case). What I don't understand is why the second route is being generated and where that format parameter comes from.
Is this a normal behaviour? If not, what am I doing wrong?

I'm using Autoschema to overide operation_id
from rest_framework.schemas.openapi import AutoSchema
class CandidateAssignToMeList(CandidateMixins,generics.ListAPIView):
schema = AutoSchema(
tags=['LISTVIEW'],
component_name='Assign To Me',
operation_id_base='CandidateAssignToMeList',
)
class CandidateList(CandidateMixins,generics.ListAPIView):
schema = AutoSchema(
tags=['LISTVIEW'],
component_name='Candidate ListView',
operation_id_base='CandidateList',
)

This might not be the way to resolve this fully but in my case, I try commenting the format_suffix_patterns and the format parameter disappear. Here is how I manage the urls:
from rest_framework.urlpatterns import format_suffix_patterns
urlpatterns = [...]
urlpatterns = format_suffix_patterns(urlpatterns) # Comment this

Related

Django rest framework XLSX renderer + Apiview

I'm setting up an endpoint on my API which should return a XLSX-file. The DRF-docs mention https://github.com/wharton/drf-renderer-xlsx as the main external rendering package, aside from the pandas which also seem to be able to render XLSX.
In their code example they use a ReadOnlyViewset paired with a mixin, but there is no mention of how it's used with APIViews. Still, I would like to use an APIView as shown by this https://harshahegde.dev/rendering-xlsx-files-in-django-rest-framework-ckagk293p00eumks1bf4dlhie
However..
This works great when using CURL or Postman, but when done through a browser I get this error:
'Request' object has no attribute 'accepted_renderer'
From what I understand this is because there is no Accept header set (e.g 'Accept':'application/xlsx')
I fixed this by removing the Mixin from the renderer_classes, so it simply returns a file called "xlsx" but I can't figure out how to set the filename without the mixin. How do I set the filename using an APIView trying to access the URL from a browser?
My view:
class SomedataXlsx(APIView):
renderer_classes = [XLSXRenderer, JSONRenderer]
def get(self, request):
queryset = Somedata.objects.all()
serializer = SomeDataSerializer(queryset, many=True)
return Response(serializer.data)
Looking at the mixin code it became clear they change the content-disposition header, and so since the DRF Response() takes a header argument I tried changing it, and it worked perfectly.
class SomedataXlsx(APIView):
renderer_classes = [XLSXRenderer, JSONRenderer]
def get(self, request):
user_sub_fund_data = Somedata.objects.all()
serializer = SomeDataSerializer(queryset, many=True)
return Response(serializer.data, headers={"content-disposition":"attachment; filename=mynewfilename.xlsx"})

DRF Register a sub viewset

I try to configure a viewset "behind" another one. Here is the code :
from rest_framework import routers
router = routers.DefaultRouter()
router.register('places/nice', NicePlacesViewSet)
router.register('places/nice/current', CurrentNice remove last slash PlacesViewSet)
Both routes are available according to /manage.py show_urls, but if a do a GET on places/nice/current, I just get a 404 error.
And if a change the last line to remove last slash:
router.register('places/nice_current', CurrentPlacesViewSet)
everything work perfectly and I can get current viewset informations.
Is there any limitations on nested viewsets like that ? Viewset should not be nested on the same path ? :)
Change the order of the register() statements as
router = DefaultRouter()
router.register('places/nice/current', CurrentPlacesViewSet)
router.register('places/nice', NicePlacesViewSet)

Parameters URL with DRF routers

I'm using Django Rest Framework for created a API. In this project i want to capture parameters in the URL. For example i want to capture the username and password of a user and my idea is like this:
http://localhost:8000/accounts/login/?unsername=username&password=password
But i cant, I' usin routers and django-filter, but i cant get the parameters url. My project files are there:
view.py:
class AccountsData(viewsets.ModelViewSet):
queryset = models.UserData.objects.all()
serializer_class = serializers.AccountsDataSerializer
permission_classes = (IsAuthenticated,)
filter_backends = (filters.DjangoFilterBackend,)
filterset_fields = ['username', 'password']
lookup_url_kwarg = 'username'
#action(methods=['get'], detail=True, url_name='login', url_path='login')
def login(self, request, pk=None):
return Response({"Login successfully"}, 200)
urls.py:
from api import views
router = routers.SimpleRouter()
router.register(r'accounts', views.AccountsData)
Request query parameters have nothing to do with routing, they are passed with the request independently of how you configure the route. You have access to them in request.query_params, for example, request.query_params.get('username') would get the value of the username parameter.
Being said that, your idea has a terrible mistake: password or any kind of confidential data should NEVER go in query parameters, you should use an http verb that carries the data in its body (POST, for example).

How to do AJAX in Django CMS Admin

I have 2 models in Django CMS - a Map (which has name and an image attributes), and a Location, which one of it's attributes is a Map. I want, when the user changes the Map, to perform an AJAX request to get the map details for that item so that I can add the Map image to the page to do some further jQuery processing with it. But I'm new to Django and I can't seem to figure it out. Anything I find seems unrelated - in that it talks about using AJAX on front end forms.
I have my jQuery file ready to go but I don't know what to put for the URL of the AJAX call and how/where to set up the endpoint in Django.
Your question seem related to custom Django admin url.
First, update your MapAdmin to provide an endpoint to search location
from django.contrib import admin
from django.http import JsonResponse
class MapAdmin(admin.ModelAdmin):
def get_urls(self):
admin_view = self.admin_site.admin_view
info = self.model._meta.app_label, self.model._meta.model_name
urls = [
url(r'^search_location$', admin_view(self.search_location), name=("%s_%s_search_location" % (info))),
]
return urls + super(VideoAdmin, self).get_urls()
def search_location(self, request, *args, **kwargs):
map = request.GET.get('map')
# Do something with map param to get location.
# Set safe=False if location_data is an array.
return JsonResponse(["""..location_data"""], safe=False)
Next, somewhere in your template file, define the URL point to search location endpoint. And use that URL to fetch location data
once map is changed.
var searchLocationUrl = "{% url 'admin:appName_mapModel_search_location' %}";

DRF detail route without a PK

I’d like to create a detail route that renders the data of the current user.
If I use a custom router:
class CustomRouter(routers.DefaultRouter):
def __init__(self, *args, **kwargs):
super(CustomRouter, self).__init__(*args, **kwargs)
self.routes.append(
routers.Route(url=r'^{prefix}/current{trailing_slash}',
mapping={'get': 'current'},
name='{basename}-current',
initkwargs={'suffix': 'Detail'}))
rest_router = CustomRouter()
urlpatterns = [
url(r'^', include(rest_router.urls)),
]
it works, and I can access my logged in user at /api/users/current/.
However, if I use my CustomUser and register the my whole UserViewSet (which descends from viewsets.ReadOnlyModelViewSet):
rest_router.register(r'users', viewsets.UserViewSet)
my current/ route stops working. Is there a way I can add such a custom detail route that doesn’t require a pk?
Edit: my second approach was to add a #list_route that actually returns only one object instead of one. This doesn’t require a custom router at all, just an extra method on my UserViewSet:
class UserViewSet(viewsets.ReadOnlyModelViewSet):
…
#list_route(methods=['get'])
def current(self, request):
return Response(self.serializer_class(request.user).data)
This, however, is pretty ugly for obvious reasons: why would I return one object in a list route?

Resources