4

I'm trying to limit selections in a dropdown based on a prior selection the user has made. This is how my flask looks:

init.py

@app.route('/create/', methods=['GET','POST'])
def create():
    mySQL2 = SelectCustomer(session['ID']) #displayed invoicereceiver
    global sessioncur
    try:
        form = CreateinvoiceForm(request.form)
        if request.method == 'POST' and form.validate():
            #HEADER
            #This fetches from HTML
            customer = request.form.get('customer')
            goodsrec = request.form.get('goodsrec')
    return render_template("createinvoice.html", form=form,  mySQL2 = mySQL2)

customer is populated from an html form using mySQL2 as possible variables to select from:

html select form

<select required name="customer" class="selectpicker form-control" , 
placeholder="Select">
<option selected="selected"></option>
{% for o in mySQL2 %}      
<option value="{{ o[2] }}">{{ o[2] }}</option>  
{% endfor %}
</select>

The selection for goodsrec has to dependend on which customer was selected. My idea was to obtain the customer ID doing as follows:

c, conn = connection()
customerID = c.execute("SELECT Cm_Id FROM customer WHERE Cm_name ='" +
str(customer) +"' limit 1")
customerID = c.fetchone()[0]

This value I could then use in a function I have to obtain the goodsreceivers with that ID:

def SelectGoodsrecSEE(customerID):
    c,conn = connection()
    c.execute("SELECT * FROM goodsrec WHERE Gr_Cm_id=" +str(id))
    mySQL8 = c.fetchall()
    c.close()
    conn.close()
    gc.collect()
    return mySQL8

So far I am quite sure that would work. What I don't know is how to structure the flask to make it load the first selection and take it into account for the second one. Similiar to the html I would have to loop through mySQL8. But how does the strucutre look in flask to get that done? At the moment what I have looks like

@app.route('/create/', methods=['GET','POST'])
def create():
    mySQL2 = SelectCustomer(session['ID']) #displayed invoicereceiver
    global sessioncur
    try:
    form = CreateinvoiceForm(request.form)
    if request.method == 'POST' and form.validate():
        #HEADER
        #This fetches from HTML
        customer = request.form.get('customer')
        c, conn = connection()
        customerID = c.execute("SELECT Cm_Id FROM customer WHERE Cm_name ='" +
        str(customer) +"' limit 1")
        customerID = c.fetchone()[0]
        mySQL8 = SelectGoodsrecSEE(customerID)
        goodsrec = request.form.get('goodsrec')
    return render_template("create.html", form=form,  mySQL2 = mySQL2)

I need to be able to pass mySQL8 to the create.html, so that I can make the selection from it in html. Any ideas? Hope its more or less clear what I'm looking for..

EDIT

SELECT * FROM goodsrec WHERE Gr_Cm_id=18; mySQL8

Ignacio Vergara Kausel
  • 5,521
  • 4
  • 31
  • 41
