GibbsConsulting / django-plotly-dash

Expose plotly dash apps as django tags
MIT License
539 stars 122 forks source link

context not consistently transfered to dash app #378

Open ex3t3r opened 2 years ago

ex3t3r commented 2 years ago

Hi,

I have an issue where the context defined in the Django view is not consistently transferred to the Dash app. I am using Dash 2.0.0, Django 3.2.7, Django-plotly-dash 1.6.5. I was made aware of the version compatibility issue as per e.g. #353, which I have also experienced.

I got the basics working to my satisfaction using #364 and other resources.

In views.py I define the context as: dash_context = {"dash_context":{"my_key":{"value":str(my_value)}}} (my_value is obtained from Django and shows the value consistently and correctly populated.)

And then the page is rendered using: return render(request, 'page_1/page_1.html', context=dash_context)

When running the app a print statement in the view shows that the context is correctly constructed:

my-page Context: {'dash_context':{'my_key':{'value':'somevalue'}}}

Checking the rest of the Django session attributes indicate that all is populated as expected.

The HTML template contains the following:

{% block content %}
        {% load plotly_dash %}
        <div class="{% plotly_class name='MyGraphs' %} card" style="height:100%; width:100%">
                {% plotly_app name='MyGraphs' ratio=0.65 initial_arguments=dash_context %}
        </div>
{% endblock %}

Looking at my callback function that is initially called I can see that the value in the context is correctly passed and it triggers the required callbacks to be executed.

My callback functions use the my_key value as an input to the callbacks. e.g.:

@app.callback(
    Output('id_overview_graph', 'figure'),
    [
        Input('period_selection', 'value'),
        Input('type_selection','value'),
        Input("my_key", "value")
    ]
)
def update_overview_graph(period, metricType, my_key,**kwargs):
    # The rest of the function

Inspecting the get command in the browser looks as follows:

http://172.31.11.19:8888/django_plotly_dash/app/MyGraphs/initial/dpd-initial-args-af9f58d8224b4e5ab959ec0dfda9a015/

On the HTML page I can see the graphs and also a hidden tag correctly populated:

This part of getting the values through the context does not work consistently and reliably. Symptoms seen:

  1. Sometimes the context is not passed or the value received in the Dash app and callback functions are empty.
    1. In the cases where the value is empty the callback is still triggered. This is indicated by some logs inside the callback function and the graph titles being correctly updated. Verified with a callback function only triggered by the context key received.
  2. Sometimes reloading the page once results in the context values being passed correctly.
  3. Sometimes if I reload the page on the browser multiple times, the context is correctly passed. There is no fixed pattern to this. Sometimes it works the first time the page is loaded. Sometimes it only works after ten consecutive page downloads.
  4. It also does not help to go to another page and then return to the page with the empty context.

The session_key authenticated user and other Django session related arguments stay the same, irrespective if the context is passed correctly or not.

Any thoughts on why this behaviour is experienced? Is it a Dash v2 compatibility related issue? (Not in a position to test it with v1.21.0 at the very moment)

Thank you for your help.

GibbsConsulting commented 2 years ago

This is probably a v2 compatibility issue - one of the known challenges is that v2 Dash essentially stops additional arguments being passed to callbacks.

ex3t3r commented 2 years ago

Thanks for the feedback.

I now had an opportunity to do a test using dash 1.21.0. I get the same behaviour.

Package versions used: dash 1.21.0 dash-bootstrap-components 0.13.0 dash-core-components 1.17.1 dash-html-components 1.1.4 dash-renderer 1.9.1 dash-table 4.12.0 Django 3.2.7 django-cors-headers 3.1.0 django-crispy-forms 1.12.0 django-plotly-dash 1.6.5

How can I do more in-depth checks to see where the context info disappears? The App is running in a container.

lBulgur commented 2 years ago

Got the same Problem :( Is there any workaround to transfer intial arguments without channels or redis?

ex3t3r commented 2 years ago

@lBulgur

Try putting 'cache_arguments': False into your django settings.py file.

Have a look at Configuration options on how to add it.

I inserted that into my settings.py file and found an immediate improvement in the reliability of the initial arguments being passed through. Still testing but looking promising.

lBulgur commented 2 years ago

@ex3t3r Thank you a lot! It solved my problem!

kotnikd3 commented 2 years ago

I have the same problem. @lBulgur How can you access initial arguments with cache_arguments: False option?

GibbsConsulting commented 2 years ago

@lBulgur how are you running your app?

lBulgur commented 2 years ago

I have the same problem. @lBulgur How can you access initial arguments with cache_arguments: False option?

settings.py :

INSTALLED_APPS = [
    ...
    'django_plotly_dash.apps.DjangoPlotlyDashConfig',
    ...
]

X_FRAME_OPTIONS = 'SAMEORIGIN'

PLOTLY_DASH = {
    # Flag to control location of initial argument storage
    "cache_arguments": False,
}

Main urls.py

urlpatterns = [
...
path('django_plotly_dash/', include('django_plotly_dash.urls')),
...
]

App urls.py

from . import dash_app_filename
...

App views.py

def dash_app_view(request, pk):
    return TemplateResponse(request, 'django_template.html', {'dash_context' : {'target_id': {'value': pk }}})

App template

{% load plotly_dash %}
...
{% plotly_app name="DashAppName" ratio=1.0 initial_arguments=dash_context %}

dash_app

...
app = DjangoDash(name='DashAppName', id='target_id', add_bootstrap_links=True)
app.layout = html.Div(id='page-content', children=[
    dcc.Input(id='target_id', type='hidden', value='filler text'),
    ...
    html.Div(id="my_layout"),
    ...
]
@app.callback([Output('my_layout', 'children')],
               [Input('target_id', 'value')])
def update_analyse_options(object_pk):
    my_object = models.MyDjangoModel.objects.all().filter(pk=object_pk)[0]
    my_layout = # creating some buttons and inputs based on the object
    return[my_layout]

dash: 2.0.0 django-plotly-dash: 1.6.0

kotnikd3 commented 2 years ago

@lBulgur Thank you for response! Unfortunately it doesn't work for me. I can't receive pk (generated in dash_app_view), instead I always receive filler text in the update_analyse_options callback method.

kotnikd3 commented 2 years ago

@lBulgur @GibbsConsulting By using Redis for caching (django-redis library), I can not reproduce the issue. So this is the solution for me.

CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': f'redis://{REDIS_HOSTNAME}:{REDIS_PORT}/{REDIS_DB}',
        # "LOCATION": "redis://127.0.0.1:6379/1",
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
        }
    }
}
GibbsConsulting commented 2 years ago

