└── 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 | {{ top }} 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 |
9 |
10 | 11 |
12 | 13 | 14 |
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 |
9 |
10 | 11 |
12 |
13 | 14 |
15 | 16 |
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 |
9 |
10 | 11 |
12 |
13 | 14 |
15 | 16 |
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 |
9 |
10 | 11 |
12 |
13 | 14 |
15 |
16 | 17 |
18 | 19 |
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 |
9 |
10 | 16 |
17 |
18 | 19 |
20 | 21 |
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 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {% for row in historyList %} 20 | 21 | 22 | 23 | {% if row["price"] < 0 %} 24 | 25 | 26 | {% else %} 27 | 28 | 29 | {% endif %} 30 | 31 | 32 | {% endfor %} 33 | 34 |
SymbolSharesPriceBought/Sold?Time of Transaction
{{ row["stock"] }}{{ row["shares"] }}${{ row["price"] * -1 }}Sold${{ row["price"] }}Bought{{ row["time"] }}
35 | {% endblock %} -------------------------------------------------------------------------------- /CS50 Finance/templates/sold.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | 3 | {% block title %} 4 | Sold 5 | {% endblock %} 6 | 7 | {% block main %} 8 |
9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {% for row in stockList %} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {% endfor %} 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
SymbolNameSharesPriceTotal
{{ row["symbol"] }}{{ row["stock"] }}{{ row["shares"] }}${{ row["price"] }}${{ row["total"] }}
CASH${{cashLeft}}
${{totalVal}}
44 | {% endblock %} -------------------------------------------------------------------------------- /CS50 Finance/templates/bought.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | 3 | {% block title %} 4 | Bought 5 | {% endblock %} 6 | 7 | {% block main %} 8 |
9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {% for row in stockList %} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {% endfor %} 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
SymbolNameSharesPriceTotal
{{ row["symbol"] }}{{ row["stock"] }}{{ row["shares"] }}${{ row["price"] }}${{ row["total"] }}
CASH${{cashLeft}}
${{totalVal}}
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 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {% for row in stockList %} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | {% endfor %} 28 | 29 | 30 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
SymbolNameSharesPriceTotal
{{ row["symbol"] }}{{ row["stock"] }}{{ row["shares"] }}${{ row["price"] }}${{ row["total"] }}
CASH
31 |
32 | 33 |
34 | 35 |
${{cashLeft}}
${{totalVal}}
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 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {% for row in stockList %} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | {% endfor %} 28 | 29 | 30 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
SymbolNameSharesPriceTotal
{{ row["symbol"] }}{{ row["stock"] }}{{ row["shares"] }}${{ row["price"] }}${{ row["total"] }}
CASH
31 |
32 | 33 |
34 | 35 |
${{cashLeft}}
${{totalVal}}
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 |
56 | 59 |
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 | --------------------------------------------------------------------------------