0

I'm creating a configurator app for heating systems. Essentially the idea is by putting in a few inputs - the app will spit out the part number for a system pack where a user can see a detailed bill of material (BOM).

One key element is the output where we need to show a few options. I.e. if someone needs a 200kW system, there could be 3-4 packs that are suitable (190kW -210kW might be more cost effective).

I want in the first instance to show on a route the pack options that are suitable- then the user selects the pack they want- which takes you to a route (/cart) which shows the BOM.

I have put in input variables min and max which searches a database cascades.db. This successfully shows me the table of options.

from cs50 import SQL
from flask import Flask, render_template, request, url_for, redirect

app = Flask(__name__)

db = SQL("sqlite:///cascades.db")

@app.route("/")
def index():
    return render_template("index.html")
    
@app.route("/search", methods=["GET", "POST"])
def search():
    output = request.form.get("output")
    hydraulic = request.form.get("hydraulic")
    layout = request.form.get("layout")
    controls = request.form.get("controls")

    min_output = int(output) - 15
    max_output = int(output) + 15
    
    cascades = db.execute("SELECT * FROM cascades WHERE hydraulic = ? AND layout = ? AND output BETWEEN ? and ?", hydraulic, layout, min_output, max_output)
    
    return render_template("search.html", cascades=cascades)
    
@app.route("/cart", methods=["GET", "POST"])
def cart():
    bom_id = request.form.get("bom_id")
    bom = db.execute("SELECT * FROM bom WHERE bom_id = ?", bom_id)
    return render_template("bom.html",  bom = bom)

When running the app- the first bit works - i.e. it shows me a list of all the packs that meet the criteria- but when clicking the 'Choose' button Im getting stuck.

{% extends "layout.html" %}

{% block body %}
<h3> Boiler Cascade Pack Options:</h3>

<table class="table table-striped table-boardered">
    <tr>
      <th>Part Number</th>
      <th>Description</th>
      <th>Number of boilers</th>
      <th>BOM</th>
    </tr>
    {% for cascades in cascades %}
    <tr>
      <td scope="cascades">{{ cascades["id"] }}</td>
      <td>{{ cascades["description"] }}</td>
      <td>{{ cascades["number_of_boilers"] }}</td>
      <td>
        <form action "/cart" method="post">
          <input name="bom_id" type="hidden" value="{{ cascades["id"] }}">
          <input class="btn btn-primary" type="submit" value="Choose">
        </form>
      </td>
    </tr>
   {% endfor %}
</table>
{% endblock %}

But when submitting the form- where they select the pack (which has the pack id number- as a hidden form) I get the following error:

File "/home/ubuntu/cascade2/application.py", line 26, in search
min_output = int(output) - 15 TypeError: int() argument must be a
string, a bytes-like object or a number, not 'NoneType'

It seems like the route is trying to use the same logic in the second search but at this point its redundant. The second search all I want is to show me information where the Pack id = BOM.

I've noticed the URL stays on the (/search) route with this and not going to the (/cart).

I've tried several things such as putting in a IF NOT output- redirect to Cart to try and bi pass this- which successfully loads the bill of material page but nothing comes up as I don't think its posted the id to the route.

I've also changed it to GET instead of POST, which results in the query string /search?bom_id=71723827132. Which shows its picking up the part number- but staying on the /search route where I see the error.

<h3> Boiler Cascade Pack Options:</33>

<table class="table table-striped table-boardered">
    <tr>
      <th>Part Number</th>
      <th>Description</th>
      <th>Number of boilers</th>
    </tr>
    {% for cascade in cascade %}
    <tr>
      <td scope="cascade">{{ cascades["id"] }}</td>
      <td>{{ cascades["description"] }}</td>
      <td>{{ cascades["number_of_boilers"] }}</td>
    </tr>
   {% endfor %}
</table>
 
<br>
<h4>Bill of material:</h4>
<br>

<table class="table table-striped table-boardered">
    <tr>
      <th>Product ID</th>
      <th>Product Description</th>
      <th>Quantity</th>
    </tr>
    {% for bom in bom %}
    <tr>
      <td scope="bom">{{ bom["part_number"] }}</td>
      <td>{{ bom["product_description"] }}</td>
      <td>{{ bom["quantity"] }}</td>
    </tr>
   {% endfor %}
</table>

Been stuck on this for a month. This to me is the last piece of the puzzle. Any help/suggestions would be great. I'm new to programming so I bet I've missed something obvious :)

Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • Bill of material – Michael Berry Jan 09 '22 at 12:51
  • Your `
    ` is missing an `=` after `action`, that's why it stays at `/search`
    – Tomalak Jan 09 '22 at 13:13
  • On a different note: Use `POST` forms for activities that change the server state (e.g. adding an item to a shopping cart), and `GET` forms for activities that don't (e.g. searching with parameters). This way the user can press F5 on a search page and they won't get the "confirm form re-submission" popup from their browsers. With that in mind, your `"/search"` route should be `GET` exclusively. – Tomalak Jan 09 '22 at 13:18
  • Third remark: Parameters ("form fields") from `POST` requests will end up in `request.form` on the server side. Parameters from `GET` requests will end up in `request.args`. – Tomalak Jan 09 '22 at 13:33
  • BY jove it worked!!!!! – Michael Berry Jan 09 '22 at 14:06
  • @Tomalak- I am blown away- first post on stack overflow. Is there somewhere I can leave you a positive review or something- you my friend are a legend! – Michael Berry Jan 09 '22 at 14:07

1 Answers1

0

Your <form action "/cart" method="post"> is missing an = after action. That means the action attribute is not properly defined, and the default for action always is the URL you're currently on. That's why it stays at /search.

Tomalak
  • 332,285
  • 67
  • 532
  • 628