Search code examples
jquerypythonajaxflaskmpld3

Fetching form data through query string in flask


This is my first question here so please tell me if I missed out on something ;)

I'm building a website that will run some python code to solve a specific problem and show the results as numbers and a plot (all on the same page). In order to play with the parameters and visualize them before running the python code, I first need to send some form data from html to flask, so I figured I could do this through ajax - as I have done before to plot the results with standard parameters, based on the mpld3 example.

First, the html/form side (look for the comments):

<div class="row">
    <div class="col-md-8">
        <div class="panel panel-primary">
            <div class="panel-heading">
                <h3 class="panel-title">Context Plots</h3>
            </div>
            <div class="panel-body overflow">
                <div calss="loading-div" id="context_loading_div">
                    <div class="loading-tag">
                        <i class="fa fa-cog fa-spin"></i>
                    </div>
                </div>
                <div class="plot" id="context_plot_container"></div>
                <!-- ### plot script here ### -->
                <script src="{{ url_for('static', filename='js/solver_context.js') }}" type="text/javascript">
                </script>
            </div>
        </div>
    </div>

    <div class="col-md-4">
        <div class="panel panel-primary">
            <div class="panel-heading">
                <h3 class="panel-title">Context Parameters</h3>
            </div>
            <div class="panel-body">
                <form action="" method="post" class="form-horizontal">
                    <div class="form-group">
                        <label for="buy_upper" class="col-sm-6 control-label" title="Utility buying price low tariff">Buying high [CHF]</label>
                        <div class="col-sm-6 input-group">
                            <span class="input-group-addon"><i class="fa fa-usd"></i></span>
                            <--! ### form element to fetch data from here ### -->
                            <--! the jinja templating stuff here works fine -->
                            <input type="range" name="buy_upper" class="form-control" id="buy_upper" min="0.05" max="1.0" step="0.01" value={% if reqMeth == 'POST' %}{{ request.form['buy_upper'] }} {% else %}"0.22"{% endif %} />
                        </div>
                        <--! some more form elements -->
                    </div>
                </form>
            </div>
        </div>
    </div>
</div>

Second, the javascript/ajax side:

$(window).load(function() {
    $("#context_plot_container").hide();
    $("#context_loading_div").show();
    var buy_upper = document.getElementById("buy_upper").value;
    var buy_lower = document.getElementById("buy_lower").value;
    var sell_upper = document.getElementById("sell_upper").value;
    var sell_lower = document.getElementById("sell_lower").value;
    var qu = {"buy_upper": buy_upper, "buy_lower": buy_lower, "sell_upper": sell_upper, "sell_lower": sell_lower};
    $.ajax({
        type: "POST",
        async: true,
        contentType: "application/json; charset=utf-8",
        url: "/context_plot",
        data: JSON.stringify(qu),
        success: function (data) {     
            var graph = $("#context_plot_container");
            graph.html(data);
            $("#context_plot_container").show();
            $("#context_loading_div").hide();
        },
        dataType: "html"
    });
});

This script should run after pressing a refresh button, but for now I'm loading it at the same time as the rest of the page. As I'm new to anything that's javascript/jquery/ajax I've more or less just copied the mpld3 example mentioned above.

Lastly, the python/flask side:

## plot routes
@app.route('/context_plot', methods=['GET', 'POST'])
def context_plot():
    import numpy as np
    plt.close(); # (opened above - works fine in the results plot)

    # check params
    # try:
    buy_upper = float(request.args.get('buy_upper'));
    buy_lower = float(request.args.get('buy_lower'));
    sell_upper = -float(request.args.get('sell_upper'));
    sell_lower = -float(request.args.get('sell_lower'));
    # except:
    #     buy_lower = 0.19;
    #     buy_upper = 0.29;
    #     sell_lower = -0.09;
    #     sell_upper = -0.09;

    # data
    up = np.array([buy_lower] * 28 + [buy_upper] * 52 + [buy_lower] * 16);
    urp = np.array([sell_lower] * 28 + [sell_upper] * 52 + [sell_lower] * 16);

    # time vector
    t = np.arange(0, len(up) - 1, 1 / 96.0)[:len(up)];

    # figure
    fig, (ax1, ax2) = plt.subplots(2, 1, figsize = (9, 6), sharex = 'col');
    # subplot 1
    ax1.grid(True, alpha = 0.5);
    ax1.set_ylabel('costs [CHF/kWh]');
    ax1.set_title('Utility profiles');
    ax1.plot(t, up, t, urp, 'k');
    ax1.margins(y = 0.1);
    leg = ax1.legend(['buying', 'selling'], loc = 'upper left', framealpha = 0);
    leg.set_title(''); # workaround for "None" at the bottom left
    # subplot 2
    ax2.grid(True, alpha = 0.5);
    ax2.plot(t, urp);
    ax2.margins(y = 0.1);

    plugins.connect(fig, plugins.MousePosition(fontsize=12));

    return mpld3.fig_to_html(fig); # (works fine in the results plot)

Now when I try to run everything, the figure doesn't show up. And when I look at firefox's network inspection tool ('Response' part) I find Werkzeugs's debugger saying:

File "X:\...\opt.py", line 84, in context_plot
    sell_upper = -1 * request.args.get('sell_upper');
TypeError: unsupported operand type(s) for *: 'int' and 'NoneType'

(From here)

So I'm interpreting that there is no 'sell_upper' in the query string. Then again I look at firefox's network inspection tool ('Params' part) and I see:

{"buy_upper":"0.99","buy_lower":"0.18","sell_upper":"0.27","sell_lower":"0.16"}

I also tried fiddling around with the way the parameters are passed by using quinstead of JSON.stringify(qu) or taking quotes away and so on... and I just don't seem to get it right.

So I guess the problem is that either the query string is still malformed, or the request.args.get() doesn't work as expected, when calling the route through ajax... or something else entirely -.-


Solution

  • You are performing a POST request with AJAX but accessing GET request parameters in Flask (they are located in a query part of an url, which begins with ?). See Request object documentation.

    You should use form attribute instead.

     var = request.form.get('sell_upper')
    

    If the response can be cached and does not actually perform anything but calculations on the server you can also simply use GET request in your AJAX and leave the Python code as it is.