-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapp.py
324 lines (262 loc) · 11.6 KB
/
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
import base64
import os
import sys
import io
import dash
import dash_bootstrap_components as dbc
import flask
import pandas as pd
from dash import html, dcc
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
from flask import Flask, render_template, request, make_response, jsonify, send_file
from src.app.views import formulaire, dataset
from src.app.views import sidebar, header, dashboard
from src.app.views.auth import login
from src.app.views.graphs import treemap_fig
from src.auth.exceptions import InvalidToken
from src.auth.exceptions import LoginError, UsernameError
from src.auth.infrastructure import PostgresqlAccountRepository, InMemoryAccountRepository
from src.auth.usecases import login as user_login, check_token, decode_token
# Importez FlaskForm du module wtforms
from flask_wtf import FlaskForm
# Importez TextAreaField pour traiter le champ de texte du formulaire
from wtforms import StringField, SubmitField, TextAreaField
from src.scripts.inference import inference_script
from src.app.views import sandbox
from flask_sqlalchemy import SQLAlchemy
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView
# Initialiser le serveur Flask
server = Flask(__name__, template_folder="src/app/templates")
server.config["SECRET_KEY"] = "asma"
server.config["WTF_CSRF_ENABLED"] = False
# Database configuration
server.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://postgres:password@localhost:5432/app_db'
server.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(server)
# Flask-Admin configuration
admin = Admin(server, name='Flask-Admin', template_mode='bootstrap3')
# Définir le modèle SQLAlchemy pour la classe Account
class Account(db.Model):
__tablename__ = 'account' # Nom de la table dans la base de données
pk = db.Column(db.Integer, primary_key=True)
sk = db.Column(db.String(36), unique=True, nullable=False) # Remplacez par le type de données réel
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password = db.Column(db.String(255), nullable=False)
token = db.Column(db.String(255))
@property
def is_logged_in(self):
return self.token is not None
def token_is_valid(self, token):
return str(self.token) == str(token)
# Ajouter le modèle à Flask-Admin
admin.add_view(ModelView(Account, db.session))
repository = PostgresqlAccountRepository()
if os.environ["APP_ENV"] == "test":
repository = InMemoryAccountRepository()
app = dash.Dash(
__name__,
server=server,
suppress_callback_exceptions=True,
external_stylesheets=[dbc.themes.BOOTSTRAP, dbc.themes.MATERIA, dbc.icons.FONT_AWESOME],
assets_folder="static",
)
# pour le formulaire flask !
app.config['suppress_callback_exceptions'] = True
# Chargement des données depuis le fichier CSV (peut être placé dans app.py)
df = pd.read_csv("data/raw/inference/predicted_data_models.csv")
################## SRUCTURE DE LA PAGE ############################################################################
app.layout = html.Div(
[
sidebar.layout,
dash.page_container,
header.layout,
dcc.Location(id="url", refresh=False),
html.Div(id="page-content"),
],
className="flex-container",
)
################### FILTRES PAR DATE ###########################################################################
# Callback function pour mettre à jour le graphique sunburst
@app.callback(
Output("treemap-graph", "figure"),
[
Input("category-filter", "value"),
Input("date-range-picker", "start_date"),
Input("date-range-picker", "end_date"),
Input("timeline-filter", "value"),
],
)
def update_charts(selected_categories, start_date, end_date, timeline_value):
try:
start_date = pd.to_datetime(start_date)
end_date = pd.to_datetime(end_date)
start_month_index, end_month_index = timeline_value
selected_start_month = pd.Timestamp(f"{start_month_index + 1}/1/{start_date.year}")
selected_end_month = pd.Timestamp(f"{end_month_index + 1}/1/{end_date.year}")
filtered_df = df[
(df["predictions_motifs_label"].isin(selected_categories))
& (df["created_discussion"] >= start_date)
& (df["created_discussion"] <= end_date)
& (df["created_discussion"].dt.month >= start_month_index + 1)
& (df["created_discussion"].dt.month <= end_month_index + 1)
]
updated_treemap_fig = treemap_fig
return updated_treemap_fig
except Exception as e:
print(str(e))
###################### FORMULAIRE D'INSCRIPTION #######################################################################
# Routes
@server.route("/form_traite", methods=["POST"])
def traiter_formulaire():
if request.method == "POST":
# Récupérer les valeurs du formulaire
nom = request.form.get("nom")
prenom = request.form.get("prenom")
print(f"Nom: {nom}, Prénom: {prenom}")
# Ajoutez le code pour enregistrer les données dans la base de données
# et pour alimenter le modèle d'IA avec ces données
# Redirigez l'utilisateur vers une nouvelle page ou faites autre chose selon vos besoins
return render_template("formulaire_traite.html", nom=nom, prenom=prenom)
##################### DATASET : TELECHARGER LE JDD EN .CSV (src/app/views/dataset.py) ######################################################################
# Définition d'une route pour le téléchargement du dataset
@server.route("/download-dataset")
def download_dataset():
#On récupère le df à partir de la source de données + conversion en csv
df_download = df
csv_string = df_download.to_csv(index=False, encoding="utf-8")
#io.BytesIO pour traiter les données binaires
csv_binary = io.BytesIO(csv_string.encode("utf-8"))
# Flask send_file permet d'envoyer les données en tant que fichier téléchargeable
return send_file(
csv_binary,
mimetype="text/csv",
as_attachment=True,
download_name="dataset.csv",
)
# Définir le callback pour mettre à jour le lien de téléchargement
@app.callback(
Output("download-link", "href"),
[Input("download-link", "n_clicks")],
)
def update_download_link(n_clicks):
print(f"Nombre de clics : {n_clicks}")
if n_clicks is None:
raise dash.exceptions.PreventUpdate
df_download = df
# Convertir le DataFrame en CSV
csv_string = df_download.to_csv(index=False, encoding="utf-8")
# Convertir en base64 et créer le lien de téléchargement
csv_base64 = base64.b64encode(csv_string.encode("utf-8")).decode("utf-8")
#href = f"data:text/csv;base64,{csv_base64}"
href = "/download-dataset"
return href
######################## CONNEXION ###################################################################
@app.callback(Output("login-error", "children"), [Input("url", "pathname")], [State("url", "search")])
def display_error(pathname, search):
if search:
error_message = search.split("=")[1]
return dbc.Alert(f"Error: {error_message}", color="danger")
return None
@app.callback(
Output("login-output", "children"),
Input("login-button", "n_clicks"),
State("username-input", "value"),
State("password-input", "value"),
)
def check_login(n_clicks, username, password):
if n_clicks is None:
raise PreventUpdate
try:
session_token = user_login(repository=repository, username=username, password=password)
token = str(session_token)
dash.callback_context.response.set_cookie("session-token", token) #envoie le cookies au navigateur pour le stocker en mémoire
return dcc.Location(pathname="/", id="homepage")
except (LoginError, UsernameError, KeyError) as e:
print(e)
return dbc.Alert("Mauvais couple d'identifiants.", color="warning", dismissable=True)
except TypeError:
pass
@server.route("/test/user-is-logged-in", methods=["GET"])
def user_is_logged_in():
cookies = flask.request.cookies
user_session_cookie = cookies.get("session-token", "No cookie found")
try:
is_logged = check_token(repository=repository, encoded_token=user_session_cookie)
username, token = decode_token(user_session_cookie)
return make_response(
jsonify({"username": username, "token": token, "cookie": user_session_cookie, "is logged in": is_logged}),
200,
)
except Exception as e:
return make_response(jsonify(f"Error: {e}"), 403)
def login_required(repository, view_func):
cookies = flask.request.cookies
try:
user_session_cookie = cookies.get("session-token", None)
if not user_session_cookie:
return dcc.Location(href="/login", id="login-page")
user_is_logged_in = check_token(repository=repository, encoded_token=user_session_cookie)
if not user_is_logged_in:
return dcc.Location(href="/login", id="login-page")
return view_func
except InvalidToken:
return dcc.Location(href="/login", id="login-page")
# Callback pour gérer le clic sur le bouton de connexion/déconnexion
@app.callback(
[
Output("login-logout-icon", "className"),
Output("login-logout-text", "children"),
Output("login-logout-link", "href"),
],
[Input("login-logout-link", "n_clicks")],
prevent_initial_call=True,
)
def toggle_login_logout(n_clicks):
print("Toggle Clicks:", n_clicks)
# Obtenir le cookie de session
cookies = flask.request.cookies
user_session_cookie = cookies.get("session-token", None)
print("Session Cookie:", user_session_cookie)
if user_session_cookie:
# L'utilisateur est connecté donc bouton deconnexion
dash.callback_context.response.delete_cookie("session-token") #envoie le cookies au navigateur pour le stocker en mémoire
#return "fa fa-sign-out", "Se déconnecter", "/"
return "fa fa-sign-out", "", "/"
# L'utilisateur n'est pas connecté, donc connexion
#return "fa fa-sign-in", "Se connecter", "/login"
return "fa fa-sign-in", "", "/login"
##################### JOUER AVEC L'IA : FORMULAIRE #################################################################
# Ajoutez la route pour le formulaire
@server.route("/form", methods=["GET", "POST"])
def sandbox_route():
return sandbox.sandbox()
# Ajoutez une nouvelle route pour le résultat du formulaire
@server.route("/form/result", methods=["POST"])
def process_form():
title = request.form.get("title")
message = request.form.get("message")
return sandbox.process_form(title, message)
######################## ROUTES SIDEBAR #############################################################################
# Callback pour afficher le contenu de la vue en fonction de l'URL
@app.callback(Output("page-content", "children"), [Input("url", "pathname")])
def display_page(pathname):
if pathname == "/" or pathname == "/home":
return login_required(repository, dashboard.dashboard_layout())
elif pathname == "/login":
return login.layout
#elif pathname == "/form":
#Appliquez le décorateur @login_required uniquement à la vue associée à '/form'
#return login_required(repository, formulaire.layout())
elif pathname == "/dataset":
return dataset.dataset_layout()
elif pathname == "/form":
return sandbox.sandbox()
elif pathname == "/form/result":
return sandbox.process_form()
else:
return "404 - Page introuvable"
if __name__ == "__main__":
app.run_server(debug=True)