from collections import OrderedDict from copy import deepcopy from django import forms from django.contrib import admin from django.contrib.auth.admin import GroupAdmin as AuthGroupAdmin from django.contrib.auth.models import Group from django.db import models from django.http import HttpResponse, HttpResponseRedirect from django.urls import reverse from django.utils.html import format_html, format_html_join from django.utils.safestring import mark_safe from .models import ( Teacher, Option, Student, StudentFile, Section, Level, Klass, Corporation, CorpContact, Domain, Period, Availability, Training, Course, LogBookReason, LogBook, ExamEDESession, Examination, SupervisionBill ) from .views.export import OpenXMLExport def print_charge_sheet(modeladmin, request, queryset): return HttpResponseRedirect( reverse('print-charge-sheet') + '?ids=%s' % ",".join( request.POST.getlist(admin.ACTION_CHECKBOX_NAME) ) ) print_charge_sheet.short_description = "Imprimer les feuilles de charge" class ArchivedListFilter(admin.BooleanFieldListFilter): """ Default filter that shows by default unarchived elements. """ def __init__(self, request, params, *args, **kwargs): super().__init__(request, params, *args, **kwargs) if self.lookup_val is None: self.lookup_val = '0' def choices(self, cl): # Removing the "all" choice return list(super().choices(cl))[1:] def queryset(self, request, queryset): if not self.used_parameters: self.used_parameters[self.lookup_kwarg] = '0' return super().queryset(request, queryset) class KlassRelatedListFilter(admin.RelatedFieldListFilter): def field_choices(self, field, request, model_admin): return [ (k.pk, k.name) for k in Klass.active.order_by('name') ] class StudentInline(admin.StackedInline): model = Student ordering = ('last_name', 'first_name') fields = ( ('last_name', 'first_name', 'birth_date'), ('pcode', 'city', 'tel', 'mobile', 'email'), ) can_delete = False extra = 0 class KlassAdmin(admin.ModelAdmin): list_display = ('name', 'section') ordering = ('name',) list_filter = ('section', 'level',) fields = ( ('name',), ('section', 'level'), ('teacher', 'teacher_ecg', 'teacher_eps'), ) inlines = [StudentInline] class LogBookInline(admin.TabularInline): model = LogBook ordering = ('input_date',) fields = ('start_date', 'end_date', 'reason', 'comment', 'nb_period') extra = 0 class TeacherAdmin(admin.ModelAdmin): list_display = ('__str__', 'abrev', 'email', 'contract', 'rate', 'total_logbook', 'archived') list_filter = (('archived', ArchivedListFilter), 'contract') search_fields = ('last_name', 'first_name', 'email') fields = (('civility', 'last_name', 'first_name', 'abrev'), ('birth_date', 'email', 'ext_id'), ('contract', 'rate', 'can_examinate', 'archived'), ('previous_report', 'next_report', 'total_logbook'), ('user')) readonly_fields = ('total_logbook',) actions = [print_charge_sheet] inlines = [LogBookInline] class SupervisionBillInline(admin.TabularInline): model = SupervisionBill extra = 0 class ExaminationInline(admin.StackedInline): model = Examination extra = 1 verbose_name = "Procédure de qualification" verbose_name_plural = "Procédures de qualification" autocomplete_fields = ('internal_expert', 'external_expert') fields = (('session', 'type_exam', 'date_exam', 'room'), ('internal_expert', 'external_expert'), ('mark', 'mark_acq'), ('examination_actions'), ('date_soutenance_mailed', 'date_confirm_received'),) readonly_fields = ( 'examination_actions', 'date_soutenance_mailed' ) def examination_actions(self, obj): missing_message = mark_safe( '
Veuillez compléter les informations ' 'd’examen (date/salle/experts) pour accéder aux boutons d’impression.
' ) if obj and obj.student.is_ede_3(): if obj.missing_examination_data(): return missing_message else: return format_html( 'Courrier pour l’expert ' 'Mail convocation soutenance ' 'Indemnité EP ' 'Indemnité soutenance', reverse('print-expert-letter-ede', args=[obj.pk]), reverse('student-ede-convocation', args=[obj.pk]), reverse('print-compens-form', args=[obj.pk, 'ep']), reverse('print-compens-form', args=[obj.pk, 'sout']), ) elif obj and obj.student.is_eds_3(): if obj.missing_examination_data(): return missing_message else: return format_html( 'Courrier pour l’expert ' 'Mail convocation soutenance ' 'Indemnité EP ' 'Indemnité soutenance', reverse('print-expert-letter-eds', args=[obj.pk]), reverse('student-eds-convocation', args=[obj.pk]), reverse('print-compens-form', args=[obj.pk, 'ep']), reverse('print-compens-form', args=[obj.pk, 'sout']), ) else: return missing_message examination_actions.short_description = 'Actions pour la procédure' class StudentAdmin(admin.ModelAdmin): list_display = ('__str__', 'pcode', 'city', 'klass', 'archived') 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') readonly_fields = ('report_sem1_sent', 'report_sem2_sent', 'mentor_indemn') fieldsets = [ (None, { 'fields': ( ('last_name', 'first_name', 'ext_id'), ('street', 'pcode', 'city', 'district'), ('email', 'tel', 'mobile'), ('gender', 'avs', 'birth_date'), ('archived', 'dispense_ecg', 'dispense_eps', 'soutien_dys'), ('klass', 'option_ase'), ('report_sem1', 'report_sem1_sent'), ('report_sem2', 'report_sem2_sent'), ('corporation', 'instructor',) )} ), ("Procédure de qualification", { 'classes': ['collapse'], 'fields': ( ('supervisor', 'supervision_attest_received'), ('subject', 'title'), ('training_referent', 'referent'), ('mentor', 'mentor_indemn'), ) }), ] actions = ['archive'] inlines = [ExaminationInline, SupervisionBillInline] def mentor_indemn(self, obj): if obj is None or not obj.mentor: return '-' return format_html( 'Indemnité au mentor', reverse('print-mentor-compens-form', args=[obj.pk]), ) mentor_indemn.short_description = 'Indemnité' def get_inlines(self, request, obj=None): if obj is None: return [] inlines = super().get_inlines(request, obj=obj) # SupervisionBillInline is only adequate for EDE students if not obj.klass or obj.klass.section.name != 'EDE': inlines = [inl for inl in inlines if inl != SupervisionBillInline] if not obj.is_ede_3() and not obj.is_eds_3(): inlines = [inl for inl in inlines if inl != ExaminationInline] return inlines def get_fieldsets(self, request, obj=None): if not obj or (not obj.is_ede_3() and not obj.is_eds_3()): # Hide group "Procédure de qualification" fieldsets = deepcopy(self.fieldsets) fieldsets[1][1]['classes'] = ['hidden'] return fieldsets return super().get_fieldsets(request, obj) def archive(self, request, queryset): for student in queryset: # Save each item individually to allow for custom save() logic. student.archived = True student.save() archive.short_description = "Marquer les étudiants sélectionnés comme archivés" class CorpContactAdmin(admin.ModelAdmin): list_display = ('__str__', 'corporation', 'role') list_filter = (('archived', ArchivedListFilter),) ordering = ('last_name', 'first_name') search_fields = ('last_name', 'first_name', 'role') fields = (('civility', 'last_name', 'first_name'), ('street', 'pcode', 'city'), ('birth_date', 'nation'), ('corporation',), ('sections', 'is_main', 'always_cc', 'archived'), ('role', 'ext_id'), ('tel', 'email'), ('avs',), ('iban', 'bank', 'clearing' ), ('qualification', 'fields_of_interest'), ) formfield_overrides = { models.ManyToManyField: {'widget': forms.CheckboxSelectMultiple}, } def get_form(self, *args, **kwargs): form = super().get_form(*args, **kwargs) form.base_fields['sections'].widget.can_add_related = False return form def get_search_results(self, request, qs, term): qs, distinct = super().get_search_results(request, qs, term) return qs.exclude(archived=True), distinct class ContactInline(admin.StackedInline): model = CorpContact fields = (('civility', 'last_name', 'first_name'), ('sections', 'is_main', 'always_cc', 'archived'), ('role', 'tel', 'email')) extra = 1 formfield_overrides = { models.ManyToManyField: {'widget': forms.CheckboxSelectMultiple}, } class CorporationAdmin(admin.ModelAdmin): list_display = ('name', 'short_name', 'pcode', 'city', 'district', 'ext_id') list_editable = ('short_name',) # Temporarily? list_filter = (('archived', ArchivedListFilter),) search_fields = ('name', 'street', 'pcode', 'city') ordering = ('name',) fields = ( ('name', 'short_name'), 'parent', ('sector', 'typ', 'ext_id'), 'street', ('pcode', 'city', 'district'), ('tel', 'email'), 'web', 'archived', ) inlines = [ContactInline] actions = ['export_corporations'] def get_search_results(self, request, qs, term): qs, distinct = super().get_search_results(request, qs, term) return qs.exclude(archived=True), distinct def export_corporations(self, request, queryset): """ Export all Corporations in Excel file. """ export_fields = OrderedDict([ (getattr(f, 'verbose_name', f.name), f.name) for f in Corporation._meta.get_fields() if f.name in ( 'name', 'short_name', 'sector', 'typ', 'street', 'pcode', 'city', 'district', 'tel', 'email', 'web', 'ext_id', 'archived' ) ]) export = OpenXMLExport('Exportation') export.write_line(export_fields.keys(), bold=True) for corp in queryset.values_list(*export_fields.values()): values = [] for value, field_name in zip(corp, export_fields.values()): if field_name == 'archived': value = 'Oui' if value else '' values.append(value) export.write_line(values) return export.get_http_response('corporations_export') export_corporations.short_description = 'Exportation Excel' class AvailabilityAdminForm(forms.ModelForm): """ Custom avail form to create several availabilities at once when inlined in the PeriodAdmin interface """ num_avail = forms.IntegerField(label="Nombre de places", initial=1, required=False) class Media: js = ('admin/js/jquery.init.js', 'js/avail_form.js',) class Meta: model = Availability fields = '__all__' widgets = { 'num_avail': forms.TextInput(attrs={'size': 3}), } def __init__(self, data=None, files=None, **kwargs): super().__init__(data=data, files=files, **kwargs) if self.instance.pk is not None: # Hide num_avail on existing instances self.fields['num_avail'].widget = forms.HiddenInput() # Limit CorpContact objects to contacts of chosen corporation if data is None and self.instance.corporation_id: self.fields['contact'].queryset = self.instance.corporation.corpcontact_set def save(self, **kwargs): instance = super().save(**kwargs) # Create supplementary availabilities depending on num_avail num_avail = self.cleaned_data.get('num_avail', 1) or 1 for i in range(1, num_avail): Availability.objects.create( corporation=instance.corporation, period=instance.period, domain=instance.domain, contact=instance.contact, comment=instance.comment) return instance class AvailabilityInline(admin.StackedInline): model = Availability form = AvailabilityAdminForm ordering = ('corporation__name',) extra = 1 formfield_overrides = { models.TextField: {'widget': forms.Textarea(attrs={'rows':2, 'cols':40})}, } autocomplete_fields = ['corporation'] class PeriodAdmin(admin.ModelAdmin): list_display = ('title', 'dates', 'section', 'level') list_filter = ('section', 'level') inlines = [AvailabilityInline] class AvailabilityAdmin(admin.ModelAdmin): list_display = ('corporation', 'period', 'domain', 'contact') list_filter = ('period',) fields = (('corporation', 'period'), 'domain', 'contact', 'priority', 'comment') form = AvailabilityAdminForm def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == "corporation": kwargs["queryset"] = Corporation.objects.filter(archived=False).order_by('name') if db_field.name == "contact": kwargs["queryset"] = CorpContact.objects.filter(archived=False) return super().formfield_for_foreignkey(db_field, request, **kwargs) class TrainingAdmin(admin.ModelAdmin): search_fields = ('student__first_name', 'student__last_name', 'availability__corporation__name') raw_id_fields = ('availability',) class CourseAdmin(admin.ModelAdmin): list_display = ('teacher', 'public', 'subject', 'period', 'imputation') list_filter = ('imputation', ) search_fields = ('teacher__last_name', 'public', 'subject') class GroupAdmin(AuthGroupAdmin): list_display = ['name', 'membres'] def membres(self, grp): return format_html_join(', ', '{}', [ (reverse('admin:auth_user_change', args=(user.pk,)), user.username) for user in grp.user_set.all().order_by('username') ]) admin.site.register(Section) admin.site.register(Level) admin.site.register(Klass, KlassAdmin) admin.site.register(Option) admin.site.register(Student, StudentAdmin) admin.site.register(StudentFile) admin.site.register(Teacher, TeacherAdmin) admin.site.register(Course, CourseAdmin) admin.site.register(Corporation, CorporationAdmin) admin.site.register(CorpContact, CorpContactAdmin) admin.site.register(Domain) admin.site.register(Period, PeriodAdmin) admin.site.register(Availability, AvailabilityAdmin) admin.site.register(Training, TrainingAdmin) admin.site.register(LogBookReason) admin.site.register(LogBook) admin.site.register(ExamEDESession) admin.site.register(Examination) admin.site.unregister(Group) admin.site.register(Group, GroupAdmin)