Refactor application configuration; implement dynamic database URI setup based on environment variables for improved flexibility and maintainability
This commit is contained in:
parent
86a4e4d22f
commit
e67ae63eb8
10 changed files with 88 additions and 29 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
__pycache__/
|
__pycache__/
|
||||||
.venv/
|
.venv/
|
||||||
|
.env
|
29
__init__.py
29
__init__.py
|
@ -1,37 +1,28 @@
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from urllib.parse import quote_plus
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
db = SQLAlchemy()
|
db = SQLAlchemy()
|
||||||
|
|
||||||
logger = logging.getLogger('sqlalchemy.engine')
|
logger = logging.getLogger('sqlalchemy.engine')
|
||||||
logger.setLevel(logging.INFO)
|
logger.setLevel(logging.INFO)
|
||||||
handler = logging.StreamHandler()
|
if not logger.handlers:
|
||||||
handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s'))
|
handler = logging.StreamHandler()
|
||||||
logger.addHandler(handler)
|
handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s'))
|
||||||
|
logger.addHandler(handler)
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
|
from config import Config
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
app.config.from_object(Config)
|
||||||
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
|
|
||||||
|
|
||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
from . import models
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
from .routes import main
|
from .routes import main
|
||||||
app.register_blueprint(main)
|
app.register_blueprint(main)
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
def index():
|
|
||||||
return "Hello, you've reached the terrible but functional root of the site."
|
|
||||||
|
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
60
config.py
Normal file
60
config.py
Normal file
|
@ -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."
|
||||||
|
)
|
Binary file not shown.
|
@ -3,8 +3,7 @@ if TYPE_CHECKING:
|
||||||
from .inventory import Inventory
|
from .inventory import Inventory
|
||||||
from .inventory import User
|
from .inventory import User
|
||||||
|
|
||||||
from sqlalchemy import Boolean, ForeignKeyConstraint, Identity, Integer, ForeignKey, Unicode, text
|
from sqlalchemy import Boolean, ForeignKeyConstraint, Identity, Integer, ForeignKey, Unicode, DateTime, text
|
||||||
from sqlalchemy.dialects.mssql import DATETIME2
|
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
import datetime
|
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)
|
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)
|
start_time: Mapped[Optional[datetime.datetime]] = mapped_column('Start Timestamp', DateTime)
|
||||||
end_time: Mapped[Optional[datetime.datetime]] = mapped_column('End Timestamp', DATETIME2)
|
end_time: Mapped[Optional[datetime.datetime]] = mapped_column('End Timestamp', DateTime)
|
||||||
notes: Mapped[Optional[str]] = mapped_column('Description & Notes', Unicode())
|
notes: Mapped[Optional[str]] = mapped_column('Description & Notes', Unicode())
|
||||||
complete: Mapped[Optional[bool]] = mapped_column("Complete", Boolean, server_default=text('((0))'))
|
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))'))
|
followup: Mapped[Optional[bool]] = mapped_column('Needs Follow-Up', Boolean, server_default=text('((0))'))
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
|
dotenv
|
||||||
flask
|
flask
|
||||||
flask_sqlalchemy
|
flask_sqlalchemy
|
||||||
pyodbc
|
|
||||||
pandas
|
pandas
|
||||||
|
pyodbc
|
|
@ -165,7 +165,12 @@ def index():
|
||||||
'Deployed','Inoperable', 'Partially Inoperable',
|
'Deployed','Inoperable', 'Partially Inoperable',
|
||||||
'Unverified', 'Working'
|
'Unverified', 'Working'
|
||||||
]
|
]
|
||||||
|
print(df)
|
||||||
|
if 'condition' in df.columns:
|
||||||
pivot = df['condition'].value_counts().reindex(expected_conditions, fill_value=0)
|
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
|
# Convert pandas/numpy int64s to plain old Python ints
|
||||||
pivot = pivot.astype(int)
|
pivot = pivot.astype(int)
|
||||||
|
@ -376,7 +381,7 @@ def search():
|
||||||
query = request.args.get('q', '').strip()
|
query = request.args.get('q', '').strip()
|
||||||
|
|
||||||
if not query:
|
if not query:
|
||||||
return redirect(url_for('index'))
|
return redirect(url_for('main.index'))
|
||||||
|
|
||||||
InventoryAlias = aliased(Inventory)
|
InventoryAlias = aliased(Inventory)
|
||||||
UserAlias = aliased(User)
|
UserAlias = aliased(User)
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li class="breadcrumb-item">
|
<li class="breadcrumb-item">
|
||||||
<a href="{{ url_for('index') }}" class="link-success link-underline-opacity-0">
|
<a href="{{ url_for('main.index') }}" class="link-success link-underline-opacity-0">
|
||||||
{{ icons.render_icon('house', 16) }}
|
{{ icons.render_icon('house', 16) }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if (datasets[0]['values'] | sum) > 0 %}
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
@ -33,6 +34,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
<body class="bg-tertiary text-primary-emphasis">
|
<body class="bg-tertiary text-primary-emphasis">
|
||||||
<nav class="navbar navbar-expand bg-body-tertiary border-bottom">
|
<nav class="navbar navbar-expand bg-body-tertiary border-bottom">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="{{ url_for('index') }}">
|
<a class="navbar-brand" href="{{ url_for('main.index') }}">
|
||||||
Inventory Manager
|
Inventory Manager
|
||||||
</a>
|
</a>
|
||||||
<button class="navbar-toggler">
|
<button class="navbar-toggler">
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue