Plain is headed towards 1.0! Subscribe for development updates →

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 functools
 2import uuid
 3
 4
 5class IntConverter:
 6    regex = "[0-9]+"
 7
 8    def to_python(self, value):
 9        return int(value)
10
11    def to_url(self, value):
12        return str(value)
13
14
15class StringConverter:
16    regex = "[^/]+"
17
18    def to_python(self, value):
19        return value
20
21    def to_url(self, value):
22        return value
23
24
25class UUIDConverter:
26    regex = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
27
28    def to_python(self, value):
29        return uuid.UUID(value)
30
31    def to_url(self, value):
32        return str(value)
33
34
35class SlugConverter(StringConverter):
36    regex = "[-a-zA-Z0-9_]+"
37
38
39class PathConverter(StringConverter):
40    regex = ".+"
41
42
43DEFAULT_CONVERTERS = {
44    "int": IntConverter(),
45    "path": PathConverter(),
46    "slug": SlugConverter(),
47    "str": StringConverter(),
48    "uuid": UUIDConverter(),
49}
50
51
52REGISTERED_CONVERTERS = {}
53
54
55def register_converter(converter, type_name):
56    REGISTERED_CONVERTERS[type_name] = converter()
57    get_converters.cache_clear()
58
59
60@functools.cache
61def get_converters():
62    return {**DEFAULT_CONVERTERS, **REGISTERED_CONVERTERS}
63
64
65def get_converter(raw_converter):
66    return get_converters()[raw_converter]