Django and Dash - ead's Method

Thank you all for continuing to contribute ideas here. I’ve learned a ton from all of you. This continues to be a productive thread.

Hi @delsim,

Your solution sounds promising.

I tried setting another callback input argument in your demo but this failed:

@app.callback(
    dash.dependencies.Output('output-color', 'children'),
    [dash.dependencies.Input('dropdown-color', 'value'),
    dash.dependencies.Input('url', 'pathname')])
def callback_color(dropdown_value, pathname):
    print(pathname)
    return "The selected color is %s." % dropdown_value

with error:

'NoneType' object has no attribute 'form_dash_instance'

Is there another way to pass Django context like a relevant ID to Dash?

Hi @eddy_oj, assuming you’re using the current version (0.1.0 at the moment, I think) then you can inject some Django properties by using @app.expanded_callback to register your callback, and then adding a **kwargs argument to the callback. Once any callback is registered in this way, all of the callbacks will get passed these extra arguments.

In other words, the following should work:

@app.expanded_callback(
      dash.dependencies.Output('output-color', 'children'),
     [dash.dependencies.Input('dropdown-color', 'value')])
def callback_color(dropdown_value, **kwargs):
     print(kwargs)
     return "The selected color is %s." % dropdown_value

At the moment, only a few properties are injected; running the example is probably the quickest way to see what is actually there for the version that you’re using. The url is not in that list, but the identifier for the particular Dash app is, and a call to the django.urls.reverse function can be used to determine the url. This is how the form_dash_instance function of the DjangoDash class supplies it to the underlying Dash application, for example.

I do like your idea of explicitly listing the desired properties, as it is in keeping with how the other callback properties are defined.

@delsim, if I’m reading your comment correctly, dcc.Location / (‘url’, ‘pathname’) won’t work with DjangoDash – is this the only standard component that won’t work? (presumably because you want to use Django routing instead)

