Remove exam fields on Student (replaced by Examination)

This commit is contained in:
Claude Paroz 2020-04-21 15:29:28 +02:00
parent 1b0b759c59
commit fa519cddff
10 changed files with 189 additions and 160 deletions

View file

@ -131,8 +131,8 @@ class ExaminationInline(admin.StackedInline):
'<a class="button" href="{}">Courrier pour lexpert</a>&nbsp;'
'<a class="button" href="{}">Mail convocation soutenance</a>&nbsp;'
'<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-expert-compens-ede', args=[obj.pk]),
reverse('student-ede-convocation', args=[obj.pk]),
reverse('print-mentor-compens-ede', args=[obj.student.pk]),
)
elif obj and obj.student.is_eds_3():
@ -143,8 +143,8 @@ class ExaminationInline(admin.StackedInline):
'<a class="button" href="{}">Courrier pour lexpert</a>&nbsp;'
'<a class="button" href="{}">Mail convocation soutenance</a>&nbsp;'
'<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-expert-compens-eds', args=[obj.pk]),
reverse('student-eds-convocation', args=[obj.pk]),
reverse('print-mentor-compens-ede', args=[obj.student.pk]),
)
else:
@ -157,7 +157,7 @@ class StudentAdmin(admin.ModelAdmin):
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')
autocomplete_fields = ('corporation', 'instructor', 'supervisor', 'mentor')
readonly_fields = ('report_sem1_sent', 'report_sem2_sent')
fieldsets = [
(None, {

View file

@ -0,0 +1,84 @@
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('stages', '0028_remove_last_appointment'),
]
operations = [
migrations.AlterModelOptions(
name='examedesession',
options={'ordering': ['-year', 'season'], 'verbose_name': 'Session dexamen'},
),
migrations.RemoveField(
model_name='student',
name='date_confirm_ep_received',
),
migrations.RemoveField(
model_name='student',
name='date_confirm_received',
),
migrations.RemoveField(
model_name='student',
name='date_exam',
),
migrations.RemoveField(
model_name='student',
name='date_exam_ep',
),
migrations.RemoveField(
model_name='student',
name='date_soutenance_ep_mailed',
),
migrations.RemoveField(
model_name='student',
name='date_soutenance_mailed',
),
migrations.RemoveField(
model_name='student',
name='expert_ep',
),
migrations.RemoveField(
model_name='student',
name='internal_expert',
),
migrations.RemoveField(
model_name='student',
name='internal_expert_ep',
),
migrations.RemoveField(
model_name='student',
name='mark',
),
migrations.RemoveField(
model_name='student',
name='mark_acq',
),
migrations.RemoveField(
model_name='student',
name='mark_ep',
),
migrations.RemoveField(
model_name='student',
name='mark_ep_acq',
),
migrations.RemoveField(
model_name='student',
name='room',
),
migrations.RemoveField(
model_name='student',
name='room_ep',
),
migrations.RemoveField(
model_name='student',
name='session',
),
migrations.RemoveField(
model_name='student',
name='session_ep',
),
]

View file

@ -252,7 +252,8 @@ class ExamEDESession(models.Model):
season = models.CharField('saison', max_length=10)
class Meta:
verbose_name = "Session dexamen EDE"
verbose_name = "Session dexamen"
ordering = ['-year', 'season']
def __str__(self):
return '{0} {1}'.format(self.year, self.season)
@ -315,36 +316,6 @@ class Student(models.Model):
on_delete=models.SET_NULL, verbose_name='Référent de PP')
referent = models.ForeignKey(Teacher, null=True, blank=True, related_name='rel_referent',
on_delete=models.SET_NULL, verbose_name='Référent avant-projet')
internal_expert = models.ForeignKey(Teacher, related_name='rel_internal_expert', verbose_name='Expert interne',
null=True, blank=True, on_delete=models.SET_NULL)
session = models.ForeignKey(
ExamEDESession, null=True, blank=True, on_delete=models.SET_NULL, related_name='students_es',
)
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)
date_soutenance_mailed = models.DateTimeField("Convoc. env.", blank=True, null=True)
date_confirm_received = models.DateTimeField("Récept. confirm", blank=True, null=True)
# ============== Fields for Entretien professionnel =========
session_ep = models.ForeignKey(
ExamEDESession, null=True, blank=True, on_delete=models.SET_NULL, verbose_name='Session EP',
related_name='students_ep',
)
date_exam_ep = models.DateTimeField("Date exam. EP", blank=True, null=True)
room_ep = models.CharField('Salle EP', max_length=15, blank=True)
mark_ep = models.DecimalField('Note EP', max_digits=3, decimal_places=2, blank=True, null=True)
mark_ep_acq = models.CharField('Note EP', max_length=5, choices=ACQ_MARK_CHOICES, blank=True)
internal_expert_ep = models.ForeignKey(
Teacher, related_name='rel_internal_expert_ep', verbose_name='Expert interne EP',
null=True, blank=True, on_delete=models.SET_NULL
)
expert_ep = models.ForeignKey(
'CorpContact', related_name='rel_expert_ep', verbose_name='Expert externe EP',
null=True, blank=True, on_delete=models.SET_NULL
)
date_soutenance_ep_mailed = models.DateTimeField("Convoc. env.", blank=True, null=True)
date_confirm_ep_received = models.DateTimeField("Récept. confirm", blank=True, null=True)
# ===============
mc_comment = models.TextField("Commentaires", blank=True)
@ -411,30 +382,6 @@ class Student(models.Model):
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:
missing.append("La date dexamen est manquante")
if not self.room:
missing.append("La salle dexamen nest pas définie")
if not self.expert:
missing.append("Lexpert externe nest pas défini")
if not self.internal_expert:
missing.append("Lexpert interne nest pas défini")
return missing
def missing_examination_ep_data(self):
missing = []
if not self.date_exam_ep:
missing.append("La date dexamen est manquante")
if not self.room_ep:
missing.append("La salle dexamen nest pas définie")
if not self.expert_ep:
missing.append("Lexpert externe nest pas défini")
if not self.internal_expert_ep:
missing.append("Lexpert interne nest pas défini")
return missing
class Examination(models.Model):
ACQ_MARK_CHOICES = (

View file

@ -335,7 +335,7 @@ class CompensationForm:
self.story.append(t)
self.story.append(Spacer(0, 0.5 * cm))
def add_accounting_stamp(self, mandat=None):
def add_accounting_stamp(self, student, mandat=None):
account = otp = total = ''
if mandat == self.EXPERT_MANDAT:
account = self.EXPERT_ACCOUNT
@ -343,9 +343,9 @@ class CompensationForm:
account = self.MENTOR_ACCOUNT
total = '500.-'
if self.student.klass.is_Ede_pe():
if student.klass.is_Ede_pe():
otp = self.OTP_EDE_PE_OTP
elif self.student.klass.is_Ede_ps():
elif student.klass.is_Ede_ps():
otp = self.OTP_EDE_PS_OTP
self.story.append((Paragraph(self.points * 2, style_normal)))
@ -433,8 +433,8 @@ class ExpertEdeLetterPdf(CompensationForm, EpcBaseDocTemplate):
<br/><br/><br/>
"""
def __init__(self, out, student):
self.student = student
def __init__(self, out, exam):
self.exam = exam
super().__init__(out)
self.addPageTemplates([
PageTemplate(id='FirstPage', frames=[self.page_frame], onPage=self.header),
@ -443,10 +443,10 @@ class ExpertEdeLetterPdf(CompensationForm, EpcBaseDocTemplate):
def exam_data(self):
return {
'expert': self.student.expert,
'internal_expert': self.student.internal_expert,
'date_exam': self.student.date_exam,
'room': self.student.room,
'expert': self.exam.external_expert,
'internal_expert': self.exam.internal_expert,
'date_exam': self.exam.date_exam,
'room': self.exam.room,
}
def produce(self):
@ -471,7 +471,7 @@ class ExpertEdeLetterPdf(CompensationForm, EpcBaseDocTemplate):
title_lower=self.title.lower(),
expert_civility=exam_data['expert'].civility,
expert_accord=exam_data['expert'].adjective_ending,
student_civility_full_name=self.student.civility_full_name,
student_civility_full_name=self.exam.student.civility_full_name,
), style_normal))
date_text = "<br/>{0} à l'Ecole Santé-social Pierre-Coullery, salle {1}<br/><br/>"
@ -507,7 +507,7 @@ class ExpertEdeLetterPdf(CompensationForm, EpcBaseDocTemplate):
self.story.append(Paragraph(
"Mandat: Soutenance de {0} {1}, classe {2}".format(
self.student.civility, self.student.full_name, self.student.klass
self.exam.student.civility, self.exam.student.full_name, self.exam.student.klass
), style_normal
))
self.story.append(Paragraph(
@ -515,7 +515,7 @@ class ExpertEdeLetterPdf(CompensationForm, EpcBaseDocTemplate):
))
self.story.append(Spacer(0, 2 * cm))
self.add_accounting_stamp(self.EXPERT_MANDAT)
self.add_accounting_stamp(self.exam.student, self.EXPERT_MANDAT)
self.build(self.story)
@ -556,7 +556,7 @@ class MentorCompensationPdfForm(CompensationForm, EpcBaseDocTemplate):
))
self.story.append(Spacer(0, 3 * cm))
self.add_accounting_stamp(self.MENTOR_MANDAT)
self.add_accounting_stamp(self.student, self.MENTOR_MANDAT)
self.build(self.story)

View file

@ -12,7 +12,7 @@ from django.utils.html import escape
from candidats.models import Candidate
from .models import (
Level, Domain, Section, Klass, Option, Period, Student, Corporation, Availability,
CorpContact, Teacher, Training, Course,
CorpContact, Teacher, Training, Course, Examination, ExamEDESession,
)
from .utils import school_year
@ -211,23 +211,24 @@ class StagesTests(TestCase):
def test_send_ede_convocation(self):
st = Student.objects.get(first_name="Albin")
exam = Examination.objects.create(student=st, session=ExamEDESession.objects.create(year=2020, season='1'))
self.client.login(username='me', password='mepassword')
url = reverse('student-ede-convocation', args=[st.pk])
url = reverse('student-ede-convocation', args=[exam.pk])
response = self.client.get(url, follow=True)
for err in ("La date dexamen est manquante",
"La salle dexamen nest pas définie",
"Lexpert externe nest pas défini",
"Lexpert interne nest pas défini"):
self.assertContains(response, err)
st.date_exam = datetime(2018, 6, 28, 12, 00)
st.room = "B123"
st.expert = CorpContact.objects.get(last_name="Horner")
st.internal_expert = Teacher.objects.get(last_name="Caux")
st.save()
exam.date_exam = datetime(2018, 6, 28, 12, 00)
exam.room = "B123"
exam.external_expert = CorpContact.objects.get(last_name="Horner")
exam.internal_expert = Teacher.objects.get(last_name="Caux")
exam.save()
response = self.client.get(url, follow=True)
self.assertContains(response, "Lexpert externe na pas de courriel valide !")
st.expert.email = "horner@example.org"
st.expert.save()
exam.external_expert.email = "horner@example.org"
exam.external_expert.save()
response = self.client.get(url)
expected_message = """ Albin Dupond,
Madame Julie Caux,
@ -261,8 +262,8 @@ tél. 032 886 33 00
'sender': 'me@example.org',
})
self.assertEqual(len(mail.outbox), 1)
st.refresh_from_db()
self.assertIsNotNone(st.date_soutenance_mailed)
exam.refresh_from_db()
self.assertIsNotNone(exam.date_soutenance_mailed)
def test_send_eds_convocation(self):
klass = Klass.objects.create(
@ -272,9 +273,10 @@ tél. 032 886 33 00
first_name="Laurent", last_name="Hots", birth_date="1994-07-12",
pcode="2000", city="Neuchâtel", klass=klass
)
exam = Examination.objects.create(student=st, session=ExamEDESession.objects.create(year=2020, season='1'))
self.client.login(username='me', password='mepassword')
url = reverse('student-eds-convocation', args=[st.pk])
url = reverse('student-eds-convocation', args=[exam.pk])
response = self.client.get(url, follow=True)
for err in ("Létudiant-e na pas de courriel valide",
"La date dexamen est manquante",
@ -283,15 +285,16 @@ tél. 032 886 33 00
"Lexpert interne nest pas défini"):
self.assertContains(response, err)
st.email = 'hots@example.org'
st.date_exam = datetime(2018, 6, 28, 12, 00)
st.room = "B123"
st.expert = CorpContact.objects.get(last_name="Horner")
st.internal_expert = Teacher.objects.get(last_name="Caux")
st.save()
exam.date_exam = datetime(2018, 6, 28, 12, 00)
exam.room = "B123"
exam.external_expert = CorpContact.objects.get(last_name="Horner")
exam.internal_expert = Teacher.objects.get(last_name="Caux")
exam.save()
response = self.client.get(url, follow=True)
self.assertContains(response, "Lexpert externe na pas de courriel valide !")
st.expert.email = "horner@example.org"
st.expert.save()
exam.external_expert.email = "horner@example.org"
exam.external_expert.save()
response = self.client.get(url)
expected_message = """ Laurent Hots,
Madame Julie Caux,
@ -325,21 +328,22 @@ tél. 032 886 33 00
'sender': 'me@example.org',
})
self.assertEqual(len(mail.outbox), 1)
st.refresh_from_db()
self.assertIsNotNone(st.date_soutenance_mailed)
exam.refresh_from_db()
self.assertIsNotNone(exam.date_soutenance_mailed)
def test_print_ede_compensation_forms(self):
st = Student.objects.get(first_name="Albin")
url = reverse('print-expert-compens-ede', args=[st.pk])
exam = Examination.objects.create(student=st, session=ExamEDESession.objects.create(year=2020, season='1'))
url = reverse('print-expert-compens-ede', args=[exam.pk])
self.client.login(username='me', password='mepassword')
response = self.client.get(url, follow=True)
self.assertContains(response, "Toutes les informations ne sont pas disponibles")
st.expert = CorpContact.objects.get(last_name="Horner")
st.internal_expert = Teacher.objects.get(last_name="Caux")
st.date_exam = datetime(2018, 6, 28, 12, 00)
st.room = "B123"
st.save()
exam.external_expert = CorpContact.objects.get(last_name="Horner")
exam.internal_expert = Teacher.objects.get(last_name="Caux")
exam.date_exam = datetime(2018, 6, 28, 12, 00)
exam.room = "B123"
exam.save()
self.client.login(username='me', password='mepassword')
response = self.client.get(url, follow=True)
self.assertEqual(
@ -349,8 +353,8 @@ tél. 032 886 33 00
self.assertEqual(response['Content-Type'], 'application/pdf')
self.assertGreater(int(response['Content-Length']), 1000)
# Expert without corporation
st.expert = CorpContact.objects.create(first_name='James', last_name='Bond')
st.save()
exam.external_expert = CorpContact.objects.create(first_name='James', last_name='Bond')
exam.save()
response = self.client.get(url, follow=True)
self.assertEqual(response.status_code, 200)
@ -373,16 +377,17 @@ tél. 032 886 33 00
first_name="Laurent", last_name="Hots", birth_date="1994-07-12",
pcode="2000", city="Neuchâtel", klass=klass
)
url = reverse('print-expert-compens-eds', args=[st.pk])
exam = Examination.objects.create(student=st, session=ExamEDESession.objects.create(year=2020, season='1'))
url = reverse('print-expert-compens-eds', args=[exam.pk])
self.client.login(username='me', password='mepassword')
response = self.client.get(url, follow=True)
self.assertContains(response, "Toutes les informations ne sont pas disponibles")
st.expert = CorpContact.objects.get(last_name="Horner")
st.internal_expert = Teacher.objects.get(last_name="Caux")
st.date_exam = datetime(2018, 6, 28, 12, 00)
st.room = "B123"
st.save()
exam.external_expert = CorpContact.objects.get(last_name="Horner")
exam.internal_expert = Teacher.objects.get(last_name="Caux")
exam.date_exam = datetime(2018, 6, 28, 12, 00)
exam.room = "B123"
exam.save()
response = self.client.get(url, follow=True)
self.assertEqual(
@ -392,8 +397,8 @@ tél. 032 886 33 00
self.assertEqual(response['Content-Type'], 'application/pdf')
self.assertGreater(int(response['Content-Length']), 1000)
# Expert without corporation
st.expert = CorpContact.objects.create(first_name='James', last_name='Bond')
st.save()
exam.external_expert = CorpContact.objects.create(first_name='James', last_name='Bond')
exam.save()
response = self.client.get(url, follow=True)
self.assertEqual(response.status_code, 200)

View file

@ -24,7 +24,7 @@ from .imports import HPContactsImportView, HPImportView, ImportReportsView, Stud
from ..forms import CorporationMergeForm, EmailBaseForm, StudentCommentForm
from ..models import (
Klass, Section, Student, Teacher, Corporation, CorpContact, Period,
Training, Availability
Training, Availability, Examination,
)
from ..pdf import (
ChargeSheetPDF, ExpertEdeLetterPdf, ExpertEdsLetterPdf, UpdateDataFormPDF,
@ -401,53 +401,41 @@ class StudentConvocationExaminationView(EmailConfirmationView):
title = "Convocation à la soutenance du travail de diplôme"
email_template = 'email/student_convocation_EDE.txt'
@property
def expert(self):
return self.student.expert
@property
def internal_expert(self):
return self.student.internal_expert
@property
def date_soutenance_mailed(self):
return self.student.date_soutenance_mailed
def missing_examination_data(self):
return self.student.missing_examination_data()
def dispatch(self, request, *args, **kwargs):
self.student = Student.objects.get(pk=self.kwargs['pk'])
errors = self.missing_examination_data()
self.exam = Examination.objects.get(pk=self.kwargs['pk'])
errors = self.exam.missing_examination_data()
errors.extend(self.check_errors())
if errors:
messages.error(request, "\n".join(errors))
return redirect(reverse("admin:stages_student_change", args=(self.student.pk,)))
return redirect(reverse("admin:stages_student_change", args=(self.exam.student.pk,)))
return super().dispatch(request, *args, **kwargs)
def get_person(self):
return self.exam.student
def check_errors(self):
errors = []
if not self.student.email:
if not self.exam.student.email:
errors.append("Létudiant-e na pas de courriel valide !")
if self.expert and not self.expert.email:
if self.exam.external_expert and not self.exam.external_expert.email:
errors.append("Lexpert externe na pas de courriel valide !")
if self.internal_expert and not self.internal_expert.email:
if self.exam.internal_expert and not self.exam.internal_expert.email:
errors.append("Lexpert interne n'a pas de courriel valide !")
if self.date_soutenance_mailed is not None:
if self.exam.date_soutenance_mailed is not None:
errors.append("Une convocation a déjà été envoyée !")
return errors
def msg_context(self):
# Recipients with ladies first!
recip_names = sorted([
self.student.civility_full_name,
self.expert.civility_full_name,
self.internal_expert.civility_full_name,
self.exam.student.civility_full_name,
self.exam.external_expert.civility_full_name,
self.exam.internal_expert.civility_full_name,
])
titles = [
self.student.civility,
self.expert.civility,
self.internal_expert.civility,
self.exam.student.civility,
self.exam.external_expert.civility,
self.exam.internal_expert.civility,
]
mme_count = titles.count('Madame')
# Civilities, with ladies first!
@ -463,16 +451,18 @@ class StudentConvocationExaminationView(EmailConfirmationView):
'recipient1': recip_names[0],
'recipient2': recip_names[1],
'recipient3': recip_names[2],
'student': self.student,
'student': self.exam.student,
'sender': self.request.user,
'global_civilities': civilities,
'date_examen': django_format(self.student.date_exam, 'l j F Y à H\hi') if self.student.date_exam else '-',
'salle': self.student.room,
'date_examen': django_format(self.exam.date_exam, 'l j F Y à H\hi') if self.exam.date_exam else '-',
'salle': self.exam.room,
'internal_expert': self.exam.internal_expert,
'external_expert': self.exam.external_expert,
}
def get_initial(self):
initial = super().get_initial()
to = [self.student.email, self.expert.email, self.internal_expert.email]
to = [self.exam.student.email, self.exam.external_expert.email, self.exam.internal_expert.email]
initial.update({
'cci': self.request.user.email,
@ -484,8 +474,8 @@ class StudentConvocationExaminationView(EmailConfirmationView):
return initial
def on_success(self, student):
self.student.date_soutenance_mailed = timezone.now()
self.student.save()
self.exam.date_soutenance_mailed = timezone.now()
self.exam.save()
class StudentConvocationEDSView(StudentConvocationExaminationView):
@ -523,24 +513,24 @@ class PrintExpertEDECompensationForm(PDFBaseView):
"""
pdf_class = ExpertEdeLetterPdf
def filename(self, student):
return slugify('{0}_{1}'.format(student.last_name, student.first_name)) + '_Expert.pdf'
def filename(self, exam):
return slugify('{0}_{1}'.format(exam.student.last_name, exam.student.first_name)) + '_Expert.pdf'
def get_object(self):
return Student.objects.get(pk=self.kwargs['pk'])
return Examination.objects.get(pk=self.kwargs['pk'])
def check_object(self, student):
missing = student.missing_examination_data()
def check_object(self, exam):
missing = exam.missing_examination_data()
if missing:
messages.error(self.request, "\n".join(
["Toutes les informations ne sont pas disponibles pour la lettre à lexpert!"]
+ missing
))
return redirect(reverse("admin:stages_student_change", args=(student.pk,)))
return redirect(reverse("admin:stages_student_change", args=(exam.student.pk,)))
def get(self, request, *args, **kwargs):
student = self.get_object()
response = self.check_object(student)
exam = self.get_object()
response = self.check_object(exam)
if response:
return response
return super().get(request, *args, **kwargs)
@ -575,14 +565,14 @@ class PrintExpertEDSCompensationForm(PrintExpertEDECompensationForm):
"""
pdf_class = ExpertEdsLetterPdf
def check_object(self, student):
missing = student.missing_examination_data()
def check_object(self, exam):
missing = exam.missing_examination_data()
if missing:
messages.error(self.request, "\n".join(
["Toutes les informations ne sont pas disponibles pour la lettre à lexpert!"]
+ missing
))
return redirect(reverse("admin:stages_student_change", args=(student.pk,)))
return redirect(reverse("admin:stages_student_change", args=[exam.student.pk]))
class PrintKlassList(ZippedFilesBaseView):

View file

@ -21,6 +21,9 @@ class EmailConfirmationBaseView(FormView):
success_message = "Le message a été envoyé pour {person}"
error_message = "Échec denvoi pour {person} ({err})"
def get_person(self):
return self.person_model.objects.get(pk=self.kwargs['pk'])
def form_valid(self, form):
email = EmailMessage(
subject=form.cleaned_data['subject'],
@ -29,7 +32,7 @@ class EmailConfirmationBaseView(FormView):
to=form.cleaned_data['to'].split(';'),
bcc=form.cleaned_data['cci'].split(';'),
)
person = self.person_model.objects.get(pk=self.kwargs['pk'])
person = self.get_person()
try:
email.send()
except Exception as err:
@ -46,7 +49,7 @@ class EmailConfirmationBaseView(FormView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'person': self.person_model.objects.get(pk=self.kwargs['pk']),
'person': self.get_person(),
'title': self.title,
})
return context

View file

@ -432,7 +432,7 @@ def export_qualification(request, section='ede'):
# 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',
).select_related('klass', 'referent', 'training_referent', 'mentor',
).prefetch_related('examination_set'
).order_by('klass__name', 'last_name'):
stud_values = [