diff --git a/.env.sample b/.env.sample index 137dd572..22149446 100644 --- a/.env.sample +++ b/.env.sample @@ -14,8 +14,10 @@ MEDIA_ROOT=./files MEDIA_URL=https://example.com/geoshop_media/ ALLOWED_HOST=localhost,127.0.0.1,proxy.example.com -# Base URL of the api +# Base URL of the api: +# 1. forces the application to prepend a script name to all URLs generated by the reverse function and the {% url %} template tag FORCE_SCRIPT_NAME= +# 2. add a prefix to all URL paths in the API ROOTURL=api DEFAULT_FROM_EMAIL=no-reply@geoshop.ch diff --git a/urls.py b/urls.py index b04fc56d..7f40515a 100644 --- a/urls.py +++ b/urls.py @@ -19,10 +19,14 @@ from api import views import oidc -# Use ROOTURL from settings +# Use ROOTURL from settings to prefix all urls ROOTURL = getattr(settings, 'ROOTURL', '') + +# Ensure ROOTURL is properly formatted if ROOTURL.startswith('/'): ROOTURL = ROOTURL[1:] +if not ROOTURL.endswith('/'): + ROOTURL += '/' admin.site.site_header = _("GeoShop Administration") @@ -41,52 +45,60 @@ router.register(r'product', views.ProductViewSet, basename='product') router.register(r'productformat', views.ProductFormatViewSet) router.register(r'pricing', views.PricingViewSet) -router.register_additional_route_to_root(f'{ROOTURL}/extract/order/', 'extract_order') -router.register_additional_route_to_root(f'{ROOTURL}/extract/order/fake', 'extract_order_fake') -router.register_additional_route_to_root(f'{ROOTURL}/extract/orderitem/', 'extract_orderitem') -router.register_additional_route_to_root(f'{ROOTURL}/token', 'token_obtain_pair') -router.register_additional_route_to_root(f'{ROOTURL}/token/refresh', 'token_refresh') -router.register_additional_route_to_root(f'{ROOTURL}/token/verify', 'token_verify') -router.register_additional_route_to_root(f'{ROOTURL}/auth/change', 'auth_change_user') -router.register_additional_route_to_root(f'{ROOTURL}/auth/current', 'auth_current_user') -router.register_additional_route_to_root(f'{ROOTURL}/auth/password', 'auth_password') -router.register_additional_route_to_root(f'{ROOTURL}/auth/password/confirm', 'auth_password_confirm') -router.register_additional_route_to_root(f'{ROOTURL}/auth/register', 'auth_register') +router.register_additional_route_to_root(f'{ROOTURL}extract/order/', 'extract_order') +router.register_additional_route_to_root(f'{ROOTURL}extract/order/fake', 'extract_order_fake') +router.register_additional_route_to_root(f'{ROOTURL}extract/orderitem/', 'extract_orderitem') +router.register_additional_route_to_root(f'{ROOTURL}token', 'token_obtain_pair') +router.register_additional_route_to_root(f'{ROOTURL}token/refresh', 'token_refresh') +router.register_additional_route_to_root(f'{ROOTURL}token/verify', 'token_verify') +router.register_additional_route_to_root(f'{ROOTURL}auth/change', 'auth_change_user') +router.register_additional_route_to_root(f'{ROOTURL}auth/current', 'auth_current_user') +router.register_additional_route_to_root(f'{ROOTURL}auth/password', 'auth_password') +router.register_additional_route_to_root(f'{ROOTURL}auth/password/confirm', 'auth_password_confirm') +router.register_additional_route_to_root(f'{ROOTURL}auth/register', 'auth_register') # Wire up our API using automatic URL routing. # Additionally, we include login URLs for the browsable API. urlpatterns = [ # this url is used to generate email content - path(f'{ROOTURL}/favicon.ico', RedirectView.as_view(url=f'{settings.STATIC_URL}{ROOTURL}/favicon.ico')), - re_path(rf'^{ROOTURL}/auth/reset/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', TemplateView.as_view(), name='password_reset_confirm'), - path(f'{ROOTURL}/auth/change/', views.UserChangeView.as_view(), name='auth_change_user'), - path(f'{ROOTURL}/auth/current/', views.CurrentUserView.as_view(), name='auth_current_user'), - re_path(rf'^{ROOTURL}/download/(?P\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b)$', views.OrderByUUIDView.as_view(), name='order_uuid'), - re_path(rf'^{ROOTURL}/download/(?P\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b)/get_link$', views.DownloadLinkView.as_view(), name='order_uuid_link'), - path(f'{ROOTURL}/auth/password/', views.PasswordResetView.as_view(), name='auth_password'), - path(f'{ROOTURL}/auth/password/confirm', views.PasswordResetConfirmView.as_view(), name='auth_password_confirm'), - path(f'{ROOTURL}/auth/verify-email/', views.VerifyEmailView.as_view(), name='auth_verify_email'), - re_path(rf'^{ROOTURL}/auth/account-confirm-email/(?P[-:\w]+)/$', TemplateView.as_view(), name='account_confirm_email'), - path(f'{ROOTURL}/auth/register/', views.RegisterView.as_view(), name='auth_register'), - path(f'{ROOTURL}/extract/order/', views.ExtractOrderView.as_view(), name='extract_order'), - path(f'{ROOTURL}/extract/orderitem/', views.ExtractOrderItemView.as_view(), name='extract_orderitem'), - re_path(rf'^{ROOTURL}/extract/orderitem/(?P[0-9]+)$', views.ExtractOrderItemView.as_view(), name='extract_orderitem'), - path(f'{ROOTURL}/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), - path(f'{ROOTURL}/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), - path(f'{ROOTURL}/token/verify/', TokenVerifyView.as_view(), name='token_verify'), - path(f'{ROOTURL}/session-auth/', include('rest_framework.urls', namespace='rest_framework')), - re_path(rf'^{ROOTURL}/validate/orderitem/(?P[a-zA-Z0-9_-]+)$', views.OrderItemByTokenView.as_view(), name='orderitem_validate'), - path(f'{ROOTURL}/admin/', admin.site.urls, name='admin'), - path(f'{ROOTURL}/', include(router.urls)), - path(f'{ROOTURL}/docs/schema', SpectacularAPIView.as_view(), name='schema'), - path(f'{ROOTURL}/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), - path(f'{ROOTURL}/health/', include('health_check.urls')), + #TODO FIXME: the favicon is not served correctly + path(f'{ROOTURL}favicon.ico', RedirectView.as_view(url=f'{ROOTURL}{settings.STATIC_URL}favicon.ico')), + re_path(rf'^{ROOTURL}auth/reset/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', + TemplateView.as_view(), + name='password_reset_confirm'), + path(f'{ROOTURL}auth/change/', views.UserChangeView.as_view(), name='auth_change_user'), + path(f'{ROOTURL}auth/current/', views.CurrentUserView.as_view(), name='auth_current_user'), + re_path(rf'^{ROOTURL}download/(?P\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b)$', + views.OrderByUUIDView.as_view(), name='order_uuid'), + re_path(rf'^{ROOTURL}download/(?P\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b)/get_link$', + views.DownloadLinkView.as_view(), name='order_uuid_link'), + path(f'{ROOTURL}auth/password/', views.PasswordResetView.as_view(),name='auth_password'), + path(f'{ROOTURL}auth/password/confirm', views.PasswordResetConfirmView.as_view(), name='auth_password_confirm'), + path(f'{ROOTURL}auth/verify-email/', views.VerifyEmailView.as_view(), name='auth_verify_email'), + re_path(rf'^{ROOTURL}auth/account-confirm-email/(?P[-:\w]+)/$', TemplateView.as_view(), + name='account_confirm_email'), + path(f'{ROOTURL}auth/register/', views.RegisterView.as_view(), name='auth_register'), + path(f'{ROOTURL}extract/order/', views.ExtractOrderView.as_view(), name='extract_order'), + path(f'{ROOTURL}extract/orderitem/', views.ExtractOrderItemView.as_view(), name='extract_orderitem'), + re_path(rf'^{ROOTURL}extract/orderitem/(?P[0-9]+)$', + views.ExtractOrderItemView.as_view(), name='extract_orderitem'), + path(f'{ROOTURL}token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), + path(f'{ROOTURL}token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), + path(f'{ROOTURL}token/verify/', TokenVerifyView.as_view(), name='token_verify'), + path(f'{ROOTURL}session-auth/', include('rest_framework.urls', namespace='rest_framework')), + re_path(rf'^{ROOTURL}validate/orderitem/(?P[a-zA-Z0-9_-]+)$', + views.OrderItemByTokenView.as_view(), name='orderitem_validate'), + path(f'{ROOTURL}admin/', admin.site.urls, name='admin'), + path(f'{ROOTURL}', include(router.urls)), + path(f'{ROOTURL}docs/schema', SpectacularAPIView.as_view(), name='schema'), + path(f'{ROOTURL}docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), + path(f'{ROOTURL}health/', include('health_check.urls')), ] + static(settings.STATIC_URL,document_root=settings.STATIC_ROOT) + static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT) # OIDC urls if settings.OIDC_ENABLED: urlpatterns += [ - path(f'{ROOTURL}/oidc/callback', OIDCCallbackClass.as_view(), name='oidc_authentication_callback'), - path(f'{ROOTURL}/oidc/authenticate/', OIDCAuthenticateClass.as_view(), name='oidc_authentication_init'), - path(f'{ROOTURL}/oidc/logout', OIDCLogoutView.as_view(), name='oidc_logout'), + path(f'{ROOTURL}oidc/callback', OIDCCallbackClass.as_view(), name='oidc_authentication_callback'), + path(f'{ROOTURL}oidc/authenticate/', OIDCAuthenticateClass.as_view(), name='oidc_authentication_init'), + path(f'{ROOTURL}oidc/logout', OIDCLogoutView.as_view(), name='oidc_logout'), ]