Removed React and added HTMX
This commit is contained in:
parent
60ea5945be
commit
62834b7280
@ -1,5 +1,5 @@
|
||||
# Flask-Project-Template
|
||||
A template to quickly standup a Flask app with Bootstrap 5, React, OIDC IAM, and a database.
|
||||
A template to quickly standup a Flask app with Bootstrap 5, HTMX, OIDC IAM, and a database.
|
||||
|
||||
# Dependencies
|
||||
``` sudo apt install python3 ```
|
||||
|
@ -32,5 +32,5 @@ builtins.app_name = ':::APP TITLE:::'
|
||||
builtins.threaded = threaded_wrapper
|
||||
builtins.daemon_threaded = daemon_threaded_wrapper
|
||||
builtins.ROOT_FILE_PTH = os.path.dirname(os.path.realpath(__file__))
|
||||
builtins.logger = Logger().get_logger()
|
||||
builtins.logger = Logger(f"{ROOT_FILE_PTH}/../logs", _ch_log_lvl=10, _fh_log_lvl=20).get_logger()
|
||||
builtins.json_message = MessageHandler()
|
||||
|
@ -44,10 +44,6 @@ app.jinja_env.globals['TITLE'] = app.config["TITLE"]
|
||||
from core.models import db
|
||||
from core.models import User
|
||||
|
||||
db.init_app(app)
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
||||
from core.forms import RegisterForm
|
||||
from core.forms import LoginForm
|
||||
from core import routes
|
||||
|
@ -5,12 +5,14 @@ from flask_login import UserMixin
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
# Apoplication imports
|
||||
from core import app, login_manager
|
||||
from core import app
|
||||
from core import login_manager
|
||||
|
||||
|
||||
|
||||
db = SQLAlchemy(app)
|
||||
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
return User.query.get(int(user_id))
|
||||
@ -24,3 +26,8 @@ class User(db.Model, UserMixin):
|
||||
|
||||
def __repr__(self):
|
||||
return f"['{self.username}', '{self.email}', '{self.password}', '{self.id}']"
|
||||
|
||||
|
||||
db.init_app(app)
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
@ -8,3 +8,4 @@ from .login_controller import oidc_register
|
||||
from .login_controller import controller
|
||||
|
||||
from . import routes
|
||||
from . import htmx_page
|
||||
|
30
src/core/routes/htmx_page.py
Normal file
30
src/core/routes/htmx_page.py
Normal file
@ -0,0 +1,30 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
from flask import request
|
||||
from flask import render_template
|
||||
from flask_login import current_user
|
||||
|
||||
# Application imports
|
||||
# Get from __init__
|
||||
from core import app
|
||||
from core import oidc
|
||||
from core import db
|
||||
|
||||
|
||||
@app.route('/htmx-page', methods=['GET', 'POST'])
|
||||
def htmx_page():
|
||||
if request.method == 'GET':
|
||||
return render_template('pages/htmx-page.html')
|
||||
|
||||
return render_template('error.html', title = 'Error!',
|
||||
message = 'Must use GET request type...')
|
||||
|
||||
|
||||
|
||||
@app.route('/load-hello-world', methods=['GET', 'POST'])
|
||||
def load_hello_world():
|
||||
if request.method == 'GET':
|
||||
return "<h1>Hello, World!</h1>"
|
||||
|
||||
return 'Must use GET request type...'
|
@ -21,6 +21,16 @@ def home():
|
||||
title='Error!',
|
||||
message='Must use GET request type...')
|
||||
|
||||
# NOTE: Yeah, not exactly 'logged' but mostly used with the terminal anyway.
|
||||
@app.route('/log-client-exception', methods=['GET', 'POST'])
|
||||
def ui_failure_exception_tracker():
|
||||
if request.method == 'POST':
|
||||
DATA = str(request.values['exception_data']).strip()
|
||||
print(f"\n\n{DATA}")
|
||||
return json_message.create("success", "UI Exception logged...")
|
||||
|
||||
return json_message.create("danger", "Must use POST request type...")
|
||||
|
||||
|
||||
@app.route('/about', methods=['GET', 'POST'])
|
||||
def about():
|
||||
@ -30,14 +40,6 @@ def about():
|
||||
return render_template('error.html', title = 'Error!',
|
||||
message = 'Must use GET request type...')
|
||||
|
||||
@app.route('/react-page', methods=['GET', 'POST'])
|
||||
def react_page():
|
||||
if request.method == 'GET':
|
||||
return render_template('pages/react-page.html')
|
||||
|
||||
return render_template('error.html', title = 'Error!',
|
||||
message = 'Must use GET request type...')
|
||||
|
||||
|
||||
@app.route('/protected-zone', methods=['GET', 'POST'])
|
||||
def protected_zone():
|
||||
|
@ -1,3 +1,10 @@
|
||||
const logUIExceptionAjax = (data, action = "client-exception-logger") => {
|
||||
const postArgs = 'exception_data=' + data;
|
||||
|
||||
doAjax('log-client-exception', postArgs, action);
|
||||
}
|
||||
|
||||
|
||||
const doAjax = (actionPath, data, action) => {
|
||||
let xhttp = new XMLHttpRequest();
|
||||
|
||||
|
@ -1,3 +1,16 @@
|
||||
window.onload = (eve) => {
|
||||
console.log("Loaded...");
|
||||
}
|
||||
|
||||
window.onerror = function(msg, url, line, col, error) {
|
||||
// Note that col & error are new to the HTML 5 spec and may not be supported in every browser.
|
||||
const suppressErrorAlert = false;
|
||||
let extra = !col ? '' : '\ncolumn: ' + col;
|
||||
extra += !error ? '' : '\nerror: ' + error;
|
||||
const data = `Error: ${msg} \nurl: ${url} \nline: ${line} ${extra}`
|
||||
|
||||
logUIExceptionAjax(data);
|
||||
|
||||
// If you return true, then error alerts (like in older versions of Internet Explorer) will be suppressed.
|
||||
return suppressErrorAlert;
|
||||
};
|
||||
|
@ -101,15 +101,13 @@
|
||||
|
||||
{% block body_scripts %}
|
||||
<!-- For internal scripts... -->
|
||||
<script src="{{ url_for('static', filename='js/libs/jquery-3.6.0.min.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/libs/jquery-3.7.1.min.js')}}"></script>
|
||||
|
||||
<!-- For Bootstrap in this exact order... -->
|
||||
<!-- For Bootstrap... -->
|
||||
<script src="{{ url_for('static', filename='js/libs/bootstrap5/bootstrap.bundle.min.js')}}"></script>
|
||||
|
||||
<!-- For React -->
|
||||
<script src="{{ url_for('static', filename='js/libs/babel.min.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/libs/react/react.production.min.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/libs/react/react-dom.production.min.js')}}"></script>
|
||||
<!-- For HTMX... -->
|
||||
<script src="{{ url_for('static', filename='js/libs/htmx-1.9.5.min.js')}}"></script>
|
||||
|
||||
<!-- Application Imports -->
|
||||
{% block body_scripts_additional %}
|
||||
|
@ -14,7 +14,8 @@
|
||||
{% endblock body_header_additional %}
|
||||
|
||||
{% block body_content_additional %}
|
||||
<div id="react-container">
|
||||
<div id="htmx-container" hx-get="/load-hello-world" hx-trigger="load" hx-swap="innerHTML">
|
||||
I got replaced...
|
||||
</div>
|
||||
{% endblock body_content_additional %}
|
||||
|
||||
@ -26,5 +27,4 @@
|
||||
<script src="{{ url_for('static', filename='js/post-ajax.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/ajax.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/events.js')}}"></script>
|
||||
<script type="text/jsx" src="{{ url_for('static', filename='js/react-ui-logic.js')}}"></script>
|
||||
{% endblock body_scripts_additional %}
|
@ -17,9 +17,9 @@
|
||||
<div class="row">
|
||||
<div class="col justify-content-center text-center">
|
||||
<p>Using Bootstrap 5 Themeing with Dark Mode defaulted...</p>
|
||||
<p>With React available...</p>
|
||||
<p>A React Example Page:
|
||||
<a href="{{ url_for('react_page') }}">
|
||||
<p>With HTMX available...</p>
|
||||
<p>An HTMX Example Page:
|
||||
<a href="{{ url_for('htmx_page') }}">
|
||||
<button type="button" class="btn btn-dark">
|
||||
Try Me!
|
||||
</button>
|
||||
|
@ -1,63 +0,0 @@
|
||||
# Python imports
|
||||
import os
|
||||
import logging
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Apoplication imports
|
||||
|
||||
|
||||
|
||||
class Logger:
|
||||
def __init__(self, name = "NO_LOGGER_NAME_PASSED_ON_INIT"):
|
||||
self.logger = self.create_logger(name)
|
||||
|
||||
def get_logger(self):
|
||||
return self.logger
|
||||
|
||||
def create_logger(self, loggerName, createFile = True):
|
||||
"""
|
||||
Create a new logging object and return it.
|
||||
:note:
|
||||
NOSET # Don't know the actual log level of this... (defaulting or literally none?)
|
||||
Log Levels (From least to most)
|
||||
Type Value
|
||||
CRITICAL 50
|
||||
ERROR 40
|
||||
WARNING 30
|
||||
INFO 20
|
||||
DEBUG 10
|
||||
:param loggerName: Sets the name of the logger object. (Used in log lines)
|
||||
:param createFile: Whether we create a log file or just pump to terminal
|
||||
|
||||
:return: the logging object we created
|
||||
"""
|
||||
|
||||
globalLogLvl = logging.DEBUG # Keep this at highest so that handlers can filter to their desired levels
|
||||
chLogLevel = logging.CRITICAL # Prety musch the only one we change ever
|
||||
fhLogLevel = logging.DEBUG
|
||||
log = logging.getLogger(loggerName)
|
||||
log.setLevel(globalLogLvl)
|
||||
|
||||
# Set our log output styles
|
||||
fFormatter = logging.Formatter('[%(asctime)s] %(pathname)s:%(lineno)d %(levelname)s - %(message)s', '%m-%d %H:%M:%S')
|
||||
cFormatter = logging.Formatter('%(pathname)s:%(lineno)d] %(levelname)s - %(message)s')
|
||||
|
||||
ch = logging.StreamHandler()
|
||||
ch.setLevel(level=chLogLevel)
|
||||
ch.setFormatter(cFormatter)
|
||||
log.addHandler(ch)
|
||||
|
||||
if createFile:
|
||||
folder = "logs"
|
||||
file = f"{folder}/flask-application.log"
|
||||
|
||||
if not os.path.exists(folder):
|
||||
os.mkdir(folder)
|
||||
|
||||
fh = logging.FileHandler(file)
|
||||
fh.setLevel(level=fhLogLevel)
|
||||
fh.setFormatter(fFormatter)
|
||||
log.addHandler(fh)
|
||||
|
||||
return log
|
@ -1,2 +1,3 @@
|
||||
from .Logger import Logger
|
||||
from .MessageHandler import MessageHandler
|
||||
from .singleton import Singleton
|
||||
from .logger import Logger
|
||||
from .message_handler import MessageHandler
|
||||
|
61
src/core/utils/logger.py
Normal file
61
src/core/utils/logger.py
Normal file
@ -0,0 +1,61 @@
|
||||
# Python imports
|
||||
import os
|
||||
import logging
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from . import Singleton
|
||||
|
||||
|
||||
|
||||
class Logger(Singleton):
|
||||
"""
|
||||
Create a new logging object and return it.
|
||||
:note:
|
||||
NOSET # Don't know the actual log level of this... (defaulting or literally none?)
|
||||
Log Levels (From least to most)
|
||||
Type Value
|
||||
CRITICAL 50
|
||||
ERROR 40
|
||||
WARNING 30
|
||||
INFO 20
|
||||
DEBUG 10
|
||||
:param loggerName: Sets the name of the logger object. (Used in log lines)
|
||||
:param createFile: Whether we create a log file or just pump to terminal
|
||||
|
||||
:return: the logging object we created
|
||||
"""
|
||||
|
||||
def __init__(self, config_path: str, _ch_log_lvl = logging.CRITICAL, _fh_log_lvl = logging.INFO):
|
||||
self._CONFIG_PATH = config_path
|
||||
self.global_lvl = logging.DEBUG # Keep this at highest so that handlers can filter to their desired levels
|
||||
self.ch_log_lvl = _ch_log_lvl # Prety much the only one we ever change
|
||||
self.fh_log_lvl = _fh_log_lvl
|
||||
|
||||
def get_logger(self, loggerName: str = "NO_LOGGER_NAME_PASSED", createFile: bool = True) -> logging.Logger:
|
||||
log = logging.getLogger(loggerName)
|
||||
log.setLevel(self.global_lvl)
|
||||
|
||||
# Set our log output styles
|
||||
fFormatter = logging.Formatter('[%(asctime)s] %(pathname)s:%(lineno)d %(levelname)s - %(message)s', '%m-%d %H:%M:%S')
|
||||
cFormatter = logging.Formatter('%(pathname)s:%(lineno)d] %(levelname)s - %(message)s')
|
||||
|
||||
ch = logging.StreamHandler()
|
||||
ch.setLevel(level=self.ch_log_lvl)
|
||||
ch.setFormatter(cFormatter)
|
||||
log.addHandler(ch)
|
||||
|
||||
if createFile:
|
||||
folder = self._CONFIG_PATH
|
||||
file = f"{folder}/application.log"
|
||||
|
||||
if not os.path.exists(folder):
|
||||
os.mkdir(folder)
|
||||
|
||||
fh = logging.FileHandler(file)
|
||||
fh.setLevel(level=self.fh_log_lvl)
|
||||
fh.setFormatter(fFormatter)
|
||||
log.addHandler(fh)
|
||||
|
||||
return log
|
@ -11,5 +11,5 @@ class MessageHandler:
|
||||
print("MessageHandler initialized...")
|
||||
|
||||
|
||||
def create_JSON_message(self, type, text):
|
||||
def create(self, type, text):
|
||||
return '{"message": { "type": "' + type + '", "text": "' + text + '" } }'
|
24
src/core/utils/singleton.py
Normal file
24
src/core/utils/singleton.py
Normal file
@ -0,0 +1,24 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class SingletonError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class Singleton:
|
||||
ccount = 0
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
obj = super(Singleton, cls).__new__(cls)
|
||||
cls.ccount += 1
|
||||
|
||||
if cls.ccount == 2:
|
||||
raise SingletonError(f"Exceeded {cls.__name__} instantiation limit...")
|
||||
|
||||
return obj
|
@ -1,4 +1,4 @@
|
||||
from core import app
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=False)
|
||||
app.run(debug = False, host = '127.0.0.1', port = 5000)
|
||||
|
Loading…
Reference in New Issue
Block a user