And to ask a very vague/broad question that might not have an answer, but how brittle do you expect your approach to be with future versions of Dash? I think a lot of people (myself included) would be very excited to use Dash in Django, and this approach seems great, but not if a few months down the road everything breaks (:

Finally, since you’re not using Flask, have you noticed any differences (either way) in performance?

Many thanks for making the code open source btw, looking forward to trying it!

@mkhorton, I think that dcc.Location and similar should work OK, and from a quick check that seems to be the case, but I haven’t had the chance to do an in-depth validation. This would make sense given how DjangoDash is written - it just exposes the urls through Django and relies on the the fact that the underlying Dash class works in terms of relative paths to a base url.

This also means that I don’t think the approach is particularly brittle. We do have two current projects making use of this work - one is pure Django, the other also leverages Jupyter and we’re working on extending the wrapper for that also. In both cases the implementation goal is the same - just replace dash.Dash with the appropriate wrapper and leave everything else unchanged. Keeping this wrapper up to date with changes in the underlying Dash library shouldn’t be too hard, and pull requests are always welcome! If you’re looking for some form of commercial stability then please contact me privately.

As for performance, not done any measurements yet. Our projects are embedded into Django as part of a robust application exposed to the internet, so considerations like caching, reverse proxies, authentication etc are important. My assumption is that the difference in processing time of Django vs (Flask plus that part of any web framework needed for robustness etc) is going to be marginal anyway when considered as part of the end-to-end chain processing a request. Having said that, we’re adding some routes to our front-end reverse proxies so that the static part of the dash components can be directly served rather than go through a python server.

Glad to hear that the project is of interest and hopefully useful to others. Any and all feedback is most welcome!

Edit: I should add for @eddy_oj that I think I misunderstood the context of their earlier question, and hopefully this additional info makes sense.

3 Likes

@delsim what is the process for integrating your app into an existing project and loading a dash app into an existing project?

The tutorials seemed a bit confusing - I’m trying to add dash to an existing app to display on a page.

Any chance of this for Django 1.11?

I just pushed a new (but still prerelease!) version to update the documentation (ie the code changes aren’t that useful yet) and hopefully it (the documentation) is now a bit more useful. Its still a long way short of where it needs to be however. You can find it at:

https://django-plotly-dash.readthedocs.io/en/latest/installation.html

Briefly, the installation process is (1) pip install the package (2) add django_plotly_dash to the list of applications and (3) run the migration. There are a couple of extra parts in the demo (some messing around with static files and use of the channels project) on top of this, but don’t look too closely at the moment as they’re a work in progress and also not relevant to ordinary use.

Edit: wrt Django 1.11, its a ‘maybe’. Restricting the code to 2.0 was something that happened by default rather than explicit choice, and I don’t think there are any dependency constraints. If its easy to relax the constraint - for example if it is just a case of changing the urls - then that could happen soon but I don’t have anything forcing this to happen immediately; pull requests most welcome of course!

1 Like

Hi @delsim thanks for posting this new alternative solution for Django users.

I Implemented 2 or 3 months ago the NedNed solution.

I’m deploying my project right now.

Considering that the NedNed solution uses a Flask server, that could not be the best aproach for production. I was wondering If you could explain the benefits for production to consider refactor our code to use your solution?

Hi @pepetodash - our motivation in embedding the Dash server in Django was driven by a requirement to use some Django features and operational simplicity, not by anything to do with Flask. Flask is widely used in production by many people and the deployment steps as described in the Flask documentation are very similar to the Django ones.

There is also a question about relative performance, which I did already comment on earlier in this thread. For our use case, we don’t think that any performance difference between Django and (Flask plus the Django features we need) is going to be particularly relevant, particularly once the embedding of either application inside a more complete stack with reverse proxy, caching, WSGI/ASGI server etc is taken into account. Crudely, we have a solution that is acceptable for our use cases, so we’re not worrying about the alternatives at this point in time.

I can’t really comment on your specific needs but my company in general follows a ‘first make it work, then make it better’ approach. I often find that ‘better’ is best left for the end users to define, so I’d be tempted to go with what is already working and refactor later if needed.

2 Likes

Great! thank you for the advice. Have a nice day!

1 Like

I have started to play around with your repo. I tried to suss your code out - wow youve been busy.

I have a question on states. In the docs you suggest that to use django-plotly-dash by itself results in a stateless app i.e. “like a dash in an iframe”. Does this mean we cant use extended callbacks passing over the django session variable unless we include all the other packages like channels and redis?

Thanks for the comments @eddy_oj - and with respect to the other packages you shouldn’t need them. The goal is for the storage of internal state, use of django session and related variables, and the live updating to be three separate features.

The third of those is what needs the extra packages (redis, channels, running in an ASGI server, etc). If you’re not using the live updating feature then you don’t need them and the code should just work without them. However, I suspect all of the combinatorics of installing/not installing packages hasn’t been tried out, let alone tested, yet.

OK that sounds awesome!

I have been playing around with the code and tried setting context in the Django request.session dictionary and passing this to an app. As far as I can tell, this isn’t propagated to django-dash-plotly. Am I correct in assuming this is what is articulated here? I just want to check as a previous post suggested this is possible. If it is possible, I am wondering how to explicitly pass a django session variable over to Dash. Does this require the use of Channels and Redis for live state?

You should be able to see content from a Django session in the callback - put whatever you want into a variable (ideally a dict) called django_plotly_dash in a Django request.session and it will get passed as the session_state kwarg to the (extended) callback. It was a deliberate decision not to pass the whole session in, but there is no fundamental constraint (that I can think of right now) that would stop us adding the entire session.

At the end of the callback, the Django session will be updated with any changes to this variable. And you don’t need Channels and Redis for this!

Here is an example view that hopefully helps demonstrate the session part - add a path somewhere to call this, and then look at the ‘Demo Three - Enhanced Callbacks’ page in the demo, which dumps out the part of the Django session that it sees

from django.template.response import TemplateResponse

def add_to_session(request, template_name="index.html", **kwargs):
    'Add some info to a session in a place that django-plotly-dash can pass to a callback'

    django_plotly_dash = request.session.get("django_plotly_dash", dict())

    session_add_count = django_plotly_dash.get('add_counter',0)

    django_plotly_dash['add_counter'] = session_add_count + 1
    request.session['django_plotly_dash'] = django_plotly_dash

    return TemplateResponse(request, template_name, {})

Your code is awesome - this seems to be working fantastic so far.

I am now at the stage where I am adding increasingly complex layouts. Now that I have been looking around, I now see what you mean by the Dash content being inserted as an iframe.

No css is applied to the iframes yet. Is there a recommended way to apply the same css across Django and Dash?

@delsim I’ve tried to get the code running from your simple usage documentation at https://django-plotly-dash.readthedocs.io/en/latest/simple_use.html

For some reason I’m getting an error: “PseudoFlask’ object has no attribute ‘register_blueprint’”

‘self.server.register_blueprint’ is referenced in Dash.init()

Any idea what might be causing this?

EDIT - I solved this issue by downgrading dash from 0.22.0 to 0.21.0 which didn’t have the ‘register_blueprint’ attribute on the Dash object.

Sounds like Dash has added something and django-plotly-dash needs to also add it in order to keep up. Thanks for the info - I’ve added an issue to the github repo.

1 Like

i’ve been using this approach for a while now, and it’s working relatively fine, i do have one problem

how can i add download file routes?

tried using flask send_file and send_from_directory, also django Response object to download a file as attachment but nothing works well

am i missing something, or this is an expected issue due to the double passing via flask then django ?

Thanks

found a solution to the file download issue by adding another django route (in the urls.py) which returns an appropriate response for downloads

however this solution requires to save the file in a callback, and add a link for downloads…
(can’t send files generated on the fly)

happy to hear if anyone has a better option