· 6 years ago · Apr 20, 2020, 09:34 PM
1import os
2
3from cs50 import SQL
4from flask import Flask, flash, jsonify, redirect, render_template, request, session
5from flask_session import Session
6from tempfile import mkdtemp
7from werkzeug.exceptions import default_exceptions, HTTPException, InternalServerError
8from werkzeug.security import check_password_hash, generate_password_hash
9
10from helpers import apology, login_required, lookup, usd
11from datetime import datetime
12
13
14# Configure application
15app = Flask(__name__)
16
17# Ensure templates are auto-reloaded
18app.config["TEMPLATES_AUTO_RELOAD"] = True
19
20# Ensure responses aren't cached
21@app.after_request
22def after_request(response):
23 response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
24 response.headers["Expires"] = 0
25 response.headers["Pragma"] = "no-cache"
26 return response
27
28# Custom filter
29app.jinja_env.filters["usd"] = usd
30
31# Configure session to use filesystem (instead of signed cookies)
32app.config["SESSION_FILE_DIR"] = mkdtemp()
33app.config["SESSION_PERMANENT"] = False
34app.config["SESSION_TYPE"] = "filesystem"
35Session(app)
36
37# Configure CS50 Library to use SQLite database
38db = SQL("sqlite:///finance.db")
39
40# Make sure API key is set
41if not os.environ.get("API_KEY"):
42 raise RuntimeError("API_KEY not set")
43
44
45@app.route("/")
46@login_required
47def index():
48 # a list in which each index is a table row/stock dict(currprice, name, symb,shares)
49 disp = list() # similar in structure to = db.execute
50 # list of rows dicts
51 # shares bas would print each transaction
52 # Checks if alle column value is zero to not wouldn't print
53 rows = db.execute("SELECT symbol, name, SUM(shares) FROM stock WHERE id = ? AND alle != 0 GROUP BY symbol", session["user_id"])
54
55 # Stocks’ total value plus cash
56 grand = 0.0
57
58 for row in rows: # EACH row is only one symbol not a transaction
59 # current price get
60 lookdict = lookup(row["symbol"])
61 curr_price = lookdict["price"]
62 row["price"] = round(curr_price, 2)
63 total = curr_price * row["SUM(shares)"]
64 grand = total + grand
65 row["total"] = f'{round(total, 2):,}'
66 disp.append(row)
67
68 cash_l = db.execute("SELECT cash FROM users WHERE id = ?", session["user_id"])
69 cash = cash_l[0]["cash"]
70 grand = grand + cash
71 # f'{value:,}' for apostrophes in thousands places(more in notes), round to 2 decimals
72 grand = f'{round(grand, 2):,}'
73 cash = f'{round(cash, 2):,}'
74
75
76 return render_template("index.html", disp=disp, grand=grand, cash=cash)
77
78
79
80@app.route("/buy", methods=["GET", "POST"])
81@login_required
82def buy():
83 if request.method == "GET":
84 return render_template("buy.html")
85 # POST from buy.html
86 else:
87 # Calculating time of buying using datetime libary imported
88 time = datetime.now()
89
90 symbol = request.form.get("symbol")
91 # shares_no passed as a string from the form
92 shares_no = int(request.form.get("shareno"))
93 if shares_no <= 0:
94 return apology("Shares no. not a positive integer")
95 # stonks is a dict ....more info in quote
96 stonks = lookup(symbol)
97 if stonks == None:
98 return apology("Invalid symbol", 403)
99 # Checking if shares is not a positive no
100
101 # Current stock price per one share, (price is converted to float in lookup())
102 price = stonks["price"]
103
104 # Checking if cash is enough to buy amount of stock with current price, NOTICE:session id
105 # i.e. cashlist = [{'cash': 155}]
106 # session_id and id in the users database are the same
107 cashlist = db.execute("SELECT cash FROM users WHERE id = ?", session["user_id"])
108 cash = cashlist[0]["cash"]
109 # price is returned from lookup() as a string not a number i.e '3'
110 total = price * shares_no
111 if total > cash:
112 return apology("Not enough cash", 403)
113
114 # Update cash after buying
115 cash = cash - (price * shares_no)
116 db.execute("UPDATE users SET cash=? WHERE id = ?", (cash, session["user_id"]))
117
118 price = round(price, 2)
119
120 # Checking if stock shares owned to update the total number
121 total_lu = db.execute("SELECT SUM(shares) FROM stock WHERE id = ? AND symbol = ?", (session["user_id"], symbol))
122 # Not the first stock owned
123 # SUM wil return None or NULL if there is no shares(fisrt purchase)
124 if total_lu[0]["SUM(shares)"] != None:
125 # stock owned before + purchased now
126 owned = int(total_lu[0]["SUM(shares)"]) + shares_no
127 db.execute("INSERT INTO stock (id, name, symbol, shares, price, time, alle) VALUES(?,?,?,?,?,?,?)", (session["user_id"], stonks["name"], symbol, shares_no, price, time, owned))
128 # UPDATING for the whole (alle column same symbol) the new value to be easier
129 db.execute("UPDATE stock SET alle = ? WHERE id = ? ", (owned, session["user_id"]))
130 # First stock purchase
131 else:
132 # the first stock to purchase
133 db.execute("INSERT INTO stock (id, name, symbol, shares, price, time, alle) VALUES(?,?,?,?,?,?, ?)",(session["user_id"], stonks["name"], symbol, shares_no, price, time, shares_no))
134
135
136 # redirecting to index.html after finishing
137 return redirect("/")
138
139
140@app.route("/history")
141@login_required
142def history():
143 # a list in which each index is a table row/stock dict(currprice, name, symb,shares)
144 disp = list() # similar in structure to = db.execute
145 # list of rows dicts
146 # shares bas would print each transaction
147 # Checks if alle column value is zero to not wouldn't print
148 rows = db.execute("SELECT symbol, name, price, shares, time FROM stock WHERE id = ? AND alle != 0 ORDER BY time", session["user_id"])
149
150 # Insert into a list to pass it to html
151 for row in rows:
152 disp.append(row)
153
154 return render_template("history.html", disp=disp)
155
156
157@app.route("/login", methods=["GET", "POST"])
158def login():
159 """Log user in"""
160
161 # Forget any user_id
162 session.clear()
163
164 # User reached route via POST (as by submitting a form via POST)
165 if request.method == "POST":
166
167 # Ensure username was submitted
168 if not request.form.get("username"):
169 return apology("must provide username", 403)
170
171 # Ensure password was submitted
172 elif not request.form.get("password"):
173 return apology("must provide password", 403)
174
175 # Query database for username
176 rows = db.execute("SELECT * FROM users WHERE username = :username",
177 username=request.form.get("username"))
178
179 # Ensure username exists and password is correct
180 if len(rows) != 1 or not check_password_hash(rows[0]["hash"], request.form.get("password")):
181 return apology("invalid username and/or password", 403)
182
183 # Remember which user has logged in
184 session["user_id"] = rows[0]["id"]
185
186 # Redirect user to home page
187 return redirect("/")
188
189 # User reached route via GET (as by clicking a link or via redirect)
190 else:
191 return render_template("login.html")
192
193
194@app.route("/logout")
195def logout():
196 """Log user out"""
197
198 # Forget any user_id
199 session.clear()
200
201 # Redirect user to login form
202 return redirect("/")
203
204
205@app.route("/quote", methods=["GET", "POST"])
206@login_required
207def quote():
208 if request.method == "GET":
209 return render_template("quote.html")
210 # POST from quote.html, display to quoted.html
211 else:
212 symbol = request.form.get("symbol")
213 # lookup(symbol) function returns a dict with the name, symbol and current price(float)
214 stock_dict = lookup(symbol)
215 #lookup returns None if symbol invalid
216 if stock_dict == None:
217 return apology("Invalid symbol", 403)
218 # Pass name, symbol, price to quoted for printing (could use stock_dict["symbol"])
219 return render_template("quoted.html", name=stock_dict["name"], symbol=symbol, price=stock_dict["price"])
220
221
222@app.route("/register", methods=["GET", "POST"])
223def register():
224 if request.method == "GET":
225 return render_template("register.html")
226 #POST input
227 else:
228 if not request.form.get("username"):
229 return apology("must provide username", 403)
230
231 if not request.form.get("password"):
232 return apology("must provid password", 403)
233
234 if request.form.get("password") != request.form.get("passwordconfirm"):
235 return apology("password doesn't match", 403)
236 # Minimum 6 characters required
237 if len(request.form.get("password")) < 6:
238 return apology("Min 6 characters :(")
239 # Checking if username entered is already used in the database
240 # name_check is a list of rows dicts (username field)
241 name_check = db.execute("SELECT username FROM users WHERE username = ?",request.form.get("username"))
242 # List won't be empty if name is in db (if not ==empty, if == notempty),None notion isn't same as null
243 if name_check:
244 return apology("username taken", 403)
245
246 # Input is valid, insert username and hash into users table
247 db.execute("INSERT INTO users (username, hash) VALUES(?,?)",request.form.get("username"), generate_password_hash(request.form.get("password")))
248 # For better design could use vars username, pass instead of request.form
249 # redirects to login
250 return redirect("/")
251
252
253@app.route("/sell", methods=["GET", "POST"])
254@login_required
255def sell():
256 if request.method == "GET":
257 # Passing symbols owned to the option menu
258 rows = db.execute("SELECT DISTINCT symbol FROM stock WHERE alle != 0 AND id = ?", session["user_id"])
259 return render_template("sell.html", rows=rows)
260 # POST method from sell.html
261 else:
262 # Calculating time of buying using datetime libary imported
263 time = datetime.now()
264
265 # Symbol chosen from select menu
266 symbol = request.form.get("symbol")
267 # passed as a string despite input number specification
268 shares = int(request.form.get("shares"))
269 # If user didn't choose a stock
270 if not symbol:
271 return apology("Choose a stock", 403)
272 # Checking if stock number is not +ve int
273
274 if shares <= 0:
275 print("Positive integer maybe??", 403)
276 # Checking if stock number more than owned
277 # alle is not like cash UNIQUE but it's one value so distinct (fix the property)
278 rows = db.execute("SELECT DISTINCT alle FROM stock WHERE symbol = ? AND id = ?", (symbol, session["user_id"]))
279 if rows[0]["alle"] < shares:
280 print("Not enough stocks", 403)
281
282 alle = rows[0]["alle"] # no. of stocks before selling
283 look = lookup(symbol)
284 current_price = look["price"]
285
286 rows = db.execute("SELECT cash FROM users WHERE id = ?", session["user_id"])
287 cash = rows[0]["cash"] + (shares * current_price)
288 # UPDATING cash value
289 db.execute("UPDATE users SET cash = ? WHERE id = ?", (cash, session["user_id"]))
290
291 # Currently own after selling
292 own = alle - shares
293 current_price = round(current_price, 2)
294 shares = - shares
295 # INSERT the selling transaction
296 db.execute("INSERT INTO stock (id, name, symbol, shares, price, time, alle) VALUES(?,?,?,?,?,?, ?)",(session["user_id"], look["name"], symbol, shares, current_price, time, own))
297 # UPDATING current stock-shares owned for all transactions
298 db.execute("UPDATE stock SET alle = ? WHERE id = ? AND symbol = ?", (own, session["user_id"], symbol))
299
300 return redirect("/")
301
302
303@app.route("/cash", methods=["GET", "POST"])
304@login_required
305def cash():
306 if request.method == "POST":
307 surge = float(request.form.get("cash"))
308 if surge <= 0:
309 return apology("Positive cash?", 403)
310
311 row = db.execute("SELECT cash FROM users WHERE id = ?", session["user_id"])
312 cash = row[0]["cash"] + surge
313 db.execute("UPDATE users SET cash = ? WHERE id = ?", (cash, session["user_id"]))
314 return redirect("/")
315
316 else:
317 return render_template("cashin.html")
318
319
320def errorhandler(e):
321 """Handle error"""
322 if not isinstance(e, HTTPException):
323 e = InternalServerError()
324 return apology(e.name, e.code)
325
326
327# Listen for errors
328for code in default_exceptions:
329 app.errorhandler(code)(errorhandler)