Files
Superior-Finance/app.py
2026-08-09 22:36:10 -05:00

241 lines
7.8 KiB
Python

import os
import json
import uuid
from flask import Flask, render_template, request, redirect, url_for, flash
from flask_sqlalchemy import SQLAlchemy
from dotenv import load_dotenv
from werkzeug.utils import secure_filename
from slugify import slugify
load_dotenv()
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///info.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['UPLOAD_FOLDER'] = os.path.join(app.root_path, 'static', 'uploads')
app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET_KEY', 'dev-secret-key-change-me')
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
db = SQLAlchemy(app)
ALLOWED_IMAGE_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp'}
def allowed_image(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_IMAGE_EXTENSIONS
def unique_slug(base_title, model):
base = slugify(base_title)
slug = base
suffix = 2
while model.query.filter_by(slug=slug).first() is not None:
slug = f'{base}-{suffix}'
suffix += 1
return slug
class Goal(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(255), nullable=False)
description = db.Column(db.Text, nullable=False)
images = db.Column(db.Text, nullable=True)
goal_amount = db.Column(db.Integer, nullable=False)
completed = db.Column(db.Boolean, default=False, nullable=False)
slug = db.Column(db.String(255), nullable=False, unique=True)
babysteps = db.relationship('BabyStep', backref='goal', lazy=True, cascade='all, delete-orphan')
@property
def image_list(self):
return json.loads(self.images) if self.images else []
@property
def saved_amount(self):
return sum(step.saved_amount for step in self.babysteps)
@property
def progress_percent(self):
if not self.goal_amount:
return 0
return min(100, round(self.saved_amount / self.goal_amount * 100))
class BabyStep(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(255), nullable=False)
description = db.Column(db.Text, nullable=False)
monthly_amount = db.Column(db.Integer, nullable=False)
completed = db.Column(db.Boolean, default=False, nullable=False)
slug = db.Column(db.String(255), nullable=False, unique=True)
goal_id = db.Column(db.Integer, db.ForeignKey('goal.id'), nullable=False)
payments = db.relationship('Payment', backref='babystep', lazy=True, cascade='all, delete-orphan')
@property
def saved_amount(self):
return sum(payment.amount for payment in self.payments)
@property
def progress_percent(self):
if not self.monthly_amount:
return 0
return min(100, round(self.saved_amount / self.monthly_amount * 100))
class Payment(db.Model):
id = db.Column(db.Integer, primary_key=True)
description = db.Column(db.Text, nullable=False)
amount = db.Column(db.Integer, nullable=False)
received_at = db.Column(db.DateTime, server_default=db.func.now())
babystep_id = db.Column(db.Integer, db.ForeignKey('baby_step.id'), nullable=False)
@app.route('/')
def index():
goals = Goal.query.order_by(Goal.id.desc()).all()
return render_template('index.html', goals=goals)
@app.route('/goals')
def view_goals():
goals = Goal.query.order_by(Goal.id.desc()).all()
return render_template('goals.html', goals=goals)
@app.route('/goals/<slug>')
def view_goal(slug):
goal = Goal.query.filter_by(slug=slug).first_or_404()
return render_template('goal_detail.html', goal=goal)
@app.route('/babysteps')
def view_babysteps():
babysteps = BabyStep.query.order_by(BabyStep.id.desc()).all()
return render_template('babysteps.html', babysteps=babysteps)
@app.route('/babysteps/<slug>')
def view_babystep(slug):
babystep = BabyStep.query.filter_by(slug=slug).first_or_404()
return render_template('babystep_detail.html', babystep=babystep)
@app.route('/payments')
def view_payments():
payments = Payment.query.order_by(Payment.id.desc()).all()
return render_template('payments.html', payments=payments)
@app.route('/newgoal', methods=['GET', 'POST'])
def new_goal():
if request.method == 'POST':
title = request.form.get('title', '').strip()
description = request.form.get('description', '').strip()
goal_amount = request.form.get('goal_amount', type=int)
completed = 'completed' in request.form
images = request.files.getlist('images')
if not title or goal_amount is None:
flash('Title and a valid goal amount are required.')
return render_template('new_goal.html')
image_filenames = []
for image in images:
if image.filename and allowed_image(image.filename):
filename = f'{uuid.uuid4().hex}_{secure_filename(image.filename)}'
image.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
image_filenames.append(filename)
goal = Goal(
title=title,
description=description,
images=json.dumps(image_filenames),
goal_amount=goal_amount,
completed=completed,
slug=unique_slug(title, Goal),
)
db.session.add(goal)
db.session.commit()
flash(f'Goal "{goal.title}" created.')
return redirect(url_for('view_goal', slug=goal.slug))
return render_template('new_goal.html')
@app.route('/newbabystep', methods=['GET', 'POST'])
def new_babystep():
goals = Goal.query.order_by(Goal.title).all()
if request.method == 'POST':
title = request.form.get('title', '').strip()
description = request.form.get('description', '').strip()
monthly_amount = request.form.get('monthly_amount', type=int)
goal_id = request.form.get('goal_id', type=int)
completed = 'completed' in request.form
goal = db.session.get(Goal, goal_id) if goal_id else None
if not title or monthly_amount is None or goal is None:
flash('Title, a valid monthly amount, and a goal are required.')
return render_template('new_babystep.html', goals=goals)
babystep = BabyStep(
title=title,
description=description,
monthly_amount=monthly_amount,
completed=completed,
slug=unique_slug(title, BabyStep),
goal_id=goal.id,
)
db.session.add(babystep)
db.session.commit()
flash(f'Babystep "{babystep.title}" added to "{goal.title}".')
return redirect(url_for('view_babystep', slug=babystep.slug))
return render_template('new_babystep.html', goals=goals)
@app.route('/newpayment', methods=['GET', 'POST'])
def new_payment():
babysteps = BabyStep.query.order_by(BabyStep.title).all()
if request.method == 'POST':
description = request.form.get('description', '').strip()
amount = request.form.get('amount', type=int)
babystep_id = request.form.get('babystep_id', type=int)
babystep = db.session.get(BabyStep, babystep_id) if babystep_id else None
if not description or amount is None or babystep is None:
flash('Description, a valid amount, and a babystep are required.')
return render_template('new_payment.html', babysteps=babysteps)
payment = Payment(
description=description,
amount=amount,
babystep_id=babystep.id,
)
db.session.add(payment)
db.session.commit()
flash(f'Payment of {amount} applied to "{babystep.title}".')
return redirect(url_for('view_babystep', slug=babystep.slug))
return render_template('new_payment.html', babysteps=babysteps)
def init_db():
with app.app_context():
db.create_all()
if __name__ == '__main__':
init_db()
app.run(debug=False)