Redirect with different Query Strings

Hello Everyone :slight_smile: ,
I am building a dashboard to get “general” data from a database. I would like to make it possible that when a user wants to view more details of a specific entry, he can click on a button and should be redirected to another details page. I use Query Strings on both the first and second page.

The problem now is the following: I cannot get from, for example,
http://127.0.0.1:8050/PageAlpha?keyA=valueX
to
http://127.0.0.1:8050/PageBeta?keyB=valueY”.

I have already tried it with dozens of variants. To better understand my approach I also have a code example.

app.py

from dash import Dash, html, dcc
import dash

app = Dash(
    __name__,
    use_pages=True,
    suppress_callback_exceptions=True
)


app.layout = html.Div([
    html.H1('Multi-page app with Dash Pages'),

    html.Div(
        [
            html.Div(
                dcc.Link(
                    f"{page['name']} - {page['path']}", href=page["relative_path"]
                )
            )
            for page in dash.page_registry.values()
        ]
    ),

    dash.page_container
])

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

home.py

import urllib

import dash
from dash import html, dcc, callback, Input, Output, State

dash.register_page(__name__, path='/')


def layout(options=False, **other_unknown_query_strings):

   return html.Div(
       children=[
           dcc.Location(id='currentLocation'),
           html.H1(children='Page Home'),
           html.Button(
               id='showFruitsButton',
               children='Show Fruits?',
           ),
           html.Div(id='showFruitsRedirectDiv'),
           html.Hr(),
           html.Div(
               children=[
                   html.P('Select Fruit'),
                   dcc.RadioItems(id='showOptionsRadioItems'),
                   html.Button('Show Fruit Details', id='goToFruitDetailsButton')
               ]
           ),
           html.Div(id='redirectToDetailsDiv')
       ]
   )


# Disable/Enable Buttons Logic
@callback(
   Output('showFruitsButton', 'disabled'),
   Output('goToFruitDetailsButton', 'disabled'),
   Input('currentLocation', 'search'),
   prevent_initial_call=False
)
def check_if_button_is_needed(search):
   # Convert '?options=True' to '{'options': ['True']}'
   param = urllib.parse.parse_qs(urllib.parse.urlparse(search).query)
   # This means basically:
   #   If 'a URL-Parameter Exists':
   #       'return this Parameter'
   #   else:
   #       'return False'
   showFruitsButton_bool = eval(param['options'][0]) if bool(param) else False
   goToFruitDetailsButton_bool = not showFruitsButton_bool

   return showFruitsButton_bool, goToFruitDetailsButton_bool


# Set URL Parameter: options
@callback(
   Output('showFruitsRedirectDiv', 'children'),
   Input('showFruitsButton', 'n_clicks'),
   prevent_initial_call=True
)
def show_fruits_redirect(button_click):
   return dcc.Location(
       id='someIdDoesntMatter',
       pathname='/',
       search='?options=True'
   )


# Listen to URL and do/don't show RadioButtons
@callback(
   Output('showOptionsRadioItems', 'options'),
   Output('showOptionsRadioItems', 'value'),
   Input('currentLocation', 'search'),
   prevent_initial_call=False
)
def show_options(search):
   if bool(search):
       if eval(urllib.parse.parse_qs(urllib.parse.urlparse(search).query)['options'][0]):
           # Example Data...
           database_response = ['Apple', 'Banana', 'Peach']
           return database_response, database_response[0]
   raise dash.exceptions.PreventUpdate


# Redirect to Detail-Page with URL-Parameter 'fruit'
@callback(
   Output('redirectToDetailsDiv', 'children'),
   Input('goToFruitDetailsButton', 'n_clicks'),
   State('showOptionsRadioItems', 'value'),
   prevent_initial_call=True
)
def redirect_details(n_clicks, selected_fruit):
   # Here is the Error...
   location = dcc.Location(
       id='someOtherId',
       pathname='/details',
       search='?fruit={}'.format(selected_fruit)
   )
   return location

details.py

import dash
from dash import html

dash.register_page(
   __name__,
   path='/details',
)

def layout(fruit='nothing', **other_unknown_query_strings):
   return html.Div(
       children=[
           html.H1(children='Archive'),
           html.P('Show details for {}'.format(fruit))
       ]
   )

It never changes the entire URL. Either only the “pathname” or “search” changes, depending on whether I comment out “search”. I have also tried to access “url” directly, but this usually ends in an unwanted loop. It also makes no difference if I change ‘pathname’ and ‘search’ from a dcc.Location in an output or return a whole dcc.Location in a div.

I’m running out of ideas. Can anyone help me?
Thanks in advance.

1 Like

Hi @FirstNumberTwo and welcome to the Dash community :slight_smile:

You should avoid using eval because it can introduce security risks. This could execute what someone typed in the URL

if eval(urllib.parse.parse_qs(urllib.parse.urlparse(search).query)['options'][0]):

For more information see:

Since you are using Pages, you can do the navigation without adding any dcc.Location components to your app.

To disable/enable buttons and to update the options of the RadioItems, you can use a dcc.Store component rather than the search prop of a dcc.Location

Then to navigate to the details page, you can update the href prop of the gotToFruitDetailButton. That way, when the button is clicked, then Pages will automatically navigate to that page automatically - you don’t have to write that callback.

So your callback might look something like:

@callback(   
   Output('goToFruitDetailsButton', 'href'),
   Input('showOptionsRadioItems', 'value'),   
)
def redirect_details(selected_fruit):
   return f'/details?fruit={selected_fruit}'

You can see some examples in this repo. I recommend cloning it and running some of the examples so you can see it working. Example 10 shows navigating to a new page and using the query strings to show different details on the page. Examples 15 and 16 show how to navigate by updating buttons or links in a callback.

2 Likes

Hello @AnnMarieW,
Thank you very much for the quick reply. The approach via href works :+1:. It seems like a simple solution but I struggled with this for 3 days. I guess I had a Tunnel Vision :sweat_smile:

You should avoid using eval because it can introduce security risks. This could execute what someone typed in the URL

To disable/enable buttons and to update the options of the RadioItems, you can use a dcc.Store component rather than the search prop of a dcc.Location

The button to deactivate should be an example for my app-structure. In my app, the user is supposed to enter something into an input field, gets Information with which he then can be forwarded to another page.

I initially took the input for all subcomponents from the InputField, but this quickly became messy. I didnt know the Store-Component until now. That’s why I keep the input as a parameter in the URL. This also has the advantage that users can send links to each other, where the search then starts automatically.

I see the problem when working with dropdown, radio buttons, etc. So the URL parameters offer unwanted selection possibilities. I don’t see any security concerns in my case (Correct me if Im wrong). Whether the user can enter via. URL or via. InputField should not matter for the time being. Each of my components just look at the URL and check the parameter if it exists and if it is valid. If both do not occur a PreventUpdate is simply raised.

However, your repo with the examples looks very helpful though. I will definitely have a closer look at it. Thanks a lot for your help!

@FirstNumberTwo

This is very important. Yes you still have security risks in your code.

The issue is in code like here: eval(param['options'][0])

If you type in the URL ?options=print(“hello”) you will see hello printed to the console because it executed print("hello")

For more info please read

2 Likes

@FirstNumberTwo,

This is a very basic example, something more scary would be to wipe your entire system data. Or, transmit all of it, download malware, etc.

A fun thing would be if they took a look at your app setup and was able to use dash to infect all your clients too. :slight_smile:

2 Likes