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. 15
      main/models.py
  4. 38
      main/templates/export_pdf.js
  5. 73
      main/templates/resultat_appreciations.html
  6. 24
      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',)

15
main/models.py

@ -7,3 +7,18 @@ class UserProfile(models.Model):
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');
}

73
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">
<div>
<label for="modele-select" class="form-label">Choisir un modèle</label> <label for="modele-select" class="form-label">Choisir un modèle</label>
<select id="modele-select" class="form-select"> <select id="modele-select" class="form-select">
<option value="ft:gpt-4o-2024-08-06:personal:app-gen-gangneux2:AYJecsON">Modèle Gangneux</option> {% for modele in modeles %}
<option value="gpt-4.1-mini">GPT-4.1 mini</option> <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> </select>
</div> </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="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>
<table class="table table-bordered" id="appreciations-table"> <table class="table table-bordered" id="appreciations-table">
<thead> <thead>
<tr> <tr>
@ -126,6 +146,8 @@ body, .bg-light {
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 creditAlert = document.getElementById('credit-alert');
let rows = getRowsSansAppreciation(); let rows = getRowsSansAppreciation();
function setButtonState(state) { function setButtonState(state) {
@ -151,7 +173,7 @@ body, .bg-light {
} }
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...';
@ -166,6 +188,17 @@ body, .bg-light {
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
appreciationCell.textContent = data.appreciation; appreciationCell.textContent = data.appreciation;
if (typeof data.credit !== 'undefined') {
creditRestant.textContent = 'Crédits restants : ' + data.credit;
}
if (data.stop) {
stopGeneration = true;
setButtonState('lancer');
if (data.credit === 0) {
creditAlert.classList.remove('d-none');
}
return;
}
traiterLignes(); traiterLignes();
}) })
.catch(() => { .catch(() => {
@ -175,6 +208,13 @@ body, .bg-light {
} }
btnToggle.addEventListener('click', function() { btnToggle.addEventListener('click', function() {
const credit = parseInt(creditRestant.textContent.replace(/\D/g, ''));
if (credit === 0) {
creditAlert.classList.remove('d-none');
return;
} else {
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');
@ -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>

24
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')
user = request.user
# 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) appreciation = generer_appreciation_pour_eleve(eleve, request.session.get('appreciations_json', []), modele)
return JsonResponse({'appreciation': appreciation}) # 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