Hi, I’m trying to change the app.layout depending if the device is mobile or PC. My Dash app is a page with 2 graphs and a dbc.Col of info. If it’s mobile, the layout should be in 3 dbc.Rows. if it’s PC, the layout should be in 3 dbc.Col’s. Rows vs. Columns. So just using xs, xl, md, etc. won’t help me here, because the width isn’t the problem, it’s the actual layout.
I keep getting
RuntimeError: Working outside of request context.
This typically means that you attempted to use functionality that needed
an active HTTP request. Consult the documentation on testing for
information about how to avoid this problem.
when I try to use
parse(request.headers.get('User-Agent'))
to get the user agent.
My code is:
def serve_layout():
user_agent = parse(request.headers.get('User-Agent'))
# if the user is a PC
if user_agent.is_pc == True:
app.layout = dbc.Container(children=[
dbc.Row( # First row: title card
[
dbc.Col([title_card]),
]
),
dbc.Row( # Second row: the rest
[
dbc.Col([user_options_card], width = 4),
dbc.Col([map_card], width = 8)
]
),
],
fluid = True,
className = "dbc",
id = 'layout'
)
elif user_agent.is_mobile == True:
app.layout = dbc.Container(children=[
dbc.Row( # First row: title card
[
dbc.Col([title_card]),
]
),
dbc.Row( # Second row: the rest
[
dbc.Col([user_options_card]),
dbc.Col([map_card])
]
),
],
fluid = True,
className = "dbc",
id = 'layout'
)
return app.layout
app.layout = serve_layout
@app.callback(
Output(component_id='cluster', component_property='children'),
Output('layout', 'children'),
[
Input(component_id='subtype_checklist', component_property='value'),
Input(component_id='pets_radio', component_property='value'),
Input(component_id='terms_checklist', component_property='value'),
Input(component_id='garage_spaces_slider', component_property='value'),
Input(component_id='rental_price_slider', component_property='value'),
Input(component_id='bedrooms_slider', component_property='value'),
Input(component_id='bathrooms_slider', component_property='value'),
Input(component_id='sqft_slider', component_property='value'),
Input(component_id='yrbuilt_slider', component_property='value'),
Input(component_id='sqft_missing_radio', component_property='value'),
Input(component_id='yrbuilt_missing_radio', component_property='value'),
Input(component_id='garage_missing_radio', component_property='value'),
Input(component_id='ppsqft_slider', component_property='value'),
Input(component_id='ppsqft_missing_radio', component_property='value'),
Input(component_id='furnished_checklist', component_property='value'),
Input(component_id='security_deposit_slider', component_property='value'),
Input(component_id='security_deposit_missing_radio', component_property='value'),
Input(component_id='pet_deposit_slider', component_property='value'),
Input(component_id='pet_deposit_missing_radio', component_property='value'),
Input(component_id='key_deposit_slider', component_property='value'),
Input(component_id='key_deposit_missing_radio', component_property='value'),
Input(component_id='other_deposit_slider', component_property='value'),
Input(component_id='other_deposit_missing_radio', component_property='value'),
Input(component_id='listed_date_datepicker', component_property='start_date'),
Input(component_id='listed_date_datepicker', component_property='end_date'),
Input(component_id='listed_date_radio', component_property='value'),
Input(component_id='layout', component_property='children'),
]
)
# The following function arguments are positional related to the Inputs in the callback above
# Their order must match
def update_map(subtypes_chosen, pets_chosen, terms_chosen, garage_spaces, rental_price, bedrooms_chosen, bathrooms_chosen, sqft_chosen, years_chosen, sqft_missing_radio_choice, yrbuilt_missing_radio_choice, garage_missing_radio_choice, ppsqft_chosen, ppsqft_missing_radio_choice, furnished_choice, security_deposit_chosen, security_deposit_radio_choice, pet_deposit_chosen, pet_deposit_radio_choice, key_deposit_chosen, key_deposit_radio_choice, other_deposit_chosen, other_deposit_radio_choice, listed_date_datepicker_start, listed_date_datepicker_end, listed_date_radio, user_agent):
df_filtered = df[
(df['Sub Type'].isin(subtypes_chosen)) &
pets_radio_button(pets_chosen) &
(df['Terms'].isin(terms_chosen)) &
# For the slider, we need to filter the dataframe by an integer range this time and not a string like the ones aboves
# To do this, we can use the Pandas .between function
# See https://stackoverflow.com/a/40442778
(((df['Garage Spaces'].between(garage_spaces[0], garage_spaces[1])) | garage_radio_button(garage_missing_radio_choice, garage_spaces[0], garage_spaces[1]))) & # for this one, combine a dataframe of both the slider inputs and the radio button input
# Repeat but for rental price
(df['List Price'].between(rental_price[0], rental_price[1])) &
(df['Bedrooms'].between(bedrooms_chosen[0], bedrooms_chosen[1])) &
(df['Total Bathrooms'].between(bathrooms_chosen[0], bathrooms_chosen[1])) &
(((df['Sqft'].between(sqft_chosen[0], sqft_chosen[1])) | sqft_radio_button(sqft_missing_radio_choice, sqft_chosen[0], sqft_chosen[1]))) &
(((df['YrBuilt'].between(years_chosen[0], years_chosen[1])) | yrbuilt_radio_button(yrbuilt_missing_radio_choice, years_chosen[0], years_chosen[1]))) &
(((df['Price Per Square Foot'].between(ppsqft_chosen[0], ppsqft_chosen[1])) | ppsqft_radio_button(ppsqft_missing_radio_choice, ppsqft_chosen[0], ppsqft_chosen[1]))) &
furnished_checklist_function(furnished_choice) &
security_deposit_function(security_deposit_radio_choice, security_deposit_chosen[0], security_deposit_chosen[1]) &
pet_deposit_function(pet_deposit_radio_choice, pet_deposit_chosen[0], pet_deposit_chosen[1]) &
key_deposit_function(key_deposit_radio_choice, key_deposit_chosen[0], key_deposit_chosen[1]) &
other_deposit_function(other_deposit_radio_choice, other_deposit_chosen[0], other_deposit_chosen[1]) &
listed_date_function(listed_date_radio, listed_date_datepicker_start, listed_date_datepicker_end)
]
# Create markers & associated popups from dataframe
markers = [dl.Marker(children=dl.Popup(popup_html(row)), position=[row.Latitude, row.Longitude]) for row in df_filtered.itertuples()]
# Debug print a statement to check if we have all markers in the dataframe displayed
print(f"The original dataframe has {len(df.index)} rows. There are {len(df_filtered.index)} rows in the filtered dataframe. There are {len(markers)} markers on the map.")
user_agent = parse(request.headers.get('User-Agent'))
# Generate the map & app layout
return dl.MarkerClusterGroup(id=str(uuid.uuid4()), children=markers), user_agent
# Launch the Flask app
if __name__ == '__main__':
app.run_server(mode='external', host='192.168.4.196', port='9208', debug='false')
I wish it wasn’t so complicated to just get the user agent string I honestly have no idea what I’m doing and would welcome any advice… or a much simpler solution, lol.