Skip to content

Commit

Permalink
added budget features
Browse files Browse the repository at this point in the history
  • Loading branch information
eddyharrington committed May 7, 2020
1 parent 42f9b9f commit 70cf646
Show file tree
Hide file tree
Showing 9 changed files with 990 additions and 31 deletions.
142 changes: 139 additions & 3 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import config
import tendie_dashboard
import tendie_expenses
import tendie_budgets

from cs50 import SQL
from flask import Flask, flash, jsonify, redirect, render_template, request, session
Expand Down Expand Up @@ -48,6 +49,7 @@ def after_request(response):
def register():
"""Register user"""

# User reached route via POST
if request.method == "POST":
# Query DB for all existing user names and make sure new username isn't already taken
username = request.form.get("username").strip()
Expand Down Expand Up @@ -80,7 +82,7 @@ def register():
# Redirect user to home page
return redirect("/")

# User reached route via GET (as by clicking a link or via redirect)
# User reached route via GET
else:
return render_template("register.html")

Expand All @@ -92,7 +94,7 @@ def login():
# Forget any user_id
session.clear()

# User reached route via POST (as by submitting a form via POST)
# User reached route via POST
if request.method == "POST":

# Ensure username was submitted
Expand All @@ -117,7 +119,7 @@ def login():
# Redirect user to home page
return redirect("/")

# User reached route via GET (as by clicking a link or via redirect)
# User reached route via GET
else:
return render_template("login.html")

Expand Down Expand Up @@ -228,6 +230,8 @@ def addexpenses():

# Redirect to results page and render a summary of the submitted expenses
return render_template("expensed.html", results=expenses)

# User reached route via GET
else:
# Get the users spend categories
categories = tendie_dashboard.getSpendCategories(session["user_id"])
Expand Down Expand Up @@ -300,6 +304,138 @@ def history():
return render_template("expensed.html", results=expensed)


@app.route("/budgets", methods=["GET", "POST"])
@login_required
def budgets():
"""Manage budgets"""

# User reached route via GET
if request.method == "GET":
# Get the users income
income = tendie_dashboard.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"])

return render_template("budgets.html", income=income, budgets=budgets, budgeted=budgeted, deletedBudgetName=None)

# User reached route via POST
else:
# Get the name of the budget the user wants to delete
budgetName = request.form.get("delete").strip()

# Delete the budget
deletedBudgetName = tendie_budgets.deleteBudget(
budgetName, session["user_id"])

# Render the budgets page with a success message, otherwise throw an error/apology
if deletedBudgetName:
# Get the users income, current budgets, and sum their budgeted amount unless they don't have any budgets (same steps as a GET for this route)
income = tendie_dashboard.getIncome(session["user_id"])
budgets = tendie_budgets.getBudgets(session["user_id"])
budgeted = tendie_budgets.getTotalBudgeted(session["user_id"])

return render_template("budgets.html", income=income, budgets=budgets, budgeted=budgeted, deletedBudgetName=deletedBudgetName)
else:
return apology("Uh oh! Your budget could not be deleted.")


@app.route("/createbudget", methods=["GET", "POST"])
@login_required
def createbudget():
"""Create a budget"""

# User reached route via POST
if request.method == "POST":
# Get all of the budget info provided from the HTML form
formData = list(request.form.items())

# Generate data structure to hold budget info from form
budgetDict = tendie_budgets.generateBudgetFromForm(formData)
# Render error message if budget name or categories contained invalid data
if "apology" in budgetDict:
return apology(budget["apology"])
else:
# Add budget to DB for user
budget = tendie_budgets.createBudget(
budgetDict, session["user_id"])
# Render error message if budget name is a duplicate of another budget the user has
if "apology" in budget:
return apology(budget["apology"])
else:
return render_template("budgetcreated.html", results=budget)
else:
# TODO need to make sure user has at least 1 spend category otherwise no selects will appear for
# budgeting (need to design something here that works for every page e.g. addexpense). Initial idea is to not allow user to delete ALL categories, must have at least 1.

# Get the users income
income = tendie_dashboard.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"])

# Get the users spend categories
categories = tendie_dashboard.getSpendCategories(session["user_id"])

return render_template("createbudget.html", income=income, budgeted=budgeted, categories=categories)


@app.route("/updatebudget/<urlvar_budgetname>", methods=["GET", "POST"])
@login_required
def updatebudget(urlvar_budgetname):
"""Update a budget"""

# User reached route via POST
if request.method == "POST":
# Get all of the budget info provided from the HTML form
formData = list(request.form.items())

# Generate data structure to hold budget info from form
budgetDict = tendie_budgets.generateBudgetFromForm(formData)

# Render error message if budget name or categories contained invalid data
if "apology" in budgetDict:
return apology(budget["apology"])
else:
# Update budget in the DB for user
budget = tendie_budgets.updateBudget(
urlvar_budgetname, budgetDict, session["user_id"])

# Render error message if budget name is a duplicate of another budget the user has
if "apology" in budget:
return apology(budget["apology"])
else:
return render_template("budgetcreated.html", results=budget)

