Coverage for src/backend/InvenTree/InvenTree/urls.py: 85%

51 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 17:47 +0000

1"""Top-level URL lookup for InvenTree application. 

2 

3Passes URL lookup downstream to each app as required. 

4""" 

5 

6from django.conf import settings 

7from django.conf.urls.static import static 

8from django.contrib import admin 

9from django.urls import include, path, re_path 

10from django.views.decorators.csrf import csrf_exempt 

11from django.views.generic.base import RedirectView 

12 

13from allauth.headless.urls import Client, build_urlpatterns 

14from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView 

15from flags.urls import flagged_path 

16from oauth2_provider import urls as oauth2_urls 

17from sesame.views import LoginView 

18 

19import build.api 

20import common.api 

21import company.api 

22import importer.api 

23import InvenTree.logging # noqa: F401 - ensure logging handlers are registered 

24import machine.api 

25import order.api 

26import part.api 

27import plugin.api 

28import report.api 

29import stock.api 

30import users.api 

31from plugin.urls import get_plugin_urls 

32from web.urls import cui_compatibility_urls 

33from web.urls import urlpatterns as platform_urls 

34 

35from .api import ( 

36 APISearchView, 

37 InfoView, 

38 LicenseView, 

39 NotFoundView, 

40 VersionTextView, 

41 VersionView, 

42) 

43from .config import get_setting 

44from .magic_login import GetSimpleLoginView 

45from .views import auth_request 

46 

47# Set admin header from config or use default 

48admin.site.site_header = get_setting( 

49 'INVENTREE_SITE_HEADER', 'customize.site_header', 'InvenTree Admin' 

50) 

51 

52 

53apipatterns = [ 

54 # Global search 

55 path('admin/', include(common.api.admin_api_urls)), 

56 path('bom/', include(part.api.bom_api_urls)), 

57 path('build/', include(build.api.build_api_urls)), 

58 path('company/', include(company.api.company_api_urls)), 

59 path('importer/', include(importer.api.importer_api_urls)), 

60 path('label/', include(report.api.label_api_urls)), 

61 path('machine/', include(machine.api.machine_api_urls)), 

62 path('order/', include(order.api.order_api_urls)), 

63 path('part/', include(part.api.part_api_urls)), 

64 path('report/', include(report.api.report_api_urls)), 

65 path('search/', APISearchView.as_view(), name='api-search'), 

66 path('settings/', include(common.api.settings_api_urls)), 

67 path('stock/', include(stock.api.stock_api_urls)), 

68 path( 

69 'generate/', 

70 include([ 

71 path( 

72 'batch-code/', 

73 stock.api.GenerateBatchCode.as_view(), 

74 name='api-generate-batch-code', 

75 ), 

76 path( 

77 'serial-number/', 

78 stock.api.GenerateSerialNumber.as_view(), 

79 name='api-generate-serial-number', 

80 ), 

81 ]), 

82 ), 

83 path('user/', include(users.api.user_urls)), 

84 # Plugin endpoints 

85 path('', include(plugin.api.plugin_api_urls)), 

86 # Common endpoints endpoint 

87 path('', include(common.api.common_api_urls)), 

88 # OpenAPI Schema 

89 path( 

90 'schema/', 

91 SpectacularAPIView.as_view(custom_settings={'SCHEMA_PATH_PREFIX': '/api/'}), 

92 name='schema', 

93 ), 

94 # InvenTree information endpoints 

95 path('license/', LicenseView.as_view(), name='api-license'), # license info 

96 path( 

97 'version-text', VersionTextView.as_view(), name='api-version-text' 

98 ), # version text 

99 path('version/', VersionView.as_view(), name='api-version'), # version info 

100 path('', InfoView.as_view(), name='api-inventree-info'), # server info 

101 # Auth API endpoints 

102 path( 

103 'auth/', 

104 include([ 

105 path( 

106 'login-redirect/', 

107 users.api.LoginRedirect.as_view(), 

108 name='api-login-redirect', 

109 ), 

110 path( 

111 '', 

112 include( 

113 (build_urlpatterns(Client.BROWSER), 'headless'), namespace='browser' 

114 ), 

115 ), # Allauth headless logic (only the browser client is included as we only use sessions based auth there) 

116 ]), 

117 ), 

118 # Magic login URLs 

119 path( 

120 'email/generate/', 

121 csrf_exempt(GetSimpleLoginView().as_view()), 

122 name='sesame-generate', 

123 ), 

124 path('email/login/', LoginView.as_view(), name='sesame-login'), 

125 # Unknown endpoint 

126 re_path(r'^.*$', NotFoundView.as_view(), name='api-404'), 

127] 

