Skip to content

Commit

Permalink
migrated codebase to SQL Alchemy
Browse files Browse the repository at this point in the history
  • Loading branch information
eddyharrington committed May 21, 2020
1 parent f8ce4b7 commit 240abba
Show file tree
Hide file tree
Showing 14 changed files with 295 additions and 194 deletions.
53 changes: 28 additions & 25 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import json
import requests
import copy
import config
import calendar
import tendie_dashboard
import tendie_expenses
Expand All @@ -11,9 +10,10 @@
import tendie_reports
import tendie_account

from cs50 import SQL
from flask import Flask, jsonify, redirect, render_template, request, session
from flask_session import Session
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from tempfile import mkdtemp
from werkzeug.exceptions import default_exceptions, HTTPException, InternalServerError
from werkzeug.security import check_password_hash, generate_password_hash
Expand All @@ -24,6 +24,9 @@
# Configure application
app = Flask(__name__)

# Set app key
app.secret_key = os.environ.get("SECRET_KEY")

# Ensure templates are auto-reloaded
app.config["TEMPLATES_AUTO_RELOAD"] = True

Expand All @@ -45,9 +48,9 @@ def after_request(response):
# Custom filter
app.jinja_env.filters["usd"] = usd

# Configure CS50 Library to use SQLite database
# db = SQL("sqlite:///localhostDBForTesting.db") # can be used for testing locally
db = SQL(config.testingDB)
# Create engine object to manage connections to DB, and scoped session to separate user interactions with DB
engine = create_engine(os.getenv("DATABASE_URL"))
db = scoped_session(sessionmaker(bind=engine))


@app.route("/register", methods=["GET", "POST"])
Expand All @@ -59,7 +62,7 @@ def register():
# Query DB for all existing user names and make sure new username isn't already taken
username = request.form.get("username").strip()
existingUsers = db.execute(
"SELECT username FROM users WHERE LOWER(username) = :username", username=username.lower())
"SELECT username FROM users WHERE LOWER(username) = :username", {"username": username.lower()}).fetchone()
if existingUsers:
return render_template("register.html", username=username)

Expand All @@ -75,12 +78,14 @@ def register():
# Insert user into the database
hashedPass = generate_password_hash(password)
now = datetime.now().strftime("%m/%d/%Y %H:%M:%S")
newUserID = db.execute("INSERT INTO users (username, hash, registerDate, lastLogin) VALUES (:username, :hashedPass, :registerDate, :lastLogin)",
username=username, hashedPass=hashedPass, registerDate=now, lastLogin=now)
newUserID = db.execute("INSERT INTO users (username, hash, registerDate, lastLogin) VALUES (:username, :hashedPass, :registerDate, :lastLogin) RETURNING id",
{"username": username, "hashedPass": hashedPass, "registerDate": now, "lastLogin": now}).fetchone()[0]
db.commit()

# Create default spending categories for user
db.execute("INSERT INTO userCategories (category_id, user_id) VALUES (1, :usersID), (2, :usersID), (3, :usersID), (4, :usersID), (5, :usersID), (6, :usersID), (7, :usersID), (8, :usersID)",
usersID=newUserID)
{"usersID": newUserID})
db.commit()

# Auto-login the user after creating their username
session["user_id"] = newUserID
Expand Down Expand Up @@ -113,7 +118,7 @@ def login():

# Query database for username
rows = db.execute("SELECT * FROM users WHERE username = :username",
username=request.form.get("username"))
{"username": request.form.get("username")}).fetchall()

# Ensure username exists and password is correct
if len(rows) != 1 or not check_password_hash(rows[0]["hash"], request.form.get("password")):
Expand All @@ -125,7 +130,8 @@ def login():
# Record the login time
now = datetime.now().strftime("%m/%d/%Y %H:%M:%S")
db.execute(
"UPDATE users SET lastLogin = :lastLogin WHERE id = :usersID", lastLogin=now, usersID=session["user_id"])
"UPDATE users SET lastLogin = :lastLogin WHERE id = :usersID", {"lastLogin": now, "usersID": session["user_id"]})
db.commit()

# Redirect user to home page
return redirect("/")
Expand Down Expand Up @@ -401,9 +407,6 @@ def createbudget():
# Get the users income
income = tendie_account.getIncome(session["user_id"])

# Get the users current budgets
budgets = tendie_budgets.getBudgets(session["user_id"])

# Get the users total budgeted amount
budgeted = tendie_budgets.getTotalBudgeted(session["user_id"])

Expand Down Expand Up @@ -667,6 +670,17 @@ def spendingreport():
return render_template("spendingreport.html", spending_trends_chart=spendingReport["chart"], spending_trends_table=spendingReport["table"], categories=spendingReport["categories"])


@app.route("/payersreport", methods=["GET"])
@login_required
def payersreport():
"""View payers spending report"""

# Generate a data structure that combines the users payers and expense data for chart and table
payersReport = tendie_reports.generatePayersReport(session["user_id"])

return render_template("payersreport.html", payers=payersReport)


@app.route("/account", methods=["GET", "POST"])
@login_required
def updateaccount():
Expand Down Expand Up @@ -795,17 +809,6 @@ def updateaccount():
return render_template("account.html", username=user["name"], income=user["income"], payers=user["payers"], stats=user["stats"], newIncome=None, addPayer=None, renamedPayer=None, deletedPayer=None, updatedPassword=None)


@app.route("/payersreport", methods=["GET"])
@login_required
def payersreport():
"""View payers spending report"""

# Generate a data structure that combines the users payers and expense data for chart and table
payersReport = tendie_reports.generatePayersReport(session["user_id"])

