0

Dug into the docs and trawled all the usual online sources but starting to suspect that I'm trying to solve this in a way that's impossible!

This is how I've edited the index page to allow user input to buy/sell shares without leaving the page:

enter image description here

Here's the HTML in my index.html template that renders the top table that I'm trying to make interactive:

        <tbody>
            {% for row in portfolio %}
                <tr>
                    <td>{{ row["name"] }}</td>
                    <td>{{ row["SUM(quantity)"] }}</td>
                    <td>{{ row["price"] }}</td>
                    <td>{{ row["value"] }}</td>
                    <form id={{ row["symbol"] }} action="/" method="post">
                        <td><input class="form-control" name="shares" placeholder="Quantity to trade" type="text"></td>
                        <td><button class="btn btn-primary" type="submit" name="transaction" value="buy">Buy</button></td>
                        <td><button class="btn btn-primary" type="submit" name="transaction" value="sell">Sell</button></td>
                    </form>
                </tr>
            {% endfor %}
        </tbody>
 </table>

Here's the Python from the relevant route of my Flask app:

@app.route("/", methods=["GET", "POST"])
@login_required
def index():
    """Show portfolio of stocks"""

    # Select user cash from users table in database and assign returned list of dict to variable
    cash = db.execute("SELECT cash FROM users WHERE id = :id",
                        id=session["user_id"])

    # Select all records relating to this user from transactions table in database and assign returned list of dict to variable
    portfolio = db.execute("SELECT symbol, SUM(quantity) FROM transactions WHERE user_id = :user_id GROUP BY symbol",
                        user_id=session["user_id"])

    # Initialise rolling total for value of all stocks in portfolio to zero
    stocks_total = 0

    # For each stock in portfolio (list of dicts)
    for i in range(len(portfolio)):

        # Fetch current price for this stock
        current = lookup(portfolio[i]["symbol"])

        # Append current price and calcuated value to the dict for this stock in portfolio
        portfolio[i].update(name = current["name"], price = current["price"], value = (current["price"] * int(portfolio[i]["SUM(quantity)"])))

        # Add value for this stock to rolling total for value of all stocks
        stocks_total += portfolio[i]["value"]

        # Format price and value using dollars function
        portfolio[i]["price"] = usd(portfolio[i]["price"])
        portfolio[i]["value"] = usd(portfolio[i]["value"])

    # Calculate total value of all assets
    assets_total = cash[0]["cash"] + stocks_total

    # User reached route via POST (by submitting a form via POST)
    if request.method == "POST":

        # Ensure valid quantity was submitted
        if int(request.form.get("shares")) <= 0:
            return apology("must provide quantity", 403)

        # FOR DEBUGGING ONLY
        print(request.form("id"))
        print(lookup(request.form("id")))

        # Query API based on and assign result to variable
        price = lookup(request.form("id"))

        # Display error if stock can't be found
        if price == None:
            return apology("stock can't be found - see https://www.investopedia.com/terms/s/stocksymbol.asp", 403)

        # Calculate total value of transaction
        total = price["price"] * int(request.form.get("shares"))

        # if buy button clicked
        if request.form["transaction"] == "buy":

            # Calculate total value of transaction
            total = price["price"] * int(request.form.get("shares"))

            # Query database to check user's balance
            balance = db.execute("SELECT cash FROM users WHERE id = :id",
                                id=session["user_id"])

            # Display apology if user has insufficient funds for requested purchase
            if float(balance[0]['cash']) < total:
                return apology("insufficient funds available", 403)

            # If user has sufficient funds, complete transaction by updating transactions and users tables in database
            inserted = db.execute("INSERT INTO transactions (user_id, symbol, price, quantity) VALUES(:user_id, :symbol, :price, :quantity)",
                                user_id=session["user_id"],
                                symbol=request.form.get("symbol"),
                                price=price["price"],
                                quantity=(request.form.get("shares")))

            updated = db.execute("UPDATE users SET cash = :new_balance WHERE id = :id",
                                new_balance=(float(balance[0]['cash']) - total),
                                id=session["user_id"])

            # Display result of lookup to user
            return render_template("bought.html", shares=request.form.get("shares"), name=price["name"], total=usd(total))

        # if sell
        elif request.form["transaction"] == "sell":

            # Query database to check that stocks to be sold exist
            available = db.execute("SELECT SUM(quantity) FROM transactions WHERE user_id = :user_id AND symbol = :symbol",
                            user_id=session["user_id"], symbol=request.form.get("symbol"))

            # Display apology if user has insufficient shares for requested sale
            if float(available[0]['SUM(quantity)']) < int(request.form.get("shares")):
                return apology("insufficient shares available", 403)

            # Select user cash from users table in database and assign returned list of dict to variable
            cash = db.execute("SELECT cash FROM users WHERE id = :id",
                            id=session["user_id"])

            # Calculate new balance
            new_balance = cash[0]['cash'] + total

            # If user has sufficient shares, complete transaction by updating transactions and users tables in database
            inserted = db.execute("INSERT INTO transactions (user_id, symbol, price, quantity) VALUES(:user_id, :symbol, :price, :quantity)",
                                user_id=session["user_id"],
                                symbol=request.form.get("symbol"),
                                price=price["price"],
                                quantity=(-(float((request.form.get("shares"))))))

            updated = db.execute("UPDATE users SET cash = :new_balance WHERE id = :id",
                                new_balance=new_balance,
                                id=session["user_id"])

            # Display result of lookup to user
            return render_template("sold.html", shares=request.form.get("shares"), price=price, total=total)

    # User reached route via GET (by clicking a link or via redirect)
    else:

        # Pass user data from transactions data to render template
        return render_template("index.html", portfolio=portfolio, cash=usd(cash[0]["cash"]), assets=usd(assets_total))

