From e67ae63eb804a5ada2b9589d180ea5f4e204fa20 Mon Sep 17 00:00:00 2001 From: Yaro Kasear Date: Fri, 20 Jun 2025 13:48:03 -0500 Subject: [PATCH] Refactor application configuration; implement dynamic database URI setup based on environment variables for improved flexibility and maintainability --- .gitignore | 3 +- __init__.py | 29 +++------ config.py | 60 ++++++++++++++++++ models/__pycache__/work_log.cpython-313.pyc | Bin 3198 -> 3157 bytes models/work_log.py | 7 +- requirements.txt | 3 +- routes.py | 9 ++- templates/fragments/_breadcrumb_fragment.html | 2 +- templates/index.html | 2 + templates/layout.html | 2 +- 10 files changed, 88 insertions(+), 29 deletions(-) create mode 100644 config.py diff --git a/.gitignore b/.gitignore index cafd598..099fcce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ __pycache__/ -.venv/ \ No newline at end of file +.venv/ +.env \ No newline at end of file diff --git a/__init__.py b/__init__.py index 199ca67..6494705 100644 --- a/__init__.py +++ b/__init__.py @@ -1,37 +1,28 @@ from flask import Flask from flask_sqlalchemy import SQLAlchemy -from urllib.parse import quote_plus import logging db = SQLAlchemy() logger = logging.getLogger('sqlalchemy.engine') logger.setLevel(logging.INFO) -handler = logging.StreamHandler() -handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')) -logger.addHandler(handler) +if not logger.handlers: + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')) + logger.addHandler(handler) def create_app(): + from config import Config app = Flask(__name__) - - params = quote_plus( - "DRIVER=ODBC Driver 17 for SQL Server;" - "SERVER=NDVASQLCR01;" - "DATABASE=conradTest;" - "Trusted_Connection=yes;" - ) - - app.config['SQLALCHEMY_DATABASE_URI'] = f"mssql+pyodbc:///?odbc_connect={params}" - app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + app.config.from_object(Config) db.init_app(app) + with app.app_context(): + from . import models + db.create_all() + from .routes import main app.register_blueprint(main) - @app.route("/") - def index(): - return "Hello, you've reached the terrible but functional root of the site." - - return app diff --git a/config.py b/config.py new file mode 100644 index 0000000..012c37e --- /dev/null +++ b/config.py @@ -0,0 +1,60 @@ +import os +import urllib.parse +from dotenv import load_dotenv + +load_dotenv() + +def quote(value): + return urllib.parse.quote_plus(value) + +class Config: + SQLALCHEMY_TRACK_MODIFICATIONS = False + DEBUG = False + TESTING = False + + DB_BACKEND = os.getenv('DB_BACKEND', 'sqlite').lower() + DB_WINDOWS_AUTH = os.getenv('DB_WINDOWS_AUTH', 'false').strip().lower() in ['true', '1', 'yes'] + + DB_USER = os.getenv('DB_USER', '') + DB_PASSWORD = os.getenv('DB_PASSWORD', '') + DB_HOST = os.getenv('DB_HOST', 'localhost') + DB_PORT = os.getenv('DB_PORT', '') + DB_NAME = os.getenv('DB_NAME', 'app.db') # default file for sqlite + + SQLALCHEMY_DATABASE_URI = None # <-- initialize properly + + if DB_BACKEND == 'mssql': + driver = os.getenv('DB_DRIVER', 'ODBC Driver 17 for SQL Server') + quoted_driver = quote(driver) + + if DB_WINDOWS_AUTH: + SQLALCHEMY_DATABASE_URI = ( + f"mssql+pyodbc://@{DB_HOST}/{DB_NAME}?driver={quoted_driver}&Trusted_Connection=yes" + ) + else: + SQLALCHEMY_DATABASE_URI = ( + f"mssql+pyodbc://{quote(DB_USER)}:{quote(DB_PASSWORD)}@{DB_HOST}:{DB_PORT}/{DB_NAME}" + f"?driver={quoted_driver}" + ) + + elif DB_BACKEND == 'postgres': + SQLALCHEMY_DATABASE_URI = ( + f"postgresql://{quote(DB_USER)}:{quote(DB_PASSWORD)}@{DB_HOST}:{DB_PORT or '5432'}/{DB_NAME}" + ) + + elif DB_BACKEND in ['mariadb', 'mysql']: + SQLALCHEMY_DATABASE_URI = ( + f"mysql+pymysql://{quote(DB_USER)}:{quote(DB_PASSWORD)}@{DB_HOST}:{DB_PORT or '3306'}/{DB_NAME}" + ) + + elif DB_BACKEND == 'sqlite': + if DB_NAME == ':memory:': + SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:' + else: + SQLALCHEMY_DATABASE_URI = f"sqlite:///{DB_NAME}" + + else: + raise ValueError( + f"Unsupported DB_BACKEND: {DB_BACKEND}. " + "Supported backends: mssql, postgres, mariadb, mysql, sqlite." + ) diff --git a/models/__pycache__/work_log.cpython-313.pyc b/models/__pycache__/work_log.cpython-313.pyc index 11854dc220198b7fce4d677d2ee999cb584f9972..85fa3ee68915e6b633bb91d2e2625027fc7575aa 100644 GIT binary patch delta 302 zcmew-aaDr%GcPX}0|NuY{$-&V7bfybGH#iu9vkDuSH!2l5X=zF=FRUVP$b|bSS08r zR3rozWA_#=5)Nhx=I|Er5-k!{V31%?VhHAxVhHAn5e?D-nTCo@nIZfl@pN`g?ujp~ z8964aFnaKFxFnXOhGgcZ-eM_9tti=C$GD1#k!Q0e%PnR``OPdG3XF_=lT|ql8TluN zb7nL0Og_vRY#V>sETO?`0^5X&1svBE3@<7eUKBUFC}4byq1qKNQC5B)wDTZL~7_lI|i3g-vCDJ)Gc_u!v zW@Mi%!{{NzQj%IxqRDuR)5S5wHN?}`)o61u<0>Xb-pz6>x0o3fHveQ-U}WT}C%xF-Ato$?@D_ z(zhgw3v&{4k~30sEA>(`6LV6NON#Y!i;D|$CU4~q({f{AU|?imU?{d`U|{&b%*e=i hpF!&$v%p;zmAfqR&l!@gGbCMPNV?0QKiP;!8359YOZNZ( diff --git a/models/work_log.py b/models/work_log.py index fbea696..bec28c6 100644 --- a/models/work_log.py +++ b/models/work_log.py @@ -3,8 +3,7 @@ if TYPE_CHECKING: from .inventory import Inventory from .inventory import User -from sqlalchemy import Boolean, ForeignKeyConstraint, Identity, Integer, ForeignKey, Unicode, text -from sqlalchemy.dialects.mssql import DATETIME2 +from sqlalchemy import Boolean, ForeignKeyConstraint, Identity, Integer, ForeignKey, Unicode, DateTime, text from sqlalchemy.orm import Mapped, mapped_column, relationship import datetime @@ -17,8 +16,8 @@ class WorkLog(db.Model): ) id: Mapped[int] = mapped_column("ID", Integer, Identity(start=1, increment=1), primary_key=True) - start_time: Mapped[Optional[datetime.datetime]] = mapped_column('Start Timestamp', DATETIME2) - end_time: Mapped[Optional[datetime.datetime]] = mapped_column('End Timestamp', DATETIME2) + start_time: Mapped[Optional[datetime.datetime]] = mapped_column('Start Timestamp', DateTime) + end_time: Mapped[Optional[datetime.datetime]] = mapped_column('End Timestamp', DateTime) notes: Mapped[Optional[str]] = mapped_column('Description & Notes', Unicode()) complete: Mapped[Optional[bool]] = mapped_column("Complete", Boolean, server_default=text('((0))')) followup: Mapped[Optional[bool]] = mapped_column('Needs Follow-Up', Boolean, server_default=text('((0))')) diff --git a/requirements.txt b/requirements.txt index d12b286..b7cb7bb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ +dotenv flask flask_sqlalchemy -pyodbc pandas +pyodbc \ No newline at end of file diff --git a/routes.py b/routes.py index 6bc10ee..ed51a34 100644 --- a/routes.py +++ b/routes.py @@ -165,7 +165,12 @@ def index(): 'Deployed','Inoperable', 'Partially Inoperable', 'Unverified', 'Working' ] - pivot = df['condition'].value_counts().reindex(expected_conditions, fill_value=0) + print(df) + if 'condition' in df.columns: + pivot = df['condition'].value_counts().reindex(expected_conditions, fill_value=0) + else: + pivot = pd.Series([0] * len(expected_conditions), index=expected_conditions) + # Convert pandas/numpy int64s to plain old Python ints pivot = pivot.astype(int) @@ -376,7 +381,7 @@ def search(): query = request.args.get('q', '').strip() if not query: - return redirect(url_for('index')) + return redirect(url_for('main.index')) InventoryAlias = aliased(Inventory) UserAlias = aliased(User) diff --git a/templates/fragments/_breadcrumb_fragment.html b/templates/fragments/_breadcrumb_fragment.html index aed2779..dbb9922 100644 --- a/templates/fragments/_breadcrumb_fragment.html +++ b/templates/fragments/_breadcrumb_fragment.html @@ -5,7 +5,7 @@
{% endif %} + {% if (datasets[0]['values'] | sum) > 0 %}
@@ -33,6 +34,7 @@
+ {% endif %} {% endblock %} diff --git a/templates/layout.html b/templates/layout.html index 5eec65d..c6758f7 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -30,7 +30,7 @@