└── CS50 Finance
├── requirements.txt
├── finance.db
├── static
├── favicon.ico
└── styles.css
├── templates
├── quoted.html
├── apology.html
├── quote.html
├── buy.html
├── login.html
├── register.html
├── sell.html
├── history.html
├── sold.html
├── bought.html
├── index.html
├── cashadded.html
└── layout.html
├── helpers.py
└── application.py
/CS50 Finance/requirements.txt:
--------------------------------------------------------------------------------
1 | cs50
2 | Flask
3 | Flask-Session
4 | requests
5 |
--------------------------------------------------------------------------------
/CS50 Finance/finance.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LiamMcB/Finance-App/HEAD/CS50 Finance/finance.db
--------------------------------------------------------------------------------
/CS50 Finance/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LiamMcB/Finance-App/HEAD/CS50 Finance/static/favicon.ico
--------------------------------------------------------------------------------
/CS50 Finance/templates/quoted.html:
--------------------------------------------------------------------------------
1 | {% extends "layout.html" %}
2 |
3 | {% block title %}
4 | Quoted
5 | {% endblock %}
6 |
7 | {% block main %}
8 |
9 | A share of {{companyName}} ({{symbol}}) costs ${{price}}.
10 |
11 | {% endblock %}
--------------------------------------------------------------------------------
/CS50 Finance/templates/apology.html:
--------------------------------------------------------------------------------
1 | {% extends "layout.html" %}
2 |
3 | {% block title %}
4 | Apology
5 | {% endblock %}
6 |
7 | {% block main %}
8 |
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/CS50 Finance/templates/quote.html:
--------------------------------------------------------------------------------
1 | {% extends "layout.html" %}
2 |
3 | {% block title %}
4 | Quote
5 | {% endblock %}
6 |
7 | {% block main %}
8 |
15 | {% endblock %}
16 |
--------------------------------------------------------------------------------
/CS50 Finance/templates/buy.html:
--------------------------------------------------------------------------------
1 | {% extends "layout.html" %}
2 |
3 | {% block title %}
4 | Buy
5 | {% endblock %}
6 |
7 | {% block main %}
8 |
17 | {% endblock %}
--------------------------------------------------------------------------------
/CS50 Finance/templates/login.html:
--------------------------------------------------------------------------------
1 | {% extends "layout.html" %}
2 |
3 | {% block title %}
4 | Log In
5 | {% endblock %}
6 |
7 | {% block main %}
8 |
17 | {% endblock %}
18 |
--------------------------------------------------------------------------------
/CS50 Finance/templates/register.html:
--------------------------------------------------------------------------------
1 | {% extends "layout.html" %}
2 |
3 | {% block title %}
4 | Register
5 | {% endblock %}
6 |
7 | {% block main %}
8 |
20 | {% endblock %}
21 |
--------------------------------------------------------------------------------
/CS50 Finance/templates/sell.html:
--------------------------------------------------------------------------------
1 | {% extends "layout.html" %}
2 |
3 | {% block title %}
4 | Sell
5 | {% endblock %}
6 |
7 | {% block main %}
8 |
22 | {% endblock %}
--------------------------------------------------------------------------------
/CS50 Finance/static/styles.css:
--------------------------------------------------------------------------------
1 | nav .navbar-brand
2 | {
3 | /* size for brand */
4 | font-size: xx-large;
5 | }
6 |
7 | /* colors for brand */
8 | nav .navbar-brand .blue
9 | {
10 | color: #537fbe;
11 | }
12 | nav .navbar-brand .red
13 | {
14 | color: #ea433b;
15 | }
16 | nav .navbar-brand .yellow
17 | {
18 | color: #f5b82e;
19 | }
20 | nav .navbar-brand .green
21 | {
22 | color: #2e944b;
23 | }
24 |
25 | main .form-control
26 | {
27 | /* center form controls */
28 | display: inline-block;
29 |
30 | /* override Bootstrap's 100% width for form controls */
31 | width: auto;
32 | }
33 |
34 | main
35 | {
36 | /* scroll horizontally as needed */
37 | overflow-x: auto;
38 |
39 | /* center contents */
40 | text-align: center;
41 | }
42 |
43 | main img
44 | {
45 | /* constrain images on small screens */
46 | max-width: 100%;
47 | }
48 |
--------------------------------------------------------------------------------
/CS50 Finance/templates/history.html:
--------------------------------------------------------------------------------
1 | {% extends "layout.html" %}
2 |
3 | {% block title %}
4 | Sell
5 | {% endblock %}
6 |
7 | {% block main %}
8 |
9 |
10 |
11 | | Symbol |
12 | Shares |
13 | Price |
14 | Bought/Sold? |
15 | Time of Transaction |
16 |
17 |
18 |
19 | {% for row in historyList %}
20 |
21 | | {{ row["stock"] }} |
22 | {{ row["shares"] }} |
23 | {% if row["price"] < 0 %}
24 | ${{ row["price"] * -1 }} |
25 | Sold |
26 | {% else %}
27 | ${{ row["price"] }} |
28 | Bought |
29 | {% endif %}
30 | {{ row["time"] }} |
31 |
32 | {% endfor %}
33 |
34 |
35 | {% endblock %}
--------------------------------------------------------------------------------
/CS50 Finance/templates/sold.html:
--------------------------------------------------------------------------------
1 | {% extends "layout.html" %}
2 |
3 | {% block title %}
4 | Sold
5 | {% endblock %}
6 |
7 | {% block main %}
8 |
11 |
12 |
13 |
14 | | Symbol |
15 | Name |
16 | Shares |
17 | Price |
18 | Total |
19 |
20 |
21 |
22 | {% for row in stockList %}
23 |
24 | | {{ row["symbol"] }} |
25 | {{ row["stock"] }} |
26 | {{ row["shares"] }} |
27 | ${{ row["price"] }} |
28 | ${{ row["total"] }} |
29 |
30 | {% endfor %}
31 |
32 | | CASH |
33 | |
34 | ${{cashLeft}} |
35 |
36 |
37 |
38 |
39 | |
40 | ${{totalVal}} |
41 |
42 |
43 |
44 | {% endblock %}
--------------------------------------------------------------------------------
/CS50 Finance/templates/bought.html:
--------------------------------------------------------------------------------
1 | {% extends "layout.html" %}
2 |
3 | {% block title %}
4 | Bought
5 | {% endblock %}
6 |
7 | {% block main %}
8 |
11 |
12 |
13 |
14 | | Symbol |
15 | Name |
16 | Shares |
17 | Price |
18 | Total |
19 |
20 |
21 |
22 | {% for row in stockList %}
23 |
24 | | {{ row["symbol"] }} |
25 | {{ row["stock"] }} |
26 | {{ row["shares"] }} |
27 | ${{ row["price"] }} |
28 | ${{ row["total"] }} |
29 |
30 | {% endfor %}
31 |
32 | | CASH |
33 | |
34 | ${{cashLeft}} |
35 |
36 |
37 |
38 |
39 | |
40 | ${{totalVal}} |
41 |
42 |
43 |
44 | {% endblock %}
--------------------------------------------------------------------------------
/CS50 Finance/templates/index.html:
--------------------------------------------------------------------------------
1 | {% extends "layout.html" %}
2 |
3 | {% block title %}
4 | Home
5 | {% endblock %}
6 |
7 | {% block main %}
8 |
9 |
10 |
11 | | Symbol |
12 | Name |
13 | Shares |
14 | Price |
15 | Total |
16 |
17 |
18 |
19 | {% for row in stockList %}
20 |
21 | | {{ row["symbol"] }} |
22 | {{ row["stock"] }} |
23 | {{ row["shares"] }} |
24 | ${{ row["price"] }} |
25 | ${{ row["total"] }} |
26 |
27 | {% endfor %}
28 |
29 | | CASH |
30 | |
36 | |
37 | ${{cashLeft}} |
38 |
39 |
40 |
41 |
42 | |
43 | ${{totalVal}} |
44 |
45 |
46 |
47 | {% endblock %}
--------------------------------------------------------------------------------
/CS50 Finance/templates/cashadded.html:
--------------------------------------------------------------------------------
1 | {% extends "layout.html" %}
2 |
3 | {% block title %}
4 | Cash Added!
5 | {% endblock %}
6 |
7 | {% block main %}
8 |
9 |
10 |
11 | | Symbol |
12 | Name |
13 | Shares |
14 | Price |
15 | Total |
16 |
17 |
18 |
19 | {% for row in stockList %}
20 |
21 | | {{ row["symbol"] }} |
22 | {{ row["stock"] }} |
23 | {{ row["shares"] }} |
24 | ${{ row["price"] }} |
25 | ${{ row["total"] }} |
26 |
27 | {% endfor %}
28 |
29 | | CASH |
30 | |
36 | |
37 | ${{cashLeft}} |
38 |
39 |
40 |
41 |
42 | |
43 | ${{totalVal}} |
44 |
45 |
46 |
47 | {% endblock %}
--------------------------------------------------------------------------------
/CS50 Finance/helpers.py:
--------------------------------------------------------------------------------
1 | import os
2 | import requests
3 | import urllib.parse
4 |
5 | from flask import redirect, render_template, request, session
6 | from functools import wraps
7 |
8 |
9 | def apology(message, code=400):
10 | """Render message as an apology to user."""
11 | def escape(s):
12 | """
13 | Escape special characters.
14 |
15 | https://github.com/jacebrowning/memegen#special-characters
16 | """
17 | for old, new in [("-", "--"), (" ", "-"), ("_", "__"), ("?", "~q"),
18 | ("%", "~p"), ("#", "~h"), ("/", "~s"), ("\"", "''")]:
19 | s = s.replace(old, new)
20 | return s
21 | return render_template("apology.html", top=code, bottom=escape(message)), code
22 |
23 |
24 | def login_required(f):
25 | """
26 | Decorate routes to require login.
27 |
28 | http://flask.pocoo.org/docs/1.0/patterns/viewdecorators/
29 | """
30 | @wraps(f)
31 | def decorated_function(*args, **kwargs):
32 | if session.get("user_id") is None:
33 | return redirect("/login")
34 | return f(*args, **kwargs)
35 | return decorated_function
36 |
37 |
38 | def lookup(symbol):
39 | """Look up quote for symbol."""
40 |
41 | # Contact API
42 | try:
43 | api_key = os.environ.get("API_KEY")
44 | response = requests.get(f"https://cloud-sse.iexapis.com/stable/stock/{urllib.parse.quote_plus(symbol)}/quote?token={api_key}")
45 | response.raise_for_status()
46 | except requests.RequestException:
47 | return None
48 |
49 | # Parse response
50 | try:
51 | quote = response.json()
52 | return {
53 | "name": quote["companyName"],
54 | "price": float(quote["latestPrice"]),
55 | "symbol": quote["symbol"]
56 | }
57 | except (KeyError, TypeError, ValueError):
58 | return None
59 |
60 |
61 | def usd(value):
62 | """Format value as USD."""
63 | return f"${value:,.2f}"
64 |
--------------------------------------------------------------------------------
/CS50 Finance/templates/layout.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | C$50 Finance: {% block title %}{% endblock %}
24 |
25 |
26 |
27 |
28 |
29 |
53 |
54 | {% if get_flashed_messages() %}
55 |
60 | {% endif %}
61 |
62 |
63 | {% block main %}{% endblock %}
64 |
65 |
66 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/CS50 Finance/application.py:
--------------------------------------------------------------------------------
1 | import os
2 | import datetime
3 |
4 | from cs50 import SQL
5 | from flask import Flask, flash, jsonify, redirect, render_template, request, session
6 | from flask_session import Session
7 | from tempfile import mkdtemp
8 | from werkzeug.exceptions import default_exceptions, HTTPException, InternalServerError
9 | from werkzeug.security import check_password_hash, generate_password_hash
10 |
11 | from helpers import apology, login_required, lookup, usd
12 |
13 | # Configure application
14 | app = Flask(__name__)
15 |
16 | # Ensure templates are auto-reloaded
17 | app.config["TEMPLATES_AUTO_RELOAD"] = True
18 |
19 | # Ensure responses aren't cached
20 | @app.after_request
21 | def after_request(response):
22 | response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
23 | response.headers["Expires"] = 0
24 | response.headers["Pragma"] = "no-cache"
25 | return response
26 |
27 | # Custom filter
28 | app.jinja_env.filters["usd"] = usd
29 |
30 | # Configure session to use filesystem (instead of signed cookies)
31 | app.config["SESSION_FILE_DIR"] = mkdtemp()
32 | app.config["SESSION_PERMANENT"] = False
33 | app.config["SESSION_TYPE"] = "filesystem"
34 | Session(app)
35 |
36 | # Configure CS50 Library to use SQLite database
37 | db = SQL("sqlite:///finance.db")
38 |
39 | # Make sure API key is set
40 | if not os.environ.get("API_KEY"):
41 | raise RuntimeError("API_KEY not set")
42 |
43 |
44 | @app.route("/")
45 | @login_required
46 | def index():
47 | """Show portfolio of stocks"""
48 | # Find current user
49 | user_id = int(session['user_id'])
50 |
51 | # cashLeft represents how much cash the user has left
52 | cashRow = db.execute("SELECT cash FROM users WHERE id=:user_id", user_id=user_id)
53 | cashLeft = round(cashRow[0]['cash'], 2)
54 |
55 | # Get lists of stocks with symbols, names, shares, current price, and total
56 | stockList = db.execute("""SELECT symbol, stock, shares, price, total
57 | FROM portfolio WHERE user_id=:user_id""", user_id=user_id)
58 |
59 | # totalVal represents the total value of all stocks purchased
60 | totalVal = 0
61 | for row in stockList:
62 | totalVal += row["total"]
63 | totalVal = round(totalVal + cashLeft, 2)
64 |
65 | return render_template("index.html", cashLeft = cashLeft, stockList=stockList, totalVal=totalVal)
66 |
67 | @app.route("/buy", methods=["GET", "POST"])
68 | @login_required
69 | def buy():
70 | """Buy shares of stock"""
71 | if request.method == "GET":
72 | return render_template("buy.html")
73 |
74 | if request.method == "POST":
75 | # If the user didn't enter a symbol or a number of shares
76 | if not request.form.get("symbol") or not request.form.get("shares"):
77 | return apology("You must enter a stock symbol and number of shares!")
78 |
79 | symbol = request.form.get("symbol").upper()
80 | shares = int(request.form.get("shares"))
81 | info = lookup(symbol)
82 | user_id = int(session['user_id'])
83 |
84 | # If the user entered a symbol not relating to a stock
85 | if not info:
86 | return apology("The symbol you entered doesn't correspond to a stock", 403)
87 |
88 | # If the user entered a negative number of shares
89 | if shares <= 0:
90 | return apology("You must enter a positive number of shares", 403)
91 |
92 | companyName = info["name"]
93 | price = round(info["price"], 2)
94 |
95 | # Find out how much cash the user has
96 | cashResult = db.execute("SELECT cash FROM users WHERE id = :userid", userid = user_id)
97 | cash = float(cashResult[0]['cash'])
98 |
99 | # Find total price of purchase
100 | totalPrice = price * int(shares)
101 | cashLeft = round(cash - totalPrice, 2)
102 |
103 | # See if user can afford purchase
104 | if cashLeft < 0:
105 | return apology("You cannot afford this many shares")
106 |
107 | # See if user has stock already
108 | stockExist = db.execute("""SELECT * FROM portfolio WHERE user_id=:user_id AND symbol=:symbol""",
109 | user_id=user_id, symbol=symbol)
110 |
111 | if len(stockExist) == 1:
112 | newShares = shares + int(stockExist[0]["shares"])
113 | # newTotal is the total value of all shares at current price, to be put into index table, not history
114 | newTotal = newShares*price
115 |
116 | # Update portfolio to show current number of shares and value of stock
117 | db.execute("""UPDATE portfolio SET shares=:shares, price=:price, total=:total
118 | WHERE user_id=:user_id AND symbol=:symbol""",
119 | shares=newShares, price=price, total=newTotal, user_id=user_id, symbol=symbol)
120 |
121 | # Else if first time user is buying this stock
122 | else:
123 | # Insert new row into portfolio
124 | db.execute("""INSERT INTO portfolio (user_id, symbol, stock, shares, price, total)
125 | VALUES (:user_id, :symbol, :stock, :shares, :price, :total)""",
126 | user_id=user_id, symbol=symbol, stock=companyName, shares=shares, price=price, total=totalPrice)
127 |
128 |
129 | # Update history table to reflect new transaction, total is the total of that transaction
130 | db.execute("""INSERT INTO history (user_id, stock, shares, time, price, total)
131 | VALUES (:user_id, :symbol, :shares, :datetime, :price, :total)""",
132 | user_id=user_id, symbol=symbol, shares=shares, datetime=datetime.datetime.now(), price=price, total=totalPrice)
133 |
134 | # Update user's cash
135 | db.execute("UPDATE users SET cash = :cashLeft WHERE id=:user_id", cashLeft=cashLeft, user_id=user_id)
136 |
137 | return redirect("/bought")
138 |
139 |
140 | @app.route("/bought")
141 | @login_required
142 | def bought():
143 | # Get variables to display portfolio table on bought.html
144 | user_id = int(session['user_id'])
145 | cashRow = db.execute("SELECT cash FROM users WHERE id=:user_id", user_id=user_id)
146 | cashLeft = round(cashRow[0]['cash'], 2)
147 | stockList = db.execute("""SELECT symbol, stock, shares, price, total
148 | FROM portfolio WHERE user_id=:user_id""", user_id=user_id)
149 | totalVal = 0
150 | for row in stockList:
151 | totalVal += row["total"]
152 | totalVal = round(totalVal + cashLeft,2)
153 |
154 | return render_template("bought.html", cashLeft=cashLeft, stockList=stockList, totalVal=totalVal)
155 |
156 |
157 | @app.route("/history")
158 | @login_required
159 | def history():
160 | """Show history of transactions"""
161 | user_id = int(session["user_id"])
162 |
163 | # Query database for every transaction this user has made
164 | historyList = db.execute("SELECT * FROM history WHERE user_id=:user_id", user_id=user_id)
165 |
166 | return render_template("history.html", historyList=historyList)
167 |
168 | @app.route("/login", methods=["GET", "POST"])
169 | def login():
170 | """Log user in"""
171 |
172 | # Forget any user_id
173 | session.clear()
174 |
175 | # User reached route via POST (as by submitting a form via POST)
176 | if request.method == "POST":
177 |
178 | # Ensure username was submitted
179 | if not request.form.get("username"):
180 | return apology("must provide username", 403)
181 |
182 | # Ensure password was submitted
183 | elif not request.form.get("password"):
184 | return apology("must provide password", 403)
185 |
186 | # Query database for username
187 | rows = db.execute("SELECT * FROM users WHERE username = :username",
188 | username=request.form.get("username"))
189 |
190 | # Ensure username exists and password is correct
191 | if len(rows) != 1 or not check_password_hash(rows[0]["hash"], request.form.get("password")):
192 | return apology("invalid username and/or password", 403)
193 |
194 | # Remember which user has logged in
195 | session["user_id"] = rows[0]["id"]
196 |
197 | # Redirect user to home page
198 | return redirect("/")
199 |
200 | # User reached route via GET (as by clicking a link or via redirect)
201 | else:
202 | return render_template("login.html")
203 |
204 |
205 | @app.route("/logout")
206 | def logout():
207 | """Log user out"""
208 |
209 | # Forget any user_id
210 | session.clear()
211 |
212 | # Redirect user to login form
213 | return redirect("/")
214 |
215 |
216 | @app.route("/quote", methods=["GET", "POST"])
217 | @login_required
218 | def quote():
219 | """Get stock quote."""
220 | if request.method == "GET":
221 | return render_template("quote.html")
222 | else:
223 | # Get symbol from quote and run lookup function in helpers.py to insert info into quoted
224 | symbol = request.form.get("quote")
225 | if lookup(symbol) == None:
226 | return apology("The symbol you entered doesn't correspond to a stock", 403)
227 | else:
228 | info = lookup(symbol)
229 | companyName = info["name"]
230 | price = round(info["price"], 2)
231 | return render_template("quoted.html", symbol=symbol, companyName=companyName, price=price)
232 |
233 |
234 |
235 | @app.route("/register", methods=["GET", "POST"])
236 | def register():
237 | """Register user"""
238 | # GET Method is default, means form wasn't submitted by user so we should just display it
239 | if request.method == "GET":
240 | return render_template("register.html")
241 | else:
242 | username = request.form.get("username")
243 | password = request.form.get("password")
244 | confirmation = request.form.get("confirmation")
245 | # Checks if username slot was empty or it exists already
246 | rows = db.execute("SELECT * FROM users WHERE username = :username", username=username)
247 | if not username:
248 | return apology("you must enter a username", 403)
249 | if len(rows) == 1:
250 | return apology("username is taken", 403)
251 | # Check if password is same as confirmation and not empty
252 | if password != confirmation or not password:
253 | return apology("the two passwords you entered are not the same", 403)
254 | elif not password:
255 | return apology("you must enter a password", 403)
256 | # Submit user's input via POST to register
257 |
258 | # INSERT new user into users
259 | db.execute("""INSERT INTO users (username, hash)
260 | VALUES (:username, :password)""",
261 | username=username, password=generate_password_hash(password))
262 |
263 | return redirect("/")
264 |
265 | @app.route("/sell", methods=["GET", "POST"])
266 | @login_required
267 | def sell():
268 | """Sell shares of stock"""
269 | # Get a list of stocks the user has to show on the page
270 | user_id = int(session["user_id"])
271 | symbolList = db.execute("SELECT symbol FROM portfolio WHERE user_id=:user_id", user_id=user_id)
272 |
273 | if request.method == "GET":
274 | return render_template("sell.html", symbolList=symbolList)
275 | if request.method == "POST":
276 | # Find how much the user wants to sell and of what stock
277 | shares = request.form.get("shares")
278 | symbol = request.form.get("symbol")
279 |
280 | # If user doesn't enter a symbol, render apology
281 | if not symbol:
282 | return apology("You must enter a symbol")
283 |
284 | # IF user doesn't enter shares, render apology
285 | if not shares:
286 | return apology("You must enter a number of shares")
287 |
288 | # Get info on current price of stock and number of shares user has, assign variables
289 | shares = int(shares)
290 | info = lookup(symbol)
291 | stockList = db.execute("SELECT * FROM portfolio WHERE user_id=:user_id AND symbol=:symbol",
292 | user_id=user_id, symbol=symbol)
293 | price = round(info["price"], 2)
294 | userShares = int(stockList[0]["shares"])
295 | totalSale = round((shares * price), 2)
296 | stockVal = round((userShares * price), 2)
297 | newTotal = stockVal - totalSale
298 |
299 | # Find out how much cash the user has after sale
300 | cashList = db.execute("SELECT cash FROM users WHERE id=:user_id", user_id=user_id)
301 | cash = round(cashList[0]["cash"], 2)
302 | cashLeft = totalSale + cash
303 |
304 | # Render apology if user doesn't have enough shares to sell or doesn't own stock
305 | if userShares < shares:
306 | return apology("You don't own enough shares of this stock")
307 |
308 | # If all else is well, update portfolio and history to reflect the sale
309 |
310 | # If user is selling all their shares, delete row from portfolio
311 | elif userShares == shares:
312 | db.execute("DELETE FROM portfolio WHERE user_id=:user_id AND symbol=:symbol",
313 | user_id=user_id, symbol=symbol)
314 | # Else, update portfolio
315 | else:
316 | newShares = userShares - shares
317 | db.execute("""UPDATE portfolio SET shares=:shares, price=:price, total=:total
318 | WHERE user_id=:user_id AND symbol=:symbol""", shares=newShares, price=price,
319 | total=newTotal, user_id=user_id, symbol=symbol)
320 |
321 | # Add transaction to history table, total is the total of that transaction
322 | db.execute("""INSERT INTO history (user_id, stock, shares, time, price, total)
323 | VALUES (:user_id, :symbol, :shares, :datetime, :price, :total)""",
324 | user_id=user_id, symbol=symbol, shares=shares, datetime=datetime.datetime.now(),
325 | price=(-1)*price, total=(-1)*totalSale)
326 |
327 | # Update user's cash
328 | db.execute("UPDATE users SET cash = :cashLeft WHERE id=:user_id", cashLeft=cashLeft, user_id=user_id)
329 |
330 | return redirect("/sold")
331 |
332 |
333 | @app.route("/sold")
334 | @login_required
335 | def sold():
336 | # Get variables to display portfolio table on sold.html
337 | user_id = int(session['user_id'])
338 | cashRow = db.execute("SELECT cash FROM users WHERE id=:user_id", user_id=user_id)
339 | cashLeft = round(cashRow[0]['cash'], 2)
340 | stockList = db.execute("""SELECT symbol, stock, shares, price, total
341 | FROM portfolio WHERE user_id=:user_id""", user_id=user_id)
342 | totalVal = 0
343 | for row in stockList:
344 | totalVal += row["total"]
345 | totalVal = round(totalVal + cashLeft,2)
346 |
347 | return render_template("sold.html", cashLeft=cashLeft, stockList=stockList, totalVal=totalVal)
348 |
349 |
350 | def errorhandler(e):
351 | """Handle error"""
352 | if not isinstance(e, HTTPException):
353 | e = InternalServerError()
354 | return apology(e.name, e.code)
355 |
356 |
357 | # Listen for errors
358 | for code in default_exceptions:
359 | app.errorhandler(code)(errorhandler)
360 |
361 | # Give user the option to add cash
362 | @app.route("/addcash", methods=["GET", "POST"])
363 | @login_required
364 | def addcash():
365 | user_id = int(session["user_id"])
366 | if request.method == "POST":
367 | # Make sure user enters a valid dollar amount
368 | if not request.form.get("cash"):
369 | return apology("You must enter a valid dollar amount")
370 |
371 | # Get how much cash the user added and how much they had before
372 | cashAdd = round(float(request.form.get("cash")), 2)
373 | cashRow = db.execute("SELECT cash FROM users WHERE id=:user_id", user_id=user_id)
374 | cashLeft = round(cashRow[0]["cash"],2)
375 | cashAfter = cashLeft + cashAdd
376 |
377 | # Update users to reflect change
378 | db.execute("UPDATE users SET cash=:cashAfter WHERE id=:user_id", cashAfter=cashAfter, user_id=user_id)
379 |
380 | # Update history to show transaction
381 | db.execute("""INSERT INTO history (user_id, stock, shares, time, price, total)
382 | VALUES (:user_id, :stock, :shares, :time, :price, :total)""",
383 | user_id=user_id, stock="ADD CASH", shares=1, time=datetime.datetime.now(), price=cashAdd, total=cashAdd)
384 |
385 | # Get lists of stocks with symbols, names, shares, current price, and total
386 | stockList = db.execute("""SELECT symbol, stock, shares, price, total
387 | FROM portfolio WHERE user_id=:user_id""", user_id=user_id)
388 |
389 | # totalVal represents the total value of all stocks purchased
390 | totalVal = 0
391 | for row in stockList:
392 | totalVal += row["total"]
393 | totalVal = round(totalVal + cashAfter, 2)
394 |
395 | # Return to homepage
396 | return render_template("cashadded.html", stockList=stockList, cashLeft=cashAfter, totalVal=totalVal)
397 |
398 |
--------------------------------------------------------------------------------