Add new examination model
This commit is contained in:
parent
4fbacd3d93
commit
ad3b9bd936
5 changed files with 223 additions and 97 deletions
156
stages/admin.py
156
stages/admin.py
|
|
@ -14,7 +14,7 @@ from django.utils.safestring import mark_safe
|
|||
from .models import (
|
||||
Teacher, Option, Student, StudentFile, Section, Level, Klass, Corporation,
|
||||
CorpContact, Domain, Period, Availability, Training, Course,
|
||||
LogBookReason, LogBook, ExamEDESession, SupervisionBill
|
||||
LogBookReason, LogBook, ExamEDESession, Examination, SupervisionBill
|
||||
)
|
||||
from .views.export import OpenXMLExport
|
||||
|
||||
|
|
@ -87,6 +87,7 @@ class LogBookInline(admin.TabularInline):
|
|||
class TeacherAdmin(admin.ModelAdmin):
|
||||
list_display = ('__str__', 'abrev', 'email', 'contract', 'rate', 'total_logbook', 'archived')
|
||||
list_filter = (('archived', ArchivedListFilter), 'contract')
|
||||
search_fields = ('last_name', 'first_name', 'email')
|
||||
fields = (('civility', 'last_name', 'first_name', 'abrev'),
|
||||
('birth_date', 'email', 'ext_id'),
|
||||
('contract', 'rate', 'can_examinate', 'archived'),
|
||||
|
|
@ -102,76 +103,105 @@ class SupervisionBillInline(admin.TabularInline):
|
|||
extra = 0
|
||||
|
||||
|
||||
class ExaminationInline(admin.StackedInline):
|
||||
model = Examination
|
||||
extra = 1
|
||||
verbose_name = "Procédure de qualification"
|
||||
verbose_name_plural = "Procédures de qualification"
|
||||
autocomplete_fields = ('internal_expert', 'external_expert')
|
||||
fields = (('session', 'type_exam', 'date_exam', 'room'),
|
||||
('internal_expert', 'external_expert'),
|
||||
('mark', 'mark_acq'),
|
||||
('examination_actions'),
|
||||
('date_soutenance_mailed', 'date_confirm_received'),)
|
||||
readonly_fields = (
|
||||
'examination_actions', 'date_soutenance_mailed'
|
||||
)
|
||||
|
||||
def examination_actions(self, obj):
|
||||
missing_message = mark_safe(
|
||||
'<div class="warning">Veuillez compléter les informations '
|
||||
'd’examen (date/salle/experts) pour accéder aux boutons d’impression.</div>'
|
||||
)
|
||||
if obj and obj.student.is_ede_3():
|
||||
if obj.missing_examination_data():
|
||||
return missing_message
|
||||
else:
|
||||
return format_html(
|
||||
'<a class="button" href="{}">Courrier pour l’expert</a> '
|
||||
'<a class="button" href="{}">Mail convocation soutenance</a> '
|
||||
'<a class="button" href="{}">Indemnité au mentor</a>',
|
||||
reverse('print-expert-compens-ede', args=[obj.student.pk]),
|
||||
reverse('student-ede-convocation', args=[obj.student.pk]),
|
||||
reverse('print-mentor-compens-ede', args=[obj.student.pk]),
|
||||
)
|
||||
elif obj and obj.student.is_eds_3():
|
||||
if obj.missing_examination_data():
|
||||
return missing_message
|
||||
else:
|
||||
return format_html(
|
||||
'<a class="button" href="{}">Courrier pour l’expert</a> '
|
||||
'<a class="button" href="{}">Mail convocation soutenance</a> '
|
||||
'<a class="button" href="{}">Indemnité au mentor</a>',
|
||||
reverse('print-expert-compens-eds', args=[obj.student.pk]),
|
||||
reverse('student-eds-convocation', args=[obj.student.pk]),
|
||||
reverse('print-mentor-compens-ede', args=[obj.student.pk]),
|
||||
)
|
||||
else:
|
||||
return missing_message
|
||||
examination_actions.short_description = 'Actions pour la procédure'
|
||||
|
||||
|
||||
class StudentAdmin(admin.ModelAdmin):
|
||||
list_display = ('__str__', 'pcode', 'city', 'klass', 'archived')
|
||||
ordering = ('last_name', 'first_name')
|
||||
list_filter = (('archived', ArchivedListFilter), ('klass', KlassRelatedListFilter))
|
||||
search_fields = ('last_name', 'first_name', 'pcode', 'city', 'klass__name')
|
||||
autocomplete_fields = ('corporation', 'instructor', 'supervisor', 'mentor', 'expert', 'expert_ep')
|
||||
readonly_fields = (
|
||||
'report_sem1_sent', 'report_sem2_sent',
|
||||
'examination_actions',
|
||||
'date_soutenance_mailed', 'date_soutenance_ep_mailed'
|
||||
)
|
||||
readonly_fields = ('report_sem1_sent', 'report_sem2_sent')
|
||||
fieldsets = [
|
||||
(None, {
|
||||
'fields': (('last_name', 'first_name', 'ext_id'), ('street', 'pcode', 'city', 'district'),
|
||||
('email', 'tel', 'mobile'), ('gender', 'avs', 'birth_date'),
|
||||
('archived', 'dispense_ecg', 'dispense_eps', 'soutien_dys'),
|
||||
('klass', 'option_ase'),
|
||||
('report_sem1', 'report_sem1_sent'),
|
||||
('report_sem2', 'report_sem2_sent'),
|
||||
('corporation', 'instructor',)
|
||||
)
|
||||
}
|
||||
),
|
||||
("Examen Qualification ES", {
|
||||
'fields': (
|
||||
('last_name', 'first_name', 'ext_id'), ('street', 'pcode', 'city', 'district'),
|
||||
('email', 'tel', 'mobile'), ('gender', 'avs', 'birth_date'),
|
||||
('archived', 'dispense_ecg', 'dispense_eps', 'soutien_dys'),
|
||||
('klass', 'option_ase'),
|
||||
('report_sem1', 'report_sem1_sent'),
|
||||
('report_sem2', 'report_sem2_sent'),
|
||||
('corporation', 'instructor',)
|
||||
)}
|
||||
),
|
||||
("Procédure de qualification", {
|
||||
'classes': ['collapse'],
|
||||
'fields': (
|
||||
('session', 'date_exam', 'room'),
|
||||
('supervisor', 'supervision_attest_received'),
|
||||
('subject', 'title'),
|
||||
('training_referent', 'referent', 'mentor'),
|
||||
('internal_expert', 'expert'),
|
||||
('date_soutenance_mailed', 'date_confirm_received'),
|
||||
('examination_actions',),
|
||||
('mark', 'mark_acq'),
|
||||
)
|
||||
}),
|
||||
("Entretien professionnel ES", {
|
||||
'classes': ['collapse'],
|
||||
'fields': (
|
||||
('session_ep', 'date_exam_ep', 'room_ep'),
|
||||
('internal_expert_ep', 'expert_ep'),
|
||||
('date_soutenance_ep_mailed', 'date_confirm_ep_received'),
|
||||
('mark_ep', 'mark_ep_acq'),
|
||||
)
|
||||
}),
|
||||
]
|
||||
actions = ['archive']
|
||||
inlines = [SupervisionBillInline]
|
||||
inlines = [ExaminationInline, SupervisionBillInline]
|
||||
|
||||
def get_inline_instances(self, request, obj=None):
|
||||
# SupervisionBillInline is only adequate for EDE students
|
||||
if obj is None or not obj.klass or obj.klass.section.name != 'EDE':
|
||||
def get_inlines(self, request, obj=None):
|
||||
if obj is None:
|
||||
return []
|
||||
return super().get_inline_instances(request, obj=obj)
|
||||
inlines = super().get_inlines(request, obj=obj)
|
||||
# SupervisionBillInline is only adequate for EDE students
|
||||
if not obj.klass or obj.klass.section.name != 'EDE':
|
||||
inlines = [inl for inl in inlines if inl != SupervisionBillInline]
|
||||
if not obj.is_ede_3() and not obj.is_eds_3():
|
||||
inlines = [inl for inl in inlines if inl != ExaminationInline]
|
||||
return inlines
|
||||
|
||||
def get_fieldsets(self, request, obj=None):
|
||||
if not self.is_ede_3(obj) and not self.is_eds_3(obj):
|
||||
# Hide "Examen Qualification ES"/"Entretien professionnel ES"
|
||||
if not obj or (not obj.is_ede_3() and not obj.is_eds_3()):
|
||||
# Hide group "Procédure de qualification"
|
||||
fieldsets = deepcopy(self.fieldsets)
|
||||
fieldsets[1][1]['classes'] = ['hidden']
|
||||
fieldsets[2][1]['classes'] = ['hidden']
|
||||
return fieldsets
|
||||
return super().get_fieldsets(request, obj)
|
||||
|
||||
def is_ede_3(self, obj):
|
||||
return obj and obj.klass and obj.klass.section.name == 'EDE' and obj.klass.level.name == '3'
|
||||
|
||||
def is_eds_3(self, obj):
|
||||
return obj and obj.klass and obj.klass.section.name == 'EDS' and obj.klass.level.name == '3'
|
||||
|
||||
def archive(self, request, queryset):
|
||||
for student in queryset:
|
||||
# Save each item individually to allow for custom save() logic.
|
||||
|
|
@ -179,41 +209,6 @@ class StudentAdmin(admin.ModelAdmin):
|
|||
student.save()
|
||||
archive.short_description = "Marquer les étudiants sélectionnés comme archivés"
|
||||
|
||||
def examination_actions(self, obj):
|
||||
if self.is_ede_3(obj):
|
||||
if obj.missing_examination_data():
|
||||
return mark_safe(
|
||||
'<div class="warning">Veuillez compléter les informations '
|
||||
'd’examen (date/salle/experts) pour accéder aux boutons d’impression.</div>'
|
||||
)
|
||||
else:
|
||||
return format_html(
|
||||
'<a class="button" href="{}">Courrier pour l’expert</a> '
|
||||
'<a class="button" href="{}">Mail convocation soutenance</a> '
|
||||
'<a class="button" href="{}">Indemnité au mentor</a>',
|
||||
reverse('print-expert-compens-ede', args=[obj.pk]),
|
||||
reverse('student-ede-convocation', args=[obj.pk]),
|
||||
reverse('print-mentor-compens-ede', args=[obj.pk]),
|
||||
)
|
||||
elif self.is_eds_3(obj):
|
||||
if obj.missing_examination_data():
|
||||
return mark_safe(
|
||||
'<div class="warning">Veuillez compléter les informations '
|
||||
'd’examen (date/salle/experts) pour accéder aux boutons d’impression.</div>'
|
||||
)
|
||||
else:
|
||||
return format_html(
|
||||
'<a class="button" href="{}">Courrier pour l’expert</a> '
|
||||
'<a class="button" href="{}">Mail convocation soutenance</a> '
|
||||
'<a class="button" href="{}">Indemnité au mentor</a>',
|
||||
reverse('print-expert-compens-eds', args=[obj.pk]),
|
||||
reverse('student-eds-convocation', args=[obj.pk]),
|
||||
reverse('print-mentor-compens-ede', args=[obj.pk]),
|
||||
)
|
||||
else:
|
||||
return ''
|
||||
examination_actions.short_description = 'Actions pour les examens'
|
||||
|
||||
|
||||
class CorpContactAdmin(admin.ModelAdmin):
|
||||
list_display = ('__str__', 'corporation', 'role')
|
||||
|
|
@ -412,6 +407,7 @@ admin.site.register(Training, TrainingAdmin)
|
|||
admin.site.register(LogBookReason)
|
||||
admin.site.register(LogBook)
|
||||
admin.site.register(ExamEDESession)
|
||||
admin.site.register(Examination)
|
||||
|
||||
admin.site.unregister(Group)
|
||||
admin.site.register(Group, GroupAdmin)
|
||||
|
|
|
|||
32
stages/migrations/0026_examination.py
Normal file
32
stages/migrations/0026_examination.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('stages', '0025_section_has_stages'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Examination',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('date_exam', models.DateTimeField(blank=True, null=True)),
|
||||
('room', models.CharField(blank=True, max_length=15, verbose_name='Salle')),
|
||||
('mark', models.DecimalField(blank=True, decimal_places=2, max_digits=3, null=True, verbose_name='Note')),
|
||||
('mark_acq', models.CharField(blank=True, choices=[('non', 'Non acquis'), ('part', 'Partiellement acquis'), ('acq', 'Acquis')], max_length=5, verbose_name='Note')),
|
||||
('date_soutenance_mailed', models.DateTimeField(blank=True, null=True, verbose_name='Convoc. env.')),
|
||||
('date_confirm_received', models.DateTimeField(blank=True, null=True, verbose_name='Récept. confirm')),
|
||||
('external_expert', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='stages.CorpContact', verbose_name='Expert externe')),
|
||||
('internal_expert', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='stages.Teacher', verbose_name='Expert interne')),
|
||||
('session', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='stages.ExamEDESession', verbose_name='Session')),
|
||||
('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='stages.Student')),
|
||||
('type_exam', models.CharField(choices=[('exam', 'Examen qualification'), ('entr', 'Entretien professionnel')], default='', max_length=10, verbose_name='Type')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Examen',
|
||||
},
|
||||
),
|
||||
]
|
||||
35
stages/migrations/0027_migrate_exams.py
Normal file
35
stages/migrations/0027_migrate_exams.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
from django.db import migrations
|
||||
|
||||
|
||||
def migrate_exams(apps, schema_editor):
|
||||
Student = apps.get_model('stages', 'Student')
|
||||
Examination = apps.get_model('stages', 'Examination')
|
||||
|
||||
for student in Student.objects.all():
|
||||
if student.session or student.date_exam:
|
||||
Examination.objects.create(
|
||||
student=student, type_exam='exam',
|
||||
session=student.session, date_exam=student.date_exam,
|
||||
room=student.room, mark=student.mark, mark_acq=student.mark_acq,
|
||||
internal_expert=student.internal_expert, external_expert=student.expert,
|
||||
date_soutenance_mailed=student.date_soutenance_mailed,
|
||||
date_confirm_received=student.date_confirm_received,
|
||||
)
|
||||
if student.session_ep or student.date_exam_ep:
|
||||
Examination.objects.create(
|
||||
student=student, type_exam='entre',
|
||||
session=student.session_ep, date_exam=student.date_exam_ep,
|
||||
room=student.room_ep, mark=student.mark_ep, mark_acq=student.mark_ep_acq,
|
||||
internal_expert=student.internal_expert_ep, external_expert=student.expert_ep,
|
||||
date_soutenance_mailed=student.date_soutenance_ep_mailed,
|
||||
date_confirm_received=student.date_confirm_ep_received,
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('stages', '0026_examination'),
|
||||
]
|
||||
|
||||
operations = [migrations.RunPython(migrate_exams)]
|
||||
|
|
@ -406,6 +406,12 @@ class Student(models.Model):
|
|||
return user.has_perm('stages.change_student') or user.teacher == self.klass.teacher
|
||||
return False
|
||||
|
||||
def is_ede_3(self):
|
||||
return self.klass and self.klass.section.name == 'EDE' and self.klass.level.name == '3'
|
||||
|
||||
def is_eds_3(self):
|
||||
return self.klass and self.klass.section.name == 'EDS' and self.klass.level.name == '3'
|
||||
|
||||
def missing_examination_data(self):
|
||||
missing = []
|
||||
if not self.date_exam:
|
||||
|
|
@ -431,6 +437,53 @@ class Student(models.Model):
|
|||
return missing
|
||||
|
||||
|
||||
class Examination(models.Model):
|
||||
ACQ_MARK_CHOICES = (
|
||||
('non', 'Non acquis'),
|
||||
('part', 'Partiellement acquis'),
|
||||
('acq', 'Acquis'),
|
||||
)
|
||||
TYPE_EXAM_CHOICES = (
|
||||
('exam', 'Examen qualification'),
|
||||
('entr', 'Entretien professionnel'),
|
||||
)
|
||||
|
||||
student = models.ForeignKey(Student, on_delete=models.CASCADE)
|
||||
session = models.ForeignKey(
|
||||
ExamEDESession, null=True, blank=True, on_delete=models.SET_NULL, verbose_name='Session',
|
||||
)
|
||||
type_exam = models.CharField("Type", max_length=10, choices=TYPE_EXAM_CHOICES)
|
||||
date_exam = models.DateTimeField(blank=True, null=True)
|
||||
room = models.CharField('Salle', max_length=15, blank=True)
|
||||
mark = models.DecimalField('Note', max_digits=3, decimal_places=2, blank=True, null=True)
|
||||
mark_acq = models.CharField('Note', max_length=5, choices=ACQ_MARK_CHOICES, blank=True)
|
||||
internal_expert = models.ForeignKey(
|
||||
Teacher, verbose_name='Expert interne',
|
||||
null=True, blank=True, on_delete=models.SET_NULL
|
||||
)
|
||||
external_expert = models.ForeignKey(
|
||||
'CorpContact', verbose_name='Expert externe',
|
||||
null=True, blank=True, on_delete=models.SET_NULL
|
||||
)
|
||||
date_soutenance_mailed = models.DateTimeField("Convoc. env.", blank=True, null=True)
|
||||
date_confirm_received = models.DateTimeField("Récept. confirm", blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Examen"
|
||||
|
||||
def missing_examination_data(self):
|
||||
missing = []
|
||||
if not self.date_exam:
|
||||
missing.append("La date d’examen est manquante")
|
||||
if not self.room:
|
||||
missing.append("La salle d’examen n’est pas définie")
|
||||
if not self.external_expert:
|
||||
missing.append("L’expert externe n’est pas défini")
|
||||
if not self.internal_expert:
|
||||
missing.append("L’expert interne n’est pas défini")
|
||||
return missing
|
||||
|
||||
|
||||
class StudentFile(models.Model):
|
||||
student = models.ForeignKey(Student, on_delete=models.CASCADE)
|
||||
fichier = models.FileField(upload_to='etudiants')
|
||||
|
|
|
|||
|
|
@ -418,11 +418,9 @@ def ortra_export(request):
|
|||
def export_qualification(request, section='ede'):
|
||||
headers = [
|
||||
'Classe', 'Etudiant-e',
|
||||
'Référent pratique', 'Résumé TD', 'Ens. référent', 'dernier RDV',
|
||||
'Référent pratique', 'Titre TD', 'Résumé TD', 'Ens. référent',
|
||||
'Mentor',
|
||||
'Session',
|
||||
'Titre TD',
|
||||
'Exp_int.',
|
||||
'Session', 'Type', 'Exp_int.',
|
||||
'Expert ext. Civilité', 'Expert ext. Nom', 'Expert ext. Adresse', 'Expert ext. Localité',
|
||||
'Date', 'Salle', 'Note',
|
||||
]
|
||||
|
|
@ -432,29 +430,41 @@ def export_qualification(request, section='ede'):
|
|||
export.write_line(headers, bold=True)
|
||||
|
||||
# Data
|
||||
empty_values = [''] * 7
|
||||
for student in Student.objects.filter(klass__name__startswith='3%s' % section.upper(), archived=False
|
||||
).select_related('klass', 'referent', 'training_referent', 'mentor', 'expert', 'internal_expert',
|
||||
).prefetch_related('examination_set'
|
||||
).order_by('klass__name', 'last_name'):
|
||||
values = [
|
||||
stud_values = [
|
||||
student.klass.name,
|
||||
student.full_name,
|
||||
student.training_referent.full_name if student.training_referent else '',
|
||||
student.title,
|
||||
student.subject,
|
||||
student.referent.full_name if student.referent else '',
|
||||
student.last_appointment,
|
||||
student.mentor.full_name if student.mentor else '',
|
||||
str(student.session),
|
||||
student.title,
|
||||
student.internal_expert.full_name if student.internal_expert else '',
|
||||
student.expert.civility if student.expert else '',
|
||||
student.expert.full_name if student.expert else '',
|
||||
student.expert.street if student.expert else '',
|
||||
student.expert.pcode_city if student.expert else '',
|
||||
student.date_exam,
|
||||
student.room,
|
||||
student.mark,
|
||||
]
|
||||
export.write_line(values)
|
||||
lines_exported = 0
|
||||
for exam in student.examination_set.all():
|
||||
exam_values = [
|
||||
str(exam.session),
|
||||
exam.get_type_exam_display(),
|
||||
exam.internal_expert.full_name if exam.internal_expert else '',
|
||||
exam.external_expert.civility if exam.external_expert else '',
|
||||
exam.external_expert.full_name if exam.external_expert else '',
|
||||
exam.external_expert.street if exam.external_expert else '',
|
||||
exam.external_expert.pcode_city if exam.external_expert else '',
|
||||
exam.date_exam,
|
||||
exam.room,
|
||||
exam.mark,
|
||||
]
|
||||
if lines_exported == 0:
|
||||
export.write_line(stud_values + exam_values)
|
||||
else:
|
||||
export.write_line(empty_values + exam_values)
|
||||
lines_exported += 1
|
||||
if lines_exported == 0:
|
||||
export.write_line(stud_values)
|
||||
|
||||
return export.get_http_response(export_name)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue