""" GMAO Collège - Application Flask Modulaire Structure par domaine métier """ from flask import Flask, render_template from flask_sqlalchemy import SQLAlchemy from flask_login import LoginManager from flask_migrate import Migrate from flask_wtf.csrf import CSRFProtect from datetime import timedelta import os import json # Extensions (définies dans extensions.py) from .extensions import db, login_manager, migrate, csrf # Import all models to register them in SQLAlchemy metadata from .core import models def create_app(config_name='default'): """Factory pour créer l'application Flask.""" # Chemin absolu vers le répertoire app_new app_dir = os.path.dirname(os.path.abspath(__file__)) # Chemins absolus pour templates et instance template_dir = os.path.join(app_dir, 'templates') instance_dir = os.path.join(app_dir, 'instance') # Créer le dossier instance s'il n'existe pas os.makedirs(instance_dir, exist_ok=True) # Charger les variables d'environnement depuis .env AVANT la config from dotenv import load_dotenv load_dotenv() app = Flask(__name__, instance_path=instance_dir, instance_relative_config=True, template_folder=template_dir, static_folder=os.path.join(app_dir, 'static')) # Tolerer les slashs finaux dans les URLs app.url_map.strict_slashes = False # Configuration from .config import config as config_dict config_class = config_dict[config_name] app.config.from_object(config_class) config_class.init_app(app) # Initialisation des extensions db.init_app(app) login_manager.init_app(app) migrate.init_app(app, db) csrf.init_app(app) # Configuration du login manager login_manager.login_view = 'auth.login' login_manager.login_message = 'Veuillez vous connecter pour accéder à cette page.' login_manager.login_message_category = 'warning' # Import des modèles AVANT les blueprints from .core import models as core_models from .pronote import models as pronote_models from .ent import models as ent_models from .outlook import models as outlook_models from .yeastar import models as yeastar_models # Enregistrement des blueprints from .core.routes import auth_bp, admin_bp, dashboard_bp, setup_wizard_bp app.register_blueprint(auth_bp, url_prefix='/auth') app.register_blueprint(admin_bp, url_prefix='/admin') app.register_blueprint(dashboard_bp, url_prefix='/') app.register_blueprint(setup_wizard_bp, url_prefix='/setup-wizard') # Blueprints des modules métier from .equipments.routes import ( equipments_bp, meters_bp, documents_bp, restrictions_bp, scheduled_bp ) from .equipments.buildings import buildings_bp from .equipments.zones import zones_bp from .equipments.rooms import rooms_bp app.register_blueprint(equipments_bp, url_prefix='/equipments') app.register_blueprint(buildings_bp, url_prefix='/equipments/buildings') app.register_blueprint(zones_bp, url_prefix='/equipments/zones') app.register_blueprint(rooms_bp, url_prefix='/equipments/rooms') app.register_blueprint(meters_bp, url_prefix='/equipments') app.register_blueprint(documents_bp, url_prefix='/equipments') app.register_blueprint(restrictions_bp, url_prefix='/equipments') app.register_blueprint(scheduled_bp, url_prefix='/equipments') from .interventions.routes import interventions_bp, interventions_planning_bp app.register_blueprint(interventions_bp, url_prefix='/interventions') app.register_blueprint(interventions_planning_bp, url_prefix='/interventions') from .planning.routes import planning_bp app.register_blueprint(planning_bp, url_prefix='/planning') from .companies.routes import companies_bp app.register_blueprint(companies_bp, url_prefix='/companies') from .lots.routes import lots_bp app.register_blueprint(lots_bp, url_prefix='/lots') from .training.routes import training_bp app.register_blueprint(training_bp, url_prefix='/training') from .meters.routes import meters_bp app.register_blueprint(meters_bp, url_prefix='/meters') from .parts.routes import parts_bp app.register_blueprint(parts_bp, url_prefix='/parts') from .services_module.routes import services_bp app.register_blueprint(services_bp, url_prefix='/services') from .room_types.routes import room_types_bp app.register_blueprint(room_types_bp, url_prefix='/room-types') from .documents.routes import documents_bp app.register_blueprint(documents_bp, url_prefix='/documents') from .gmao_config.routes import gmao_config_bp app.register_blueprint(gmao_config_bp, url_prefix='/gmao-config') from .wizard.routes import wizard_bp app.register_blueprint(wizard_bp) # Blueprints des intégrations from .pronote.routes import pronote_bp csrf.exempt(pronote_bp) app.register_blueprint(pronote_bp, url_prefix='/pronote') from .ent.routes import ent_bp app.register_blueprint(ent_bp, url_prefix='/ent') from .outlook.routes import outlook_bp, outlook_auth_bp, outlook_sync_bp, outlook_dashboard_bp app.register_blueprint(outlook_bp, url_prefix='/outlook') app.register_blueprint(outlook_auth_bp, url_prefix='/outlook') app.register_blueprint(outlook_sync_bp, url_prefix='/outlook') app.register_blueprint(outlook_dashboard_bp, url_prefix='/outlook') from .yeastar.routes import yeastar_bp app.register_blueprint(yeastar_bp, url_prefix='/yeastar') from .chatbot.routes import chatbot_bp app.register_blueprint(chatbot_bp, url_prefix='/chatbot') # Blueprint status systeme from .status.routes import status_bp app.register_blueprint(status_bp) from .health import health_bp app.register_blueprint(health_bp) # Logs centralises des watchdogs from .logs.routes import logs_bp app.register_blueprint(logs_bp) # API REST v1 from .api.routes import api_bp app.register_blueprint(api_bp) csrf.exempt(api_bp) # Notifications temps reel from .notifications.routes import notifications_bp app.register_blueprint(notifications_bp) csrf.exempt(notifications_bp) # Exports PDF/CSV from .exports.routes import exports_bp app.register_blueprint(exports_bp) # Messagerie unifiee from .messagerie.routes import messagerie_bp app.register_blueprint(messagerie_bp) csrf.exempt(messagerie_bp) # Contrats d'entreprise from .contracts.models import Contract from .contracts.routes import contracts_bp app.register_blueprint(contracts_bp) # Configuration IA (OpenRouter) from .ai_config.routes import ai_config_bp app.register_blueprint(ai_config_bp) # Contraintes de zone from .constraints.routes import constraints_bp app.register_blueprint(constraints_bp) # Planificateur automatique from .scheduler.routes import scheduler_bp app.register_blueprint(scheduler_bp) csrf.exempt(scheduler_bp) # Filtres Jinja2 pour les templates from .utils import format_status, status_badge_class, priority_badge_class, bootstrap_color_to_hex, bootstrap_text_color app.jinja_env.filters["date_fmt"] = lambda value: value.strftime("%d/%m/%Y") if value else "" app.jinja_env.filters["datetime_fmt"] = lambda value: value.strftime("%d/%m/%Y %H:%M") if value else "" app.jinja_env.filters["format_status"] = format_status app.jinja_env.filters["status_badge"] = status_badge_class app.jinja_env.filters["priority_badge"] = priority_badge_class app.jinja_env.filters["from_json"] = lambda value: json.loads(value) if value else [] app.jinja_env.filters["hex_color"] = bootstrap_color_to_hex app.jinja_env.filters["text_color"] = bootstrap_text_color app.jinja_env.globals["timedelta"] = timedelta # Création des tables (fallback si migrations pas encore appliquees) with app.app_context(): db.create_all() # Gestionnaires d'erreurs personnalisées @app.errorhandler(404) def not_found_error(e): return render_template('errors/error.html', error_code='404', error_title='Page non trouvée', error_message='La page demandée n’existe pas ou a été déplacée.'), 404 @app.errorhandler(500) def internal_error(e): try: db.session.rollback() except Exception: pass return render_template('errors/error.html', error_code='500', error_title='Erreur serveur', error_message='Une erreur est survenue. Elle a été signalée automatiquement.'), 500 @app.errorhandler(403) def forbidden_error(e): return render_template('errors/error.html', error_code='403', error_title='Accès refusé', error_message='Vous n’avez pas les droits nécessaires pour accéder à cette page.'), 403 return app # User loader pour Flask-Login @login_manager.user_loader def load_user(user_id): """Charge un utilisateur par son ID.""" from .core.models.user import User return User.query.get(int(user_id))