Docfile commited on
Commit
4d2fc58
·
verified ·
1 Parent(s): a90d8df

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +391 -0
app.py ADDED
@@ -0,0 +1,391 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, send_file
2
+ import os
3
+ import docx
4
+ from docx import Document
5
+ from docx.shared import Pt, Inches, Cm, RGBColor
6
+ from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_COLOR_INDEX
7
+ from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_TABLE_ALIGNMENT
8
+ from docx.oxml.ns import nsdecls
9
+ from docx.oxml import parse_xml
10
+
11
+ # --- Classe de génération de document ---
12
+ class EvaluationGymnique:
13
+ def __init__(self):
14
+ self.document = Document()
15
+ self.document.sections[0].page_height = Cm(29.7)
16
+ self.document.sections[0].page_width = Cm(21)
17
+ self.document.sections[0].left_margin = Cm(1.5)
18
+ self.document.sections[0].right_margin = Cm(1.5)
19
+ self.document.sections[0].top_margin = Cm(1)
20
+ self.document.sections[0].bottom_margin = Cm(1)
21
+
22
+ # Données par défaut
23
+ self.centre_examen = "Centre d'examen"
24
+ self.type_examen = "Bac Général"
25
+ self.serie = "Série"
26
+ self.etablissement = "Établissement"
27
+ self.session = "2025"
28
+ self.nom_candidat = "Candidat"
29
+ self.elements_techniques = [
30
+ {"nom": "Salutation", "categorie": "A", "points": 1},
31
+ {"nom": "Roulade avant élevée", "categorie": "B", "points": 1.5},
32
+ {"nom": "Croix de Saint André, Sursaut préparatoire", "categorie": "A", "points": 1},
33
+ {"nom": "Roue à 2 mains, ¾ tour", "categorie": "A", "points": 1},
34
+ {"nom": "Roulade arrière arrivée jambes écartées", "categorie": "B", "points": 1.5},
35
+ {"nom": "Planche écrasée", "categorie": "A", "points": 1},
36
+ {"nom": "Chandelle sans pose des mains", "categorie": "B", "points": 1.5},
37
+ {"nom": "Roulade arrière arrivée jambes tendues et serrées", "categorie": "B", "points": 1.5},
38
+ {"nom": "Planche costale", "categorie": "C", "points": 2},
39
+ {"nom": "Elan couru, 2 roues enchainées", "categorie": "C", "points": 2},
40
+ {"nom": "Rondade saut carpé jambes écarts Salutation", "categorie": "C", "points": 2}
41
+ ]
42
+ self.appreciations = ["M", "PM", "NM", "NR"]
43
+
44
+ def ajouter_entete_colore(self):
45
+ header_paragraph = self.document.add_paragraph()
46
+ header_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
47
+ header_paragraph.space_after = Pt(6)
48
+ header_run = header_paragraph.add_run("ÉVALUATION GYMNASTIQUE")
49
+ header_run.bold = True
50
+ header_run.font.size = Pt(14)
51
+ header_run.font.color.rgb = RGBColor(0, 32, 96)
52
+
53
+ header_table = self.document.add_table(rows=3, cols=2)
54
+ header_table.style = 'Table Grid'
55
+ header_table.autofit = False
56
+
57
+ for row in header_table.rows:
58
+ row.height = Cm(0.8)
59
+ for row in header_table.rows:
60
+ for cell in row.cells:
61
+ shading_elm = parse_xml(f'<w:shd {nsdecls("w")} w:fill="D9E2F3"/>')
62
+ cell._tc.get_or_add_tcPr().append(shading_elm)
63
+
64
+ # Première ligne
65
+ cell = header_table.cell(0, 0)
66
+ paragraph = cell.paragraphs[0]
67
+ run = paragraph.add_run("Centre d'examen: ")
68
+ run.bold = True
69
+ run.font.size = Pt(10)
70
+ run.font.color.rgb = RGBColor(0, 32, 96)
71
+ paragraph.add_run(self.centre_examen).font.size = Pt(10)
72
+
73
+ cell = header_table.cell(0, 1)
74
+ paragraph = cell.paragraphs[0]
75
+ run = paragraph.add_run("Examen: ")
76
+ run.bold = True
77
+ run.font.size = Pt(10)
78
+ run.font.color.rgb = RGBColor(0, 32, 96)
79
+ paragraph.add_run(self.type_examen).font.size = Pt(10)
80
+
81
+ # Deuxième ligne
82
+ cell = header_table.cell(1, 0)
83
+ paragraph = cell.paragraphs[0]
84
+ run = paragraph.add_run("Série: ")
85
+ run.bold = True
86
+ run.font.size = Pt(10)
87
+ run.font.color.rgb = RGBColor(0, 32, 96)
88
+ paragraph.add_run(self.serie).font.size = Pt(10)
89
+
90
+ cell = header_table.cell(1, 1)
91
+ paragraph = cell.paragraphs[0]
92
+ run = paragraph.add_run("Établissement: ")
93
+ run.bold = True
94
+ run.font.size = Pt(10)
95
+ run.font.color.rgb = RGBColor(0, 32, 96)
96
+ paragraph.add_run(self.etablissement).font.size = Pt(10)
97
+
98
+ # Troisième ligne
99
+ cell = header_table.cell(2, 0)
100
+ paragraph = cell.paragraphs[0]
101
+ run = paragraph.add_run("Session: ")
102
+ run.bold = True
103
+ run.font.size = Pt(10)
104
+ run.font.color.rgb = RGBColor(0, 32, 96)
105
+ paragraph.add_run(self.session).font.size = Pt(10)
106
+
107
+ cell = header_table.cell(2, 1)
108
+ paragraph = cell.paragraphs[0]
109
+ run = paragraph.add_run("Candidat: ")
110
+ run.bold = True
111
+ run.font.size = Pt(10)
112
+ run.font.color.rgb = RGBColor(0, 32, 96)
113
+ paragraph.add_run(self.nom_candidat).font.size = Pt(10)
114
+
115
+ self.document.add_paragraph().space_after = Pt(4)
116
+
117
+ def creer_tableau_elements(self):
118
+ table = self.document.add_table(rows=len(self.elements_techniques) + 1, cols=5)
119
+ table.style = 'Table Grid'
120
+ table.alignment = WD_TABLE_ALIGNMENT.CENTER
121
+
122
+ for cell in table.columns[0].cells:
123
+ cell.width = Cm(8)
124
+ for cell in table.columns[1].cells:
125
+ cell.width = Cm(3)
126
+ for cell in table.columns[2].cells:
127
+ cell.width = Cm(2)
128
+ for cell in table.columns[3].cells:
129
+ cell.width = Cm(2.5)
130
+ for cell in table.columns[4].cells:
131
+ cell.width = Cm(2.5)
132
+
133
+ for row in table.rows:
134
+ row.height = Cm(1)
135
+
136
+ header_row = table.rows[0]
137
+ for cell in header_row.cells:
138
+ shading_elm = parse_xml(f'<w:shd {nsdecls("w")} w:fill="BDD7EE"/>')
139
+ cell._tc.get_or_add_tcPr().append(shading_elm)
140
+
141
+ headers = ["ELEMENTS TECHNIQUES", "CATEGORIES D'ELEMENTS TECHNIQUES ET PONDERATION", "", "APPRECIATIONS", "POINTS Accordés"]
142
+ for i, header in enumerate(headers):
143
+ cell = table.cell(0, i)
144
+ cell.text = header
145
+ cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
146
+ run = cell.paragraphs[0].runs[0]
147
+ run.bold = True
148
+ run.font.size = Pt(9)
149
+ run.font.color.rgb = RGBColor(0, 32, 96)
150
+ table.cell(0, 1).merge(table.cell(0, 2))
151
+
152
+ for i, element in enumerate(self.elements_techniques, 1):
153
+ element_cell = table.cell(i, 0)
154
+ element_cell.text = element["nom"]
155
+ element_cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.LEFT
156
+ run = element_cell.paragraphs[0].runs[0]
157
+ run.bold = True
158
+ run.font.size = Pt(9)
159
+
160
+ categorie_cell = table.cell(i, 1)
161
+ categorie_cell.text = element["categorie"]
162
+ categorie_cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
163
+ run = categorie_cell.paragraphs[0].runs[0]
164
+ run.bold = True
165
+ run.font.size = Pt(9)
166
+ run.italic = True
167
+
168
+ points_cell = table.cell(i, 2)
169
+ points_cell.text = str(element["points"])
170
+ points_cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
171
+ run = points_cell.paragraphs[0].runs[0]
172
+ run.bold = True
173
+ run.font.size = Pt(9)
174
+ run.italic = True
175
+ # Colonnes "APPRECIATIONS" et "POINTS Accordés" laissées vides
176
+
177
+ def ajouter_note_jury(self):
178
+ para = self.document.add_paragraph()
179
+ para.space_before = Pt(4)
180
+ para.space_after = Pt(4)
181
+ run = para.add_run("NB1 : Zone réservée aux membres du jury ! Le jury cochera le point correspondant au niveau de réalisation de l'élément gymnique par le candidat.")
182
+ run.bold = True
183
+ run.font.color.rgb = RGBColor(255, 0, 0)
184
+ run.font.size = Pt(9)
185
+
186
+ def creer_tableau_recapitulatif(self):
187
+ note_table = self.document.add_table(rows=3, cols=13)
188
+ note_table.style = 'Table Grid'
189
+ note_table.alignment = WD_TABLE_ALIGNMENT.CENTER
190
+
191
+ for row in note_table.rows:
192
+ row.height = Cm(0.6)
193
+ for cell in note_table.rows[0].cells:
194
+ shading_elm = parse_xml(f'<w:shd {nsdecls("w")} w:fill="BDD7EE"/>')
195
+ cell._tc.get_or_add_tcPr().append(shading_elm)
196
+
197
+ for col, (type_lettre, points) in enumerate([("A", "1pt"), ("B", "1,5pt"), ("C", "2pts"), ("D", "2,5pts"), ("E", "3pts")]):
198
+ idx = col * 2
199
+ if idx + 1 < len(note_table.columns):
200
+ cell = note_table.cell(0, idx)
201
+ cell.merge(note_table.cell(0, idx + 1))
202
+ cell.text = f"Type {type_lettre}\n{points}"
203
+ cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
204
+ for run in cell.paragraphs[0].runs:
205
+ run.bold = True
206
+ run.font.size = Pt(9)
207
+ run.font.color.rgb = RGBColor(0, 32, 96)
208
+
209
+ for col, (titre, points) in enumerate([("ROV", "2pts"), ("Projet", "2pts"), ("Réalisation", "16pts")], 10):
210
+ if col < len(note_table.columns):
211
+ cell = note_table.cell(0, col)
212
+ cell.text = f"{titre}\n{points}"
213
+ cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
214
+ for run in cell.paragraphs[0].runs:
215
+ run.bold = True
216
+ run.font.size = Pt(9)
217
+ run.font.color.rgb = RGBColor(0, 32, 96)
218
+
219
+ for col in range(5):
220
+ idx = col * 2
221
+ if idx + 1 < len(note_table.columns):
222
+ neg_cell = note_table.cell(1, idx)
223
+ neg_cell.text = "NEG"
224
+ neg_cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
225
+ for run in neg_cell.paragraphs[0].runs:
226
+ run.italic = True
227
+ run.font.size = Pt(9)
228
+
229
+ note_cell = note_table.cell(1, idx + 1)
230
+ note_cell.text = "Note"
231
+ note_cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
232
+ for run in note_cell.paragraphs[0].runs:
233
+ run.italic = True
234
+ run.font.size = Pt(9)
235
+
236
+ for col in range(10, 13):
237
+ if col < len(note_table.columns):
238
+ cell = note_table.cell(1, col)
239
+ cell.text = "Note"
240
+ cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
241
+ for run in cell.paragraphs[0].runs:
242
+ run.italic = True
243
+ run.font.size = Pt(9)
244
+
245
+ for col, type_lettre in enumerate(["A", "B", "C", "D", "E"]):
246
+ idx = col * 2
247
+ if idx < len(note_table.columns):
248
+ neg_cell = note_table.cell(2, idx)
249
+ neg_cell.text = ""
250
+ neg_cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
251
+
252
+ def ajouter_note_candidat_avec_cadre(self):
253
+ note_table = self.document.add_table(rows=1, cols=1)
254
+ note_table.style = 'Table Grid'
255
+ note_table.alignment = WD_TABLE_ALIGNMENT.CENTER
256
+
257
+ cell = note_table.cell(0, 0)
258
+ shading_elm = parse_xml(f'<w:shd {nsdecls("w")} w:fill="E2EFDA"/>')
259
+ cell._tc.get_or_add_tcPr().append(shading_elm)
260
+
261
+ p = cell.paragraphs[0]
262
+ run = p.add_run("NB2: Après le choix des catégories d'éléments gymniques par le candidat, ce dernier remplira la colonne de pointage selon l'orientation suivante: A (0.25; 0.5; 0.75; 1) B (0.25; 0.5; 0.75; 1; 1.25; 1.5) C (0.5; 0.75; 1; 1.25; 1.5; 2) D (0.75; 1; 1.25; 1.5; 2; 2.5) et E (0.75; 1; 1.5; 2; 2.5; 3) aussi, il sera exigé au candidat 2 copies de son projet sur une page! appréciations:NR (non réalisation) NM (non maitrisé), PM (peu maitrisé) et M (maitrisé).")
263
+ run.italic = True
264
+ run.font.size = Pt(8)
265
+
266
+ def ajouter_zone_note(self):
267
+ zone_note = self.document.add_table(rows=1, cols=2)
268
+ zone_note.style = 'Table Grid'
269
+ zone_note.alignment = WD_TABLE_ALIGNMENT.RIGHT
270
+
271
+ cell_libelle = zone_note.cell(0, 0)
272
+ cell_libelle.text = "Note finale"
273
+ cell_libelle.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.RIGHT
274
+ run = cell_libelle.paragraphs[0].runs[0]
275
+ run.bold = True
276
+ run.font.size = Pt(12)
277
+ run.font.color.rgb = RGBColor(0, 32, 96)
278
+
279
+ cell_saisie = zone_note.cell(0, 1)
280
+ cell_saisie.width = Cm(1.5)
281
+ zone_note.rows[0].height = Cm(1.5)
282
+ shading_elm = parse_xml(f'<w:shd {nsdecls("w")} w:fill="F2F2F2"/>')
283
+ cell_saisie._tc.get_or_add_tcPr().append(shading_elm)
284
+ cell_saisie.text = "/20"
285
+ cell_saisie.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
286
+ run = cell_saisie.paragraphs[0].runs[0]
287
+ run.bold = True
288
+ run.font.size = Pt(12)
289
+
290
+ self.document.add_paragraph()
291
+
292
+ def ajouter_lignes_correcteurs(self):
293
+ for role in ["Projet", "principal", "ROV"]:
294
+ para = self.document.add_paragraph()
295
+ para.space_before = Pt(2)
296
+ para.space_after = Pt(2)
297
+ run = para.add_run(f"Correcteur {role} : ")
298
+ run.bold = True
299
+ para.add_run(".................................................................")
300
+
301
+ # Méthodes de modification des données
302
+ def modifier_centre_examen(self, nom):
303
+ self.centre_examen = nom
304
+
305
+ def modifier_type_examen(self, type_examen):
306
+ self.type_examen = type_examen
307
+
308
+ def modifier_serie(self, serie):
309
+ self.serie = serie
310
+
311
+ def modifier_etablissement(self, nom):
312
+ self.etablissement = nom
313
+
314
+ def modifier_session(self, annee):
315
+ self.session = annee
316
+
317
+ def modifier_candidat(self, nom):
318
+ self.nom_candidat = nom
319
+
320
+ def ajouter_element(self, nom, categorie, points):
321
+ self.elements_techniques.append({
322
+ "nom": nom,
323
+ "categorie": categorie,
324
+ "points": float(points)
325
+ })
326
+
327
+ def supprimer_element(self, index):
328
+ if 0 <= index < len(self.elements_techniques):
329
+ del self.elements_techniques[index]
330
+
331
+ def modifier_element(self, index, nom=None, categorie=None, points=None):
332
+ if 0 <= index < len(self.elements_techniques):
333
+ if nom:
334
+ self.elements_techniques[index]["nom"] = nom
335
+ if categorie:
336
+ self.elements_techniques[index]["categorie"] = categorie
337
+ if points:
338
+ self.elements_techniques[index]["points"] = float(points)
339
+
340
+ def generer_document(self, nom_fichier="evaluation_gymnastique.docx"):
341
+ self.ajouter_entete_colore()
342
+ self.creer_tableau_elements()
343
+ self.ajouter_note_jury()
344
+ self.creer_tableau_recapitulatif()
345
+ self.ajouter_lignes_correcteurs()
346
+ self.ajouter_zone_note()
347
+ self.ajouter_note_candidat_avec_cadre()
348
+ self.document.save(nom_fichier)
349
+ return nom_fichier
350
+
351
+ # --- Application Flask ---
352
+ app = Flask(__name__)
353
+
354
+ @app.route("/", methods=["GET", "POST"])
355
+ def index():
356
+ if request.method == "POST":
357
+ # Récupération des données du formulaire
358
+ centre_examen = request.form.get("centre_examen", "Centre d'examen")
359
+ type_examen = request.form.get("type_examen", "Bac Général")
360
+ serie = request.form.get("serie", "Série")
361
+ etablissement = request.form.get("etablissement", "Établissement")
362
+ session_value = request.form.get("session", "2025")
363
+ nom_candidat = request.form.get("nom_candidat", "Candidat")
364
+
365
+ # Création de l'instance et modification des données
366
+ evaluation = EvaluationGymnique()
367
+ evaluation.modifier_centre_examen(centre_examen)
368
+ evaluation.modifier_type_examen(type_examen)
369
+ evaluation.modifier_serie(serie)
370
+ evaluation.modifier_etablissement(etablissement)
371
+ evaluation.modifier_session(session_value)
372
+ evaluation.modifier_candidat(nom_candidat)
373
+
374
+ # Optionnel : ajout d'un nouvel élément technique si renseigné
375
+ new_element_name = request.form.get("new_element_name")
376
+ new_element_categorie = request.form.get("new_element_categorie")
377
+ new_element_points = request.form.get("new_element_points")
378
+ if new_element_name and new_element_categorie and new_element_points:
379
+ evaluation.ajouter_element(new_element_name, new_element_categorie, new_element_points)
380
+
381
+ # Génération du document
382
+ filename = "evaluation_gymnastique.docx"
383
+ evaluation.generer_document(filename)
384
+
385
+ # Envoi du fichier généré en téléchargement
386
+ return send_file(filename, as_attachment=True)
387
+
388
+ return render_template("index.html")
389
+
390
+ if __name__ == "__main__":
391
+ app.run(debug=True)