Add ESTER students import

This commit is contained in:
Claude Paroz 2018-07-16 10:32:07 +02:00
parent e5147187e2
commit b84289e25c
5 changed files with 133 additions and 28 deletions

View file

@ -15,6 +15,7 @@ urlpatterns = [
path('admin/', admin.site.urls),
path('import_students/', views.StudentImportView.as_view(), name='import-students'),
path('import_students_ester/', views.imports.StudentEsterImportView.as_view(), name='import-students-ester'),
path('import_hp/', views.HPImportView.as_view(), name='import-hp'),
path('import_hp_contacts/', views.HPContactsImportView.as_view(), name='import-hp-contacts'),

Binary file not shown.

View file

@ -438,7 +438,7 @@ class ImportTests(TestCase):
for f in os.listdir(bulletins_dir):
os.remove(os.path.join(bulletins_dir, f))
def test_import_students(self):
def test_import_students_EPC(self):
"""
Import CLOEE file for FE students (ASAFE, ASEFE, ASSCF, EDE, EDS) version 2018!!
@ -512,6 +512,59 @@ class ImportTests(TestCase):
stud_arch = Student.objects.get(ext_id=44444)
self.assertTrue(stud_arch.archived)
def test_import_students_ESTER(self):
"""
Import CLOEE file for ESTER students (MP_*) version 2018
Student :
- S. Lampion, 1MPTS ASE1 - Généraliste
- B. Castafiore, 1MPTS ASE1
Export CLOEE:
- S. Lampion, 2MPTS ASE1 - Accompagnement des enfants
- T. Tournesol, 2MPS ASSC1
Results in Student:
- S. Lampion, 2MPTS ASE1 (Student data + Cloee klass)
- T. Tournesol, 2MPS ASSC1 (Candidate data + Cloee klass)
- B. Castafiore, archived
"""
lev1 = Level.objects.create(name='1')
lev2 = Level.objects.create(name='2')
mp_ase = Section.objects.create(name='MP_ASE')
mp_assc = Section.objects.create(name='MP_ASSC')
Option.objects.create(name='Accompagnement des enfants')
Option.objects.create(name='Généraliste')
k1 = Klass.objects.create(name='1MPTS ASE1', section=mp_ase, level=lev1)
k2 = Klass.objects.create(name='2MPTS ASE1', section=mp_ase, level=lev2)
# Existing students, klass should be changed or student archived.
Student.objects.create(ext_id=11111, first_name="Séraphin", last_name="Lampion", city='Marin', klass=k1)
Student.objects.create(ext_id=22222, first_name="Bianca", last_name="Castafiore", city='Marin', klass=k1)
path = os.path.join(os.path.dirname(__file__), 'test_files', 'CLOEE2_Export_Ester_2018.xls')
self.client.login(username='me', password='mepassword')
with open(path, 'rb') as fh:
response = self.client.post(reverse('import-students-ester'), {'upload': fh}, follow=True)
msg = "\n".join(str(m) for m in response.context['messages'])
self.assertIn("La classe '2MPS ASSC1' n'existe pas encore", msg)
k3 = Klass.objects.create(name='2MPS ASSC1', section=mp_assc, level=lev2)
with open(path, 'rb') as fh:
response = self.client.post(reverse('import-students-ester'), {'upload': fh}, follow=True)
msg = "\n".join(str(m) for m in response.context['messages'])
self.assertIn("Objets créés : 1", msg)
self.assertIn("Objets modifiés : 1", msg)
# Student already existed, klass changed
student1 = Student.objects.get(ext_id=11111)
self.assertEqual(student1.klass.name, '2MPTS ASE1')
self.assertEqual(student1.option_ase.name, 'Accompagnement des enfants')
self.assertEqual(student1.city, 'Le Locle')
# Castafiore was archived
stud_arch = Student.objects.get(ext_id=22222)
self.assertTrue(stud_arch.archived)
def test_import_hp(self):
teacher = Teacher.objects.create(
first_name='Jeanne', last_name='Dupond', birth_date='1974-08-08'

View file

@ -4,6 +4,7 @@ import tempfile
from collections import OrderedDict
from datetime import datetime
from fnmatch import fnmatch
from subprocess import PIPE, Popen, call
from tabimport import CSVImportedFile, FileFactory
@ -72,7 +73,7 @@ class ImportViewBase(FormView):
class StudentImportView(ImportViewBase):
title = "Importation étudiants"
title = "Importation étudiants EPC"
form_class = StudentImportForm
# Mapping between column names of a tabular file and Student field names
student_mapping = {
@ -81,6 +82,7 @@ class StudentImportView(ImportViewBase):
'ELE_PRENOM': 'first_name',
'ELE_RUE': 'street',
'ELE_NPA_LOCALITE': 'city', # pcode is separated from city in prepare_import
'ELE_CODE_CANTON': 'district',
'ELE_TEL_PRIVE': 'tel',
'ELE_TEL_MOBILE': 'mobile',
'ELE_EMAIL_RPN': 'email',
@ -102,12 +104,14 @@ class StudentImportView(ImportViewBase):
}
mapping_option_ase = {
'GEN': 'Généraliste',
'Enfants': 'Accompagnement des enfants',
'ENF': 'Accompagnement des enfants',
'HAN': 'Accompagnement des personnes handicapées',
'PAG': 'Accompagnement des personnes âgées',
}
# Those values are always taken from the import file
fields_to_overwrite = ['klass', 'login_rpn']
klasses_to_skip = []
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
@ -142,6 +146,27 @@ class StudentImportView(ImportViewBase):
values['option_ase'] = None
return values
@property
def _existing_students(self):
return Student.objects.filter(
archived=False,
ext_id__isnull=False,
klass__section__in=[s for s in Section.objects.all() if s.is_EPC]
)
def update_defaults_from_candidate(self, defaults):
# Any DoesNotExist exception will bubble up.
candidate = Candidate.objects.get(last_name=defaults['last_name'],
first_name=defaults['first_name'])
# Mix CLOEE data and Candidate data
if candidate.option in self.mapping_option_ase:
defaults['option_ase'] = Option.objects.get(name=self.mapping_option_ase[candidate.option])
if candidate.corporation:
defaults['corporation'] = candidate.corporation
defaults['instructor'] = candidate.instructor
defaults['dispense_ecg'] = candidate.exemption_ecg
defaults['soutien_dys'] = candidate.handicap
def import_data(self, up_file):
""" Import Student data from uploaded file. """
@ -151,12 +176,8 @@ class StudentImportView(ImportViewBase):
obj_created = obj_modified = 0
err_msg = []
seen_students_ids = set()
fe_students_ids = set(
Student.objects.filter(
archived=False,
ext_id__isnull=False,
klass__section__in=[s for s in Section.objects.all() if s.is_EPC]
).values_list('ext_id', flat=True)
existing_students_ids = set(
self._existing_students.values_list('ext_id', flat=True)
)
for line in up_file:
@ -166,12 +187,20 @@ class StudentImportView(ImportViewBase):
if student_defaults['ext_id'] in seen_students_ids:
# Second line for student, ignore it
continue
for klass in self.klasses_to_skip:
if fnmatch(student_defaults['klass'], klass):
continue
seen_students_ids.add(student_defaults['ext_id'])
corporation_defaults = {
val: strip(line[key]) for key, val in self.corporation_mapping.items()
}
student_defaults['corporation'] = self.get_corporation(corporation_defaults)
if self.corporation_mapping:
corporation_defaults = {
val: strip(line[key]) for key, val in self.corporation_mapping.items()
}
student_defaults['corporation'] = self.get_corporation(corporation_defaults)
if 'option_ase' in self.fields_to_overwrite:
if student_defaults['option_ase'] in self.mapping_option_ase:
student_defaults['option_ase'] = self.mapping_option_ase[student_defaults['option_ase']]
defaults = self.clean_values(student_defaults)
try:
@ -189,18 +218,7 @@ class StudentImportView(ImportViewBase):
obj_modified += 1
except Student.DoesNotExist:
try:
candidate = Candidate.objects.get(last_name=defaults['last_name'],
first_name=defaults['first_name'])
# Mix CLOEE data and Candidate data
if candidate.option in self.mapping_option_ase:
defaults['option_ase'] = Option.objects.get(name=self.mapping_option_ase[candidate.option])
if candidate.corporation:
defaults['corporation'] = candidate.corporation
defaults['instructor'] = candidate.instructor
defaults['dispense_ecg'] = candidate.exemption_ecg
defaults['soutien_dys'] = candidate.handicap
Student.objects.create(**defaults)
obj_created += 1
self.update_defaults_from_candidate(defaults)
except Candidate.DoesNotExist:
# New student with no matching Candidate
err_msg.append('Étudiant non trouvé dans les candidats: {0} {1} - classe: {2}'.format(
@ -208,12 +226,12 @@ class StudentImportView(ImportViewBase):
defaults['first_name'],
defaults['klass'])
)
Student.objects.create(**defaults)
obj_created += 1
Student.objects.create(**defaults)
obj_created += 1
# Archive students who have not been exported
rest = fe_students_ids - seen_students_ids
rest = existing_students_ids - seen_students_ids
archived = 0
for student_id in rest:
st = Student.objects.get(ext_id=student_id)
@ -237,6 +255,38 @@ class StudentImportView(ImportViewBase):
return corp
class StudentEsterImportView(StudentImportView):
title = "Importation étudiants ESTER"
# Mapping between column names of a tabular file and Student field names
student_mapping = {
'ELE_NUMERO': 'ext_id',
'ELE_NOM': 'last_name',
'ELE_PRENOM': 'first_name',
'ELE_RUE': 'street',
'ELE_NPA_LOCALITE': 'city', # pcode is separated from city in prepare_import
'ELE_DATE_NAISSANCE': 'birth_date',
'ELE_AVS': 'avs',
'ELE_SEXE': 'gender',
'INS_CLASSE': 'klass',
'PROF_DOMAINE_SPEC': 'option_ase',
}
corporation_mapping = None
# Those values are always taken from the import file
fields_to_overwrite = ['klass', 'street', 'city', 'option_ase']
klasses_to_skip = ['1CMS*'] # Abandon classes 1CMS ASE + 1CMS ASSC
@property
def _existing_students(self):
return Student.objects.filter(
archived=False,
ext_id__isnull=False,
klass__section__in=[s for s in Section.objects.all() if s.is_ESTER]
)
def update_defaults_from_candidate(self, defaults):
pass
class HPImportView(ImportViewBase):
"""
Importation du fichier HyperPlanning pour l'établissement des feuilles

View file

@ -84,7 +84,8 @@ document.addEventListener("DOMContentLoaded", function(event) {
<div class="module" id="custom-actions-module">
<h2>Importation/exportation</h2>
<ul>
<li><a href="{% url 'import-students' %}">Importer un fichier d'étudiants</a></li>
<li><a href="{% url 'import-students' %}">Importer un fichier d'étudiants EPC</a></li>
<li><a href="{% url 'import-students-ester' %}">Importer un fichier d'étudiants ESTER</a></li>
<li><a href="{% url 'import-hp' %}">Importer le fichier HP</a></li>
<li><a href="{% url 'import-hp-contacts' %}">Importer les formateurs (fichier HP)</a></li>
<li style="margin-top: 1em;"><a href="{% url 'stages_export' %}">Exporter les données de stages</a> (récentes)</li>