@mawe - Super, glad it worked for you
This will be a good example to include in the dash-labs docs too.
Hi Ann, after reading all the posts, I am wondering if you can help me undrstand the difference in creating multiple dash apps and putting them through flask, versus the multi-page approach above. I see a lot of people are pushing their apps through flasj, what benefits and advantages are theit to this approach. This would be very helpfull, because it would require a lot of fundamental changes to go from one to the other. Best Derek.
Hi @snowde
I havenāt made a multi-page app using flask and multiple apps so I canāt speak to the pros and cons. (If someone else has, please chime in).
However, this is from the Dash 2.0 announcement:
@dash.callback
will not work if your project has multipleapp
declarations. Some members of the community have used this pattern to create multi-page apps instead of the officialdcc.Location
multi-page app solution.. The multi-app pattern was never officially documented or supported by our team.
We built and officially support the dcc.Location method of multiple pages vs multiple flask instances for a couple of reasons:
- āSingle page app (SPA)ā links with dcc.Link: This allows page navigation without reloading the browser page (and therefore reloading and re-evaluating the JS scripts and CSS), making page navigation quite a bit faster
- Ability to share common components in the āframeā of the page rather than redefining within each page like headers and sidebars
- Ability to share data like dcc.Store
- More easily use query parameters in dash callbacks
- āItās just Dashā - dcc.Location and dcc.Link provide a multi page app experience using the same simple foundational principles of dash: Rich components tied together with callback functions
Now with /pages, weāre adding even more functionality out of the box (see original post) that you would otherwise need to program from scratch using the flask method.
Thanks, Chris that makes this method really appealing, but I must ask if you could also comment on the possible benefits of using the Flask method, for example, I have read that it is useful for user authentication. Happy holidays.
Latest Dash Labs Docs and Examples:
Based on @vuthanhdatt 's question in this post, see the new example of how to pass parameters to pages using query strings.
Based on the question from @bigmike and @vnnw in this post, there is now an example of how to use @app.long_callback
with pages/
.
The new chapter on layout functions is based on @maweās question on how to make a nav menu for certain pages. Special thanks to @pandamodium for pointing out that itās necessary to make the layout
a function if you are using dash.page_registry
from within the `pages/ folder.
Note - This documentation is a work in progress. Please feel free to make suggestions for improvements by commenting here or making a pull request in dash-labs.
Multi-Page App Docs
New in dash-labs>=1.0.0:
-
08-MultiPageDashApp.md
- Overview, quickstart apps, reference for the
pages
plug-in
- Overview, quickstart apps, reference for the
-
09-MultiPageDashApp-NestedFolders.md
- Example of organizing app pages in nested folders within the
pages/
folder
- Example of organizing app pages in nested folders within the
-
10-MultiPageDashApp-MetaTags.md
- Details on how the title, image, and description are used to create the meta tags which determine how your app looks when shared on social media.
-
11-MultiPageDashApp-LayoutFunctions.md
- Examples of when to make the
layout
a function - like when creating a custom menu by page, or using query strings.
- Examples of when to make the
Multi-Page App Demos
Examples and demos are located in the docs/demos directory.
-
multi_page_basics
- Minimal examples of all the features and basic quickstart apps. (see chapter 8 for details.)
-
multi_page_example1
- A quickstart app using
dash-bootstrap-components
and some simple callbacks.
- A quickstart app using
-
multi_page_layout_functions
- An app that creates a sidebar menu for certain pages. (See chapter 11 for details.)
-
multi_page_long_callback
- An example of how to use
@app.long_callback()
withpages/
- An example of how to use
-
multi_page_meta_tags
- The example app used to show how the meta tags are generated. (See chapter 10 for details.)
-
multi_page_nested_folders
- This is the example app used in chapter 9.
-
multi_page_query_strings
- An example of using query strings in the URL to pass parameters from one page to another.
Installation
dash-labs
is regularly updated, so be sure to install the latest version:
$ pip install -U dash-labs
New feature coming soon: Variables in the pathname.
@AnnMarieW Is there a way to incorporate flask decorators in with the /pages
functionality? For example, there were a few packages that I was looking at in flask that help you restrict access to certain pages based on user auth status using a decorator - would I need to use the previous url router callback to handle that or do you happen to know of a way to incorporate that into here? Thanks!
I would love to see this include customizing the meta data based on the variables.
Example:
We might have urls like /news/sports/1
and /news/sports/2
. Each of these articles cover different topics and the article title and overview should be displayed when sharing instead of a generic text about sports articles.
Now you are asking for the hard stuff!
That feature came up during the code review for handling variables in the pathname, but we decided to start with the generic version. I wasnāt sure how popular this would be ā but since you brought it up before this was even released, Iāll open an issue on Gitub for the feature request.
Thanks for the suggestion - you can track the progress here.
@raptorbrad Update: Hereās a potential solution . Feedback is welcome
I supposed that solution does work, but I donāt think itās ideal for more dynamic page. Iām not fully aware of how the meta tags work, but a solution I thought of is either:
- some meta component defined in the layout that overrides the
register_page
meta information. OR - an additional return object within the layout function that is a dictionary of meta tags
Iām not sure if either of these are feasible, since Iām not too familiar with whatās going on under the hood.
Another follow-up thought to this thread - would be awesome as multipage apps start to become more supported by Dash if there was an extension to it that tackled the namespace issues discussed in this thread - I find that I sometimes copy and paste code between pages / naturally end up using similar naming schemes in different pages, which muddies up the namespace. Would be awesome if this register page feature was able to incorporate a uuid into each page declaration and prepend it to the callbacks and the idās of that page (I personally never need a callback to be able to access information from separate pages, but I guess the ability to remove the uuid for spot cases would also be nice if needed in a niche case to access something elsewhereā¦).
I agree on this point, @dash-beginner. Maybe we could do something similar to the implementation in dash-extensions
where each page has an optional prefix
argument which is prefixed onto the IDs of the involved component and callbacks to avoid ID collisions between pages.
Hey all, thanks for the extensive work on this project. The multi-page app solution presented here and the other improvements to the development repo since the Dash 2.0 release are exciting! Since there hasnāt been a release since 2.0 back in August/September, can we expect 2.1 or another minor update to ship with these changes in the near future?
Hey @Emil and @dash-beginner
ID clashes have been an issue for a long time, especially in multi-page apps. A prefix is a good idea.
Emil, does your implementation of adding a prefix work with the current version of Dash? Iāve run into issues doing this in certain cases in Dash>=1.20 and I think itās because of this change.
Hi @jborman-exos - welcome to the Dash community and Iām glad you like `pages/
Dash 2.1 is going to be released soon, and since thereās still more work to do with pages/
, it may not be ready in time.
However it is available in dash-labs. I encourage you to give it a try ā and feedback is appreiciated!
I just tested it on Dash 2.0, and it seems to work just fine. Here is a small app that demonstrates the concept,
from dash_extensions.enrich import DashProxy, html, Output, Input, dcc
from dash_extensions.multipage import app_to_page, PageCollection, CONTENT_ID, URL_ID
def get_page(i: int):
# Create a small app that represents the desired page.
app = DashProxy()
app.layout = html.Div([html.Button("Click me", id="btn"), html.Div(f"I am app {i}", id="log")])
@app.callback(Output("log", "children"), Input("btn", "n_clicks"))
def on_click(n_clicks):
return f"You clicked {n_clicks} times!"
# Convert the app to a page.
return app_to_page(app, id=f"app{i}", label=f"App {i}"). # the id argument here is the prefix
# Create a few pages.
pc = PageCollection([get_page(i) for i in range(5)])
links = [html.A(children=page.label, href="/{}".format(page.id)) for page in pc.pages]
# Embed the pages in the main app.
app = DashProxy(suppress_callback_exceptions=True, prevent_initial_callbacks=True)
app.layout = html.Div(links + [html.Br(), html.Div(id=CONTENT_ID), dcc.Location(id=URL_ID)])
# Register callbacks.
pc.navigation(app)
pc.callbacks(app)
if __name__ == '__main__':
app.run_server(port=8889)
It embeds five pages that all have ID clashes, but the difference in prefixes make it work.
Hey @raptorbrad Thanks for your feedback on the first attempt at a solution for customizing the meta tags based on the variables in the pathname. I added your comments to the issue here:
Iām leaving the issue open - and pull requests are welcome if anyone would like to help
Here is a situation:
There are some page: a login page, a system page, and a main page.
When I click login button in main page, I will go to login page, but I need to check if I logined.
When I have logined, I will go to system page.
The question is : How to redirect to system page from login page automatically.
Hi all,
Iāve got the multi-page app working successfully on pythonanywhere but am trying to deploy to a Dash Enterprise server. The pages donāt work because I believe in pythonanywhere I have a wsgi file I can set the working directory. I have the app.py and the pages folder one level into the repository.
I can get the app working but the multiple pages arenāt working. The log file is giving me an error stating that āPaths that arenāt prefixed with requests_pathname_prefix are not supported.ā Any idea how I can fix this? I asked during our onboarding session but the Plotly folks on the call were not as familiar with how the pages.py worked.
2022-01-25T19:11:28.318126649Z app[web.1]: File "/app/.heroku/python/lib/python3.9/site-packages/dash_labs/plugins/pages.py", line 260, in update
2022-01-25T19:11:28.318132049Z app[web.1]: path_id = app.strip_relative_path(pathname)
2022-01-25T19:11:28.318136949Z app[web.1]: File "/app/.heroku/python/lib/python3.9/site-packages/dash/dash.py", line 1562, in strip_relative_path
2022-01-25T19:11:28.318142849Z app[web.1]: return strip_relative_path(self.config.requests_pathname_prefix, path)
2022-01-25T19:11:28.318148449Z app[web.1]: File "/app/.heroku/python/lib/python3.9/site-packages/dash/_utils.py", line 85, in strip_relative_path
2022-01-25T19:11:28.318154049Z app[web.1]: raise exceptions.UnsupportedRelativePath(
2022-01-25T19:11:28.318179349Z app[web.1]: dash.exceptions.UnsupportedRelativePath: Paths that aren't prefixed with requests_pathname_prefix are not supported.
2022-01-25T19:11:28.318186349Z app[web.1]: You supplied: /chapter/shop_hours_backup and requests_pathname_prefix was /capacity/
2022-01-25T19:11:28.318793252Z app[web.1]: 172.17.0.21 - - [25/Jan/2022:19:11:28 +0000] "POST /capacity/_dash-update-component HTTP/1.1" 500 290 "https://dash-cox-automotive.plotly.host/chapter/shop_hours_backup" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36"