URLs
Route requests to views.
URLs are typically the "entrypoint" to your app. Virtually all request handling up to this point happens behind the scenes, and then you decide how to route specific URL patterns to your views.
The URLS_ROUTER
is the primary router that handles all incoming requests. It is defined in your app/settings.py
file. This will typically point to a Router
class in your app.urls
module.
# app/settings.py
URLS_ROUTER = "app.urls.AppRouter"
The root router often has an empty namespace (""
) and some combination of individual paths and sub-routers.
# app/urls.py
from plain.urls import Router, path, include
from plain.admin.urls import AdminRouter
from . import views
class AppRouter(Router):
namespace = ""
urls = [
include("admin/", AdminRouter),
path("about/", views.AboutView, name="about"), # A named URL
path("", views.HomeView), # An unnamed URL
]
Reversing URLs
In templates, you will use the {{ url("<url name>") }}
function to look up full URLs by name.
<a href="{{ url('about') }}">About</a>
And the same can be done in Python code with the reverse
(or reverse_lazy
) function.
from plain.urls import reverse
url = reverse("about")
A URL path has to include a name
attribute if you want to reverse it. The router's namespace
will be used as a prefix to the URL name.
from plain.urls import reverse
url = reverse("admin:dashboard")
URL args and kwargs
URL patterns can include arguments and keyword arguments.
# app/urls.py
from plain.urls import Router, path
from . import views
class AppRouter(Router):
namespace = ""
urls = [
path("user/<int:user_id>/", views.UserView, name="user"),
path("search/<str:query>/", views.SearchView, name="search"),
]
These will be accessible inside the view as self.url_args
and self.url_kwargs
.
# app/views.py
from plain.views import View
class SearchView(View):
def get(self):
query = self.url_kwargs["query"]
print(f"Searching for {query}")
# ...
To reverse a URL with args or kwargs, simply pass them in the reverse
function.
from plain.urls import reverse
url = reverse("search", query="example")
There are a handful of built-in converters that can be used in URL patterns.
from plain.urls import Router, path
from . import views
class AppRouter(Router):
namespace = ""
urls = [
path("user/<int:user_id>/", views.UserView, name="user"),
path("search/<str:query>/", views.SearchView, name="search"),
path("post/<slug:post_slug>/", views.PostView, name="post"),
path("document/<uuid:uuid>/", views.DocumentView, name="document"),
path("path/<path:subpath>/", views.PathView, name="path"),
]
Package routers
Installed packages will often provide a URL router to include in your root URL router.
# plain/assets/urls.py
from plain.urls import Router, path
from .views import AssetView
class AssetsRouter(Router):
"""
The router for serving static assets.
Include this router in your app router if you are serving assets yourself.
"""
namespace = "assets"
urls = [
path("<path:path>", AssetView, name="asset"),
]
Import the package's router and include
it at any path you choose.
from plain.urls import include, Router
from plain.assets.urls import AssetsRouter
class AppRouter(Router):
namespace = ""
urls = [
include("assets/", AssetsRouter),
# Your other URLs here...
]
1import re
2from typing import TYPE_CHECKING
3
4from .patterns import RegexPattern, RoutePattern, URLPattern
5from .resolvers import (
6 URLResolver,
7)
8
9if TYPE_CHECKING:
10 from plain.views import View
11
12
13class Router:
14 """
15 Base class for defining url patterns.
16
17 A namespace is required, and generally recommended,
18 except for the root router in app.urls where it is typically "".
19 """
20
21 namespace: str
22 urls: list
23
24
25def include(
26 route: str | re.Pattern, router_or_urls: list | tuple | str | Router
27) -> URLResolver:
28 """
29 Include URLs from another module or a nested list of URL patterns.
30 """
31 if isinstance(route, str):
32 pattern = RoutePattern(route, is_endpoint=False)
33 elif isinstance(route, re.Pattern):
34 pattern = RegexPattern(route.pattern, is_endpoint=False)
35 else:
36 raise TypeError("include() route must be a string or regex")
37
38 if isinstance(router_or_urls, list | tuple):
39 # We were given an explicit list of sub-patterns,
40 # so we generate a router for it
41 class _IncludeRouter(Router):
42 namespace = ""
43 urls = router_or_urls
44
45 return URLResolver(pattern=pattern, router=_IncludeRouter())
46 elif isinstance(router_or_urls, type) and issubclass(router_or_urls, Router):
47 router_class = router_or_urls
48 router = router_class()
49
50 return URLResolver(
51 pattern=pattern,
52 router=router,
53 )
54 else:
55 raise TypeError(
56 f"include() urls must be a list, tuple, or Router class (not a Router() instance): {router_or_urls}"
57 )
58
59
60def path(route: str | re.Pattern, view: "View", *, name: str = "") -> URLPattern:
61 """
62 Standard URL with a view.
63 """
64 from plain.views import View
65
66 if isinstance(route, str):
67 pattern = RoutePattern(route, name=name, is_endpoint=True)
68 elif isinstance(route, re.Pattern):
69 pattern = RegexPattern(route.pattern, name=name, is_endpoint=True)
70 else:
71 raise TypeError("path() route must be a string or regex")
72
73 # You can't pass a View() instance to path()
74 if isinstance(view, View):
75 view_cls_name = view.__class__.__name__
76 raise TypeError(
77 f"view must be a callable, pass {view_cls_name} or {view_cls_name}.as_view(*args, **kwargs), not "
78 f"{view_cls_name}()."
79 )
80
81 # You typically pass a View class and we call as_view() for you
82 if isinstance(view, type) and issubclass(view, View):
83 return URLPattern(pattern=pattern, view=view.as_view(), name=name)
84
85 # If you called View.as_view() yourself (or technically any callable)
86 if callable(view):
87 return URLPattern(pattern=pattern, view=view, name=name)
88
89 raise TypeError("view must be a View class or View.as_view()")