· 7 years ago · Nov 09, 2018, 04:02 AM
1from cs50 import SQL
2from flask import Flask, flash, redirect, render_template, request, session, url_for
3from flask_session import Session
4from passlib.apps import custom_app_context as pwd_context
5from tempfile import gettempdir
6
7from helpers import *
8
9# configure application
10app = Flask(__name__)
11
12# ensure responses aren't cached
13if app.config["DEBUG"]:
14 @app.after_request
15 def after_request(response):
16 response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
17 response.headers["Expires"] = 0
18 response.headers["Pragma"] = "no-cache"
19 return response
20
21# custom filter
22app.jinja_env.filters["usd"] = usd
23
24# configure session to use filesystem (instead of signed cookies)
25app.config["SESSION_FILE_DIR"] = gettempdir()
26app.config["SESSION_PERMANENT"] = False
27app.config["SESSION_TYPE"] = "filesystem"
28Session(app)
29
30# configure CS50 Library to use SQLite database
31db = SQL("sqlite:///finance.db")
32
33@app.route("/")
34@login_required
35def index():
36
37 # stores the information for the user in rows
38 rows = db.execute("SELECT * FROM stocks WHERE id = :id", id = session["user_id"])
39
40 # this is the user's cash balance
41 cash_balance = db.execute("SELECT cash FROM users WHERE id = :id", id = session["user_id"])
42
43 # this is used to the print the cash balance nicely
44 user_cash_balance = usd(cash_balance[0]["cash"])
45
46 # this is the total amount of cash the user has (assets + cash balance)
47 total = cash_balance[0]["cash"]
48
49 # goes through the rows adding in things we need
50 for row in rows:
51
52 # current price of the stock
53 row["current_price"] = lookup(row["symbol"])["price"]
54 # current price of the stock formatted to print on the webpage
55 row["usd_current_price"] = usd(row["current_price"])
56
57 # total value of stocks owned for a certain stock
58 row["total_price"] = lookup(row["symbol"])["price"] * row["shares"]
59 # formatted to print on the webpage
60 row["usd_total_price"] = usd(row["total_price"])
61
62 # puts the name of the stock in the table to print
63 row["name"] = lookup(row["symbol"])["name"]
64 # updates the total amount of cash
65 total = total + row["total_price"]
66
67 # formats total for printing on the webpage
68 total = usd(total)
69
70 return render_template("index.html", rows = rows, cash_balance = cash_balance, total = total,
71 user_cash_balance = user_cash_balance)
72
73@app.route("/buy", methods=["GET", "POST"])
74@login_required
75def buy():
76 """Buy shares of stock."""
77
78 # loads the buy page
79 if request.method == "GET":
80 return render_template("buy.html")
81
82 # makes sure the user inputs a valid stock symbol
83 if not request.form.get("symbol"):
84 return apology("must provide a stock symbol")
85
86 # makes sure the user provides a number of shares
87 if not request.form.get("shares"):
88 return apology("must provide a number of shares")
89
90 # makes sure the user inputs a positive integer
91 if not request.form.get("shares").isdigit():
92 return apology("must provide a positive integer to purchase")
93
94 # gets facts about the stocks
95 stock_facts = lookup(request.form.get("symbol"))
96
97 # makes sure the stock symbol inputted was valid
98 if not stock_facts:
99 return apology("invalid stock symbol")
100
101 # determines the cost
102 cost = stock_facts["price"] * int(request.form.get("shares"))
103
104 # checks the amount of cash that the user has
105 cash = float(db.execute("SELECT cash FROM users WHERE id = :id", id = session["user_id"])[0]["cash"])
106
107 # ensures that the user has enough funds
108 if cost > cash:
109 return apology("not enough funds")
110
111 # creates something to check if a stock exists
112 test_stock = db.execute("SELECT shares FROM stocks WHERE id = :id AND symbol = :symbol", id = session["user_id"],
113 symbol = request.form.get("symbol"))
114
115 # checks if the user already owns the stock
116 if len(test_stock) != 0:
117 # updates the amount the user owns
118 db.execute("UPDATE stocks SET shares = shares + :shares WHERE id = :id", shares = request.form.get("shares"),
119 id = session["user_id"])
120
121 # inserts purchase into the history
122 db.execute("INSERT INTO history (id, symbol, shares, price) VALUES(:id, :symbol, :shares, :price)", id = session["user_id"],
123 symbol = request.form.get("symbol"), shares = int(request.form.get("shares")), price = stock_facts["price"])
124
125 else:
126 # inserts the new information into the table
127 db.execute("INSERT INTO stocks (id, symbol, shares, price) VALUES(:id, :symbol, :shares, :price)", id = session["user_id"],
128 symbol = request.form.get("symbol"), shares = int(request.form.get("shares")), price = stock_facts["price"])
129
130 # inserts purchase into the history
131 db.execute("INSERT INTO history (id, symbol, shares, price) VALUES(:id, :symbol, :shares, :price)", id = session["user_id"],
132 symbol = request.form.get("symbol"), shares = int(request.form.get("shares")), price = stock_facts["price"])
133
134 # updates the user's cash amount
135 db.execute("UPDATE users SET cash = :cash WHERE id = :id", cash = cash - cost, id = session["user_id"])
136
137 return redirect(url_for("index"))
138
139
140@app.route("/history")
141@login_required
142def history():
143 """Show history of transactions."""
144
145 # stores the rows of the history for the user
146 rows = db.execute("SELECT * FROM history WHERE id = :id", id = session["user_id"])
147
148 return render_template("history.html", rows = rows)
149
150@app.route("/login", methods=["GET", "POST"])
151def login():
152 """Log user in."""
153
154 # forget any user_id
155 session.clear()
156
157 # if user reached route via POST (as by submitting a form via POST)
158 if request.method == "POST":
159
160 # ensure username was submitted
161 if not request.form.get("username"):
162 return apology("must provide username")
163
164 # ensure password was submitted
165 elif not request.form.get("password"):
166 return apology("must provide password")
167
168 # query database for username
169 rows = db.execute("SELECT * FROM users WHERE username = :username", username=request.form.get("username"))
170
171 # ensure username exists and password is correct
172 if len(rows) != 1 or not pwd_context.verify(request.form.get("password"), rows[0]["hash"]):
173 return apology("invalid username and/or password")
174
175 # remember which user has logged in
176 session["user_id"] = rows[0]["id"]
177
178 # redirect user to home page
179 return redirect(url_for("index"))
180
181 # else if user reached route via GET (as by clicking a link or via redirect)
182 else:
183 return render_template("login.html")
184
185@app.route("/logout")
186def logout():
187 """Log user out."""
188
189 # forget any user_id
190 session.clear()
191
192 # redirect user to login form
193 return redirect(url_for("login"))
194
195@app.route("/quote", methods=["GET", "POST"])
196@login_required
197def quote():
198 """Get stock quote."""
199 if request.method == "GET":
200
201 # renders the quote page
202 return render_template("quote.html")
203
204 elif request.method == "POST":
205
206 # makes sure user inputted a stock
207 if not request.form.get("stock"):
208 return apology("must provide symbol for a stock")
209
210 stock_facts = lookup(request.form.get("stock"))
211
212 # makes sure that the stock symbol was valid (dict will be empty if it's not)
213 if stock_facts:
214 stock_facts["price"] = usd(stock_facts["price"])
215 return render_template("quoted.html", stock_facts = stock_facts)
216
217 else:
218 return apology("invalid stock symbol")
219
220@app.route("/register", methods=["GET", "POST"])
221def register():
222 """Register user."""
223 # forget any user_id
224 session.clear()
225
226 # if user reached route via POST (as by submitting a form via POST)
227 if request.method == "POST":
228
229 # there was an error where upon registering and not having matching passwords the user was still create and added to the
230 # table / this caused errors if a user tried to register again with the same username, and this variable is used later to
231 # remove the accidental addition to the table so the user can try again with the username if their passwords did not match /
232 # I am not sure why the name was being added after the apology was rendered
233 error = 0
234
235 # ensures a username is submitted
236 if not request.form.get("username"):
237 return apology("must provide username")
238
239 # ensures a password is submitted
240 elif not request.form.get("password"):
241 return apology("must provide password")
242
243 # ensures both passwords match
244 elif request.form.get("password") != request.form.get("password2"):
245 # sets error to 1 / will be used further down when user is accidentally registered
246 error = 1
247 return apology("passwords do not match")
248
249 # query database for username
250 rows = db.execute("SELECT * FROM users WHERE username = :username", username=request.form.get("username"))
251
252 # checks to see if username already exists
253 if len(rows) > 0:
254 return apology("username is already taken")
255
256 # inserts the username and password into the table (registers the user)
257 else:
258 db.execute("INSERT INTO users (username, hash) VALUES(:username, :password)", username = request.form.get("username"),
259 password = pwd_context.encrypt(request.form.get("password")))
260
261 # if the user shouldn't not have been registered, delete the user
262 if error == 1:
263 db.execute("DELETE FROM users WHERE username = username:", username = request.form.get("username"))
264
265 # redirect user to home page
266 return redirect(url_for("index"))
267
268 else:
269 return render_template("register.html")
270
271@app.route("/sell", methods=["GET", "POST"])
272@login_required
273def sell():
274 """Sell shares of stock."""
275 # loads the buy page
276 if request.method == "GET":
277 return render_template("sell.html")
278
279 # makes sure the user inputs a valid stock symbol
280 if not request.form.get("symbol"):
281 return apology("must provide a stock symbol")
282
283 # makes sure the user provides a number of shares
284 if not request.form.get("shares"):
285 return apology("must provide a number of shares")
286
287 # makes sure the user inputs a positive integer
288 if not request.form.get("shares").isdigit():
289 return apology("must provide a positive integer to sell")
290
291 # gets facts about the stocks
292 stock_facts = lookup(request.form.get("symbol"))
293
294 # makes sure the stock symbol inputted was valid
295 if not stock_facts:
296 return apology("invalid stock symbol")
297
298 # checks if user has the stock and how much of it
299 stock_amount = int(db.execute("SELECT shares FROM stocks WHERE id = :id AND symbol = :symbol", id = session["user_id"],
300 symbol = request.form.get("symbol"))[0]["shares"])
301
302 # makes sure the user has enough stocks to sell
303 if stock_amount < int(request.form.get("shares")):
304 return apology("not enough stocks to sell")
305
306 # determines the value
307 value = stock_facts["price"] * int(request.form.get("shares"))
308
309 # updates the amount the user owns
310 db.execute("UPDATE stocks SET shares = shares - :shares WHERE id = :id AND symbol = :symbol", shares = request.form.get("shares"),
311 id = session["user_id"], symbol = request.form.get("symbol"))
312
313 # checks the amount of cash that the user has
314 cash = float(db.execute("SELECT cash FROM users WHERE id = :id", id = session["user_id"])[0]["cash"])
315
316 # updates the user's cash amount
317 db.execute("UPDATE users SET cash = :cash WHERE id = :id", cash = cash + value, id = session["user_id"])
318
319 # adds the sale to the history
320 db.execute("INSERT INTO history (id, symbol, shares, price) VALUES(:id, :symbol, :shares, :price)", id = session["user_id"],
321 symbol = request.form.get("symbol"), shares = int(request.form.get("shares")) * -1, price = stock_facts["price"])
322
323
324 # checks the new stock amount
325 new_stock_amount = int(db.execute("SELECT shares FROM stocks WHERE id = :id AND symbol = :symbol", id = session["user_id"],
326 symbol = request.form.get("symbol"))[0]["shares"])
327
328 # if the user now does not own any of the stock, deletes the stock information from the table (but not from the history of
329 # transactions)
330 if new_stock_amount == 0:
331 db.execute("DELETE FROM stocks WHERE id = :id AND symbol = :symbol", id = session["user_id"],
332 symbol = request.form.get("symbol"))
333
334 return redirect(url_for("index"))
335
336@app.route("/add_funds", methods=["GET", "POST"])
337@login_required
338def add_funds():
339
340 if request.method == "GET":
341 return render_template("add_funds.html")
342
343 else:
344
345 # makes sure the user provides an amount of money to add
346 if not request.form.get("amount"):
347 return apology("must provide an amount of money to add")
348
349 # makes sure the user inputs a number (can be a float)
350 try:
351 float(request.form.get("amount"))
352 except ValueError:
353 return apology("must provide a positive integer to purchase")
354
355 # makes sure the amount to add is positive
356 if float(request.form.get("amount")) < 0:
357 return apology("must provide a positive integer to add funds")
358
359 # checks the amount of cash that the user has
360 cash = float(db.execute("SELECT cash FROM users WHERE id = :id", id = session["user_id"])[0]["cash"])
361
362 # adds the funds
363 db.execute("UPDATE users SET cash = :cash WHERE id = :id", cash = cash + float(request.form.get("amount")),
364 id = session["user_id"])
365
366 return redirect(url_for("index"))