diff --git a/common/settings.py b/common/settings.py index 402ea44..86089c7 100644 --- a/common/settings.py +++ b/common/settings.py @@ -110,6 +110,22 @@ INSTALLED_APPS = ( 'stages', ) +FILE_UPLOAD_HANDLERS = ["django.core.files.uploadhandler.TemporaryFileUploadHandler"] + ALLOWED_HOSTS = ['localhost', 'stages.pierre-coullery.ch'] +# Mapping between column names of a tabular file and Student field names +STUDENT_IMPORT_MAPPING = { + 'Num élève': 'ext_id', + 'Nom élève': 'last_name', + 'Prénom élève': 'first_name', + 'Rue élève': 'street', + 'Localité élève': 'city', # pcode is separated from city in prepare_import + 'Tél. élève': 'tel', + 'Natel élève': 'mobile', + 'Email élève': 'email', + 'Date nais. élève': 'birth_date', + 'Classe': 'klass', +} + from .local_settings import * diff --git a/common/urls.py b/common/urls.py index 367e08b..2c2b1bb 100644 --- a/common/urls.py +++ b/common/urls.py @@ -8,7 +8,7 @@ urlpatterns = [ url(r'^$', RedirectView.as_view(url='/admin/', permanent=True), name='home'), url(r'^admin/', include(admin.site.urls)), - url(r'^data-import/', include('tabimport.urls')), + url(r'^data-import/', views.StudentImportView.as_view(), name='tabimport'), url(r'^attribution/$', views.AttributionView.as_view(), name='attribution'), url(r'^stages/export/(?Pall)?/?$', views.stages_export, name='stages_export'), diff --git a/stages/forms.py b/stages/forms.py index 57cd624..96656b7 100644 --- a/stages/forms.py +++ b/stages/forms.py @@ -1,8 +1,29 @@ from django import forms +from django.conf import settings + +from tabimport import FileFactory, UnsupportedFileFormat from .models import Section, Period +class StudentImportForm(forms.Form): + upload = forms.FileField(label="Fichier des étudiants") + + def clean_upload(self): + f = self.cleaned_data['upload'] + try: + imp_file = FileFactory(f) + except UnsupportedFileFormat as e: + raise forms.ValidationError("Erreur: %s" % e) + # Check needed headers are present + headers = imp_file.get_headers() + missing = set(settings.STUDENT_IMPORT_MAPPING.keys()) - set(headers) + if missing: + raise forms.ValidationError("Erreur: il manque les colonnes %s" % ( + ", ".join(missing))) + return f + + class PeriodForm(forms.Form): section = forms.ModelChoiceField(queryset=Section.objects.all()) period = forms.ModelChoiceField(queryset=None) diff --git a/stages/views.py b/stages/views.py index 1c30006..1450709 100644 --- a/stages/views.py +++ b/stages/views.py @@ -2,12 +2,18 @@ import json from collections import OrderedDict from datetime import date, datetime, timedelta -from django.db.models import Count -from django.http import HttpResponse, HttpResponseNotAllowed -from django.shortcuts import get_object_or_404 -from django.views.generic import DetailView, TemplateView, ListView +from tabimport import FileFactory -from .forms import PeriodForm +from django.conf import settings +from django.contrib import messages +from django.core.urlresolvers import reverse +from django.db.models import Count +from django.http import HttpResponse, HttpResponseNotAllowed, HttpResponseRedirect +from django.shortcuts import get_object_or_404 +from django.utils.translation import ugettext as _ +from django.views.generic import DetailView, FormView, TemplateView, ListView + +from .forms import PeriodForm, StudentImportForm from .models import (Section, Student, Corporation, CorpContact, Period, Training, Referent, Availability) @@ -197,7 +203,7 @@ def new_training(request): avail.save() except Exception as exc: return HttpResponse(str(exc)) - return HttpResponse('OK') + return HttpResponse(b'OK') def del_training(request): """ Delete training and return the referent id """ @@ -209,6 +215,44 @@ def del_training(request): return HttpResponse(json.dumps({'ref_id': ref_id}), content_type="application/json") +class StudentImportView(FormView): + template_name = 'student_import.html' + form_class = StudentImportForm + + def form_valid(self, form): + try: + imp_file = FileFactory(form.cleaned_data['upload']) + created, modified = 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}) + return HttpResponseRedirect(reverse('admin:index')) + + def import_data(self, up_file): + """ Import Student data from uploaded file. """ + mapping = settings.STUDENT_IMPORT_MAPPING + rev_mapping = {v: k for k, v in mapping.items()} + obj_created = obj_modified = 0 + for line in up_file: + defaults = dict((val, line[key]) for key, val in mapping.items() if val != 'ext_id') + defaults = Student.prepare_import(defaults) + obj, created = Student.objects.get_or_create( + ext_id=line[rev_mapping['ext_id']], defaults=defaults) + if not created: + for key, val in defaults.items(): + setattr(obj, key, val) + obj.save() + obj_modified += 1 + else: + obj_created += 1 + #FIXME: implement arch_staled + return obj_created, obj_modified + + EXPORT_FIELDS = [ ('Prénom', 'student__first_name'), ('Nom', 'student__last_name'), ('ID externe', 'student__ext_id'), diff --git a/templates/admin/index.html b/templates/admin/index.html index 933ecdb..d30e9ca 100644 --- a/templates/admin/index.html +++ b/templates/admin/index.html @@ -72,7 +72,7 @@

Importation/exportation