CSS not being served by dash

I’m having trouble getting my CSS to work. I’ve got the following code:

external_stylesheets = [
    'https://codepen.io/chriddyp/pen/bWLwgP.css'
]

app = Dash(
    __name__,
    server=server,
    url_base_pathname=URL_BASE_PATHNAME,
    assets_folder = 'assets',
    external_stylesheets=external_stylesheets
)

With another .css sheet in my assets folder. The code doesn’t seem to be picking up the .css file in the assests folder, only the external stylesheet. I’m running my app through a django server and the only work around I can get is by putting my css online and then using it as an external stylesheet - which is a bit annoying if I want to try out quick changes.

Anything I can try? I’ve updated to the latest Dash (version 0.40.0)
Thanks in advance!

Can you elaborate on what you mean by you’re running your app through a django server?

I would try your app running on a plain Flask server first and see if you have the same problem. If not, then it’s probably something to do with your Django setup.

I’m running dash via the method outlined here: Django and Dash - ead's Method
So I’ve got a django site that is also running a dash app.

Running it on a plain Flask server did work. So it must be something with my django setup. I’m not really sure what to try from here, if it can’t be done then I’ll live with it but it seems I might be missing some trick to make dash look for the assets folder.

You may need to change the value of the name parameter that is provided to the Dash app. This parameter is responsible for finding the location of the assets folder. You can test whats coming up with by putting this in your module with the Dash instance and running it how you run with Django:

from flask.helpers import get_root_path
print(get_root_path(__name__))

The Flask docs has a section on the name parameter, pointing out that you may need to modify/hardcode it’s value when running within a package.

I ran that little bit of code and got the directory I expected (’/home/dfinch/ukatmosphere/dataplot’ or ‘/app/dataplot’ when running through my website) - my css file is in /app/dataplot/assets/dataplot.css

I’ve done a bit of fiddling about to try and get things work and have come up with an error that on investigation seems a bit odd:

Internal Server Error: /dataplot/assets/dataplot.css
Traceback (most recent call last):
  File "/home/dfinch/ukatmosphere/lib/python3.6/site-packages/django/core/handlers/exception.py", line 41, in inner
    response = get_response(request)
  File "/home/dfinch/ukatmosphere/lib/python3.6/site-packages/django/core/handlers/base.py", line 187, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/home/dfinch/ukatmosphere/lib/python3.6/site-packages/django/core/handlers/base.py", line 185, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/dfinch/ukatmosphere/dataplot/views.py", line 43, in dash
    return HttpResponse(dispatcher(request))
  File "/home/dfinch/ukatmosphere/dataplot/views.py", line 32, in dispatcher
    return response.get_data()
  File "/home/dfinch/ukatmosphere/lib/python3.6/site-packages/werkzeug/wrappers.py", line 986, in get_data
    self._ensure_sequence()
  File "/home/dfinch/ukatmosphere/lib/python3.6/site-packages/werkzeug/wrappers.py", line 1035, in _ensure_sequence
    raise RuntimeError('Attempted implicit sequence conversion '
RuntimeError: Attempted implicit sequence conversion but the response object is in direct passthrough mode.

I’m not sure why I’m suddenly getting this error but judging by the first line it seems to be now at least seeing the dataplot.css file.
Googling the error isn’t really making it any clearer…

That’s strange, as far as I can tell, it looks like this issue is meant to be fixed by Flask-Compress having set response.direct_passthrough = False

(Flask-Compress (which Dash uses) is a fork of Flask-gzip.)

Perhaps try disabling gzip compression with Dash(compress=False). If that works then you could use gzip compression from your webserver instead (which is better to do in production anyway, since doing compression at the Python application level is always going to be slower)

Wait, scratch that. Since the request is being created manually, I think you need to add the flag corresponding to that above fix I mentioned yourself.

response.direct_passthrough = False

Within the dispatcher method that creates the responses.

Thats got rid of the error but unfortunately the original problem still exists - its not loading the CSS in.

Is there a way of printing out what css dash is seeing? As in something along the lines of print(app.css) ?

The next thing to do is open up the Dev tools in the browser (F12 in Chrome and Firefox) and switch to the Network tab and do a page refresh. Do you see a request for the css file? If so, what is the http response?

alternatively you can inspect the DOM and see if the CSS file is included in the . If it’s not included, then it’s a Dash/Flask config issue. If it’s included and the request is is failing, then it might be a web server config issue.

I checked the DOM and it is being included but it seems to have changed the name of the css file by appending a random string of numbers and digits to the end - so it now looks like this:
<link rel="stylesheet" href="/dataplot/assets/dataplot.css?m=1553618097.0">

Is this expected? I’ve followed the link of the href and it the css has been all condensed onto one line (including all the comments) and it looks a bit of mess to read. can the browser still interpret this?

Yeah that’s expected, that junk at the end of added automatically in order to bust the browser’s cache for when you make changes to the file.

Could you post the http response headers for the file request?

I’m a little unsure what those are (still learning the ropes). Is that this?

HTTP/1.0 200 OK
Date: Wed, 27 Mar 2019 11:54:59 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 9072

This is from the dev tools network tab under the response headers when the css file is selected

Yep that’s it! And that provided the relevant clue. The CSS was being served as the text/html mimetype, which means the browser doesn’t know to treat it as CSS.

This appears to be a limitation of the embedding in django method, at least as far as that minimal example that I had posted goes. The problem is that handling of content type was kind of being done manually, essentially being determined in viz/urls.py via the different routes (either it was a JSON request because it was going to a Dash endpoint, or it was left to default to text/html for the index.html queries). A pretty brittle solution.

I don’t know how Flask does this normally, but I’ve updated my recipe to make urls.py guess the mimetype for all files being served in the assets folder using the Python mimetypes module in viz/views.py.

This is still a pretty brittle and hacky solution which I’d only recommend using for development. If you’re doing anything at all production-like, you really should be serving your static assets using a proper webserver anyway, not passing them through Django/Flask as this is always going to be a dramatic performance hit. All you would need to do is tell the webserver that the assets route prefix should be mapped to the assets directory, and then your Django app will never receive those requests.

1 Like

It works! Thank you very much for all the help. Its only a prototype website at the moment so the performance hit isn’t such a big issue at the moment.
I didn’t work for a while but then I forgot that my urls in urls.py were in the wrong order, so I had make sure the dash_guess_mimetype was before the dash_index call - otherwise it never got called.
Hopefully when I get everything working I’ll put a link on the show and tell thread.

Awesome!

My pleasure. People seem to be using that Django recipe to some extent, so I’m glad we were able to work out the problem so that I could update the recipe. Serving static assets even just in dev is a fairly common use case. I’ll make a note over on that post about the changes.