Black Lives Matter. Please consider donating to Black Girls Code today.

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.

1 Like

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: https://github.com/plotly/dash-core-components/blob/6bbe63640268dd8b3a37783ecce7ae1e0093a094/test/test_integration.py#L331-L455

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”)

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