The problem is that Django RelatedObjectLookups.js is triggering select changes for the initial value and we don't want to change the contact list for this initial change (unless we lose the currently selected value).
417 lines
16 KiB
Python
417 lines
16 KiB
Python
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, 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')
|
||
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 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', 'expert', 'expert_ep')
|
||
readonly_fields = (
|
||
'report_sem1_sent', 'report_sem2_sent',
|
||
'examination_actions',
|
||
'date_soutenance_mailed', 'date_soutenance_ep_mailed'
|
||
)
|
||
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',)
|
||
)
|
||
}
|
||
),
|
||
("Examen Qualification ES", {
|
||
'classes': ['collapse'],
|
||
'fields': (
|
||
('session', 'date_exam', 'room'),
|
||
('supervisor', 'supervision_attest_received'),
|
||
('subject', 'title'),
|
||
('training_referent', 'referent', 'mentor'),
|
||
('internal_expert', 'expert'),
|
||
('date_soutenance_mailed', 'date_confirm_received'),
|
||
('examination_actions',),
|
||
('mark', 'mark_acq'),
|
||
)
|
||
}),
|
||
("Entretien professionnel ES", {
|
||
'classes': ['collapse'],
|
||
'fields': (
|
||
('session_ep', 'date_exam_ep', 'room_ep'),
|
||
('internal_expert_ep', 'expert_ep'),
|
||
('date_soutenance_ep_mailed', 'date_confirm_ep_received'),
|
||
('mark_ep', 'mark_ep_acq'),
|
||
)
|
||
}),
|
||
]
|
||
actions = ['archive']
|
||
inlines = [SupervisionBillInline]
|
||
|
||
def get_inline_instances(self, request, obj=None):
|
||
# SupervisionBillInline is only adequate for EDE students
|
||
if obj is None or not obj.klass or obj.klass.section.name != 'EDE':
|
||
return []
|
||
return super().get_inline_instances(request, obj=obj)
|
||
|
||
def get_fieldsets(self, request, obj=None):
|
||
if not self.is_ede_3(obj) and not self.is_eds_3(obj):
|
||
# Hide "Examen Qualification ES"/"Entretien professionnel ES"
|
||
fieldsets = deepcopy(self.fieldsets)
|
||
fieldsets[1][1]['classes'] = ['hidden']
|
||
fieldsets[2][1]['classes'] = ['hidden']
|
||
return fieldsets
|
||
return super().get_fieldsets(request, obj)
|
||
|
||
def is_ede_3(self, obj):
|
||
return obj and obj.klass and obj.klass.section.name == 'EDE' and obj.klass.level.name == '3'
|
||
|
||
def is_eds_3(self, obj):
|
||
return obj and obj.klass and obj.klass.section.name == 'EDS' and obj.klass.level.name == '3'
|
||
|
||
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"
|
||
|
||
def examination_actions(self, obj):
|
||
if self.is_ede_3(obj):
|
||
if obj.missing_examination_data():
|
||
return mark_safe(
|
||
'<div class="warning">Veuillez compléter les informations '
|
||
'd’examen (date/salle/experts) pour accéder aux boutons d’impression.</div>'
|
||
)
|
||
else:
|
||
return format_html(
|
||
'<a class="button" href="{}">Courrier pour l’expert</a> '
|
||
'<a class="button" href="{}">Mail convocation soutenance</a> '
|
||
'<a class="button" href="{}">Indemnité au mentor</a>',
|
||
reverse('print-expert-compens-ede', args=[obj.pk]),
|
||
reverse('student-ede-convocation', args=[obj.pk]),
|
||
reverse('print-mentor-compens-ede', args=[obj.pk]),
|
||
)
|
||
elif self.is_eds_3(obj):
|
||
if obj.missing_examination_data():
|
||
return mark_safe(
|
||
'<div class="warning">Veuillez compléter les informations '
|
||
'd’examen (date/salle/experts) pour accéder aux boutons d’impression.</div>'
|
||
)
|
||
else:
|
||
return format_html(
|
||
'<a class="button" href="{}">Courrier pour l’expert</a> '
|
||
'<a class="button" href="{}">Mail convocation soutenance</a> '
|
||
'<a class="button" href="{}">Indemnité au mentor</a>',
|
||
reverse('print-expert-compens-eds', args=[obj.pk]),
|
||
reverse('student-eds-convocation', args=[obj.pk]),
|
||
reverse('print-mentor-compens-ede', args=[obj.pk]),
|
||
)
|
||
else:
|
||
return ''
|
||
examination_actions.short_description = 'Actions pour les examens'
|
||
|
||
|
||
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',),
|
||
('corporation',),
|
||
('sections', 'is_main', 'always_cc', 'archived'),
|
||
('role', 'ext_id'), ('tel', 'email'),
|
||
('ccp', 'bank', 'clearing' ),
|
||
('iban',),
|
||
('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(', ', '<a href="{}">{}</a>', [
|
||
(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.unregister(Group)
|
||
admin.site.register(Group, GroupAdmin)
|