I'm trying to pull through the stock symbol from the row of the table that the user would interact with to initiate a transaction. The stock symbol isn't displayed in the table, but I've tried embedding it as an id in various HTML elements of the table in my index.html Jinja template. Currently it's embedded in the <form> tag as id={{ row["symbol"] }}. I've tested that this produces ids that accurately reflect the relevant stock symbols, but I can't figure out whether it's possible to pull these through into the POST section of my route so that the symbol can be used to call the API to complete the transaction.

Tracebacks helped pinpoint the problem, so I've tried printing return value for debugging:

# FOR DEBUGGING ONLY
print(request.form("id"))
print(lookup(request.form("id")))

# Query API based on and assign result to variable
price = lookup(request.form("id"))

Here's the most recent traceback for reference:

enter image description here

These are the articles I've read that seem to come closest to describing the issue I'm facing:

https://stackoverflow.com/questions/19794695/flask-python-buttons

https://stackoverflow.com/questions/32022568/get-value-of-a-form-input-by-id-python-flask

https://stackoverflow.com/questions/10434599/get-the-data-received-in-a-flask-request

https://stackoverflow.com/questions/77552/id-is-a-bad-variable-name-in-python

Any help hugely appreciated.

Thanks!

1 Answer 1

1

The proximate cause of the error is id is not submitted with the form. There may be an easier way that would eliminate the code that is presumably duplicated between index and buy/sell.

The button element has an attribute formaction that will override the action attribute of the form element.

3
  • Thanks Dino! All that duplicate code felt flabby, so thanks for confirming that there's a better way! I've added formaction attributes to my buttons so that the user is sent to the /buy or /sell route as appropriate, but I'm still struggling to understand how I can meet this requirements of the brief - "Allow users to buy more shares or sell shares of stocks they already own via index itself, without having to type stocks’ symbols manually". Guess I can pass user input from my Quantity to trade field in index but specification for that page doesn't include symbol data. Commented Nov 6, 2020 at 9:57
  • change the name and value attributes of the buttons to send the necessary data to the server. Commented Nov 6, 2020 at 11:12
  • Thanks again Dino! Seem to have got this working thanks to your help and successfully submitted, although I don't think it's the most elegant solution at the moment! Reckon I could factor out input validation into a standalone function and make a couple of UX changes, including flipping the sign of sold shares in /history (which are currently displayed as negative integers). Anyway, realise these are questions for another post, another day. Excited to be moving on the final project now and just wanted to say how grateful I am for your prompt and constructive support. All the best for now! Commented Nov 10, 2020 at 10:10

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .