too much to enumerate, too little time, have to push

This commit is contained in:
Yisroel Baum 2024-09-29 12:38:32 +03:00
parent 9f90d357fd
commit a8f0deab28
14 changed files with 567 additions and 17 deletions

2
.gitignore vendored
View file

@ -1,4 +1,2 @@
**/__pycache__/
instance/
migrations/
static/

View file

@ -16,6 +16,3 @@ migrate.init_app(app,db)
from app import models #.models import Vendor, LineItem, BudgetCategory
from app import routes
if __name__ == '__main__':
app.run(debug=True)

View file

@ -3,18 +3,18 @@ from sqlalchemy import Column, INTEGER, String
class Vendor(db.Model):
__tablename__ = 'vendor'
id = Column('id', INTEGER(), primary_key=True)
id = Column('id', INTEGER(), primary_key=True, autoincrement=True)
name = Column('name', String(), nullable=False)
bc_id = Column('bc_id', INTEGER(), nullable=True)
class BudgetCategory(db.Model):
__tablename__ = 'budget_category'
id = Column('id', INTEGER(), primary_key=True)
id = Column('id', INTEGER(), primary_key=True, autoincrement=True)
name = Column('name', String(), nullable=False)
class LineItem(db.Model):
__tablename__ = 'line_item'
id = Column('id', INTEGER(), primary_key=True)
id = Column('id', INTEGER(), primary_key=True, autoincrement=True)
parent_line_item_id = Column('parent_line_item_id', INTEGER(), nullable=True)
amount = Column('amount', INTEGER(), nullable=False)
currency_type = Column('currency_type', String(), default='shekel')

View file

@ -1,8 +1,99 @@
from app import app
from flask import render_template
from app import app, db
from flask import render_template, redirect, url_for, request
from sqlalchemy import select, delete, update
from app.models import LineItem, BudgetCategory, Vendor
import datetime
import os
import openpyxl
import time
@app.route('/')
def home():
return render_template('index.html')
this_year = datetime.datetime.now().date().year
this_month = datetime.datetime.now().date().month
# line_item_dates = db.session.query(select(LineItem.date).order_by(LineItem.date))
notes = db.session.execute(select(LineItem.note)).all()
vendors = db.session.execute(select(Vendor)).all()
budget_categories = db.session.execute(select(BudgetCategory)).all()
files = os.listdir('C:/Users/Lenovo/Desktop/BudgetingApp/app/static/uploadable')
return render_template('index.html',
files=files,
notes=notes,
vendors=vendors,
budget_categories=budget_categories)
@app.route('/upload_file/<filename>')
def upload_file(filename):
file_path = 'C:/Users/Lenovo/Desktop/BudgetingApp/app/static/uploadable/' + filename
xl = openpyxl.load_workbook(file_path, read_only=True)
wb = xl.worksheets[0]
items_to_add = []
vendors = db.session.execute(select(Vendor.name)).all()
vendors_added = [vendor[0] for vendor in vendors]
for line, row in enumerate(wb.rows):
row = [x.value for x in row]
if line == 0:
columns = {x: i for i,x in enumerate(row)}
continue
if row[columns['Charge']]:
amount=row[columns['Charge']] * -1
else:
amount=row[columns['Deposit']]
date = row[0]
date = time.mktime(date.timetuple())
if not row[columns['Vendor']] in vendors_added:
vendors_added.append(row[columns['Vendor']])
vendor = Vendor(
name=row[columns['Vendor']]
)
db.session.add(vendor)
db.session.commit()
line_item = LineItem(
parent_line_item_id=None,
amount=amount,
currency_type='shekel',
vendor_id=None,
date=date,
confirmation_code=row[columns['Confirmation Code']],
note=row[columns['Note']]
)
items_to_add.append(line_item)
xl.close()
db.session.add_all(items_to_add)
db.session.commit()
# os.remove(file_path)
return redirect(url_for('home'))
@app.route('/view_month/<year>/<month>')
def view_month(year, month):
return render_template('view_month.html',
month=month,
year=year)
@app.route('/add_budget_category', methods=['POST'])
def add_budget_category():
if request.method == 'POST':
category_name = request.form['category_name']
bc = BudgetCategory(name=category_name)
db.session.add(bc)
db.session.commit()
return redirect(url_for('home'))
@app.route('/delete_budget_category/<id>', methods=['POST'])
def delete_budget_category(id):
db.session.execute(update(Vendor).where(Vendor.bc_id==id).values(bc_id=None))
db.session.execute(delete(BudgetCategory).where(BudgetCategory.id==id))
db.session.commit()
return {"status":'success'}

