From 23908d40d3f3da5f415982e87b71418ed4170978 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 25 Aug 2017 16:51:01 +0200 Subject: [PATCH] Add new import for student instructors (from HyperPlanning export) --- common/urls.py | 1 + stages/test_files/Export_HP_Formateurs.xlsx | Bin 0 -> 7288 bytes stages/tests.py | 26 +++++- stages/views.py | 84 +++++++++++++------- templates/admin/base_site.html | 8 ++ templates/admin/index.html | 3 +- 6 files changed, 92 insertions(+), 30 deletions(-) create mode 100644 stages/test_files/Export_HP_Formateurs.xlsx diff --git a/common/urls.py b/common/urls.py index ebd9891..3265871 100644 --- a/common/urls.py +++ b/common/urls.py @@ -10,6 +10,7 @@ urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^import_students/', views.StudentImportView.as_view(), name='import-students'), url(r'^import_hp/', views.HPImportView.as_view(), name='import-hp'), + url(r'^import_hp_contacts/', views.HPContactsImportView.as_view(), name='import-hp-contacts'), url(r'^attribution/$', views.AttributionView.as_view(), name='attribution'), url(r'^stages/export/(?Pall)?/?$', views.stages_export, name='stages_export'), diff --git a/stages/test_files/Export_HP_Formateurs.xlsx b/stages/test_files/Export_HP_Formateurs.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..93a9b6e5a2eb5142f838d64d61f47645aba9e62f GIT binary patch literal 7288 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 literal 0 HcmV?d00001 diff --git a/stages/tests.py b/stages/tests.py index aa7c7df..30c2cf8 100644 --- a/stages/tests.py +++ b/stages/tests.py @@ -299,7 +299,11 @@ class ImportTests(TestCase): with open(path, 'rb') as fh: # , override_settings(DEBUG=True): response = self.client.post(reverse('import-students'), {'upload': fh}, follow=True) msg = "\n".join(str(m) for m in response.context['messages']) - self.assertIn("Created objects: 2", msg) + self.assertIn("Objets créés : 2", msg) + student1 = Student.objects.get(last_name='Fellmann') + self.assertEqual(student1.corporation.name, "Crèche Les Mousaillons") + # Instructor not set through this import + self.assertIsNone(student1.instructor) def test_import_hp(self): teacher = Teacher.objects.create( @@ -309,5 +313,23 @@ class ImportTests(TestCase): self.client.login(username='me', password='mepassword') with open(path, 'rb') as fh: response = self.client.post(reverse('import-hp'), {'upload': fh}, follow=True) - self.assertContains(response, "Created objects: 13, modified objects: 10") + self.assertContains(response, "Objets créés : 13") + self.assertContains(response, "Objets modifiés : 10") self.assertEqual(teacher.course_set.count(), 13) + + def test_import_hp_contacts(self): + # Those data should have been imported with the student main import file. + corp = Corporation.objects.create( + ext_id=44444, name="Crèche Les Mousaillons", typ="Institution", street="Rue des champs 12", + city="Moulineaux", pcode="2500" + ) + st1 = Student.objects.create( + ext_id=164718, first_name='Margot', last_name='Fellmann', birth_date="1994-05-12", + pcode="2300", city="La Chaux-de-Fonds", corporation=corp) + + path = os.path.join(os.path.dirname(__file__), 'test_files', 'Export_HP_Formateurs.xlsx') + self.client.login(username='me', password='mepassword') + with open(path, 'rb') as fh: + response = self.client.post(reverse('import-hp-contacts'), {'upload': fh}, follow=True) + st1.refresh_from_db() + self.assertEqual(st1.instructor.last_name, 'Geiser') diff --git a/stages/views.py b/stages/views.py index 9a0bfe6..aef6eee 100644 --- a/stages/views.py +++ b/stages/views.py @@ -301,14 +301,19 @@ class ImportViewBase(FormView): imp_file = CSVImportedFile(File(upfile)) else: imp_file = FileFactory(upfile) - created, modified = self.import_data(imp_file) + stats = self.import_data(imp_file) except Exception as e: if settings.DEBUG: raise messages.error(self.request, _("The import failed. Error message: %s") % e) else: - messages.info(self.request, _("Created objects: %(cr)d, modified objects: %(mod)d") % { - 'cr': created, 'mod': modified}) + non_fatal_errors = stats.get('errors', []) + if 'created' in stats: + messages.info(self.request, "Objets créés : %d" % stats['created']) + if 'modified' in stats: + messages.info(self.request, "Objets modifiés : %d" % stats['modified']) + if non_fatal_errors: + messages.warning(self.request, "Erreurs rencontrées: %s" % "\n".join(non_fatal_errors)) return HttpResponseRedirect(reverse('admin:index')) @@ -344,11 +349,6 @@ class StudentImportView(ImportViewBase): } student_defaults['corporation'] = self.get_corporation(corporation_defaults) - instructor_defaults = { - val: strip(line[key]) for key, val in instructor_mapping.items() - } - student_defaults['instructor'] = self.get_instructor(student_defaults['corporation'], instructor_defaults) - defaults = Student.prepare_import(student_defaults) try: student = Student.objects.get(ext_id=student_defaults['ext_id']) @@ -364,7 +364,7 @@ class StudentImportView(ImportViewBase): student = Student.objects.create(**defaults) obj_created += 1 #FIXME: implement arch_staled - return obj_created, obj_modified + return {'created': obj_created, 'modified': obj_modified} def get_corporation(self, corp_values): if corp_values['ext_id'] == '': @@ -377,23 +377,6 @@ class StudentImportView(ImportViewBase): ) return corp - def get_instructor(self, corp, inst_values): - if inst_values['ext_id'] == '': - return None - try: - inst = CorpContact.objects.get(ext_id=inst_values['ext_id']) - except CorpContact.DoesNotExist: - try: - inst = corp.corpcontact_set.get( - first_name__iexact=inst_values['first_name'], last_name__iexact=inst_values['last_name'] - ) - inst.ext_id = inst_values['ext_id'] - inst.save() - except CorpContact.DoesNotExist: - inst_values['corporation'] = corp - inst = CorpContact.objects.create(**inst_values) - return inst - class HPImportView(ImportViewBase): """ @@ -462,7 +445,54 @@ class HPImportView(ImportViewBase): obj.period += period obj_modified += 1 obj.save() - return obj_created, obj_modified + return {'created': obj_created, 'modified': obj_modified} + + +class HPContactsImportView(ImportViewBase): + """ + Importation du fichier Hyperplanning contenant les formateurs d'étudiants. + """ + form_class = UploadHPFileForm + + def import_data(self, up_file): + obj_modified = 0 + errors = [] + for line in up_file: + try: + student = Student.objects.get(ext_id=int(line['UID_ETU'])) + except Student.DoesNotExist: + errors.append( + "Impossible de trouver l'étudiant avec le numéro %s" % int(line['UID_ETU']) + ) + continue + try: + corp = Corporation.objects.get(ext_id=int(line['NoSIRET'])) + except Corporation.DoesNotExist: + errors.append( + "Impossible de trouver l'institution avec le numéro %s" % int(line['NoSIRET']) + ) + continue + + # Check corporation matches + if student.corporation_id != corp.pk: + # This import has priority over the corporation set by StudentImportView + student.corporation = corp + student.save() + + contact = corp.corpcontact_set.filter( + first_name__iexact=line['PRENOMMDS'].strip(), + last_name__iexact=line['NOMMDS'].strip() + ).first() + if contact is None: + contact = CorpContact.objects.create( + corporation=corp, first_name=line['PRENOMMDS'].strip(), + last_name=line['NOMMDS'].strip(), title=line['CIVMDS'], email=line['EMAILMDS'] + ) + if student.instructor != contact: + student.instructor = contact + student.save() + obj_modified += 1 + return {'modified': obj_modified, 'errors': errors} EXPORT_FIELDS = [ diff --git a/templates/admin/base_site.html b/templates/admin/base_site.html index efeca77..fa8c082 100644 --- a/templates/admin/base_site.html +++ b/templates/admin/base_site.html @@ -8,3 +8,11 @@ {% endblock %} {% block nav-global %}{% endblock %} + +{% block messages %} + {% if messages %} +
    {% for message in messages %} + {{ message|linebreaksbr|capfirst }} + {% endfor %}
+ {% endif %} +{% endblock messages %} diff --git a/templates/admin/index.html b/templates/admin/index.html index 7150580..5983014 100644 --- a/templates/admin/index.html +++ b/templates/admin/index.html @@ -75,7 +75,8 @@