Merge branch 'master' into master
This commit is contained in:
commit
bc033cbcf9
16 changed files with 535 additions and 137 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,5 +1,7 @@
|
|||
*.pyc
|
||||
database.db
|
||||
common/local_settings.py
|
||||
media/
|
||||
scripts/epcstages.json
|
||||
.idea/*.*
|
||||
static/
|
||||
.idea/
|
||||
|
|
|
|||
0
candidats/__init__.py
Normal file
0
candidats/__init__.py
Normal file
126
candidats/admin.py
Normal file
126
candidats/admin.py
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
from collections import OrderedDict
|
||||
from datetime import date
|
||||
|
||||
from django import forms
|
||||
from django.contrib import admin
|
||||
from django.core.mail import send_mail
|
||||
from django.db.models import BooleanField
|
||||
from django.template import loader
|
||||
|
||||
from stages.exports import OpenXMLExport
|
||||
from .models import Candidate, GENDER_CHOICES
|
||||
|
||||
|
||||
def export_candidates(modeladmin, request, queryset):
|
||||
"""
|
||||
Export all candidates fields.
|
||||
"""
|
||||
export_fields = OrderedDict(
|
||||
[(f.verbose_name, f.name) for f in Candidate._meta.get_fields() if f.name != 'ID']
|
||||
)
|
||||
boolean_fields = [f.name for f in Candidate._meta.get_fields() if isinstance(f, BooleanField)]
|
||||
export_fields['Employeur'] = 'corporation__name'
|
||||
export_fields['Employeur_localite'] = 'corporation__city'
|
||||
export_fields['FEE/FPP'] = 'instructor__last_name'
|
||||
|
||||
export = OpenXMLExport('Exportation')
|
||||
export.write_line(export_fields.keys(), bold=True)
|
||||
for cand in queryset.values_list(*export_fields.values()):
|
||||
values = []
|
||||
for value, field_name in zip(cand, export_fields.values()):
|
||||
if field_name == 'gender':
|
||||
value = dict(GENDER_CHOICES)[value]
|
||||
if field_name in boolean_fields:
|
||||
value = 'Oui' if value else ''
|
||||
values.append(value)
|
||||
export.write_line(values)
|
||||
return export.get_http_response('candidats_export')
|
||||
|
||||
export_candidates.short_description = "Exporter les candidats sélectionnés"
|
||||
|
||||
|
||||
def send_confirmation_mail(modeladmin, request, queryset):
|
||||
from_email = request.user.email
|
||||
subject = "Confirmation de votre inscription à l'Ecole Santé-social Pierre-Coullery"
|
||||
|
||||
for candidate in queryset.filter(
|
||||
deposite_date__isnull=False, date_confirmation_mail__isnull=True, canceled_file=False):
|
||||
to = [candidate.email]
|
||||
|
||||
if candidate.corporation and candidate.corporation.email:
|
||||
to.append(candidate.corporation.email)
|
||||
if candidate.instructor and candidate.instructor.email:
|
||||
to.append(candidate.instructor.email)
|
||||
|
||||
context = {
|
||||
'candidate_civility': candidate.civility,
|
||||
'candidate_name': " ".join([candidate.civility, candidate.first_name, candidate.last_name]),
|
||||
'section': candidate.section,
|
||||
'sender_name': " ".join([request.user.first_name, request.user.last_name]),
|
||||
'sender_email': from_email,
|
||||
}
|
||||
|
||||
if candidate.section == 'EDE':
|
||||
body = loader.render_to_string('email/candidate_confirm_EDE.txt', context)
|
||||
else:
|
||||
body = loader.render_to_string('email/candidate_confirm_FE.txt', context)
|
||||
try:
|
||||
send_mail(subject, body, from_email, to, fail_silently=False)
|
||||
except Exception as err:
|
||||
self.message_user(request, "Échec d'envoi pour le candidat {0} ({1})".format(candidate, err))
|
||||
else:
|
||||
candidate.date_confirmation_mail = date.today()
|
||||
candidate.save()
|
||||
|
||||
send_confirmation_mail.short_description = "Envoyer email de confirmation"
|
||||
|
||||
|
||||
class CandidateAdminForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Candidate
|
||||
widgets = {
|
||||
'comment': forms.Textarea(attrs={'cols': 100, 'rows': 1}),
|
||||
'pcode': forms.TextInput(attrs={'size': 10}),
|
||||
}
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class CandidateAdmin(admin.ModelAdmin):
|
||||
form = CandidateAdminForm
|
||||
list_display = ('last_name', 'first_name', 'section', 'confirm_mail')
|
||||
list_filter = ('section', 'option')
|
||||
readonly_fields = ('total_result_points', 'total_result_mark', 'date_confirmation_mail')
|
||||
actions = [export_candidates, send_confirmation_mail]
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'fields': (('first_name', 'last_name', 'gender'),
|
||||
('street', 'pcode', 'city', 'district'),
|
||||
('mobile', 'email'),
|
||||
('birth_date', 'avs', 'handicap', 'has_photo'),
|
||||
('section', 'option'),
|
||||
('corporation', 'instructor'),
|
||||
('deposite_date', 'date_confirmation_mail', 'canceled_file'),
|
||||
'comment',
|
||||
),
|
||||
}),
|
||||
("FE", {
|
||||
'classes': ('collapse',),
|
||||
'fields': (('exemption_ecg', 'integration_second_year', 'validation_sfpo'),),
|
||||
}),
|
||||
("EDE/EDS", {
|
||||
'classes': ('collapse',),
|
||||
'fields': (('registration_form', 'certificate_of_payement', 'cv', 'certif_of_cfc',
|
||||
'police_record', 'certif_of_800h', 'reflexive_text', 'work_certificate',
|
||||
'marks_certificate', 'proc_admin_ext', 'promise', 'contract'),
|
||||
('interview_date', 'interview_room'),
|
||||
('examination_result', 'interview_result', 'file_result', 'total_result_points',
|
||||
'total_result_mark')
|
||||
),
|
||||
}),
|
||||
)
|
||||
|
||||
def confirm_mail(self, obj):
|
||||
return obj.date_confirmation_mail is not None
|
||||
confirm_mail.boolean = True
|
||||
|
||||
admin.site.register(Candidate, CandidateAdmin)
|
||||
69
candidats/migrations/0001_initial.py
Normal file
69
candidats/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('stages', '0002_add_student_option_ase'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Candidate',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('first_name', models.CharField(max_length=40, verbose_name='Prénom')),
|
||||
('last_name', models.CharField(max_length=40, verbose_name='Nom')),
|
||||
('gender', models.CharField(choices=[('M', 'Masculin'), ('F', 'Féminin'), ('I', 'Inconnu')], max_length=1, verbose_name='Genre')),
|
||||
('birth_date', models.DateField(blank=True, null=True, verbose_name='Date de naissance')),
|
||||
('street', models.CharField(blank=True, max_length=150, verbose_name='Rue')),
|
||||
('pcode', models.CharField(max_length=4, verbose_name='Code postal')),
|
||||
('city', models.CharField(max_length=40, verbose_name='Localité')),
|
||||
('district', models.CharField(blank=True, max_length=2, verbose_name='Canton')),
|
||||
('mobile', models.CharField(blank=True, max_length=40, verbose_name='Portable')),
|
||||
('email', models.EmailField(blank=True, max_length=254, verbose_name='Courriel')),
|
||||
('avs', models.CharField(blank=True, max_length=15, verbose_name='No AVS')),
|
||||
('handicap', models.BooleanField(default=False)),
|
||||
('section', models.CharField(choices=[('ASA', 'Aide en soin et accompagnement AFP'), ('ASE', 'Assist. socio-éducatif-ve CFC'), ('ASSC', 'Assist. en soin et santé communautaire CFC'), ('EDE', "Educ. de l'enfance, dipl. ES"), ('EDS', 'Educ. social-e, dipl. ES')], max_length=10, verbose_name='Filière')),
|
||||
('option', models.CharField(blank=True, choices=[('GEN', 'Généraliste'), ('ENF', 'Enfance'), ('PAG', 'Personnes âgées'), ('HAN', 'Handicap'), ('PE-5400h', 'Parcours Emploi 5400h.'), ('PE-3600h', 'Parcours Emploi 3600h.'), ('PS', 'Parcours stage')], max_length=20, verbose_name='Option')),
|
||||
('exemption_ecg', models.BooleanField(default=False)),
|
||||
('validation_sfpo', models.DateField(blank=True, null=True, verbose_name='Confirmation SFPO')),
|
||||
('integration_second_year', models.BooleanField(default=False, verbose_name='Intégration')),
|
||||
('date_confirmation_mail', models.DateField(blank=True, null=True, verbose_name='Mail de confirmation')),
|
||||
('canceled_file', models.BooleanField(default=False, verbose_name='Dossier retiré')),
|
||||
('has_photo', models.BooleanField(default=False, verbose_name='Photo')),
|
||||
('registration_form', models.BooleanField(default=False, verbose_name="Formulaire d'inscription")),
|
||||
('certificate_of_payement', models.BooleanField(default=False, verbose_name='Attest. paiement')),
|
||||
('police_record', models.BooleanField(default=False, verbose_name='Casier judic.')),
|
||||
('cv', models.BooleanField(default=False, verbose_name='CV')),
|
||||
('certif_of_cfc', models.BooleanField(default=False, verbose_name='CFC')),
|
||||
('certif_of_800h', models.BooleanField(default=False, verbose_name='Attest. 800h.')),
|
||||
('reflexive_text', models.BooleanField(default=False, verbose_name='Texte réflexif')),
|
||||
('promise', models.BooleanField(default=False, verbose_name="Promesse d'eng.")),
|
||||
('contract', models.BooleanField(default=False, verbose_name='Contrat valide')),
|
||||
('comment', models.TextField(blank=True, verbose_name='Remarques')),
|
||||
('proc_admin_ext', models.BooleanField(default=False, verbose_name='Insc. autre école')),
|
||||
('work_certificate', models.BooleanField(default=False, verbose_name='Certif. de travail')),
|
||||
('marks_certificate', models.BooleanField(default=False, verbose_name='Bull. notes')),
|
||||
('deposite_date', models.DateField(blank=True, null=True, verbose_name='Date dépôt dossier')),
|
||||
('interview_date', models.DateTimeField(blank=True, null=True, verbose_name='Date entretien prof.')),
|
||||
('interview_room', models.CharField(blank=True, max_length=50, verbose_name="Salle d'entretien prof.")),
|
||||
('examination_result', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='Points examen')),
|
||||
('interview_result', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='Points entretien prof.')),
|
||||
('file_result', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='Points dossier')),
|
||||
('total_result_points', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='Total points')),
|
||||
('total_result_mark', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='Note finale')),
|
||||
('accepted', models.BooleanField(default=False, verbose_name='Admis')),
|
||||
('corporation', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='stages.Corporation', verbose_name='Employeur')),
|
||||
('file_resp', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='stages.Teacher', verbose_name='Exp. dossier')),
|
||||
('instructor', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='stages.CorpContact', verbose_name='FEE/FPP')),
|
||||
('interview_resp', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='stages.Teacher', verbose_name='Exp. entretien')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Candidat',
|
||||
},
|
||||
),
|
||||
]
|
||||
0
candidats/migrations/__init__.py
Normal file
0
candidats/migrations/__init__.py
Normal file
108
candidats/models.py
Normal file
108
candidats/models.py
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
from django.db import models
|
||||
|
||||
|
||||
GENDER_CHOICES = (
|
||||
('M', 'Masculin'),
|
||||
('F', 'Féminin'),
|
||||
('I', 'Inconnu')
|
||||
)
|
||||
|
||||
SECTION_CHOICES = (
|
||||
('ASA', 'Aide en soin et accompagnement AFP'),
|
||||
('ASE', 'Assist. socio-éducatif-ve CFC'),
|
||||
('ASSC', 'Assist. en soin et santé communautaire CFC'),
|
||||
('EDE', 'Educ. de l\'enfance, dipl. ES'),
|
||||
('EDS', 'Educ. social-e, dipl. ES'),
|
||||
)
|
||||
|
||||
OPTION_CHOICES = (
|
||||
('GEN', 'Généraliste'),
|
||||
('ENF', 'Enfance'),
|
||||
('PAG', 'Personnes âgées'),
|
||||
('HAN', 'Handicap'),
|
||||
('PE-5400h', 'Parcours Emploi 5400h.'),
|
||||
('PE-3600h', 'Parcours Emploi 3600h.'),
|
||||
('PS', 'Parcours stage'),
|
||||
)
|
||||
|
||||
class Candidate(models.Model):
|
||||
"""
|
||||
Inscriptions for new students
|
||||
"""
|
||||
first_name = models.CharField('Prénom', max_length=40)
|
||||
last_name = models.CharField('Nom', max_length=40)
|
||||
gender = models.CharField('Genre', max_length=1, choices=GENDER_CHOICES)
|
||||
birth_date = models.DateField('Date de naissance', blank=True, null=True)
|
||||
street = models.CharField('Rue', max_length=150, blank=True)
|
||||
pcode = models.CharField('Code postal', max_length=4)
|
||||
city = models.CharField('Localité', max_length=40)
|
||||
district = models.CharField('Canton', max_length=2, blank=True)
|
||||
mobile = models.CharField('Portable', max_length=40, blank=True)
|
||||
email = models.EmailField('Courriel', blank=True)
|
||||
avs = models.CharField('No AVS', max_length=15, blank=True)
|
||||
handicap = models.BooleanField(default=False)
|
||||
|
||||
section = models.CharField('Filière', max_length=10, choices=SECTION_CHOICES)
|
||||
option = models.CharField('Option', max_length=20, choices=OPTION_CHOICES, blank=True)
|
||||
exemption_ecg = models.BooleanField(default=False)
|
||||
validation_sfpo = models.DateField('Confirmation SFPO', blank=True, null=True)
|
||||
integration_second_year = models.BooleanField('Intégration', default=False)
|
||||
date_confirmation_mail = models.DateField('Mail de confirmation', blank=True, null=True)
|
||||
canceled_file = models.BooleanField('Dossier retiré', default=False)
|
||||
has_photo = models.BooleanField(default=False, verbose_name='Photo')
|
||||
|
||||
corporation = models.ForeignKey(
|
||||
'stages.Corporation', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='Employeur'
|
||||
)
|
||||
instructor = models.ForeignKey(
|
||||
'stages.CorpContact', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='FEE/FPP'
|
||||
)
|
||||
|
||||
# Checking for registration file
|
||||
registration_form = models.BooleanField("Formulaire d'inscription", default=False)
|
||||
certificate_of_payement = models.BooleanField("Attest. paiement", default=False)
|
||||
police_record = models.BooleanField("Casier judic.", default=False)
|
||||
cv = models.BooleanField("CV", default=False)
|
||||
certif_of_cfc = models.BooleanField("CFC", default=False)
|
||||
certif_of_800h = models.BooleanField("Attest. 800h.", default=False)
|
||||
reflexive_text = models.BooleanField("Texte réflexif", default=False)
|
||||
promise = models.BooleanField("Promesse d'eng.", default=False)
|
||||
contract = models.BooleanField("Contrat valide", default=False)
|
||||
comment = models.TextField('Remarques', blank=True)
|
||||
|
||||
proc_admin_ext = models.BooleanField("Insc. autre école", default=False)
|
||||
work_certificate = models.BooleanField("Certif. de travail", default=False)
|
||||
marks_certificate = models.BooleanField("Bull. notes", default=False)
|
||||
deposite_date = models.DateField('Date dépôt dossier', blank=True, null=True)
|
||||
interview_date = models.DateTimeField('Date entretien prof.', blank=True, null=True)
|
||||
interview_room = models.CharField("Salle d'entretien prof.", max_length=50, blank=True)
|
||||
examination_result = models.PositiveSmallIntegerField('Points examen', blank=True, null=True)
|
||||
interview_result = models.PositiveSmallIntegerField('Points entretien prof.', blank=True, null=True)
|
||||
file_result = models.PositiveSmallIntegerField('Points dossier', blank=True, null=True)
|
||||
total_result_points = models.PositiveSmallIntegerField('Total points', blank=True, null=True)
|
||||
total_result_mark = models.PositiveSmallIntegerField('Note finale', blank=True, null=True)
|
||||
|
||||
accepted = models.BooleanField('Admis', default=False)
|
||||
interview_resp = models.ForeignKey(
|
||||
'stages.Teacher', null=True, blank=True, related_name='+', verbose_name='Exp. entretien',
|
||||
on_delete=models.SET_NULL
|
||||
)
|
||||
file_resp = models.ForeignKey(
|
||||
'stages.Teacher', null=True, blank=True, related_name='+', verbose_name='Exp. dossier',
|
||||
on_delete=models.SET_NULL
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Candidat'
|
||||
|
||||
def __str__(self):
|
||||
return "%s %s" % (self.last_name, self.first_name)
|
||||
|
||||
@property
|
||||
def civility(self):
|
||||
if self.gender == 'M':
|
||||
return 'Monsieur'
|
||||
if self.gender == 'F':
|
||||
return 'Madame'
|
||||
else:
|
||||
return ''
|
||||
79
candidats/tests.py
Normal file
79
candidats/tests.py
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
from datetime import date
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core import mail
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from stages.models import Section
|
||||
from .models import Candidate
|
||||
|
||||
|
||||
class CandidateTests(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
User.objects.create_superuser(
|
||||
'me', 'me@example.org', 'mepassword', first_name='Hans', last_name='Schmid',
|
||||
)
|
||||
|
||||
def test_send_confirmation_mail(self):
|
||||
ede = Section.objects.create(name='EDE')
|
||||
ase = Section.objects.create(name='ASE')
|
||||
Candidate.objects.bulk_create([
|
||||
# A mail should NOT be sent for those first 4
|
||||
Candidate(
|
||||
first_name='Sara', last_name='Hitz', gender='F', section=ede,
|
||||
deposite_date=None),
|
||||
Candidate(
|
||||
first_name='Jill', last_name='Simth', gender='F', section=ede,
|
||||
date_confirmation_mail=date.today()),
|
||||
Candidate(first_name='Hervé', last_name='Bern', gender='M', section=ede,
|
||||
canceled_file=True),
|
||||
Candidate(first_name='Frank', last_name='Pit', gender='M', section=ede, email=''),
|
||||
# Good
|
||||
Candidate(first_name='Joé', last_name='Glatz', gender='F', section=ase,
|
||||
email='joe@example.org', deposite_date=date.today()),
|
||||
Candidate(first_name='Henri', last_name='Dupond', gender='M', section=ede,
|
||||
email='henri@example.org', deposite_date=date.today()),
|
||||
])
|
||||
change_url = reverse('admin:candidats_candidate_changelist')
|
||||
self.client.login(username='me', password='mepassword')
|
||||
response = self.client.post(change_url, {
|
||||
'action': 'send_confirmation_mail',
|
||||
'_selected_action': Candidate.objects.values_list('pk', flat=True)
|
||||
}, follow=True)
|
||||
self.assertEqual(len(mail.outbox), 2)
|
||||
self.assertEqual(mail.outbox[0].recipients(), ['henri@example.org'])
|
||||
self.assertEqual(mail.outbox[1].recipients(), ['joe@example.org'])
|
||||
# Mail content differ depending on the section
|
||||
self.assertEqual(mail.outbox[0].body, """Monsieur,
|
||||
|
||||
Par ce courriel, nous vous confirmons la bonne réception de votre dossier de candidature à la formation ES d’Educateur-trice de l’enfance et vous remercions de l’intérêt que vous portez à notre institution.
|
||||
|
||||
Celui-ci sera traité et des nouvelles vous seront communiquées par courriel durant la 2ème quinzaine du mois de février.
|
||||
|
||||
Dans l’intervalle, nous vous adressons, Monsieur, nos salutations les plus cordiales.
|
||||
|
||||
|
||||
Secrétariat de l'EPC
|
||||
tél. 032 886 33 00
|
||||
|
||||
Hans Schmid
|
||||
me@example.org
|
||||
""".format()
|
||||
)
|
||||
self.assertEqual(mail.outbox[1].body, """Madame, Monsieur,
|
||||
|
||||
Nous vous confirmons la bonne réception de l'inscription de Madame Joé Glatz dans la filière ASE pour l'année scolaire à venir.
|
||||
|
||||
Nous nous tenons à votre disposition pour tout renseignement complémentaire et vous prions de recevoir, Madame, Monsieur, nos salutations les plus cordiales.
|
||||
|
||||
Secrétariat de l'EPC
|
||||
tél. 032 886 33 00
|
||||
|
||||
Hans Schmid
|
||||
me@example.org
|
||||
""".format()
|
||||
)
|
||||
# One was already set, 2 new.
|
||||
self.assertEqual(Candidate.objects.filter(date_confirmation_mail__isnull=False).count(), 3)
|
||||
|
|
@ -108,6 +108,7 @@ INSTALLED_APPS = (
|
|||
|
||||
'tabimport',
|
||||
'stages',
|
||||
'candidats',
|
||||
)
|
||||
|
||||
FILE_UPLOAD_HANDLERS = ["django.core.files.uploadhandler.TemporaryFileUploadHandler"]
|
||||
|
|
|
|||
41
stages/exports.py
Normal file
41
stages/exports.py
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
from datetime import date
|
||||
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.cell import get_column_letter
|
||||
from openpyxl.styles import Font, Style
|
||||
from openpyxl.writer.excel import save_virtual_workbook
|
||||
|
||||
from django.http import HttpResponse
|
||||
|
||||
openxml_contenttype = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
|
||||
|
||||
class OpenXMLExport:
|
||||
def __init__(self, sheet_title):
|
||||
self.wb = Workbook()
|
||||
self.ws = self.wb.active
|
||||
self.ws.title = sheet_title
|
||||
self.bold = Style(font=Font(bold=True))
|
||||
self.row_idx = 1
|
||||
|
||||
def write_line(self, values, bold=False, col_widths=()):
|
||||
for col_idx, value in enumerate(values, start=1):
|
||||
cell = self.ws.cell(row=self.row_idx, column=col_idx)
|
||||
try:
|
||||
cell.value = value
|
||||
except KeyError:
|
||||
# Ugly workaround for https://bugs.python.org/issue28969
|
||||
from openpyxl.utils.datetime import to_excel
|
||||
to_excel.cache_clear()
|
||||
cell.value = value
|
||||
if bold:
|
||||
cell.style = self.bold
|
||||
if col_widths:
|
||||
self.ws.column_dimensions[get_column_letter(col_idx)].width = col_widths[col_idx - 1]
|
||||
self.row_idx += 1
|
||||
|
||||
def get_http_response(self, filename_base):
|
||||
response = HttpResponse(save_virtual_workbook(self.wb), content_type=openxml_contenttype)
|
||||
response['Content-Disposition'] = 'attachment; filename=%s_%s.xlsx' % (
|
||||
filename_base, date.strftime(date.today(), '%Y-%m-%d'))
|
||||
return response
|
||||
|
|
@ -143,7 +143,7 @@ class Migration(migrations.Migration):
|
|||
('ext_id', models.IntegerField(null=True, unique=True, verbose_name='ID externe')),
|
||||
('first_name', models.CharField(max_length=40, verbose_name='Prénom')),
|
||||
('last_name', models.CharField(max_length=40, verbose_name='Nom')),
|
||||
('gender', models.CharField(blank=True, max_length=3, verbose_name='Genre')),
|
||||
('gender', models.CharField(blank=True, choices=[('M', 'Masculin'), ('F', 'Féminin')], max_length=3, verbose_name='Genre')),
|
||||
('birth_date', models.DateField(blank=True, verbose_name='Date de naissance')),
|
||||
('street', models.CharField(blank=True, max_length=150, verbose_name='Rue')),
|
||||
('pcode', models.CharField(max_length=4, verbose_name='Code postal')),
|
||||
|
|
|
|||
|
|
@ -148,11 +148,16 @@ class Option(models.Model):
|
|||
return self.name
|
||||
|
||||
|
||||
GENDER_CHOICES = (
|
||||
('M', 'Masculin'),
|
||||
('F', 'Féminin'),
|
||||
)
|
||||
|
||||
class Student(models.Model):
|
||||
ext_id = models.IntegerField(null=True, unique=True, verbose_name='ID externe')
|
||||
first_name = models.CharField(max_length=40, verbose_name='Prénom')
|
||||
last_name = models.CharField(max_length=40, verbose_name='Nom')
|
||||
gender = models.CharField(max_length=3, blank=True, verbose_name='Genre')
|
||||
gender = models.CharField('Genre', max_length=3, blank=True, choices=GENDER_CHOICES)
|
||||
birth_date = models.DateField(blank=True, verbose_name='Date de naissance')
|
||||
street = models.CharField(max_length=150, blank=True, verbose_name='Rue')
|
||||
pcode = models.CharField(max_length=4, verbose_name='Code postal')
|
||||
|
|
@ -279,7 +284,7 @@ class CorpContact(models.Model):
|
|||
verbose_name = "Contact"
|
||||
|
||||
def __str__(self):
|
||||
return '%s %s' % (self.last_name, self.first_name)
|
||||
return '{0} {1}, {2}'.format(self.last_name, self.first_name, self.corporation)
|
||||
|
||||
|
||||
class Domain(models.Model):
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@ class ChargeSheetPDF(SimpleDocTemplate):
|
|||
|
||||
def produce(self, activities):
|
||||
self.story = []
|
||||
self.story.append(Image(find('img/header.gif'), width=520, height=75))
|
||||
header = open(find('img/header.gif'), 'rb')
|
||||
self.story.append(Image(header, width=520, height=75))
|
||||
self.story.append(Spacer(0, 2*cm))
|
||||
destinataire = '{0}<br/>{1}'.format(self.teacher.civility, str(self.teacher))
|
||||
self.story.append(Paragraph(destinataire, style_adress))
|
||||
|
|
@ -80,6 +81,7 @@ class ChargeSheetPDF(SimpleDocTemplate):
|
|||
self.story.append(Paragraph(d, style_normal))
|
||||
self.story.append(PageBreak())
|
||||
self.build(self.story)
|
||||
header.close()
|
||||
|
||||
|
||||
class UpdateDataFormPDF(SimpleDocTemplate):
|
||||
|
|
@ -100,8 +102,9 @@ class UpdateDataFormPDF(SimpleDocTemplate):
|
|||
|
||||
def produce(self, klass):
|
||||
self.story = []
|
||||
header = open(find('img/header.gif'), 'rb')
|
||||
for student in klass.student_set.filter(archived=False):
|
||||
self.story.append(Image(find('img/header.gif'), width=520, height=75))
|
||||
self.story.append(Image(header, width=520, height=75))
|
||||
self.story.append(Spacer(0, 2*cm))
|
||||
destinataire = '{0}<br/>{1}<br/>{2}'.format(student.civility, student.full_name, student.klass)
|
||||
self.story.append(Paragraph(destinataire, style_adress))
|
||||
|
|
@ -180,6 +183,7 @@ class UpdateDataFormPDF(SimpleDocTemplate):
|
|||
self.story.append(Paragraph("Pas d'élèves dans cette classe", style_normal))
|
||||
|
||||
self.build(self.story)
|
||||
header.close()
|
||||
|
||||
def is_corp_required(self, klass_name):
|
||||
return any(el in klass_name for el in ['FE', 'EDS', 'EDEpe'])
|
||||
|
|
|
|||
|
|
@ -274,7 +274,7 @@ class TeacherTests(TestCase):
|
|||
response = self.client.get(reverse('imputations_export'))
|
||||
self.assertEqual(
|
||||
response['Content-Disposition'],
|
||||
'attachment; filename=Imputations_export%s.xlsx' % date.strftime(date.today(), '%Y-%m-%d')
|
||||
'attachment; filename=Imputations_export_%s.xlsx' % date.strftime(date.today(), '%Y-%m-%d')
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
198
stages/views.py
198
stages/views.py
|
|
@ -6,10 +6,6 @@ from collections import OrderedDict
|
|||
from datetime import date, datetime, timedelta
|
||||
|
||||
from tabimport import CSVImportedFile, FileFactory
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.cell import get_column_letter
|
||||
from openpyxl.styles import Font, Style
|
||||
from openpyxl.writer.excel import save_virtual_workbook
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
|
|
@ -21,6 +17,7 @@ from django.urls import reverse
|
|||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic import DetailView, FormView, TemplateView, ListView
|
||||
|
||||
from .exports import OpenXMLExport
|
||||
from .forms import PeriodForm, StudentImportForm, UploadHPFileForm
|
||||
from .models import (
|
||||
Klass, Section, Option, Student, Teacher, Corporation, CorpContact, Course, Period,
|
||||
|
|
@ -29,8 +26,6 @@ from .models import (
|
|||
from .pdf import UpdateDataFormPDF
|
||||
from .utils import is_int
|
||||
|
||||
openxml_contenttype = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
|
||||
|
||||
def school_year_start():
|
||||
""" Return first official day of current school year """
|
||||
|
|
@ -97,39 +92,26 @@ class KlassView(DetailView):
|
|||
if self.request.GET.get('format') != 'xls':
|
||||
return super().render_to_response(context, **response_kwargs)
|
||||
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = self.object.name
|
||||
bold = Style(font=Font(bold=True))
|
||||
headers = [
|
||||
export = OpenXMLExport(self.object.name)
|
||||
# Headers
|
||||
export.write_line([
|
||||
'Nom', 'Prénom', 'Domicile', 'Date de naissance',
|
||||
'Stage 1', 'Domaine 1', 'Stage 2', 'Domaine 2', 'Stage 3', 'Domaine 3',
|
||||
]
|
||||
col_widths = [18, 15, 20, 14, 25, 12, 25, 12, 25, 12]
|
||||
# Headers
|
||||
for col_idx, header in enumerate(headers, start=1):
|
||||
cell = ws.cell(row=1, column=col_idx)
|
||||
cell.value = header
|
||||
cell.style = bold
|
||||
ws.column_dimensions[get_column_letter(col_idx)].width = col_widths[col_idx - 1]
|
||||
], bold=True, col_widths=[18, 15, 20, 14, 25, 12, 25, 12, 25, 12])
|
||||
# Data
|
||||
for row_idx, student in enumerate(context['students'], start=2):
|
||||
ws.cell(row=row_idx, column=1).value = student.last_name
|
||||
ws.cell(row=row_idx, column=2).value = student.first_name
|
||||
ws.cell(row=row_idx, column=3).value = " ".join([student.pcode, student.city])
|
||||
ws.cell(row=row_idx, column=4).value = student.birth_date
|
||||
col_idx = 5
|
||||
for student in context['students']:
|
||||
values = [
|
||||
student.last_name, student.first_name,
|
||||
" ".join([student.pcode, student.city]), student.birth_date,
|
||||
]
|
||||
for training in student.training_set.select_related(
|
||||
'availability', 'availability__corporation', 'availability__domain'
|
||||
).all():
|
||||
ws.cell(row=row_idx, column=col_idx).value = training.availability.corporation.name
|
||||
ws.cell(row=row_idx, column=col_idx + 1).value = training.availability.domain.name
|
||||
col_idx += 2
|
||||
values.append(training.availability.corporation.name)
|
||||
values.append(training.availability.domain.name)
|
||||
export.write_line(values)
|
||||
|
||||
response = HttpResponse(save_virtual_workbook(wb), content_type=openxml_contenttype)
|
||||
response['Content-Disposition'] = 'attachment; filename=%s_export_%s.xlsx' % (
|
||||
self.object.name.replace(' ', '_'), date.strftime(date.today(), '%Y-%m-%d'))
|
||||
return response
|
||||
return export.get_http_response('%s_export' % self.object.name.replace(' ', '_'))
|
||||
|
||||
|
||||
class AttributionView(TemplateView):
|
||||
|
|
@ -629,78 +611,51 @@ def stages_export(request, scope=None):
|
|||
if not default_contacts[contact.corporation.name][sname]:
|
||||
default_contacts[contact.corporation.name][sname] = contact
|
||||
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = 'Stages'
|
||||
bold = Style(font=Font(bold=True))
|
||||
# Headers
|
||||
for col_idx, header in enumerate(export_fields.keys(), start=1):
|
||||
cell = ws.cell(row=1, column=col_idx)
|
||||
cell.value = header
|
||||
cell.style = bold
|
||||
export = OpenXMLExport('Stages')
|
||||
export.write_line(export_fields.keys(), bold=True) # Headers
|
||||
# Data
|
||||
query_keys = [f for f in export_fields.values() if f is not None]
|
||||
for row_idx, tr in enumerate(query.values(*query_keys), start=2):
|
||||
for col_idx, field in enumerate(query_keys, start=1):
|
||||
value = tr[field]
|
||||
for line in query.values(*query_keys):
|
||||
values = []
|
||||
for field in query_keys:
|
||||
value = line[field]
|
||||
if 'gender' in field:
|
||||
value = {'F': 'Madame', 'M': 'Monsieur', '': ''}[value]
|
||||
ws.cell(row=row_idx, column=col_idx).value = value
|
||||
if tr[contact_test_field] is None:
|
||||
values.append(value)
|
||||
if line[contact_test_field] is None:
|
||||
# Use default contact
|
||||
contact = default_contacts.get(tr[corp_name_field], {}).get(tr[export_fields['Filière']])
|
||||
contact = default_contacts.get(line[corp_name_field], {}).get(line[export_fields['Filière']])
|
||||
if contact:
|
||||
contact_col_idx = list(export_fields.keys()).index('Civilité contact') + 1
|
||||
ws.cell(row=row_idx, column=contact_col_idx).value = contact.title
|
||||
ws.cell(row=row_idx, column=contact_col_idx + 1).value = contact.first_name
|
||||
ws.cell(row=row_idx, column=contact_col_idx + 2).value = contact.last_name
|
||||
ws.cell(row=row_idx, column=contact_col_idx + 3).value = contact.ext_id
|
||||
ws.cell(row=row_idx, column=contact_col_idx + 4).value = contact.tel
|
||||
ws.cell(row=row_idx, column=contact_col_idx + 5).value = contact.email
|
||||
if always_ccs[tr[corp_name_field]].get(tr[export_fields['Filière']]):
|
||||
ws.cell(row=row_idx, column=col_idx+1).value = "; ".join(
|
||||
[c.email for c in always_ccs[tr[corp_name_field]].get(tr[export_fields['Filière']])]
|
||||
)
|
||||
values = values[:-6] + [
|
||||
contact.title, contact.first_name, contact.last_name, contact.ext_id,
|
||||
contact.tel, contact.email
|
||||
]
|
||||
if always_ccs[line[corp_name_field]].get(line[export_fields['Filière']]):
|
||||
values.append("; ".join(
|
||||
[c.email for c in always_ccs[line[corp_name_field]].get(line[export_fields['Filière']])]
|
||||
))
|
||||
export.write_line(values)
|
||||
|
||||
response = HttpResponse(save_virtual_workbook(wb), content_type=openxml_contenttype)
|
||||
response['Content-Disposition'] = 'attachment; filename=%s%s.xlsx' % (
|
||||
'stages_export_', date.strftime(date.today(), '%Y-%m-%d'))
|
||||
return response
|
||||
return export.get_http_response('stages_export')
|
||||
|
||||
|
||||
def imputations_export(request):
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = 'Imputations'
|
||||
bold = Style(font=Font(bold=True))
|
||||
for col_idx, header in enumerate(IMPUTATIONS_EXPORT_FIELDS, start=1):
|
||||
cell = ws.cell(row=1, column=col_idx)
|
||||
cell.value = header
|
||||
cell.style = bold
|
||||
export = OpenXMLExport('Imputations')
|
||||
export.write_line(IMPUTATIONS_EXPORT_FIELDS, bold=True) # Headers
|
||||
|
||||
for row_idx, teacher in enumerate(Teacher.objects.filter(archived=False), start=2):
|
||||
for teacher in Teacher.objects.filter(archived=False):
|
||||
activities, imputations = teacher.calc_imputations()
|
||||
ws.cell(row=row_idx, column=1).value = teacher.last_name
|
||||
ws.cell(row=row_idx, column=2).value = teacher.first_name
|
||||
ws.cell(row=row_idx, column=3).value = teacher.previous_report
|
||||
ws.cell(row=row_idx, column=4).value = activities['tot_ens']
|
||||
ws.cell(row=row_idx, column=5).value = 'Ens. prof.'
|
||||
ws.cell(row=row_idx, column=6).value = activities['tot_mandats'] + activities['tot_formation']
|
||||
ws.cell(row=row_idx, column=7).value = 'Accompagnement'
|
||||
ws.cell(row=row_idx, column=8).value = activities['tot_paye']
|
||||
ws.cell(row=row_idx, column=9).value = 'Charge globale'
|
||||
ws.cell(row=row_idx, column=10).value = '{0:.2f}'.format(activities['tot_paye']/21.50)
|
||||
ws.cell(row=row_idx, column=11).value = teacher.next_report
|
||||
values = [
|
||||
teacher.last_name, teacher.first_name, teacher.previous_report,
|
||||
activities['tot_ens'], 'Ens. prof.', activities['tot_mandats'] + activities['tot_formation'],
|
||||
'Accompagnement', activities['tot_paye'], 'Charge globale',
|
||||
'{0:.2f}'.format(activities['tot_paye']/21.50),
|
||||
teacher.next_report,
|
||||
]
|
||||
values.extend(imputations.values())
|
||||
export.write_line(values)
|
||||
|
||||
col_idx = 12
|
||||
for k, v in imputations.items():
|
||||
ws.cell(row=row_idx, column=col_idx).value = v
|
||||
col_idx += 1
|
||||
|
||||
response = HttpResponse(save_virtual_workbook(wb), content_type=openxml_contenttype)
|
||||
response['Content-Disposition'] = 'attachment; filename=%s%s.xlsx' % (
|
||||
'Imputations_export', date.strftime(date.today(), '%Y-%m-%d'))
|
||||
return response
|
||||
return export.get_http_response('Imputations_export')
|
||||
|
||||
|
||||
def print_update_form(request):
|
||||
|
|
@ -769,33 +724,23 @@ def general_export(request):
|
|||
Export all current students data
|
||||
"""
|
||||
export_fields = OrderedDict(GENERAL_EXPORT_FIELDS)
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = 'Exportation'
|
||||
bold = Style(font=Font(bold=True))
|
||||
for col_idx, header in enumerate(export_fields.keys(), start=1):
|
||||
cell = ws.cell(row=1, column=col_idx)
|
||||
cell.value = header
|
||||
cell.style = bold
|
||||
export = OpenXMLExport('Exportation')
|
||||
export.write_line(export_fields.keys(), bold=True) # Headers
|
||||
# Data
|
||||
query_keys = [f for f in export_fields.values() if f is not None]
|
||||
query = Student.objects.filter(archived=False).order_by('klass__name', 'last_name', 'first_name')
|
||||
for row_idx, tr in enumerate(query.values(*query_keys), start=2):
|
||||
for col_idx, field in enumerate(query_keys, start=1):
|
||||
for line in query.values(*query_keys):
|
||||
values = []
|
||||
for field in query_keys:
|
||||
if field == 'gender':
|
||||
tr[field] = ('Madame', 'Monsieur')[tr[field] == 'M']
|
||||
if field == 'dispense_ecg':
|
||||
tr[field] = ('', 'Oui')[tr[field] == 1]
|
||||
if field == 'dispense_eps':
|
||||
tr[field] = ('', 'Oui')[tr[field] == 1]
|
||||
if field == 'soutien_dys':
|
||||
tr[field] = ('', 'Oui')[tr[field] == 1]
|
||||
ws.cell(row=row_idx, column=col_idx).value = tr[field]
|
||||
values.append(('Madame', 'Monsieur')[line[field] == 'M'])
|
||||
elif field in ('dispense_ecg', 'dispense_eps', 'soutien_dys'):
|
||||
values.append('Oui' if line[field] is True else '')
|
||||
else:
|
||||
values.append(line[field])
|
||||
export.write_line(values)
|
||||
|
||||
response = HttpResponse(save_virtual_workbook(wb), content_type=openxml_contenttype)
|
||||
response['Content-Disposition'] = 'attachment; filename=%s%s.xlsx' % (
|
||||
'general_export_', date.strftime(date.today(), '%Y-%m-%d'))
|
||||
return response
|
||||
return export.get_http_response('general_export')
|
||||
|
||||
|
||||
ORTRA_EXPORT_FIELDS = [
|
||||
|
|
@ -836,14 +781,8 @@ def ortra_export(request):
|
|||
Export students data from sections ASAFE, ASEFE and ASSCFE
|
||||
"""
|
||||
export_fields = OrderedDict(ORTRA_EXPORT_FIELDS)
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = 'Exportation'
|
||||
bold = Style(font=Font(bold=True))
|
||||
for col_idx, header in enumerate(export_fields.keys(), start=1):
|
||||
cell = ws.cell(row=1, column=col_idx)
|
||||
cell.value = header
|
||||
cell.style = bold
|
||||
export = OpenXMLExport('Exportation')
|
||||
export.write_line(export_fields.keys(), bold=True) # Headers
|
||||
# Data
|
||||
query_keys = [f for f in export_fields.values() if f is not None]
|
||||
query = Student.objects.filter(Q(klass__name__contains='ASAFE') |
|
||||
|
|
@ -853,14 +792,13 @@ def ortra_export(request):
|
|||
'last_name',
|
||||
'first_name')
|
||||
|
||||
for row_idx, tr in enumerate(query.values(*query_keys), start=2):
|
||||
for col_idx, field in enumerate(query_keys, start=1):
|
||||
for line in query.values(*query_keys):
|
||||
values = []
|
||||
for field in query_keys:
|
||||
if field == 'gender':
|
||||
tr[field] = ('Madame', 'Monsieur')[tr[field] == 'M']
|
||||
ws.cell(row=row_idx, column=col_idx).value = tr[field]
|
||||
|
||||
response = HttpResponse(save_virtual_workbook(wb), content_type=openxml_contenttype)
|
||||
response['Content-Disposition'] = 'attachment; filename=%s%s.xlsx' % (
|
||||
'ortra_export_', date.strftime(date.today(), '%Y-%m-%d'))
|
||||
return response
|
||||
values.append(('Madame', 'Monsieur')[line[field] == 'M'])
|
||||
else:
|
||||
values.append(line[field])
|
||||
export.write_line(values)
|
||||
|
||||
return export.get_http_response('ortra_export')
|
||||
|
|
|
|||
14
templates/email/candidate_confirm_EDE.txt
Normal file
14
templates/email/candidate_confirm_EDE.txt
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{{ candidate_civility }},
|
||||
|
||||
Par ce courriel, nous vous confirmons la bonne réception de votre dossier de candidature à la formation ES d’Educateur-trice de l’enfance et vous remercions de l’intérêt que vous portez à notre institution.
|
||||
|
||||
Celui-ci sera traité et des nouvelles vous seront communiquées par courriel durant la 2ème quinzaine du mois de février.
|
||||
|
||||
Dans l’intervalle, nous vous adressons, {{ candidate_civility }}, nos salutations les plus cordiales.
|
||||
|
||||
|
||||
Secrétariat de l'EPC
|
||||
tél. 032 886 33 00
|
||||
|
||||
{{ sender_name }}
|
||||
{{ sender_email }}
|
||||
11
templates/email/candidate_confirm_FE.txt
Normal file
11
templates/email/candidate_confirm_FE.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
Madame, Monsieur,
|
||||
|
||||
Nous vous confirmons la bonne réception de l'inscription de {{ candidate_name }} dans la filière {{ section }} pour l'année scolaire à venir.
|
||||
|
||||
Nous nous tenons à votre disposition pour tout renseignement complémentaire et vous prions de recevoir, Madame, Monsieur, nos salutations les plus cordiales.
|
||||
|
||||
Secrétariat de l'EPC
|
||||
tél. 032 886 33 00
|
||||
|
||||
{{ sender_name }}
|
||||
{{ sender_email }}
|
||||
Loading…
Add table
Add a link
Reference in a new issue