return render_template("payersreport.html", payers=payersReport)


# Handle errors by rendering apology
def errorhandler(e):
"""Handle error"""
Expand Down
21 changes: 21 additions & 0 deletions helpers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import requests
import urllib.parse
import decimal

from flask import redirect, render_template, request, session
from functools import wraps
Expand Down Expand Up @@ -38,3 +39,23 @@ def decorated_function(*args, **kwargs):
def usd(value):
"""Format value as USD."""
return f"${value:,.2f}"


# Converts a list of SQL Alchemy RowProxy objects into a list of dictionary objects with the column name as the key (https://github.com/cs50/python-cs50/blob/develop/src/cs50/sql.py#L328)
# Used for SQL SELECT .fetchall() results
def convertSQLToDict(listOfRowProxy):
# Coerce types
rows = [dict(row) for row in listOfRowProxy]
for row in rows:
for column in row:

# Coerce decimal.Decimal objects to float objects
# https://groups.google.com/d/msg/sqlalchemy/0qXMYJvq8SA/oqtvMD9Uw-kJ
if type(row[column]) is decimal.Decimal:
row[column] = float(row[column])

# Coerce memoryview objects (as from PostgreSQL's bytea columns) to bytes
elif type(row[column]) is memoryview:
row[column] = bytes(row[column])

return rows
2 changes: 1 addition & 1 deletion templates/account.html
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ <h5 class="modal-title" id="deleteModalLabel">Delete Payer</h5>
</ul>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-danger" form="deletePayer" id="btnDeletePayer" name="btnDeletePayer">Delete Category</button>
<button type="submit" class="btn btn-danger" form="deletePayer" id="btnDeletePayer" name="btnDeletePayer">Delete Payer</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions templates/categories.html
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,9 @@ <h5 class="modal-title" id="deleteModalLabel">Delete Category</h5>
</form>
<p class="text-danger"><strong>Are you sure you want to delete this category?</strong></p>
<ul class="text-danger" style="text-align:left">
<li class="text-danger small">It will also be deleted from any budgets that currently use it - make sure to update your budget!</li>
<li class="text-danger small">The category will no longer be selectable in Expenses or Budgets</li>
<li class="text-danger small">Expense history using the old name will not be affected unless you update the expense and choose a new category</li>
<li class="text-danger small">Reports and Dashboard charts will continue to show the old name unless you update your expense history to a new category</li>
<li class="text-danger small">Dashboard charts, Expense History, and Reports will continue to show the category unless you update your expense history to a new category</li>
</ul>
</div>
<div class="modal-footer">
Expand Down
2 changes: 1 addition & 1 deletion templates/expensehistory.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ <h1>Expense History</h1>
{% for result in history %}
<tr>
<td>{{ loop.index }}</td>
<td><a href="#" data-toggle="modal" data-target="#updateModal" data-description="{{ result['description'] }}" data-category="{{ result['category'] }}" data-date="{{ result['date'] }}" data-payer="{{ result['payer'] }}" data-amount="{{ result['amount'] }}" data-submittime="{{ result['submitTime'] }}">{{ result["description"] }}</a></td>
<td><a href="#" data-toggle="modal" data-target="#updateModal" data-description="{{ result['description'] }}" data-category="{{ result['category'] }}" data-date="{{ result['date'] }}" data-payer="{{ result['payer'] }}" data-amount="{{ result['amount'] }}" data-submittime="{{ result['submittime'] }}">{{ result["description"] }}</a></td>
<td>{{ result["category"] }}</td>
<td>{{ result["date"] }}</td>
<td>{{ result["payer"] }}</td>
Expand Down
4 changes: 2 additions & 2 deletions templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ <h4 style="text-align:left">Last 5 Expenses</h4>
<tr>
<td>{{ expense['description'] }}</td>
<td>{{ expense['category'] }}</td>
<td>{{ expense['expenseDate'] }}</td>
<td>{{ expense['expensedate'] }}</td>
<td>{{ expense['payer'] }}</td>
<td>{{ expense['amount'] | usd }}</td>
</tr>
Expand Down Expand Up @@ -336,7 +336,7 @@ <h2 style="text-align:left">Payer Spending</h2>

{% if spending_trends %}
<script>
var trendsData = JSON.stringify({{spending_trends | tojson }});
var trendsData = JSON.stringify({{ spending_trends | tojson }});
loadTrendsData(trendsData);
</script>
{% endif %}
Expand Down
2 changes: 1 addition & 1 deletion templates/monthlyreport.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ <h3>Last 12 months of spending</h3>
<td>{{ loop.index }}</td>
<td>{{ record["description"] }}</td>
<td>{{ record["category"] }}</td>
<td>{{ record["expenseDate"] }}</td>
<td>{{ record["expensedate"] }}</td>
<td>{{ record["payer"] }}</td>
<td>{{ record["amount"] | usd}}</td>
</tr>
Expand Down
4 changes: 2 additions & 2 deletions templates/spendingreport.html
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ <h3>Total Paid Per Month By Category</h3>
{% if spending_trends_chart or spending_trends_table %}
<script src="/static/js/reports.js"></script>
<script>
var trendsData_chart = JSON.stringify({{spending_trends_chart | tojson }});
var trendsData_table = JSON.stringify({{spending_trends_table | tojson }});
var trendsData_chart = JSON.stringify({{ spending_trends_chart | tojson }});
var trendsData_table = JSON.stringify({{ spending_trends_table | tojson }});
loadTrendsData(trendsData_chart, trendsData_table);
</script>
{% endif %}
Expand Down
Loading

0 comments on commit 240abba

Please sign in to comment.