How to use URL query strings inside Dash?

I’ve seen this asked previously, but dcc.Location doesn’t seem to support this: it gives the path, but strips out any query strings from the url?

e.g. /myapp?option1=a&option2=b

This would be very nice to create links to apps with specific customizations.

Is this possible? I understand there’s probably a way to do this by creating a custom React component, but I’m trying to use Dash precisely because my JavaScript isn’t that hot :slight_smile:

3 Likes

You might be able to get the current query strings with flask.request.args inside your app.callback. I haven’t tried this though.

from flask import request
# ... inside app.callback ... 
    print(request.args)

just gives ImmutableMultiDict([]) even when the url contains query items, so I don’t think it works. But thanks for the response!

Ah right, I forgot that it only applies to the request itself, not necessarily the query parameters on the page :see_no_evil:. OK, well it seems like the solution is adding this to the dcc.Location pathname.

As a +1 it would be amazing to be able to set specific customisations of apps!

I have a set of multipage apps that I’d like to be able to switch between with pre-defined input. For example,

App 1, Countries comparisons page:

  • Lists countries:
    • [USA](http://app2/?country=USA)
    • [Canada](http://app2/?country=Canada)

App 2, Cities comparisons page:

  • List cities, based on country filter (set filter=USA):
    • New York City
    • San Francisco
    • Cincinnati

It would be great to go from App 1 -> Click USA -> Switch to Page 2 with the filter now set to USA!

1 Like

In the meantime, it might be possible to encode these situations with regular URLs without querystrings, like:

http://app2/USA
http://app2/Canada
1 Like

The problem with that approach is when you have multiple options, both the order of the options matters, and you can’t have defaults, at least that I can see (e.g. app2/option1/option2/option3 … you’d always have to specify option1 and option2). It just makes the routing a little messy. Great for when there’s just a single option though.

For multiple options, what if you encoded the options as a JSON string with with URL encoding? i.e. {'a': 3, 'b': 5} would be "http://myapp.com/app1/%7B%22a%22:3,%22b%22:5%7D".

Not ideal, but might be a workaround until this is fixed.

1 Like

That sounds like a great idea actually! Thank you.

2 Likes

Thank you for the response Chris! Is the following along the lines of what you were thinking of? Very rough first pass, adapted from the URL tutorial:

# index.py

@app.callback(Output('page-content', 'children'),
              [Input('url', 'pathname')])
def display_page(pathname):
    pathname = str(pathname)
    if pathname.startswith('/app2'):
        city = None
        # i.e. for paths /app2/Canada
        if pathname.startswith('/app2/'):
            country = pathname.split('/')[-1]

        # Parameterize the layout to set an initial value of the dropdown
        return layout_app2(country=country)
    else:
        return '404'

How would you suggest handling the JSON?

import json
import urllib

url = urllib.unquote(pathname).decode('utf8')
json_string = url.split('/')[-1]
my_dict = json.loads(json_string)

ps. It seems like (suppress_callback_exceptions=True) has been ‘misspelled’ in the documentation / changed at some point, believe it should be supress rather than suppress?

This looks good to me!

Both are actually supported now (in the latest version of Dash). Originally I spelled it wrong :woman_facepalming: with supress and then I corrected it with suppress but kept the mispelled version for backwards compatability.

This is currently being addressed by a Dash community member in https://github.com/plotly/dash-core-components/pull/131.

1 Like

This has been merged. Query strings are now available in pip install dash-core-components==0.15.4. Until this makes it into the docs, see the integration test for usage: dash-core-components/test/test_integration.py at 6bbe63640268dd8b3a37783ecce7ae1e0093a094 · plotly/dash-core-components · GitHub

4 Likes

This is excellent, thanks for the update!

1 Like

I’m having trouble getting this to work the way I think it should on a multi-page, multi-file application. I’d like to have multiple pages in my application. The sub-pages would require a query string to query from a database and display the results. When I try to get the query string and write to a Div in a different layout, it doesn’t seem to work. What is the best way of reacting to a query string and modifying a sub-page layout based on it?

The index file:

from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html

from app import app
from apps import app1, app2

app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(id='page-content'),
    html.Div(id='searchRes1'), # this works
])

@app.callback(Output('page-content', 'children'),
              [Input('url', 'pathname')])
def display_page(pathname):
    if pathname == '/apps/app1':
         return app1.layout
    elif pathname == '/apps/app2':
         return app2.layout
    else:
        return '404'

if __name__ == '__main__':
    app.run_server(debug=True)

app file:

from dash.dependencies import Input, Output
import dash_html_components as html
import dash_core_components as dcc

