Docfile commited on
Commit
1b2f27f
·
verified ·
1 Parent(s): 9b8923c

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +459 -0
app.py ADDED
@@ -0,0 +1,459 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, send_file
2
+ import os
3
+ import convertapi
4
+ from docx import Document
5
+ from docx.shared import Pt, Cm, RGBColor
6
+ from docx.enum.text import WD_ALIGN_PARAGRAPH
7
+ from docx.enum.table import WD_TABLE_ALIGNMENT
8
+ from docx.oxml.ns import nsdecls, qn
9
+ from docx.oxml import parse_xml, OxmlElement
10
+ from docx.enum.style import WD_STYLE_TYPE
11
+
12
+ # Configuration des identifiants ConvertAPI
13
+ convertapi.api_credentials = 'secret_8wCI6pgOP9AxLVJG'
14
+
15
+ def set_cell_border(cell, border_color="92D050", border_sz="8"):
16
+ """
17
+ Ajoute ou met à jour les bordures d'une cellule avec la couleur et taille spécifiées.
18
+ """
19
+ tc = cell._tc
20
+ tcPr = tc.get_or_add_tcPr()
21
+ borders = tcPr.find(qn("w:tcBorders"))
22
+ if borders is None:
23
+ borders = OxmlElement("w:tcBorders")
24
+ tcPr.append(borders)
25
+ for border in ['top', 'left', 'bottom', 'right']:
26
+ element = borders.find(qn("w:" + border))
27
+ if element is None:
28
+ element = OxmlElement("w:" + border)
29
+ borders.append(element)
30
+ element.set(qn("w:val"), "single")
31
+ element.set(qn("w:sz"), border_sz)
32
+ element.set(qn("w:space"), "0")
33
+ element.set(qn("w:color"), border_color)
34
+
35
+ class EvaluationGymnique:
36
+ def __init__(self):
37
+ self.document = Document()
38
+ self.document.sections[0].page_height = Cm(29.7)
39
+ self.document.sections[0].page_width = Cm(21)
40
+ self.document.sections[0].left_margin = Cm(1.5)
41
+ self.document.sections[0].right_margin = Cm(1.5)
42
+ self.document.sections[0].top_margin = Cm(1)
43
+ self.document.sections[0].bottom_margin = Cm(1)
44
+
45
+ # Appliquer des styles généraux (personnalisation des polices et couleurs)
46
+ self.appliquer_styles_generaux()
47
+
48
+ # Informations d'en-tête par défaut
49
+ self.centre_examen = "Centre d'examen"
50
+ self.type_examen = "Bac Général"
51
+ self.serie = "Série"
52
+ self.etablissement = "Établissement"
53
+ self.session = "2025"
54
+ self.nom_candidat = "Candidat"
55
+
56
+ # Liste vide pour les éléments techniques
57
+ self.elements_techniques = []
58
+ self.appreciations = ["M", "PM", "NM", "NR"]
59
+
60
+ def appliquer_styles_generaux(self):
61
+ # Modifier le style Normal pour un rendu moderne
62
+ style = self.document.styles['Normal']
63
+ style.font.name = 'Calibri'
64
+ style.font.size = Pt(11)
65
+
66
+ # Créer (ou réutiliser) un style personnalisé pour les titres
67
+ try:
68
+ custom_heading = self.document.styles.add_style('CustomHeading', WD_STYLE_TYPE.PARAGRAPH)
69
+ except Exception:
70
+ custom_heading = self.document.styles['Heading 1']
71
+ custom_heading.font.name = 'Arial'
72
+ custom_heading.font.size = Pt(16)
73
+ custom_heading.font.bold = True
74
+ custom_heading.font.color.rgb = RGBColor(0, 32, 96)
75
+
76
+ def ajouter_entete_colore(self):
77
+ header_paragraph = self.document.add_paragraph()
78
+ header_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
79
+ header_paragraph.space_after = Pt(6)
80
+ header_run = header_paragraph.add_run("ÉVALUATION GYMNASTIQUE")
81
+ header_run.bold = True
82
+ header_run.font.size = Pt(14)
83
+ header_run.font.color.rgb = RGBColor(0, 32, 96)
84
+
85
+ header_table = self.document.add_table(rows=3, cols=2)
86
+ header_table.style = 'Table Grid'
87
+ header_table.autofit = False
88
+
89
+ # Configuration de la hauteur des lignes et ajout de shading sur chaque cellule
90
+ for row in header_table.rows:
91
+ row.height = Cm(0.8)
92
+ for cell in row.cells:
93
+ shading_elm = parse_xml(f'<w:shd {nsdecls("w")} w:fill="D9E2F3"/>')
94
+ cell._tc.get_or_add_tcPr().append(shading_elm)
95
+
96
+ # Première ligne
97
+ cell = header_table.cell(0, 0)
98
+ paragraph = cell.paragraphs[0]
99
+ run = paragraph.add_run("Centre d'examen: ")
100
+ run.bold = True
101
+ run.font.size = Pt(10)
102
+ run.font.color.rgb = RGBColor(0, 32, 96)
103
+ paragraph.add_run(self.centre_examen).font.size = Pt(10)
104
+
105
+ cell = header_table.cell(0, 1)
106
+ paragraph = cell.paragraphs[0]
107
+ run = paragraph.add_run("Examen: ")
108
+ run.bold = True
109
+ run.font.size = Pt(10)
110
+ run.font.color.rgb = RGBColor(0, 32, 96)
111
+ paragraph.add_run(self.type_examen).font.size = Pt(10)
112
+
113
+ # Deuxième ligne
114
+ cell = header_table.cell(1, 0)
115
+ paragraph = cell.paragraphs[0]
116
+ run = paragraph.add_run("Série: ")
117
+ run.bold = True
118
+ run.font.size = Pt(10)
119
+ run.font.color.rgb = RGBColor(0, 32, 96)
120
+ paragraph.add_run(self.serie).font.size = Pt(10)
121
+
122
+ cell = header_table.cell(1, 1)
123
+ paragraph = cell.paragraphs[0]
124
+ run = paragraph.add_run("Établissement: ")
125
+ run.bold = True
126
+ run.font.size = Pt(10)
127
+ run.font.color.rgb = RGBColor(0, 32, 96)
128
+ paragraph.add_run(self.etablissement).font.size = Pt(10)
129
+
130
+ # Troisième ligne
131
+ cell = header_table.cell(2, 0)
132
+ paragraph = cell.paragraphs[0]
133
+ run = paragraph.add_run("Session: ")
134
+ run.bold = True
135
+ run.font.size = Pt(10)
136
+ run.font.color.rgb = RGBColor(0, 32, 96)
137
+ paragraph.add_run(self.session).font.size = Pt(10)
138
+
139
+ cell = header_table.cell(2, 1)
140
+ paragraph = cell.paragraphs[0]
141
+ run = paragraph.add_run("Candidat: ")
142
+ run.bold = True
143
+ run.font.size = Pt(10)
144
+ run.font.color.rgb = RGBColor(0, 32, 96)
145
+ paragraph.add_run(self.nom_candidat).font.size = Pt(10)
146
+
147
+ self.document.add_paragraph().space_after = Pt(4)
148
+
149
+ def creer_tableau_elements(self):
150
+ # Création d'un tableau pour les éléments techniques avec une mise en forme améliorée
151
+ table = self.document.add_table(rows=len(self.elements_techniques) + 1, cols=5)
152
+ table.style = 'Table Grid'
153
+ table.alignment = WD_TABLE_ALIGNMENT.CENTER
154
+
155
+ # Définir les largeurs de colonnes pour une meilleure lisibilité
156
+ col_widths = [Cm(8), Cm(3), Cm(2), Cm(2.5), Cm(2.5)]
157
+ for col_idx, width in enumerate(col_widths):
158
+ for cell in table.columns[col_idx].cells:
159
+ cell.width = width
160
+
161
+ # Ajustement de la hauteur des lignes et espacement interne
162
+ for row in table.rows:
163
+ row.height = Cm(1)
164
+ for cell in row.cells:
165
+ for paragraph in cell.paragraphs:
166
+ paragraph.paragraph_format.space_before = Pt(2)
167
+ paragraph.paragraph_format.space_after = Pt(2)
168
+
169
+ # En-tête du tableau avec arrière-plan personnalisé
170
+ header_row = table.rows[0]
171
+ for cell in header_row.cells:
172
+ shading_elm = parse_xml(f'<w:shd {nsdecls("w")} w:fill="BDD7EE"/>')
173
+ cell._tc.get_or_add_tcPr().append(shading_elm)
174
+
175
+ headers = ["ELEMENTS TECHNIQUES", "CATEGORIES D'ELEMENTS TECHNIQUES ET PONDERATION", "", "APPRECIATIONS", "POINTS Accordés"]
176
+ for i, header in enumerate(headers):
177
+ cell = table.cell(0, i)
178
+ cell.text = header
179
+ cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
180
+ run = cell.paragraphs[0].runs[0]
181
+ run.bold = True
182
+ run.font.size = Pt(9)
183
+ run.font.color.rgb = RGBColor(0, 32, 96)
184
+ # Fusionner les deux cellules centrales de l'en-tête
185
+ table.cell(0, 1).merge(table.cell(0, 2))
186
+
187
+ # Remplissage du tableau avec les éléments techniques
188
+ for i, element in enumerate(self.elements_techniques, 1):
189
+ # Colonne nom de l'élément
190
+ element_cell = table.cell(i, 0)
191
+ element_cell.text = element["nom"]
192
+ element_cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.LEFT
193
+ run = element_cell.paragraphs[0].runs[0]
194
+ run.bold = True
195
+ run.font.size = Pt(9)
196
+
197
+ # Colonne catégorie
198
+ categorie_cell = table.cell(i, 1)
199
+ categorie_cell.text = element["categorie"]
200
+ categorie_cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
201
+ run = categorie_cell.paragraphs[0].runs[0]
202
+ run.bold = True
203
+ run.font.size = Pt(9)
204
+ run.italic = True
205
+
206
+ # Colonne points
207
+ points_cell = table.cell(i, 2)
208
+ points_cell.text = str(element["points"])
209
+ points_cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
210
+ run = points_cell.paragraphs[0].runs[0]
211
+ run.bold = True
212
+ run.font.size = Pt(9)
213
+ run.italic = True
214
+
215
+ def ajouter_note_jury(self):
216
+ para = self.document.add_paragraph()
217
+ para.space_before = Pt(4)
218
+ para.space_after = Pt(4)
219
+ 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.")
220
+ run.bold = True
221
+ run.font.color.rgb = RGBColor(255, 0, 0)
222
+ run.font.size = Pt(9)
223
+
224
+ def creer_tableau_recapitulatif(self):
225
+ note_table = self.document.add_table(rows=3, cols=13)
226
+ note_table.style = 'Table Grid'
227
+ note_table.alignment = WD_TABLE_ALIGNMENT.CENTER
228
+
229
+ for row in note_table.rows:
230
+ row.height = Cm(0.6)
231
+ for cell in note_table.rows[0].cells:
232
+ shading_elm = parse_xml(f'<w:shd {nsdecls("w")} w:fill="BDD7EE"/>')
233
+ cell._tc.get_or_add_tcPr().append(shading_elm)
234
+
235
+ for col, (type_lettre, points) in enumerate([("A", "1pt"), ("B", "1,5pt"), ("C", "2pts"), ("D", "2,5pts"), ("E", "3pts")]):
236
+ idx = col * 2
237
+ if idx + 1 < len(note_table.columns):
238
+ cell = note_table.cell(0, idx)
239
+ cell.merge(note_table.cell(0, idx + 1))
240
+ cell.text = f"Type {type_lettre}\n{points}"
241
+ cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
242
+ for run in cell.paragraphs[0].runs:
243
+ run.bold = True
244
+ run.font.size = Pt(9)
245
+ run.font.color.rgb = RGBColor(0, 32, 96)
246
+
247
+ for col, (titre, points) in enumerate([("ROV", "2pts"), ("Projet", "2pts"), ("Réalisation", "16pts")], 10):
248
+ if col < len(note_table.columns):
249
+ cell = note_table.cell(0, col)
250
+ cell.text = f"{titre}\n{points}"
251
+ cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
252
+ for run in cell.paragraphs[0].runs:
253
+ run.bold = True
254
+ run.font.size = Pt(9)
255
+ run.font.color.rgb = RGBColor(0, 32, 96)
256
+
257
+ for col in range(5):
258
+ idx = col * 2
259
+ if idx + 1 < len(note_table.columns):
260
+ neg_cell = note_table.cell(1, idx)
261
+ neg_cell.text = "NEG"
262
+ neg_cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
263
+ for run in neg_cell.paragraphs[0].runs:
264
+ run.italic = True
265
+ run.font.size = Pt(9)
266
+
267
+ note_cell = note_table.cell(1, idx + 1)
268
+ note_cell.text = "Note"
269
+ note_cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
270
+ for run in note_cell.paragraphs[0].runs:
271
+ run.italic = True
272
+ run.font.size = Pt(9)
273
+
274
+ for col in range(10, 13):
275
+ if col < len(note_table.columns):
276
+ cell = note_table.cell(1, col)
277
+ cell.text = "Note"
278
+ cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
279
+ for run in cell.paragraphs[0].runs:
280
+ run.italic = True
281
+ run.font.size = Pt(9)
282
+
283
+ for col, type_lettre in enumerate(["A", "B", "C", "D", "E"]):
284
+ idx = col * 2
285
+ if idx < len(note_table.columns):
286
+ neg_cell = note_table.cell(2, idx)
287
+ neg_cell.text = ""
288
+ neg_cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
289
+
290
+ def ajouter_accolade(self):
291
+ # Créer un tableau pour simuler l'accolade
292
+ accolade_table = self.document.add_table(rows=1, cols=3)
293
+ accolade_table.alignment = WD_TABLE_ALIGNMENT.CENTER
294
+
295
+ # Supprimer les bordures (simulation)
296
+ for cell in accolade_table.rows[0].cells:
297
+ cell.border_width = 0
298
+
299
+ # Définir les largeurs des cellules
300
+ accolade_table.columns[0].width = Cm(8)
301
+ accolade_table.columns[1].width = Cm(2)
302
+ accolade_table.columns[2].width = Cm(8)
303
+
304
+ cell = accolade_table.cell(0, 1)
305
+ paragraph = cell.paragraphs[0]
306
+ paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
307
+ run = paragraph.add_run("}")
308
+ run.font.size = Pt(48)
309
+ run.font.bold = True
310
+
311
+ # Rotation du symbole grâce à une modification XML
312
+ rPr = run._element.get_or_add_rPr()
313
+ rotation = OxmlElement('w:eastAsianLayout')
314
+ rotation.set(qn('w:vert'), 'eaVert270')
315
+ rPr.append(rotation)
316
+
317
+ accolade_table.rows[0].height = Cm(3)
318
+
319
+ def ajouter_zone_note(self):
320
+ # Tableau pour la note finale avec bordure verte
321
+ zone_note = self.document.add_table(rows=1, cols=1)
322
+ zone_note.style = 'Table Grid'
323
+ zone_note.alignment = WD_TABLE_ALIGNMENT.CENTER
324
+
325
+ cell = zone_note.cell(0, 0)
326
+ cell.width = Cm(5)
327
+ zone_note.rows[0].height = Cm(2)
328
+
329
+ # Appliquer la bordure verte à la cellule via la fonction utilitaire
330
+ set_cell_border(cell, border_color="92D050", border_sz="8")
331
+
332
+ p = cell.paragraphs[0]
333
+ p.alignment = WD_ALIGN_PARAGRAPH.CENTER
334
+ run = p.add_run("Note finale/20")
335
+ run.bold = True
336
+ run.font.size = Pt(14)
337
+ run.font.color.rgb = RGBColor(0, 0, 0)
338
+
339
+ self.document.add_paragraph().space_after = Pt(12)
340
+
341
+ def ajouter_note_candidat_avec_cadre(self):
342
+ note_table = self.document.add_table(rows=1, cols=1)
343
+ note_table.style = 'Table Grid'
344
+ note_table.alignment = WD_TABLE_ALIGNMENT.CENTER
345
+
346
+ cell = note_table.cell(0, 0)
347
+ shading_elm = parse_xml(f'<w:shd {nsdecls("w")} w:fill="E2EFDA"/>')
348
+ cell._tc.get_or_add_tcPr().append(shading_elm)
349
+
350
+ p = cell.paragraphs[0]
351
+ 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) également, le candidat devra fournir 2 copies de son projet sur une page! (appréciations: NR, NM, PM, M).")
352
+ run.italic = True
353
+ run.font.size = Pt(8)
354
+
355
+ def ajouter_lignes_correcteurs(self):
356
+ para_intro = self.document.add_paragraph()
357
+ para_intro.space_before = Pt(6)
358
+
359
+ for role in ["Projet", "principal", "ROV"]:
360
+ para = self.document.add_paragraph()
361
+ para.space_before = Pt(4)
362
+ para.space_after = Pt(4)
363
+ run = para.add_run(f"Correcteur {role} : ")
364
+ run.bold = True
365
+ para.add_run(".................................................................")
366
+
367
+ # Méthodes de modification des données
368
+ def modifier_centre_examen(self, nom):
369
+ self.centre_examen = nom
370
+
371
+ def modifier_type_examen(self, type_examen):
372
+ self.type_examen = type_examen
373
+
374
+ def modifier_serie(self, serie):
375
+ self.serie = serie
376
+
377
+ def modifier_etablissement(self, nom):
378
+ self.etablissement = nom
379
+
380
+ def modifier_session(self, annee):
381
+ self.session = annee
382
+
383
+ def modifier_candidat(self, nom):
384
+ self.nom_candidat = nom
385
+
386
+ def ajouter_element(self, nom, categorie, points):
387
+ self.elements_techniques.append({
388
+ "nom": nom,
389
+ "categorie": categorie,
390
+ "points": float(points)
391
+ })
392
+
393
+ def generer_document(self, nom_fichier="evaluation_gymnastique.docx"):
394
+ self.ajouter_entete_colore()
395
+ self.creer_tableau_elements()
396
+ self.ajouter_note_jury()
397
+ self.creer_tableau_recapitulatif()
398
+ self.ajouter_accolade()
399
+ self.ajouter_zone_note()
400
+ self.ajouter_note_candidat_avec_cadre()
401
+ self.ajouter_lignes_correcteurs()
402
+ self.document.save(nom_fichier)
403
+ return nom_fichier
404
+
405
+ # --- Application Flask ---
406
+ app = Flask(__name__)
407
+
408
+ @app.route("/", methods=["GET", "POST"])
409
+ def index():
410
+ if request.method == "POST":
411
+ # Récupération des informations depuis le formulaire
412
+ centre_examen = request.form.get("centre_examen", "Centre d'examen")
413
+ type_examen = request.form.get("type_examen", "Bac Général")
414
+ serie = request.form.get("serie", "Série")
415
+ etablissement = request.form.get("etablissement", "Établissement")
416
+ session_value = request.form.get("session", "2025")
417
+ nom_candidat = request.form.get("nom_candidat", "Candidat")
418
+
419
+ # Création et configuration du document
420
+ evaluation = EvaluationGymnique()
421
+ evaluation.modifier_centre_examen(centre_examen)
422
+ evaluation.modifier_type_examen(type_examen)
423
+ evaluation.modifier_serie(serie)
424
+ evaluation.modifier_etablissement(etablissement)
425
+ evaluation.modifier_session(session_value)
426
+ evaluation.modifier_candidat(nom_candidat)
427
+
428
+ # Récupération des éléments techniques ajoutés dynamiquement
429
+ element_names = request.form.getlist("new_element_name")
430
+ element_categories = request.form.getlist("new_element_categorie")
431
+ element_points = request.form.getlist("new_element_points")
432
+ for name, cat, pts in zip(element_names, element_categories, element_points):
433
+ if name and cat and pts:
434
+ evaluation.ajouter_element(name, cat, pts)
435
+
436
+ # Génération du document DOCX
437
+ filename = "evaluation_gymnastique.docx"
438
+ evaluation.generer_document(filename)
439
+
440
+ # Vérification du format de sortie demandé
441
+ output_format = request.form.get("format", "docx")
442
+ if output_format == "pdf":
443
+ # Conversion en PDF via ConvertAPI
444
+ result = convertapi.convert('pdf', {
445
+ 'File': filename,
446
+ 'FileName': 'evaluation_gymnastique',
447
+ 'ConvertMetadata': 'false',
448
+ 'ConvertHeadings': 'false'
449
+ }, from_format='doc')
450
+ pdf_filename = "evaluation_gymnastique.pdf"
451
+ result.save_files(pdf_filename)
452
+ return send_file(pdf_filename, as_attachment=True)
453
+ else:
454
+ return send_file(filename, as_attachment=True)
455
+
456
+ return render_template("index.html")
457
+
458
+ if __name__ == "__main__":
459
+ app.run(debug=True)