diff --git a/biscuit/apps/schild_nrw/__init__.py b/biscuit/apps/schild_nrw/__init__.py index 8492c21f5d01729a49bcb676292e68037c5184e0..2f8dec67ea6459c90a2cd74118a51aefdc13e498 100644 --- a/biscuit/apps/schild_nrw/__init__.py +++ b/biscuit/apps/schild_nrw/__init__.py @@ -1,8 +1,8 @@ import pkg_resources try: - __version__ = pkg_resources.get_distribution('BiscuIT-App-SchILD-NRW').version + __version__ = pkg_resources.get_distribution("BiscuIT-App-SchILD-NRW").version except Exception: - __version__ = 'unknown' + __version__ = "unknown" -default_app_config = 'biscuit.apps.schild_nrw.apps.SchILDNRWConfig' +default_app_config = "biscuit.apps.schild_nrw.apps.SchILDNRWConfig" diff --git a/biscuit/apps/schild_nrw/apps.py b/biscuit/apps/schild_nrw/apps.py index 2b3c34d08c07b7918adda153343d32446c16a14f..14b2657b6b44dc6a48c7089808667a566098ce12 100644 --- a/biscuit/apps/schild_nrw/apps.py +++ b/biscuit/apps/schild_nrw/apps.py @@ -2,5 +2,5 @@ from biscuit.core.util.apps import AppConfig class SchILDNRWConfig(AppConfig): - name = 'biscuit.apps.schild_nrw' - verbose_name = 'BiscuIT - SchILD-NRW interface' + name = "biscuit.apps.schild_nrw" + verbose_name = "BiscuIT - SchILD-NRW interface" diff --git a/biscuit/apps/schild_nrw/forms.py b/biscuit/apps/schild_nrw/forms.py index e917be98c94333b2ee274ecbb06a26d9d9abf72b..13c39ee3085772e2cb589aef242130002ee23cf5 100644 --- a/biscuit/apps/schild_nrw/forms.py +++ b/biscuit/apps/schild_nrw/forms.py @@ -3,6 +3,6 @@ from django.utils.translation import ugettext_lazy as _ class SchILDNRWUploadForm(forms.Form): - teachers_csv = forms.FileField(label=_('CSV export of teachers')) - students_csv = forms.FileField(label=_('CSV export of students')) - guardians_csv = forms.FileField(label=_('CSV export of guardians/parents')) + teachers_csv = forms.FileField(label=_("CSV export of teachers")) + students_csv = forms.FileField(label=_("CSV export of students")) + guardians_csv = forms.FileField(label=_("CSV export of guardians/parents")) diff --git a/biscuit/apps/schild_nrw/management/commands/schild_import.py b/biscuit/apps/schild_nrw/management/commands/schild_import.py index 1de0ff0a34997c1d513421f9d50da0f938e8b685..3ae440a0d5bf1bebab359b743bb56e6a525fb20d 100644 --- a/biscuit/apps/schild_nrw/management/commands/schild_import.py +++ b/biscuit/apps/schild_nrw/management/commands/schild_import.py @@ -6,16 +6,19 @@ from biscuit.apps.schild_nrw.util import schild_import_csv class Command(BaseCommand): def add_arguments(self, parser): - parser.add_argument('teachers_csv_path', help=_( - 'Path to CSV file with exported teachers')) - parser.add_argument('students_csv_path', help=_( - 'Path to CSV file with exported students')) - parser.add_argument('guardians_csv_path', help=_( - 'Path to CSV file with exported guardians')) + parser.add_argument( + "teachers_csv_path", help=_("Path to CSV file with exported teachers") + ) + parser.add_argument( + "students_csv_path", help=_("Path to CSV file with exported students") + ) + parser.add_argument( + "guardians_csv_path", help=_("Path to CSV file with exported guardians") + ) def handle(self, *args, **options): - teachers_csv = open(options['teachers_csv_path'], 'rb') - students_csv = open(options['students_csv_path'], 'rb') - guardians_csv = open(options['guardians_csv_path'], 'rb') + teachers_csv = open(options["teachers_csv_path"], "rb") + students_csv = open(options["students_csv_path"], "rb") + guardians_csv = open(options["guardians_csv_path"], "rb") schild_import_csv(None, teachers_csv, students_csv, guardians_csv) diff --git a/biscuit/apps/schild_nrw/menus.py b/biscuit/apps/schild_nrw/menus.py index 508c80b33baeb7ce3382effcde50bc43b58758e3..42386fd062d595facd960b8e65d8de4dcef9a7fd 100644 --- a/biscuit/apps/schild_nrw/menus.py +++ b/biscuit/apps/schild_nrw/menus.py @@ -1,11 +1,15 @@ from django.utils.translation import ugettext_lazy as _ MENUS = { - 'DATA_MANAGEMENT_MENU': [ + "DATA_MANAGEMENT_MENU": [ { - 'name': _('SchILD-NRW import'), - 'url': 'schild_import', - 'validators': ['menu_generator.validators.is_authenticated', 'menu_generator.validators.is_superuser', 'biscuit.core.util.core_helpers.has_person'], + "name": _("SchILD-NRW import"), + "url": "schild_import", + "validators": [ + "menu_generator.validators.is_authenticated", + "menu_generator.validators.is_superuser", + "biscuit.core.util.core_helpers.has_person", + ], } ] } diff --git a/biscuit/apps/schild_nrw/urls.py b/biscuit/apps/schild_nrw/urls.py index 1ad44e760814f930c7fec0a05999887f133d7561..65df5923a3b6f5d7aa0afc78fc3a5e2fc04cb50c 100644 --- a/biscuit/apps/schild_nrw/urls.py +++ b/biscuit/apps/schild_nrw/urls.py @@ -2,7 +2,6 @@ from django.urls import path from . import views - urlpatterns = [ - path('import', views.schild_import, name='schild_import'), + path("import", views.schild_import, name="schild_import"), ] diff --git a/biscuit/apps/schild_nrw/util.py b/biscuit/apps/schild_nrw/util.py index ccb7acb357b5b90d10cfecc42368f9f9edccd8c0..1a89e95674f9cc15ec448c704fbd2552aecb0131 100644 --- a/biscuit/apps/schild_nrw/util.py +++ b/biscuit/apps/schild_nrw/util.py @@ -10,7 +10,6 @@ import phonenumbers from biscuit.core.models import Person from biscuit.core.util import messages - SCHILD_STATE_ACTIVE = (True, 2) @@ -19,15 +18,32 @@ def is_active(person_row: dict) -> bool: at different attributes. """ - if 'is_active' in person_row: - return person_row['is_active'] in SCHILD_STATE_ACTIVE + if "is_active" in person_row: + return person_row["is_active"] in SCHILD_STATE_ACTIVE return True -def schild_import_csv_single(request: HttpRequest, csv: Union[BinaryIO, str], cols: Dict[str, Any], converters: Dict[str, Callable[[Optional[str]], Any]]) -> None: - persons = pandas.read_csv(csv, sep=';', names=cols.keys(), dtype=cols, usecols=lambda k: not k.startswith('_'), keep_default_na=False, - converters=converters, parse_dates=['date_of_birth'], quotechar='"', encoding='utf-8-sig', true_values=['+', 'Ja'], false_values=['-', 'Nein']) +def schild_import_csv_single( + request: HttpRequest, + csv: Union[BinaryIO, str], + cols: Dict[str, Any], + converters: Dict[str, Callable[[Optional[str]], Any]], +) -> None: + persons = pandas.read_csv( + csv, + sep=";", + names=cols.keys(), + dtype=cols, + usecols=lambda k: not k.startswith("_"), + keep_default_na=False, + converters=converters, + parse_dates=["date_of_birth"], + quotechar='"', + encoding="utf-8-sig", + true_values=["+", "Ja"], + false_values=["-", "Nein"], + ) # Clean up invalid date values persons.date_of_birth = persons.date_of_birth.astype(object) @@ -39,19 +55,33 @@ def schild_import_csv_single(request: HttpRequest, csv: Union[BinaryIO, str], co for person_row in persons.transpose().to_dict().values(): # Fill the is_active field from other fields if necessary - person_row['is_active'] = is_active(person_row) + person_row["is_active"] = is_active(person_row) - if person_row['is_active']: + if person_row["is_active"]: try: person, created = Person.objects.update_or_create( - import_ref=person_row['import_ref'], defaults=person_row) + import_ref=person_row["import_ref"], defaults=person_row + ) except (ValueError, phonenumbers.NumberParseException) as err: - messages.error(request, _( - 'Failed to import person %s' % ('%s, %s' % (person_row['last_name'], person_row['first_name']))) + ': %s' % err, fail_silently=True) + messages.error( + request, + _( + "Failed to import person %s" + % ( + "%s, %s" + % (person_row["last_name"], person_row["first_name"]) + ) + ) + + ": %s" % err, + fail_silently=True, + ) all_ok = False # Ensure that newly set primary group is also in member_of - if person.primary_group and person.primary_group not in person.member_of.all(): + if ( + person.primary_group + and person.primary_group not in person.member_of.all() + ): person.member_of.add(person.primary_group) person.save() @@ -59,56 +89,80 @@ def schild_import_csv_single(request: HttpRequest, csv: Union[BinaryIO, str], co created_count += 1 else: # Store import refs to deactivate later - inactive_refs.append(person_row['import_ref']) + inactive_refs.append(person_row["import_ref"]) # Deactivate all persons that existed but are now inactive if inactive_refs: affected = Person.objects.filter( - import_ref__in=inactive_refs, - is_active=True - ).update( - is_active=False - ) + import_ref__in=inactive_refs, is_active=True + ).update(is_active=False) if affected: - messages.warning(request, _('%d existing persons were deactivated.') % affected) + messages.warning( + request, _("%d existing persons were deactivated.") % affected + ) if created_count: - messages.success(request, _('%d persons were newly created.') % created_count) + messages.success(request, _("%d persons were newly created.") % created_count) if all_ok: - messages.success(request, _( - 'All persons were imported successfully.')) + messages.success(request, _("All persons were imported successfully.")) else: - messages.warning(request, _( - 'Some persons failed to be imported.')) - - -def schild_import_csv(request: HttpRequest, teachers_csv: Union[BinaryIO, str], students_csv: Union[BinaryIO, str], guardians_csv: Union[BinaryIO, str]) -> None: - csv_converters = {'phone_number': lambda v: phonenumbers.parse(v, 'DE') if v else '', - 'mobile_number': lambda v: phonenumbers.parse(v, 'DE') if v else '', - 'sex': lambda v: 'f' if v == 'w' else v} - - teachers_csv_cols = OrderedDict([('import_ref', str), ('email', str), ('_email_business', str), - ('date_of_birth', str), ('sex', - str), ('short_name', str), - ('last_name', str), ('first_name', - str), ('street', str), - ('postal_code', str), ('place', - str), ('phone_number', str), - ('mobile_number', str), ('is_active', 'bool')]) - - schild_import_csv_single( - request, teachers_csv, teachers_csv_cols, csv_converters) - - students_csv_cols = OrderedDict([('import_ref', str), ('_internal_id', int), ('primary_group_short_name', str), - ('last_name', str), ('first_name', - str), ('additional_name', str), - ('date_of_birth', str), ('email', - str), ('_email_business', str), - ('sex', str), ('street', - str), ('housenumber', str), - ('postal_code', str), ('place', str), ('phone_number', str), ('is_active', int)]) - - schild_import_csv_single( - request, students_csv, students_csv_cols, csv_converters) + messages.warning(request, _("Some persons failed to be imported.")) + + +def schild_import_csv( + request: HttpRequest, + teachers_csv: Union[BinaryIO, str], + students_csv: Union[BinaryIO, str], + guardians_csv: Union[BinaryIO, str], +) -> None: + csv_converters = { + "phone_number": lambda v: phonenumbers.parse(v, "DE") if v else "", + "mobile_number": lambda v: phonenumbers.parse(v, "DE") if v else "", + "sex": lambda v: "f" if v == "w" else v, + } + + teachers_csv_cols = OrderedDict( + [ + ("import_ref", str), + ("email", str), + ("_email_business", str), + ("date_of_birth", str), + ("sex", str), + ("short_name", str), + ("last_name", str), + ("first_name", str), + ("street", str), + ("postal_code", str), + ("place", str), + ("phone_number", str), + ("mobile_number", str), + ("is_active", "bool"), + ] + ) + + schild_import_csv_single(request, teachers_csv, teachers_csv_cols, csv_converters) + + students_csv_cols = OrderedDict( + [ + ("import_ref", str), + ("_internal_id", int), + ("primary_group_short_name", str), + ("last_name", str), + ("first_name", str), + ("additional_name", str), + ("date_of_birth", str), + ("email", str), + ("_email_business", str), + ("sex", str), + ("street", str), + ("housenumber", str), + ("postal_code", str), + ("place", str), + ("phone_number", str), + ("is_active", int), + ] + ) + + schild_import_csv_single(request, students_csv, students_csv_cols, csv_converters) diff --git a/biscuit/apps/schild_nrw/views.py b/biscuit/apps/schild_nrw/views.py index a1f4d528f7c585835e3a64167f033314d5ce4274..0275bd653bb0c7f1725eb089e445cc0a2156d00c 100644 --- a/biscuit/apps/schild_nrw/views.py +++ b/biscuit/apps/schild_nrw/views.py @@ -2,11 +2,11 @@ from django.contrib.auth.decorators import login_required from django.http import HttpRequest, HttpResponse from django.shortcuts import render +from biscuit.core.decorators import admin_required + from .forms import SchILDNRWUploadForm from .util import schild_import_csv -from biscuit.core.decorators import admin_required - @login_required @admin_required @@ -15,13 +15,17 @@ def schild_import(request: HttpRequest) -> HttpResponse: upload_form = SchILDNRWUploadForm() - if request.method == 'POST': + if request.method == "POST": upload_form = SchILDNRWUploadForm(request.POST, request.FILES) if upload_form.is_valid(): schild_import_csv( - request, request.FILES['teachers_csv'], request.FILES['students_csv'], request.FILES['guardians_csv']) + request, + request.FILES["teachers_csv"], + request.FILES["students_csv"], + request.FILES["guardians_csv"], + ) - context['upload_form'] = upload_form + context["upload_form"] = upload_form - return render(request, 'schild_nrw/schild_import.html', context) + return render(request, "schild_nrw/schild_import.html", context)