128 

129 

130backendpatterns = [ 

131 path( 

132 'auth/', include('rest_framework.urls', namespace='rest_framework') 

133 ), # Used for (DRF) browsable API auth 

134 path( 

135 'auth/', auth_request, name='auth-check' 

136 ), # Used for proxies to check if user is authenticated 

137 path('accounts/', include('allauth.urls')), 

138 # OAuth2 

139 flagged_path('OIDC', 'o/', include(oauth2_urls)), 

140 path( 

141 'accounts/login/', 

142 RedirectView.as_view(url=f'/{settings.FRONTEND_URL_BASE}', permanent=False), 

143 name='account_login', 

144 ), # Add a redirect for login views 

145 path('anymail/', include('anymail.urls')), # Emails 

146] 

147 

148urlpatterns = [] 

149 

150if settings.INVENTREE_ADMIN_ENABLED: 150 ↛ 159line 150 didn't jump to line 159 because the condition on line 150 was always true

151 admin_url = settings.INVENTREE_ADMIN_URL 

152 

153 urlpatterns += [ 

154 path(f'{admin_url}/error_log/', include('error_report.urls')), 

155 path(f'{admin_url}/doc/', include('django.contrib.admindocs.urls')), 

156 path(f'{admin_url}/', admin.site.urls, name='inventree-admin'), 

157 ] 

158 

159urlpatterns += backendpatterns 

160urlpatterns += [ # API URLs 

161 path('api/', include(apipatterns)), 

162 path('api-doc/', SpectacularRedocView.as_view(url_name='schema'), name='api-doc'), 

163] 

164urlpatterns += platform_urls 

165 

166# Append custom plugin URLs (if custom plugin support is enabled) 

167if settings.PLUGINS_ENABLED: 167 ↛ 168line 167 didn't jump to line 168 because the condition on line 167 was never true

168 urlpatterns.append(get_plugin_urls()) 

169 

170# Server running in "DEBUG" mode? 

171if settings.DEBUG: 171 ↛ 173line 171 didn't jump to line 173 because the condition on line 171 was never true

172 # Static file access 

173 urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 

174 

175 # Media file access 

176 urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 

177 

178# Redirect for favicon.ico 

179urlpatterns.append( 

180 path( 

181 'favicon.ico', 

182 RedirectView.as_view(url=f'{settings.STATIC_URL}img/favicon/favicon.ico'), 

183 ) 

184) 

185 

186# Compatibility layer for old (CUI) URLs 

187if settings.FRONTEND_SETTINGS.get('url_compatibility'): 187 ↛ 190line 187 didn't jump to line 190 because the condition on line 187 was always true

188 urlpatterns += cui_compatibility_urls(settings.FRONTEND_URL_BASE) 

189 

190if settings.DJANGO_SILK_ENABLED: 190 ↛ 191line 190 didn't jump to line 191 because the condition on line 190 was never true

191 urlpatterns += [path('silk/', include('silk.urls', namespace='silk'))] 

192 

193# Send any unknown URLs to the index page 

194urlpatterns += [ 

195 re_path( 

196 r'^.*$', 

197 RedirectView.as_view(url=f'/{settings.FRONTEND_URL_BASE}', permanent=False), 

198 name='index', 

199 ) 

200]