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:
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!:
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!