Django and Dash - ead's Method

Is there any way to avoid this kind of routing being triggered for other pages as the ones in routes? Like if a user enters /viz/something, I would prefer django itself handling the routing instead of the user being redirected to the dash layout with the “unknown link” message.

For some reason also one of my prior django views is “hijacked” by this routing and displays the “unknown link” page instead of the usual content. The only distinct things I can think of is that this page is usually called from a form by a HttpResponseRedirect and depends on some context stored in the session. However, hijacking also occurs when i directly enter /viz/mypage.

My first instinct would be to change the regex in urls.py.
Currently its catching anything written as a URL and feeding it to views.dash because the regex is so broad. I unfortunately don’t know regex well enough to know what you could replace this with. If you made it so it only picked up URLS that matched something like ‘figure*’ then the URL wouldn’t get picked up by dash unless it had the word figure in it and therefore would be handled by django.
Maybe not the total solution but possibly somewhere to start?

1 Like

Wow, this worked surprisingly well, thanks!

I changed the regex from ‘^’ to ‘^dash/’. Also in router.py I changed the entries in pages to include ‘dash/’ at the beginning. Now all dash pages are available at /viz/dash/ and don’t interfere with my other stuff anymore.

1 Like

The other approach is to put all of your other views in a separate Django app that’s routed to something at the same level. eg /viz/ and /otherapp/ .

Giving Dash its own app and letting it take over routing of everything below the url prefix where the app is mounted was what I was going for. Seems like a good separation of concerns.

1 Like

looks like a great solution,
i’ve been toying with the idea of moving my dash to django as a more complete solution and this seems to work

now all i need is figure out the best way to move my app selection menu out of the dash layout :slight_smile:

It’s probably worth pointing out that method used to embed Dash in Django here could well suffer from performance issues as there’s some double handling going on between Django and Flask, or maybe other surprises since its using flask machinery that’s intended to be used in a testing context rather than deployment. That said, for a lot of scenario’s it may well be fine.

1 Like

Thank you for this super solution ead and nedned. It plugs in well with an existing solution I have.

I have another question about mixing context between Django and Dash:

If the “display_page” callback in router.py selects the layout, how can I get it to accept a context variable from Django and input in to ‘page()’?

I was thinking that the context could be a variable such as a country name stored as a json:

# From router.py:

@app.callback(Output('content', 'children'),
                     [Input('url', 'pathname')],
                      Input('django_context', 'children')])
def display_page(pathname, django_context):

   if pathname is None:
       return ''

   page = routes.get(pathname, 'Unknown link') 

   if callable(page):
       # can add arguments to layout functions if needed etc
       layout = page(**django_context**)
   else:
       layout = page

   return layout

I am also guessing that the ‘django_context’ variable would need to be passed as an arg to the ‘dispatcher’ method in views.py.

Are you able to give an indication as to how I go about passing this from the Flask server to Dash in this method? Any assistance, however vague, would be much appreciated at this stage!

I think I have found a (very hacky) solution but I need to test it before coming back to the forum. I will post it soon but may start a separate thread as it is running Dash within the context of Django, which is different from running a separated Dash app within Django.

1 Like

Great thank you a lot for your help @nedned @eddy_ojb

I have a question:

I understand with this solution We got one Server, one App that could handle multi sessions for serveral users.

Question: If I need to do 3 different graphs interfaces that are not related and I like those to be accessible through different urls. How many Dash apps should I do? How many Flask servers should I do? Should I have to do one Django app for each Dash App handling each graph interfaces? (of course I know that one Dash App will handle multi sessions for multi users)

I know the @nedned solution use the layouts.py functions to permit render different graphs, but I’m talking about completley differents graphs interfaces whit different sets of layouts, inputs and callbacks.

It depends on how you want to implement it really. You could have a single Django app that has three different Dash apps hooked up to different Django routes, or you could have one single Dash app attached to a single Django route (as in my example) and use the Dash router.py logic to add different endpoints within the Dash app. I suspect the second option might be simpler. The first option involves either having three separate Flask instances (which seems a bit wasteful) or having them all try to reuse the one Flask instance, which adds extra complexity.

Thank you a lot for the answer!

1 Like

Hi all,

I have produced an extension to ead’s and nedned’s code (thank you both very much!).

The code can be found here:

The package versions I used are:

  • Python 3.6.5
  • Django 2.0.2
  • dash 0.21.1
  • dash-core-components 0.22.1
  • dash-html-components 0.10.1
  • dash-renderer 0.12.1
  • numpy 1.14.3

The key differences are outlined below:

  • This is a Django app with Django controlling the page navigation, context and data models with a Dash incorporated in to a page

  • The database can be queried from a Dash layout using Django models and context. In the EU, this type of functionality is useful in staying compliant with GDPR regulations if you have a data security model tied to session context at the backend

  • Separation of Dash and Django files to try and make it more discoverable (since they aren’t cleanly interleaved)

  • There is also a filthy function called: “clean_dash_content”, which essentially removes characters such as ‘\n’ in the content returned by Flask (I am looking for a better solution here…)

  • Since most servers I have seen are offline, the Dash is served locally, which you can see in dash_server.py. This upset the CSS path that Dash tries to find so you will also notice that I put the Dash css in the ‘static’ folder and served it to fix this problem.

  • Since the base pathname might not be known in advance, I changed the “display_page” function which loops through each layout function name to check whether it is in the url - the applicable function is then returned. There may be an easier way using regex but I didn’t have the regex chops to do this at the time.

  • Lines 14 to 21 in dash_layouts.py show how context is extracted from the url and used in a Django model.

  • Lastly, if you look at the dash_django_page.html, you will see how the Dash content is injected:
    {{ dash_content | safe}}. It won’t work without the “safe” keyword.

The downside to this whole approach is some double handling by Flask and Django. However, I have hooked this Django app to MS IIS to serve pages and haven’t noticed a major problem yet. I imagine the Flask dev instance might fall over under heavy load. I don’t know what the security implications are either.

I think a more professional, integrated approach between Dash and Django is highly desirable moving forward. For me personally, I need time to prove to my stakeholders at work that there is value in our system (which heavily relies on Django) before I can approach the firm to get funding to work with Plotly to develop this as a project.

I am eager to receive any feedback from the community and any ways to improve the code!

4 Likes

We’ve taken a different approach and wrapped dash into a django app in a way that removes the need for a flask server. The main motivation is to reduce the number of moving parts.

You can find an initial version at https://github.com/GibbsConsulting/django-plotly-dash

It is very much at a preliminary stage and any and all comments and other feedback is most welcome.

6 Likes

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?