Browse Source

Gestion de l'affichage des appréciations individuelles dans le tableau de bord

main
scayac 6 months ago
parent
commit
563f466360
  1. 624
      main/templates/resultat_appreciations.html
  2. 3
      main/urls.py
  3. 20
      main/views.py

624
main/templates/resultat_appreciations.html

@ -7,69 +7,87 @@
<link href="https://cdn.jsdelivr.net/npm/startbootstrap-sb-admin@7.0.6/dist/css/styles.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/startbootstrap-sb-admin@7.0.6/dist/css/styles.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style> <style>
:root { :root {
color-scheme: light dark; color-scheme: light dark;
} }
body, .bg-light { body, .bg-light {
background-color: #f8f9fa; background-color: #f8f9fa;
color: #212529; color: #212529;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
html, body, .bg-light { html, body, .bg-light {
background-color: #181a1b !important; background-color: #181a1b !important;
color: #f8f9fa !important; color: #f8f9fa !important;
} }
.card, .card-header, .card-body { .card, .card-header, .card-body {
background-color: #23272b !important; background-color: #23272b !important;
color: #f8f9fa !important; color: #f8f9fa !important;
} }
.table, .table-bordered { .table, .table-bordered {
color: #f8f9fa; color: #f8f9fa;
background-color: #23272b !important; background-color: #23272b !important;
border-color: #444c56 !important; border-color: #444c56 !important;
} }
.table-bordered th, .table-bordered td { .table-bordered th, .table-bordered td {
border-color: #444c56 !important; border-color: #444c56 !important;
background-color: #23272b !important; background-color: #23272b !important;
color: #f8f9fa !important; color: #f8f9fa !important;
} }
.table-striped > tbody > tr:nth-of-type(odd) { .table-striped > tbody > tr:nth-of-type(odd) {
background-color: #20232a !important; background-color: #20232a !important;
} }
.form-select, .form-label, .btn, .alert { .form-select, .form-label, .btn, .alert {
background-color: #23272b !important; background-color: #23272b !important;
color: #f8f9fa !important; color: #f8f9fa !important;
border-color: #444c56 !important; border-color: #444c56 !important;
} }
.btn-primary { .btn-primary {
background-color: #375a7f !important; background-color: #375a7f !important;
border-color: #375a7f !important; border-color: #375a7f !important;
} }
.btn-danger { .btn-danger {
background-color: #c9302c !important; background-color: #c9302c !important;
border-color: #c9302c !important; border-color: #c9302c !important;
} }
.btn-success { .btn-success {
background-color: #449d44 !important; background-color: #449d44 !important;
border-color: #449d44 !important; border-color: #449d44 !important;
} }
.navbar, .navbar-dark, .bg-dark { .navbar, .navbar-dark, .bg-dark {
background-color: #23272b !important; background-color: #23272b !important;
} }
.alert-warning { .alert-warning {
background-color: #3a3a3a !important; background-color: #3a3a3a !important;
color: #ffe082 !important; color: #ffe082 !important;
border-color: #444c56 !important; border-color: #444c56 !important;
} }
[class*="bg-"] { [class*="bg-"] {
background-color: inherit !important; background-color: inherit !important;
} }
#credit-restant { #credit-restant {
background-color: #ffe082 !important; background-color: #ffe082 !important;
color: #23272b !important; color: #23272b !important;
border: 1px solid #444c56 !important; border: 1px solid #444c56 !important;
} }
} .btn-outline-primary, .btn-outline-primary:focus, .btn-outline-primary:active, .btn-outline-primary:hover {
color: #23272b !important;
background-color: #ffe082 !important;
border-color: #ffe082 !important;
box-shadow: none !important;
}
.btn-outline-primary:disabled, .btn-outline-primary.disabled {
color: #bdbdbd !important;
background-color: #444c56 !important;
border-color: #444c56 !important;
}
}
#bulletinModal .modal-content, #bulletinModal .modal-body, #bulletinModal .modal-header {
background-color: #fff !important;
color: #23272b !important;
}
#bulletinModal .modal-title {
color: #23272b !important;
}
</style> </style>
</head> </head>
<body class="bg-light"> <body class="bg-light">
@ -108,7 +126,7 @@ body, .bg-light {
</div> </div>
<div class="mb-3 d-flex gap-2"> <div class="mb-3 d-flex gap-2">
<button id="generation-toggle" class="btn btn-primary mb-3">Lancer la génération</button> <button id="generation-toggle" class="btn btn-primary mb-3">Lancer la génération</button>
<button id="export-pdf" class="btn btn-secondary mb-3" type="button" onclick="exportTableToPDF()">Exporter en PDF</button> <button id="export-pdf" class="btn btn-primary mb-3" type="button" onclick="exportTableToPDF()">Exporter en PDF</button>
</div> </div>
<div id="credit-alert" class="alert alert-danger d-none" role="alert"> <div id="credit-alert" class="alert alert-danger d-none" role="alert">
Vous n'avez plus de crédits pour générer des appréciations. Vous n'avez plus de crédits pour générer des appréciations.
@ -122,219 +140,301 @@ body, .bg-light {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% if appreciations_json and appreciations_json|length > 0 %} {% if appreciations_json and appreciations_json|length > 0 %}
{% for appreciation in appreciations_json %} {% for appreciation in appreciations_json %}
<tr>
<td class="eleve" style="width:1%; white-space:nowrap;">{{ appreciation.eleve }}</td>
<td class="appreciation"></td>
<td style="width:1%; text-align:center; vertical-align:middle;">
<div class="d-flex justify-content-center gap-2">
<button class="btn btn-outline-primary btn-sm action-generate w-100" type="button">Générer</button>
<button class="btn btn-outline-primary btn-sm action-bulletin w-100" type="button" data-eleve="{{ appreciation.eleve }}">Bulletin</button>
</div>
</td>
</tr>
{% endfor %}
{% else %}
<tr> <tr>
<td class="eleve" style="width:1%; white-space:nowrap;">{{ appreciation.eleve }}</td> <td colspan="3"><div class="alert alert-warning mb-0">Aucune appréciation générée.</div></td>
<td class="appreciation"></td>
<td style="width:1%; text-align:center;"><button class="btn btn-outline-primary btn-sm action-generate" type="button">Générer</button></td>
</tr> </tr>
{% endfor %} {% endif %}
{% else %}
<tr>
<td colspan="3"><div class="alert alert-warning mb-0">Aucune appréciation générée.</div></td>
</tr>
{% endif %}
</tbody> </tbody>
</table> </table>
<!-- Injection des appréciations dans une variable JS globale pour accès rapide -->
<script id="appreciations-data" type="application/json">
{{ appreciations_json|safe }}
</script>
<script> <script>
let stopGeneration = false; window.allAppreciations = {};
let enCours = false; try {
const appreciationsList = JSON.parse(document.getElementById('appreciations-data').textContent);
appreciationsList.forEach(eleve => {
let appreciations = eleve.appreciations;
if (typeof appreciations === 'string') {
try { appreciations = JSON.parse(appreciations); } catch (e) { appreciations = []; }
}
window.allAppreciations[eleve.eleve] = appreciations;
});
} catch (e) { window.allAppreciations = {}; }
</script>
<script>
let stopGeneration = false;
let enCours = false;
function updateActionButtons() { function updateActionButtons() {
const rows = document.querySelectorAll('#appreciations-table tbody tr'); const rows = document.querySelectorAll('#appreciations-table tbody tr');
rows.forEach(row => { rows.forEach(row => {
const appreciationCell = row.querySelector('.appreciation'); const appreciationCell = row.querySelector('.appreciation');
const btn = row.querySelector('.action-generate'); const btn = row.querySelector('.action-generate');
if (!btn) return; if (!btn) return;
if (appreciationCell.textContent.trim()) { if (appreciationCell.textContent.trim()) {
btn.textContent = 'Régénérer'; btn.textContent = 'Régénérer';
} else { } else {
btn.textContent = 'Générer'; btn.textContent = 'Générer';
} }
btn.disabled = enCours; btn.disabled = enCours;
}); });
} }
function getRowsSansAppreciation() { function getRowsSansAppreciation() {
return Array.from(document.querySelectorAll('#appreciations-table tbody tr')).filter(row => !row.querySelector('.appreciation').textContent.trim()); return Array.from(document.querySelectorAll('#appreciations-table tbody tr')).filter(row => !row.querySelector('.appreciation').textContent.trim());
} }
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const btnToggle = document.getElementById('generation-toggle'); const btnToggle = document.getElementById('generation-toggle');
const selectModele = document.getElementById('modele-select'); const selectModele = document.getElementById('modele-select');
const creditRestant = document.getElementById('credit-restant'); const creditRestant = document.getElementById('credit-restant');
const creditAlert = document.getElementById('credit-alert'); const creditAlert = document.getElementById('credit-alert');
let rows = getRowsSansAppreciation(); let rows = getRowsSansAppreciation();
function setButtonState(state) { function setButtonState(state) {
if (state === 'lancer') { if (state === 'lancer') {
btnToggle.textContent = 'Lancer la génération'; btnToggle.textContent = 'Lancer la génération';
btnToggle.className = 'btn btn-primary mb-3'; btnToggle.className = 'btn btn-primary mb-3';
} else if (state === 'arreter') { } else if (state === 'arreter') {
btnToggle.textContent = 'Arrêter la génération'; btnToggle.textContent = 'Arrêter la génération';
btnToggle.className = 'btn btn-danger mb-3'; btnToggle.className = 'btn btn-danger mb-3';
} else if (state === 'continuer') { } else if (state === 'continuer') {
btnToggle.textContent = 'Continuer la génération'; btnToggle.textContent = 'Continuer la génération';
btnToggle.className = 'btn btn-success mb-3'; btnToggle.className = 'btn btn-success mb-3';
} }
} }
function traiterLignes() { function traiterLignes() {
rows = getRowsSansAppreciation(); rows = getRowsSansAppreciation();
updateActionButtons(); updateActionButtons();
if (rows.length === 0 || stopGeneration) { if (rows.length === 0 || stopGeneration) {
enCours = false; enCours = false;
updateActionButtons(); updateActionButtons();
if (stopGeneration) setButtonState('continuer'); if (stopGeneration) setButtonState('continuer');
else setButtonState('lancer'); else setButtonState('lancer');
return; return;
} }
enCours = true; enCours = true;
updateActionButtons(); updateActionButtons();
setButtonState('arreter'); setButtonState('arreter');
const row = rows[0]; const row = rows[0];
const eleve = row.querySelector('.eleve').textContent; const eleve = row.querySelector('.eleve').textContent;
const appreciationCell = row.querySelector('.appreciation'); const appreciationCell = row.querySelector('.appreciation');
appreciationCell.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Génération...'; appreciationCell.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Génération...';
fetch("{% url 'generer_appreciation_ajax' %}", { fetch("{% url 'generer_appreciation_ajax' %}", {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token }}' 'X-CSRFToken': '{{ csrf_token }}'
}, },
body: JSON.stringify({eleve: eleve, modele: selectModele.value}) body: JSON.stringify({eleve: eleve, modele: selectModele.value})
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
appreciationCell.textContent = data.appreciation; appreciationCell.textContent = data.appreciation;
updateActionButtons(); updateActionButtons();
if (typeof data.credit !== 'undefined') { if (typeof data.credit !== 'undefined') {
creditRestant.textContent = 'Crédits restants : ' + data.credit; creditRestant.textContent = 'Crédits restants : ' + data.credit;
} }
if (data.stop) { if (data.stop) {
stopGeneration = true; stopGeneration = true;
setButtonState('lancer'); setButtonState('lancer');
if (data.credit === 0) { if (data.credit === 0) {
creditAlert.classList.remove('d-none'); creditAlert.classList.remove('d-none');
} }
return; return;
} }
traiterLignes(); traiterLignes();
}) })
.catch(() => { .catch(() => {
appreciationCell.textContent = 'Erreur lors de la génération'; appreciationCell.textContent = 'Erreur lors de la génération';
updateActionButtons(); updateActionButtons();
traiterLignes(); traiterLignes();
}); });
} }
btnToggle.addEventListener('click', function() { btnToggle.addEventListener('click', function() {
const credit = parseInt(creditRestant.textContent.replace(/\D/g, '')); const credit = parseInt(creditRestant.textContent.replace(/\D/g, ''));
if (credit === 0) { if (credit === 0) {
creditAlert.classList.remove('d-none'); creditAlert.classList.remove('d-none');
return; return;
} else { } else {
creditAlert.classList.add('d-none'); creditAlert.classList.add('d-none');
} }
if (!enCours && (btnToggle.textContent === 'Lancer la génération' || btnToggle.textContent === 'Continuer la génération')) { if (!enCours && (btnToggle.textContent === 'Lancer la génération' || btnToggle.textContent === 'Continuer la génération')) {
stopGeneration = false; stopGeneration = false;
setButtonState('arreter'); setButtonState('arreter');
traiterLignes(); traiterLignes();
} else if (enCours && btnToggle.textContent === 'Arrêter la génération') { } else if (enCours && btnToggle.textContent === 'Arrêter la génération') {
stopGeneration = true; stopGeneration = true;
setButtonState('continuer'); setButtonState('continuer');
} }
}); });
// Action individuelle sur bouton Générer/Régénérer // Action individuelle sur bouton Générer/Régénérer
document.querySelectorAll('.action-generate').forEach(btn => { document.querySelectorAll('.action-generate').forEach(btn => {
btn.addEventListener('click', function() { btn.addEventListener('click', function() {
if (enCours) return; if (enCours) return;
const row = btn.closest('tr'); const row = btn.closest('tr');
const eleve = row.querySelector('.eleve').textContent; const eleve = row.querySelector('.eleve').textContent;
const appreciationCell = row.querySelector('.appreciation'); const appreciationCell = row.querySelector('.appreciation');
appreciationCell.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Génération...'; appreciationCell.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Génération...';
btn.disabled = true; btn.disabled = true;
fetch("{% url 'generer_appreciation_ajax' %}", { fetch("{% url 'generer_appreciation_ajax' %}", {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token }}' 'X-CSRFToken': '{{ csrf_token }}'
}, },
body: JSON.stringify({eleve: eleve, modele: selectModele.value}) body: JSON.stringify({eleve: eleve, modele: selectModele.value})
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
appreciationCell.textContent = data.appreciation; appreciationCell.textContent = data.appreciation;
updateActionButtons(); updateActionButtons();
if (typeof data.credit !== 'undefined') { if (typeof data.credit !== 'undefined') {
creditRestant.textContent = 'Crédits restants : ' + data.credit; creditRestant.textContent = 'Crédits restants : ' + data.credit;
} }
if (data.credit === 0) { if (data.credit === 0) {
creditAlert.classList.remove('d-none'); creditAlert.classList.remove('d-none');
} }
}) })
.catch(() => { .catch(() => {
appreciationCell.textContent = 'Erreur lors de la génération'; appreciationCell.textContent = 'Erreur lors de la génération';
updateActionButtons(); updateActionButtons();
}); });
}); });
}); });
setButtonState('lancer'); // Action individuelle sur bouton Bulletin
updateActionButtons(); document.querySelectorAll('.action-bulletin').forEach(btn => {
}); btn.addEventListener('click', function() {
</script> const eleve = btn.getAttribute('data-eleve');
fetch("{% url 'get_bulletin_eleve' %}", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token }}'
},
body: JSON.stringify({eleve: eleve})
})
.then(response => response.json())
.then(data => {
// Mettre le titre dans le header du modal
const modalTitle = document.getElementById('bulletinModalLabel');
if (modalTitle) {
modalTitle.textContent = `Bulletin de l'élève ${eleve}`;
}
// Mettre uniquement les appréciations dans le body
let message = '';
if (Array.isArray(data.appreciations) && data.appreciations.length > 0) {
message += data.appreciations.map(app => `<div style='margin-bottom:8px;'><strong>${app.matiere} :</strong><br>${app.appreciation}</div>`).join('');
} else {
message += '<em>Aucune appréciation trouvée dans le bulletin.</em>';
}
showBulletinModal(message);
})
.catch(() => {
const modalTitle = document.getElementById('bulletinModalLabel');
if (modalTitle) {
modalTitle.textContent = `Bulletin`;
}
showBulletinModal('<em>Erreur lors de la récupération du bulletin.</em>');
});
});
});
setButtonState('lancer');
updateActionButtons();
});
</script>
<script> <script>
// CDN jsPDF + autoTable pour export PDF // CDN jsPDF + autoTable pour export PDF
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
if (!window.jspdfLoaded) { if (!window.jspdfLoaded) {
const script1 = document.createElement('script'); const script1 = document.createElement('script');
script1.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js'; script1.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js';
script1.onload = function() { script1.onload = function() {
const script2 = document.createElement('script'); const script2 = document.createElement('script');
script2.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.8.2/jspdf.plugin.autotable.min.js'; script2.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.8.2/jspdf.plugin.autotable.min.js';
script2.onload = function() { window.jspdfLoaded = true; }; script2.onload = function() { window.jspdfLoaded = true; };
document.body.appendChild(script2); document.body.appendChild(script2);
}; };
document.body.appendChild(script1); document.body.appendChild(script1);
} }
}); });
function exportTableToPDF() { function exportTableToPDF() {
const doc = new window.jspdf.jsPDF({ orientation: 'portrait', unit: 'pt', format: 'a4' }); const doc = new window.jspdf.jsPDF({ orientation: 'portrait', unit: 'pt', format: 'a4' });
doc.text('Tableau des appréciations', 40, 40); doc.text('Tableau des appréciations', 40, 40);
// Couleurs fixes mode clair // Couleurs fixes mode clair
const headColor = [55, 90, 127]; const headColor = [55, 90, 127];
const textColor = [33, 37, 41]; const textColor = [33, 37, 41];
const bgColor = [255, 255, 255]; const bgColor = [255, 255, 255];
const borderColor = [200, 200, 200]; const borderColor = [200, 200, 200];
const table = document.getElementById('appreciations-table'); const table = document.getElementById('appreciations-table');
const rows = Array.from(table.querySelectorAll('tbody tr')) const rows = Array.from(table.querySelectorAll('tbody tr'))
.map(tr => [ .map(tr => [
tr.querySelector('.eleve')?.textContent.trim() || '', tr.querySelector('.eleve')?.textContent.trim() || '',
tr.querySelector('.appreciation')?.textContent.trim() || '' tr.querySelector('.appreciation')?.textContent.trim() || ''
]); ]);
doc.autoTable({ doc.autoTable({
head: [['Élève', 'Appréciation']], head: [['Élève', 'Appréciation']],
body: rows, body: rows,
startY: 60, startY: 60,
margin: { left: 30, right: 30 }, margin: { left: 30, right: 30 },
styles: { fontSize: 11, overflow: 'linebreak', cellWidth: 'auto', textColor: textColor, fillColor: bgColor, lineColor: borderColor }, styles: { fontSize: 11, overflow: 'linebreak', cellWidth: 'auto', textColor: textColor, fillColor: bgColor, lineColor: borderColor },
headStyles: { fillColor: headColor, textColor: textColor, lineColor: borderColor }, headStyles: { fillColor: headColor, textColor: textColor, lineColor: borderColor },
bodyStyles: { fillColor: bgColor, textColor: textColor, lineColor: borderColor }, bodyStyles: { fillColor: bgColor, textColor: textColor, lineColor: borderColor },
alternateRowStyles: { fillColor: [245, 245, 245] }, alternateRowStyles: { fillColor: [245, 245, 245] },
columnStyles: { columnStyles: {
0: { cellWidth: 'auto'}, 0: { cellWidth: 'auto'},
1: { cellWidth: 'auto' } 1: { cellWidth: 'auto' }
}, },
tableWidth: 'auto', tableWidth: 'auto',
pageBreak: 'auto', pageBreak: 'auto',
}); });
doc.save('appreciations.pdf'); doc.save('appreciations.pdf');
} }
</script> </script>
<script>
function showBulletinModal(html) {
const modalBody = document.getElementById('bulletinModalBody');
if (modalBody) {
modalBody.innerHTML = html;
const modal = new bootstrap.Modal(document.getElementById('bulletinModal'));
modal.show();
}
}
</script>
<!-- Modal Bulletin -->
<div class="modal fade" id="bulletinModal" tabindex="-1" aria-labelledby="bulletinModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" style="max-width:80vw; width:80vw;">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="bulletinModalLabel">Bulletin</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
</div>
<div class="modal-body" id="bulletinModalBody" style="font-size:1.1rem; line-height:1.5; word-break:break-word;"></div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