View file

@ -4,8 +4,124 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Budgeting</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
{% if files %}
<div class="card">
<div class="card-header bg-primary text-white">
File available in static folder for upload
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th scope="col">Filename</th>
</tr>
</thead>
<tbody>
{% for file in files %}
<tr>
<th scope="row"><a href="{{url_for('upload_file', filename=file)}}">{{file}}</a></th>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% else %}
<div class="card">
<div class="card-header bg-primary text-white">
No files in the upload folder
</div>
</div>
{% endif %}
<div class="card">
<div class="card-header bg-primary text-white">
Budget Categories
</div>
<div class="card-body">
<form action="{{ url_for('add_budget_category') }}" method="POST">
<div class="mb-3">
<label for="categoryName" class="form-label">Add Budget Category</label>
<input type="text" class="form-control" id="categoryName" name="category_name" placeholder="Enter category name" required>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">Name</th>
<th scope="col">Delete</th>
</tr>
</thead>
<tbody>
{% for bc in budget_categories %}
<tr id="{{bc[0].id}}-row">
<th scope="row">{{bc[0].id}}</th>
<th scope="row">{{bc[0].name}}</th>
<th scope="row"><button class="btn btn-danger del-button" id="{{bc[0].id}}">Delete</button></th>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="card">
<div class="card-header bg-primary text-white">
Vendors
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">Name</th>
</tr>
</thead>
<tbody>
{% for vendor in vendors %}
<tr>
<th scope="row">{{vendor[0].id}}</th>
<th scope="row">{{vendor[0].name}}</th>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="card">
<div class="card-header bg-primary text-white">
Notes
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered">
<tbody>
{% for note in notes %}
{% if note[0] %}
<tr>
<th scope="row">{{note[0]}}</th>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js"></script>
<script src="{{url_for('.static', filename='index.js')}}"></script>
</body>
</html>

View file

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Budgeting</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
{% if files %}
<div class="card">
<div class="card-header bg-primary text-white">
{{month}} - {{year}}
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th scope="col">Line</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
{% endif %}
</div>
<!-- Bootstrap JS and dependencies -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

BIN
instance/site.db Normal file

Binary file not shown.

1
migrations/README Normal file
View file

@ -0,0 +1 @@
Single-database configuration for Flask.

50
migrations/alembic.ini Normal file
View file

@ -0,0 +1,50 @@
# A generic, single database configuration.
[alembic]
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic,flask_migrate
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[logger_flask_migrate]
level = INFO
handlers =
qualname = flask_migrate
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

113
migrations/env.py Normal file
View file

@ -0,0 +1,113 @@
import logging
from logging.config import fileConfig
from flask import current_app
from alembic import context
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
logger = logging.getLogger('alembic.env')
def get_engine():
try:
# this works with Flask-SQLAlchemy<3 and Alchemical
return current_app.extensions['migrate'].db.get_engine()
except (TypeError, AttributeError):
# this works with Flask-SQLAlchemy>=3
return current_app.extensions['migrate'].db.engine
def get_engine_url():
try:
return get_engine().url.render_as_string(hide_password=False).replace(
'%', '%%')
except AttributeError:
return str(get_engine().url).replace('%', '%%')
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
config.set_main_option('sqlalchemy.url', get_engine_url())
target_db = current_app.extensions['migrate'].db
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def get_metadata():
if hasattr(target_db, 'metadatas'):
return target_db.metadatas[None]
return target_db.metadata
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url, target_metadata=get_metadata(), literal_binds=True
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
# this callback is used to prevent an auto-migration from being generated
# when there are no changes to the schema
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
def process_revision_directives(context, revision, directives):
if getattr(config.cmd_opts, 'autogenerate', False):
script = directives[0]
if script.upgrade_ops.is_empty():
directives[:] = []
logger.info('No changes in schema detected.')
conf_args = current_app.extensions['migrate'].configure_args
if conf_args.get("process_revision_directives") is None:
conf_args["process_revision_directives"] = process_revision_directives
connectable = get_engine()
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=get_metadata(),
**conf_args
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

