"""
This module provides urls for Oxylus applications.
It scans over applications' directory for module ``urls`` from which it gets:
- ``urls``: the list of urls to regular views. They will be namespaced under the app label. The final url will look like: ``{root_url}/{path}``.
- ``api_urls``: the list of urls to API views. They will be namespaced as ``{app.label}-api``. The final url will look like: ``api/{root_url}/{path}``.
Where ``root_url`` is specified on the AppConfig instance (if none, uses app label). Note that only subclasses of :py:class:`ox.core.apps.AppConfig` are taken in account).
Thoses urls will be automatically added to url patterns.
"""
from collections import namedtuple
from functools import cached_property
from typing import ClassVar
from django import apps
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
from ox.core.views import core, accounts
from ox.utils.apps import DiscoverModules
AppUrls = namedtuple("AppUrls", ["urls", "api"])
[docs]
class Router:
"""This class is used to retrieve urls in app's ``urls`` module, under keys
``urls`` and ``api_urls``.
It will generate three kind of url:
- ``
- ``/{url}``: for ``urls``'s absolute url (starting with a ``/``);
- ``/{app_root_url}/{url}``: for ``urls``'s relative url;
- ``/api/{app_root_url}/``: for url of api views and viewsets (declared using ``api_urls``);
"""
[docs]
class Discover(DiscoverModules):
module_names = "urls"
def handle_urls(self, app, module, app_urls, **kw):
urls = [
getattr(module, "urls", None),
getattr(module, "api_urls", None),
]
if any(urls):
app_urls[app] = AppUrls(*urls)
discover: ClassVar[Discover] = Discover()
[docs]
@cached_property
def apps_urls(self) -> dict[apps.AppConfig, tuple]:
"""Return urls as a dict of `{app: (urls, api_urls)}`."""
app_urls = {}
self.discover.run_handler("urls", app_urls=app_urls)
return app_urls
[docs]
def get_urls(self) -> list:
"""Generate and return url for all applications."""
patterns = []
for app, urls in self.apps_urls.items():
patterns.extend(self.get_app_urls(app, urls))
return patterns
[docs]
def get_app_urls(self, app, urls: AppUrls) -> list:
"""Return urls for a specific app."""
root_url, patterns = app.root_url, []
if urls.urls:
patterns.append(path(f"{root_url}/", include((urls.urls, app.label))))
if urls.api:
patterns.append(path(f"api/{root_url}/", include((urls.api, app.label), namespace=f"{app.label}-api")))
return patterns
router = Router()
urlpatterns = [
path("", accounts.DashboardView.as_view(), name="index"),
path("api/", (lambda *a, **kw: {}), name="api-index"),
*router.get_urls(),
path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
path("api/swagger/", SpectacularSwaggerView.as_view(url_name="schema")),
path("admin/", admin.site.urls),
path("accounts/login/", accounts.LoginView.as_view(), name="login"),
path("accounts/logout/", accounts.LogoutView.as_view(), name="logout"),
]
if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + static(
settings.MEDIA_URL, document_root=settings.MEDIA_ROOT
)
handler403 = core.PermissionForbiddenView.as_view()
handler405 = core.InternalErrorView.as_view()