· 5 years ago · Jul 02, 2020, 02:26 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 return apology("TODO")
55
56
57@app.route("/buy", methods=["GET", "POST"])
58@login_required
59def buy():
60 """Buy shares of stock"""
61
62 # User reached route via GET (as by clicking a link or via redirect)
63 if request.method == "GET":
64 # Return a rendered form with a page that will ask the user what stock they want to buy, and how many shares of that stock
65 return render_template("buy.html")
66
67 # User reached route via POST (as by submitting a form via POST)
68 else:
69 # Ensure stock symbol were submitted
70 if not request.form.get("symbol"):
71 return apology("must provide a valid stock symbol", 403)
72
73 # Ensure shares were submitted
74 if not request.form.get("shares"):
75 return apology("must provide a valid count of shares", 403)
76
77 # take the stock symbol the user inputed in the rendered HTML page and store it inside of a variable
78 stock_symbol = request.form.get("symbol")
79
80 # take the amount of shares the user inputed in the rendered HTML page and store it inside of a variable
81 count_shares = request.form.get("shares")
82
83 if not count_shares.isdigit():
84 return apology("the count of shares must be numeric", 403)
85
86 # convert collected count_shares as a string into an int and store it inside of num_of_shares of type int
87 num_of_shares = int(count_shares)
88
89 # 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
90 stock_dict = lookup(stock_symbol)
91
92 # if stock_dict that was returned has a value of 'None'
93 if stock_dict == None:
94 return apology("Sorry, thats not a valid stock ID", 403)
95
96 # extract the name, price and symbol from this dictionary that was returned, and store these values in seperate variables
97 company_name = stock_dict["name"]
98 stock_price = stock_dict["price"]
99 stock_symbol = stock_dict["symbol"]
100
101 # Multiply the "stock_price" we just got in that variable above against the value of "count_shares" the user intends to purchase + store this value in a new variable, "stock_multiplied"
102 stock_multiplied = num_of_shares * stock_price
103
104 # extract the current logged in users name from the session["user_id"] dict and store it inside of "current_user_id" to use below
105 user_id = session["user_id"]
106
107 # Query our db for the current user_name we previously extracted from session["user_id"] above, then extract the "cash" from this users row in the users table, ensuring they can afford the stock. this will be returned as a dict
108 cash_amount_dict = db.execute("SELECT cash FROM users WHERE id = :userid;",
109 userid=user_id)
110 print(user_id)
111 print(cash_amount_dict)
112
113 # extract the 'cash' amount from this dictionary: inside of the first element of the list [0] of dicts, and then, the "cash" column from that dict: ["cash"]
114 user_cash_amount = cash_amount_dict[0]["cash"]
115
116 # If: value of "stock_multiplied" is GREATER THAN the "Cash" column inside the Users table of our db, for this current user account being queried
117 if stock_multiplied > user_cash_amount:
118 return apology("Sorry buddy, you don't have enough moneh to do that", 403)
119
120 # subtract stock_multiplied from user_cash_amount, to get a leftover new cash value we'll want to reinject into the table for this current user in our DB
121 cash_leftover = user_cash_amount - stock_multiplied
122
123 # PCODE > query db for the current session["user_id"], subtract the "stock_multiplied" value from the "Cash" column inside the Users table of our db for this users account
124 db.execute("INSERT INTO users (id, cash) VALUES (?, ?);",
125 user_id, cash_leftover)
126
127 # check if the user already owns stock
128 owns_stock = db.execute("SELECT symbol, amount FROM portfolio WHERE id = :userid AND stock_name = :stocksymbol;",
129 userid=user_id, stocksymbol=stock_symbol)
130
131
132 if owns_stock = True:
133 # PCODE > 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
134 db.execute("INSERT INTO portfolio (id, symbol, amount) VALUES (?, ?, ?);",
135 user_id, stock_symbol, count_shares)
136
137@app.route("/history")
138@login_required
139def history():
140 """Show history of transactions"""
141 return apology("TODO")
142
143
144@app.route("/login", methods=["GET", "POST"])
145def login():
146 """Log user in"""
147
148 # Forget any user_id
149 session.clear()
150
151 # User reached route via POST (as by submitting a form via POST)
152 if request.method == "POST":
153
154 # Ensure username was submitted
155 if not request.form.get("username"):
156 return apology("must provide username", 403)
157
158 # Ensure password was submitted
159 elif not request.form.get("password"):
160 return apology("must provide password", 403)
161
162 # Query database for username
163 rows = db.execute("SELECT * FROM users WHERE username = :username",
164 username=request.form.get("username"))
165
166 # Ensure username exists and password is correct
167 if len(rows) != 1 or not check_password_hash(rows[0]["hash"], request.form.get("password")):
168 return apology("invalid username and/or password", 403)
169
170 # Remember which user has logged in
171 session["user_id"] = rows[0]["id"]
172
173 # Redirect user to home page
174 return redirect("/")
175
176 # User reached route via GET (as by clicking a link or via redirect)
177 else:
178 return render_template("login.html")
179
180
181@app.route("/logout")
182def logout():
183 """Log user out"""
184
185 # Forget any user_id
186 session.clear()
187
188 # Redirect user to login form
189 return redirect("/")
190
191
192@app.route("/quote", methods=["GET", "POST"])
193@login_required
194def quote():
195 """Get stock quote."""
196
197 # User reached route via GET (as by clicking a link or via redirect)
198 if request.method == "GET":
199 return render_template("quote.html")
200
201 # User reached route via POST (as by submitting a form via POST)
202 else:
203 # Ensure stock symbol was not submitted
204 if not request.form.get("symbol"):
205 return apology("must provide a valid stock symbol", 403)
206
207 # take the stock symbol the user inputed in the rendered HTML page and store it inside of a variable
208 stock_symbol = request.form.get("symbol")
209
210 # 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
211 stock_dict = lookup(stock_symbol)
212
213 # if stock_dict that was returned has a value of 'None'
214 if stock_dict == None:
215 return apology("Sorry, thats not a valid stock ID", 403)
216
217 # extract the name, price and symbol from this dictionary that was returned, and store these values in seperate variables
218 company_name = stock_dict["name"]
219 stock_price = stock_dict["price"]
220 stock_symbol = stock_dict["symbol"]
221
222 # 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
223 return render_template("quote_result.html" , returned_name=company_name, returned_price=stock_price, returned_symbol=stock_symbol)
224
225
226@app.route("/register", methods=["GET", "POST"])
227def register():
228 """Register user"""
229 if request.method == "GET":
230 return render_template("register.html")
231 else:
232 # Ensure username was submitted
233 if not request.form.get("username"):
234 return apology("must provide username", 403)
235 # Ensure password was submitted
236 elif not request.form.get("password"):
237 return apology("must provide password", 403)
238 # compare strings in both password and confimration fields, if they dont match, throw an error
239 elif request.form.get("password") != request.form.get("confirmation"):
240 return apology("password and confirmation must match", 403)
241 # Query the db & check if the username they typed in at username=request.form.get, is of another user that exists in our database
242 # 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
243 rows = db.execute("SELECT username FROM users WHERE username = :username;",
244 username=request.form.get("username"))
245 # 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
246 if len(rows) != 0:
247 return apology("username is taken", 403)
248 # collect users password they entered in the form to hash
249 password_var = request.form.get("password")
250 # hash this password
251 hash_pw = generate_password_hash(password_var)
252 # collect the username from this HTMl form
253 user_name = request.form.get("username")
254 # if all of the above passes, insert this new user + pw hash into our users table in our DB
255 db.execute("INSERT INTO users(username,hash) VALUES (?,?);",
256 user_name, hash_pw)
257 # redirect user to home after registering this user
258 return redirect("/")
259
260
261@app.route("/sell", methods=["GET", "POST"])
262@login_required
263def sell():
264 """Sell shares of stock"""
265 return apology("TODO")
266
267
268def errorhandler(e):
269 """Handle error"""
270 if not isinstance(e, HTTPException):
271 e = InternalServerError()
272 return apology(e.name, e.code)
273
274
275# Listen for errors
276for code in default_exceptions:
277 app.errorhandler(code)(errorhandler)