pallets-eco / flask-debugtoolbar

A toolbar overlay for debugging Flask applications
https://flask-debugtoolbar.readthedocs.io
BSD 3-Clause "New" or "Revised" License
945 stars 143 forks source link

Add option to enable the toolbar to display on JSON responses? #89

Open jeffwidman opened 8 years ago

jeffwidman commented 8 years ago

I am debugging a Flask app that serves up JSON for an API endpoint.

I'm viewing the JSON in the browser, and it'd be convenient to display the toolbar as well.

My understanding of content types is a little hazy... is it possible to add an option to display the toolbar alongside this JSON? Like perhaps a config setting that lets me turn on the toolbar for all responses, not just with <! doctype html>?

sumoward commented 8 years ago

Jef did you have any luck getting this done?

jeffwidman commented 8 years ago

No. The way to do it I think, so you don't have to get funky with adding a HTML wrapper around the JSON is detect the content type and have the toolbar append its own bit of JSON with the sqlalchemy queries + page timing info, but thats still a pain methinks compared to just manually setting sqlalchemy to echo queries to stdout and watching your logs as you hit various endpoints.

sumoward commented 8 years ago

Thanks Jeff.

sarimak commented 8 years ago

Workaround:

@app.after_request
def after_request(response):
    # Wrap JSON responses by HTML and ask flask-debugtoolbar to inject own HTML into it
    if app.config.get("DEBUG_TB_ENABLED", False) and response.mimetype == "application/json" and response.status_code != 401:
        response.direct_passthrough = False  # Work-around for profiling of streamed JSON data download
        args = dict(response=response.data.decode("utf-8"), http_code=response.status)
        html_wrapped_response = make_response(render_template("wrap_json.html", **args), response.status_code)
        response = app.extensions["debugtoolbar"].process_response(html_wrapped_response)

    return response

and

<html>
    <head>
        <title>JSON Response</title>
    </head>

    <body>
        <h1>HTML Wrapper for JSON Response</h1>

        <h2>HTTP Code</h2>
        <pre>{{ http_code }}</pre>

        <h2>JSON Response</h2>
        <pre>{{ response }}</pre>
    </body>
</html>
asldevi commented 6 years ago

I also had to init the FDT like the following to make the above snippet work instead of just DebugToolbarExtension(app)

    app.extensions = getattr(app, 'extensions', {})
    app.extensions['debugtoolbar'] = DebugToolbarExtension(app)
guyecode commented 6 years ago

@sarimak I add code exactly as yours, the toolbar show up on the page, but nothing pop up when click the menus. I compared all the requests to the example in the source, there are two toolbar.css requests send to server, the rest requests are totally same.

sarimak commented 6 years ago

@guyecode You're right, FDT has changed since I posted the workaround. I can reproduce the issue (FDT for JSON response is displayed but ignores click events -- while it works fine for HTML response) with the following code:

import flask
import flask_debugtoolbar

app = flask.Flask('test', template_folder='.')
app.config['DEBUG_TB_ENABLED'] = True
app.config['SECRET_KEY'] = '123'
toolbar = flask_debugtoolbar.DebugToolbarExtension(app)

@app.after_request
def after_request(response):
    if response.mimetype == "application/json":
        html_wrapped_response = flask.make_response(flask.render_template("wrap_json.html", response=response.data.decode("utf-8"), http_code=response.status), response.status_code)
        return toolbar.process_response(html_wrapped_response)

    return response

@app.route('/json')
def test_json():
    return flask.jsonify(dict(key1='value1')), 200

@app.route('/html')
def test_html():
    return '<head/><body>test</body>', 200

if __name__ == '__main__':
    app.run(debug=True)

Unfortunately, I am not skilled enough in JavaScript to troubleshoot the issue.

RealLiuSha commented 6 years ago

@sarimak

modify wrap_json.html to:

<html>
    <head>
        <title>JSON Response</title>
    </head>

    <body>
        <h1>HTML Wrapper for JSON Response</h1>

        <h2>HTTP Code</h2>
        <pre>{{ http_code }}</pre>

        <h2>JSON Response</h2>
        <pre>{{ response }}</pre>
    </body>

<script>
$('#flDebugPanelList').find('li a').click(function () {
    const current = $('#flDebug #' + this.className + '-content');
    if (current.is(':visible')) {
      $(document).trigger('close.flDebug');
      $(this).parent().removeClass('active');
    } else {
      $('.panelContent').hide();
      current.show();

      $('#flDebugToolbar').find('li').removeClass('active');
      $(this).parent().addClass('active');
    }
  });
</script>
</html>

image

KyleJamesWalker commented 5 years ago

I was also having issues with the click events not working with If you create the after_request before the toolbar extension it will see the html mimetype and work without needing future changes. Here's an extension I wrote for this in my app.

"""Flask Extensions"""
import flask
import flask_debugtoolbar

class DevToolbar:
    """Add debug toolbars with json to html

    Note you must pass `_debug` param to convert the json response
    """
    def __init__(self, app):
        wrap_json = """
        <html>
            <head>
                <title>Debugging JSON Response</title>
            </head>

            <body>
                <h1>Wrapped JSON Response</h1>

                <h2>HTTP Code</h2>
                <pre>{{ http_code }}</pre>

                <h2>JSON Response</h2>
                <pre>{{ response }}</pre>
            </body>
        </html>
        """

        @app.after_request
        def after_request(response):
            if response.mimetype == "application/json" and \
                '_debug' in flask.request.args:
                html_wrapped_response = flask.make_response(
                    flask.render_template_string(
                        wrap_json,
                        response=response.data.decode("utf-8"),
                        http_code=response.status
                    ),
                    response.status_code,
                )
                return app.process_response(html_wrapped_response)

            return response

        flask_debugtoolbar.DebugToolbarExtension(app)