drf-nested-routers icon indicating copy to clipboard operation
drf-nested-routers copied to clipboard

Ability to have the default parameter name in urls.

Open trevorphillipscoding opened this issue 5 years ago • 2 comments

Just want to start off by saying, what a great library. I love it!

I was wondering if there is a way currently, or maybe implement here in the future, have the default parameter catchers in the URL. For example...

Here are some URLs that I generated using the django-extensions library.

/api/v1/users/ 
/api/v1/users/<pk>/
/api/v1/users/<pk>/deductions/
/api/v1/users/<user_pk>/businesses/
/api/v1/users/<user_pk>/businesses/<pk>/
/api/v1/users/<user_pk>/reports/
/api/v1/users/<user_pk>/reports/<pk>/
/api/v1/users/<user_pk>/vehicles/
/api/v1/users/<user_pk>/vehicles/<pk>/

and here is what my urls.py file looks like with the nested routing.

router = routers.SimpleRouter()
router.register("", UserViewSet)

user_router = routers.NestedSimpleRouter(router, "", lookup="user")
user_router.register("businesses", BusinessViewSet)
user_router.register("reports", ReportViewSet)
user_router.register("vehicles", VehicleViewSet)

urlpatterns = [
    path("/", include(router.urls)),
    path("", include(user_router.urls)),
]

Obviously I could change the lookup key to something else but I want it to be the same as the 'default' way that I think DRF/Django uses?

I also use the library drf_yasg to generate a schema API page and my URLs look like so. Screen Shot 2020-07-02 at 9 54 28 PM

Anyone have an idea how I could accomplish this?

Edit: I want to elaborate more since I don't think I explained super well. I want to be able to put the default pk in the URL which then would make it {id} in the API webpage that I generated with drf_yasg

trevorphillipscoding avatar Jul 03 '20 04:07 trevorphillipscoding

drf_yasg.generators.OpenAPISchemaGenerator must be overridden

this method is {pk} Force translation of path arguments into model field names. https://drf-yasg.readthedocs.io/en/stable/drf_yasg.html#drf_yasg.generators.OpenAPISchemaGenerator.coerce_path

from drf_yasg.generators import OpenAPISchemaGenerator

class CustomOpenAPISchemaGenerator(OpenAPISchemaGenerator):
    def get_endpoints(self, request):
        """Iterate over all the registered endpoints in the API and return a fake view with the right parameters.

        :param request: request to bind to the endpoint views
        :type request: rest_framework.request.Request or None
        :return: {path: (view_class, list[(http_method, view_instance)])
        :rtype: dict[str,(type,list[(str,rest_framework.views.APIView)])]
        """
        enumerator = self.endpoint_enumerator_class(self._gen.patterns, self._gen.urlconf, request=request)
        endpoints = enumerator.get_api_endpoints()

        view_paths = defaultdict(list)
        view_cls = {}
        for path, method, callback in endpoints:
            view = self.create_view(callback, method, request)
            # path = self.coerce_path(path, view)
            view_paths[path].append((method, view))
            view_cls[path] = callback.cls
        return {path: (view_cls[path], methods) for path, methods in view_paths.items()}

settings.py

SWAGGER_SETTINGS = {
    'DEFAULT_GENERATOR_CLASS': 'doc.schemas.CustomOpenAPISchemaGenerator'
}

But pk It is clearer to use the model field name instead.

gongul avatar Jan 27 '21 06:01 gongul

I ported this to drf-spectacular but still don't understand what to do with it...!? :-(

from collections import defaultdict
# from drf_yasg.generators import OpenAPISchemaGenerator
from drf_spectacular.generators import SchemaGenerator
from rest_framework import views
from .views import VideoViewSet, FrameViewSet, AnnotationViewSet #,...

class CustomSchemaGenerator(SchemaGenerator):
    def get_endpoints(self, request):
        """Iterate over all the registered endpoints in the API and return a fake view with the right parameters.
        :param request: request to bind to the endpoint views
        :type request: rest_framework.request.Request or None
        :return: {path: (view_class, list[(http_method, view_instance)])
        :rtype: dict[str,(type,list[(str,rest_framework.views.APIView)])]"""
        enumerator = self.endpoint_enumerator_class(self._gen.patterns, self._gen.urlconf, request=request)
        endpoints = enumerator.get_api_endpoints()
        view_paths = defaultdict(list)
        view_cls = { views.APIView } #?
        for path, method, callback in endpoints:
            logger.debug(f"*** path, method, callback = {path}, {method}, {callback}")
            view = self.create_view(callback, method, request)
            path = self.coerce_path(path, view) #?
            view_paths[path].append((method, view))
            view_cls[path] = callback.cls
        return {path: (view_cls[path], methods) for path, methods in view_paths.items()}

matthewsheeran avatar Apr 18 '23 05:04 matthewsheeran