# User reached route via GET
else:
# Get the budget details from the DB based on the budget name provided via URL. Throw an apology/error if budget can't be found.
budgetID = tendie_budgets.getBudgetID(
urlvar_budgetname, session["user_id"])
if budgetID is None:
return apology("'" + urlvar_budgetname + "' budget does not exist.")
else:
budget = tendie_budgets.getBudgetByID(budgetID, session["user_id"])

# Get the users income
income = tendie_dashboard.getIncome(session["user_id"])

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

# Generate the full, updatable budget data structure (name, amount for budget w/ all categories and their budgeted amounts)
budget = tendie_budgets.getUpdatableBudget(budget, session["user_id"])

# Render the budget update page
return render_template("updatebudget.html", income=income, budgeted=budgeted, budget=budget)


# Handle errors by rendering apology
def errorhandler(e):
"""Handle error"""
Expand Down
51 changes: 51 additions & 0 deletions templates/budgetcreated.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{% extends "layout.html" %}

{% block title %}
Budget Created
{% endblock %}

{% block main %}
<h1>Budget Created</h1>
<br>
<div class="alert alert-dismissible alert-success">
<button type="button" class="close" data-dismiss="alert">×</button>
<strong>Well done! </strong>This budget will now appear in your <a href="/">Dashboard</a> and <a href="/reports">Reports</a> where you can monitor how well you're spending against your budget.
</div>
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-body">
<h5 class="card-title">Budget Details</h5>
<p><strong>Name</strong>: {{ results["name"] }}</p>
<p><strong>Amount</strong>: {{ results["amount"] | usd }}</p>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th scope="col">Category</th>
<th scope="col">%</th>
<th scope="col">Amount</th>
</tr>
</thead>
<tbody>
{% for category in results["categories"] %}
{% set amount = (results["amount"] * category["percent"]) %}
<tr>
<td>{{ category["name"] }}</td>
<td>{{ ((category["percent"] * 100) | string)[:-2] }}%</td>
<td>{{ amount | usd }}
</tr>
{% endfor %}
<tr>
<td></td>
<td>100%</td>
<td>{{ results["amount"] | usd }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
102 changes: 102 additions & 0 deletions templates/budgets.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
{% extends "layout.html" %}

{% block title %}
Manage Budgets
{% endblock %}

{% block main %}
<h1>Manage Budgets</h1>
<br>
{% if deletedBudgetName != None %}
<div class="alert alert-dismissible alert-danger">
<button type="button" class="close" data-dismiss="alert">&times;</button>
<strong>POOF!</strong> You deleted the budget '<strong>{{ deletedBudgetName }}</strong>'.
</div>
{% endif %}
<div class="row">
<div class="col-sm-6">
<div class="card">
<div class="card-body">
<h5 class="card-title">Create a new budget</h5>
{% if (income - budgeted) > 0 %}
<p>You currently have <strong>{{ (income - budgeted) | usd}}</strong> of unbudgeted income.</p>
<a href="/createbudget" class="btn btn-success">Create a Budget</a>
{% else %}
<p>All of your income is budgeted for. Good job 👍</p>
<a href="/createbudget" class="btn btn-success">Create a Budget</a>
{% endif %}
</div>
</div>
</div>
<div class="col-sm-6">
<div class="card">
<div class="card-body">
<h5 class="card-title">Update existing budgets</h5>
{% if budgets %}
{% for budget in budgets %}
<div class="btn-group">
<button type="button" class="btn btn-info dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{ budget["name"] }}
</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="/updatebudget/{{ budget['name'] }}">Update</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" data-toggle="modal" data-target="#deleteModal" data-budget="{{ budget['name'] }}" data-amount="{{ budget['amount'] | usd }}">Delete</a>
</div>
</div>
<small class="form-text text-muted">Amount: {{ budget["amount"] | usd }}</a></small>
<br>
{% endfor %}
{% else %}
<p>You have not created any budgets yet 😢</p>
{% endif %}
</div>
</div>
</div>
</div>

<!-- Budget delete modal -->
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="deleteModalLabel">Delete Budget</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form action="/budgets" id="deleteBudget" method="post">
<div class="form-group">
<input type="hidden" class="form-control" name="delete" id="delete" value="" readonly>
</div>
</form>
<p class="text-danger"><strong>Are you sure you want to delete this budget?</strong></p>
<ul class="text-danger" style="text-align:left">
<li class="text-danger small">The budget will no longer appear in your Dashboard or Reports</li>
<li class="text-danger small" id="amountAlert"></li>
</ul>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-danger" form="deleteBudget" id="btnDeleteBudget">Delete Budget</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>

<script>
// Set the title and fields of the delete modal on load/show
$('#deleteModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget) // Button that triggered the modal
var budget = button.data('budget') // Extract info from data-* attributes
var amount = button.data('amount')
var modal = $(this)
modal.find('.modal-title').text("Delete budget '" + budget + "'")
modal.find('#delete').val(budget)
modal.find('#amountAlert').text(amount + " of your income will no longer be budgeted for")
})
</script>

{% endblock %}

Loading

0 comments on commit 70cf646

Please sign in to comment.