It is quite possible that there is a cache miss causing the arguments to not be found - this could be happening when running in a debug environment as the reduced resources (eg possibly single threaded execution, ordering of requests, etc) lead to the cache being accessed before it is written.

Is it the case that the issue only arises (a) when using the cache for the initial arguments and (b) when running with the debug/test server? Or has someone seen the issue in other use cases?

kotnikd3 commented 2 years ago

(a) The issue only arises when using the initial arguments (with cache_arguments set on True or False). (b) I've only tried with DEBUG=True: the issue can be reproduced on Google Cloud Run in ~40% cases, but never locally.

Cache misses might be the reason for the issue.

GibbsConsulting commented 2 years ago

@kotnikd3 one approach might be to use state within the application. In other words, instead of setting the initial arguments in the template call, create an instance of the DashApp model. This will essentially use the backend to share app state, and you'll need some way to work out which instance to use (eg per user or similar).

If the issue is intermittent, it does sound like a cache miss or similar. Running with DEBUG=True could also be contributing to this as it may imply an ordering of calls in a given execution environment.

BatoolGasser commented 3 months ago

Hi,

I have an issue where the context defined in the Django view is not consistently transferred to the Dash app. I am using Dash 2.0.0, Django 3.2.7, Django-plotly-dash 1.6.5. I was made aware of the version compatibility issue as per e.g. #353, which I have also experienced.

I got the basics working to my satisfaction using #364 and other resources.

In views.py I define the context as: dash_context = {"dash_context":{"my_key":{"value":str(my_value)}}} (_myvalue is obtained from Django and shows the value consistently and correctly populated.)

And then the page is rendered using: return render(request, 'page_1/page_1.html', context=dash_context)

When running the app a print statement in the view shows that the context is correctly constructed:

my-page Context: {'dash_context':{'my_key':{'value':'somevalue'}}}

Checking the rest of the Django session attributes indicate that all is populated as expected.

The HTML template contains the following:

{% block content %}
        {% load plotly_dash %}
        <div class="{% plotly_class name='MyGraphs' %} card" style="height:100%; width:100%">
                {% plotly_app name='MyGraphs' ratio=0.65 initial_arguments=dash_context %}
        </div>
{% endblock %}

Looking at my callback function that is initially called I can see that the value in the context is correctly passed and it triggers the required callbacks to be executed.

My callback functions use the _mykey value as an input to the callbacks. e.g.:

@app.callback(
    Output('id_overview_graph', 'figure'),
    [
        Input('period_selection', 'value'),
        Input('type_selection','value'),
        Input("my_key", "value")
    ]
)
def update_overview_graph(period, metricType, my_key,**kwargs):
    # The rest of the function

Inspecting the get command in the browser looks as follows:

http://172.31.11.19:8888/django_plotly_dash/app/MyGraphs/initial/dpd-initial-args-af9f58d8224b4e5ab959ec0dfda9a015/

On the HTML page I can see the graphs and also a hidden tag correctly populated:

This part of getting the values through the context does not work consistently and reliably. Symptoms seen:

  1. Sometimes the context is not passed or the value received in the Dash app and callback functions are empty.

    1. In the cases where the value is empty the callback is still triggered. This is indicated by some logs inside the callback function and the graph titles being correctly updated. Verified with a callback function only triggered by the context key received.
  2. Sometimes reloading the page once results in the context values being passed correctly.
  3. Sometimes if I reload the page on the browser multiple times, the context is correctly passed. There is no fixed pattern to this. Sometimes it works the first time the page is loaded. Sometimes it only works after ten consecutive page downloads.
  4. It also does not help to go to another page and then return to the page with the empty context.

The session_key authenticated user and other Django session related arguments stay the same, irrespective if the context is passed correctly or not.

Any thoughts on why this behaviour is experienced? Is it a Dash v2 compatibility related issue? (Not in a position to test it with v1.21.0 at the very moment)

Thank you for your help.

how can i pass data from django template to dash app while i am using plotly_direct not plotly_app {%plotly_direct name="student_register"%} which doesn't accept initital_argument as a parameter