3
main/urls.py

@ -1,5 +1,5 @@
from django.urls import path from django.urls import path
from .views import login_view, generation, resultat_appreciations, logout_view, generer_appreciation_ajax from .views import login_view, generation, resultat_appreciations, logout_view, generer_appreciation_ajax, get_bulletin_eleve
urlpatterns = [ urlpatterns = [
path('login/', login_view, name='login'), path('login/', login_view, name='login'),
@ -7,4 +7,5 @@ urlpatterns = [
path('resultat/', resultat_appreciations, name='resultat_appreciations'), path('resultat/', resultat_appreciations, name='resultat_appreciations'),
path('logout/', logout_view, name='logout'), path('logout/', logout_view, name='logout'),
path('generer_appreciation_ajax/', generer_appreciation_ajax, name='generer_appreciation_ajax'), path('generer_appreciation_ajax/', generer_appreciation_ajax, name='generer_appreciation_ajax'),
path('get_bulletin_eleve/', get_bulletin_eleve, name='get_bulletin_eleve'),
] ]

20
main/views.py

@ -10,6 +10,7 @@ from main.models import UserCredit, Modele
import os import os
import uuid import uuid
import json import json
from django.views.decorators.csrf import csrf_exempt
def login_view(request): def login_view(request):
if request.method == 'POST': if request.method == 'POST':
@ -76,6 +77,25 @@ def generer_appreciation_ajax(request):
return JsonResponse({'appreciation': 'Crédit épuisé', 'credit': 0, 'stop': True}) return JsonResponse({'appreciation': 'Crédit épuisé', 'credit': 0, 'stop': True})
return JsonResponse({'error': 'Méthode non autorisée'}, status=405) return JsonResponse({'error': 'Méthode non autorisée'}, status=405)
@login_required(login_url='/login/')
def get_bulletin_eleve(request):
if request.method == 'POST':
data = json.loads(request.body)
eleve_nom = data.get('eleve')
appreciations_json = request.session.get('appreciations_json', [])
appreciations = []
for eleve in appreciations_json:
if eleve.get('eleve') == eleve_nom:
appreciations = eleve.get('appreciations', [])
if isinstance(appreciations, str):
try:
appreciations = json.loads(appreciations)
except Exception:
appreciations = []
break
return JsonResponse({'appreciations': appreciations})
return JsonResponse({'error': 'Méthode non autorisée'}, status=405)
def logout_view(request): def logout_view(request):
logout(request) logout(request)
return redirect('login') return redirect('login')

Loading…
Cancel
Save