Routing based on url pathname

hi,

i’m trying to use pn.state.location to effectively create a router - by observing the url pathname however I get 404 errors. If i click on the links on the page that works however when I reload the url with /section1/page1 I get a 404 error in the browser as if something intercepts the request before it gets to the app.py file below.

import panel as pn

class MainApp(pn.viewable.Viewer):

    APPS = {
        "section1": {
            "page1": pn.pane.Markdown("sec1, page1"),
        },
        "section2": {
            "page1": pn.pane.Markdown("sec1, page1"),
        }
    }

    flat_APPS = {
        (section, page): v
        for section, v_section in APPS.items()
        for page, v in v_section.items()
    }

    @pn.depends(pn.state.location.param.pathname)
    def _load_page_layout(self, pathname):
        key = pathname.split("/")
        if len(key) == 3:
            _, section, page = key
            return self.flat_APPS.get((section, page))
        else:
            return "Invalid URL"

    def get_sidebar(self):
        sidebar = []
        for section, section_apps in self.APPS.items():
            sidebar.append(pn.pane.Markdown("### " + section.upper()))
            for page, page_app in section_apps.items():

                def set_pathname(event, section=section, page=page):
                    pn.state.location.pathname = f"/{section}/{page}"

                button = pn.widgets.Button(
                    name=f"• {page}", button_style='solid', button_type="light")
                button.on_click(set_pathname)
                sidebar.append(button)
        return sidebar

    def __panel__(self):
        template = pn.template.FastListTemplate(
            site="My Multi-Page App",
            title="Dashboard",
            header_background="#0072B5",
            main=self._load_page_layout,
            sidebar=self.get_sidebar(),
        )
        return template


main_app = MainApp()
main_app.servable(location=pn.state.location)

I found the below as a decent solution that I think could be improved on.

  • serve the apps so that allows the slugs to be caught by whatever is happening at the server routing level (ie pn.serve)
    • this creates an issue where to launch the app you have to launch the app as python app.py as opposed to panel serve app.py. so you can’t do things like --dev
  • initialise a location variable (pn.io.location.Location()) and pass it to the pn.serve
  • use that in the pn.depends statement

Side effects that aren’t ideal

  • you end up with a “main” page that is the default panel multi page landing page
  • i haven’t figured out how to launch the app with panel serve app.py
import panel as pn
import panel.io.location


location = pn.io.location.Location(reload=True)


class MainApp(pn.viewable.Viewer):

    APPS = {
        "section1": {
            "page1": pn.pane.Markdown("sec1, page1"),
        },
        "section2": {
            "page1": pn.pane.Markdown("sec2, page1"),
        }
    }

    flat_APPS = {
        (section, page): v
        for section, v_section in APPS.items()
        for page, v in v_section.items()
    }

    @pn.depends(location.param.pathname)
    def _load_page_layout(self, pathname):
        key = pathname.strip("/").split("/")
        if len(key) == 2:
            section, page = key
            return self.flat_APPS.get((section, page))
        else:
            return "Invalid URL"

    def get_sidebar(self):
        sidebar = []
        for section, section_apps in self.APPS.items():
            sidebar.append(pn.pane.Markdown("### " + section.upper()))
            for page, page_app in section_apps.items():

                def set_pathname(event, section=section, page=page):
                    pn.state.location.pathname = f"/{section}/{page}"

                button = pn.widgets.Button(
                    name=f"• {page}", button_style='solid', button_type="light")
                button.on_click(set_pathname)
                sidebar.append(button)
        return sidebar

    def __panel__(self):
        template = pn.template.FastListTemplate(
            site="My Multi-Page App",
            title="Dashboard",
            header_background="#0072B5",
            main=self._load_page_layout,
            sidebar=self.get_sidebar(),
        )
        return template


main_app = MainApp()
apps = {
        "/".join(slug): main_app
        for slug in main_app.flat_APPS
}
pn.serve(
    apps,
    port=5006,
    location=location,
)
1 Like