seo-dev-env 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- seo/__init__.py +3 -3
- seo/cli.py +165 -0
- seo/commandes.py +93 -0
- seo/generators.py +206 -25
- seo/templates/debutant/app.py +16 -0
- seo/templates/debutant/index.html +46 -0
- seo/templates/debutant/style.css +110 -0
- seo/templates/intermediaire/app/__init__.py +38 -0
- seo/templates/intermediaire/app/core/__init__.py +0 -0
- seo/templates/intermediaire/app/core/config.py +23 -0
- seo/templates/intermediaire/app/taches/__init__.py +0 -0
- seo/templates/intermediaire/app/taches/models.py +18 -0
- seo/templates/intermediaire/app/taches/routes.py +44 -0
- seo/templates/intermediaire/app/utilisateurs/__init__.py +0 -0
- seo/templates/intermediaire/app/utilisateurs/models.py +29 -0
- seo/templates/intermediaire/app/utilisateurs/routes.py +34 -0
- seo/templates/intermediaire/config.py +10 -0
- seo/templates/intermediaire/docker-compose.yml +39 -0
- seo/templates/intermediaire/run.py +17 -0
- seo/templates/pro/Dockerfile +26 -0
- seo/templates/pro/docker-compose.yml +39 -0
- seo/utils.py +32 -24
- {seo_dev_env-0.1.0.dist-info → seo_dev_env-0.1.2.dist-info}/METADATA +2 -2
- seo_dev_env-0.1.2.dist-info/RECORD +38 -0
- {seo_dev_env-0.1.0.dist-info → seo_dev_env-0.1.2.dist-info}/WHEEL +1 -1
- seo_dev_env-0.1.2.dist-info/entry_points.txt +3 -0
- seo_dev_env-0.1.0.dist-info/RECORD +0 -26
- seo_dev_env-0.1.0.dist-info/entry_points.txt +0 -2
- {seo_dev_env-0.1.0.dist-info → seo_dev_env-0.1.2.dist-info}/licenses/LICENSE.ls +0 -0
- {seo_dev_env-0.1.0.dist-info → seo_dev_env-0.1.2.dist-info}/top_level.txt +0 -0
seo/__init__.py
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
from .generators import creer_environnement, creer_projet
|
|
2
|
-
|
|
3
|
-
__all__ = ['creer_environnement', 'creer_projet']
|
|
1
|
+
from .generators import creer_environnement, creer_projet, creer_projet_interactif, main
|
|
2
|
+
|
|
3
|
+
__all__ = ['creer_environnement', 'creer_projet', 'creer_projet_interactif', 'main']
|
seo/cli.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""CLI interactif pour SEO Dev Env"""
|
|
2
|
+
import sys
|
|
3
|
+
from typing import Dict, Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def afficher_titre():
|
|
7
|
+
print("\n" + "="*60)
|
|
8
|
+
print(" SEO Dev Env - Créateur d'architecture Flask")
|
|
9
|
+
print(" pour développeurs francophones")
|
|
10
|
+
print("="*60 + "\n")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def poser_question(question: str, options: list, defaut: int = 1) -> int:
|
|
14
|
+
print(f"\n{question}")
|
|
15
|
+
for i, option in enumerate(options, 1):
|
|
16
|
+
marqueur = "" if i == defaut else " "
|
|
17
|
+
print(f" {marqueur} {i}. {option}")
|
|
18
|
+
while True:
|
|
19
|
+
try:
|
|
20
|
+
choix = input(f"\nVotre choix [1-{len(options)}] (défaut: {defaut}): ").strip()
|
|
21
|
+
if not choix:
|
|
22
|
+
return defaut - 1
|
|
23
|
+
choix_int = int(choix)
|
|
24
|
+
if 1 <= choix_int <= len(options):
|
|
25
|
+
return choix_int - 1
|
|
26
|
+
else:
|
|
27
|
+
print(f" Veuillez choisir entre 1 et {len(options)}")
|
|
28
|
+
except ValueError:
|
|
29
|
+
print(" Veuillez entrer un nombre valide")
|
|
30
|
+
except KeyboardInterrupt:
|
|
31
|
+
print("\n\n Annulé")
|
|
32
|
+
sys.exit(0)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def poser_question_texte(question: str, defaut: str = "") -> str:
|
|
36
|
+
prompt = f"{question} (défaut: {defaut}): " if defaut else f"{question}: "
|
|
37
|
+
try:
|
|
38
|
+
reponse = input(prompt).strip()
|
|
39
|
+
return reponse if reponse else defaut
|
|
40
|
+
except KeyboardInterrupt:
|
|
41
|
+
print("\n\n Annulé")
|
|
42
|
+
sys.exit(0)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def confirmer(question: str, defaut: bool = True) -> bool:
|
|
46
|
+
suffixe = "[O/n]" if defaut else "[o/N]"
|
|
47
|
+
try:
|
|
48
|
+
reponse = input(f"{question} {suffixe}: ").strip().lower()
|
|
49
|
+
if not reponse:
|
|
50
|
+
return defaut
|
|
51
|
+
return reponse in ["o", "oui", "y", "yes"]
|
|
52
|
+
except KeyboardInterrupt:
|
|
53
|
+
print("\n\n Annulé")
|
|
54
|
+
sys.exit(0)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def collecter_preferences() -> Dict[str, Any]:
|
|
58
|
+
afficher_titre()
|
|
59
|
+
preferences = {}
|
|
60
|
+
|
|
61
|
+
print(" Configuration du projet")
|
|
62
|
+
preferences["nom_projet"] = poser_question_texte("Nom de votre projet", "mon-projet")
|
|
63
|
+
|
|
64
|
+
type_options = [
|
|
65
|
+
"Apprentissage (simple, pour débuter)",
|
|
66
|
+
"Application web (structure MVC complète)",
|
|
67
|
+
"API professionnelle (production-ready)",
|
|
68
|
+
"Startup SaaS (auth, paiement, dashboard)"
|
|
69
|
+
]
|
|
70
|
+
type_choix = poser_question(" Quel type de projet ?", type_options)
|
|
71
|
+
types_mapping = {0: "apprentissage", 1: "application", 2: "api", 3: "saas"}
|
|
72
|
+
preferences["type_projet"] = types_mapping[type_choix]
|
|
73
|
+
|
|
74
|
+
if preferences["type_projet"] in ["application", "api", "saas"]:
|
|
75
|
+
db_options = [
|
|
76
|
+
"SQLite (simple, fichier local)",
|
|
77
|
+
"PostgreSQL (recommandé pour production)",
|
|
78
|
+
"MySQL (compatible, largement utilisé)"
|
|
79
|
+
]
|
|
80
|
+
db_choix = poser_question(" Quelle base de données ?", db_options,
|
|
81
|
+
defaut=2 if preferences["type_projet"] in ["api", "saas"] else 1)
|
|
82
|
+
db_mapping = {0: "sqlite", 1: "postgresql", 2: "mysql"}
|
|
83
|
+
preferences["base_donnees"] = db_mapping[db_choix]
|
|
84
|
+
|
|
85
|
+
if preferences["base_donnees"] == "sqlite" and preferences["type_projet"] in ["api", "saas"]:
|
|
86
|
+
print("\n ATTENTION: SQLite non recommandé pour production.")
|
|
87
|
+
if not confirmer("\n Continuer avec SQLite ?", defaut=False):
|
|
88
|
+
print("\n Passage à PostgreSQL")
|
|
89
|
+
preferences["base_donnees"] = "postgresql"
|
|
90
|
+
|
|
91
|
+
auth_options = [
|
|
92
|
+
"Session classique (cookies Flask)",
|
|
93
|
+
"JWT (tokens, pour API)",
|
|
94
|
+
"OAuth2 (Google, GitHub, etc.)"
|
|
95
|
+
]
|
|
96
|
+
auth_choix = poser_question(" Type d'authentification ?", auth_options,
|
|
97
|
+
defaut=2 if preferences["type_projet"] == "api" else 1)
|
|
98
|
+
auth_mapping = {0: "session", 1: "jwt", 2: "oauth"}
|
|
99
|
+
preferences["auth"] = auth_mapping[auth_choix]
|
|
100
|
+
|
|
101
|
+
if preferences["type_projet"] in ["api", "saas"]:
|
|
102
|
+
preferences["docker"] = True
|
|
103
|
+
print("\n Docker inclus automatiquement")
|
|
104
|
+
else:
|
|
105
|
+
preferences["docker"] = confirmer("\n Inclure Docker ?", defaut=True)
|
|
106
|
+
else:
|
|
107
|
+
preferences["base_donnees"] = "sqlite"
|
|
108
|
+
preferences["auth"] = "session"
|
|
109
|
+
preferences["docker"] = False
|
|
110
|
+
|
|
111
|
+
if preferences["type_projet"] == "saas":
|
|
112
|
+
preferences["stripe"] = confirmer("\n Inclure Stripe ?", defaut=True)
|
|
113
|
+
preferences["email"] = confirmer(" Inclure Flask-Mail ?", defaut=True)
|
|
114
|
+
preferences["celery"] = confirmer(" Inclure Celery ?", defaut=True)
|
|
115
|
+
|
|
116
|
+
preferences["git"] = confirmer("\n Initialiser Git ?", defaut=True)
|
|
117
|
+
return preferences
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def afficher_resume(preferences: Dict[str, Any]):
|
|
121
|
+
print("\n" + "="*60)
|
|
122
|
+
print(" Résumé")
|
|
123
|
+
print("="*60)
|
|
124
|
+
print(f"\n Projet: {preferences['nom_projet']}")
|
|
125
|
+
print(f" Type: {preferences['type_projet']}")
|
|
126
|
+
if "base_donnees" in preferences:
|
|
127
|
+
print(f" Base: {preferences['base_donnees']}")
|
|
128
|
+
if "auth" in preferences:
|
|
129
|
+
print(f" Auth: {preferences['auth']}")
|
|
130
|
+
if preferences.get("docker"):
|
|
131
|
+
print(" Docker: ")
|
|
132
|
+
if preferences.get("stripe"):
|
|
133
|
+
print(" Stripe: ")
|
|
134
|
+
if preferences.get("email"):
|
|
135
|
+
print(" Email: ")
|
|
136
|
+
if preferences.get("celery"):
|
|
137
|
+
print(" Celery: ")
|
|
138
|
+
if preferences.get("git"):
|
|
139
|
+
print(" Git: ")
|
|
140
|
+
print("\n" + "="*60 + "\n")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def afficher_prochaines_etapes(nom_projet: str, type_projet: str, docker: bool = False):
|
|
144
|
+
print("\n" + "="*60)
|
|
145
|
+
print(" Projet créé !")
|
|
146
|
+
print("="*60)
|
|
147
|
+
print(f"\n Prochaines étapes:\n")
|
|
148
|
+
print(f" 1. cd {nom_projet}")
|
|
149
|
+
if docker:
|
|
150
|
+
print(" 2. docker-compose up --build")
|
|
151
|
+
else:
|
|
152
|
+
if type_projet == "apprentissage":
|
|
153
|
+
print(" 2. python app.py")
|
|
154
|
+
else:
|
|
155
|
+
print(" 2. python -m venv venv")
|
|
156
|
+
print(" 3. venv\\Scripts\\activate")
|
|
157
|
+
print(" 4. pip install -r requirements.txt")
|
|
158
|
+
print(" 5. seo db init")
|
|
159
|
+
print(" 6. seo user create")
|
|
160
|
+
print(" 7. seo run")
|
|
161
|
+
print("\n Commandes:")
|
|
162
|
+
print(" seo db init - Initialiser la base")
|
|
163
|
+
print(" seo user create - Créer un admin")
|
|
164
|
+
print(" seo run - Lancer l'app")
|
|
165
|
+
print("\n" + "="*60 + "\n")
|
seo/commandes.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Commandes internes pour gérer le projet Flask
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import subprocess
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def commande_db(action: str):
|
|
11
|
+
"""Gestion de la base de données"""
|
|
12
|
+
if action == "init":
|
|
13
|
+
print(" Initialisation de la base...")
|
|
14
|
+
subprocess.run([sys.executable, "-m", "flask", "db", "init"])
|
|
15
|
+
print(" Base initialisée")
|
|
16
|
+
elif action == "migrate":
|
|
17
|
+
message = input("Message (optionnel): ").strip()
|
|
18
|
+
if message:
|
|
19
|
+
subprocess.run([sys.executable, "-m", "flask", "db", "migrate", "-m", message])
|
|
20
|
+
else:
|
|
21
|
+
subprocess.run([sys.executable, "-m", "flask", "db", "migrate"])
|
|
22
|
+
print(" Migration créée")
|
|
23
|
+
elif action == "upgrade":
|
|
24
|
+
subprocess.run([sys.executable, "-m", "flask", "db", "upgrade"])
|
|
25
|
+
print(" Migrations appliquées")
|
|
26
|
+
else:
|
|
27
|
+
print(f" Action '{action}' inconnue")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def commande_user(action: str):
|
|
31
|
+
"""Gestion des utilisateurs"""
|
|
32
|
+
if action == "create":
|
|
33
|
+
print(" Création admin\n")
|
|
34
|
+
username = input("Username: ").strip()
|
|
35
|
+
email = input("Email: ").strip()
|
|
36
|
+
import getpass
|
|
37
|
+
password = getpass.getpass("Password: ")
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
from app import create_app, db
|
|
41
|
+
from app.models import User
|
|
42
|
+
app = create_app()
|
|
43
|
+
with app.app_context():
|
|
44
|
+
user = User(username=username, email=email, is_admin=True)
|
|
45
|
+
user.set_password(password)
|
|
46
|
+
db.session.add(user)
|
|
47
|
+
db.session.commit()
|
|
48
|
+
print(f" Utilisateur '{username}' créé")
|
|
49
|
+
except ImportError as e:
|
|
50
|
+
print(f" Erreur: {e}")
|
|
51
|
+
elif action == "list":
|
|
52
|
+
try:
|
|
53
|
+
from app import create_app, db
|
|
54
|
+
from app.models import User
|
|
55
|
+
app = create_app()
|
|
56
|
+
with app.app_context():
|
|
57
|
+
users = User.query.all()
|
|
58
|
+
print("\n Utilisateurs:\n")
|
|
59
|
+
for user in users:
|
|
60
|
+
print(f" {user.username}")
|
|
61
|
+
except ImportError:
|
|
62
|
+
print(" Impossible de charger les utilisateurs")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def commande_run(mode: str = "dev"):
|
|
66
|
+
"""Lance l'application"""
|
|
67
|
+
if mode == "dev":
|
|
68
|
+
print(" Mode développement...\n")
|
|
69
|
+
os.environ["FLASK_ENV"] = "development"
|
|
70
|
+
os.environ["FLASK_DEBUG"] = "1"
|
|
71
|
+
if Path("run.py").exists():
|
|
72
|
+
subprocess.run([sys.executable, "run.py"])
|
|
73
|
+
elif Path("app.py").exists():
|
|
74
|
+
subprocess.run([sys.executable, "app.py"])
|
|
75
|
+
else:
|
|
76
|
+
subprocess.run([sys.executable, "-m", "flask", "run", "--debug"])
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def afficher_aide():
|
|
80
|
+
"""Affiche l'aide"""
|
|
81
|
+
print("\n" + "="*60)
|
|
82
|
+
print(" Commandes SEO")
|
|
83
|
+
print("="*60 + "\n")
|
|
84
|
+
print(" Base de données:")
|
|
85
|
+
print(" seo db init - Initialiser")
|
|
86
|
+
print(" seo db migrate - Créer migration")
|
|
87
|
+
print(" seo db upgrade - Appliquer")
|
|
88
|
+
print("\n Utilisateurs:")
|
|
89
|
+
print(" seo user create - Créer admin")
|
|
90
|
+
print(" seo user list - Lister")
|
|
91
|
+
print("\n Lancement:")
|
|
92
|
+
print(" seo run - Mode dev")
|
|
93
|
+
print("\n" + "="*60 + "\n")
|
seo/generators.py
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import subprocess
|
|
3
|
-
import sys
|
|
4
|
-
import shutil
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
import shutil
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Dict, Any
|
|
7
|
+
from .utils import creer_fichier, copier_dossier
|
|
8
|
+
from .cli import collecter_preferences, afficher_resume, afficher_prochaines_etapes
|
|
9
|
+
|
|
10
|
+
class EnvironnementGenerator:
|
|
9
11
|
"""Classe de base pour générer des environnements"""
|
|
10
12
|
|
|
11
13
|
def __init__(self, niveau: str, type_app: str, chemin: str):
|
|
@@ -96,21 +98,28 @@ class IntermediaireWebGenerator(EnvironnementGenerator):
|
|
|
96
98
|
class ProWebGenerator(EnvironnementGenerator):
|
|
97
99
|
"""Générateur pro - Applications professionnelles"""
|
|
98
100
|
|
|
99
|
-
def __init__(self, chemin):
|
|
100
|
-
super().__init__('pro', 'web', chemin)
|
|
101
|
-
self.packages = [
|
|
102
|
-
'flask',
|
|
103
|
-
'flask-restx',
|
|
104
|
-
'flask-cors',
|
|
105
|
-
'flask-sqlalchemy',
|
|
106
|
-
'flask-migrate',
|
|
107
|
-
'flask-jwt-extended',
|
|
108
|
-
'python-dotenv',
|
|
109
|
-
'gunicorn',
|
|
110
|
-
'psycopg2-binary'
|
|
111
|
-
]
|
|
112
|
-
|
|
113
|
-
def
|
|
101
|
+
def __init__(self, chemin):
|
|
102
|
+
super().__init__('pro', 'web', chemin)
|
|
103
|
+
self.packages = [
|
|
104
|
+
'flask',
|
|
105
|
+
'flask-restx',
|
|
106
|
+
'flask-cors',
|
|
107
|
+
'flask-sqlalchemy',
|
|
108
|
+
'flask-migrate',
|
|
109
|
+
'flask-jwt-extended',
|
|
110
|
+
'python-dotenv',
|
|
111
|
+
'gunicorn',
|
|
112
|
+
'psycopg2-binary'
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
def _creer_structure(self):
|
|
116
|
+
# Création du fichier requirements
|
|
117
|
+
creer_fichier(
|
|
118
|
+
self.chemin_projet / 'requirements.txt',
|
|
119
|
+
"\n".join(self.packages)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
def _post_creation(self):
|
|
114
123
|
# Initialiser un dépôt Git
|
|
115
124
|
try:
|
|
116
125
|
subprocess.run(['git', 'init', str(self.chemin_projet)], check=True)
|
|
@@ -157,5 +166,177 @@ def creer_environnement(niveau, type_app='web', chemin='.'):
|
|
|
157
166
|
print(f"cd {chemin}")
|
|
158
167
|
print("docker-compose up --build")
|
|
159
168
|
|
|
160
|
-
# Alias pour une utilisation plus simple
|
|
161
|
-
creer_projet = creer_environnement
|
|
169
|
+
# Alias pour une utilisation plus simple
|
|
170
|
+
creer_projet = creer_environnement
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def main():
|
|
174
|
+
"""Point d'entrée principal pour le CLI"""
|
|
175
|
+
import argparse
|
|
176
|
+
|
|
177
|
+
parser = argparse.ArgumentParser(
|
|
178
|
+
description='SEO Dev Env - Générateur de projets Flask pour francophones'
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
subparsers = parser.add_subparsers(dest='commande', help='Commandes disponibles')
|
|
182
|
+
|
|
183
|
+
# Commande create (mode interactif)
|
|
184
|
+
parser_create = subparsers.add_parser('create', help='Créer un nouveau projet (mode interactif)')
|
|
185
|
+
parser_create.add_argument('nom', nargs='?', help='Nom du projet (optionnel, sera demandé si non fourni)')
|
|
186
|
+
|
|
187
|
+
# Commandes db
|
|
188
|
+
parser_db = subparsers.add_parser('db', help='Gestion de la base de données')
|
|
189
|
+
parser_db.add_argument('action', choices=['init', 'migrate', 'upgrade', 'downgrade'])
|
|
190
|
+
|
|
191
|
+
# Commandes user
|
|
192
|
+
parser_user = subparsers.add_parser('user', help='Gestion des utilisateurs')
|
|
193
|
+
parser_user.add_argument('action', choices=['create', 'list'])
|
|
194
|
+
|
|
195
|
+
# Commande run
|
|
196
|
+
parser_run = subparsers.add_parser('run', help='Lancer l\'application')
|
|
197
|
+
parser_run.add_argument('mode', nargs='?', default='dev', choices=['dev', 'prod'])
|
|
198
|
+
|
|
199
|
+
# Commande help
|
|
200
|
+
parser_help = subparsers.add_parser('help', help='Afficher l\'aide')
|
|
201
|
+
|
|
202
|
+
args = parser.parse_args()
|
|
203
|
+
|
|
204
|
+
# Si aucune commande, afficher l'aide
|
|
205
|
+
if not args.commande:
|
|
206
|
+
from .commandes import afficher_aide
|
|
207
|
+
afficher_aide()
|
|
208
|
+
return
|
|
209
|
+
|
|
210
|
+
# Traiter les commandes
|
|
211
|
+
if args.commande == 'create':
|
|
212
|
+
creer_projet_interactif(args.nom)
|
|
213
|
+
|
|
214
|
+
elif args.commande == 'db':
|
|
215
|
+
from .commandes import commande_db
|
|
216
|
+
commande_db(args.action)
|
|
217
|
+
|
|
218
|
+
elif args.commande == 'user':
|
|
219
|
+
from .commandes import commande_user
|
|
220
|
+
commande_user(args.action)
|
|
221
|
+
|
|
222
|
+
elif args.commande == 'run':
|
|
223
|
+
from .commandes import commande_run
|
|
224
|
+
commande_run(args.mode)
|
|
225
|
+
|
|
226
|
+
elif args.commande == 'help':
|
|
227
|
+
from .commandes import afficher_aide
|
|
228
|
+
afficher_aide()
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def creer_projet_interactif(nom_fourni: str = None):
|
|
232
|
+
"""Crée un projet en mode interactif"""
|
|
233
|
+
# Collecter les préférences
|
|
234
|
+
preferences = collecter_preferences()
|
|
235
|
+
|
|
236
|
+
# Utiliser le nom fourni en argument si disponible
|
|
237
|
+
if nom_fourni:
|
|
238
|
+
preferences['nom_projet'] = nom_fourni
|
|
239
|
+
|
|
240
|
+
# Afficher le résumé
|
|
241
|
+
afficher_resume(preferences)
|
|
242
|
+
|
|
243
|
+
# Confirmer
|
|
244
|
+
from .cli import confirmer
|
|
245
|
+
if not confirmer("Créer le projet avec cette configuration ?"):
|
|
246
|
+
print("❌ Annulé")
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
# Générer le projet selon le type
|
|
250
|
+
generer_selon_preferences(preferences)
|
|
251
|
+
|
|
252
|
+
# Afficher les prochaines étapes
|
|
253
|
+
afficher_prochaines_etapes(
|
|
254
|
+
preferences['nom_projet'],
|
|
255
|
+
preferences['type_projet'],
|
|
256
|
+
preferences.get('docker', False)
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def generer_selon_preferences(preferences: Dict[str, Any]):
|
|
261
|
+
"""Génère le projet selon les préférences"""
|
|
262
|
+
type_projet = preferences['type_projet']
|
|
263
|
+
nom_projet = preferences['nom_projet']
|
|
264
|
+
|
|
265
|
+
if type_projet == 'apprentissage':
|
|
266
|
+
generator = ApprentissageGenerator(nom_projet, preferences)
|
|
267
|
+
elif type_projet == 'application':
|
|
268
|
+
generator = ApplicationGenerator(nom_projet, preferences)
|
|
269
|
+
elif type_projet == 'api':
|
|
270
|
+
generator = APIGenerator(nom_projet, preferences)
|
|
271
|
+
elif type_projet == 'saas':
|
|
272
|
+
generator = SaaSGenerator(nom_projet, preferences)
|
|
273
|
+
else:
|
|
274
|
+
raise ValueError(f"Type de projet inconnu: {type_projet}")
|
|
275
|
+
|
|
276
|
+
generator.generer()
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class ApprentissageGenerator(EnvironnementGenerator):
|
|
280
|
+
"""Générateur pour débutants"""
|
|
281
|
+
def __init__(self, nom: str, preferences: Dict[str, Any]):
|
|
282
|
+
super().__init__('debutant', 'web', nom)
|
|
283
|
+
self.preferences = preferences
|
|
284
|
+
self.packages = ['flask', 'python-dotenv']
|
|
285
|
+
|
|
286
|
+
def _creer_structure(self):
|
|
287
|
+
creer_fichier(self.chemin_projet / 'README.md',
|
|
288
|
+
f"# {self.preferences['nom_projet']}\n\nProjet Flask pour apprentissage")
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class ApplicationGenerator(EnvironnementGenerator):
|
|
292
|
+
"""Générateur pour applications web (architecture par feature)"""
|
|
293
|
+
def __init__(self, nom: str, preferences: Dict[str, Any]):
|
|
294
|
+
super().__init__('intermediaire', 'web', nom)
|
|
295
|
+
self.preferences = preferences
|
|
296
|
+
self.packages = [
|
|
297
|
+
'flask', 'flask-sqlalchemy', 'flask-migrate',
|
|
298
|
+
'flask-wtf', 'flask-login', 'python-dotenv'
|
|
299
|
+
]
|
|
300
|
+
|
|
301
|
+
def _creer_structure(self):
|
|
302
|
+
creer_fichier(self.chemin_projet / 'requirements.txt', "\n".join(self.packages))
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class APIGenerator(EnvironnementGenerator):
|
|
306
|
+
"""Générateur pour API professionnelles"""
|
|
307
|
+
def __init__(self, nom: str, preferences: Dict[str, Any]):
|
|
308
|
+
super().__init__('pro', 'api', nom)
|
|
309
|
+
self.preferences = preferences
|
|
310
|
+
self.packages = [
|
|
311
|
+
'flask', 'flask-restx', 'flask-cors', 'flask-sqlalchemy',
|
|
312
|
+
'flask-migrate', 'flask-jwt-extended', 'python-dotenv', 'gunicorn'
|
|
313
|
+
]
|
|
314
|
+
if preferences.get('base_donnees') == 'postgresql':
|
|
315
|
+
self.packages.append('psycopg2-binary')
|
|
316
|
+
elif preferences.get('base_donnees') == 'mysql':
|
|
317
|
+
self.packages.append('pymysql')
|
|
318
|
+
|
|
319
|
+
def _creer_structure(self):
|
|
320
|
+
creer_fichier(self.chemin_projet / 'requirements.txt', "\n".join(self.packages))
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
class SaaSGenerator(EnvironnementGenerator):
|
|
324
|
+
"""Générateur pour startup SaaS"""
|
|
325
|
+
def __init__(self, nom: str, preferences: Dict[str, Any]):
|
|
326
|
+
super().__init__('pro', 'saas', nom)
|
|
327
|
+
self.preferences = preferences
|
|
328
|
+
self.packages = [
|
|
329
|
+
'flask', 'flask-restx', 'flask-cors', 'flask-sqlalchemy',
|
|
330
|
+
'flask-migrate', 'flask-jwt-extended', 'python-dotenv', 'gunicorn'
|
|
331
|
+
]
|
|
332
|
+
if preferences.get('stripe'):
|
|
333
|
+
self.packages.append('stripe')
|
|
334
|
+
if preferences.get('email'):
|
|
335
|
+
self.packages.append('flask-mail')
|
|
336
|
+
if preferences.get('celery'):
|
|
337
|
+
self.packages.extend(['celery', 'redis'])
|
|
338
|
+
if preferences.get('base_donnees') == 'postgresql':
|
|
339
|
+
self.packages.append('psycopg2-binary')
|
|
340
|
+
|
|
341
|
+
def _creer_structure(self):
|
|
342
|
+
creer_fichier(self.chemin_projet / 'requirements.txt', "\n".join(self.packages))
|
seo/templates/debutant/app.py
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from flask import Flask, render_template
|
|
2
|
+
|
|
3
|
+
app = Flask(__name__)
|
|
4
|
+
app.config["SECRET_KEY"] = "votre-cle-secrete-changez-moi"
|
|
5
|
+
|
|
6
|
+
@app.route("/")
|
|
7
|
+
def accueil():
|
|
8
|
+
return render_template("index.html", titre="Bienvenue")
|
|
9
|
+
|
|
10
|
+
@app.route("/a-propos")
|
|
11
|
+
def a_propos():
|
|
12
|
+
return render_template("a_propos.html", titre="À propos")
|
|
13
|
+
|
|
14
|
+
if __name__ == "__main__":
|
|
15
|
+
print(" Application Flask démarrée sur http://localhost:5000")
|
|
16
|
+
app.run(debug=True)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="fr">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>{{ titre }} - Mon Projet Flask</title>
|
|
7
|
+
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<nav>
|
|
11
|
+
<div class="container">
|
|
12
|
+
<h1> Mon Projet Flask</h1>
|
|
13
|
+
<ul>
|
|
14
|
+
<li><a href="/">Accueil</a></li>
|
|
15
|
+
<li><a href="/a-propos">À propos</a></li>
|
|
16
|
+
</ul>
|
|
17
|
+
</div>
|
|
18
|
+
</nav>
|
|
19
|
+
|
|
20
|
+
<main class="container">
|
|
21
|
+
<h2>Bienvenue sur votre première application Flask !</h2>
|
|
22
|
+
<p>Cette application a été générée avec <strong>SEO Dev Env</strong>.</p>
|
|
23
|
+
|
|
24
|
+
<div class="card">
|
|
25
|
+
<h3> Prochaines étapes</h3>
|
|
26
|
+
<ul>
|
|
27
|
+
<li>Personnalisez ce template dans <code>templates/index.html</code></li>
|
|
28
|
+
<li>Modifiez le style dans <code>static/style.css</code></li>
|
|
29
|
+
<li>Ajoutez de nouvelles routes dans <code>app.py</code></li>
|
|
30
|
+
</ul>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div class="card">
|
|
34
|
+
<h3> Ressources utiles</h3>
|
|
35
|
+
<ul>
|
|
36
|
+
<li><a href="https://flask.palletsprojects.com/" target="_blank">Documentation Flask</a></li>
|
|
37
|
+
<li><a href="https://jinja.palletsprojects.com/" target="_blank">Documentation Jinja2</a></li>
|
|
38
|
+
</ul>
|
|
39
|
+
</div>
|
|
40
|
+
</main>
|
|
41
|
+
|
|
42
|
+
<footer>
|
|
43
|
+
<p>Créé avec en utilisant Flask</p>
|
|
44
|
+
</footer>
|
|
45
|
+
</body>
|
|
46
|
+
</html>
|
seo/templates/debutant/style.css
CHANGED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
* {
|
|
2
|
+
margin: 0;
|
|
3
|
+
padding: 0;
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
body {
|
|
8
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
9
|
+
line-height: 1.6;
|
|
10
|
+
color: #333;
|
|
11
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
12
|
+
min-height: 100vh;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.container {
|
|
16
|
+
max-width: 1200px;
|
|
17
|
+
margin: 0 auto;
|
|
18
|
+
padding: 0 20px;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
nav {
|
|
22
|
+
background: rgba(255, 255, 255, 0.95);
|
|
23
|
+
padding: 1rem 0;
|
|
24
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
nav .container {
|
|
28
|
+
display: flex;
|
|
29
|
+
justify-content: space-between;
|
|
30
|
+
align-items: center;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
nav h1 {
|
|
34
|
+
color: #667eea;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
nav ul {
|
|
38
|
+
display: flex;
|
|
39
|
+
list-style: none;
|
|
40
|
+
gap: 2rem;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
nav a {
|
|
44
|
+
color: #333;
|
|
45
|
+
text-decoration: none;
|
|
46
|
+
font-weight: 500;
|
|
47
|
+
transition: color 0.3s;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
nav a:hover {
|
|
51
|
+
color: #667eea;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
main {
|
|
55
|
+
margin-top: 3rem;
|
|
56
|
+
padding: 2rem;
|
|
57
|
+
background: white;
|
|
58
|
+
border-radius: 10px;
|
|
59
|
+
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
h2 {
|
|
63
|
+
color: #667eea;
|
|
64
|
+
margin-bottom: 1rem;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.card {
|
|
68
|
+
background: #f7f9fc;
|
|
69
|
+
padding: 1.5rem;
|
|
70
|
+
border-radius: 8px;
|
|
71
|
+
margin: 1.5rem 0;
|
|
72
|
+
border-left: 4px solid #667eea;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.card h3 {
|
|
76
|
+
margin-bottom: 1rem;
|
|
77
|
+
color: #333;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.card ul {
|
|
81
|
+
margin-left: 1.5rem;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.card li {
|
|
85
|
+
margin: 0.5rem 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
code {
|
|
89
|
+
background: #e7eaf6;
|
|
90
|
+
padding: 2px 6px;
|
|
91
|
+
border-radius: 3px;
|
|
92
|
+
font-family: "Courier New", monospace;
|
|
93
|
+
color: #667eea;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
footer {
|
|
97
|
+
text-align: center;
|
|
98
|
+
padding: 2rem;
|
|
99
|
+
color: white;
|
|
100
|
+
margin-top: 3rem;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
a {
|
|
104
|
+
color: #667eea;
|
|
105
|
+
text-decoration: none;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
a:hover {
|
|
109
|
+
text-decoration: underline;
|
|
110
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Point d'entrée de l'application
|
|
3
|
+
Architecture par feature (domain-based)
|
|
4
|
+
"""
|
|
5
|
+
from flask import Flask
|
|
6
|
+
from flask_sqlalchemy import SQLAlchemy
|
|
7
|
+
from flask_migrate import Migrate
|
|
8
|
+
from flask_login import LoginManager
|
|
9
|
+
from .core.config import Config
|
|
10
|
+
|
|
11
|
+
db = SQLAlchemy()
|
|
12
|
+
migrate = Migrate()
|
|
13
|
+
login_manager = LoginManager()
|
|
14
|
+
login_manager.login_view = "utilisateurs.connexion"
|
|
15
|
+
login_manager.login_message = "Veuillez vous connecter pour accéder à cette page."
|
|
16
|
+
|
|
17
|
+
def create_app(config_class=Config):
|
|
18
|
+
app = Flask(__name__)
|
|
19
|
+
app.config.from_object(config_class)
|
|
20
|
+
|
|
21
|
+
# Initialiser les extensions
|
|
22
|
+
db.init_app(app)
|
|
23
|
+
migrate.init_app(app, db)
|
|
24
|
+
login_manager.init_app(app)
|
|
25
|
+
|
|
26
|
+
# Enregistrer les blueprints (features)
|
|
27
|
+
from app.utilisateurs.routes import bp as utilisateurs_bp
|
|
28
|
+
from app.taches.routes import bp as taches_bp
|
|
29
|
+
|
|
30
|
+
app.register_blueprint(utilisateurs_bp)
|
|
31
|
+
app.register_blueprint(taches_bp)
|
|
32
|
+
|
|
33
|
+
# Route racine
|
|
34
|
+
@app.route("/")
|
|
35
|
+
def index():
|
|
36
|
+
return "Application Flask - Architecture par feature"
|
|
37
|
+
|
|
38
|
+
return app
|
|
Binary file
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Configuration de l'application"""
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
basedir = Path(__file__).resolve().parent.parent.parent
|
|
6
|
+
|
|
7
|
+
class Config:
|
|
8
|
+
SECRET_KEY = os.environ.get("SECRET_KEY") or "dev-secret-key-changez-moi"
|
|
9
|
+
SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL") or f"sqlite:///{basedir / 'app.db'}"
|
|
10
|
+
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
|
11
|
+
|
|
12
|
+
class DevelopmentConfig(Config):
|
|
13
|
+
DEBUG = True
|
|
14
|
+
|
|
15
|
+
class ProductionConfig(Config):
|
|
16
|
+
DEBUG = False
|
|
17
|
+
# Configurations production ici
|
|
18
|
+
|
|
19
|
+
config = {
|
|
20
|
+
"development": DevelopmentConfig,
|
|
21
|
+
"production": ProductionConfig,
|
|
22
|
+
"default": DevelopmentConfig
|
|
23
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Modèle Tâche"""
|
|
2
|
+
from app import db
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
class Tache(db.Model):
|
|
6
|
+
__tablename__ = "taches"
|
|
7
|
+
|
|
8
|
+
id = db.Column(db.Integer, primary_key=True)
|
|
9
|
+
titre = db.Column(db.String(200), nullable=False)
|
|
10
|
+
description = db.Column(db.Text)
|
|
11
|
+
termine = db.Column(db.Boolean, default=False)
|
|
12
|
+
date_creation = db.Column(db.DateTime, default=datetime.utcnow)
|
|
13
|
+
|
|
14
|
+
# Relation avec utilisateur
|
|
15
|
+
user_id = db.Column(db.Integer, db.ForeignKey("utilisateurs.id"), nullable=False)
|
|
16
|
+
|
|
17
|
+
def __repr__(self):
|
|
18
|
+
return f"<Tache {self.titre}>"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Routes tâches"""
|
|
2
|
+
from flask import Blueprint, render_template, redirect, url_for, flash, request
|
|
3
|
+
from flask_login import login_required, current_user
|
|
4
|
+
from app import db
|
|
5
|
+
from app.taches.models import Tache
|
|
6
|
+
|
|
7
|
+
bp = Blueprint("taches", __name__, url_prefix="/taches")
|
|
8
|
+
|
|
9
|
+
@bp.route("/")
|
|
10
|
+
@login_required
|
|
11
|
+
def liste():
|
|
12
|
+
taches = current_user.taches.order_by(Tache.date_creation.desc()).all()
|
|
13
|
+
return render_template("taches/liste.html", taches=taches)
|
|
14
|
+
|
|
15
|
+
@bp.route("/ajouter", methods=["POST"])
|
|
16
|
+
@login_required
|
|
17
|
+
def ajouter():
|
|
18
|
+
titre = request.form.get("titre")
|
|
19
|
+
if titre:
|
|
20
|
+
tache = Tache(titre=titre, proprietaire=current_user)
|
|
21
|
+
db.session.add(tache)
|
|
22
|
+
db.session.commit()
|
|
23
|
+
flash("Tâche ajoutée!", "success")
|
|
24
|
+
return redirect(url_for("taches.liste"))
|
|
25
|
+
|
|
26
|
+
@bp.route("/terminer/<int:id>")
|
|
27
|
+
@login_required
|
|
28
|
+
def terminer(id):
|
|
29
|
+
tache = Tache.query.get_or_404(id)
|
|
30
|
+
if tache.proprietaire == current_user:
|
|
31
|
+
tache.termine = not tache.termine
|
|
32
|
+
db.session.commit()
|
|
33
|
+
flash("Tâche mise à jour!", "success")
|
|
34
|
+
return redirect(url_for("taches.liste"))
|
|
35
|
+
|
|
36
|
+
@bp.route("/supprimer/<int:id>")
|
|
37
|
+
@login_required
|
|
38
|
+
def supprimer(id):
|
|
39
|
+
tache = Tache.query.get_or_404(id)
|
|
40
|
+
if tache.proprietaire == current_user:
|
|
41
|
+
db.session.delete(tache)
|
|
42
|
+
db.session.commit()
|
|
43
|
+
flash("Tâche supprimée!", "success")
|
|
44
|
+
return redirect(url_for("taches.liste"))
|
|
Binary file
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Modèle Utilisateur (gestion des utilisateurs)"""
|
|
2
|
+
from app import db, login_manager
|
|
3
|
+
from flask_login import UserMixin
|
|
4
|
+
from werkzeug.security import generate_password_hash, check_password_hash
|
|
5
|
+
|
|
6
|
+
class User(UserMixin, db.Model):
|
|
7
|
+
__tablename__ = "utilisateurs"
|
|
8
|
+
|
|
9
|
+
id = db.Column(db.Integer, primary_key=True)
|
|
10
|
+
username = db.Column(db.String(64), unique=True, nullable=False, index=True)
|
|
11
|
+
email = db.Column(db.String(120), unique=True, nullable=False, index=True)
|
|
12
|
+
password_hash = db.Column(db.String(256))
|
|
13
|
+
is_admin = db.Column(db.Boolean, default=False)
|
|
14
|
+
|
|
15
|
+
# Relations
|
|
16
|
+
taches = db.relationship("Tache", backref="proprietaire", lazy="dynamic", cascade="all, delete-orphan")
|
|
17
|
+
|
|
18
|
+
def set_password(self, password):
|
|
19
|
+
self.password_hash = generate_password_hash(password)
|
|
20
|
+
|
|
21
|
+
def check_password(self, password):
|
|
22
|
+
return check_password_hash(self.password_hash, password)
|
|
23
|
+
|
|
24
|
+
def __repr__(self):
|
|
25
|
+
return f"<User {self.username}>"
|
|
26
|
+
|
|
27
|
+
@login_manager.user_loader
|
|
28
|
+
def load_user(user_id):
|
|
29
|
+
return User.query.get(int(user_id))
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Routes utilisateurs (auth, profil, etc.)"""
|
|
2
|
+
from flask import Blueprint, render_template, redirect, url_for, flash, request
|
|
3
|
+
from flask_login import login_user, logout_user, login_required, current_user
|
|
4
|
+
from app import db
|
|
5
|
+
from app.utilisateurs.models import User
|
|
6
|
+
|
|
7
|
+
bp = Blueprint("utilisateurs", __name__, url_prefix="/auth")
|
|
8
|
+
|
|
9
|
+
@bp.route("/connexion", methods=["GET", "POST"])
|
|
10
|
+
def connexion():
|
|
11
|
+
if current_user.is_authenticated:
|
|
12
|
+
return redirect(url_for("taches.liste"))
|
|
13
|
+
|
|
14
|
+
if request.method == "POST":
|
|
15
|
+
username = request.form.get("username")
|
|
16
|
+
password = request.form.get("password")
|
|
17
|
+
|
|
18
|
+
user = User.query.filter_by(username=username).first()
|
|
19
|
+
if user and user.check_password(password):
|
|
20
|
+
login_user(user)
|
|
21
|
+
flash("Connexion réussie!", "success")
|
|
22
|
+
next_page = request.args.get("next")
|
|
23
|
+
return redirect(next_page or url_for("taches.liste"))
|
|
24
|
+
else:
|
|
25
|
+
flash("Identifiants invalides", "error")
|
|
26
|
+
|
|
27
|
+
return render_template("utilisateurs/connexion.html")
|
|
28
|
+
|
|
29
|
+
@bp.route("/deconnexion")
|
|
30
|
+
@login_required
|
|
31
|
+
def deconnexion():
|
|
32
|
+
logout_user()
|
|
33
|
+
flash("Déconnexion réussie", "info")
|
|
34
|
+
return redirect(url_for("utilisateurs.connexion"))
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Configuration racine du projet"""
|
|
2
|
+
import os
|
|
3
|
+
from dotenv import load_dotenv
|
|
4
|
+
|
|
5
|
+
load_dotenv()
|
|
6
|
+
|
|
7
|
+
class Config:
|
|
8
|
+
SECRET_KEY = os.environ.get("SECRET_KEY") or "dev-secret-changez-moi-en-production"
|
|
9
|
+
SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL") or "sqlite:///app.db"
|
|
10
|
+
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
version: '3.8'
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
web:
|
|
5
|
+
build: .
|
|
6
|
+
container_name: flask_app
|
|
7
|
+
ports:
|
|
8
|
+
- "5000:5000"
|
|
9
|
+
environment:
|
|
10
|
+
- FLASK_ENV=production
|
|
11
|
+
- DATABASE_URL=postgresql://postgres:postgres@db:5432/app_db
|
|
12
|
+
- SECRET_KEY=changez-moi-en-production
|
|
13
|
+
depends_on:
|
|
14
|
+
- db
|
|
15
|
+
- redis
|
|
16
|
+
volumes:
|
|
17
|
+
- .:/app
|
|
18
|
+
command: gunicorn --bind 0.0.0.0:5000 --workers 4 --reload run:app
|
|
19
|
+
|
|
20
|
+
db:
|
|
21
|
+
image: postgres:15-alpine
|
|
22
|
+
container_name: postgres_db
|
|
23
|
+
environment:
|
|
24
|
+
- POSTGRES_USER=postgres
|
|
25
|
+
- POSTGRES_PASSWORD=postgres
|
|
26
|
+
- POSTGRES_DB=app_db
|
|
27
|
+
volumes:
|
|
28
|
+
- postgres_data:/var/lib/postgresql/data
|
|
29
|
+
ports:
|
|
30
|
+
- "5432:5432"
|
|
31
|
+
|
|
32
|
+
redis:
|
|
33
|
+
image: redis:7-alpine
|
|
34
|
+
container_name: redis_cache
|
|
35
|
+
ports:
|
|
36
|
+
- "6379:6379"
|
|
37
|
+
|
|
38
|
+
volumes:
|
|
39
|
+
postgres_data:
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Point d'entrée pour lancer l'application"""
|
|
2
|
+
import os
|
|
3
|
+
from app import create_app, db
|
|
4
|
+
from app.utilisateurs.models import User
|
|
5
|
+
from app.taches.models import Tache
|
|
6
|
+
|
|
7
|
+
app = create_app()
|
|
8
|
+
|
|
9
|
+
@app.shell_context_processor
|
|
10
|
+
def make_shell_context():
|
|
11
|
+
return {"db": db, "User": User, "Tache": Tache}
|
|
12
|
+
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
print(" Application Flask démarrée")
|
|
15
|
+
print(" Architecture par feature (domain-based)")
|
|
16
|
+
print(" http://localhost:5000")
|
|
17
|
+
app.run(debug=True)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
FROM python:3.11-slim
|
|
2
|
+
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
|
|
5
|
+
# Installer les dépendances système
|
|
6
|
+
RUN apt-get update && apt-get install -y \
|
|
7
|
+
gcc \
|
|
8
|
+
postgresql-client \
|
|
9
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
10
|
+
|
|
11
|
+
# Copier les requirements
|
|
12
|
+
COPY requirements.txt .
|
|
13
|
+
RUN pip install --no-cache-dir -r requirements.txt
|
|
14
|
+
|
|
15
|
+
# Copier l'application
|
|
16
|
+
COPY . .
|
|
17
|
+
|
|
18
|
+
# Exposer le port
|
|
19
|
+
EXPOSE 5000
|
|
20
|
+
|
|
21
|
+
# Variables d'environnement
|
|
22
|
+
ENV FLASK_APP=run.py
|
|
23
|
+
ENV PYTHONUNBUFFERED=1
|
|
24
|
+
|
|
25
|
+
# Commande par défaut
|
|
26
|
+
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "run:app"]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
version: '3.8'
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
web:
|
|
5
|
+
build: .
|
|
6
|
+
container_name: flask_app
|
|
7
|
+
ports:
|
|
8
|
+
- "5000:5000"
|
|
9
|
+
environment:
|
|
10
|
+
- FLASK_ENV=production
|
|
11
|
+
- DATABASE_URL=postgresql://postgres:postgres@db:5432/app_db
|
|
12
|
+
- SECRET_KEY=changez-moi-en-production
|
|
13
|
+
depends_on:
|
|
14
|
+
- db
|
|
15
|
+
- redis
|
|
16
|
+
volumes:
|
|
17
|
+
- .:/app
|
|
18
|
+
command: gunicorn --bind 0.0.0.0:5000 --workers 4 --reload run:app
|
|
19
|
+
|
|
20
|
+
db:
|
|
21
|
+
image: postgres:15-alpine
|
|
22
|
+
container_name: postgres_db
|
|
23
|
+
environment:
|
|
24
|
+
- POSTGRES_USER=postgres
|
|
25
|
+
- POSTGRES_PASSWORD=postgres
|
|
26
|
+
- POSTGRES_DB=app_db
|
|
27
|
+
volumes:
|
|
28
|
+
- postgres_data:/var/lib/postgresql/data
|
|
29
|
+
ports:
|
|
30
|
+
- "5432:5432"
|
|
31
|
+
|
|
32
|
+
redis:
|
|
33
|
+
image: redis:7-alpine
|
|
34
|
+
container_name: redis_cache
|
|
35
|
+
ports:
|
|
36
|
+
- "6379:6379"
|
|
37
|
+
|
|
38
|
+
volumes:
|
|
39
|
+
postgres_data:
|
seo/utils.py
CHANGED
|
@@ -1,24 +1,32 @@
|
|
|
1
|
-
import os
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
chemin
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
def creer_fichier(chemin, contenu):
|
|
6
|
+
"""Crée un fichier avec le contenu spécifié"""
|
|
7
|
+
chemin = Path(chemin)
|
|
8
|
+
chemin.parent.mkdir(parents=True, exist_ok=True)
|
|
9
|
+
with open(chemin, 'w', encoding='utf-8') as f:
|
|
10
|
+
f.write(contenu)
|
|
11
|
+
|
|
12
|
+
def copier_dossier(source, destination):
|
|
13
|
+
"""Copie récursivement un dossier"""
|
|
14
|
+
source = Path(source)
|
|
15
|
+
destination = Path(destination)
|
|
16
|
+
|
|
17
|
+
destination.mkdir(parents=True, exist_ok=True)
|
|
18
|
+
|
|
19
|
+
for item in source.iterdir():
|
|
20
|
+
# Skip .gitignore and other hidden files that might have encoding issues
|
|
21
|
+
if item.name.startswith('.'):
|
|
22
|
+
continue
|
|
23
|
+
|
|
24
|
+
dest_item = destination / item.name
|
|
25
|
+
if item.is_dir():
|
|
26
|
+
copier_dossier(item, dest_item)
|
|
27
|
+
else:
|
|
28
|
+
# Use shutil.copy2 to preserve metadata and handle binary files
|
|
29
|
+
try:
|
|
30
|
+
shutil.copy2(item, dest_item)
|
|
31
|
+
except Exception as e:
|
|
32
|
+
print(f"⚠️ Erreur lors de la copie de {item.name}: {e}")
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: seo-dev-env
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Générateur d'environnements de développement pour tous niveaux
|
|
5
|
-
Home-page: https://github.com/
|
|
5
|
+
Home-page: https://github.com/elkast/seo-dev-env
|
|
6
6
|
Author: Votre Nom
|
|
7
7
|
Author-email: orsinimelchisedek@gmail.com
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
seo/__init__.py,sha256=5_-smvpT5QbwLmxKXc4X-JfmzFuyueswNbCKCQIGftA,174
|
|
2
|
+
seo/cli.py,sha256=svTbLiZHUuD5_PUBkeNzl-hjELggT_wHH_VDxymJfkk,6224
|
|
3
|
+
seo/commandes.py,sha256=IV2adIYBdP8PjZoE6MK9D0ZXrow1M7B6CX2TYQD99Io,3150
|
|
4
|
+
seo/generators.py,sha256=cXpXny_w6lXDY4XG_LjpQoYN-HnNB8PAb_4nxh064Gk,12268
|
|
5
|
+
seo/utils.py,sha256=ssHO8tNVJPKSTcfxRlFfijtNzZ2i1USOzMogQK9OviA,1067
|
|
6
|
+
seo/templates/debutant/app.py,sha256=NqLiFgvPttDi_Ce5O-oT0yiPtViTfbkWSuLpN1QRNDU,444
|
|
7
|
+
seo/templates/debutant/index.html,sha256=GWGIhLUjvY22vOgxO-s1ocU752KeR057CQEG4cTSwtE,1557
|
|
8
|
+
seo/templates/debutant/style.css,sha256=pIa_Il6FDr-WVS1x3IS9F0kYiDJTYIP4PjuHFnoj_7A,1661
|
|
9
|
+
seo/templates/intermediaire/config.py,sha256=mKWh1yCEsYS5PG_PBfXO9XQqlXNyC0Jf7X-xZhRvous,326
|
|
10
|
+
seo/templates/intermediaire/docker-compose.yml,sha256=hjtekZDcaxglQ4wNjE_3Yvlvb2YqAXX4KQCUGBiqrHs,805
|
|
11
|
+
seo/templates/intermediaire/run.py,sha256=2l746zNzGoSlxcr1Hk_1ydc3kQ9VS7yvRC0MWJJsu38,487
|
|
12
|
+
seo/templates/intermediaire/app/__init__.py,sha256=HBK_2SrWkde3etSPXH8OP2m7rs0g4ljYu73t0iYdBHs,1076
|
|
13
|
+
seo/templates/intermediaire/app/models.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
seo/templates/intermediaire/app/routes.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
seo/templates/intermediaire/app/core/__init__.py,sha256=LgjR9gAK71QXl9AIwFrDb02-v7NsusVhV4jm_MWzAKc,6
|
|
16
|
+
seo/templates/intermediaire/app/core/config.py,sha256=7M0zTUu1uwbbT8mCG5QcELVcmad8m6pHCb2BdL7qEd0,626
|
|
17
|
+
seo/templates/intermediaire/app/taches/__init__.py,sha256=LgjR9gAK71QXl9AIwFrDb02-v7NsusVhV4jm_MWzAKc,6
|
|
18
|
+
seo/templates/intermediaire/app/taches/models.py,sha256=0ieYf4AUqFvGb1DIYID5nyRIirQ304jenzVfMB8uECU,582
|
|
19
|
+
seo/templates/intermediaire/app/taches/routes.py,sha256=hnpX9_Ql1amxI8uopWlnF8A6WpGrGKlCSRwd1n888-s,1406
|
|
20
|
+
seo/templates/intermediaire/app/utilisateurs/__init__.py,sha256=LgjR9gAK71QXl9AIwFrDb02-v7NsusVhV4jm_MWzAKc,6
|
|
21
|
+
seo/templates/intermediaire/app/utilisateurs/models.py,sha256=-rWh2w4LvwlRLSmwhmEqgxSFrSj2OsfIcmB233y1Yo4,1086
|
|
22
|
+
seo/templates/intermediaire/app/utilisateurs/routes.py,sha256=uUE82cBWLL-PiFFlJd2w5AsbNbh31oOjD2o8GAyXgEg,1246
|
|
23
|
+
seo/templates/pro/Dockerfile,sha256=VlrKrPXWBj8Ohj6BENbgVTvyHGZeDHDB1Ybe8OHqSTI,526
|
|
24
|
+
seo/templates/pro/docker-compose.yml,sha256=hjtekZDcaxglQ4wNjE_3Yvlvb2YqAXX4KQCUGBiqrHs,805
|
|
25
|
+
seo/templates/pro/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
+
seo/templates/pro/app/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
+
seo/templates/pro/app/api/v1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
|
+
seo/templates/pro/app/api/v1/endpoints.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
|
+
seo/templates/pro/app/core/config.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
|
+
seo/templates/pro/app/core/security.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
|
+
seo/templates/pro/app/db/base.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
|
+
seo/templates/pro/app/db/models.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
|
+
seo_dev_env-0.1.2.dist-info/licenses/LICENSE.ls,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
|
+
seo_dev_env-0.1.2.dist-info/METADATA,sha256=NsR7chxa3ym32ovKhaPLcsRnGRNr0cQxeEvcA-cvwAQ,13942
|
|
35
|
+
seo_dev_env-0.1.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
36
|
+
seo_dev_env-0.1.2.dist-info/entry_points.txt,sha256=tAn_vOcrXDG6ilFg7Zvacd4bf9fQXAhsmimMr9lEU84,96
|
|
37
|
+
seo_dev_env-0.1.2.dist-info/top_level.txt,sha256=5WsA3DhwhBGXTOKiKgDtiTUjXlpLk4oeUvrSKrtRFoQ,4
|
|
38
|
+
seo_dev_env-0.1.2.dist-info/RECORD,,
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
seo/__init__.py,sha256=UZS5pTtyPZ4-3JGGZSgfEKl8aTTFY9IsiM6gvwKRMzA,110
|
|
2
|
-
seo/generators.py,sha256=E2lTwkZNvtJiT4FyC3tMt_wDJaPGBxjVmvXTaxORwuo,5755
|
|
3
|
-
seo/utils.py,sha256=ocTb_CotkmmB_5aZSZXGCG_gJNaC9GdvZFb2ayWHaLE,794
|
|
4
|
-
seo/templates/debutant/app.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
seo/templates/debutant/index.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
seo/templates/debutant/style.css,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
seo/templates/intermediaire/config.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
seo/templates/intermediaire/run.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
seo/templates/intermediaire/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
seo/templates/intermediaire/app/models.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
-
seo/templates/intermediaire/app/routes.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
seo/templates/pro/docker-compose.yml,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
-
seo/templates/pro/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
-
seo/templates/pro/app/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
seo/templates/pro/app/api/v1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
seo/templates/pro/app/api/v1/endpoints.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
seo/templates/pro/app/core/config.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
-
seo/templates/pro/app/core/security.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
-
seo/templates/pro/app/db/base.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
-
seo/templates/pro/app/db/models.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
-
seo_dev_env-0.1.0.dist-info/licenses/LICENSE.ls,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
-
seo_dev_env-0.1.0.dist-info/METADATA,sha256=bKC9Xe9jcyCuwYf93L9W3o0-JAPJp268E_48UuIFWK8,13961
|
|
23
|
-
seo_dev_env-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
24
|
-
seo_dev_env-0.1.0.dist-info/entry_points.txt,sha256=lrqV5FhVM9pbEiYK-U9rtiWP1ZaUditqz9hpnLxy9E4,51
|
|
25
|
-
seo_dev_env-0.1.0.dist-info/top_level.txt,sha256=5WsA3DhwhBGXTOKiKgDtiTUjXlpLk4oeUvrSKrtRFoQ,4
|
|
26
|
-
seo_dev_env-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|