Docfile commited on
Commit
811b997
·
verified ·
1 Parent(s): d7ea0bc

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +399 -0
app.py ADDED
@@ -0,0 +1,399 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
9
+ from docx.oxml import parse_xml
10
+
11
+ # Configuration des identifiants ConvertAPI
12
+ convertapi.api_credentials = 'secret_8wCI6pgOP9AxLVJG'
13
+
14
+ # --- Classe de génération de document ---
15
+ class EvaluationGymnique:
16
+ def __init__(self):
17
+ self.document = Document()
18
+ self.document.sections[0].page_height = Cm(29.7)
19
+ self.document.sections[0].page_width = Cm(21)
20
+ self.document.sections[0].left_margin = Cm(1.5)
21
+ self.document.sections[0].right_margin = Cm(1.5)
22
+ self.document.sections[0].top_margin = Cm(1)
23
+ self.document.sections[0].bottom_margin = Cm(1)
24
+
25
+ # Informations d'en-tête par défaut
26
+ self.centre_examen = "Centre d'examen"
27
+ self.type_examen = "Bac Général"
28
+ self.serie = "Série"
29
+ self.etablissement = "Établissement"
30
+ self.session = "2025"
31
+ self.nom_candidat = "Candidat"
32
+
33
+ # Liste vide pour les éléments techniques
34
+ self.elements_techniques = []
35
+ self.appreciations = ["M", "PM", "NM", "NR"]
36
+
37
+ def ajouter_entete_colore(self):
38
+ header_paragraph = self.document.add_paragraph()
39
+ header_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
40
+ header_paragraph.space_after = Pt(6)
41
+ header_run = header_paragraph.add_run("ÉVALUATION GYMNASTIQUE")
42
+ header_run.bold = True
43
+ header_run.font.size = Pt(14)
44
+ header_run.font.color.rgb = RGBColor(0, 32, 96)
45
+
46
+ header_table = self.document.add_table(rows=3, cols=2)
47
+ header_table.style = 'Table Grid'
48
+ header_table.autofit = False
49
+
50
+ for row in header_table.rows:
51
+ row.height = Cm(0.8)
52
+ for row in header_table.rows:
53
+ for cell in row.cells:
54
+ shading_elm = parse_xml(f'<w:shd {nsdecls("w")} w:fill="D9E2F3"/>')
55
+ cell._tc.get_or_add_tcPr().append(shading_elm)
56
+
57
+ # Première ligne
58
+ cell = header_table.cell(0, 0)
59
+ paragraph = cell.paragraphs[0]
60
+ run = paragraph.add_run("Centre d'examen: ")
61
+ run.bold = True
62
+ run.font.size = Pt(10)
63
+ run.font.color.rgb = RGBColor(0, 32, 96)
64
+ paragraph.add_run(self.centre_examen).font.size = Pt(10)
65
+
66
+ cell = header_table.cell(0, 1)
67
+ paragraph = cell.paragraphs[0]
68
+ run = paragraph.add_run("Examen: ")
69
+ run.bold = True
70
+ run.font.size = Pt(10)
71
+ run.font.color.rgb = RGBColor(0, 32, 96)
72
+ paragraph.add_run(self.type_examen).font.size = Pt(10)
73
+
74
+ # Deuxième ligne
75
+ cell = header_table.cell(1, 0)
76
+ paragraph = cell.paragraphs[0]
77
+ run = paragraph.add_run("Série: ")
78
+ run.bold = True
79
+ run.font.size = Pt(10)
80
+ run.font.color.rgb = RGBColor(0, 32, 96)
81
+ paragraph.add_run(self.serie).font.size = Pt(10)
82
+
83
+ cell = header_table.cell(1, 1)
84
+ paragraph = cell.paragraphs[0]
85
+ run = paragraph.add_run("Établissement: ")
86
+ run.bold = True
87
+ run.font.size = Pt(10)
88
+ run.font.color.rgb = RGBColor(0, 32, 96)
89
+ paragraph.add_run(self.etablissement).font.size = Pt(10)
90
+
91
+ # Troisième ligne
92
+ cell = header_table.cell(2, 0)
93
+ paragraph = cell.paragraphs[0]
94
+ run = paragraph.add_run("Session: ")
95
+ run.bold = True
96
+ run.font.size = Pt(10)
97
+ run.font.color.rgb = RGBColor(0, 32, 96)
98
+ paragraph.add_run(self.session).font.size = Pt(10)
99
+
100
+ cell = header_table.cell(2, 1)
101
+ paragraph = cell.paragraphs[0]
102
+ run = paragraph.add_run("Candidat: ")
103
+ run.bold = True
104
+ run.font.size = Pt(10)
105
+ run.font.color.rgb = RGBColor(0, 32, 96)
106
+ paragraph.add_run(self.nom_candidat).font.size = Pt(10)
107
+
108
+ self.document.add_paragraph().space_after = Pt(4)
109
+
110
+ def creer_tableau_elements(self):
111
+ table = self.document.add_table(rows=len(self.elements_techniques) + 1, cols=5)
112
+ table.style = 'Table Grid'
113
+ table.alignment = WD_TABLE_ALIGNMENT.CENTER
114
+
115
+ # Configuration des largeurs de colonnes
116
+ for cell in table.columns[0].cells:
117
+ cell.width = Cm(8)
118
+ for cell in table.columns[1].cells:
119
+ cell.width = Cm(3)
120
+ for cell in table.columns[2].cells:
121
+ cell.width = Cm(2)
122
+ for cell in table.columns[3].cells:
123
+ cell.width = Cm(2.5)
124
+ for cell in table.columns[4].cells:
125
+ cell.width = Cm(2.5)
126
+
127
+ # Réduire la hauteur des lignes
128
+ for row in table.rows:
129
+ row.height = Cm(0.8) # Réduit de 1cm à 0.8cm
130
+
131
+ # En-tête du tableau
132
+ header_row = table.rows[0]
133
+ for cell in header_row.cells:
134
+ shading_elm = parse_xml(f'<w:shd {nsdecls("w")} w:fill="BDD7EE"/>')
135
+ cell._tc.get_or_add_tcPr().append(shading_elm)
136
+
137
+ headers = ["ELEMENTS TECHNIQUES", "CATEGORIES D'ELEMENTS TECHNIQUES ET PONDERATION", "", "APPRECIATIONS", "POINTS Accordés"]
138
+ for i, header in enumerate(headers):
139
+ cell = table.cell(0, i)
140
+ cell.text = header
141
+ cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
142
+ run = cell.paragraphs[0].runs[0]
143
+ run.bold = True
144
+ run.font.size = Pt(9)
145
+ run.font.color.rgb = RGBColor(0, 32, 96)
146
+ table.cell(0, 1).merge(table.cell(0, 2))
147
+
148
+ # Ajout des éléments techniques
149
+ for i, element in enumerate(self.elements_techniques, 1):
150
+ element_cell = table.cell(i, 0)
151
+ element_cell.text = element["nom"]
152
+ element_cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.LEFT
153
+ run = element_cell.paragraphs[0].runs[0]
154
+ run.bold = True
155
+ run.font.size = Pt(9)
156
+
157
+ categorie_cell = table.cell(i, 1)
158
+ categorie_cell.text = element["categorie"]
159
+ categorie_cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
160
+ run = categorie_cell.paragraphs[0].runs[0]
161
+ run.bold = True
162
+ run.font.size = Pt(9)
163
+ run.italic = True
164
+
165
+ points_cell = table.cell(i, 2)
166
+ points_cell.text = str(element["points"])
167
+ points_cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
168
+ run = points_cell.paragraphs[0].runs[0]
169
+ run.bold = True
170
+ run.font.size = Pt(9)
171
+ run.italic = True
172
+
173
+ def ajouter_note_jury(self):
174
+ para = self.document.add_paragraph()
175
+ para.space_before = Pt(4)
176
+ para.space_after = Pt(4)
177
+ 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.")
178
+ run.bold = True
179
+ run.font.color.rgb = RGBColor(255, 0, 0)
180
+ run.font.size = Pt(9)
181
+
182
+ def creer_tableau_recapitulatif(self):
183
+ note_table = self.document.add_table(rows=3, cols=13)
184
+ note_table.style = 'Table Grid'
185
+ note_table.alignment = WD_TABLE_ALIGNMENT.CENTER
186
+
187
+ for row in note_table.rows:
188
+ row.height = Cm(0.6)
189
+ for cell in note_table.rows[0].cells:
190
+ shading_elm = parse_xml(f'<w:shd {nsdecls("w")} w:fill="BDD7EE"/>')
191
+ cell._tc.get_or_add_tcPr().append(shading_elm)
192
+
193
+ for col, (type_lettre, points) in enumerate([("A", "1pt"), ("B", "1,5pt"), ("C", "2pts"), ("D", "2,5pts"), ("E", "3pts")]):
194
+ idx = col * 2
195
+ if idx + 1 < len(note_table.columns):
196
+ cell = note_table.cell(0, idx)
197
+ cell.merge(note_table.cell(0, idx + 1))
198
+ cell.text = f"Type {type_lettre}\n{points}"
199
+ cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
200
+ for run in cell.paragraphs[0].runs:
201
+ run.bold = True
202
+ run.font.size = Pt(9)
203
+ run.font.color.rgb = RGBColor(0, 32, 96)
204
+
205
+ for col, (titre, points) in enumerate([("ROV", "2pts"), ("Projet", "2pts"), ("Réalisation", "16pts")], 10):
206
+ if col < len(note_table.columns):
207
+ cell = note_table.cell(0, col)
208
+ cell.text = f"{titre}\n{points}"
209
+ cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
210
+ for run in cell.paragraphs[0].runs:
211
+ run.bold = True
212
+ run.font.size = Pt(9)
213
+ run.font.color.rgb = RGBColor(0, 32, 96)
214
+
215
+ for col in range(5):
216
+ idx = col * 2
217
+ if idx + 1 < len(note_table.columns):
218
+ neg_cell = note_table.cell(1, idx)
219
+ neg_cell.text = "NEG"
220
+ neg_cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
221
+ for run in neg_cell.paragraphs[0].runs:
222
+ run.italic = True
223
+ run.font.size = Pt(9)
224
+
225
+ note_cell = note_table.cell(1, idx + 1)
226
+ note_cell.text = "Note"
227
+ note_cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
228
+ for run in note_cell.paragraphs[0].runs:
229
+ run.italic = True
230
+ run.font.size = Pt(9)
231
+
232
+ for col in range(10, 13):
233
+ if col < len(note_table.columns):
234
+ cell = note_table.cell(1, col)
235
+ cell.text = "Note"
236
+ cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
237
+ for run in cell.paragraphs[0].runs:
238
+ run.italic = True
239
+ run.font.size = Pt(9)
240
+
241
+ for col, type_lettre in enumerate(["A", "B", "C", "D", "E"]):
242
+ idx = col * 2
243
+ if idx < len(note_table.columns):
244
+ neg_cell = note_table.cell(2, idx)
245
+ neg_cell.text = ""
246
+ neg_cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
247
+
248
+ def ajouter_zone_note(self):
249
+ zone_note = self.document.add_table(rows=1, cols=2)
250
+ zone_note.style = 'Table Grid'
251
+ zone_note.alignment = WD_TABLE_ALIGNMENT.RIGHT
252
+
253
+ # Réduire la largeur des cellules
254
+ cell_width = Cm(1.8) # Réduit de 2cm à 1.8cm
255
+
256
+ cell_libelle = zone_note.cell(0, 0)
257
+ cell_libelle.width = cell_width
258
+ cell_libelle.text = "Note finale"
259
+ cell_libelle.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.RIGHT
260
+ run = cell_libelle.paragraphs[0].runs[0]
261
+ run.bold = True
262
+ run.font.size = Pt(10) # Légèrement réduit de 11pt à 10pt
263
+ run.font.color.rgb = RGBColor(0, 32, 96)
264
+
265
+ cell_saisie = zone_note.cell(0, 1)
266
+ cell_saisie.width = cell_width
267
+ # Réduire la hauteur de la ligne également
268
+ zone_note.rows[0].height = Cm(1.2) # Réduit de 2cm à 1.2cm
269
+ shading_elm = parse_xml(f'<w:shd {nsdecls("w")} w:fill="F2F2F2"/>')
270
+ cell_saisie._tc.get_or_add_tcPr().append(shading_elm)
271
+ cell_saisie.text = "/20"
272
+ cell_saisie.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
273
+ run = cell_saisie.paragraphs[0].runs[0]
274
+ run.bold = True
275
+ run.font.size = Pt(10) # Légèrement réduit de 11pt à 10pt
276
+
277
+ def ajouter_note_candidat_avec_cadre(self):
278
+ # Pas d'espace supplémentaire avant
279
+ note_table = self.document.add_table(rows=1, cols=1)
280
+ note_table.style = 'Table Grid'
281
+ note_table.alignment = WD_TABLE_ALIGNMENT.CENTER
282
+
283
+ cell = note_table.cell(0, 0)
284
+ shading_elm = parse_xml(f'<w:shd {nsdecls("w")} w:fill="E2EFDA"/>')
285
+ cell._tc.get_or_add_tcPr().append(shading_elm)
286
+
287
+ p = cell.paragraphs[0]
288
+ 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).")
289
+ run.italic = True
290
+ run.font.size = Pt(7) # Légèrement réduit de 8pt à 7pt
291
+
292
+ # Ajouter un petit espace après
293
+ self.document.add_paragraph().space_after = Pt(3)
294
+
295
+ def ajouter_lignes_correcteurs(self):
296
+ # Réduire l'espace avant
297
+ para_intro = self.document.add_paragraph()
298
+ para_intro.space_before = Pt(3) # Réduit de 6pt à 3pt
299
+
300
+ for role in ["Projet", "principal", "ROV"]:
301
+ para = self.document.add_paragraph()
302
+ para.space_before = Pt(1) # Réduit de 2pt à 1pt
303
+ para.space_after = Pt(1) # Réduit de 2pt à 1pt
304
+ run = para.add_run(f"Correcteur {role} : ")
305
+ run.bold = True
306
+ para.add_run(".................................................................")
307
+
308
+ # Méthodes de modification des données
309
+ def modifier_centre_examen(self, nom):
310
+ self.centre_examen = nom
311
+
312
+ def modifier_type_examen(self, type_examen):
313
+ self.type_examen = type_examen
314
+
315
+ def modifier_serie(self, serie):
316
+ self.serie = serie
317
+
318
+ def modifier_etablissement(self, nom):
319
+ self.etablissement = nom
320
+
321
+ def modifier_session(self, annee):
322
+ self.session = annee
323
+
324
+ def modifier_candidat(self, nom):
325
+ self.nom_candidat = nom
326
+
327
+ def ajouter_element(self, nom, categorie, points):
328
+ self.elements_techniques.append({
329
+ "nom": nom,
330
+ "categorie": categorie,
331
+ "points": float(points)
332
+ })
333
+
334
+ def generer_document(self, nom_fichier="evaluation_gymnastique.docx"):
335
+ self.ajouter_entete_colore()
336
+ self.creer_tableau_elements()
337
+ self.ajouter_note_jury()
338
+ self.creer_tableau_recapitulatif()
339
+ self.ajouter_zone_note() # Zone de note finale
340
+ self.ajouter_note_candidat_avec_cadre() # NB2 juste après la note finale
341
+ self.ajouter_lignes_correcteurs() # Déplacé après le NB2
342
+ self.document.save(nom_fichier)
343
+ return nom_fichier
344
+
345
+ # --- Application Flask ---
346
+ app = Flask(__name__)
347
+
348
+ @app.route("/", methods=["GET", "POST"])
349
+ def index():
350
+ if request.method == "POST":
351
+ # Récupération des informations depuis le formulaire
352
+ centre_examen = request.form.get("centre_examen", "Centre d'examen")
353
+ type_examen = request.form.get("type_examen", "Bac Général")
354
+ serie = request.form.get("serie", "Série")
355
+ etablissement = request.form.get("etablissement", "Établissement")
356
+ session_value = request.form.get("session", "2025")
357
+ nom_candidat = request.form.get("nom_candidat", "Candidat")
358
+
359
+ # Création et configuration du document
360
+ evaluation = EvaluationGymnique()
361
+ evaluation.modifier_centre_examen(centre_examen)
362
+ evaluation.modifier_type_examen(type_examen)
363
+ evaluation.modifier_serie(serie)
364
+ evaluation.modifier_etablissement(etablissement)
365
+ evaluation.modifier_session(session_value)
366
+ evaluation.modifier_candidat(nom_candidat)
367
+
368
+ # Récupération des éléments techniques ajoutés dynamiquement
369
+ element_names = request.form.getlist("new_element_name")
370
+ element_categories = request.form.getlist("new_element_categorie")
371
+ element_points = request.form.getlist("new_element_points")
372
+ for name, cat, pts in zip(element_names, element_categories, element_points):
373
+ if name and cat and pts:
374
+ evaluation.ajouter_element(name, cat, pts)
375
+
376
+ # Génération du document DOCX
377
+ filename = "evaluation_gymnastique.docx"
378
+ evaluation.generer_document(filename)
379
+
380
+ # Vérification du format de sortie demandé
381
+ output_format = request.form.get("format", "docx")
382
+ if output_format == "pdf":
383
+ # Conversion en PDF via ConvertAPI
384
+ result = convertapi.convert('pdf', {
385
+ 'File': filename,
386
+ 'FileName': 'evaluation_gymnastique',
387
+ 'ConvertMetadata': 'false',
388
+ 'ConvertHeadings': 'false'
389
+ }, from_format='doc')
390
+ pdf_filename = "evaluation_gymnastique.pdf"
391
+ result.save_files(pdf_filename)
392
+ return send_file(pdf_filename, as_attachment=True)
393
+ else:
394
+ return send_file(filename, as_attachment=True)
395
+
396
+ return render_template("index.html")
397
+
398
+ if __name__ == "__main__":
399
+ app.run(debug=True)