Files
natureinpots_community/app/__init__.py

181 lines
7.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# File: app/__init__.py
import os
import json
import importlib
import time
from flask import Flask, request
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
from flask_wtf.csrf import CSRFProtect
from datetime import datetime
from dotenv import load_dotenv, find_dotenv
# ─── Load .env ────────────────────────────────────────────────────────────────
dotenv_path = find_dotenv()
if dotenv_path:
load_dotenv(dotenv_path, override=True)
# ─── Core extensions ───────────────────────────────────────────────────────────
db = SQLAlchemy()
migrate = Migrate()
login_manager = LoginManager()
csrf = CSRFProtect()
# ─── Template helper (still in core) ──────────────────────────────────────────
from plugins.media.routes import generate_image_url # noqa: E402
def create_app():
# ─── Configure Flask ────────────────────────────────────────────────────────
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
app = Flask(
__name__,
static_folder=os.path.join(project_root, 'static'),
static_url_path='/static'
)
app.config.from_object('app.config.Config')
# ─── Init extensions ───────────────────────────────────────────────────────
csrf.init_app(app)
db.init_app(app)
migrate.init_app(app, db)
login_manager.init_app(app)
login_manager.login_view = 'auth.login'
# ─── Core routes & errors ───────────────────────────────────────────────────
from .errors import bp as errors_bp # noqa: E402
app.register_blueprint(errors_bp)
from .routes import init_app as register_core_routes # noqa: E402
register_core_routes(app)
app.logger.info("✔️ Registered core routes")
# ─── JSONdriven plugin loader ──────────────────────────────────────────────
plugins_dir = os.path.join(project_root, 'plugins')
for name in sorted(os.listdir(plugins_dir)):
plugin_path = os.path.join(plugins_dir, name)
manifest = os.path.join(plugin_path, 'plugin.json')
if not os.path.isfile(manifest):
continue
errors = []
try:
meta = json.load(open(manifest))
except Exception as e:
print(f"Plugin '{name}' 🛑 manifest load failed: {e}")
continue
# 1) Import models
for model_path in meta.get('models', []):
try:
importlib.import_module(model_path)
except Exception as e:
errors.append(f"model import ({model_path}): {e}")
# 1.b) user_loader hook
ul = meta.get('user_loader')
if ul:
try:
mod = importlib.import_module(ul['module'])
fn = getattr(mod, ul['callable'])
fn(app)
except Exception as e:
errors.append(f"user_loader ({ul['module']}:{ul['callable']}): {e}")
# 2) Register routes
routes_cfg = meta.get('routes')
if routes_cfg:
try:
mod = importlib.import_module(routes_cfg['module'])
bp_obj = getattr(mod, routes_cfg['blueprint'])
prefix = routes_cfg.get('url_prefix')
app.register_blueprint(bp_obj, url_prefix=prefix, strict_slashes=False)
except Exception as e:
errors.append(f"routes ({routes_cfg['module']}): {e}")
# 3) Register CLI commands
cli_cfg = meta.get('cli')
if cli_cfg:
try:
mod = importlib.import_module(cli_cfg['module'])
fn = getattr(mod, cli_cfg['callable'])
app.cli.add_command(fn)
except Exception as e:
errors.append(f"cli ({cli_cfg['module']}:{cli_cfg['callable']}): {e}")
# 4) Template globals
for tg in meta.get('template_globals', []):
try:
mod_name, fn_name = tg['callable'].rsplit('.', 1)
mod = importlib.import_module(mod_name)
fn = getattr(mod, fn_name)
app.jinja_env.globals[tg['name']] = fn
except Exception as e:
errors.append(f"template_global ({tg}): {e}")
# 5) Subplugins (models + routes)
for sp in meta.get('subplugins', []):
for mp in sp.get('models', []):
try:
importlib.import_module(mp)
except Exception as e:
errors.append(f"subplugin model ({mp}): {e}")
sp_rt = sp.get('routes')
if sp_rt:
try:
mod = importlib.import_module(sp_rt['module'])
bp_obj = getattr(mod, sp_rt['blueprint'])
prefix = sp_rt.get('url_prefix')
app.register_blueprint(bp_obj, url_prefix=prefix, strict_slashes=False)
except Exception as e:
errors.append(f"subplugin routes ({sp_rt['module']}): {e}")
# Final status
if errors:
print(f"Plugin '{name}' 🛑 failed to load: {'; '.join(errors)}")
else:
print(f"Plugin '{name}' ✔️ Loaded Successfully.")
# ─── Context processors, analytics, teardown ───────────────────────────────
@app.context_processor
def inject_current_year():
return {'current_year': datetime.now().year}
@app.context_processor
def inject_now():
return {'utcnow': datetime.utcnow()}
@app.before_request
def start_timer():
request._start_time = time.time()
@app.after_request
def log_analytics(response):
from plugins.admin.models import AnalyticsEvent # noqa: E402
try:
duration = time.time() - getattr(request, '_start_time', time.time())
ev = AnalyticsEvent(
method = request.method,
path = request.path,
status_code = response.status_code,
response_time = duration,
user_agent = request.headers.get('User-Agent'),
referer = request.headers.get('Referer'),
accept_language=request.headers.get('Accept-Language'),
)
db.session.add(ev)
db.session.commit()
except Exception:
db.session.rollback()
return response
@app.teardown_appcontext
def shutdown_session(exception=None):
db.session.remove()
# ─── Keep the template helper exposed ──────────────────────────────────────
app.jinja_env.globals['generate_image_url'] = generate_image_url
return app