Mark Wellings
  • 71
  • 1
  • 9
  • I wrote a [**post**](http://stackoverflow.com/questions/41232105/populate-wtforms-select-field-using-value-selected-from-previous-field/41246506#41246506) on this subject not too long ago that describes the logic to implement cascading selects with AJAX/XHR using the jQuery [**$.getJSON()**](http://api.jquery.com/jquery.getjson/) method. You add a second endpoint in your Flask application that takes in `customerID` and returns `jsonify(mySQL8)` and then call it whenever the user makes a selection on customer. – abigperson Mar 19 '17 at 14:58
  • So you suggest I do the selection fields in flask? At the moment I've got them in html.. thanks for the comment!! – Mark Wellings Mar 19 '17 at 23:11
  • Definitely depends. If it's a CRUD style app with lots of forms then using WTForms will make your life much easier. If it's a couple of simple selects that control some other features of your site it might be overkill. Either way the overarching logic around using AJAX to keep a second select box pegged to the value of the first is the same. – abigperson Mar 19 '17 at 23:22
  • I've tried to use your solution, but can't get it to work. Is there a more simple way of storing the variable select in the first dropdown and then use that to select the respective select options? I thought maybe I could use a flask definition inside the jinja template.. – Mark Wellings Mar 20 '17 at 11:37

1 Answers1

5

SQL Injection Risk

First and foremost you should improve your SQL code because as you have it now you are vulnerable to SQL Injection attacks. So, instead of:

c.execute("SELECT Cm_Id FROM customer WHERE Cm_name ='" + str(customer) + "' limit 1")

the recommended usage is:

sql = 'SELECT Cm_Id FROM customer WHERE Cm_name = %s LIMIT 1'
parameters = [str(customer)]
c.execute(sql, parameters)

a couple additional SO posts that discuss this issue:

Implementing Cascading Selects

Python:

@app.route('/create/', methods=['GET','POST'])
def create():
    mySQL2 = SelectCustomer(session['ID'])
    global sessioncur
    try:
        form = CreateinvoiceForm(request.form)
        if request.method == 'POST' and form.validate():
            customer = request.form.get('customer')
            goodsrec = request.form.get('goodsrec')
            # do stuff with submitted form...
    return render_template("createinvoice.html", form=form,  mySQL2 = mySQL2)


@app.route('/get_goods_receivers/')
def get_goods_receivers():
    customer = request.args.get('customer')
    print(customer)
    if customer:
        c = connection()
        customerID = c.execute("SELECT Cm_Id FROM customer WHERE Cm_name = %s LIMIT 1", [customer])
        customerID = c.fetchone()[0]
        print customerID
        c.execute("SELECT * FROM goodsrec WHERE Gr_Cm_id = %s", [customerID])
        mySQL8 = c.fetchall()
        c.close()
        # x[0] here is Gr_id (for application use)
        # x[3] here is the Gr_name field (for user display)
        data = [{"id": x[0], "name": x[3]} for x in mySQL8]
        print(data)
    return jsonify(data)

HTML/Javascript:

<select name="customer" id="select_customer" class="selectpicker form-control">
    <option selected="selected"></option>
    {% for o in mySQL2 %}
    <option value="{{ o[2] }}">{{ o[2] }}</option>
    {% endfor %}
</select>

<select name="goodsrec" id="select_goodsrec" class="selectpicker form-control" disabled>
    <option>Select a Customer...</option>
</select>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script charset="utf-8" type="text/javascript">
    $(function() {
        var dropdown = {
            customer: $('#select_customer'),
            goodsrec: $('#select_goodsrec')
        };

        // function to call XHR and update goodsrec dropdown
        function updateGoodsrec() {
            var customer = dropdown.customer.val();
            dropdown.goodsrec.attr('disabled', 'disabled');
            console.log(customer);

            if (customer.length) {
                dropdown.goodsrec.empty();
                $.getJSON("{{ url_for('get_goods_receivers') }}", {customer: customer}, function(data) {
                    console.log(data);
                    data.forEach(function(item) {
                        dropdown.goodsrec.append(
                            $('<option>', {
                                value: item.id,
                                text: item.name
                            })
                        );
                    });
                    dropdown.goodsrec.removeAttr('disabled');
                });
            }
        }

        // event listener to customer dropdown change
        dropdown.customer.on('change', function() {
            updateGoodsrec();
        });

    });
</script>
Community
  • 1
  • 1
abigperson
  • 5,252
  • 3
  • 22
  • 25
  • I was using thwart to prevent SQL injections, thanks for the advice. I'll review my SQL commands. Unfortunately, when I select a *customer* the dropwdown for *goodsreceiver* remains disabled. I've put the javascript inside and outside the form in html. For simplicity I changed the definition to `data = mySQL8` bear with me :-) – Mark Wellings Mar 20 '17 at 15:59
  • Sorry just noticed it's necessary to jsonify.. still the goodsrec dropdown is not refreshed.. the sql queries should be fine.. – Mark Wellings Mar 20 '17 at 16:35
  • can you show me what `mySQL8` raw data looks like. SQL result sets don't always serialize to JSON nicely which is why I was using the list comprehension on the results. – abigperson Mar 20 '17 at 16:41
  • i made a few updates and added some `print` and `console.log` statements that i would use to confirm everything is working correctly. – abigperson Mar 20 '17 at 19:01
  • Where can I check the logs? It's still not working the *goodsrec* dropdown is disabled. The query for customerID was not working. I've changed it to str(customer) which did the trick. No result though.. :-/ My understanding is that I dont have to edit the *goodsrec* select part as that should be done with JS, correct? – Mark Wellings Mar 20 '17 at 19:14
  • if you're running the flask dev server the `print` statements should show up in the terminal window where it is running. `console.log()` in the javascript console of your web browser (they are all a little different) – abigperson Mar 20 '17 at 19:16
  • I've done this: `from __future__ import print_function` and added: `def eprint(*args, **kwargs): print(*args, file=sys.stderr, **kwargs)` when I run the init.py it gives the this error: *-bash: syntax error near unexpected token `print' root@myinv:/var/www/FlaskApp/FlaskApp# python __init__.py File "__init__.py", line 663 print customer* – Mark Wellings Mar 20 '17 at 19:27
  • ok, so you'll probably want to modify my print statement to `print(customer)` and `print(data)` – abigperson Mar 20 '17 at 20:30
  • Can't get the print to work nor the dropdown. I've executed the init.py but it doesn't print when I use the webpage. Sorry dude feel like I'm wasting your time – Mark Wellings Mar 20 '17 at 21:39
  • must be something about your environment or system. i've implemented more complex select behavior many times over, these are all snippets i use frequently. more importantly the print statement is **critical** to being able to debug effectively. not sure what i'd do without it. i'd get that sorted out ASAP. – abigperson Mar 20 '17 at 21:50
  • So when you execute your flask it prints in the console? – Mark Wellings Mar 20 '17 at 21:56
  • when i'm doing development work absolutely. helps to make sure request parameters are showing up as expected, queries producing the results you expect, etc... helps tremendously to find the route cause of errors/bugs quickly versus trying to guess what is wrong. i remove print statements in production and replace some of it with logging. – abigperson Mar 20 '17 at 22:00
  • As you might have noticed I'm very new to this.. the goal should be to run my init.py in console and then it should print once I am doing stuff on my page.. ? – Mark Wellings Mar 20 '17 at 22:05
  • Alright I just found out that, as I'm using apache the print or error go to error.log.. This is what I see: *AttributeError: 'tuple' object has no attribute 'execute',* – Mark Wellings Mar 20 '17 at 22:28
  • Wow, it works.. there was a small mistake which I fixed now: *c, conn = connection()* THANKS SO MUCH PJ FOR YOUR HELP!!!! GREAT WORK!! – Mark Wellings Mar 20 '17 at 22:36
  • OK, so most people would do development work on their local machine. So you can very easily run the [**Flask development server**](http://flask.pocoo.org/docs/0.12/server/) right from command line and view your app in your browser at `localhost:5000` You are welcome, happy to help! – abigperson Mar 20 '17 at 22:43
  • One last question: The select goodsrec is not stored, so atm I can't submit the form. How do I tackle that issue? It would have to say value in my select in html, right? What I'm trying to say, how to I submit the actual selected value? – Mark Wellings Mar 20 '17 at 22:46
  • *'NoneType' object has no attribute '__getitem__'* I guess that happends because goodsrec is empty – Mark Wellings Mar 20 '17 at 22:57
  • i'm not really sure what you're saying that it's not "stored". jquery (javascript) is modifying the html page (DOM) in real time so if the select is part of a valid form that submits everything should work just fine. if you're having another problem you can always [Ask a New Question](http://stackoverflow.com/questions/ask) rather than dragging out the comments here. (already quite a long discussion here!) – abigperson Mar 20 '17 at 23:05
  • Sorry for that, this is my last post. Just to clarify how it was fixed: *value: item.name, text: item.name* – Mark Wellings Mar 20 '17 at 23:08