fix: Réintégration du bouton de Bilan de compétence + harmonisation des paths d'upload de fichier

This commit is contained in:
N3WT DE COMPET
2026-04-05 09:24:30 +02:00
parent 409cf05f1a
commit 2a223fe3dd
6 changed files with 223 additions and 52 deletions

View File

@ -54,17 +54,38 @@ class Sibling(models.Model):
return "SIBLING"
def registration_photo_upload_to(instance, filename):
return f"registration_files/dossier_rf_{instance.pk}/parent/{filename}"
"""
Génère le chemin de stockage pour la photo élève.
Structure : Etablissement/dossier_NomEleve_PrenomEleve/filename
"""
register_form = getattr(instance, 'registrationform', None)
if register_form and register_form.establishment:
est_name = register_form.establishment.name
elif instance.associated_class and instance.associated_class.establishment:
est_name = instance.associated_class.establishment.name
else:
est_name = "unknown_establishment"
student_last = instance.last_name if instance and instance.last_name else "unknown"
student_first = instance.first_name if instance and instance.first_name else "unknown"
return f"{est_name}/dossier_{student_last}_{student_first}/{filename}"
def registration_bilan_form_upload_to(instance, filename):
# On récupère le RegistrationForm lié à l'élève
"""
Génère le chemin de stockage pour les bilans de compétences.
Structure : Etablissement/dossier_NomEleve_PrenomEleve/filename
"""
register_form = getattr(instance.student, 'registrationform', None)
if register_form:
pk = register_form.pk
if register_form and register_form.establishment:
est_name = register_form.establishment.name
elif instance.student.associated_class and instance.student.associated_class.establishment:
est_name = instance.student.associated_class.establishment.name
else:
# fallback sur l'id de l'élève si pas de registrationform
pk = instance.student.pk
return f"registration_files/dossier_rf_{pk}/bilan/{filename}"
est_name = "unknown_establishment"
student_last = instance.student.last_name if instance.student else "unknown"
student_first = instance.student.first_name if instance.student else "unknown"
return f"{est_name}/dossier_{student_last}_{student_first}/{filename}"
class BilanCompetence(models.Model):
student = models.ForeignKey('Subscriptions.Student', on_delete=models.CASCADE, related_name='bilans')

View File

