· 5 years ago · Jul 03, 2020, 02:56 AM
1import os
2
3# import modules that we want to use
4from cs50 import SQL
5from flask import Flask, flash, jsonify, redirect, render_template, request, session
6from flask_session import Session
7from tempfile import mkdtemp
8from werkzeug.exceptions import default_exceptions, HTTPException, InternalServerError
9from werkzeug.security import check_password_hash, generate_password_hash
10
11from helpers import apology, login_required, lookup, usd
12
13# Configure application
14app = Flask(__name__)
15# ensure that user sessions when they are logged in are not permanent
16app.config["SESSION_PERMANENT"] = False
17# ensure the location that we want to store the data for user sessions is going to be in the file system of the webserver we'll be running this application from (CS50 IDE)
18app.config["SESSION_TYPE"] = "filesystem"
19# we would like to enable sessions for this particular flask web app
20Session(app)
21
22# Ensure templates are auto-reloaded
23app.config["TEMPLATES_AUTO_RELOAD"] = True
24
25# Ensure responses aren't cached
26@app.after_request
27def after_request(response):
28 response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
29 response.headers["Expires"] = 0
30 response.headers["Pragma"] = "no-cache"
31 return response
32
33# Custom filter
34app.jinja_env.filters["usd"] = usd
35
36# Configure session to use filesystem (instead of signed cookies)
37app.config["SESSION_FILE_DIR"] = mkdtemp()
38app.config["SESSION_PERMANENT"] = False
39app.config["SESSION_TYPE"] = "filesystem"
40Session(app)
41
42# Configure CS50 Library to use SQLite database
43db = SQL("sqlite:///finance.db")
44
45# Make sure API key is set
46if not os.environ.get("API_KEY"):
47 raise RuntimeError("API_KEY not set")
48
49
50@app.route("/")
51@login_required
52def index():
53 """Show portfolio of stocks"""
54 # Extract the current logged in users ID from the session["user_id"] dict & store it inside of "user_id" to use below
55 user_id = session["user_id"]
56
57 # Query db for the current "user_id" who's currently logged into this session. Then extract the "cash" column from this users row inside of the users table, this is returned as a list of dicts
58 cash_amount_dict = db.execute("SELECT cash FROM users WHERE id = :userid;",
59 userid=user_id)
60
61 # Select everything from the "portfolio" table & store it in a list of dicts, "owned_stocks"
62 owned_stock = db.execute("SELECT * FROM portfolio WHERE id = :userid;",
63 userid=user_id)
64
65 # Extract the "cash" amount from this dict, thats inside of the first element of a list [0] of dicts, & then, the "cash" column from inside that dict: ["cash"]
66 cash_amount = cash_amount_dict[0]["cash"]
67
68 # set the added stock value to 0 initally
69 added_total_stock_value = 0
70
71 # loop through each index of the list containing dicts in owned_stocks
72 for dictionary in owned_stock:
73
74 # extract the amount of the stock in this dict the user currently owns
75 stock_amount = dictionary["amount"]
76 # run the lookup() function against the "symbol" in this current dict being looped through
77 stock_dict = lookup(dictionary["symbol"])
78 # extract the "price" out of the returned dictionary from lookup() for this current stock symbol
79 stock_company_name = stock_dict["name"]
80 stock_price = stock_dict["price"]
81 stock_symbol = stock_dict["symbol"]
82 # take the "price" value of this current stock multipled by the number of shares of this stock this user owns, to show the total value of each holding. store this in a new variable "total_value"
83 total_stock_value = stock_price * stock_amount
84 added_total_stock_value += total_stock_value
85 # inject this new "total_value" of this holding back into the "owned_stocks" dict as a new key + value
86 dictionary["total"] = total_stock_value
87 dictionary["name"] = stock_company_name
88 dictionary["price"] = stock_price
89
90 # create a overall net worth = to this users stocks + cash amounts added together
91 net_worth = added_total_stock_value + cash_amount
92
93 # for debugging
94 print(owned_stock)
95 # print(cash_amount_dict)
96 print(stock_dict)
97
98 # re-render the https://finance.cs50.net/ homepage to display the newly modified owned_stock list of dicts
99 return render_template("index.html" , owned_stock=owned_stock, cash_amount_dict=cash_amount_dict, net_worth=net_worth)
100
101
102# app route to buy stock
103@app.route("/buy", methods=["GET", "POST"])
104@login_required
105def buy():
106 """Buy shares of stock"""
107
108 # User reached route via GET (as by clicking a link or via redirect)
109 if request.method == "GET":
110 return render_template("buy.html")
111
112 # User reached route via POST (as by submitting a form via POST)
113 else:
114 # Ensure stock symbol were submitted
115 if not request.form.get("symbol"):
116 return apology("must provide a valid stock symbol", 403)
117
118 # Ensure shares were submitted
119 if not request.form.get("shares"):
120 return apology("must provide a valid count of shares", 403)
121
122 # Take the stock symbol the user inputed in the rendered HTML page & store it inside a variable
123 stock_symbol = request.form.get("symbol")
124 # Take the amount of shares the user inputed in the rendered HTML page & store it inside of a variable
125 count_shares = request.form.get("shares")
126
127 # If the users input in the "amount" field of the rendered HTML form is not numeric, throw an error
128 if not count_shares.isdigit():
129 return apology("the count of shares must be numeric", 403)
130
131 # Convert collected "count_shares" as a string, convert it into an int & store it inside a num_of_shares variable of type int
132 num_of_shares = int(count_shares)
133 # Run the lookup() function (from helpers.py) against the "stock_symbol" collected from the user &. This gets a dict for valid stock symbols OR 'None' for invalid stock symbols
134 stock_dict = lookup(stock_symbol)
135
136 # If the stock_dict that was returned by lookup() has a value of 'None'
137 if stock_dict == None:
138 return apology("Sorry, thats not a valid stock ID", 403)
139
140 # Extract the name, price & symbol from this dictionary that was returned. Then store these values in seperate variables
141 company_name = stock_dict["name"]
142 stock_price = stock_dict["price"]
143 stock_symbol = stock_dict["symbol"]
144 # Multiply the "stock_price" we just got in that variable against the value of "count_shares" the user intends to purchase + store this value in a new variable, "stock_multiplied"
145 stock_multiplied = num_of_shares * stock_price
146 # Extract the current logged in users ID from the session["user_id"] dict & store it inside of "user_id" to use below
147 user_id = session["user_id"]
148
149 # Query db for the current "user_id" we previously extracted above. Then extract the "cash" column from this users row inside of the users table, ensuring they can afford the stock. this is returned as a dict
150 cash_amount_dict = db.execute("SELECT cash FROM users WHERE id = :userid;",
151 userid=user_id)
152 # For debugging:
153 # print(user_id)
154 # print(cash_amount_dict)
155
156 # Extract the "cash" amount from this dict, thats inside of the first element of a list [0] of dicts, & then, the "cash" column from inside that dict: ["cash"]
157 user_cash_amount = cash_amount_dict[0]["cash"]
158
159 # If: value of "stock_multiplied" is > the "Cash" amount in the column of the Users table inside our db, for this current user account being queried
160 if stock_multiplied > user_cash_amount:
161 return apology("Sorry buddy, you don't have enough moneh to do that", 403)
162
163 # Subtract "stock_multiplied" from "user_cash_amount", to get a leftover new cash value we'll want to re-inject into the cash column for this user inside the users table
164 cash_leftover = user_cash_amount - stock_multiplied
165
166 # Query db for the current session["user_id"], then, UPDATE the existing cash amount in the users table for this current user to == "cash_leftover"
167 db.execute("UPDATE users SET cash = :cash WHERE id = :userid",
168 userid=user_id, cash=cash_leftover)
169
170 # Verify if the user already owns stock
171 owned_stock = db.execute("SELECT amount FROM portfolio WHERE id = :userid AND symbol = :stocksymbol;",
172 userid=user_id, stocksymbol=stock_symbol)
173
174 # If owned_stock is an empty list containing no dicts, indicating that user has no existing entries of that stock type in the db
175 if owned_stock == []:
176
177 # INSERT the "count_shares" value and the "stock_symbol" collected for the current user_id in the "symbol" and "amount" columns of the PORTFOLIO table in our db
178 db.execute("INSERT INTO portfolio (id, symbol, amount) VALUES (?, ?, ?);",
179 user_id, stock_symbol, count_shares)
180
181 else:
182
183 # Extract the "amount" from this "owned_stock" dictionary that was returned, & store these values in seperate variables
184 owned_dict_amount = owned_stock [0]["amount"]
185 # Create variable "added_amount" & store the extracted "owned_dict_amount" from this returned dict + our overalll "count_shares"
186 added_amount = owned_dict_amount + num_of_shares
187
188 # Update the portfolio table in our db with the users newly collected "added_amount" from this script WHERE the id in the db matches the user ID in this script && the symbol in the db matches "stock_symbol" in the script
189 db.execute("UPDATE portfolio SET amount =:amount WHERE id = :userid AND symbol = :symbol;",
190 userid=user_id, symbol=stock_symbol, amount=added_amount)
191
192 return redirect("/buy")
193
194@app.route("/history")
195@login_required
196def history():
197 """Show history of transactions"""
198 return apology("TODO")
199
200
201@app.route("/login", methods=["GET", "POST"])
202def login():
203 """Log user in"""
204
205 # Forget any user_id
206 session.clear()
207
208 # User reached route via POST (as by submitting a form via POST)
209 if request.method == "POST":
210
211 # Ensure username was submitted
212 if not request.form.get("username"):
213 return apology("must provide username", 403)
214
215 # Ensure password was submitted
216 elif not request.form.get("password"):
217 return apology("must provide password", 403)
218
219 # Query database for username
220 rows = db.execute("SELECT * FROM users WHERE username = :username",
221 username=request.form.get("username"))
222
223 # Ensure username exists and password is correct
224 if len(rows) != 1 or not check_password_hash(rows[0]["hash"], request.form.get("password")):
225 return apology("invalid username and/or password", 403)
226
227 # Remember which user has logged in
228 session["user_id"] = rows[0]["id"]
229
230 # Redirect user to home page
231 return redirect("/")
232
233 # User reached route via GET (as by clicking a link or via redirect)
234 else:
235 return render_template("login.html")
236
237
238@app.route("/logout")
239def logout():
240 """Log user out"""
241
242 # Forget any user_id
243 session.clear()
244
245 # Redirect user to login form
246 return redirect("/")
247
248
249@app.route("/quote", methods=["GET", "POST"])
250@login_required
251def quote():
252 """Get stock quote."""
253
254 # User reached route via GET (as by clicking a link or via redirect)
255 if request.method == "GET":
256 return render_template("quote.html")
257
258 # User reached route via POST (as by submitting a form via POST)
259 else:
260 # Ensure stock symbol was not submitted
261 if not request.form.get("symbol"):
262 return apology("must provide a valid stock symbol", 403)
263
264 # take the stock symbol the user inputed in the rendered HTML page and store it inside of a variable
265 stock_symbol = request.form.get("symbol")
266
267 # run the lookup() function (from helpers.py) against the stock_symbol collected from the user & get a dict for valid stock symbols OR 'None' for invalid stock symbols
268 stock_dict = lookup(stock_symbol)
269
270 # if stock_dict that was returned has a value of 'None'
271 if stock_dict == None:
272 return apology("Sorry, thats not a valid stock ID", 403)
273
274 # extract the name, price and symbol from this dictionary that was returned, and store these values in seperate variables
275 company_name = stock_dict["name"]
276 stock_price = stock_dict["price"]
277 stock_symbol = stock_dict["symbol"]
278
279 # renders the original quote page + returns the values seperately from our stock_dict to the HTML page to this page being rendered here when it is needed
280 return render_template("quote_result.html" , returned_name=company_name, returned_price=stock_price, returned_symbol=stock_symbol)
281
282
283@app.route("/register", methods=["GET", "POST"])
284def register():
285 """Register user"""
286 if request.method == "GET":
287 return render_template("register.html")
288 else:
289 # Ensure username was submitted
290 if not request.form.get("username"):
291 return apology("must provide username", 403)
292 # Ensure password was submitted
293 elif not request.form.get("password"):
294 return apology("must provide password", 403)
295 # compare strings in both password and confimration fields, if they dont match, throw an error
296 elif request.form.get("password") != request.form.get("confirmation"):
297 return apology("password and confirmation must match", 403)
298 # Query the db & check if the username they typed in at username=request.form.get, is of another user that exists in our database
299 # if these match with an existing user in our db, a list of dicts will be returned, each dict containing the name of the existing user that matched as a "value" in each keyvaluepair
300 rows = db.execute("SELECT username FROM users WHERE username = :username;",
301 username=request.form.get("username"))
302 # if rows is returned as an empty list, not containing any dictionaries with names in them, there is no existing user, is a list of any lengh apart from 0 is returned, a user already exists
303 if len(rows) != 0:
304 return apology("username is taken", 403)
305 # collect users password they entered in the form to hash
306 password_var = request.form.get("password")
307 # hash this password
308 hash_pw = generate_password_hash(password_var)
309 # collect the username from this HTMl form
310 user_name = request.form.get("username")
311 # if all of the above passes, insert this new user + pw hash into our users table in our DB
312 db.execute("INSERT INTO users(username,hash) VALUES (?,?);",
313 user_name, hash_pw)
314 # redirect user to home after registering this user
315 return redirect("/")
316
317
318@app.route("/sell", methods=["GET", "POST"])
319@login_required
320def sell():
321 """Sell shares of stock"""
322 return apology("TODO")
323
324
325def errorhandler(e):
326 """Handle error"""
327 if not isinstance(e, HTTPException):
328 e = InternalServerError()
329 return apology(e.name, e.code)
330
331
332# Listen for errors
333for code in default_exceptions:
334 app.errorhandler(code)(errorhandler)