Skip to content

Commit

Permalink
add pset7
Browse files Browse the repository at this point in the history
  • Loading branch information
mareksuscak committed Apr 14, 2018
1 parent 025ea8c commit b34cbd3
Show file tree
Hide file tree
Showing 10 changed files with 366 additions and 0 deletions.
1 change: 1 addition & 0 deletions pset7/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://docs.cs50.net/2018/x/psets/7/pset7.html
1 change: 1 addition & 0 deletions pset7/finance/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://docs.cs50.net/2018/x/psets/7/finance/finance.html
139 changes: 139 additions & 0 deletions pset7/finance/application.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import os

from cs50 import SQL
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 helpers import apology, login_required, lookup, usd

# Ensure environment variable is set
if not os.environ.get("API_KEY"):
raise RuntimeError("API_KEY not set")

# 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"""
return apology("TODO")


@app.route("/buy", methods=["GET", "POST"])
@login_required
def buy():
"""Buy shares of stock"""
return apology("TODO")


@app.route("/history")
@login_required
def history():
"""Show history of transactions"""
return apology("TODO")


@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."""
return apology("TODO")


@app.route("/register", methods=["GET", "POST"])
def register():
"""Register user"""
return apology("TODO")


@app.route("/sell", methods=["GET", "POST"])
@login_required
def sell():
"""Sell shares of stock"""
return apology("TODO")


def errorhandler(e):
"""Handle error"""
return apology(e.name, e.code)


# listen for errors
for code in default_exceptions:
app.errorhandler(code)(errorhandler)
Binary file added pset7/finance/finance.db
Binary file not shown.
84 changes: 84 additions & 0 deletions pset7/finance/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import csv
import os
import urllib.request

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."""

# Reject symbol if it starts with caret
if symbol.startswith("^"):
return None

# Reject symbol if it contains comma
if "," in symbol:
return None

# Query Alpha Vantage for quote
# https://www.alphavantage.co/documentation/
try:

# GET CSV
url = f"https://www.alphavantage.co/query?apikey={os.getenv('API_KEY')}&datatype=csv&function=TIME_SERIES_INTRADAY&interval=1min&symbol={symbol}"
webpage = urllib.request.urlopen(url)

# Parse CSV
datareader = csv.reader(webpage.read().decode("utf-8").splitlines())

# Ignore first row
next(datareader)

# Parse second row
row = next(datareader)

# Ensure stock exists
try:
price = float(row[4])
except:
return None

# Return stock's name (as a str), price (as a float), and (uppercased) symbol (as a str)
return {
"price": price,
"symbol": symbol.upper()
}

except:
return None


def usd(value):
"""Format value as USD."""
return f"${value:,.2f}"
3 changes: 3 additions & 0 deletions pset7/finance/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
cs50
Flask
Flask-Session
47 changes: 47 additions & 0 deletions pset7/finance/static/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
nav .navbar-brand
{
/* size for brand */
font-size: xx-large;
}

/* colors for brand */
nav .navbar-brand .blue
{
color: #537fbe;
}
nav .navbar-brand .red
{
color: #ea433b;
}
nav .navbar-brand .yellow
{
color: #f5b82e;
}
nav .navbar-brand .green
{
color: #2e944b;
}

main .form-control
{
/* center form controls */
display: inline-block;

/* override Bootstrap's 100% width for form controls */
width: auto;
}

main
{
/* scroll horizontally as needed */
overflow-x: auto;

/* center contents */
text-align: center;
}

main img
{
/* constrain images on small screens */
max-width: 100%;
}
9 changes: 9 additions & 0 deletions pset7/finance/templates/apology.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{% 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 %}
65 changes: 65 additions & 0 deletions pset7/finance/templates/layout.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<!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>
</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>

</body>

</html>
17 changes: 17 additions & 0 deletions pset7/finance/templates/login.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{% 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 %}

0 comments on commit b34cbd3

Please sign in to comment.