première release

This commit is contained in:
2026-03-23 20:21:04 +01:00
commit 75c0a127bf
22 changed files with 3933 additions and 0 deletions

741
app.py Normal file
View File

@@ -0,0 +1,741 @@
from flask import Flask, render_template, request, redirect, url_for, send_file, flash, session
from werkzeug.security import generate_password_hash, check_password_hash
from functools import wraps
import sqlite3
import os
from datetime import datetime, timedelta
import json
from io import BytesIO
app = Flask(__name__)
# ✅ SÉCURITÉ : Clé secrète depuis variable d'environnement
app.secret_key = os.environ.get('SECRET_KEY', 'changez-moi-en-production-utilisez-env')
app.config['SESSION_COOKIE_SECURE'] = False # ✅ Changé pour HTTP (mettre True avec HTTPS)
app.config['SESSION_COOKIE_HTTPONLY'] = True # Protection XSS
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' # Protection CSRF
# ✅ Configuration base de données compatible Docker
DB_PATH = os.path.join(os.path.dirname(__file__), 'data', 'menu_miam.db')
# ✅ CREDENTIALS depuis variables d'environnement
ADMIN_USERNAME = os.environ.get('ADMIN_USERNAME', 'admin')
ADMIN_PASSWORD_HASH = generate_password_hash(
os.environ.get('ADMIN_PASSWORD', 'changeme123')
)
# ========== DÉCORATEUR D'AUTHENTIFICATION ==========
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if 'logged_in' not in session:
flash('⚠️ Vous devez vous connecter pour accéder à cette page', 'warning')
return redirect(url_for('login', next=request.url))
return f(*args, **kwargs)
return decorated_function
# ========== ROUTES D'AUTHENTIFICATION ==========
@app.route('/login', methods=['GET', 'POST'])
def login():
# Si déjà connecté, rediriger vers le menu
if 'logged_in' in session:
return redirect(url_for('menu'))
if request.method == 'POST':
username = request.form.get('username', '').strip()
password = request.form.get('password', '')
if username == ADMIN_USERNAME and check_password_hash(ADMIN_PASSWORD_HASH, password):
session['logged_in'] = True
session['username'] = username
flash('✅ Connexion réussie !', 'success')
# ✅ CORRECTION : Gérer correctement la redirection
next_page = request.args.get('next')
# Vérifier que next_page est un chemin relatif valide
if next_page and next_page.startswith('/') and not next_page.startswith('//'):
return redirect(next_page)
# Sinon, rediriger vers le menu par défaut
return redirect(url_for('menu'))
else:
flash('❌ Identifiants incorrects', 'error')
return render_template('login.html')
@app.route('/logout')
def logout():
session.clear()
flash('✅ Déconnexion réussie', 'success')
return redirect(url_for('login'))
# ========== CONNEXION BASE DE DONNÉES ==========
def get_connection():
"""Connexion à la base de données avec chemin persistant"""
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
return conn
# ========== INITIALISATION BASE DE DONNÉES ==========
def init_db():
"""Créer le dossier data et initialiser la base de données"""
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = get_connection()
# ✅ Table recettes
conn.execute('''
CREATE TABLE IF NOT EXISTS recettes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nom TEXT NOT NULL,
ingredients TEXT,
instructions TEXT,
lien TEXT
)
''')
# ✅ Table menu
conn.execute('''
CREATE TABLE IF NOT EXISTS menu (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT NOT NULL,
jour TEXT NOT NULL,
repas TEXT NOT NULL,
recette_id INTEGER,
FOREIGN KEY (recette_id) REFERENCES recettes (id),
UNIQUE(date, jour, repas)
)
''')
# ✅ Table accompagnements du menu (MODIFIÉ - ajout de la colonne date)
conn.execute('''
CREATE TABLE IF NOT EXISTS menu_accompagnements (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT NOT NULL,
jour TEXT NOT NULL,
repas TEXT NOT NULL,
accompagnement_id INTEGER NOT NULL,
FOREIGN KEY (accompagnement_id) REFERENCES accompagnements (id) ON DELETE CASCADE,
UNIQUE(date, jour, repas, accompagnement_id)
)
''')
# ✅ Table accompagnements
conn.execute('''
CREATE TABLE IF NOT EXISTS accompagnements (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nom TEXT NOT NULL UNIQUE,
descriptif TEXT
)
''')
# ✅ Table de liaison recette-accompagnements (pour suggestions par défaut)
conn.execute('''
CREATE TABLE IF NOT EXISTS recette_accompagnements (
id INTEGER PRIMARY KEY AUTOINCREMENT,
recette_id INTEGER NOT NULL,
accompagnement_id INTEGER NOT NULL,
FOREIGN KEY (recette_id) REFERENCES recettes (id) ON DELETE CASCADE,
FOREIGN KEY (accompagnement_id) REFERENCES accompagnements (id) ON DELETE CASCADE,
UNIQUE(recette_id, accompagnement_id)
)
''')
# ✅ Ajouter des recettes d'exemple si la base est vide
count = conn.execute('SELECT COUNT(*) FROM recettes').fetchone()[0]
if count == 0:
recettes_exemple = [
("Pâtes Carbonara",
"Pâtes (400g), Lardons (200g), Œufs (4), Parmesan (100g), Crème fraîche (20cl)",
"1. Cuire les pâtes al dente\n2. Faire revenir les lardons\n3. Mélanger œufs, parmesan et crème\n4. Incorporer aux pâtes chaudes\n5. Servir immédiatement",
""),
("Salade César",
"Salade romaine (1), Poulet grillé (300g), Croûtons (100g), Parmesan (50g), Sauce césar (15cl)",
"1. Laver et couper la salade\n2. Couper le poulet en lamelles\n3. Ajouter les croûtons et le parmesan\n4. Arroser de sauce césar\n5. Mélanger délicatement",
""),
("Tiramisu",
"Mascarpone (500g), Œufs (6), Sucre (150g), Café fort (30cl), Biscuits cuillère (400g), Cacao en poudre",
"1. Préparer le café et le laisser refroidir\n2. Séparer blancs et jaunes d'œufs\n3. Mélanger jaunes avec sucre et mascarpone\n4. Monter les blancs en neige et incorporer\n5. Tremper biscuits dans le café\n6. Alterner couches de biscuits et crème\n7. Saupoudrer de cacao\n8. Réfrigérer 4h minimum",
""),
("Poulet rôti",
"Poulet entier (1,5kg), Beurre (50g), Citron (1), Thym, Ail (3 gousses), Pommes de terre (1kg)",
"1. Préchauffer four à 200°C\n2. Farcir le poulet avec citron, thym et ail\n3. Badigeonner de beurre\n4. Disposer pommes de terre autour\n5. Cuire 1h15, arroser régulièrement",
""),
("Quiche Lorraine",
"Pâte brisée (1), Lardons (200g), Œufs (4), Crème fraîche (30cl), Gruyère râpé (100g), Noix de muscade",
"1. Préchauffer four à 180°C\n2. Étaler la pâte dans un moule\n3. Faire revenir les lardons\n4. Mélanger œufs, crème et gruyère\n5. Ajouter lardons et muscade\n6. Verser sur la pâte\n7. Cuire 35-40 minutes",
"")
]
try:
conn.executemany('''
INSERT INTO recettes (nom, ingredients, instructions, lien)
VALUES (?, ?, ?, ?)
''', recettes_exemple)
conn.commit()
print("✅ Base de données initialisée avec succès!")
print(f"{len(recettes_exemple)} recettes d'exemple ajoutées!")
except sqlite3.IntegrityError as e:
print(f"⚠️ Certaines recettes existent déjà : {e}")
else:
print(f"✅ Base de données déjà initialisée ({count} recettes)")
# ✅ Ajouter des accompagnements d'exemple
count_acc = conn.execute('SELECT COUNT(*) FROM accompagnements').fetchone()[0]
if count_acc == 0:
accompagnements_exemple = [
("Riz blanc", "Riz basmati ou thaï"),
("Pâtes", "Pâtes italiennes variées"),
("Frites", "Pommes de terre frites maison"),
("Salade verte", "Salade fraîche de saison"),
("Légumes vapeur", "Carottes, brocolis, haricots verts"),
("Purée", "Purée de pommes de terre maison"),
("Ratatouille", "Légumes du soleil mijotés")
]
try:
conn.executemany('''
INSERT INTO accompagnements (nom, descriptif)
VALUES (?, ?)
''', accompagnements_exemple)
conn.commit()
print(f"{len(accompagnements_exemple)} accompagnements d'exemple ajoutés!")
except sqlite3.IntegrityError as e:
print(f"⚠️ Certains accompagnements existent déjà : {e}")
conn.close()
# ✅ INITIALISATION AU DÉMARRAGE
init_db()
# ========== ROUTES PROTÉGÉES ==========
@app.route('/menu')
@app.route('/')
@login_required
def menu():
offset = int(request.args.get('offset', 0))
conn = get_connection()
# ✅ MODIFIÉ : Calculer les dates de la semaine AVANT la requête
today = datetime.now().date() + timedelta(weeks=offset)
start_of_week = today - timedelta(days=today.weekday())
end_of_week = start_of_week + timedelta(days=6)
# ✅ MODIFIÉ : Récupération du menu pour LA SEMAINE ACTUELLE UNIQUEMENT
menu_data = conn.execute('''
SELECT m.date, m.jour, m.repas, m.recette_id, r.nom, r.ingredients, r.lien
FROM menu m
LEFT JOIN recettes r ON m.recette_id = r.id
WHERE m.date BETWEEN ? AND ?
''', (start_of_week.isoformat(), end_of_week.isoformat())).fetchall()
menu = {}
for row in menu_data:
key = f"{row['jour']}_{row['repas']}"
menu[key] = {
'id': row['recette_id'],
'nom': row['nom'],
'ingredients': row['ingredients'],
'lien': row['lien']
}
# ✅ MODIFIÉ : Charger les accompagnements en utilisant la DATE + jour + repas
accompagnements = conn.execute('''
SELECT a.id, a.nom, a.descriptif
FROM menu_accompagnements ma
JOIN accompagnements a ON ma.accompagnement_id = a.id
WHERE ma.date = ? AND ma.jour = ? AND ma.repas = ?
ORDER BY a.nom
''', (row['date'], row['jour'], row['repas'])).fetchall()
menu[key]['accompagnements'] = [dict(acc) for acc in accompagnements]
# Calcul des dates pour l'affichage
days = []
jours = ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche']
for i, jour in enumerate(jours):
date = start_of_week + timedelta(days=i)
days.append({
'name': jour,
'date': date.strftime('%d/%m/%Y')
})
# Récupération de toutes les recettes pour le dropdown
recettes = conn.execute('SELECT id, nom FROM recettes ORDER BY nom').fetchall()
# Récupération de tous les accompagnements pour le dropdown
tous_accompagnements = conn.execute('SELECT id, nom FROM accompagnements ORDER BY nom').fetchall()
start_date = start_of_week.strftime('%d/%m/%Y')
end_date = end_of_week.strftime('%d/%m/%Y')
conn.close()
return render_template('menu.html',
menu=menu,
days=days,
recettes=recettes,
tous_accompagnements=tous_accompagnements,
offset=offset,
start_date=start_date,
end_date=end_date)
@app.route('/add_to_menu', methods=['POST'])
@login_required
def add_to_menu():
day = request.form['day']
meal = request.form['meal']
recette_id = request.form['recette_id']
offset = int(request.form.get('offset', 0))
anchor = request.form.get('anchor', '') # ✅ AJOUTÉ
today = datetime.now().date()
start_of_week = today - timedelta(days=today.weekday()) + timedelta(weeks=offset)
jours_fr = ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche']
day_index = jours_fr.index(day)
target_date = start_of_week + timedelta(days=day_index)
conn = get_connection()
# Supprimer l'ancienne entrée avant d'ajouter la nouvelle
conn.execute('''
DELETE FROM menu
WHERE date = ? AND jour = ? AND repas = ?
''', (target_date.isoformat(), day, meal))
# Ajouter la nouvelle recette
conn.execute('''
INSERT INTO menu (date, jour, repas, recette_id)
VALUES (?, ?, ?, ?)
''', (target_date.isoformat(), day, meal, recette_id))
conn.commit()
conn.close()
return redirect(url_for('menu', offset=offset, _anchor=anchor)) # ✅ MODIFIÉ
@app.route('/remove_from_menu', methods=['POST'])
@login_required
def remove_from_menu():
day = request.form['day']
meal = request.form['meal']
offset = int(request.form.get('offset', 0))
anchor = request.form.get('anchor', '') # ✅ AJOUTÉ
# Calculer la date exacte
today = datetime.now().date()
start_of_week = today - timedelta(days=today.weekday()) + timedelta(weeks=offset)
jours_fr = ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche']
day_index = jours_fr.index(day)
target_date = start_of_week + timedelta(days=day_index)
conn = get_connection()
# Supprimer la recette du menu
conn.execute('''
DELETE FROM menu
WHERE date = ? AND jour = ? AND repas = ?
''', (target_date.isoformat(), day, meal))
# ✅ MODIFIÉ : Supprimer aussi les accompagnements avec la DATE
conn.execute('''
DELETE FROM menu_accompagnements
WHERE date = ? AND jour = ? AND repas = ?
''', (target_date.isoformat(), day, meal))
conn.commit()
conn.close()
return redirect(url_for('menu', offset=offset, _anchor=anchor)) # ✅ MODIFIÉ
# ✅ MODIFIÉ : Ajouter un accompagnement au menu avec la DATE
@app.route('/add_accompagnement_to_menu', methods=['POST'])
@login_required
def add_accompagnement_to_menu():
day = request.form['day']
meal = request.form['meal']
accompagnement_id = request.form['accompagnement_id']
offset = int(request.form.get('offset', 0))
anchor = request.form.get('anchor', '') # ✅ AJOUTÉ
# Calculer la date exacte
today = datetime.now().date()
start_of_week = today - timedelta(days=today.weekday()) + timedelta(weeks=offset)
jours_fr = ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche']
day_index = jours_fr.index(day)
target_date = start_of_week + timedelta(days=day_index)
conn = get_connection()
# Ajouter l'accompagnement pour CE jour/repas/date spécifique
conn.execute('''
INSERT OR IGNORE INTO menu_accompagnements (date, jour, repas, accompagnement_id)
VALUES (?, ?, ?, ?)
''', (target_date.isoformat(), day, meal, accompagnement_id))
conn.commit()
conn.close()
return redirect(url_for('menu', offset=offset, _anchor=anchor)) # ✅ MODIFIÉ
# ✅ MODIFIÉ : Retirer un accompagnement du menu avec la DATE
@app.route('/remove_accompagnement_from_menu', methods=['POST'])
@login_required
def remove_accompagnement_from_menu():
day = request.form['day']
meal = request.form['meal']
accompagnement_id = request.form['accompagnement_id']
offset = int(request.form.get('offset', 0))
anchor = request.form.get('anchor', '') # ✅ AJOUTÉ
# Calculer la date exacte
today = datetime.now().date()
start_of_week = today - timedelta(days=today.weekday()) + timedelta(weeks=offset)
jours_fr = ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche']
day_index = jours_fr.index(day)
target_date = start_of_week + timedelta(days=day_index)
conn = get_connection()
# Retirer l'accompagnement pour CE jour/repas/date spécifique
conn.execute('''
DELETE FROM menu_accompagnements
WHERE date = ? AND jour = ? AND repas = ? AND accompagnement_id = ?
''', (target_date.isoformat(), day, meal, accompagnement_id))
conn.commit()
conn.close()
return redirect(url_for('menu', offset=offset, _anchor=anchor)) # ✅ MODIFIÉ
@app.route('/change_week', methods=['POST'])
@login_required
def change_week():
offset = int(request.form['offset'])
return redirect(url_for('menu', offset=offset))
# ========== ✅ NOUVEAU : ROUTE STATISTIQUES ==========
@app.route('/stats')
@login_required
def stats():
conn = get_connection()
# 📊 Top 10 recettes les plus utilisées
top_recettes = conn.execute('''
SELECT r.nom, COUNT(*) as count
FROM menu m
JOIN recettes r ON m.recette_id = r.id
GROUP BY r.nom
ORDER BY count DESC
LIMIT 10
''').fetchall()
# 📊 Nombre total de recettes planifiées
total_planifiees = conn.execute('SELECT COUNT(*) as count FROM menu').fetchone()['count']
# 📊 Nombre total de recettes disponibles
total_recettes = conn.execute('SELECT COUNT(*) as count FROM recettes').fetchone()['count']
# 📊 Recettes jamais utilisées
jamais_utilisees = conn.execute('''
SELECT r.nom
FROM recettes r
LEFT JOIN menu m ON r.id = m.recette_id
WHERE m.id IS NULL
ORDER BY r.nom
''').fetchall()
# 📊 Accompagnements les plus utilisés
top_accompagnements = conn.execute('''
SELECT a.nom, COUNT(*) as count
FROM menu_accompagnements ma
JOIN accompagnements a ON ma.accompagnement_id = a.id
GROUP BY a.nom
ORDER BY count DESC
LIMIT 10
''').fetchall()
conn.close()
return render_template('stats.html',
top_recettes=top_recettes,
total_planifiees=total_planifiees,
total_recettes=total_recettes,
jamais_utilisees=jamais_utilisees,
top_accompagnements=top_accompagnements)
# ========== FIN NOUVEAU ==========
@app.route('/recettes')
@login_required
def recettes():
conn = get_connection()
search = request.args.get('search', '').strip()
if search:
recettes = conn.execute('''
SELECT * FROM recettes
WHERE nom LIKE ? OR ingredients LIKE ?
ORDER BY nom
''', (f'%{search}%', f'%{search}%')).fetchall()
else:
recettes = conn.execute('SELECT * FROM recettes ORDER BY nom').fetchall()
conn.close()
return render_template('recettes.html', recettes=recettes, search=search)
@app.route('/recettes/add', methods=['GET', 'POST'])
@login_required
def add_recette():
if request.method == 'POST':
nom = request.form['nom']
ingredients = request.form.get('ingredients', '')
instructions = request.form.get('instructions', '')
lien = request.form.get('lien', '')
conn = get_connection()
conn.execute('''
INSERT INTO recettes (nom, ingredients, instructions, lien)
VALUES (?, ?, ?, ?)
''', (nom, ingredients, instructions, lien))
conn.commit()
conn.close()
return redirect(url_for('recettes'))
return render_template('add_recette.html')
@app.route('/recettes/edit/<int:id>', methods=['GET', 'POST'])
@login_required
def edit_recette(id):
conn = get_connection()
if request.method == 'POST':
nom = request.form['nom']
ingredients = request.form.get('ingredients', '')
instructions = request.form.get('instructions', '')
lien = request.form.get('lien', '')
# Mise à jour de la recette
conn.execute('''
UPDATE recettes
SET nom = ?, ingredients = ?, instructions = ?, lien = ?
WHERE id = ?
''', (nom, ingredients, instructions, lien, id))
# Gestion des accompagnements (suggestions par défaut pour cette recette)
# 1. Supprimer les anciennes associations
conn.execute('DELETE FROM recette_accompagnements WHERE recette_id = ?', (id,))
# 2. Ajouter les nouvelles associations
accompagnements_ids = request.form.getlist('accompagnements')
for acc_id in accompagnements_ids:
conn.execute('''
INSERT INTO recette_accompagnements (recette_id, accompagnement_id)
VALUES (?, ?)
''', (id, acc_id))
conn.commit()
conn.close()
flash('✅ Recette modifiée avec succès !', 'success')
return redirect(url_for('recettes'))
# Récupération des données pour l'affichage
recette = conn.execute('SELECT * FROM recettes WHERE id = ?', (id,)).fetchone()
accompagnements = conn.execute('SELECT * FROM accompagnements ORDER BY nom').fetchall()
# Récupérer les accompagnements sélectionnés
selected = conn.execute('''
SELECT accompagnement_id FROM recette_accompagnements
WHERE recette_id = ?
''', (id,)).fetchall()
selected_accompagnements = [row['accompagnement_id'] for row in selected]
conn.close()
return render_template('edit_recette.html',
recette=recette,
accompagnements=accompagnements,
selected_accompagnements=selected_accompagnements)
@app.route('/recettes/delete/<int:id>', methods=['POST'])
@login_required
def delete_recette(id):
conn = get_connection()
conn.execute('DELETE FROM recettes WHERE id = ?', (id,))
conn.commit()
conn.close()
return redirect(url_for('recettes'))
@app.route('/export_recettes')
@login_required
def export_recettes():
"""Exporter toutes les recettes en JSON"""
conn = get_connection()
recettes = conn.execute('SELECT nom, ingredients, instructions, lien FROM recettes ORDER BY nom').fetchall()
conn.close()
recettes_list = [dict(row) for row in recettes]
json_data = json.dumps(recettes_list, ensure_ascii=False, indent=2)
buffer = BytesIO()
buffer.write(json_data.encode('utf-8'))
buffer.seek(0)
filename = f"recettes_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
return send_file(
buffer,
mimetype='application/json',
as_attachment=True,
download_name=filename
)
@app.route('/import_recettes', methods=['POST'])
@login_required
def import_recettes():
"""Importer des recettes depuis un fichier JSON"""
if 'file' not in request.files:
flash('Aucun fichier sélectionné', 'error')
return redirect(url_for('recettes'))
file = request.files['file']
if file.filename == '':
flash('Aucun fichier sélectionné', 'error')
return redirect(url_for('recettes'))
if not file.filename.endswith('.json'):
flash('Le fichier doit être au format JSON', 'error')
return redirect(url_for('recettes'))
try:
data = json.load(file)
if not isinstance(data, list):
flash('Format JSON invalide (doit être une liste)', 'error')
return redirect(url_for('recettes'))
conn = get_connection()
imported = 0
updated = 0
for recette in data:
nom = recette.get('nom', '').strip()
if not nom:
continue
ingredients = recette.get('ingredients', '')
instructions = recette.get('instructions', '')
lien = recette.get('lien', '')
existing = conn.execute('SELECT id FROM recettes WHERE nom = ?', (nom,)).fetchone()
if existing:
conn.execute('''
UPDATE recettes
SET ingredients=?, instructions=?, lien=?
WHERE nom=?
''', (ingredients, instructions, lien, nom))
updated += 1
else:
conn.execute('''
INSERT INTO recettes (nom, ingredients, instructions, lien)
VALUES (?, ?, ?, ?)
''', (nom, ingredients, instructions, lien))
imported += 1
conn.commit()
conn.close()
flash(f'✅ Import réussi ! {imported} recette(s) ajoutée(s), {updated} mise(s) à jour', 'success')
except json.JSONDecodeError:
flash('❌ Erreur : fichier JSON invalide', 'error')
except Exception as e:
flash(f'❌ Erreur lors de l\'import : {str(e)}', 'error')
return redirect(url_for('recettes'))
# ========== ROUTES ACCOMPAGNEMENTS ==========
@app.route('/accompagnements')
@login_required
def accompagnements():
conn = get_connection()
accompagnements = conn.execute('SELECT * FROM accompagnements ORDER BY nom').fetchall()
conn.close()
return render_template('accompagnements.html', accompagnements=accompagnements)
@app.route('/accompagnements/add', methods=['GET', 'POST'])
@login_required
def add_accompagnement():
if request.method == 'POST':
nom = request.form['nom'].strip()
descriptif = request.form.get('descriptif', '').strip()
conn = get_connection()
conn.execute('''
INSERT INTO accompagnements (nom, descriptif)
VALUES (?, ?)
''', (nom, descriptif))
conn.commit()
conn.close()
flash('✅ Accompagnement ajouté avec succès !', 'success')
return redirect(url_for('accompagnements'))
return render_template('add_accompagnement.html')
@app.route('/accompagnements/edit/<int:id>', methods=['GET', 'POST'])
@login_required
def edit_accompagnement(id):
conn = get_connection()
if request.method == 'POST':
nom = request.form['nom'].strip()
descriptif = request.form.get('descriptif', '').strip()
conn.execute('''
UPDATE accompagnements
SET nom = ?, descriptif = ?
WHERE id = ?
''', (nom, descriptif, id))
conn.commit()
conn.close()
flash('✅ Accompagnement modifié avec succès !', 'success')
return redirect(url_for('accompagnements'))
accompagnement = conn.execute('SELECT * FROM accompagnements WHERE id = ?', (id,)).fetchone()
conn.close()
return render_template('edit_accompagnement.html', accompagnement=accompagnement)
@app.route('/accompagnements/delete/<int:id>', methods=['POST'])
@login_required
def delete_accompagnement(id):
conn = get_connection()
conn.execute('DELETE FROM accompagnements WHERE id = ?', (id,))
conn.commit()
conn.close()
flash('✅ Accompagnement supprimé avec succès !', 'success')
return redirect(url_for('accompagnements'))
if __name__ == '__main__':
app.run(debug=False, host='0.0.0.0')