1

I'm have a challenge inserting into the database when I buy shares. I've made life a little complicated (or sophisticated!) because I decided to insert a separate step in the buying process: I start with the "/buy" route and "buy()" method and a simple page that lets the user insert the share symbol and number of shares they want to buy, as below:

buy screenshot Then I've created another route called "/buying" and associated method "buying()", which gives the user a summary of the purchase they want to make, and asks them to confirm the purchase. I just thought it was a better user experience, plus the spec asks you to add at least one personal touch!:

buying screenshot I want to add the data to the database when the user presses the "Confirm Purchase" button (after checking they have sufficient cash).

My challenge is that my code to insert generates the following error, from within the IDE: "Positional argument follows keyword argument" It looks like the IDE is highlighting the part of my SQL insert code where it says id=session["user_id"].

I'm sure I've made this way too complicated - for one thing, I've chosen to store lots of the variables as globals in order to pass them between the two different routes [and I'm sure there's a better way of doing this - I just don't understand Python well enough yet! Any tips here would also be appreciated!]

Relevant parts of my code are below:

First, the buy route and method:

@app.route("/buy", methods=["GET", "POST"])
@login_required
def buy():
"""Buy shares of stock."""

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

    # ensure share symbol was submitted
    if not request.form.get("symbol"):
        return apology("You must submit a share symbol")

    # ensure number of shares to buy was submitted    
    elif not request.form.get("num_shares_buy"):
        return apology ("You must submit a number of shares to buy")

    # retrieve the number of shares to buy, store as a global so can pass to buying() method
    global num_shares_buy
    num_shares_buy = int(request.form.get("num_shares_buy"))

    # ensure number of shares to buy is a positive integer    
    if num_shares_buy < 1:   
        return apology ("You must submit a positive whole number of shares to buy")

    # lookup symbol using lookup() method
    buy = lookup(request.form.get("symbol"))

    # add "num_shares_buy" variable to the "buy" dict
    buy["num_shares_buy"] = num_shares_buy

    # store share price in US dollars in the "buy" dict
    buy["price_usd"] = usd(buy["price"])

    # store price NOT IN US dollars as a global, so can pass to buying() method
    global price
    price = float(buy["price"])

    # store name as a global, so can pass to buying() method
    global name
    name = str(buy["name"])

    # calculate cost of shares to buy (number x price), and add to "buy" dict
    buy["cost"] = num_shares_buy * buy["price"]
    global cost
    cost = float(buy["cost"])

    # store cost of shares to buy as US dollars in the "buy" dict
    buy["cost_usd"] = (usd(buy["cost"]))

    # extract the user's cash balance from finance.db - call this "result"
    # NB, db.execute returns a *list* of *dict* objects, each of which contains some key-value pairs
    # we can be confident the list only contains one dict object, since the ids are unique to each user
    result = db.execute("SELECT cash FROM users WHERE id = :id", id = session["user_id"])
    # now access the dict at position [0] on the list and call this "result1"...
    result1 = result[0]
    # ...and then access the value of the "cash" entry in the dict
    global cash
    cash = float(result1["cash"])
    # ...finally store as US dollars. NB done as a separate step because...
    # ...applying the usd() function results in a string, so a test for > won't work
    cash_usd = usd(cash)

    # store symbol as a global variable in order to pass to SQLite insert statement in buying() method
    global symbol
    symbol = str(request.form.get("symbol"))

    # ensure symbol entered is valid (i.e. returns data when lookup() queries yahoo finance)
    if buy == None:
        return apology("You must submit a valid share symbol")
    # Matt note - seems a bit odd to put this if-statement here, rather than just after defining buy dict object
    # But it needs to be here, otherwise python won't accept the else-statement that follows

    # print the details of the desired share purchase
    else:
        return render_template("buying.html", placeholder = buy, cash_usd = cash_usd)
        # sending to buying.html the dict "buy", plus the variable "cash"

# else if user reached route via GET (as by clicking a link or via redirect)
else:
    return render_template("buy.html")

Then the buying route and method

@app.route("/buying", methods=["GET", "POST"])
@login_required
def buying():
"""Complete Stock Purchase"""

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

    # ensure sufficient cash available to purchase shares
    if cost > cash:
        return apology("You do not have enough cash to make this purchase")

    # insert details of transaction into the "transactions" table in finance.db
    else:
        result = db.execute("INSERT INTO transactions (id, transaction_date, symbol, name, price, number_of_shares, cost) VALUES (:id, :transaction_date, :symbol, :name, :price, :number_of_shares, :cost)", id=session["user_id"], symbol=symbol, name=name, price=price, num_shares_buy, cost=cost)
        # send the user somewhere. In the spec the page to render is the index...
        # ...but I haven't done this yet, so I'm going to return an apology saying "Shares Bought"        
        return apology("Shares Bought")

# else if user reached route via GET (as by clicking a link or via redirect)
else:
    return render_template("buying.html")

Many thanks in advance for your help!

1 Answer 1

1

I vote sophisticated :)

The problem is :transaction_date, or lack thereof.

8
  • Thanks. I left the value for that out as it's set to insert the default value (CURRENT_DATE). I've just done some more web-searching which tells me that I should just leave that field out of the INSERT statement altogether. So I've done that, and the relevant line now reads: result = db.execute("INSERT INTO transactions (id, symbol, name, price, number_of_shares, cost) VALUES (:id, :symbol, :name, :price, :number_of_shares, :cost)", id=session["user_id"], symbol=symbol, name=name, price=price, num_shares_buy, cost=cost) Commented Jun 1, 2017 at 14:36
  • But I still get the same error in the IDE, and the web-server generates the error: "SyntaxError: non-keyword arg after keyword arg" at this line. Now I'm stuck as I thought the keyword args were all at the end (e.g. id=session["user_id"], symbol=symbol, etc. What am I missing? Commented Jun 1, 2017 at 14:37
  • num_shares_buy equals what? Commented Jun 1, 2017 at 15:24
  • I just spotted that! I've now set it so that number_of_shares=num_shares_buy. Now the code seems to run: no syntax errors, and I move forward to the apology page saying "Shares Bought". However, when I look at the SQLite admin page, no rows are added to the database...but you've already answered my original question, so perhaps I should mark this one as answered, and move on. Commented Jun 1, 2017 at 15:37
  • What's PK of transactions? What is result? Perhaps add debugging info something like return apology("Shares Bought result is " + result) Commented Jun 1, 2017 at 15:42

You must log in to answer this question.

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