24
migrations/script.py.mako Normal file
View file

@ -0,0 +1,24 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

View file

@ -0,0 +1,51 @@
"""empty message
Revision ID: 8960c74c29df
Revises:
Create Date: 2024-09-29 08:46:22.567795
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '8960c74c29df'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('budget_category',
sa.Column('id', sa.INTEGER(), nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_table('line_item',
sa.Column('id', sa.INTEGER(), nullable=False),
sa.Column('parent_line_item_id', sa.INTEGER(), nullable=True),
sa.Column('amount', sa.INTEGER(), nullable=False),
sa.Column('currency_type', sa.String(), nullable=True),
sa.Column('vendor_id', sa.INTEGER(), nullable=True),
sa.Column('date', sa.INTEGER(), nullable=False),
sa.Column('confirmation_code', sa.INTEGER(), nullable=True),
sa.Column('note', sa.String(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_table('vendor',
sa.Column('id', sa.INTEGER(), nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.Column('bc_id', sa.INTEGER(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('vendor')
op.drop_table('line_item')
op.drop_table('budget_category')
# ### end Alembic commands ###

67
pythonFiles/playground.py Normal file
View file

@ -0,0 +1,67 @@
import datetime
import openpyxl
import time
from sqlalchemy import create_engine, MetaData, Table
import os
def engineer():
path1 = 'C:/Users/Lenovo/Desktop/BudgetingApp/instance/site.db'
# path2 = ''
# path3 = ''
for p in [path1]:
if os.path.exists(p):
path = p
break
engine = create_engine(f'sqlite:///{path}')
metadata_obj = MetaData()
metadata_obj.reflect(bind=engine)
return engine, metadata_obj
def playground():
engine, metadata_obj = engineer()
line_item_table = Table("line_item", metadata_obj, autoload_with=engine)
budget_category_table = Table("budget_category", metadata_obj, autoload_with=engine)
vendor_table = Table("vendor", metadata_obj, autoload_with=engine)
# xl = openpyxl.load_workbook('C:/Users/Lenovo/Desktop/BudgetingApp/app/static/uploadable/Bulk_Line_Item_Upload.xlsx',read_only=True)
# wb = xl.worksheets[0]
# items_to_add = []
# for line, row in enumerate(wb.rows):
# row = [x.value for x in row]
# if line == 0:
# columns = {x: i for i,x in enumerate(row)}
# continue
# if row[columns['Charge']]:
# amount=row[columns['Charge']] * -1
# else:
# amount=row[columns['Deposit']]
# date = row[0]
# date = time.mktime(date.timetuple())
# line_item = {
# 'parent_line_item_id':None,
# 'amount':amount,
# 'currency_type':'shekel',
# 'vendor_id':None,
# 'date':date,
# 'confirmation_code':row[columns['Confirmation Code']],
# 'note':row[columns['Note']]
# }
# items_to_add.append(line_item)
# print(len(items_to_add))
# print(items_to_add[51])
# month = datetime.datetime.now().date().month
# year = datetime.datetime.now().date().year
# print(month)
# print(year)
# print(type(month))
# print(type(year))
# print(datetime.datetime.now(datetime.timezone.utc).timestamp())
# today = datetime.datetime.today().date()
# today = time.mktime(today.timetuple())
# print(today)
if __name__ == '__main__':
playground()

5
wsgi.py Normal file
View file

@ -0,0 +1,5 @@
from app import app
if __name__ == '__main__':
app.run(debug=True)