Skip to content

Instantly share code, notes, and snippets.

@sojohnnysaid
Created January 2, 2020 12:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save sojohnnysaid/46a9143472ef0c8eb906da159837b47b to your computer and use it in GitHub Desktop.
Save sojohnnysaid/46a9143472ef0c8eb906da159837b47b to your computer and use it in GitHub Desktop.
CS50 Finance app
{% extends "layout.html" %}
{% block title %}
Apology
{% endblock %}
{% block main %}
<img alt="{{ top }}" class="border" src="http://memegen.link/custom/{{ top | urlencode }}/{{ bottom | urlencode }}.jpg?alt=https://i.imgur.com/CsCgN7Ll.png"/>
{% endblock %}
import os
from flask import Flask, flash, redirect, render_template, request, session
from flask_session import Session
from tempfile import mkdtemp
from werkzeug.exceptions import default_exceptions
from werkzeug.security import check_password_hash, generate_password_hash
from models import users_db, transactions_db, portfolio_db, SQL, sqlite3
from helpers import apology, login_required, lookup, usd, get_time
# Configure application
app = Flask(__name__)
# Ensure templates are auto-reloaded
app.config["TEMPLATES_AUTO_RELOAD"] = True
# Ensure responses aren't cached
@app.after_request
def after_request(response):
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
response.headers["Expires"] = 0
response.headers["Pragma"] = "no-cache"
return response
# Custom filter
app.jinja_env.filters["usd"] = usd
# Configure session to use filesystem (instead of signed cookies)
app.config["SESSION_FILE_DIR"] = mkdtemp()
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"
Session(app)
# Configure CS50 Library to use SQLite database
db = SQL("sqlite:///finance.db")
@app.route("/")
@login_required
def index():
"""Show portfolio of stocks"""
with sqlite3.connect('finance.db') as conn:
# get user balance
u_db = users_db(conn)
u_db.set_id(session['user_id'])
user_balance = int(u_db.get_balance()[0])
share_total = 0
# get holdings
p_db = portfolio_db(session['user_id'], conn)
holdings = p_db.get_holdings()
for holding in holdings:
holding['price'] = lookup(holding['symbol'])['price']
holding['total'] = holding['price'] * int(holding['shares'])
share_total += holding['total']
user = {}
user['balance'] = user_balance
user['grand_total'] = user_balance + share_total
return render_template("index.html", holdings=holdings, user=user)
@app.route("/buy", methods=["GET", "POST"])
@login_required
def buy():
"""Buy shares of stock"""
if request.method == "POST":
symbol = request.form.get("symbol")
shares = request.form.get("shares")
with sqlite3.connect('finance.db') as conn:
# check if # of shares positive integer
if int(shares) <= 0:
return apology("shares must be a positive number")
# get user balance
u_db = users_db(conn)
u_db.set_id(session['user_id'])
user_balance = u_db.get_balance()[0]
# get share price
share_price = lookup(symbol)['price']
total_cost = int(share_price) * int(shares)
print(user_balance)
print(total_cost)
#check if user has enough cash to purchase shares
if int(user_balance) < int(total_cost):
return apology("you do not have enough cash to make this purchase")
# update user cash
u_db.update_balance(user_balance - total_cost)
# update user portfolio
p_db = portfolio_db(session['user_id'], conn)
current_shares = p_db.get_number_of_shares(symbol)
if current_shares != None:
p_db.update_shares( str(int(current_shares[0]) + int(shares)), symbol)
else:
p_db.insert_shares(shares, symbol)
#update transaction history
t_db = transactions_db(session['user_id'], conn)
t_db.insert(('bought', symbol, share_price, shares, get_time()))
return redirect('/history')
return render_template("buy.html")
@app.route("/history")
@login_required
def history():
"""Show history of transactions"""
with sqlite3.connect('finance.db') as conn:
t_db = transactions_db(str(session['user_id']), conn)
transactions = t_db.get_history()
return render_template("history.html", transactions=transactions)
@app.route("/login", methods=["GET", "POST"])
def login():
"""Log user in"""
# Forget any user_id
session.clear()
# User reached route via POST (as by submitting a form via POST)
if request.method == "POST":
# Ensure username was submitted
if not request.form.get("username"):
return apology("must provide username", 403)
# Ensure password was submitted
elif not request.form.get("password"):
return apology("must provide password", 403)
# Query database for username
rows = db.execute("SELECT * FROM users WHERE username = :username",
username=request.form.get("username"))
# Ensure username exists and password is correct
if len(rows) != 1 or not check_password_hash(rows[0]["hash"], request.form.get("password")):
return apology("invalid username and/or password", 403)
# Remember which user has logged in
session["user_id"] = rows[0]["id"]
# Redirect user to home page
return redirect("/")
# User reached route via GET (as by clicking a link or via redirect)
else:
return render_template("login.html")
@app.route("/logout")
def logout():
"""Log user out"""
# Forget any user_id
session.clear()
# Redirect user to login form
return redirect("/")
@app.route("/quote", methods=["GET", "POST"])
@login_required
def quote():
"""Get stock quote."""
if request.method == "POST":
# show user a table with the stock price info
# stock is a dict (keys are 'name', 'price', and 'symbol')
stock = lookup(request.form.get("symbol"))
return render_template("quoted.html", stock=stock)
return render_template("quote.html")
@app.route("/register", methods=["GET", "POST"])
def register():
"""Register user"""
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
confirmation = request.form.get('confirmation')
print(password)
print(confirmation)
if username is '':
return apology('Username can not be left blank')
if password != confirmation:
return apology('Password and confirmation must match')
# store new user in users table
with sqlite3.connect('finance.db') as conn:
# access users model
u_db = users_db(conn)
# insert new user
# once user is registered this method returns the newly created user_id
user_id = u_db.register(username, generate_password_hash(password))
# set session id to new user idp
session['user_id'] = user_id
# redirect to homepage
return redirect('/')
return render_template("register.html")
@app.route("/sell", methods=["GET", "POST"])
@login_required
def sell():
"""Sell shares of stock"""
if request.method == "POST":
with sqlite3.connect('finance.db') as conn:
user_id = session["user_id"]
symbol = request.form.get("symbol")
shares_to_sell = int(request.form.get("shares"))
# portfolio_db model
p_db = portfolio_db(user_id, conn)
holdings = p_db.get_holdings()
# if user inputs shares less than or equal to 0
if shares_to_sell <= 0:
return apology("shares must be a positive integer")
# if user doesn't own the stock
if p_db.get_number_of_shares(symbol) == None:
return apology("you don't own any shares of this type")
current_shares = int(p_db.get_number_of_shares(symbol)[0])
# if user does not have enough stock to sell
if current_shares < shares_to_sell:
return apology("you don't have enough shares to sell")
## user has enough to sell ##
# get current price of symbol
share_price = lookup(symbol)['price']
# get users current balance
u_db = users_db(conn)
u_db.set_id(user_id)
current_balance = u_db.get_balance()[0]
# get shares_to_sell * price
users_new_balance = current_balance + (share_price * shares_to_sell)
# update users cash
u_db.update_balance(users_new_balance)
# subtract shares_sell from current_shares and update_shares_owned
p_db.update_shares((current_shares - shares_to_sell), symbol)
# update transaction history of this sale
t_db = transactions_db(user_id, conn)
t_db.insert(('sold', symbol, share_price, str(shares_to_sell), get_time()))
return redirect('/history')
return render_template("sell.html")
@app.route("/preferences", methods=["GET", "POST"])
@login_required
def preferences():
"""reset password"""
if request.method == "POST":
old_password = request.form.get("old_password")
new_password = request.form.get("new_password")
confirmation = request.form.get("confirmation")
with sqlite3.connect('finance.db') as conn:
u_db = users_db(conn)
u_db.set_id(session['user_id'])
u_db.update_password(generate_password_hash(new_password))
return render_template("preferences.html")
def errorhandler(e):
"""Handle error"""
return apology(e.name, e.code)
# listen for errors
for code in default_exceptions:
app.errorhandler(code)(errorhandler)
{% extends "layout.html" %}
{% block title %}
Buy
{% endblock %}
{% block main %}
<form action="/buy" method="post">
<div class="form-group">
<input autocomplete="off" autofocus class="form-control" name="symbol" placeholder="symbol" type="text"/>
</div>
<div class="form-group">
<input autocomplete="off" autofocus class="form-control" name="shares" placeholder="shares" type="text"/>
</div>
<button class="btn btn-primary" type="submit">Buy</button>
</form>
{% endblock %}
import requests
import urllib.parse
from datetime import datetime
from flask import redirect, render_template, request, session
from functools import wraps
def apology(message, code=400):
"""Render message as an apology to user."""
def escape(s):
"""
Escape special characters.
https://github.com/jacebrowning/memegen#special-characters
"""
for old, new in [("-", "--"), (" ", "-"), ("_", "__"), ("?", "~q"),
("%", "~p"), ("#", "~h"), ("/", "~s"), ("\"", "''")]:
s = s.replace(old, new)
return s
return render_template("apology.html", top=code, bottom=escape(message)), code
def login_required(f):
"""
Decorate routes to require login.
http://flask.pocoo.org/docs/0.12/patterns/viewdecorators/
"""
@wraps(f)
def decorated_function(*args, **kwargs):
if session.get("user_id") is None:
return redirect("/login")
return f(*args, **kwargs)
return decorated_function
def lookup(symbol):
"""Look up quote for symbol."""
# Contact API
try:
response = requests.get(f"https://cloud.iexapis.com/stable/stock/{urllib.parse.quote_plus(symbol)}/quote?token=pk_6d475d970a9f47b7b2b8440acf1d2c7e")
response.raise_for_status()
except requests.RequestException:
return None
# Parse response
try:
quote = response.json()
return {
"name": quote["companyName"],
"price": float(quote["latestPrice"]),
"symbol": quote["symbol"]
}
except (KeyError, TypeError, ValueError):
return None
def usd(value):
"""Format value as USD."""
return f"${value:,.2f}"
def get_time():
now = datetime.now()
#12-hour format
return now.strftime('%m/%d/%Y | %I:%M:%S %p UTC')
{% extends "layout.html" %}
{% block title %}
History
{% endblock %}
{% block main %}
<!--Table-->
<table id="tablePreview" class="table table-striped table-sm">
<!--Table head-->
<thead>
<tr>
<th>Type</th>
<th>Symbol</th>
<th>Price</th>
<th>Shares</th>
<th>Date/Time</th>
</tr>
</thead>
<!--Table head-->
<!--Table body-->
<tbody>
{% for transaction in transactions %}
<tr>
<td>{{ transaction['type'] }}</td>
<td>{{ transaction['symbol'] }}</td>
<td>{{ transaction['price'] | int | usd }}</td>
<td>{{ transaction['shares'] }}</td>
<td>{{ transaction['date_time'] }}</td>
</tr>
{% endfor %}
</tbody>
<!--Table body-->
</table>
<!--Table-->
{% endblock %}
{% extends "layout.html" %}
{% block title %}
Home
{% endblock %}
{% block main %}
<div class="jumbotron">
<p class="lead">Cash Balance: {{ user['balance'] | usd }}</p>
<p class="lead">Grand Total: {{ user['grand_total'] | usd }}</p>
</div>
<!--Table-->
<table id="tablePreview" class="table table-striped table-sm">
<!--Table head-->
<thead>
<tr>
<th>Symbol</th>
<th>Shares</th>
<th>Price</th>
<th>Total</th>
</tr>
</thead>
<!--Table head-->
<!--Table body-->
<tbody>
{% for holding in holdings %}
<tr>
<td>{{ holding['symbol'] }}</td>
<td>{{ holding['shares'] }}</td>
<td>{{ holding['price'] | usd }}</td>
<td>{{ holding['total'] | usd }}</td>
</tr>
{% endfor %}
</tbody>
<!--Table body-->
</table>
<!--Table-->
{% endblock %}
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
<!-- documentation at http://getbootstrap.com/docs/4.0/, alternative themes at https://bootswatch.com/4-alpha/ -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" rel="stylesheet"/>
<link href="/static/styles.css" rel="stylesheet"/>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js"></script>
<title>C$50 Finance: {% block title %}{% endblock %}</title>
</head>
<body>
<nav class="navbar navbar-expand-md navbar-light bg-light border">
<a class="navbar-brand" href="/"><span class="blue">C</span><span class="red">$</span><span class="yellow">5</span><span class="green">0</span> <span class="red">Finance</span></a>
<button aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation" class="navbar-toggler" data-target="#navbar" data-toggle="collapse" type="button">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbar">
{% if session.user_id %}
<ul class="navbar-nav mr-auto mt-2">
<li class="nav-item"><a class="nav-link" href="/quote">Quote</a></li>
<li class="nav-item"><a class="nav-link" href="/buy">Buy</a></li>
<li class="nav-item"><a class="nav-link" href="/sell">Sell</a></li>
<li class="nav-item"><a class="nav-link" href="/history">History</a></li>
<li class="nav-item"><a class="nav-link" href="/preferences">Preferences</a></li>
</ul>
<ul class="navbar-nav ml-auto mt-2">
<li class="nav-item"><a class="nav-link" href="/logout">Log Out</a></li>
</ul>
{% else %}
<ul class="navbar-nav ml-auto mt-2">
<li class="nav-item"><a class="nav-link" href="/register">Register</a></li>
<li class="nav-item"><a class="nav-link" href="/login">Log In</a></li>
</ul>
{% endif %}
</div>
</nav>
{% if get_flashed_messages() %}
<header>
<div class="alert alert-primary border text-center" role="alert">
{{ get_flashed_messages() | join(" ") }}
</div>
</header>
{% endif %}
<main class="container p-5">
{% block main %}{% endblock %}
</main>
<footer class="small text-center text-muted">
Data provided for free by <a href="https://iextrading.com/developer">IEX</a>. View <a href="https://iextrading.com/api-exhibit-a/">IEX’s Terms of Use</a>.
</footer>
</body>
</html>
{% extends "layout.html" %}
{% block title %}
Log In
{% endblock %}
{% block main %}
<form action="/login" method="post">
<div class="form-group">
<input autocomplete="off" autofocus class="form-control" name="username" placeholder="Username" type="text"/>
</div>
<div class="form-group">
<input class="form-control" name="password" placeholder="Password" type="password"/>
</div>
<button class="btn btn-primary" type="submit">Log In</button>
</form>
{% endblock %}
from cs50 import SQL
import sqlite3
class users_db:
def __init__(self, conn):
self.id = ''
self.conn = conn
self.c = conn.cursor()
def register(self, username, hash):
row = self.c.execute('INSERT INTO users (username, hash, cash) VALUES (?, ?, 10000)', (username, hash))
self.conn.commit()
return self.c.lastrowid
def set_id(self, id):
self.id = id
def get_balance(self):
self.c.execute('SELECT cash FROM users WHERE id=?', (self.id,))
return self.c.fetchone()
def update_balance(self, new_balance):
self.c.execute('UPDATE users SET cash=? WHERE id=?', (new_balance,self.id))
self.conn.commit()
def update_password(self, hash):
self.c.execute('UPDATE users SET hash=? WHERE id=?', (hash, self.id))
self.conn.commit()
class transactions_db:
def __init__(self, user_id, conn):
self.id = user_id
self.conn = conn
self.c = conn.cursor()
def get_history(self):
self.c.execute('SELECT * FROM transactions where user_id=?', (self.id))
results = []
for data in self.c.fetchall():
col = {}
col['type'] = data[1]
col['symbol'] = data[2]
col['price'] = data[3]
col['shares'] = data[4]
col['date_time'] = data[5]
results.append(col)
return results
def insert(self, row):
self.c.execute('INSERT INTO transactions VALUES (?,?,?,?,?,?)', (self.id,) + row)
self.conn.commit()
class portfolio_db:
def __init__(self, user_id, conn):
self.id = user_id
self.conn = conn
self.c = conn.cursor()
def get_holdings(self):
self.c.execute('SELECT symbol, shares FROM portfolio WHERE user_id=?', (self.id,))
results = []
for data in self.c.fetchall():
col = {}
col['symbol'] = data[0]
col['shares'] = data[1]
results.append(col)
return results
def get_number_of_shares(self, symbol):
self.c.execute('SELECT shares FROM portfolio WHERE user_id=? AND symbol=?', (self.id, symbol))
return self.c.fetchone()
def update_shares(self, shares, symbol):
self.c.execute('UPDATE portfolio SET shares=? WHERE symbol=? AND user_id=?', (shares, symbol, self.id))
self.conn.commit()
def insert_shares(self, shares, symbol):
self.c.execute('INSERT INTO portfolio VALUES (?,?,?)', (self.id, symbol, shares))
self.conn.commit()
{% extends "layout.html" %}
{% block title %}
Preferences
{% endblock %}
{% block main %}
<form action="/preferences" method="post">
<div class="form-group">
<input autocomplete="off" autofocus class="form-control" name="old_password" placeholder="old password" type="password"/>
</div>
<div class="form-group">
<input autocomplete="off" autofocus class="form-control" name="new_password" placeholder="new password" type="password"/>
</div>
<div class="form-group">
<input class="form-control" name="confirmation" placeholder="confirm password" type="password"/>
</div>
<button class="btn btn-primary" type="submit">Reset Password</button>
</form>
{% endblock %}
{% extends "layout.html" %}
{% block title %}
Quote
{% endblock %}
{% block main %}
<form action="/quote" method="post">
<div class="form-group">
<input autocomplete="off" autofocus class="form-control" name="symbol" placeholder="symbol" type="text"/>
</div>
<button class="btn btn-primary" type="submit">Get Quote</button>
</form>
{% endblock %}
{% extends "layout.html" %}
{% block title %}
Quoted
{% endblock %}
{% block main %}
<!--Table-->
<table id="tablePreview" class="table table-striped table-sm">
<!--Table head-->
<thead>
<tr>
<th>Symbol</th>
<th>Company Name</th>
<th>Price</th>
</tr>
</thead>
<!--Table head-->
<!--Table body-->
<tbody>
<tr>
<td>{{ stock['symbol'] }}</td>
<td>{{ stock['name'] }}</td>
<td>{{ stock['price'] | usd }}</td>
</tr>
</tbody>
<!--Table body-->
</table>
<!--Table-->
{% endblock %}
{% extends "layout.html" %}
{% block title %}
Register
{% endblock %}
{% block main %}
<form action="/register" method="post">
<div class="form-group">
<input autocomplete="off" autofocus class="form-control" name="username" placeholder="username" type="text"/>
</div>
<div class="form-group">
<input autocomplete="off" autofocus class="form-control" name="password" placeholder="new password" type="password"/>
</div>
<div class="form-group">
<input class="form-control" name="confirmation" placeholder="confirm password" type="password"/>
</div>
<button class="btn btn-primary" type="submit">Register</button>
</form>
{% endblock %}
{% extends "layout.html" %}
{% block title %}
Sell
{% endblock %}
{% block main %}
<form action="/sell" method="post">
<div class="form-group">
<input autocomplete="off" autofocus class="form-control" name="symbol" placeholder="symbol" type="text"/>
</div>
<div class="form-group">
<input class="form-control" name="shares" placeholder="shares" type="text"/>
</div>
<button class="btn btn-primary" type="submit">Sell</button>
</form>
{% endblock %}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment