Browse Source

v1.0.0

main
scayac 6 months ago
parent
commit
e2b4b24cd6
  1. 2
      ia_prof/settings.py
  2. 18
      main/admin.py
  3. 17
      main/models.py
  4. 38
      main/templates/export_pdf.js
  5. 211
      main/templates/resultat_appreciations.html
  6. 26
      main/views.py

2
ia_prof/settings.py

@ -113,4 +113,4 @@ STATIC_URL = '/static/'
# https://docs.djangoproject.com/en/4.x/ref/settings/#default-auto-field # https://docs.djangoproject.com/en/4.x/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
OPENAI_API_KEY = "sk-proj-hrV9Se3D3Vn6ro66AoMFT3BlbkFJ3kgB6P9xQFpcaymQQHFI" OPENAI_API_KEY = "your-openai-api-key"

18
main/admin.py

@ -1,4 +1,18 @@
from django.contrib import admin from django.contrib import admin
from .models import UserProfile from .models import UserProfile, Modele, UserCredit
admin.site.register(UserProfile) @admin.register(Modele)
class ModeleAdmin(admin.ModelAdmin):
list_display = ('nom', 'code', 'actif')
list_editable = ('code', 'actif')
search_fields = ('nom', 'code')
list_filter = ('actif',)
@admin.register(UserCredit)
class UserCreditAdmin(admin.ModelAdmin):
list_display = ('user', 'credit')
search_fields = ('user__username',)
@admin.register(UserProfile)
class UserProfileAdmin(admin.ModelAdmin):
list_display = ('user',)

17
main/models.py

@ -6,4 +6,19 @@ class UserProfile(models.Model):
# Ajoutez d'autres champs personnalisés ici si besoin # Ajoutez d'autres champs personnalisés ici si besoin
def __str__(self): def __str__(self):
return self.user.username return self.user.username
class Modele(models.Model):
nom = models.CharField(max_length=255)
code = models.CharField(max_length=255, unique=True, default="gpt-4.1-mini")
actif = models.BooleanField(default=True)
def __str__(self):
return self.nom
class UserCredit(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='credit')
credit = models.IntegerField(default=10)
def __str__(self):
return f"{self.user.username} - Crédit: {self.credit}"

38
main/templates/export_pdf.js

@ -0,0 +1,38 @@
// CDN jsPDF + autoTable pour export PDF
document.addEventListener('DOMContentLoaded', function() {
if (!window.jspdfLoaded) {
const script1 = document.createElement('script');
script1.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js';
script1.onload = function() {
const script2 = document.createElement('script');
script2.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.8.2/jspdf.plugin.autotable.min.js';
script2.onload = function() { window.jspdfLoaded = true; };
document.body.appendChild(script2);
};
document.body.appendChild(script1);
}
});
function exportTableToPDF() {
if (!window.jspdfLoaded) {
alert('Les librairies PDF ne sont pas encore chargées. Veuillez réessayer dans quelques secondes.');
return;
}
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
doc.text('Tableau des appréciations', 14, 14);
const table = document.getElementById('appreciations-table');
const rows = Array.from(table.querySelectorAll('tbody tr'))
.map(tr => [
tr.querySelector('.eleve')?.textContent.trim() || '',
tr.querySelector('.appreciation')?.textContent.trim() || ''
]);
doc.autoTable({
head: [['Élève', 'Appréciation']],
body: rows,
startY: 20,
styles: { fontSize: 10, cellWidth: 'wrap' },
headStyles: { fillColor: [55, 90, 127] }
});
doc.save('appreciations.pdf');
}

211
main/templates/resultat_appreciations.html

@ -64,12 +64,17 @@ body, .bg-light {
[class*="bg-"] { [class*="bg-"] {
background-color: inherit !important; background-color: inherit !important;
} }
#credit-restant {
background-color: #ffe082 !important;
color: #23272b !important;
border: 1px solid #444c56 !important;
}
} }
</style> </style>
</head> </head>
<body class="bg-light"> <body class="bg-light">
<nav class="navbar navbar-expand navbar-dark bg-dark"> <nav class="navbar navbar-expand navbar-dark bg-dark">
<a class="navbar-brand ps-3" href="#">IAProf</a> <a class="navbar-brand ps-3" href="/generation/">IAProf</a>
<ul class="navbar-nav ms-auto me-3"> <ul class="navbar-nav ms-auto me-3">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/logout/">Se déconnecter</a> <a class="nav-link" href="/logout/">Se déconnecter</a>
@ -85,14 +90,29 @@ body, .bg-light {
<h2 class="m-0 font-weight-bold text-primary">Génération des appréciations</h2> <h2 class="m-0 font-weight-bold text-primary">Génération des appréciations</h2>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="mb-3"> <div class="mb-3 d-flex justify-content-between align-items-center">
<label for="modele-select" class="form-label">Choisir un modèle</label> <div>
<select id="modele-select" class="form-select"> <label for="modele-select" class="form-label">Choisir un modèle</label>
<option value="ft:gpt-4o-2024-08-06:personal:app-gen-gangneux2:AYJecsON">Modèle Gangneux</option> <select id="modele-select" class="form-select">
<option value="gpt-4.1-mini">GPT-4.1 mini</option> {% for modele in modeles %}
</select> <option value="{{ modele.code }}" {% if modele.code == 'gpt-4.1-mini' %}selected{% endif %}>{{ modele.nom }}</option>
{% endfor %}
{% if not modeles or not modeles|dictsort:'code'|length %}
<option value="gpt-4.1-mini" selected>gpt-4.1-mini</option>
{% endif %}
</select>
</div>
<div>
<span class="badge bg-info text-dark" id="credit-restant">Crédits restants : {{ credit }}</span>
</div>
</div>
<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="export-pdf" class="btn btn-secondary mb-3" type="button">Exporter en PDF</button>
</div>
<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.
</div> </div>
<button id="generation-toggle" class="btn btn-primary mb-3">Lancer la génération</button>
<table class="table table-bordered" id="appreciations-table"> <table class="table table-bordered" id="appreciations-table">
<thead> <thead>
<tr> <tr>
@ -116,78 +136,98 @@ body, .bg-light {
</tbody> </tbody>
</table> </table>
<script> <script>
let stopGeneration = false; let stopGeneration = false;
let enCours = false; let enCours = false;
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');
let rows = getRowsSansAppreciation(); const creditRestant = document.getElementById('credit-restant');
const creditAlert = document.getElementById('credit-alert');
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();
if (rows.length === 0 || stopGeneration) { if (rows.length === 0 || stopGeneration) {
enCours = false; enCours = false;
if (stopGeneration) setButtonState('continuer'); if (stopGeneration) setButtonState('continuer');
else setButtonState('lancer'); else setButtonState('lancer');
return; return;
} }
enCours = true; enCours = true;
setButtonState('arreter'); setButtonState('arreter');
const row = rows[0]; // Toujours traiter la première ligne sans appréciation 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;
traiterLignes(); if (typeof data.credit !== 'undefined') {
}) creditRestant.textContent = 'Crédits restants : ' + data.credit;
.catch(() => { }
appreciationCell.textContent = 'Erreur lors de la génération'; if (data.stop) {
traiterLignes(); stopGeneration = true;
}); setButtonState('lancer');
} if (data.credit === 0) {
creditAlert.classList.remove('d-none');
}
return;
}
traiterLignes();
})
.catch(() => {
appreciationCell.textContent = 'Erreur lors de la génération';
traiterLignes();
});
}
btnToggle.addEventListener('click', function() { btnToggle.addEventListener('click', function() {
if (!enCours && (btnToggle.textContent === 'Lancer la génération' || btnToggle.textContent === 'Continuer la génération')) { const credit = parseInt(creditRestant.textContent.replace(/\D/g, ''));
stopGeneration = false; if (credit === 0) {
setButtonState('arreter'); creditAlert.classList.remove('d-none');
traiterLignes(); return;
} else if (enCours && btnToggle.textContent === 'Arrêter la génération') { } else {
stopGeneration = true; creditAlert.classList.add('d-none');
setButtonState('continuer'); }
} if (!enCours && (btnToggle.textContent === 'Lancer la génération' || btnToggle.textContent === 'Continuer la génération')) {
}); stopGeneration = false;
setButtonState('arreter');
traiterLignes();
} else if (enCours && btnToggle.textContent === 'Arrêter la génération') {
stopGeneration = true;
setButtonState('continuer');
}
});
setButtonState('lancer'); setButtonState('lancer');
}); });
</script> </script>
</div> </div>
</div> </div>
</div> </div>
@ -195,5 +235,28 @@ body, .bg-light {
</div> </div>
</div> </div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.8.2/jspdf.plugin.autotable.min.js"></script>
<script>
function exportTableToPDF() {
const doc = new window.jspdf.jsPDF();
doc.text('Tableau des appréciations', 14, 14);
const table = document.getElementById('appreciations-table');
const rows = Array.from(table.querySelectorAll('tbody tr'))
.map(tr => [
tr.querySelector('.eleve')?.textContent.trim() || '',
tr.querySelector('.appreciation')?.textContent.trim() || ''
]);
doc.autoTable({
head: [['Élève', 'Appréciation']],
body: rows,
startY: 20,
styles: { fontSize: 10, cellWidth: 'wrap' },
headStyles: { fillColor: [55, 90, 127] }
});
doc.save('appreciations.pdf');
}
document.getElementById('export-pdf').addEventListener('click', exportTableToPDF);
</script>
</body> </body>
</html> </html>

26
main/views.py

@ -5,6 +5,8 @@ from django.conf import settings
from main.openAIAppreciations import * from main.openAIAppreciations import *
from django.urls import reverse from django.urls import reverse
from django.http import JsonResponse from django.http import JsonResponse
from django.contrib.auth.models import User
from main.models import UserCredit, Modele
import os import os
import uuid import uuid
import json import json
@ -44,7 +46,15 @@ def generation(request):
@login_required(login_url='/login/') @login_required(login_url='/login/')
def resultat_appreciations(request): def resultat_appreciations(request):
appreciations_result = request.session.get('appreciations_json', []) appreciations_result = request.session.get('appreciations_json', [])
return render(request, 'resultat_appreciations.html', {'appreciations_json': appreciations_result}) credit = 0
if hasattr(request.user, 'credit'):
credit = request.user.credit.credit
modeles = Modele.objects.filter(actif=True)
return render(request, 'resultat_appreciations.html', {
'appreciations_json': appreciations_result,
'credit': credit,
'modeles': modeles
})
@login_required(login_url='/login/') @login_required(login_url='/login/')
def generer_appreciation_ajax(request): def generer_appreciation_ajax(request):
@ -52,8 +62,18 @@ def generer_appreciation_ajax(request):
data = json.loads(request.body) data = json.loads(request.body)
eleve = data.get('eleve') eleve = data.get('eleve')
modele = data.get('modele') modele = data.get('modele')
appreciation = generer_appreciation_pour_eleve(eleve, request.session.get('appreciations_json', []), modele) user = request.user
return JsonResponse({'appreciation': appreciation}) # Vérifier le crédit
if hasattr(user, 'credit') and user.credit.credit > 0:
appreciation = generer_appreciation_pour_eleve(eleve, request.session.get('appreciations_json', []), modele)
# Décrémenter le crédit
user.credit.credit -= 1
user.credit.save()
credit = user.credit.credit
stop = credit == 0
return JsonResponse({'appreciation': appreciation, 'credit': credit, 'stop': stop})
else:
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)
def logout_view(request): def logout_view(request):

Loading…
Cancel
Save