from app import app

layout = html.Div([
    html.H3('App 1'),
    dcc.Dropdown(
        id='app-1-dropdown',
        options=[
            {'label': 'App 1 - {}'.format(i), 'value': i} for i in [
                'NYC', 'MTL', 'LA'
            ]
        ]
    ),
    html.Div(id='app-1-display-value'),
    dcc.Link('Go to App 2', href='/apps/app2'),
    #html.Div(id='searchRes1'), # this does not seem to work
])

@app.callback(
    Output('app-1-display-value', 'children'),
    [Input('app-1-dropdown', 'value')])
def display_value(value):
    return 'You have selected "{}"'.format(value)

@app.callback(Output('searchRes1', 'children'),
              [Input('url', 'search')])
def display_value(search):
    if search is None:
        return ''
    return search

This is pretty old, but I’m having the same issue on:
dash==0.36.0
dash-core-components==0.43.0
dash-html-components==0.13.5
dash-renderer==0.18.0

It doesn’t even need to be a multi-file app, here’s a simple stand alone example that’s a small tweak of the https://dash.plot.ly/urls example. The url_fill_page2 is never called even though 'page-2-dropdown.value': {'inputs': [{'id': 'url', 'property': 'pathname'}], 'state': [], 'callback': <function url_fill_page2 at 0x7f9f69db22f0>} is in the callback_map

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State

import flask

app = dash.Dash(
    __name__,
    external_stylesheets=['https://codepen.io/chriddyp/pen/bWLwgP.css']
)

url_bar_and_content_div = html.Div([
    dcc.Location(id='url', refresh=True),
    html.Div(id='page-content')
])

layout_index = html.Div([
    dcc.Link('Navigate to "/page-1"', href='/page-1'),
    html.Br(),
    dcc.Link('Navigate to "/page-2"', href='/page-2'),
])

layout_page_1 = html.Div([
    html.H2('Page 1'),
    dcc.Input(id='input-1-state', type='text', value='Montreal'),
    dcc.Input(id='input-2-state', type='text', value='Canada'),
    html.Button(id='submit-button', n_clicks=0, children='Submit'),
    html.Div(id='output-state'),
    html.Br(),
    dcc.Link('Navigate to "/"', href='/'),
    html.Br(),
    dcc.Link('Navigate to "/page-2"', href='/page-2'),
])

layout_page_2 = html.Div([
    html.H2('Page 2'),
    dcc.Dropdown(
        id='page-2-dropdown',
        options=[{'label': i, 'value': i} for i in ['LA', 'NYC', 'MTL']],
        value='LA'
    ),
    html.Div(id='page-2-display-value'),
    html.Br(),
    dcc.Link('Navigate to "/"', href='/'),
    html.Br(),
    dcc.Link('Navigate to "/page-1"', href='/page-1'),
])


def serve_layout():
    if flask.has_request_context():
        return url_bar_and_content_div
    return html.Div([
        url_bar_and_content_div,
        layout_index,
        layout_page_1,
        layout_page_2,
    ])


app.layout = serve_layout


# Index callbacks
@app.callback(Output('page-content', 'children'),
              [Input('url', 'pathname')])
def display_page(pathname):
    if pathname is not None:
        parts = pathname.split('/')
        print(parts)
        if len(parts) > 1:
            if parts[1] == "page-1":
                return layout_page_1
            elif parts[1] == "page-2":
                return layout_page_2
    return layout_index


# Page 1 callbacks
@app.callback(Output('output-state', 'children'),
              [Input('submit-button', 'n_clicks')],
              [State('input-1-state', 'value'),
               State('input-2-state', 'value')])
def update_output(n_clicks, input1, input2):
    return ('The Button has been pressed {} times,'
            'Input 1 is "{}",'
            'and Input 2 is "{}"').format(n_clicks, input1, input2)


# Page 2 callbacks
@app.callback(Output('page-2-display-value', 'children'),
              [Input('page-2-dropdown', 'value')])
def display_value(value):
    print('display_value')
    return 'You have selected "{}"'.format(value)

# Page 2 URL callback
@app.callback(Output('page-2-dropdown', 'value'),
              [Input('url', 'pathname')])
def url_fill_page2(pathname):
    print('page-2-dropdown', pathname)
    if pathname is not None:
        parts = pathname.split('/')
        if len(parts) > 2:
            return parts[2]


if __name__ == '__main__':
    app.run_server(debug=True)

You may try with Input(“url”, “search”)

1 Like

Try to use flask.request.referrer to obtain query string inside callback and then parse_qs from urllib

This was really helpful @maxxu, thanks!!