251 lines
9.4 KiB
Python
251 lines
9.4 KiB
Python
"""
|
||
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))
|