@ -4,9 +4,22 @@
<meta charset="UTF-8">
<title>Bilan de compétences</title>
<style>
body { font-family: Arial, sans-serif; margin: 2em; }
body { font-family: Arial, sans-serif; margin: 1.2em; color: #111827; }
h1, h2 { color: #059669; }
.student-info { margin-bottom: 2em; }
.top-header { width: 100%; border-bottom: 2px solid #d1fae5; border-collapse: collapse; margin-bottom: 14px; }
.top-header td { vertical-align: top; border: none; padding: 0; }
.school-logo { width: 54px; height: 54px; object-fit: contain; margin-right: 8px; }
.product-logo { width: 58px; }
.title-row { margin: 8px 0 10px 0; }
.student-info {
margin-bottom: 1em;
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 8px 12px;
background: #f8fafc;
}
.student-info table { width: 100%; border-collapse: collapse; font-size: 0.98em; }
.student-info td { border: none; padding: 1px 0; }
.domain-table { width: 100%; border-collapse: collapse; margin-bottom: 2em; }
.domain-header th {
background: #d1fae5;
@ -25,16 +38,77 @@
th, td { border: 1px solid #e5e7eb; padding: 0.5em; }
th.competence-header { background: #d1fae5; }
td.competence-nom { word-break: break-word; max-width: 320px; }
.footer-note { margin-top: 32px; }
.comment-space {
min-height: 180px;
margin-top: 18px;
margin-bottom: 78px;
}
.footer-grid { width: 100%; border-collapse: collapse; }
.footer-grid td {
border: none;
padding: 0;
vertical-align: top;
}
.field-line { border-bottom: 1px solid #9ca3af; height: 24px; margin-top: 6px; }
.signature-line { border-bottom: 2px solid #059669; height: 30px; margin-top: 6px; }
</style>
</head>
<body>
<h1>Bilan de compétences</h1>
<table class="top-header">
<tr>
<td style="width: 68%; padding-bottom: 8px;">
<table style="border-collapse: collapse; width: 100%;">
<tr>
{% if establishment.logo_path %}
<td style="width: 64px; border: none; vertical-align: top;">
<img src="{{ establishment.logo_path }}" alt="Logo établissement" class="school-logo">
</td>
{% endif %}
<td style="border: none; vertical-align: top;">
<div style="font-size: 1.25em; font-weight: 700; color: #065f46; margin-top: 2px;">{{ establishment.name }}</div>
{% if establishment.address %}
<div style="font-size: 0.9em; color: #4b5563; margin-top: 4px;">{{ establishment.address }}</div>
{% endif %}
</td>
</tr>
</table>
</td>
<td style="width: 32%; text-align: right; padding-bottom: 8px;">
<table style="border-collapse: collapse; width: 100%; margin-left: auto;">
<tr>
<td style="border: none; text-align: right;">
<div style="font-size: 0.86em; color: #6b7280; margin-bottom: 4px;">Généré avec</div>
{% if product.logo_path %}
<div style="margin-bottom: 4px;"><img src="{{ product.logo_path }}" alt="Logo n3wt" class="product-logo"></div>
{% endif %}
<div style="font-size: 0.95em; font-weight: 700; color: #059669;">{{ product.name }}</div>
</td>
</tr>
</table>
</td>
</tr>
</table>
<div class="title-row">
<h1>Bilan de compétences</h1>
</div>
<div class="student-info">
<strong>Élève :</strong> {{ student.last_name }} {{ student.first_name }}<br>
<strong>Niveau :</strong> {{ student.level }}<br>
<strong>Classe :</strong> {{ student.class_name }}<br>
<strong>Période :</strong> {{ period }}<br>
<strong>Date :</strong> {{ date }}
<table>
<tr>
<td><strong>Élève :</strong> {{ student.last_name }} {{ student.first_name }}</td>
<td style="text-align: right;"><strong>Date :</strong> {{ date }}</td>
</tr>
<tr>
<td><strong>Niveau :</strong> {{ student.level }}</td>
<td style="text-align: right;"><strong>Période :</strong> {{ period }}</td>
</tr>
<tr>
<td><strong>Classe :</strong> {{ student.class_name }}</td>
<td></td>
</tr>
</table>
</div>
{% for domaine in domaines %}
@ -72,41 +146,33 @@
</table>
{% endfor %}
<div style="margin-top: 60px; padding: 0; max-width: 700px;">
<div style="
min-height: 180px;
background: #fff;
border: 1.5px dashed #a7f3d0;
border-radius: 12px;
padding: 24px 24px 18px 24px;
display: flex;
flex-direction: column;
justify-content: flex-start;
position: relative;
margin-bottom: 64px; /* Augmente l'espace après l'encadré */
">
<div style="font-weight: bold; color: #059669; font-size: 1.25em; display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">
<span>Appréciation générale / Commentaire : </span>
</div>
<!-- Espace vide pour écrire -->
<div style="flex:1;"></div>
<div style="flex:1;"></div>
<div style="flex:1;"></div>
<div style="flex:1;"></div>
<div style="flex:1;"></div>
<div style="flex:1;"></div>
<div style="flex:1;"></div>
</div>
<div style="display: flex; justify-content: flex-end; gap: 48px; margin-top: 32px;">
<div>
<span style="font-weight: bold; color: #059669;font-size: 1.25em;">Date :</span>
<span style="display: inline-block; min-width: 120px; border-bottom: 1.5px solid #a7f3d0; margin-left: 8px;">&nbsp;</span>
</div>
<div>
<span style="font-weight: bold; color: #059669;font-size: 1.25em;">Signature :</span>
<span style="display: inline-block; min-width: 180px; border-bottom: 2px solid #059669; margin-left: 8px;">&nbsp;</span>
</div>
<div class="footer-note">
<div style="font-weight: 700; color: #059669; font-size: 1.1em;">
Appréciation générale / Commentaire
</div>
<div class="comment-space"></div>
<table class="footer-grid">
<tr>
<td style="width: 45%;">
<table style="width: 100%; border-collapse: collapse;">
<tr>
<td style="width: 70px; border: none; font-weight: 700; color: #059669;">Date :</td>
<td style="border: none;"><div class="field-line"></div></td>
</tr>
</table>
</td>
<td style="width: 10%;"></td>
<td style="width: 45%;">
<table style="width: 100%; border-collapse: collapse;">
<tr>
<td style="width: 90px; border: none; font-weight: 700; color: #059669;">Signature :</td>
<td style="border: none;"><div class="signature-line"></div></td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</body>
</html>

View File

@ -175,13 +175,43 @@ class StudentCompetencyListCreateView(APIView):
domaine_dict["categories"].append(categorie_dict)
if domaine_dict["categories"]:
result.append(domaine_dict)
establishment = None
if student.associated_class and student.associated_class.establishment:
establishment = student.associated_class.establishment
else:
try:
establishment = student.registrationform.establishment
except Exception:
establishment = None
establishment_logo_path = None
if establishment and establishment.logo:
try:
if establishment.logo.path and os.path.exists(establishment.logo.path):
establishment_logo_path = establishment.logo.path
except Exception:
establishment_logo_path = None
n3wt_logo_path = os.path.join(settings.BASE_DIR, 'static', 'img', 'logo_min.svg')
if not os.path.exists(n3wt_logo_path):
n3wt_logo_path = None
context = {
"student": {
"first_name": student.first_name,
"last_name": student.last_name,
"level": student.level,
"class_name": student.associated_class.atmosphere_name,
"class_name": student.associated_class.atmosphere_name if student.associated_class else "Non assignée",
},
"establishment": {
"name": establishment.name if establishment else "Établissement",
"address": establishment.address if establishment else "",
"logo_path": establishment_logo_path,
},
"product": {
"name": "n3wt-school",
"logo_path": n3wt_logo_path,
},
"period": period,
"date": date.today().strftime("%d/%m/%Y"),