From 62834b72809a2b28cc0a950e70ffedce83a31871 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sat, 9 Sep 2023 18:18:43 -0500 Subject: [PATCH] Removed React and added HTMX --- README.md | 2 +- src/core/__builtins__.py | 2 +- src/core/__init__.py | 4 -- src/core/models.py | 9 ++- src/core/routes/__init__.py | 1 + src/core/routes/htmx_page.py | 30 +++++++++ src/core/routes/routes.py | 18 +++--- src/core/static/js/ajax.js | 7 +++ src/core/static/js/events.js | 13 ++++ src/core/templates/layout.html | 10 ++- .../pages/{react-page.html => htmx-page.html} | 4 +- src/core/templates/pages/index.html | 6 +- src/core/utils/Logger.py | 63 ------------------- src/core/utils/__init__.py | 5 +- src/core/utils/logger.py | 61 ++++++++++++++++++ .../{MessageHandler.py => message_handler.py} | 2 +- src/core/utils/singleton.py | 24 +++++++ src/wsgi.py | 2 +- 18 files changed, 170 insertions(+), 93 deletions(-) create mode 100644 src/core/routes/htmx_page.py rename src/core/templates/pages/{react-page.html => htmx-page.html} (85%) delete mode 100644 src/core/utils/Logger.py create mode 100644 src/core/utils/logger.py rename src/core/utils/{MessageHandler.py => message_handler.py} (83%) create mode 100644 src/core/utils/singleton.py diff --git a/README.md b/README.md index 5821404..5c10dc1 100644 --- a/README.md +++ b/README.md @@ -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 ``` diff --git a/src/core/__builtins__.py b/src/core/__builtins__.py index 465c3fb..454eaf0 100644 --- a/src/core/__builtins__.py +++ b/src/core/__builtins__.py @@ -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() diff --git a/src/core/__init__.py b/src/core/__init__.py index 4ae95f1..a522218 100644 --- a/src/core/__init__.py +++ b/src/core/__init__.py @@ -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 diff --git a/src/core/models.py b/src/core/models.py index 4978349..8259f78 100644 --- a/src/core/models.py +++ b/src/core/models.py @@ -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() diff --git a/src/core/routes/__init__.py b/src/core/routes/__init__.py index 2bbe6b6..2e25d1f 100644 --- a/src/core/routes/__init__.py +++ b/src/core/routes/__init__.py @@ -8,3 +8,4 @@ from .login_controller import oidc_register from .login_controller import controller from . import routes +from . import htmx_page diff --git a/src/core/routes/htmx_page.py b/src/core/routes/htmx_page.py new file mode 100644 index 0000000..44904b1 --- /dev/null +++ b/src/core/routes/htmx_page.py @@ -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 "

Hello, World!

" + + return 'Must use GET request type...' diff --git a/src/core/routes/routes.py b/src/core/routes/routes.py index 9883028..a8afe42 100644 --- a/src/core/routes/routes.py +++ b/src/core/routes/routes.py @@ -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(): diff --git a/src/core/static/js/ajax.js b/src/core/static/js/ajax.js index 8a500fe..eb2a61c 100644 --- a/src/core/static/js/ajax.js +++ b/src/core/static/js/ajax.js @@ -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(); diff --git a/src/core/static/js/events.js b/src/core/static/js/events.js index 902ad7a..dc96aec 100644 --- a/src/core/static/js/events.js +++ b/src/core/static/js/events.js @@ -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; +}; diff --git a/src/core/templates/layout.html b/src/core/templates/layout.html index dd1bdca..86cc1b8 100644 --- a/src/core/templates/layout.html +++ b/src/core/templates/layout.html @@ -101,15 +101,13 @@ {% block body_scripts %} - + - + - - - - + + {% block body_scripts_additional %} diff --git a/src/core/templates/pages/react-page.html b/src/core/templates/pages/htmx-page.html similarity index 85% rename from src/core/templates/pages/react-page.html rename to src/core/templates/pages/htmx-page.html index 229bd60..5c4bbce 100644 --- a/src/core/templates/pages/react-page.html +++ b/src/core/templates/pages/htmx-page.html @@ -14,7 +14,8 @@ {% endblock body_header_additional %} {% block body_content_additional %} -
+
+ I got replaced...
{% endblock body_content_additional %} @@ -26,5 +27,4 @@ - {% endblock body_scripts_additional %} diff --git a/src/core/templates/pages/index.html b/src/core/templates/pages/index.html index c2c0be6..5ffd755 100644 --- a/src/core/templates/pages/index.html +++ b/src/core/templates/pages/index.html @@ -17,9 +17,9 @@

Using Bootstrap 5 Themeing with Dark Mode defaulted...

-

With React available...

-

A React Example Page:  - +

With HTMX available...

+

An HTMX Example Page:  + diff --git a/src/core/utils/Logger.py b/src/core/utils/Logger.py deleted file mode 100644 index 7381899..0000000 --- a/src/core/utils/Logger.py +++ /dev/null @@ -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 diff --git a/src/core/utils/__init__.py b/src/core/utils/__init__.py index 9e9378f..e4013be 100644 --- a/src/core/utils/__init__.py +++ b/src/core/utils/__init__.py @@ -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 diff --git a/src/core/utils/logger.py b/src/core/utils/logger.py new file mode 100644 index 0000000..d64b4ea --- /dev/null +++ b/src/core/utils/logger.py @@ -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 diff --git a/src/core/utils/MessageHandler.py b/src/core/utils/message_handler.py similarity index 83% rename from src/core/utils/MessageHandler.py rename to src/core/utils/message_handler.py index 6cd28f6..d889394 100644 --- a/src/core/utils/MessageHandler.py +++ b/src/core/utils/message_handler.py @@ -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 + '" } }' diff --git a/src/core/utils/singleton.py b/src/core/utils/singleton.py new file mode 100644 index 0000000..23b7191 --- /dev/null +++ b/src/core/utils/singleton.py @@ -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 diff --git a/src/wsgi.py b/src/wsgi.py index 5681760..45c35ea 100644 --- a/src/wsgi.py +++ b/src/wsgi.py @@ -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)