2727
2828from django .db .models import Model , QuerySet
2929from django .http import HttpResponse
30- from django .urls import URLPattern
30+ from django .urls import URLPattern , URLResolver , include
3131from django .urls import path as django_path
3232from injector import inject , is_decorated_with_inject
3333from ninja import NinjaAPI , Router
@@ -403,8 +403,11 @@ def __init__(
403403 tags : Union [Optional [List [str ]], str ] = None ,
404404 permissions : Optional [List [BasePermissionType ]] = None ,
405405 auto_import : bool = True ,
406+ urls_namespace : Optional [str ] = None ,
406407 ) -> None :
407408 self .prefix = prefix
409+ # Optional controller-level URL namespace. Applied to all route paths.
410+ self .urls_namespace = urls_namespace or None
408411 # `auth` primarily defines APIController route function global authentication method.
409412 self .auth : Optional [AuthBase ] = auth
410413
@@ -553,16 +556,34 @@ def add_controller_route_function(self, route_function: RouteFunction) -> None:
553556 get_function_name (route_function .route .view_func )
554557 ] = route_function
555558
556- def urls_paths (self , prefix : str ) -> Iterator [URLPattern ]:
559+ def urls_paths (self , prefix : str ) -> Iterator [Union [URLPattern , URLResolver ]]:
560+ namespaced_patterns : List [URLPattern ] = []
561+
557562 for path , path_view in self .path_operations .items ():
558563 path = path .replace ("{" , "<" ).replace ("}" , ">" )
559564 route = "/" .join ([i for i in (prefix , path ) if i ])
560565 # to skip lot of checks we simply treat double slash as a mistake:
561566 route = normalize_path (route )
562567 route = route .lstrip ("/" )
568+
563569 for op in path_view .operations :
564570 op = cast (Operation , op )
565- yield django_path (route , path_view .get_view (), name = op .url_name )
571+ view = path_view .get_view ()
572+ if op .url_name :
573+ pattern = django_path (route , view , name = op .url_name )
574+ else :
575+ pattern = django_path (route , view )
576+
577+ if self .urls_namespace :
578+ namespaced_patterns .append (pattern )
579+ else :
580+ yield pattern
581+
582+ if self .urls_namespace and namespaced_patterns :
583+ yield django_path (
584+ "" ,
585+ include ((namespaced_patterns , self .urls_namespace ), namespace = self .urls_namespace ),
586+ )
566587
567588 def __repr__ (self ) -> str : # pragma: no cover
568589 return f"<controller - { self .controller_class .__name__ } >"
@@ -590,6 +611,8 @@ def _add_operation_from_route_function(self, route_function: RouteFunction) -> N
590611 f"endpoint={ get_function_name (route_function .route .view_func )} "
591612 )
592613 data = route_function .route .route_params .dict ()
614+ if not data .get ("url_name" ):
615+ data ["url_name" ] = get_function_name (route_function .route .view_func )
593616 route_function .operation = self .add_api_operation (
594617 view_func = route_function .as_view , ** data
595618 )
@@ -663,19 +686,21 @@ def api_controller(
663686 tags : Union [Optional [List [str ]], str ] = None ,
664687 permissions : Optional [List [BasePermissionType ]] = None ,
665688 auto_import : bool = True ,
689+ urls_namespace : Optional [str ] = None ,
666690) -> Callable [
667691 [Union [Type , Type [T ]]], Union [Type [ControllerBase ], Type [T ]]
668692]: # pragma: no cover
669693 ...
670694
671695
672696def api_controller (
673- prefix_or_class : Union [str , Union [ ControllerClassType , Type ] ] = "" ,
697+ prefix_or_class : Union [str , ControllerClassType ] = "" ,
674698 auth : Any = NOT_SET ,
675699 throttle : Union [BaseThrottle , List [BaseThrottle ], NOT_SET_TYPE ] = NOT_SET ,
676700 tags : Union [Optional [List [str ]], str ] = None ,
677701 permissions : Optional [List [BasePermissionType ]] = None ,
678702 auto_import : bool = True ,
703+ urls_namespace : Optional [str ] = None ,
679704) -> Union [ControllerClassType , Callable [[ControllerClassType ], ControllerClassType ]]:
680705 if isinstance (prefix_or_class , type ):
681706 return APIController (
@@ -685,6 +710,7 @@ def api_controller(
685710 permissions = permissions ,
686711 auto_import = auto_import ,
687712 throttle = throttle ,
713+ urls_namespace = urls_namespace ,
688714 )(prefix_or_class )
689715
690716 def _decorator (cls : ControllerClassType ) -> ControllerClassType :
@@ -695,6 +721,7 @@ def _decorator(cls: ControllerClassType) -> ControllerClassType:
695721 permissions = permissions ,
696722 auto_import = auto_import ,
697723 throttle = throttle ,
724+ urls_namespace = urls_namespace ,
698725 )(cls )
699726
700727 return _decorator
0 commit comments