-
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/formazione/validators.py b/formazione/validators.py
new file mode 100644
index 000000000..206113b89
--- /dev/null
+++ b/formazione/validators.py
@@ -0,0 +1,10 @@
+def validate_file_extension(value):
+ import os
+ from django.core.exceptions import ValidationError
+
+ ext = os.path.splitext(value.name)[1] # [0] returns path+filename
+ valid_extensions = ['zip', 'rar', '.pdf', '.doc', '.docx', '.jpg',
+ '.png', '.xlsx', '.xls']
+ if ext.lower() not in valid_extensions:
+ raise ValidationError("Estensione <%s> di questo file non è "
+ "accettabile." % ext)
\ No newline at end of file
diff --git a/formazione/viste.py b/formazione/viste.py
index ce708238a..bb75dac32 100644
--- a/formazione/viste.py
+++ b/formazione/viste.py
@@ -6,6 +6,7 @@
# from django.template import Context
from django.utils import timezone
from django.shortcuts import redirect, get_object_or_404
+from django.core.urlresolvers import reverse
from django.template.loader import get_template
from anagrafica.models import Persona
@@ -264,20 +265,66 @@ def aspirante_corso_base_lezioni_cancella(request, me, pk, lezione_pk):
lezione.delete()
return redirect(corso.url_lezioni)
+
@pagina_privata
def aspirante_corso_base_modifica(request, me, pk):
+ from .models import CorsoFile, CorsoLink
+ from .forms import CorsoFileFormSet, CorsoLinkFormSet
+
course = get_object_or_404(CorsoBase, pk=pk)
+ course_files = CorsoFile.objects.filter(corso=course)
+ course_links = CorsoLink.objects.filter(corso=course)
+
+ FILEFORM_PREFIX = 'files'
+ LINKFORM_PREFIX = 'links'
+
if not me.permessi_almeno(course, MODIFICA):
return redirect(ERRORE_PERMESSI)
- form = ModuloModificaCorsoBase(request.POST or None, instance=course)
- if form.is_valid():
- form.save()
+ if request.method == 'POST':
+ course_form = ModuloModificaCorsoBase(request.POST, instance=course)
+ file_formset = CorsoFileFormSet(request.POST, request.FILES,
+ queryset=course_files,
+ form_kwargs={'empty_permitted': False},
+ prefix=FILEFORM_PREFIX)
+ link_formset = CorsoLinkFormSet(request.POST,
+ queryset=course_links,
+ prefix=LINKFORM_PREFIX)
+
+ if course_form.is_valid():
+ course_form.save()
+
+ if file_formset.is_valid():
+ file_formset.save(commit=False)
+
+ for obj in file_formset.deleted_objects:
+ obj.delete()
+
+ for form in file_formset:
+ if form.is_valid() and not form.empty_permitted:
+ instance = form.instance
+ instance.corso = course
+ file_formset.save()
+
+ if link_formset.is_valid():
+ link_formset.save(commit=False)
+ for form in link_formset:
+ instance = form.instance
+ instance.corso = course
+ link_formset.save()
+
+ return redirect(reverse('aspirante:modify', args=[pk]))
+ else:
+ course_form = ModuloModificaCorsoBase(instance=course)
+ file_formset = CorsoFileFormSet(queryset=course_files, prefix=FILEFORM_PREFIX)
+ link_formset = CorsoLinkFormSet(queryset=course_links, prefix=LINKFORM_PREFIX)
context = {
- "corso": course,
- "puo_modificare": True,
- "modulo": form,
+ 'corso': course,
+ 'puo_modificare': True,
+ 'modulo': course_form,
+ 'file_formset': file_formset,
+ 'link_formset': link_formset,
}
return 'aspirante_corso_base_scheda_modifica.html', context
@@ -610,8 +657,7 @@ def aspirante_impostazioni_cancella(request, me):
# Cancella!
me.delete()
- return messaggio_generico(request, me,
- titolo="Il tuo profilo è stato cancellato da Gaia",
- messaggio="Abbiamo rimosso tutti i tuoi dati dal nostro sistema. "
- "Se cambierai idea, non esitare a iscriverti nuovamente! "
- )
+ # return messaggio_generico(request, me,
+ # titolo="Il tuo profilo è stato cancellato da Gaia",
+ # messaggio="Abbiamo rimosso tutti i tuoi dati dal nostro sistema. "
+ # "Se cambierai idea, non
\ No newline at end of file
From 0e55d8a9671076ba599c3ec821448e336a1cfa49 Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Fri, 9 Nov 2018 11:33:31 +0100
Subject: [PATCH 020/291] FIXED: last lines were trimmed in formazione.viste
(IDE crash)
---
formazione/viste.py | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/formazione/viste.py b/formazione/viste.py
index bb75dac32..5022a5568 100644
--- a/formazione/viste.py
+++ b/formazione/viste.py
@@ -657,7 +657,8 @@ def aspirante_impostazioni_cancella(request, me):
# Cancella!
me.delete()
- # return messaggio_generico(request, me,
- # titolo="Il tuo profilo è stato cancellato da Gaia",
- # messaggio="Abbiamo rimosso tutti i tuoi dati dal nostro sistema. "
- # "Se cambierai idea, non
\ No newline at end of file
+ return messaggio_generico(request, me,
+ titolo="Il tuo profilo è stato cancellato da Gaia",
+ messaggio="Abbiamo rimosso tutti i tuoi dati dal nostro sistema. "
+ "Se cambierai idea, non esitare a iscriverti nuovamente! "
+ )
\ No newline at end of file
From 1e6756080975fcd3f11ccc1d8f9f1a54f49dbfda Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Tue, 13 Nov 2018 17:00:00 +0100
Subject: [PATCH 021/291] ADDED: registered admin classes for
formazione.Title\TitleLevel\TitleGoal classes.
---
formazione/admin.py | 37 +++++++++++++++++++++++++++++++++++--
1 file changed, 35 insertions(+), 2 deletions(-)
diff --git a/formazione/admin.py b/formazione/admin.py
index 51f3e8b34..68aad29de 100755
--- a/formazione/admin.py
+++ b/formazione/admin.py
@@ -5,8 +5,9 @@
from anagrafica.models import Delega
from base.admin import InlineAutorizzazione
from gruppi.readonly_admin import ReadonlyAdminMixin
-from .models import (CorsoBase, CorsoFile, CorsoLink, Aspirante,
- PartecipazioneCorsoBase, AssenzaCorsoBase, LezioneCorsoBase, InvitoCorsoBase)
+from .models import (CorsoBase, CorsoFile, CorsoLink,
+ Aspirante, PartecipazioneCorsoBase, AssenzaCorsoBase, LezioneCorsoBase,
+ InvitoCorsoBase, FormazioneTitleGoal, FormazioneTitleLevel, FormazioneTitle)
__author__ = 'alfioemanuele'
@@ -19,6 +20,38 @@
RAW_ID_FIELDS_ASPIRANTE = ['persona', 'locazione',]
+@admin.register(FormazioneTitle)
+class AdminTitle(admin.ModelAdmin):
+ list_display = ['name', 'livello_name', 'goal_obbiettivo_stragetico',
+ 'goal_propedeuticita', 'goal_unit_reference']
+ list_filter = ['livello__goal__unit_reference',]
+
+ def livello_name(self, obj):
+ return obj.livello.name
+
+ def goal_obbiettivo_stragetico(self, obj):
+ return obj.livello.goal_obbiettivo_stragetico
+
+ def goal_propedeuticita(self, obj):
+ return obj.livello.goal_propedeuticita
+
+ def goal_unit_reference(self, obj):
+ return obj.livello.goal_unit_reference
+
+
+@admin.register(FormazioneTitleLevel)
+class AdminTitleLevel(admin.ModelAdmin):
+ list_display = ['name', 'goal_obbiettivo_stragetico',
+ 'goal_propedeuticita', 'goal_unit_reference']
+ list_filter = ['goal',]
+
+
+@admin.register(FormazioneTitleGoal)
+class AdminTitleGoal(admin.ModelAdmin):
+ list_display = ['__str__', 'obbiettivo_stragetico', 'propedeuticita', 'unit_reference']
+ list_filter = ['unit_reference',]
+
+
class InlineDelegaCorsoBase(ReadonlyAdminMixin, GenericTabularInline):
model = Delega
raw_id_fields = RAW_ID_FIELDS_DELEGA
From fed5e040d879526c0fd960ffeb91b9663f57232b Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Tue, 13 Nov 2018 17:10:26 +0100
Subject: [PATCH 022/291] NEW: (JO-756) CorsoEstensione, FormazioneTitle,
FormazioneTitleLevel, FormazioneTitleGoal models. New section Estensioni with
new view/forms/templates. Migration 0024. Related commit 1e67560 with new
registered admin classes.
---
formazione/admin.py | 8 +-
formazione/autocomplete_light_registry.py | 19 +++
formazione/forms.py | 37 +++++-
.../migrations/0024_auto_20181113_1644.py | 53 ++++++++
formazione/models.py | 113 ++++++++++++++++++
.../aspirante_corso_base_scheda.html | 30 ++---
.../aspirante_corso_estensioni_modifica.html | 57 +++++++++
formazione/urls_aspirante.py | 4 +-
formazione/viste.py | 87 +++++++++++++-
9 files changed, 387 insertions(+), 21 deletions(-)
create mode 100644 formazione/autocomplete_light_registry.py
create mode 100644 formazione/migrations/0024_auto_20181113_1644.py
create mode 100644 formazione/templates/aspirante_corso_estensioni_modifica.html
diff --git a/formazione/admin.py b/formazione/admin.py
index 68aad29de..05d42b806 100755
--- a/formazione/admin.py
+++ b/formazione/admin.py
@@ -5,7 +5,7 @@
from anagrafica.models import Delega
from base.admin import InlineAutorizzazione
from gruppi.readonly_admin import ReadonlyAdminMixin
-from .models import (CorsoBase, CorsoFile, CorsoLink,
+from .models import (CorsoBase, CorsoFile, CorsoEstensione, CorsoLink,
Aspirante, PartecipazioneCorsoBase, AssenzaCorsoBase, LezioneCorsoBase,
InvitoCorsoBase, FormazioneTitleGoal, FormazioneTitleLevel, FormazioneTitle)
@@ -116,6 +116,12 @@ class AdminCorsoLink(admin.ModelAdmin):
raw_id_fields = ('corso',)
+@admin.register(CorsoEstensione)
+class AdminCorsoEstensione(admin.ModelAdmin):
+ list_display = ['corso', 'is_active', 'segmento', 'sedi_sottostanti']
+ raw_id_fields = ['sede', 'titolo',]
+
+
@admin.register(PartecipazioneCorsoBase)
class AdminPartecipazioneCorsoBase(ReadonlyAdminMixin, admin.ModelAdmin):
search_fields = ['persona__nome', 'persona__cognome', 'persona__codice_fiscale', 'corso__progressivo', ]
diff --git a/formazione/autocomplete_light_registry.py b/formazione/autocomplete_light_registry.py
new file mode 100644
index 000000000..e77e58419
--- /dev/null
+++ b/formazione/autocomplete_light_registry.py
@@ -0,0 +1,19 @@
+from autocomplete_light import shortcuts as autocomplete_light
+
+from anagrafica.autocomplete_light_registry import AutocompletamentoBase
+from anagrafica.models import Sede
+from .models import FormazioneTitle
+
+
+class EstensioneLivelloRegionaleTitolo(AutocompletamentoBase):
+ search_fields = ['name',]
+ model = FormazioneTitle
+
+
+class EstensioneLivelloRegionaleSede(AutocompletamentoBase):
+ search_fields = ['nome',]
+ model = Sede
+
+
+autocomplete_light.register(EstensioneLivelloRegionaleTitolo)
+autocomplete_light.register(EstensioneLivelloRegionaleSede)
diff --git a/formazione/forms.py b/formazione/forms.py
index 7d848432a..24d7f36db 100644
--- a/formazione/forms.py
+++ b/formazione/forms.py
@@ -5,8 +5,8 @@
from autocomplete_light import shortcuts as autocomplete_light
from base.wysiwyg import WYSIWYGSemplice
-from .models import (CorsoBase, CorsoLink, CorsoFile, LezioneCorsoBase,
- PartecipazioneCorsoBase)
+from .models import (CorsoBase, CorsoLink, CorsoFile, CorsoEstensione,
+ LezioneCorsoBase, PartecipazioneCorsoBase)
class ModuloCreazioneCorsoBase(ModelForm):
@@ -110,6 +110,39 @@ class ModuloIscrittiCorsoBaseAggiungi(forms.Form):
"CRI da iscrivere a questo Corso Base.")
+class CorsoSelectExtensionTypeForm(ModelForm):
+ class Meta:
+ model = CorsoBase
+ fields = ['extension_type',]
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.fields['extension_type'].choices = CorsoBase.EXTENSION_TYPE_CHOICES
+
+ if not self.instance.get_course_extensions(is_active=True).count():
+ # Useful to set EXT_MIA_SEDE as select's default value to avoid
+ # possible issues with ExtensionFormSet
+ self.initial['extension_type'] = CorsoBase.EXT_MIA_SEDE
+
+class CorsoExtensionForm(ModelForm):
+ titolo = autocomplete_light.ModelMultipleChoiceField(
+ "EstensioneLivelloRegionaleTitolo", required=False)
+ sede = autocomplete_light.ModelMultipleChoiceField(
+ "EstensioneLivelloRegionaleSede", required=False)
+
+ class Meta:
+ model = CorsoEstensione
+ fields = ['segmento', 'titolo', 'sede', 'sedi_sottostanti',]
+
+ def __init__(self, *args, **kwargs):
+ self.corso = kwargs.pop('corso')
+ super().__init__(*args, **kwargs)
+
+
+CorsoSelectExtensionFormSet = modelformset_factory(CorsoEstensione, extra=1,
+ max_num=2, form=CorsoExtensionForm, can_delete=True)
+
+
class ModuloConfermaIscrizioneCorsoBase(forms.Form):
conferma_1 = forms.BooleanField(label="Ho incontrato questo aspirante, ad esempio alla presentazione del "
"corso, e mi ha chiesto di essere iscritto al Corso.")
diff --git a/formazione/migrations/0024_auto_20181113_1644.py b/formazione/migrations/0024_auto_20181113_1644.py
new file mode 100644
index 000000000..922b6b551
--- /dev/null
+++ b/formazione/migrations/0024_auto_20181113_1644.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.12 on 2018-11-13 16:44
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('anagrafica', '0049_auto_20181028_1639'),
+ ('formazione', '0023_auto_20181109_1652'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='CorsoEstensione',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('creazione', models.DateTimeField(db_index=True, default=django.utils.timezone.now)),
+ ('ultima_modifica', models.DateTimeField(auto_now=True, db_index=True)),
+ ('is_active', models.BooleanField(db_index=True, default=True)),
+ ('segmento', models.CharField(blank=True, choices=[('A', 'Tutti gli utenti di Gaia'), ('B', 'Volontari'), ('C', 'Volontari da meno di un anno'), ('D', 'Volontari da più di un anno'), ('E', 'Volontari con meno di 33 anni'), ('F', 'Volontari con 33 anni o più'), ('G', 'Sostenitori CRI'), ('H', 'Aspiranti volontari iscritti a un corso'), ('I', 'Tutti i Presidenti'), ('J', 'Presidenti di Comitati Locali'), ('K', 'Presidenti di Comitati Regionali'), ('L', 'Delegati US'), ('M', 'Delegati Obiettivo I'), ('N', 'Delegati Obiettivo II'), ('O', 'Delegati Obiettivo III'), ('P', 'Delegati Obiettivo IV'), ('Q', 'Delegati Obiettivo V'), ('R', 'Delegati Obiettivo VI'), ('S', 'Referenti di un’Attività di Area I'), ('T', 'Referenti di un’Attività di Area II'), ('U', 'Referenti di un’Attività di Area III'), ('V', 'Referenti di un’Attività di Area IV'), ('W', 'Referenti di un’Attività di Area V'), ('X', 'Referenti di un’Attività di Area VI'), ('Y', 'Delegati Autoparco'), ('Z', 'Delegati Formazione'), ('AA', 'Volontari aventi un dato titolo')], max_length=9)),
+ ('sedi_sottostanti', models.BooleanField(db_index=True, default=False)),
+ ],
+ options={
+ 'verbose_name': 'Estensione del Corso',
+ 'verbose_name_plural': 'Estensioni del Corso',
+ },
+ ),
+ migrations.AddField(
+ model_name='corsobase',
+ name='extension_type',
+ field=models.CharField(blank=True, choices=[('1', 'Solo su mia sede di appartenenza'), ('2', 'A livello regionale')], max_length=5, null=True),
+ ),
+ migrations.AddField(
+ model_name='corsoestensione',
+ name='corso',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='formazione.CorsoBase'),
+ ),
+ migrations.AddField(
+ model_name='corsoestensione',
+ name='sede',
+ field=models.ManyToManyField(to='anagrafica.Sede'),
+ ),
+ migrations.AddField(
+ model_name='corsoestensione',
+ name='titolo',
+ field=models.ManyToManyField(blank=True, to='formazione.FormazioneTitle'),
+ ),
+ ]
diff --git a/formazione/models.py b/formazione/models.py
index 0b521c1c4..93a194aae 100755
--- a/formazione/models.py
+++ b/formazione/models.py
@@ -23,6 +23,77 @@
from social.models import ConCommenti, ConGiudizio
+class FormazioneTitle(ModelloSemplice, ConMarcaTemporale):
+ name = models.CharField('Nome del corso', max_length=255)
+ livello = models.ForeignKey('FormazioneTitleLevel', null=True, blank=True,
+ verbose_name="Livello", on_delete=models.PROTECT)
+
+ class Meta:
+ verbose_name = 'Titolo'
+ verbose_name_plural = 'Titoli'
+
+ def __str__(self):
+ return str(self.name)
+
+
+class FormazioneTitleLevel(models.Model):
+ name = models.CharField('Nome', max_length=255)
+ goal = models.ForeignKey('FormazioneTitleGoal', null=True, blank=True)
+
+ @property
+ def goal_obbiettivo_stragetico(self):
+ return self.goal.unit_reference
+
+ @property
+ def goal_propedeuticita(self):
+ return self.goal.propedeuticita
+
+ @property
+ def goal_unit_reference(self):
+ return self.goal.get_unit_reference_display()
+
+ def __str__(self):
+ return "%s - %s" % (self.name, self.goal)
+
+ class Meta:
+ verbose_name = 'Titolo: Livello'
+ verbose_name_plural = 'Titoli: Livelli'
+
+
+class FormazioneTitleGoal(models.Model):
+ OBBIETTIVO_STRATEGICO_SALUTE = '1'
+ OBBIETTIVO_STRATEGICO_SOCIALE = '2'
+ OBBIETTIVO_STRATEGICO_EMERGENZA = '3'
+ OBBIETTIVO_STRATEGICO_ADVOCACY = '4'
+ OBBIETTIVO_STRATEGICO_GIOVANI = '5'
+ OBBIETTIVO_STRATEGICO_SVILUPPO = '6'
+
+ OBBIETTIVI_STRATEGICI = (
+ (OBBIETTIVO_STRATEGICO_SALUTE, 'Salute'),
+ (OBBIETTIVO_STRATEGICO_SOCIALE, 'Sociale'),
+ (OBBIETTIVO_STRATEGICO_EMERGENZA, 'Emergenza)'),
+ (OBBIETTIVO_STRATEGICO_ADVOCACY, 'Advocacy e mediazione umanitaria'),
+ (OBBIETTIVO_STRATEGICO_GIOVANI, 'Giovani'),
+ (OBBIETTIVO_STRATEGICO_GIOVANI, 'Sviluppo'),
+ # 'Operatore CRI Attività di Emergenza (OPEM)',
+ )
+ unit_reference = models.CharField("Unità riferimento", max_length=3,
+ null=True, blank=True, choices=OBBIETTIVI_STRATEGICI)
+ propedeuticita = models.CharField("Propedeuticità", max_length=255,
+ null=True, blank=True)
+
+ @property
+ def obbiettivo_stragetico(self):
+ return self.unit_reference
+
+ def __str__(self):
+ return "%s (Obbiettivo %s)" % (self.propedeuticita, self.unit_reference)
+
+ class Meta:
+ verbose_name = 'Titolo: Propedeuticità'
+ verbose_name_plural = 'Titoli: Propedeuticità'
+
+
class Corso(ModelloSemplice, ConDelegati, ConMarcaTemporale,
ConGeolocalizzazione, ConCommenti, ConGiudizio):
# Tipologia di corso
@@ -89,6 +160,13 @@ def __str__(self):
class CorsoBase(Corso, ConVecchioID, ConPDF):
MAX_PARTECIPANTI = 30
+ EXT_MIA_SEDE = '1'
+ EXT_LVL_REGIONALE = '2'
+ EXTENSION_TYPE_CHOICES = [
+ (EXT_MIA_SEDE, "Solo su mia sede di appartenenza"),
+ (EXT_LVL_REGIONALE, "A livello regionale"),
+ ]
+
data_inizio = models.DateTimeField(blank=False, null=False,
help_text="La data di inizio del corso. "
"Utilizzata per la gestione delle iscrizioni.")
@@ -103,6 +181,8 @@ class CorsoBase(Corso, ConVecchioID, ConPDF):
max_length=255, blank=True, null=True)
op_convocazione = models.CharField('Ordinanza presidenziale convocazione',
max_length=255, blank=True, null=True)
+ extension_type = models.CharField(max_length=5, blank=True, null=True,
+ choices=EXTENSION_TYPE_CHOICES)
PUOI_ISCRIVERTI_OK = "IS"
PUOI_ISCRIVERTI = (PUOI_ISCRIVERTI_OK,)
@@ -439,6 +519,10 @@ def key_cognome(elem):
)
return pdf
+ def get_course_extensions(self, **kwargs):
+ """Returns CorsoEstensione objects related to the course"""
+ return self.corsoestensione_set.filter(**kwargs)
+
class Meta:
verbose_name = "Corso Base"
verbose_name_plural = "Corsi Base"
@@ -451,6 +535,35 @@ def __str__(self):
return str(self.nome)
+class CorsoEstensione(ConMarcaTemporale):
+ from segmenti.segmenti import NOMI_SEGMENTI
+
+
+ corso = models.ForeignKey(CorsoBase, db_index=True)
+ is_active = models.BooleanField(default=True, db_index=True)
+ segmento = models.CharField(max_length=9, choices=NOMI_SEGMENTI, blank=True)
+ titolo = models.ManyToManyField(FormazioneTitle, blank=True)
+ sede = models.ManyToManyField(Sede)
+ sedi_sottostanti = models.BooleanField(default=False, db_index=True)
+
+ class Meta:
+ verbose_name = 'Estensione del Corso'
+ verbose_name_plural = 'Estensioni del Corso'
+
+ def __str__(self):
+ return '%s' % self.corso if hasattr(self, 'corso') else 'No CorsoBase set.'
+
+ def visible_by_extension_type(self):
+ type = self.corso.extension_type
+ if type == CorsoBase.EXT_MIA_SEDE:
+ self.is_active = False
+ elif type == CorsoBase.EXT_LVL_REGIONALE:
+ self.is_active = True
+
+ def save(self):
+ self.visible_by_extension_type()
+ super().save()
+
class InvitoCorsoBase(ModelloSemplice, ConAutorizzazioni,
ConMarcaTemporale, models.Model):
persona = models.ForeignKey(Persona, related_name='inviti_corsi', on_delete=models.CASCADE)
diff --git a/formazione/templates/aspirante_corso_base_scheda.html b/formazione/templates/aspirante_corso_base_scheda.html
index 3b5f3759b..6030b28fe 100644
--- a/formazione/templates/aspirante_corso_base_scheda.html
+++ b/formazione/templates/aspirante_corso_base_scheda.html
@@ -8,7 +8,7 @@
{% block pagina_titolo %}
{% block scheda_titolo %}{% endblock %}
{{ corso.nome }} ({{ corso.sede.nome_completo }})
- — Corso Base
+ — Corso
{% endblock %}
@@ -51,7 +51,7 @@
Non c'è tempo da perdere!
{% if puo_modificare and corso.stato == corso.PREPARAZIONE %}
Il corso non è ancora attivo
-
Questo corso base è ancora una bozza ("In Preparazione"). È necessario
+
Questo corso è ancora una bozza ("In Preparazione"). È necessario
attivare il corso affinché questo possa essere trovato dagli aspiranti.
Completa i passi necessari e clicca sul pulsante per attivare il corso:
+
+
+
+{% endblock %}
diff --git a/formazione/urls_aspirante.py b/formazione/urls_aspirante.py
index 8254db14c..c5d1246df 100644
--- a/formazione/urls_aspirante.py
+++ b/formazione/urls_aspirante.py
@@ -9,7 +9,7 @@
aspirante_corso_base_report_schede, aspirante_corso_base_firme,
aspirante_corso_base_modifica, aspirante_corso_base_attiva,
aspirante_corso_base_termina, aspirante_corso_base_lezioni,
- aspirante_corso_base_lezioni_cancella)
+ aspirante_corso_base_lezioni_cancella, aspirante_corso_estensioni_modifica)
url_shortcut = 'corso-base/(?P[0-9]+)'
@@ -50,4 +50,6 @@
name='formazione_iscritti_cancella'),
url(rf'^{url_shortcut}/iscriviti/$', aspirante_corso_base_iscriviti,
name='subscribe'),
+ url(rf'^{url_shortcut}/estensioni/$', aspirante_corso_estensioni_modifica,
+ name='estensioni_modifica'),
]
diff --git a/formazione/viste.py b/formazione/viste.py
index 5022a5568..eec76a872 100644
--- a/formazione/viste.py
+++ b/formazione/viste.py
@@ -21,8 +21,8 @@
from posta.models import Messaggio
from .elenchi import ElencoPartecipantiCorsiBase
from .decorators import access_to_courses
-from .models import (Corso, CorsoBase, AssenzaCorsoBase, LezioneCorsoBase,
- PartecipazioneCorsoBase, Aspirante, InvitoCorsoBase)
+from .models import (Corso, CorsoBase, CorsoEstensione, AssenzaCorsoBase,
+ LezioneCorsoBase, PartecipazioneCorsoBase, Aspirante, InvitoCorsoBase)
from .forms import (ModuloCreazioneCorsoBase, ModuloModificaLezione,
ModuloModificaCorsoBase, ModuloIscrittiCorsoBaseAggiungi,
ModuloVerbaleAspiranteCorsoBase)
@@ -120,6 +120,7 @@ def formazione_corsi_base_fine(request, me, pk):
}
return 'formazione_corsi_base_fine.html', contesto
+
@pagina_pubblica
def aspirante_corso_base_informazioni(request, me=None, pk=None):
corso = get_object_or_404(CorsoBase, pk=pk)
@@ -661,4 +662,84 @@ def aspirante_impostazioni_cancella(request, me):
titolo="Il tuo profilo è stato cancellato da Gaia",
messaggio="Abbiamo rimosso tutti i tuoi dati dal nostro sistema. "
"Se cambierai idea, non esitare a iscriverti nuovamente! "
- )
\ No newline at end of file
+ )
+
+
+@pagina_privata
+def aspirante_corso_estensioni_modifica(request, me, pk):
+ from .forms import CorsoSelectExtensionTypeForm, CorsoSelectExtensionFormSet
+
+ SELECT_EXTENSION_TYPE_FORM_PREFIX = 'extension_type'
+ SELECT_EXTENSIONS_FORMSET_PREFIX = 'extensions'
+
+ course = get_object_or_404(CorsoBase, pk=pk)
+ if not me.permessi_almeno(course, MODIFICA):
+ return redirect(ERRORE_PERMESSI)
+
+ if not course.tipo == Corso.CORSO_NUOVO:
+ # The page is not accessible if the type of course is not CORSO_NUOVO
+ return redirect(ERRORE_PERMESSI)
+
+ if request.method == 'POST':
+ select_extension_type_form = CorsoSelectExtensionTypeForm(request.POST,
+ instance=course,
+ prefix=SELECT_EXTENSION_TYPE_FORM_PREFIX)
+ select_extensions_formset = CorsoSelectExtensionFormSet(request.POST,
+ prefix=SELECT_EXTENSIONS_FORMSET_PREFIX,
+ form_kwargs={'corso': course})
+
+ if select_extension_type_form.is_valid() and \
+ select_extensions_formset.is_valid():
+ select_extensions_formset.save(commit=False)
+
+ for form in select_extensions_formset:
+ if form.is_valid:
+ cd = form.cleaned_data
+ instance = form.save(commit=False)
+ instance.corso = course
+ corso = instance.corso
+
+ # Skip blank extra formset
+ if cd == {} and len(select_extensions_formset) >= 1:
+ continue
+
+ # Do validation only with specified extension type
+ if corso.extension_type == CorsoBase.EXT_LVL_REGIONALE:
+ msg = 'Questo campo è obbligatorio.'
+ if not cd.get('sede'):
+ form.add_error('sede', msg)
+ if not cd.get('segmento'):
+ form.add_error('segmento', msg)
+ if cd.get('sedi_sottostanti') and not cd.get('sede'):
+ form.add_error('sede', 'Seleziona una sede')
+
+ # No errors nor new added error - save form instance
+ if not form.errors:
+ instance.save()
+
+ # Return form with error without saving
+ if any(select_extensions_formset.errors):
+ pass
+ else:
+ # Save all forms and redirect to the same page.
+ select_extension_type_form.save()
+ select_extensions_formset.save()
+ return redirect(reverse('aspirante:estensioni_modifica', args=[pk]))
+
+ else:
+ select_extension_type_form = CorsoSelectExtensionTypeForm(
+ prefix=SELECT_EXTENSION_TYPE_FORM_PREFIX,
+ instance=course
+ )
+ select_extensions_formset = CorsoSelectExtensionFormSet(
+ prefix=SELECT_EXTENSIONS_FORMSET_PREFIX,
+ form_kwargs={'corso': course},
+ )
+
+ context = {
+ 'corso': course,
+ 'puo_modificare': True,
+ 'select_extension_type_form': select_extension_type_form,
+ 'select_extensions_formset': select_extensions_formset,
+ }
+ return 'aspirante_corso_estensioni_modifica.html', context
From dbbd13f022c9b230cd82b9d5de6f357bda01b530 Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Tue, 13 Nov 2018 17:12:19 +0100
Subject: [PATCH 023/291] FIXED: OBBIETTIVO_STRATEGICO_SVILUPPO choice in
OBBIETTIVI_STRATEGICI choices
---
formazione/models.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/formazione/models.py b/formazione/models.py
index 93a194aae..3a75fddf0 100755
--- a/formazione/models.py
+++ b/formazione/models.py
@@ -74,7 +74,7 @@ class FormazioneTitleGoal(models.Model):
(OBBIETTIVO_STRATEGICO_EMERGENZA, 'Emergenza)'),
(OBBIETTIVO_STRATEGICO_ADVOCACY, 'Advocacy e mediazione umanitaria'),
(OBBIETTIVO_STRATEGICO_GIOVANI, 'Giovani'),
- (OBBIETTIVO_STRATEGICO_GIOVANI, 'Sviluppo'),
+ (OBBIETTIVO_STRATEGICO_SVILUPPO, 'Sviluppo'),
# 'Operatore CRI Attività di Emergenza (OPEM)',
)
unit_reference = models.CharField("Unità riferimento", max_length=3,
From 2ab431e3669b9ff932f3941c160d88c22f4786a4 Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Wed, 14 Nov 2018 14:17:34 +0100
Subject: [PATCH 024/291] NEW: fields max/min participants in CorsoBase with
changes if forms/viste, migration (0025); 0022/0023 migrations were already
commited (in 6772f47d, 364a752)
---
formazione/forms.py | 26 +++++++-
.../migrations/0022_auto_20181109_1639.py | 59 +++++++++++++++++++
.../migrations/0023_auto_20181109_1652.py | 21 +++++++
.../migrations/0025_auto_20181114_1410.py | 31 ++++++++++
formazione/models.py | 10 +++-
formazione/viste.py | 4 +-
6 files changed, 146 insertions(+), 5 deletions(-)
create mode 100644 formazione/migrations/0022_auto_20181109_1639.py
create mode 100644 formazione/migrations/0023_auto_20181109_1652.py
create mode 100644 formazione/migrations/0025_auto_20181114_1410.py
diff --git a/formazione/forms.py b/formazione/forms.py
index 24d7f36db..b0cd5ede1 100644
--- a/formazione/forms.py
+++ b/formazione/forms.py
@@ -5,7 +5,7 @@
from autocomplete_light import shortcuts as autocomplete_light
from base.wysiwyg import WYSIWYGSemplice
-from .models import (CorsoBase, CorsoLink, CorsoFile, CorsoEstensione,
+from .models import (Corso, CorsoBase, CorsoLink, CorsoFile, CorsoEstensione,
LezioneCorsoBase, PartecipazioneCorsoBase)
@@ -37,7 +37,7 @@ class Meta:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- self.order_fields(('tipo', 'data_inizio', 'sede', 'locazione'))
+ self.order_fields(('tipo', 'data_inizio', 'sede', 'locazione'))
class ModuloModificaLezione(ModelForm):
@@ -62,13 +62,33 @@ def clean(self):
class ModuloModificaCorsoBase(ModelForm):
class Meta:
model = CorsoBase
- fields = ['data_inizio', 'data_esame', 'descrizione',
+ fields = ['data_inizio', 'data_esame',
+ 'min_participants', 'max_participants',
+ 'descrizione',
'data_attivazione', 'data_convocazione',
'op_attivazione', 'op_convocazione',]
widgets = {
"descrizione": WYSIWYGSemplice(),
}
+ def clean(self):
+ cd = self.cleaned_data
+ if 'min_participants' in cd and 'max_participants' in cd:
+ min, max = cd['min_participants'], cd['max_participants']
+ if min > max:
+ self.add_error('min_participants', "Numero minimo di "
+ "partecipanti non può essere maggiore del numero massimo.")
+ if max < min:
+ self.add_error('max_participants', "Numero massimo di "
+ "partecipanti non può essere minore del numero minimo.")
+ return cd
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ if self.instance.tipo != Corso.CORSO_NUOVO:
+ self.fields.pop('min_participants')
+ self.fields.pop('max_participants')
+
class CorsoLinkForm(ModelForm):
class Meta:
diff --git a/formazione/migrations/0022_auto_20181109_1639.py b/formazione/migrations/0022_auto_20181109_1639.py
new file mode 100644
index 000000000..c8c5c8c45
--- /dev/null
+++ b/formazione/migrations/0022_auto_20181109_1639.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.12 on 2018-11-09 16:39
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('formazione', '0021_corsofile_corsolink'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='FormazioneTitle',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('creazione', models.DateTimeField(db_index=True, default=django.utils.timezone.now)),
+ ('ultima_modifica', models.DateTimeField(auto_now=True, db_index=True)),
+ ('name', models.CharField(max_length=255, verbose_name='Nome del corso')),
+ ],
+ options={
+ 'verbose_name': 'Titolo',
+ 'verbose_name_plural': 'Titoli',
+ },
+ ),
+ migrations.CreateModel(
+ name='FormazioneTitleGoal',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('unit_reference', models.CharField(blank=True, choices=[('1', 'Salute'), ('2', 'Sociale'), ('3', 'Emergenza)'), ('4', 'Advocacy e mediazione umanitaria'), ('5', 'Giovani'), ('5', 'Sviluppo')], max_length=3, null=True, verbose_name='Unità riferimento')),
+ ('propedeuticita', models.CharField(blank=True, max_length=255, null=True, verbose_name='Propedeuticità')),
+ ],
+ options={
+ 'verbose_name': 'Titolo: Propedeuticità',
+ 'verbose_name_plural': 'Titoli: Propedeuticità',
+ },
+ ),
+ migrations.CreateModel(
+ name='FormazioneTitleLevel',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=255, verbose_name='Nome')),
+ ('goal', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='formazione.FormazioneTitleGoal')),
+ ],
+ options={
+ 'verbose_name': 'Titolo: Livello',
+ 'verbose_name_plural': 'Titoli: Livelli',
+ },
+ ),
+ migrations.AddField(
+ model_name='formazionetitle',
+ name='livello',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='formazione.FormazioneTitleLevel', verbose_name='Livello'),
+ ),
+ ]
diff --git a/formazione/migrations/0023_auto_20181109_1652.py b/formazione/migrations/0023_auto_20181109_1652.py
new file mode 100644
index 000000000..a7f9e77ed
--- /dev/null
+++ b/formazione/migrations/0023_auto_20181109_1652.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.12 on 2018-11-09 16:52
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('formazione', '0022_auto_20181109_1639'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='formazionetitle',
+ name='livello',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='formazione.FormazioneTitleLevel', verbose_name='Livello'),
+ ),
+ ]
diff --git a/formazione/migrations/0025_auto_20181114_1410.py b/formazione/migrations/0025_auto_20181114_1410.py
new file mode 100644
index 000000000..3e59848de
--- /dev/null
+++ b/formazione/migrations/0025_auto_20181114_1410.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.12 on 2018-11-14 14:10
+from __future__ import unicode_literals
+
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('formazione', '0024_auto_20181113_1644'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='corsobase',
+ name='max_participants',
+ field=models.SmallIntegerField(default=50, verbose_name='Massimo partecipanti'),
+ ),
+ migrations.AddField(
+ model_name='corsobase',
+ name='min_participants',
+ field=models.SmallIntegerField(default=20, validators=[django.core.validators.MinValueValidator(20)], verbose_name='Minimo partecipanti'),
+ ),
+ migrations.AlterField(
+ model_name='formazionetitlegoal',
+ name='unit_reference',
+ field=models.CharField(blank=True, choices=[('1', 'Salute'), ('2', 'Sociale'), ('3', 'Emergenza)'), ('4', 'Advocacy e mediazione umanitaria'), ('5', 'Giovani'), ('6', 'Sviluppo')], max_length=3, null=True, verbose_name='Unità riferimento'),
+ ),
+ ]
diff --git a/formazione/models.py b/formazione/models.py
index 3a75fddf0..b025156f1 100755
--- a/formazione/models.py
+++ b/formazione/models.py
@@ -158,7 +158,10 @@ def __str__(self):
class CorsoBase(Corso, ConVecchioID, ConPDF):
- MAX_PARTECIPANTI = 30
+ from django.core.validators import MinValueValidator
+
+ MIN_PARTECIPANTI = 20
+ MAX_PARTECIPANTI = 50
EXT_MIA_SEDE = '1'
EXT_LVL_REGIONALE = '2'
@@ -183,6 +186,11 @@ class CorsoBase(Corso, ConVecchioID, ConPDF):
max_length=255, blank=True, null=True)
extension_type = models.CharField(max_length=5, blank=True, null=True,
choices=EXTENSION_TYPE_CHOICES)
+ min_participants = models.SmallIntegerField("Minimo partecipanti",
+ default=MIN_PARTECIPANTI,
+ validators=[MinValueValidator(MIN_PARTECIPANTI)])
+ max_participants = models.SmallIntegerField("Massimo partecipanti",
+ default=MAX_PARTECIPANTI)
PUOI_ISCRIVERTI_OK = "IS"
PUOI_ISCRIVERTI = (PUOI_ISCRIVERTI_OK,)
diff --git a/formazione/viste.py b/formazione/viste.py
index eec76a872..6edff087b 100644
--- a/formazione/viste.py
+++ b/formazione/viste.py
@@ -314,7 +314,9 @@ def aspirante_corso_base_modifica(request, me, pk):
instance.corso = course
link_formset.save()
- return redirect(reverse('aspirante:modify', args=[pk]))
+ if course_form.is_valid() and file_formset.is_valid() and \
+ link_formset.is_valid():
+ return redirect(reverse('aspirante:modify', args=[pk]))
else:
course_form = ModuloModificaCorsoBase(instance=course)
file_formset = CorsoFileFormSet(queryset=course_files, prefix=FILEFORM_PREFIX)
From f99e89f848300a5784725e12ce7b6e4ff4f6a10b Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Thu, 15 Nov 2018 10:13:24 +0100
Subject: [PATCH 025/291] MODIFIED:
formazione/templates/aspirante_corso_base_scheda.html
---
.../aspirante_corso_base_scheda.html | 136 +++++++++---------
1 file changed, 71 insertions(+), 65 deletions(-)
diff --git a/formazione/templates/aspirante_corso_base_scheda.html b/formazione/templates/aspirante_corso_base_scheda.html
index 6030b28fe..fe47f8100 100644
--- a/formazione/templates/aspirante_corso_base_scheda.html
+++ b/formazione/templates/aspirante_corso_base_scheda.html
@@ -13,7 +13,6 @@
{% block app_contenuto %}
-
{{ corso.nome }}
@@ -35,69 +34,6 @@
{{ corso.get_stato_display }}
- {% if puo_modificare and corso.stato == corso.PREPARAZIONE and corso.prossimo %}
-
-
Non c'è tempo da perdere!
-
Questo corso inizierà a breve, ma non hai ancora attivato il Corso su Gaia.
-
Questo vuol dire che il corso non può ancora essere raggiunto da
- nessuno dei {{ corso.aspiranti_nelle_vicinanze.count }} aspiranti
- che aspettano un corso nelle vicinanze su Gaia. Ti consigliamo di completare con
- quanti più dettagli possibile la scheda
- di questo corso e procedere all'attivazione: raggiungerai così tutti
- gli aspiranti nelle vicinanze.
-
- {% endif %}
-
- {% if puo_modificare and corso.stato == corso.PREPARAZIONE %}
-
-
Il corso non è ancora attivo
-
Questo corso è ancora una bozza ("In Preparazione"). È necessario
- attivare il corso affinché questo possa essere trovato dagli aspiranti.
- Completa i passi necessari e clicca sul pulsante per attivare il corso:
-
-
-
- {% checkbox corso.descrizione %}:
- Inserisci una descrizione del Corso per gli Aspiranti dalla scheda "Gestione corso";
-
-
- {% checkbox corso.locazione %}:
- Inserisci l'indirizzo del Corso dalla scheda "Gestione corso";
-
- {% endif %}
-
- {% if puo_modificare and corso.terminabile %}
-
-
Genera il verbale e termina il corso
-
- Il corso si è concluso, ma è ancora necessario generare il verbale
- del corso.
-
-
- Una volta generato il verbale, tutti i partecipanti verranno informati dell'esito
- e, coloro che saranno stati promossi, verranno trasformati automaticamente in
- volontari.
-
+ {% endif %}
+
+ {% if puo_modificare and corso.terminabile %}
+
+
Genera il verbale e termina il corso
+
+ Il corso si è concluso, ma è ancora necessario generare il verbale
+ del corso.
+
+
+ Una volta generato il verbale, tutti i partecipanti verranno informati dell'esito
+ e, coloro che saranno stati promossi, verranno trasformati automaticamente in
+ volontari.
+
Alla pagina "Richieste" su Gaia trovi l'elenco completo delle
- richieste di autorizzazione in attesa della tua firma.
+ CONCEDI o
+ NEGA l'autorizzazione.
+
Alla pagina "Richieste" su Gaia trovi l'elenco completo delle richieste di autorizzazione in attesa della tua firma.
{{ richiesta.richiedente.link|safe }} chiede di essere
- contattat{{ richiesta.richiedente.genere_o_a }} per
- iscriversi al {{ richiesta.oggetto.corso.link|safe }} che inizierà il
- {{ richiesta.oggetto.corso.data_inizio|date:"DATETIME_FORMAT" }}.
-
+
{{ richiesta.richiedente.link|safe }} chiede di essere contattat{{ richiesta.richiedente.genere_o_a }}
+ per iscriversi al {{ richiesta.oggetto.corso.link|safe }}
+ che inizierà il {{ richiesta.oggetto.corso.data_inizio|date:"DATETIME_FORMAT" }}.
\ No newline at end of file
diff --git a/formazione/templates/aspirante_corsi_base.html b/formazione/templates/aspirante_corsi_base.html
index 2def202b0..539a70a10 100644
--- a/formazione/templates/aspirante_corsi_base.html
+++ b/formazione/templates/aspirante_corsi_base.html
@@ -6,14 +6,13 @@
{% load social %}
{% block app_contenuto %}
-
-
Corsi Base nelle tue vicinanze
+
Corsi nelle tue vicinanze
- Questo è un elenco dei Corsi Base nel raggio di {{ me.aspirante.raggio }} km
- da {{ me.aspirante.locazione }}. Puoi modificare la posizione
- dal menu "Aspirante" > "Impostazioni".
+ Questo è un elenco dei Corsi nel raggio di {{ me.aspirante.raggio|default:"0" }} km
+ da {{ me.aspirante.locazione|default:"n/a" }}. Puoi modificare la posizione
+ dal menu "{% if me.aspirante %}Aspirante{%else%}Volontario{%endif%}" > "Impostazioni".
@@ -24,9 +23,7 @@
Corsi Base nelle tue vicinanze
{% for corso in corsi %}
-
-
{{ corso.link|safe }}
@@ -35,9 +32,7 @@
Corsi Base nelle tue vicinanze
{{ corso.locazione }}
{% endif %}
-
-
{% if not corso.iniziato %}
@@ -54,24 +49,15 @@
Corsi Base nelle tue vicinanze
{{ d.persona.link|safe }}
{% endfor %}
-
-
- {{ corso.partecipazioni.count }} richieste
-
-
+
{{ corso.partecipazioni.count }} richieste
-
{% empty %}
Ancora nessun corso pianificato.
-
Puoi controllare la domanda formativa della zona e valutare l'attivazione di un
- nuovo corso base.
+
Puoi controllare la domanda formativa della zona e valutare l'attivazione di un nuovo corso.
-
{% endfor %}
-
-
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/formazione/templates/aspirante_corso_base_scheda_attiva.html b/formazione/templates/aspirante_corso_base_scheda_attiva.html
index 86ad2cf32..7dd3b3b39 100644
--- a/formazione/templates/aspirante_corso_base_scheda_attiva.html
+++ b/formazione/templates/aspirante_corso_base_scheda_attiva.html
@@ -1,66 +1,39 @@
{% extends 'aspirante_corso_base_scheda.html' %}
-{% block scheda_titolo %}
- Attiva Corso Base
-{% endblock %}
+{% block scheda_titolo %}Attiva Corso{% endblock %}
{% block scheda_contenuto %}
-
-
{% endblock %}
\ No newline at end of file
diff --git a/formazione/templates/aspirante_corso_base_scheda_informazioni.html b/formazione/templates/aspirante_corso_base_scheda_informazioni.html
index c19e56f39..99ecf18e8 100644
--- a/formazione/templates/aspirante_corso_base_scheda_informazioni.html
+++ b/formazione/templates/aspirante_corso_base_scheda_informazioni.html
@@ -7,16 +7,12 @@
{% block scheda_contenuto %}
{% if not me or not puo_modificare %}
-
-
{% if not me or puoi_partecipare in corso.PUOI_ISCRIVERTI %}
-
-
Vuoi partecipare a questo corso base?
+
Vuoi partecipare a questo corso?
{% if not me %}
-
Per diventare volontario di Croce Rossa Italiana, iscrivendoti a
- questo o altri corsi base nelle tue vicinanze, registrati
+
Per diventare volontario di Croce Rossa Italiana, iscrivendoti a questo o altri corsi nelle tue vicinanze, registrati
come aspirante su Gaia. Potrai vedere i corsi nelle tue vicinanze ed essere informato immediatamente
quando un nuovo corso viene attivato vicino a te!
@@ -25,97 +21,70 @@
Vuoi partecipare a questo corso base?<
Voglio registrarmi come Aspirante
-
{% else %}
Se sei interessat{{ me.genere_o_a }} a iscriverti a questo corso, clicca sul pulsante seguente.
Notificheremo il tuo interesse al direttore del corso, che ti contatterà
{% elif puoi_partecipare in corso.SEI_ISCRITTO %}
-
{% if puoi_partecipare == corso.SEI_ISCRITTO_PUOI_RITIRARTI %}
Hai chiesto di partecipare a questo corso
Verrai contattat{{ me.genere_o_a }} a breve dal direttore del corso.
-
Puoi presentarti alla prima lezione del corso base, dove la tua iscrizione
+
Puoi presentarti alla prima lezione del corso, dove la tua iscrizione
verrà completata e confermata dal direttore —
nota solo che il direttore ha facoltà di limitare l'accesso
ai primi {{ me.aspirante.richiesta_corso.MAX_PARTECIPANTI }} iscritti.
Meraviglioso! Presentati alle lezioni del corso secondo il programma indicato sotto.
Per qualsiasi domanda, contatta uno dei direttori del corso, cliccando sul suo nome.
-
-
-
-
{% endif %}
-
{% elif puoi_partecipare in corso.NON_PUOI_ISCRIVERTI %}
-
{% if puoi_partecipare == corso.NON_PUOI_ISCRIVERTI_GIA_VOLONTARIO %}
- Sei già parte di Croce Rossa Italiana, le funzionalità di
- iscrizione al corso sono disabilitate.
+ Sei già parte di Croce Rossa Italiana, le funzionalità di iscrizione al corso sono disabilitate.
- Sei già iscritt{{ me.genere_o_a }} a un altro corso, quindi
- non puoi iscriverti a questo.
+ Sei già iscritt{{ me.genere_o_a }} a un altro corso, quindi non puoi iscriverti a questo.
+
ti scriviamo in riferimento alla tua richiesta di diventare volontario della Croce
- Rossa Italiana fatta attraverso Gaia.
-
Nelle tue vicinanze è stato attivato un "corso base" per Volontari CRI!
-
Il corso è stato organizzato dal {{ corso.sede.link|safe }} ed avrà inizio il
- {{ corso.data_inizio|date:"SHORT_DATETIME_FORMAT" }}, al seguente indirizzo:
- {{ corso.locazione }} (vedi in mappa).
+{% if corso.tipo == Corso.BASE %}
+
ti scriviamo in riferimento alla tua richiesta di diventare volontario della Croce Rossa Italiana fatta attraverso Gaia.
+{% endif %}
+
+
Nelle tue vicinanze è stato attivato un corso {%if corso.tipo == Corso.BASE%}base per Aspiranti{%else%}per Volontari{%endif%} CRI!
+
Il corso è stato organizzato dal {{ corso.sede.link|safe }} ed avrà inizio il {{ corso.data_inizio|date:"SHORT_DATETIME_FORMAT" }},
+ al seguente indirizzo: {{ corso.locazione }} (vedi in mappa).
Alcune informazioni su questo corso:
{{ corso.descrizione|safe }}
From 233b0aed08e87c951ba390f0c38645b73eb7eb94 Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Wed, 21 Nov 2018 11:58:42 +0100
Subject: [PATCH 037/291] NEW: base methods for CorsoNuovo in
formazione.models.CorsoBase and CorsoEstensione; various fixes.
---
formazione/admin.py | 11 +-
formazione/decorators.py | 51 +++--
formazione/forms.py | 2 +-
.../migrations/0027_auto_20181120_1637.py | 20 ++
formazione/models.py | 179 ++++++++++++++++--
5 files changed, 219 insertions(+), 44 deletions(-)
create mode 100644 formazione/migrations/0027_auto_20181120_1637.py
diff --git a/formazione/admin.py b/formazione/admin.py
index e79115d5c..dd2d33730 100755
--- a/formazione/admin.py
+++ b/formazione/admin.py
@@ -16,6 +16,7 @@
RAW_ID_FIELDS_LEZIONECORSOBASE = ['corso',]
RAW_ID_FIELDS_ASSENZACORSOBASE = ['lezione', 'persona', 'registrata_da',]
RAW_ID_FIELDS_ASPIRANTE = ['persona', 'locazione',]
+RAW_ID_FIELDS_ESTENSIONE = ['sede', 'titolo',]
class InlineDelegaCorsoBase(ReadonlyAdminMixin, GenericTabularInline):
@@ -49,6 +50,11 @@ class InlineAssenzaCorsoBase(ReadonlyAdminMixin, admin.TabularInline):
raw_id_fields = RAW_ID_FIELDS_ASSENZACORSOBASE
extra = 0
+class InlineEstensioneCorso(ReadonlyAdminMixin, admin.TabularInline):
+ model = CorsoEstensione
+ raw_id_fields = RAW_ID_FIELDS_ESTENSIONE
+ extra = 0
+
def admin_corsi_base_attivi_invia_messaggi(modeladmin, request, queryset):
corsi = queryset.filter(stato=CorsoBase.ATTIVO)
@@ -64,7 +70,8 @@ class AdminCorsoBase(ReadonlyAdminMixin, admin.ModelAdmin):
list_display = ['progressivo', 'anno', 'stato', 'sede', 'data_inizio', 'data_esame', ]
list_filter = ['anno', 'creazione', 'stato', 'data_inizio', ]
raw_id_fields = RAW_ID_FIELDS_CORSOBASE
- inlines = [InlineDelegaCorsoBase, InlinePartecipazioneCorsoBase, InlineInvitoCorsoBase, InlineLezioneCorsoBase]
+ inlines = [InlineDelegaCorsoBase, InlinePartecipazioneCorsoBase,
+ InlineInvitoCorsoBase, InlineLezioneCorsoBase, InlineEstensioneCorso]
actions = [admin_corsi_base_attivi_invia_messaggi]
@@ -85,7 +92,7 @@ class AdminCorsoLink(admin.ModelAdmin):
@admin.register(CorsoEstensione)
class AdminCorsoEstensione(admin.ModelAdmin):
list_display = ['corso', 'is_active', 'segmento', 'sedi_sottostanti']
- raw_id_fields = ['sede', 'titolo',]
+ raw_id_fields = RAW_ID_FIELDS_ESTENSIONE
@admin.register(PartecipazioneCorsoBase)
diff --git a/formazione/decorators.py b/formazione/decorators.py
index fa8d205d1..f9559593c 100644
--- a/formazione/decorators.py
+++ b/formazione/decorators.py
@@ -1,13 +1,14 @@
-def access_to_courses(function):
- from django.shortcuts import redirect
- from .models import Corso
- from anagrafica.permessi.costanti import ERRORE_PERMESSI
+from django.shortcuts import redirect
+from .models import Corso
+from anagrafica.permessi.costanti import ERRORE_PERMESSI
+
+def can_access_to_course(function):
def wrapper(request, *args, **kwargs):
me = request.me
-
- if not me.ha_aspirante:
- return redirect(ERRORE_PERMESSI)
+ REDIRECT_ERR = redirect(ERRORE_PERMESSI)
+ if not me.ha_aspirante and not me.volontario:
+ return REDIRECT_ERR
r = function(request, *args, **kwargs)
try:
@@ -15,21 +16,29 @@ def wrapper(request, *args, **kwargs):
except IndexError:
return r
else:
- if context and ('corsi' in context):
- is_aspirante = me.ha_aspirante
- is_volontario = me.volontario
-
- # Filter displayed courses by membership of Persona (nel raggio)
- params = dict()
- if is_aspirante:
- params = {'tipo': Corso.BASE}
- if is_volontario:
- params = {'tipo': Corso.CORSO_NUOVO}
-
- # Update context data with new queryset
- context['corsi'] = me.aspirante.corsi(**params)
+ if not context:
+ return r
+
+ is_aspirante = me.ha_aspirante
+ is_volontario = me.volontario
+
+ if 'corso' in context:
+ corso = context['corso']
+ if corso.tipo == Corso.CORSO_NUOVO:
+ # Aspirante can't access to CORSO_NUOVO page.
+ if is_aspirante and not is_volontario:
+ return REDIRECT_ERR
+
+ if 'corsi' in context:
+ if is_aspirante and not is_volontario:
+ # Update corsi queryset
+ context['corsi'] = me.aspirante.corsi(tipo=Corso.BASE)
+
+ if is_volontario and not is_aspirante:
+ params = {'tipo': Corso.CORSO_NUOVO}
+
return r
wrapper.__doc__ = function.__doc__
wrapper.__name__ = function.__name__
- return wrapper
\ No newline at end of file
+ return wrapper
diff --git a/formazione/forms.py b/formazione/forms.py
index 0cfbc2915..8f7908753 100644
--- a/formazione/forms.py
+++ b/formazione/forms.py
@@ -168,7 +168,7 @@ def __init__(self, *args, **kwargs):
CorsoSelectExtensionFormSet = modelformset_factory(CorsoEstensione, extra=1,
- max_num=2, form=CorsoExtensionForm, can_delete=True)
+ max_num=3, form=CorsoExtensionForm, can_delete=True)
class ModuloConfermaIscrizioneCorsoBase(forms.Form):
diff --git a/formazione/migrations/0027_auto_20181120_1637.py b/formazione/migrations/0027_auto_20181120_1637.py
new file mode 100644
index 000000000..56a2c5a94
--- /dev/null
+++ b/formazione/migrations/0027_auto_20181120_1637.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.12 on 2018-11-20 16:37
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('formazione', '0026_auto_20181119_1427'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='corsobase',
+ name='extension_type',
+ field=models.CharField(blank=True, choices=[('1', 'Solo su mia sede di appartenenza'), ('2', 'A livello regionale')], default='1', max_length=5, null=True),
+ ),
+ ]
diff --git a/formazione/models.py b/formazione/models.py
index c1afec095..aef5d3f0a 100755
--- a/formazione/models.py
+++ b/formazione/models.py
@@ -114,6 +114,7 @@ class CorsoBase(Corso, ConVecchioID, ConPDF):
op_convocazione = models.CharField('Ordinanza presidenziale convocazione',
max_length=255, blank=True, null=True)
extension_type = models.CharField(max_length=5, blank=True, null=True,
+ default=EXT_MIA_SEDE,
choices=EXTENSION_TYPE_CHOICES)
min_participants = models.SmallIntegerField("Minimo partecipanti",
default=MIN_PARTECIPANTI,
@@ -131,14 +132,22 @@ class CorsoBase(Corso, ConVecchioID, ConPDF):
NON_PUOI_ISCRIVERTI_GIA_VOLONTARIO = "VOL"
NON_PUOI_ISCRIVERTI_TROPPO_TARDI = "TAR"
NON_PUOI_ISCRIVERTI_GIA_ISCRITTO_ALTRO_CORSO = "ALT"
- NON_PUOI_ISCRIVERTI = (NON_PUOI_ISCRIVERTI_GIA_VOLONTARIO, NON_PUOI_ISCRIVERTI_TROPPO_TARDI,
- NON_PUOI_ISCRIVERTI_GIA_ISCRITTO_ALTRO_CORSO,)
+ NON_PUOI_SEI_ASPIRANTE = 'ASP'
+ NON_PUOI_ISCRIVERTI = (NON_PUOI_ISCRIVERTI_GIA_VOLONTARIO,
+ NON_PUOI_ISCRIVERTI_TROPPO_TARDI,
+ NON_PUOI_ISCRIVERTI_GIA_ISCRITTO_ALTRO_CORSO,
+ NON_PUOI_SEI_ASPIRANTE)
NON_PUOI_ISCRIVERTI_SOLO_SE_IN_AUTONOMIA = (NON_PUOI_ISCRIVERTI_TROPPO_TARDI,)
def persona(self, persona):
- if (not Aspirante.objects.filter(persona=persona).exists()) and persona.volontario:
- return self.NON_PUOI_ISCRIVERTI_GIA_VOLONTARIO
+ # Checks related to tipo.CORSO_NUOVO (not old CorsoBase)
+ if Corso.CORSO_NUOVO == self.tipo:
+ if persona.ha_aspirante:
+ return self.NON_PUOI_SEI_ASPIRANTE
+
+ # if (not Aspirante.objects.filter(persona=persona).exists()) and persona.volontario:
+ # return self.NON_PUOI_ISCRIVERTI_GIA_VOLONTARIO
if PartecipazioneCorsoBase.con_esito_ok(persona=persona, corso__stato=self.ATTIVO).exclude(corso=self).exists():
return self.NON_PUOI_ISCRIVERTI_GIA_ISCRITTO_ALTRO_CORSO
@@ -203,7 +212,8 @@ def url(self):
@property
def nome(self):
- return "Corso Base %d/%d (%s)" % (self.progressivo, self.anno, self.sede)
+ course_type = 'Corso Base' if self.tipo == Corso.BASE else 'Corso'
+ return "%s %d/%d (%s)" % (course_type, self.progressivo, self.anno, self.sede)
@property
def link(self):
@@ -337,19 +347,114 @@ def attiva(self, rispondi_a=None):
self.save()
def _invia_email_agli_aspiranti(self, rispondi_a=None):
- for aspirante in self.aspiranti_nelle_vicinanze():
- persona = aspirante.persona
- if not aspirante.persona.volontario:
- Messaggio.costruisci_e_accoda(
- oggetto="Nuovo Corso per Volontari CRI",
- modello="email_aspirante_corso.html",
- corpo={
- "persona": persona,
- "corso": self,
- },
- destinatari=[persona],
- rispondi_a=rispondi_a
- )
+ is_nuovo_corso = self.tipo == Corso.CORSO_NUOVO
+ if is_nuovo_corso:
+ print('_send_email_to_participants')
+ recepients = self.get_volunteers_by_course_requirements()
+ else:
+ recepients = self.aspiranti_nelle_vicinanze()
+
+ for recepient in recepients:
+ if is_nuovo_corso:
+ persona = recepient
+ else:
+ persona = recepient.persona
+
+ email_data = dict(
+ oggetto="Nuovo Corso per Volontari CRI",
+ modello="email_aspirante_corso.html",
+ corpo={
+ 'persona': persona,
+ 'corso': self,
+ },
+ destinatari=[persona],
+ rispondi_a=rispondi_a
+ )
+
+ if is_nuovo_corso:
+ # If course tipo is CORSO_NUOVO to send to volunteers only
+ Messaggio.costruisci_e_accoda(**email_data)
+ elif not is_nuovo_corso and not recepient.persona.volontario:
+ # to send to only
+ Messaggio.costruisci_e_accoda(**email_data)
+
+ def has_extensions(self, is_active=True, **kwargs):
+ """ Case: extension_type == EXT_LVL_REGIONALE """
+ return self.corsoestensione_set.filter(is_active=is_active).exists()
+
+ def get_extensions(self, **kwargs):
+ """ Returns CorsoEstensione objects related to the course """
+ return self.corsoestensione_set.filter(**kwargs)
+
+ def get_extensions_sede(self, with_expanded=True, **kwargs):
+ """ Returns SedeQuerySet """
+ if with_expanded:
+ return self.expand_extensions_sedi_sottostanti()
+ else:
+ return CorsoEstensione.get_sede(course=self, **kwargs)
+
+ def expand_extensions_sedi_sottostanti(self):
+ expanded = Sede.objects.none()
+
+ for e in self.get_extensions():
+ all_sede = e.sede.all()
+ expanded |= all_sede
+ for sede in all_sede:
+ if e.sedi_sottostanti:
+ expanded |= sede.esplora()
+ return expanded.distinct()
+
+ def get_extensions_titles(self, **kwargs):
+ """ Returns QuerySet """
+ return CorsoEstensione.get_titles(course=self, **kwargs)
+
+ def get_volunteers_by_course_requirements(self, **kwargs):
+ persons = None
+
+ if self.tipo == Corso.CORSO_NUOVO:
+ corso_extension = self.extension_type
+ if CorsoBase.EXT_MIA_SEDE == corso_extension:
+ by_only_sede = self.get_volunteers_by_only_sede()
+ persons = by_only_sede
+
+ if CorsoBase.EXT_LVL_REGIONALE == corso_extension:
+ by_ext_sede = self.get_volunteers_by_ext_sede()
+ by_ext_titles = self.get_volunteers_by_ext_titles()
+ persons = by_ext_sede | by_ext_titles
+
+ if persons is None:
+ # Sede of course (was set in the first step of course creation)
+ persons = Persona.objects.filter(sede=self.sede)
+
+ return persons.filter(**kwargs).distinct()
+
+ @property
+ def get_firmatario_sede(self):
+ course_created_by = self.deleghe.last()
+ if not hasattr(course_created_by, 'firmatario'):
+ return Sede.objects.none()
+
+ return course_created_by.firmatario.sede_riferimento()
+
+ def get_volunteers_by_only_sede(self):
+ app = Appartenenza.objects.filter(sede=self.get_firmatario_sede,
+ membro=Appartenenza.VOLONTARIO)
+ return self._query_get_volunteers_by_sede(app)
+
+ def get_volunteers_by_ext_sede(self):
+ app = Appartenenza.objects.filter(sede__in=self.get_extensions_sede(),
+ membro=Appartenenza.VOLONTARIO)
+ return self._query_get_volunteers_by_sede(app)
+
+ def _query_get_volunteers_by_sede(self, appartenenze):
+ return Persona.to_contact_for_courses(corso=self).filter(
+ id__in=appartenenze.values_list('persona__id', flat=True)
+ )
+
+ def get_volunteers_by_ext_titles(self):
+ sede = self.get_extensions_sede()
+ titles = self.get_extensions_titles().values_list('id', flat=True)
+ return Persona.objects.filter(sede__in=sede, titoli_personali__in=titles)
@property
def concluso(self):
@@ -467,12 +572,12 @@ def __str__(self):
class CorsoEstensione(ConMarcaTemporale):
from segmenti.segmenti import NOMI_SEGMENTI
-
+ from curriculum.models import Titolo
corso = models.ForeignKey(CorsoBase, db_index=True)
is_active = models.BooleanField(default=True, db_index=True)
segmento = models.CharField(max_length=9, choices=NOMI_SEGMENTI, blank=True)
- titolo = models.ManyToManyField(FormazioneTitle, blank=True)
+ titolo = models.ManyToManyField(Titolo, blank=True)
sede = models.ManyToManyField(Sede)
sedi_sottostanti = models.BooleanField(default=False, db_index=True)
@@ -483,10 +588,44 @@ def visible_by_extension_type(self):
elif type == CorsoBase.EXT_LVL_REGIONALE:
self.is_active = True
+ @classmethod
+ def get_sede(cls, course, **kwargs):
+ sede = cls._get_related_objects_to_course(course, 'sede', **kwargs)
+ return sede if sede else Sede.objects.none()
+
+ @classmethod
+ def get_titles(cls, course, **kwargs):
+ titles = cls._get_related_objects_to_course(course, 'titolo', **kwargs)
+ return titles if titles else Titolo.objects.none()
+
+ @classmethod
+ def _get_related_objects_to_course(cls, course, field, **kwargs):
+ course_extensions = cls.objects.filter(corso=course.pk, **kwargs)
+ if not course_extensions.exists():
+ return ValueError('_get_related_objects_to_course: field <%s>' % field)
+
+ objects = []
+ for i in course_extensions:
+ elem = getattr(i, field).all()
+ if elem:
+ for e in elem:
+ objects.append(e)
+ if objects:
+ model = ContentType.objects.get_for_model(objects[0]).model_class()
+ return model.objects.filter(id__in=[obj.id for obj in objects]).distinct()
+
+ def __str__(self):
+ return '%s' % self.corso if hasattr(self, 'corso') else 'No CorsoBase set.'
+
def save(self):
self.visible_by_extension_type()
super().save()
+ class Meta:
+ verbose_name = 'Estensione del Corso'
+ verbose_name_plural = 'Estensioni del Corso'
+
+
class InvitoCorsoBase(ModelloSemplice, ConAutorizzazioni,
ConMarcaTemporale, models.Model):
persona = models.ForeignKey(Persona, related_name='inviti_corsi', on_delete=models.CASCADE)
From 3e4a4d4b6a9cb5e47865f002e73f05b7d5e16122 Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Wed, 21 Nov 2018 17:38:18 +0100
Subject: [PATCH 038/291] NEW: test_courses.py in formazione
---
formazione/decorators.py | 6 +--
formazione/test_courses.py | 92 ++++++++++++++++++++++++++++++++++++++
formazione/viste.py | 2 -
3 files changed, 94 insertions(+), 6 deletions(-)
create mode 100644 formazione/test_courses.py
diff --git a/formazione/decorators.py b/formazione/decorators.py
index f9559593c..20da52ede 100644
--- a/formazione/decorators.py
+++ b/formazione/decorators.py
@@ -22,6 +22,7 @@ def wrapper(request, *args, **kwargs):
is_aspirante = me.ha_aspirante
is_volontario = me.volontario
+ # viste.aspirante_corso_base_informazioni
if 'corso' in context:
corso = context['corso']
if corso.tipo == Corso.CORSO_NUOVO:
@@ -29,14 +30,11 @@ def wrapper(request, *args, **kwargs):
if is_aspirante and not is_volontario:
return REDIRECT_ERR
+ # viste.aspirante_corsi
if 'corsi' in context:
if is_aspirante and not is_volontario:
# Update corsi queryset
context['corsi'] = me.aspirante.corsi(tipo=Corso.BASE)
-
- if is_volontario and not is_aspirante:
- params = {'tipo': Corso.CORSO_NUOVO}
-
return r
wrapper.__doc__ = function.__doc__
diff --git a/formazione/test_courses.py b/formazione/test_courses.py
new file mode 100644
index 000000000..7cb666daa
--- /dev/null
+++ b/formazione/test_courses.py
@@ -0,0 +1,92 @@
+from datetime import datetime, timedelta
+from django.test import TestCase
+from django.core.urlresolvers import reverse
+from base.utils_tests import (crea_persona_sede_appartenenza, crea_persona,
+ crea_appartenenza, email_fittizzia, crea_utenza, codice_fiscale, crea_locazione)
+from anagrafica.models import *
+from formazione.models import *
+
+
+def create_persona(with_utenza=True):
+ persona = crea_persona()
+ persona.email_contatto = email_fittizzia()
+ persona.codice_fiscale = codice_fiscale()
+ persona.save()
+
+ if with_utenza:
+ utenza = crea_utenza(persona, persona.email_contatto)
+ return persona, utenza
+ else:
+ return persona
+
+def create_aspirante(sede, persona=None):
+ if not persona:
+ persona = create_persona()[0]
+
+ a = Aspirante(persona=persona)
+ a.locazione = sede.locazione
+ a.save()
+ return a
+
+def create_course(data_inizio, sede, extension_type=CorsoBase.EXT_MIA_SEDE,
+ tipo=Corso.CORSO_NUOVO, **kwargs):
+
+ corso = CorsoBase.nuovo(tipo=tipo, extension_type=extension_type, sede=sede,
+ data_inizio=data_inizio, data_esame=data_inizio + timedelta(days=14),
+ stato=Corso.ATTIVO, **kwargs)
+ corso.locazione = sede.locazione
+ corso.save()
+ return corso
+
+class TestCorsoNuovo(TestCase):
+ def setUp(self):
+ """ Create users """
+ ### Presidente ###
+ self.presidente, self.presidente_utenza = create_persona()
+ self.direttore, self.sede, self.appartenenza = crea_persona_sede_appartenenza(
+ presidente=self.presidente)
+
+ ### Aspirante ###
+ self.aspirante1 = create_aspirante(sede=self.sede)
+ self.aspirante2 = create_aspirante(sede=self.sede)
+
+ ### Volontario ###
+ self.volontario = None
+
+ """ Create a new courses tipo CORSO_NUOVO """
+ data_inizio = datetime.datetime.now() + timedelta(days=14)
+ self.c1 = create_course(data_inizio, self.sede) # corso_1_ext_mia_sede
+ self.c2 = create_course(data_inizio, self.sede, extension_type=CorsoBase.EXT_LVL_REGIONALE) # corso_2_ext_a_livello_regionale
+ self.c3 = create_course(data_inizio, self.sede, tipo=Corso.BASE)
+
+ def test_corso_nuovo_invisible_to_aspirante(self):
+ email = self.aspirante1.persona.email_contatto
+ login = self.client.login(username=email, password='prova')
+ response = self.client.get(reverse('aspirante:corsi_base'))
+ ctx = response.context
+
+ ### Asserting ###
+ self.assertEqual(str(ctx['user']), email) # user is logged in
+ self.assertEqual(response.status_code, 200)
+ self.assertTrue('corsi' in ctx)
+ self.assertFalse(self.c1 in ctx['corsi']) # Nuovo not in ctx
+ self.assertFalse(self.c2 in ctx['corsi']) # Nuovo not in ctx
+ self.assertTrue(self.c3 in ctx['corsi']) # Base in ctx
+
+ # Courses can access to
+ c3 = self.c3
+ response = self.client.get(reverse('aspirante:info', args=[c3.pk]))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, c3.nome, status_code=200)
+
+ # Courses can not access to
+ response = self.client.get(reverse('aspirante:info', args=[self.c1.pk]))
+ self.assertEqual(response.status_code, 302)
+ response = self.client.get(reverse('aspirante:info', args=[self.c2.pk]))
+ self.assertEqual(response.status_code, 302)
+
+ def test_corso_nuovo_visible_to_volunteer(self):
+ pass
+
+ def test_corso_nuovo_extensions_link_visible_only_for_corso_nuovo(self):
+ pass
\ No newline at end of file
diff --git a/formazione/viste.py b/formazione/viste.py
index 16d2533a0..bbec9d2f4 100644
--- a/formazione/viste.py
+++ b/formazione/viste.py
@@ -639,8 +639,6 @@ def aspirante_corsi(request, me):
corsi = me.aspirante.corsi(tipo=Corso.BASE)
elif me.volontario:
corsi = CorsoBase.pubblici().filter(tipo=Corso.CORSO_NUOVO)
- # TODO: corsi nel raggio per VOLONTARIO
- # corsi = ConGeolocalizzazioneRaggio.nel_raggio(corsi)
# else:
# corsi = me.courses_within_area()
From 482407f9e505b675439c9c67c0d82052ffc66d4a Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Thu, 22 Nov 2018 15:47:20 +0100
Subject: [PATCH 039/291] NEW: method CorsoBase.find_courses_for_volunteer for
listing of courses to Volunteer based on course requirements.
---
formazione/models.py | 59 +++++++++++++++----
.../templates/aspirante_corsi_base.html | 5 +-
...irante_corso_base_scheda_informazioni.html | 13 +++-
formazione/viste.py | 5 +-
4 files changed, 62 insertions(+), 20 deletions(-)
diff --git a/formazione/models.py b/formazione/models.py
index aef5d3f0a..26fa049e9 100755
--- a/formazione/models.py
+++ b/formazione/models.py
@@ -9,16 +9,15 @@
from django.utils import timezone
from django.utils.timezone import now
-from anagrafica.costanti import PROVINCIALE, TERRITORIALE, LOCALE
from anagrafica.models import Sede, Persona, Appartenenza
+from anagrafica.costanti import PROVINCIALE, TERRITORIALE, LOCALE
from anagrafica.permessi.incarichi import (INCARICO_ASPIRANTE,
INCARICO_GESTIONE_CORSOBASE_PARTECIPANTI)
from base.files import PDF, Zip
-from base.models import ConAutorizzazioni, ConVecchioID, Autorizzazione
from base.geo import ConGeolocalizzazione, ConGeolocalizzazioneRaggio
-from base.models import ModelloSemplice
-from base.tratti import ConMarcaTemporale, ConDelegati, ConStorico, ConPDF
from base.utils import concept, poco_fa
+from base.tratti import ConMarcaTemporale, ConDelegati, ConStorico, ConPDF
+from base.models import ConAutorizzazioni, ConVecchioID, Autorizzazione, ModelloSemplice
from curriculum.models import Titolo
from posta.models import Messaggio
from social.models import ConCommenti, ConGiudizio
@@ -182,13 +181,47 @@ def prossimo(self):
@classmethod
@concept
def pubblici(cls):
- """
- Concept per Corsi Base pubblici (attivi e non ancora iniziati...)
- """
- return Q(
- data_inizio__gte=timezone.now() - datetime.timedelta(days=settings.FORMAZIONE_FINESTRA_CORSI_INIZIATI),
- stato=cls.ATTIVO
- )
+ """ Concept per Corsi pubblici (attivi e non ancora iniziati...) """
+ return Q(stato=cls.ATTIVO,
+ data_inizio__gte=timezone.now() - datetime.timedelta(
+ days=settings.FORMAZIONE_FINESTRA_CORSI_INIZIATI
+ ))
+
+ @classmethod
+ def find_courses_for_volunteer(cls, volunteer):
+ sede = volunteer.sede_riferimento()
+ if not sede:
+ return cls.objects.none()
+
+ titoli = volunteer.titoli_personali_confermati()
+ courses_list = list()
+ courses = cls.pubblici().filter(tipo=Corso.CORSO_NUOVO)
+ for course in courses:
+ # Course has extensions.
+ # Filter courses by titles and sede comparsion
+ if course.has_extensions():
+ volunteer_titolo = titoli.values_list('id', flat=True)
+ t = course.get_extensions_titles()
+ s = course.get_extensions_sede()
+ ext_t_list = t.values_list('id', flat=True)
+
+ # Course has required titles but volunteer has not at least one
+ if t and not (set(volunteer_titolo) & set(ext_t_list)):
+ continue
+
+ if s and (sede in s):
+ courses_list.append(course.pk)
+ else:
+ # Extensions have sede but volunteer's sede is not in the list
+ continue
+ else:
+ # Course has no extensions.
+ # Filter by firmatario sede if sede of volunteer is the same
+ firmatario = course.get_firmatario_sede
+ if firmatario and (sede in [firmatario]):
+ courses_list.append(course.pk)
+
+ return CorsoBase.objects.filter(id__in=courses_list)
@property
def iniziato(self):
@@ -600,9 +633,9 @@ def get_titles(cls, course, **kwargs):
@classmethod
def _get_related_objects_to_course(cls, course, field, **kwargs):
- course_extensions = cls.objects.filter(corso=course.pk, **kwargs)
+ course_extensions = cls.objects.filter(corso=course, **kwargs)
if not course_extensions.exists():
- return ValueError('_get_related_objects_to_course: field <%s>' % field)
+ return None
objects = []
for i in course_extensions:
diff --git a/formazione/templates/aspirante_corsi_base.html b/formazione/templates/aspirante_corsi_base.html
index 539a70a10..5ebc32758 100644
--- a/formazione/templates/aspirante_corsi_base.html
+++ b/formazione/templates/aspirante_corsi_base.html
@@ -8,12 +8,13 @@
{% block app_contenuto %}
Corsi nelle tue vicinanze
+ {% if me.aspirante %}
Questo è un elenco dei Corsi nel raggio di {{ me.aspirante.raggio|default:"0" }} km
- da {{ me.aspirante.locazione|default:"n/a" }}. Puoi modificare la posizione
- dal menu "{% if me.aspirante %}Aspirante{%else%}Volontario{%endif%}" > "Impostazioni".
+ da {{ me.aspirante.locazione|default:"n/a" }}. Puoi modificare la posizione dal menu "Aspirante" > "Impostazioni".
- {{ corso.descrizione|safe }}
+ {{ corso.descrizione|default:"Ancora non disponibili"|safe }}
+
+ {% if corso.tipo == corso.CORSO_NUOVO and corso.get_extensions_titles %}
+
+ Titoli necessari:
+
+ {% for e in corso.get_extensions_titles %}
+
{{ e.nome }}
+ {% endfor %}
+
+
+ {% endif %}
diff --git a/formazione/viste.py b/formazione/viste.py
index bbec9d2f4..62187d8ac 100644
--- a/formazione/viste.py
+++ b/formazione/viste.py
@@ -633,14 +633,11 @@ def aspirante_home(request, me):
@can_access_to_course
def aspirante_corsi(request, me):
""" url: /aspirante/corsi/ """
- from base.geo import ConGeolocalizzazioneRaggio
if me.ha_aspirante:
corsi = me.aspirante.corsi(tipo=Corso.BASE)
elif me.volontario:
- corsi = CorsoBase.pubblici().filter(tipo=Corso.CORSO_NUOVO)
- # else:
- # corsi = me.courses_within_area()
+ corsi = CorsoBase.find_courses_for_volunteer(volunteer=me)
context = {
'corsi': corsi
From ddb9551a67078f6558f0419596cfefcaa4d319f4 Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Thu, 22 Nov 2018 15:47:45 +0100
Subject: [PATCH 040/291] UPDATED: formazione.test_courses
---
formazione/test_courses.py | 23 ++++++++++++++++-------
1 file changed, 16 insertions(+), 7 deletions(-)
diff --git a/formazione/test_courses.py b/formazione/test_courses.py
index 7cb666daa..e6f0bede4 100644
--- a/formazione/test_courses.py
+++ b/formazione/test_courses.py
@@ -59,9 +59,12 @@ def setUp(self):
self.c2 = create_course(data_inizio, self.sede, extension_type=CorsoBase.EXT_LVL_REGIONALE) # corso_2_ext_a_livello_regionale
self.c3 = create_course(data_inizio, self.sede, tipo=Corso.BASE)
+ def _login_as(self, email, password='prova'):
+ self.client.login(username=email, password=password)
+
def test_corso_nuovo_invisible_to_aspirante(self):
email = self.aspirante1.persona.email_contatto
- login = self.client.login(username=email, password='prova')
+ login = self._login_as(self.aspirante1.persona.email_contatto)
response = self.client.get(reverse('aspirante:corsi_base'))
ctx = response.context
@@ -69,19 +72,24 @@ def test_corso_nuovo_invisible_to_aspirante(self):
self.assertEqual(str(ctx['user']), email) # user is logged in
self.assertEqual(response.status_code, 200)
self.assertTrue('corsi' in ctx)
- self.assertFalse(self.c1 in ctx['corsi']) # Nuovo not in ctx
- self.assertFalse(self.c2 in ctx['corsi']) # Nuovo not in ctx
- self.assertTrue(self.c3 in ctx['corsi']) # Base in ctx
+ self.assertFalse(self.c1 in ctx['corsi']) # Nuovo excluded
+ self.assertFalse(self.c2 in ctx['corsi']) # Nuovo excluded
+ self.assertTrue(self.c3 in ctx['corsi']) # CorsoBase in list of courses
- # Courses can access to
+ def test_aspirante_have_access_to_corso_base(self):
c3 = self.c3
+ login = self._login_as(self.aspirante1.persona.email_contatto)
response = self.client.get(reverse('aspirante:info', args=[c3.pk]))
+
self.assertEqual(response.status_code, 200)
self.assertContains(response, c3.nome, status_code=200)
- # Courses can not access to
+ def test_aspirante_no_access_to_corso_nuovo(self):
+ login = self._login_as(self.aspirante1.persona.email_contatto)
+
response = self.client.get(reverse('aspirante:info', args=[self.c1.pk]))
self.assertEqual(response.status_code, 302)
+
response = self.client.get(reverse('aspirante:info', args=[self.c2.pk]))
self.assertEqual(response.status_code, 302)
@@ -89,4 +97,5 @@ def test_corso_nuovo_visible_to_volunteer(self):
pass
def test_corso_nuovo_extensions_link_visible_only_for_corso_nuovo(self):
- pass
\ No newline at end of file
+ pass
+
From 78b001e63ae28d1eb24a435e5ab60a5a88afe9ea Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Thu, 22 Nov 2018 16:45:47 +0100
Subject: [PATCH 041/291] FIXED: formazione.autocomplete_light filter
---
formazione/autocomplete_light_registry.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/formazione/autocomplete_light_registry.py b/formazione/autocomplete_light_registry.py
index 2dae89b2d..43873ab44 100644
--- a/formazione/autocomplete_light_registry.py
+++ b/formazione/autocomplete_light_registry.py
@@ -9,6 +9,10 @@ class EstensioneLivelloRegionaleTitolo(AutocompletamentoBase):
search_fields = ['nome',]
model = Titolo
+ def choices_for_request(self):
+ self.choices = self.choices.filter(tipo=Titolo.TITOLO_CRI)
+ return super().choices_for_request()
+
class EstensioneLivelloRegionaleSede(AutocompletamentoBase):
search_fields = ['nome',]
From 9d972b5f3c5d7939f39c6afb693c1aecc1122a66 Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Thu, 22 Nov 2018 16:48:59 +0100
Subject: [PATCH 042/291] NEW: method Persona.has_required_titles_for_course()
need to verify if VO have required titles in CV to participate in Corso.
---
anagrafica/models.py | 9 +++++++++
formazione/models.py | 7 ++++---
2 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/anagrafica/models.py b/anagrafica/models.py
index 677b2cbfb..bd0f64d4e 100755
--- a/anagrafica/models.py
+++ b/anagrafica/models.py
@@ -1297,6 +1297,15 @@ def to_contact_for_courses(cls, corso, membro='VO', *args, **kwargs):
# TODO: what to return otherwise?
pass
+ def has_required_titles_for_course(self, course):
+ volunteer_titles = self.titoli_personali_confermati().filter(
+ titolo__tipo=Titolo.TITOLO_CRI).values_list('titolo', flat=True)
+
+ corso_titles = course.get_extensions_titles().values_list('id',flat=True)
+ intersection = set(volunteer_titles) & set(corso_titles)
+
+ return len(intersection) == len(corso_titles)
+
def save(self, *args, **kwargs):
self.nome = normalizza_nome(self.nome)
self.cognome = normalizza_nome(self.cognome)
diff --git a/formazione/models.py b/formazione/models.py
index 26fa049e9..b194ceb62 100755
--- a/formazione/models.py
+++ b/formazione/models.py
@@ -193,20 +193,21 @@ def find_courses_for_volunteer(cls, volunteer):
if not sede:
return cls.objects.none()
- titoli = volunteer.titoli_personali_confermati()
+ titoli = volunteer.titoli_personali_confermati().filter(
+ titolo__tipo=Titolo.TITOLO_CRI)
courses_list = list()
courses = cls.pubblici().filter(tipo=Corso.CORSO_NUOVO)
for course in courses:
# Course has extensions.
# Filter courses by titles and sede comparsion
if course.has_extensions():
- volunteer_titolo = titoli.values_list('id', flat=True)
+ volunteer_titolo = titoli.values_list('titolo', flat=True)
t = course.get_extensions_titles()
s = course.get_extensions_sede()
ext_t_list = t.values_list('id', flat=True)
# Course has required titles but volunteer has not at least one
- if t and not (set(volunteer_titolo) & set(ext_t_list)):
+ if t and not volunteer.has_required_titles_for_course(course):
continue
if s and (sede in s):
From 2fcfac2be773a1522f9f0815c09fabd97eb21cd8 Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Thu, 22 Nov 2018 16:50:22 +0100
Subject: [PATCH 043/291] MODIFIED: CorsoBase.persona() with new constant
NON_PUOI_ISCRIVERTI_NON_HAI_TITOLI added.
---
formazione/models.py | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/formazione/models.py b/formazione/models.py
index b194ceb62..eab24dd2d 100755
--- a/formazione/models.py
+++ b/formazione/models.py
@@ -132,10 +132,12 @@ class CorsoBase(Corso, ConVecchioID, ConPDF):
NON_PUOI_ISCRIVERTI_TROPPO_TARDI = "TAR"
NON_PUOI_ISCRIVERTI_GIA_ISCRITTO_ALTRO_CORSO = "ALT"
NON_PUOI_SEI_ASPIRANTE = 'ASP'
+ NON_PUOI_ISCRIVERTI_NON_HAI_TITOLI = 'NHT'
NON_PUOI_ISCRIVERTI = (NON_PUOI_ISCRIVERTI_GIA_VOLONTARIO,
NON_PUOI_ISCRIVERTI_TROPPO_TARDI,
NON_PUOI_ISCRIVERTI_GIA_ISCRITTO_ALTRO_CORSO,
- NON_PUOI_SEI_ASPIRANTE)
+ NON_PUOI_SEI_ASPIRANTE,
+ NON_PUOI_ISCRIVERTI_NON_HAI_TITOLI)
NON_PUOI_ISCRIVERTI_SOLO_SE_IN_AUTONOMIA = (NON_PUOI_ISCRIVERTI_TROPPO_TARDI,)
@@ -145,6 +147,9 @@ def persona(self, persona):
if persona.ha_aspirante:
return self.NON_PUOI_SEI_ASPIRANTE
+ if not persona.has_required_titles_for_course(course=self):
+ return self.NON_PUOI_ISCRIVERTI_NON_HAI_TITOLI
+
# if (not Aspirante.objects.filter(persona=persona).exists()) and persona.volontario:
# return self.NON_PUOI_ISCRIVERTI_GIA_VOLONTARIO
From 6757bfd98461db24c13f152f58220487258b8723 Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Fri, 23 Nov 2018 14:00:11 +0100
Subject: [PATCH 044/291] NEW: new fields formazione.models.LezioneCorsoBase
(migration 0028); fixed relative form.
---
formazione/autocomplete_light_registry.py | 14 +++++++++-
formazione/forms.py | 9 ++++---
.../migrations/0028_auto_20181122_1700.py | 27 +++++++++++++++++++
formazione/models.py | 12 ++++-----
4 files changed, 51 insertions(+), 11 deletions(-)
create mode 100644 formazione/migrations/0028_auto_20181122_1700.py
diff --git a/formazione/autocomplete_light_registry.py b/formazione/autocomplete_light_registry.py
index 43873ab44..8cdb8362d 100644
--- a/formazione/autocomplete_light_registry.py
+++ b/formazione/autocomplete_light_registry.py
@@ -1,10 +1,21 @@
from autocomplete_light import shortcuts as autocomplete_light
from anagrafica.autocomplete_light_registry import AutocompletamentoBase
-from anagrafica.models import Sede
+from anagrafica.models import Persona, Appartenenza, Sede
from curriculum.models import Titolo
+class DocenteLezioniCorso(AutocompletamentoBase):
+ search_fields = ['nome', 'cognome', 'codice_fiscale',]
+ model = Persona
+
+ def choices_for_request(self):
+ app_attuali = Appartenenza.query_attuale(membro__in=Appartenenza.MEMBRO_ATTIVITA)
+ app_attuali = app_attuali.values_list('persona__id', flat=True)
+ self.choices = self.choices.filter(id__in=app_attuali)
+ return super().choices_for_request()
+
+
class EstensioneLivelloRegionaleTitolo(AutocompletamentoBase):
search_fields = ['nome',]
model = Titolo
@@ -21,3 +32,4 @@ class EstensioneLivelloRegionaleSede(AutocompletamentoBase):
autocomplete_light.register(EstensioneLivelloRegionaleTitolo)
autocomplete_light.register(EstensioneLivelloRegionaleSede)
+autocomplete_light.register(DocenteLezioniCorso)
diff --git a/formazione/forms.py b/formazione/forms.py
index 8f7908753..567bcd958 100644
--- a/formazione/forms.py
+++ b/formazione/forms.py
@@ -47,10 +47,7 @@ def __init__(self, *args, **kwargs):
class ModuloModificaLezione(ModelForm):
- class Meta:
- model = LezioneCorsoBase
- fields = ['nome', 'inizio', 'fine']
-
+ docente = autocomplete_light.ModelChoiceField("DocenteLezioniCorso")
fine = forms.DateTimeField()
def clean(self):
@@ -64,6 +61,10 @@ def clean(self):
if inizio >= fine:
self.add_error('fine', "La fine deve essere successiva all'inizio.")
+ class Meta:
+ model = LezioneCorsoBase
+ fields = ['nome', 'inizio', 'fine', 'docente', 'obiettivo']
+
class ModuloModificaCorsoBase(ModelForm):
class Meta:
diff --git a/formazione/migrations/0028_auto_20181122_1700.py b/formazione/migrations/0028_auto_20181122_1700.py
new file mode 100644
index 000000000..dc835c6b8
--- /dev/null
+++ b/formazione/migrations/0028_auto_20181122_1700.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.12 on 2018-11-22 17:00
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('anagrafica', '0049_auto_20181028_1639'),
+ ('formazione', '0027_auto_20181120_1637'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='lezionecorsobase',
+ name='docente',
+ field=models.ForeignKey(default='', null=True, on_delete=django.db.models.deletion.CASCADE, to='anagrafica.Persona', verbose_name='Docente della lezione'),
+ ),
+ migrations.AddField(
+ model_name='lezionecorsobase',
+ name='obiettivo',
+ field=models.CharField(default='', max_length=128, null=True, verbose_name='Obiettivo formativo della lezione'),
+ ),
+ ]
diff --git a/formazione/models.py b/formazione/models.py
index eab24dd2d..59df8126b 100755
--- a/formazione/models.py
+++ b/formazione/models.py
@@ -980,11 +980,13 @@ def richieste_non_processabili(cls, richieste):
).values_list('pk', flat=True)
-class LezioneCorsoBase(ModelloSemplice, ConMarcaTemporale,
- ConGiudizio, ConStorico):
-
+class LezioneCorsoBase(ModelloSemplice, ConMarcaTemporale, ConGiudizio, ConStorico):
corso = models.ForeignKey(CorsoBase, related_name='lezioni', on_delete=models.PROTECT)
nome = models.CharField(max_length=128)
+ docente = models.ForeignKey(Persona, null=True, default='',
+ verbose_name='Docente della lezione',)
+ obiettivo = models.CharField('Obiettivo formativo della lezione',
+ max_length=128, null=True, default='')
class Meta:
verbose_name = "Lezione di Corso Base"
@@ -999,9 +1001,7 @@ def __str__(self):
@property
def url_cancella(self):
- return "%s%d/cancella/" % (
- self.corso.url_lezioni, self.pk
- )
+ return "%s%d/cancella/" % (self.corso.url_lezioni, self.pk)
class AssenzaCorsoBase(ModelloSemplice, ConMarcaTemporale):
From 158d91e60cf13bee7946aa19937b44c271257917 Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Fri, 23 Nov 2018 14:08:36 +0100
Subject: [PATCH 045/291] MODIFIED: Meta verbose_name/_plural in
formazione.models
---
formazione/models.py | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/formazione/models.py b/formazione/models.py
index 59df8126b..0ce4b7551 100755
--- a/formazione/models.py
+++ b/formazione/models.py
@@ -598,8 +598,8 @@ def key_cognome(elem):
return pdf
class Meta:
- verbose_name = "Corso Base"
- verbose_name_plural = "Corsi Base"
+ verbose_name = "Corso"
+ verbose_name_plural = "Corsi"
ordering = ['-anno', '-progressivo']
permissions = (
("view_corsobase", "Can view corso base"),
@@ -989,8 +989,8 @@ class LezioneCorsoBase(ModelloSemplice, ConMarcaTemporale, ConGiudizio, ConStori
max_length=128, null=True, default='')
class Meta:
- verbose_name = "Lezione di Corso Base"
- verbose_name_plural = "Lezioni di Corsi Base"
+ verbose_name = "Lezione di Corso"
+ verbose_name_plural = "Lezioni di Corsi"
ordering = ['inizio']
permissions = (
("view_lezionecorsobase", "Can view corso Lezione di Corso Base"),
@@ -1011,8 +1011,8 @@ class AssenzaCorsoBase(ModelloSemplice, ConMarcaTemporale):
registrata_da = models.ForeignKey(Persona, related_name='assenze_corsi_base_registrate', null=True, on_delete=models.SET_NULL)
class Meta:
- verbose_name = "Assenza a Corso Base"
- verbose_name_plural = "Assenze ai Corsi Base"
+ verbose_name = "Assenza a Corso"
+ verbose_name_plural = "Assenze ai Corsi"
permissions = (
("view_assenzacorsobase", "Can view corso Assenza a Corso Base"),
)
From bf102872d2082526e8f08db74e1205b79b618f21 Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Fri, 23 Nov 2018 14:45:38 +0100
Subject: [PATCH 046/291] NEW: blank test functions (to complete) in
formazione.test_courses
---
formazione/test_courses.py | 104 ++++++++++++++++++++++++++++++++++++-
1 file changed, 102 insertions(+), 2 deletions(-)
diff --git a/formazione/test_courses.py b/formazione/test_courses.py
index e6f0bede4..5bcdc1372 100644
--- a/formazione/test_courses.py
+++ b/formazione/test_courses.py
@@ -1,8 +1,9 @@
from datetime import datetime, timedelta
from django.test import TestCase
from django.core.urlresolvers import reverse
-from base.utils_tests import (crea_persona_sede_appartenenza, crea_persona,
- crea_appartenenza, email_fittizzia, crea_utenza, codice_fiscale, crea_locazione)
+from base.utils_tests import (crea_persona, crea_utenza, codice_fiscale,
+ email_fittizzia, crea_persona_sede_appartenenza, crea_appartenenza,
+ crea_locazione)
from anagrafica.models import *
from formazione.models import *
@@ -19,6 +20,7 @@ def create_persona(with_utenza=True):
else:
return persona
+
def create_aspirante(sede, persona=None):
if not persona:
persona = create_persona()[0]
@@ -28,6 +30,7 @@ def create_aspirante(sede, persona=None):
a.save()
return a
+
def create_course(data_inizio, sede, extension_type=CorsoBase.EXT_MIA_SEDE,
tipo=Corso.CORSO_NUOVO, **kwargs):
@@ -38,6 +41,24 @@ def create_course(data_inizio, sede, extension_type=CorsoBase.EXT_MIA_SEDE,
corso.save()
return corso
+def create_delega():
+ """ required for course directors """
+ return
+
+
+def create_volunteer():
+ # create appartenenza
+ return
+
+
+def create_extension():
+ return
+
+
+def create_extensions_for_course(course):
+ return
+
+
class TestCorsoNuovo(TestCase):
def setUp(self):
""" Create users """
@@ -59,6 +80,10 @@ def setUp(self):
self.c2 = create_course(data_inizio, self.sede, extension_type=CorsoBase.EXT_LVL_REGIONALE) # corso_2_ext_a_livello_regionale
self.c3 = create_course(data_inizio, self.sede, tipo=Corso.BASE)
+ """ Create titles """
+
+ """ Create extensions """
+
def _login_as(self, email, password='prova'):
self.client.login(username=email, password=password)
@@ -99,3 +124,78 @@ def test_corso_nuovo_visible_to_volunteer(self):
def test_corso_nuovo_extensions_link_visible_only_for_corso_nuovo(self):
pass
+ def test_volunteer_has_required_titles(self):
+ pass
+ """
+ has all titles
+ not all titles
+ """
+
+ def test_volunteer_available_courses_listing(self):
+ pass
+
+ def test_volunteer_can_participate_at_course(self):
+ pass
+
+ def test_corso_nuovo_new_fields_on_modify_page(self):
+ pass
+
+ def test_corso_nuovo_fields_visible_only_for_corso_nuovo(self):
+ pass
+
+ def test_corso_nuovo_extensions(self):
+ pass
+ """
+ 1) CAN list course: [user titles == corso ext titles (all required)] + [
+ user sede in corso ext sede]
+ 2) CAN list course: corso has only sede without titles, user sede in
+ corso sede
+ """
+
+ def test_corso_nuovo_extensions_sede(self):
+ pass
+ """
+ user app. sede in course extensions sede
+ """
+
+ def test_corso_nuovo_extensions_sede_expanded(self):
+ pass
+ """
+ - user appartenenza sede is in courses' extensions expanded sede
+ - 1st extension has sedi_sottostanti, 2nd extension not, user's sede
+ is in one of them.
+ - user app. sede is not in course extensions sede (corso non visible)
+ """
+
+ def test_volunteer_listing_course_found_by_firmatario_sede(self):
+ pass
+ """
+ if CorsoBase.extension_type = MIA_SEDE
+ user app sede == firmatario_sede
+ """
+
+ def test_method_corsobase_has_extensions(self):
+ # self.assertTrue(c2.has_extensions())
+ pass
+
+ def test_method_persona_has_required_titles_for_course(self):
+ pass
+
+ def test_method_corsobase_get_extensions(self):
+ pass
+
+ def test_method_corsobase_get_extensions_sede(self):
+ pass
+
+ def test_method_corsobase_get_extensions_titles(self):
+ pass
+
+ def test_method_corsobase_get_volunteers_by__(self):
+ pass
+ """
+ c.get_extensions_titles()
+ c.get_volunteers_by_course_requirements()
+ c.get_volunteers_by_only_sede()
+ c.get_volunteers_by_ext_sede()
+ c.get_volunteers_by_ext_titles()
+ """
From 8f3e573293e509e9b98609d658d9b1b71414f45b Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Fri, 23 Nov 2018 16:57:24 +0100
Subject: [PATCH 047/291] FIXED: data_esame field in
formazione.viste.formazione_corsi_base_nuovo
---
formazione/forms.py | 12 ++++++++++--
formazione/viste.py | 14 ++++++++------
2 files changed, 18 insertions(+), 8 deletions(-)
diff --git a/formazione/forms.py b/formazione/forms.py
index 567bcd958..34c0983c4 100644
--- a/formazione/forms.py
+++ b/formazione/forms.py
@@ -37,13 +37,21 @@ def clean_sede(self):
"tra cui l'indirizzo della stessa.")
return sede
+ def clean(self):
+ cd = self.cleaned_data
+ if cd['data_esame'] < cd['data_inizio']:
+ self.add_error('data_esame', "La data deve essere successiva "
+ "alla data di inizio.")
+ return cd
+
class Meta:
model = CorsoBase
- fields = ['tipo', 'data_inizio', 'sede',]
+ fields = ['tipo', 'data_inizio', 'data_esame', 'sede',]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- self.order_fields(('tipo', 'data_inizio', 'sede', 'locazione'))
+ self.order_fields(('tipo', 'data_inizio', 'data_esame', 'sede',
+ 'locazione'))
class ModuloModificaLezione(ModelForm):
diff --git a/formazione/viste.py b/formazione/viste.py
index 62187d8ac..af813c21f 100644
--- a/formazione/viste.py
+++ b/formazione/viste.py
@@ -58,20 +58,22 @@ def formazione_corsi_base_domanda(request, me):
@pagina_privata
def formazione_corsi_base_nuovo(request, me):
- data_inizio = datetime.now() + timedelta(days=14)
+ now = datetime.now() + timedelta(days=14)
form = ModuloCreazioneCorsoBase(
- request.POST or None, initial={"data_inizio": data_inizio}
+ request.POST or None, initial={"data_inizio": now}
)
form.fields['sede'].queryset = me.oggetti_permesso(GESTIONE_CORSI_SEDE)
if form.is_valid():
cd = form.cleaned_data
+ tipo, data_inizio, data_esame = cd['tipo'], cd['data_inizio'], cd['data_esame']
+ data_esame = data_esame if tipo == Corso.CORSO_NUOVO else data_inizio
course = CorsoBase.nuovo(
- anno=cd['data_inizio'].year,
+ anno=data_inizio.year,
sede=cd['sede'],
- data_inizio=cd['data_inizio'],
- data_esame=cd['data_inizio'],
- tipo=cd['tipo']
+ data_inizio=data_inizio,
+ data_esame=data_esame,
+ tipo=tipo
)
if cd['locazione'] == form.PRESSO_SEDE:
From 680f527364e9529e7887334e8cede441c85a330a Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Fri, 23 Nov 2018 17:29:53 +0100
Subject: [PATCH 048/291] NEW: properties is_reached_max_participants_limit and
is_nuovo_corso in formazione.models
---
formazione/models.py | 37 +++++++++++++++++++++----------------
formazione/viste.py | 13 +++++++++++++
2 files changed, 34 insertions(+), 16 deletions(-)
diff --git a/formazione/models.py b/formazione/models.py
index 0ce4b7551..8e36dfacc 100755
--- a/formazione/models.py
+++ b/formazione/models.py
@@ -385,20 +385,16 @@ def attiva(self, rispondi_a=None):
self.stato = self.ATTIVO
self.save()
- def _invia_email_agli_aspiranti(self, rispondi_a=None):
- is_nuovo_corso = self.tipo == Corso.CORSO_NUOVO
- if is_nuovo_corso:
- print('_send_email_to_participants')
+ def _corso_activation_recepients_for_email(self):
+ if self.is_nuovo_corso:
recepients = self.get_volunteers_by_course_requirements()
else:
recepients = self.aspiranti_nelle_vicinanze()
+ return recepients
- for recepient in recepients:
- if is_nuovo_corso:
- persona = recepient
- else:
- persona = recepient.persona
-
+ def _invia_email_agli_aspiranti(self, rispondi_a=None):
+ for recepient in self._corso_activation_recepients_for_email():
+ persona = recepient if self.is_nuovo_corso else recepient.persona
email_data = dict(
oggetto="Nuovo Corso per Volontari CRI",
modello="email_aspirante_corso.html",
@@ -410,10 +406,10 @@ def _invia_email_agli_aspiranti(self, rispondi_a=None):
rispondi_a=rispondi_a
)
- if is_nuovo_corso:
+ if self.is_nuovo_corso:
# If course tipo is CORSO_NUOVO to send to volunteers only
Messaggio.costruisci_e_accoda(**email_data)
- elif not is_nuovo_corso and not recepient.persona.volontario:
+ elif not self.is_nuovo_corso and not recepient.persona.volontario:
# to send to only
Messaggio.costruisci_e_accoda(**email_data)
@@ -450,11 +446,10 @@ def get_extensions_titles(self, **kwargs):
def get_volunteers_by_course_requirements(self, **kwargs):
persons = None
- if self.tipo == Corso.CORSO_NUOVO:
+ if self.is_nuovo_corso:
corso_extension = self.extension_type
if CorsoBase.EXT_MIA_SEDE == corso_extension:
- by_only_sede = self.get_volunteers_by_only_sede()
- persons = by_only_sede
+ persons = self.get_volunteers_by_only_sede()
if CorsoBase.EXT_LVL_REGIONALE == corso_extension:
by_ext_sede = self.get_volunteers_by_ext_sede()
@@ -493,7 +488,8 @@ def _query_get_volunteers_by_sede(self, appartenenze):
def get_volunteers_by_ext_titles(self):
sede = self.get_extensions_sede()
titles = self.get_extensions_titles().values_list('id', flat=True)
- return Persona.objects.filter(sede__in=sede, titoli_personali__in=titles)
+ return Persona.objects.filter(sede__in=sede,
+ titoli_personali__in=titles)
@property
def concluso(self):
@@ -597,6 +593,15 @@ def key_cognome(elem):
)
return pdf
+ @property
+ def is_reached_max_participants_limit(self):
+ actual_requests = PartecipazioneCorsoBase.objects.filter(corso=self)
+ return self.max_participants + 10 == actual_requests
+
+ @property
+ def is_nuovo_corso(self):
+ return self.tipo == Corso.CORSO_NUOVO
+
class Meta:
verbose_name = "Corso"
verbose_name_plural = "Corsi"
diff --git a/formazione/viste.py b/formazione/viste.py
index af813c21f..af5ac4dbd 100644
--- a/formazione/viste.py
+++ b/formazione/viste.py
@@ -153,9 +153,22 @@ def aspirante_corso_base_iscriviti(request, me=None, pk=None):
torna_url=corso.url
)
+ if corso.is_reached_max_participants_limit:
+ # TODO: informa direttore
+ # send_mail()
+
+ return errore_generico(request, me,
+ titolo="Non puoi partecipare a questo corso",
+ messaggio="È stato raggiunto il limite massimo di richieste di "
+ "partecipazione al corso.",
+ torna_titolo="Torna al corso",
+ torna_url=corso.url
+ )
+
p = PartecipazioneCorsoBase(persona=me, corso=corso)
p.save()
p.richiedi()
+
return messaggio_generico(request, me,
titolo="Sei iscritto al corso",
messaggio="Complimenti! Abbiamo inoltrato la tua richiesta al direttore "
From 6449c58caae2d2351c156785276c1f7e3dd57922 Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Fri, 23 Nov 2018 17:30:34 +0100
Subject: [PATCH 049/291] MODIFIED: formazione/templates
---
.../aspirante_corso_base_scheda_attiva.html | 15 ++++++++++-----
.../aspirante_corso_base_scheda_informazioni.html | 6 +++---
2 files changed, 13 insertions(+), 8 deletions(-)
diff --git a/formazione/templates/aspirante_corso_base_scheda_attiva.html b/formazione/templates/aspirante_corso_base_scheda_attiva.html
index 7dd3b3b39..3c26017fb 100644
--- a/formazione/templates/aspirante_corso_base_scheda_attiva.html
+++ b/formazione/templates/aspirante_corso_base_scheda_attiva.html
@@ -12,12 +12,17 @@
Attiva il Corso {% if corso.tipo == Corso.BASE %}Base{%endif%}
-
Assicurati di aver inserito tutte le informazioni utili per la partecipazione
- al Corso nella descrizione del corso.
-
Invieremo un messaggio a tutti i {{ corso.aspiranti_nelle_vicinanze.count }}
- aspiranti nelle vicinanze avvisandoli dell'attivazione di questo corso.
+
Assicurati di aver inserito tutte le informazioni utili per la partecipazione al Corso nella descrizione del corso.
+
+
Invieremo un messaggio a tutti i
+ {% if corso.is_nuovo_corso %}
+ volontari in base all' estensione impostata
+ {% else %}
+ {{ corso.aspiranti_nelle_vicinanze.count }} aspiranti nelle vicinanze
+ {% endif %} avvisandoli dell'attivazione di questo corso.
+
Tieni presente che puoi attivare il corso, ed inviare questa e-mail, solo una volta.
-
Ecco un'anteprima dell'e-mail che invieremo a tutti gli aspiranti:
+
Ecco un'anteprima dell'e-mail che invieremo a tutti {% if corso.is_nuovo_corso %}i volontari{%else%}gli aspiranti{%endif%}:
Con questo modulo puoi selezionare uno o più
- Sostenitori o Aspiranti da iscrivere a questo
- corso base.
-
Presidente e Ufficio Soci possono inserire i sostenitori
- dal pannello "Soci" > "Aggiungi Persona".
+ {% if corso.is_nuovo_corso %}
+ Volontari da iscrivere a questo corso.
+ {% else %}
+ Sostenitori o Aspiranti da iscrivere a questo corso base.
+ {% endif %}
+
+
Presidente e Ufficio Soci possono inserire i sostenitori dal pannello "Soci" > "Aggiungi Persona".
diff --git a/formazione/viste.py b/formazione/viste.py
index af5ac4dbd..cfed62920 100644
--- a/formazione/viste.py
+++ b/formazione/viste.py
@@ -59,8 +59,8 @@ def formazione_corsi_base_domanda(request, me):
@pagina_privata
def formazione_corsi_base_nuovo(request, me):
now = datetime.now() + timedelta(days=14)
- form = ModuloCreazioneCorsoBase(
- request.POST or None, initial={"data_inizio": now}
+ form = ModuloCreazioneCorsoBase(request.POST or None,
+ initial={'data_inizio': now, 'data_esame': now + timedelta(days=14)}
)
form.fields['sede'].queryset = me.oggetti_permesso(GESTIONE_CORSI_SEDE)
@@ -538,7 +538,7 @@ def aspirante_corso_base_iscritti_aggiungi(request, me, pk):
"stadio della vita del corso base.",
torna_titolo="Torna al corso base", torna_url=corso.url_iscritti)
- modulo = ModuloIscrittiCorsoBaseAggiungi(request.POST or None)
+ modulo = ModuloIscrittiCorsoBaseAggiungi(request.POST or None, corso=corso)
risultati = []
if modulo.is_valid():
From da17adbd16c010dcf0f973b5af176cd5a3ecb42f Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Tue, 27 Nov 2018 11:45:12 +0100
Subject: [PATCH 052/291] MODIFIED: CorsoFile.file upload_to param with new
function (upload path /course/id/file
---
formazione/models.py | 8 ++++++--
formazione/validators.py | 7 ++++++-
2 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/formazione/models.py b/formazione/models.py
index 8e36dfacc..7225b291e 100755
--- a/formazione/models.py
+++ b/formazione/models.py
@@ -21,7 +21,7 @@
from curriculum.models import Titolo
from posta.models import Messaggio
from social.models import ConCommenti, ConGiudizio
-
+from .validators import course_file_directory_path
class Corso(ModelloSemplice, ConDelegati, ConMarcaTemporale,
ConGeolocalizzazione, ConCommenti, ConGiudizio):
@@ -63,12 +63,16 @@ class CorsoFile(models.Model):
is_enabled = models.BooleanField(default=True)
corso = models.ForeignKey('CorsoBase')
file = models.FileField('FIle', null=True, blank=True,
- upload_to='corsi/',
+ upload_to=course_file_directory_path,
validators=[valida_dimensione_file_8mb, validate_file_extension],
# help_text="Formati dei file supportati: doc, xls, pdf, zip, "
# "jpg (max 8mb))",
)
+ def filename(self):
+ import os
+ return os.path.basename(self.file.name)
+
def __str__(self):
file = self.file if self.file else ''
corso = self.corso if hasattr(self, 'corso') else ''
diff --git a/formazione/validators.py b/formazione/validators.py
index 206113b89..7ad5be997 100644
--- a/formazione/validators.py
+++ b/formazione/validators.py
@@ -7,4 +7,9 @@ def validate_file_extension(value):
'.png', '.xlsx', '.xls']
if ext.lower() not in valid_extensions:
raise ValidationError("Estensione <%s> di questo file non è "
- "accettabile." % ext)
\ No newline at end of file
+ "accettabile." % ext)
+
+
+def course_file_directory_path(instance, filename):
+ # file will be uploaded to MEDIA_ROOT/course/
+ return 'courses/%s/%s' % (instance.id, filename)
From 1542bf7b7f22e0053d9470376eb6392c6fb6cd22 Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Tue, 27 Nov 2018 11:48:14 +0100
Subject: [PATCH 053/291] ADDED: CorsoBase.get_course_links()/_files() methods;
MODIFIED: RICHIESTA_NOME in PartecipazioneCorsoBase/InvitoCorsoBase.
---
formazione/models.py | 10 ++++--
.../aspirante_corso_base_scheda.html | 4 +--
...irante_corso_base_scheda_informazioni.html | 35 ++++++++++++++-----
3 files changed, 37 insertions(+), 12 deletions(-)
diff --git a/formazione/models.py b/formazione/models.py
index 7225b291e..d38bfd66d 100755
--- a/formazione/models.py
+++ b/formazione/models.py
@@ -606,6 +606,12 @@ def is_reached_max_participants_limit(self):
def is_nuovo_corso(self):
return self.tipo == Corso.CORSO_NUOVO
+ def get_course_links(self):
+ return self.corsolink_set.filter(is_enabled=True)
+
+ def get_course_files(self):
+ return self.corsofile_set.filter(is_enabled=True)
+
class Meta:
verbose_name = "Corso"
verbose_name_plural = "Corsi"
@@ -686,7 +692,7 @@ class InvitoCorsoBase(ModelloSemplice, ConAutorizzazioni,
IN_ATTESA_ASPIRANTE = 2
INVITO_INVIATO = -1
- RICHIESTA_NOME = "iscrizione a Corso Base"
+ RICHIESTA_NOME = "iscrizione a Corso"
APPROVAZIONE_AUTOMATICA = datetime.timedelta(days=settings.SCADENZA_AUTORIZZAZIONE_AUTOMATICA)
@@ -842,7 +848,7 @@ class Meta:
("view_partecipazionecorsobarse", "Can view corso Richiesta di partecipazione"),
)
- RICHIESTA_NOME = "Iscrizione Corso Base"
+ RICHIESTA_NOME = "Iscrizione Corso"
def autorizzazione_concessa(self, modulo=None, auto=False, notifiche_attive=True, data=None):
# Quando un aspirante viene iscritto, tutte le richieste presso altri corsi devono essere cancellati.
diff --git a/formazione/templates/aspirante_corso_base_scheda.html b/formazione/templates/aspirante_corso_base_scheda.html
index fe47f8100..c55d6321b 100644
--- a/formazione/templates/aspirante_corso_base_scheda.html
+++ b/formazione/templates/aspirante_corso_base_scheda.html
@@ -137,11 +137,11 @@
-
+ {% endif %}
+
+
+
+ {% if corso.get_course_links %}
+ File:
+ {% for file in corso.get_course_files %}
+ {{file.filename}}
+ {% endfor %}
+ {% endif %}
+
+ {% if corso.tipo == corso.CORSO_NUOVO and corso.get_extensions_titles %}
+
+ Titoli necessari:
+
+ {% for e in corso.get_extensions_titles %}
+
{{ e.nome }}
+ {% endfor %}
+
+
+ {% endif %}
{% endif %}
From 367e5b8c419f3eb7431b3ae0d6e153b1ea618a90 Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Tue, 27 Nov 2018 11:48:44 +0100
Subject: [PATCH 054/291] FIXED: formazione.test_courses
---
formazione/test_courses.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/formazione/test_courses.py b/formazione/test_courses.py
index 961bcfdae..36b48f782 100644
--- a/formazione/test_courses.py
+++ b/formazione/test_courses.py
@@ -209,6 +209,6 @@ def test_property_is_corso_nuovo(self):
self.assertFalse(self.c3.is_nuovo_corso)
def test_field_extension_type(self):
- self.assertTrue(self.c1.extension_type, CorsoBase.EXT_MIA_SEDE)
- self.assertTrue(self.c2.extension_type, CorsoBase.EXT_LVL_REGIONALE)
- self.assertTrue(self.c3.extension_type, CorsoBase.EXT_MIA_SEDE)
+ self.assertEqual(self.c1.extension_type, CorsoBase.EXT_MIA_SEDE)
+ self.assertEqual(self.c2.extension_type, CorsoBase.EXT_LVL_REGIONALE)
+ self.assertEqual(self.c3.extension_type, CorsoBase.EXT_MIA_SEDE)
From 7420cfe6b30e8fde040aeda05c3765819872b91f Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Tue, 27 Nov 2018 11:56:34 +0100
Subject: [PATCH 055/291] FIXED: attrs in
formazione.autocomplete.InvitaCorsoNuovoAutocompletamento
---
formazione/autocomplete_light_registry.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/formazione/autocomplete_light_registry.py b/formazione/autocomplete_light_registry.py
index 7cd5b539d..cf711708b 100644
--- a/formazione/autocomplete_light_registry.py
+++ b/formazione/autocomplete_light_registry.py
@@ -36,6 +36,11 @@ class InvitaCorsoNuovoAutocompletamento(AutocompletamentoBase):
model = Persona
search_fields = ['codice_fiscale',]
choice_html_format = """%s %s"""
+ attrs = {
+ 'required': False,
+ 'placeholder': 'Inserisci il codice fiscale',
+ 'data-autocomplete-minimum-characters': 16,
+ }
def choices_for_request(self):
self.choices = self.choices.filter(Q(Appartenenza.query_attuale(
From f30cc0a9d41d083d93515a6e6ddece75ba4f4254 Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Tue, 27 Nov 2018 15:30:07 +0100
Subject: [PATCH 056/291] FIXED: small fixes in formazione.models and
template/aspirante_corso_base_scheda_informazioni.html
---
formazione/models.py | 31 +++++++++----------
...irante_corso_base_scheda_informazioni.html | 22 ++++++-------
2 files changed, 26 insertions(+), 27 deletions(-)
diff --git a/formazione/models.py b/formazione/models.py
index d38bfd66d..5d1d61a6f 100755
--- a/formazione/models.py
+++ b/formazione/models.py
@@ -157,7 +157,9 @@ def persona(self, persona):
# if (not Aspirante.objects.filter(persona=persona).exists()) and persona.volontario:
# return self.NON_PUOI_ISCRIVERTI_GIA_VOLONTARIO
- if PartecipazioneCorsoBase.con_esito_ok(persona=persona, corso__stato=self.ATTIVO).exclude(corso=self).exists():
+ if PartecipazioneCorsoBase.con_esito_ok(persona=persona,
+ corso__tipo=self.BASE,
+ corso__stato=self.ATTIVO).exclude(corso=self).exists():
return self.NON_PUOI_ISCRIVERTI_GIA_ISCRITTO_ALTRO_CORSO
# Controlla se gia' iscritto.
@@ -680,8 +682,7 @@ class Meta:
verbose_name_plural = 'Estensioni del Corso'
-class InvitoCorsoBase(ModelloSemplice, ConAutorizzazioni,
- ConMarcaTemporale, models.Model):
+class InvitoCorsoBase(ModelloSemplice, ConAutorizzazioni, ConMarcaTemporale, models.Model):
persona = models.ForeignKey(Persona, related_name='inviti_corsi', on_delete=models.CASCADE)
corso = models.ForeignKey(CorsoBase, related_name='inviti', on_delete=models.PROTECT)
invitante = models.ForeignKey(Persona, related_name='+', on_delete=models.CASCADE)
@@ -696,19 +697,6 @@ class InvitoCorsoBase(ModelloSemplice, ConAutorizzazioni,
APPROVAZIONE_AUTOMATICA = datetime.timedelta(days=settings.SCADENZA_AUTORIZZAZIONE_AUTOMATICA)
- class Meta:
- verbose_name = "Invito di partecipazione a corso base"
- verbose_name_plural = "Inviti di partecipazione a corso base"
- ordering = ('persona__cognome', 'persona__nome', 'persona__codice_fiscale',)
- permissions = (
- ("view_invitocorsobase", "Can view invito partecipazione corso base"),
- )
-
- def __str__(self):
- return "Invit di part. di %s a %s" % (
- self.persona, self.corso
- )
-
def autorizzazione_concessa(self, modulo=None, auto=False, notifiche_attive=True, data=None):
with atomic():
corso = self.corso
@@ -779,6 +767,17 @@ def disiscrivi(self, mittente=None):
destinatari=[mittente],
)
+ class Meta:
+ verbose_name = "Invito di partecipazione a corso"
+ verbose_name_plural = "Inviti di partecipazione a corso"
+ ordering = ('persona__cognome', 'persona__nome', 'persona__codice_fiscale',)
+ permissions = (
+ ("view_invitocorsobase", "Can view invito partecipazione corso base"),
+ )
+
+ def __str__(self):
+ return "Invit di part. di %s a %s" % (self.persona, self.corso)
+
class PartecipazioneCorsoBase(ModelloSemplice, ConMarcaTemporale,
ConAutorizzazioni, ConPDF):
diff --git a/formazione/templates/aspirante_corso_base_scheda_informazioni.html b/formazione/templates/aspirante_corso_base_scheda_informazioni.html
index a4bfb661a..31b5d145f 100644
--- a/formazione/templates/aspirante_corso_base_scheda_informazioni.html
+++ b/formazione/templates/aspirante_corso_base_scheda_informazioni.html
@@ -158,7 +158,7 @@
Informazioni
{{ corso.descrizione|default:"Ancora non disponibili"|safe }}
- {% if puoi_partecipare == corso.SEI_ISCRITTO_NON_PUOI_RITIRARTI %}
+ {% if puoi_partecipare == corso.SEI_ISCRITTO_NON_PUOI_RITIRARTI or puo_modificare %}
{% if corso.get_course_links or corso.get_course_files %}
Materiale didattico
{%endif%}
{% if corso.get_course_links %}
Link:
@@ -175,17 +175,17 @@
From c984741961cd69f3d5ee95005e35b1dcf02cc16c Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Fri, 30 Nov 2018 10:16:02 +0100
Subject: [PATCH 063/291] ADDED: AdminInvotoCorsoBase in formazione.admin
---
formazione/admin.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/formazione/admin.py b/formazione/admin.py
index 5aa482437..d1a320378 100755
--- a/formazione/admin.py
+++ b/formazione/admin.py
@@ -75,6 +75,12 @@ class AdminCorsoBase(ReadonlyAdminMixin, admin.ModelAdmin):
actions = [admin_corsi_base_attivi_invia_messaggi]
+@admin.register(InvitoCorsoBase)
+class AdminInvitoCorsoBase(admin.ModelAdmin):
+ list_display = ['persona', 'corso', 'invitante',]
+ list_filter = ['ritirata', 'confermata', 'automatica']
+
+
@admin.register(CorsoFile)
class AdminCorsoFile(admin.ModelAdmin):
list_display = ['__str__', 'file', 'is_enabled', 'corso',]
From f523a0caca1ef5364351da1d5202809331ee0bd0 Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Fri, 30 Nov 2018 10:44:33 +0100
Subject: [PATCH 064/291] FIXED: queryset in choices_for_request, when choosing
a direttore to CorsoNuovo
---
formazione/autocomplete_light_registry.py | 41 ++++++++++++++++-------
1 file changed, 29 insertions(+), 12 deletions(-)
diff --git a/formazione/autocomplete_light_registry.py b/formazione/autocomplete_light_registry.py
index 948e4ad81..9ff506973 100644
--- a/formazione/autocomplete_light_registry.py
+++ b/formazione/autocomplete_light_registry.py
@@ -7,6 +7,23 @@
from curriculum.models import Titolo
+class AutocompletamentoBasePersonaModelMixin(AutocompletamentoBase):
+ choice_html_format = """%s %s"""
+
+ def choice_html(self, choice):
+ if choice.appartenenze_attuali(membro=Appartenenza.VOLONTARIO).exists():
+ app = choice.appartenenze_attuali(membro=Appartenenza.VOLONTARIO).first()
+ else:
+ app = choice.appartenenze_attuali().first() if choice else None
+
+ # Example: Charles Campbell (Volontario del Comitato 1 sotto Metropolitano)
+ data_value = self.choice_value(choice)
+ full_name = self.choice_label(choice)
+ membership = ("(%s del %s)" % (app.get_membro_display(), app.sede)) if app else ''
+
+ return self.choice_html_format % (data_value, full_name, membership)
+
+
class DocenteLezioniCorso(AutocompletamentoBase):
search_fields = ['nome', 'cognome', 'codice_fiscale',]
model = Persona
@@ -32,10 +49,9 @@ class EstensioneLivelloRegionaleSede(AutocompletamentoBase):
model = Sede
-class InvitaCorsoNuovoAutocompletamento(AutocompletamentoBase):
+class InvitaCorsoNuovoAutocompletamento(AutocompletamentoBasePersonaModelMixin):
model = Persona
search_fields = ['codice_fiscale',]
- choice_html_format = """%s %s"""
attrs = {
'required': False,
'placeholder': 'Inserisci il codice fiscale',
@@ -48,19 +64,20 @@ def choices_for_request(self):
))
return super().choices_for_request()
- def choice_html(self, choice):
- if choice.appartenenze_attuali(membro=Appartenenza.VOLONTARIO).exists():
- app = choice.appartenenze_attuali(membro=Appartenenza.VOLONTARIO).first()
- else:
- app = choice.appartenenze_attuali().first() if choice else None
- return self.choice_html_format % (
- self.choice_value(choice),
- self.choice_label(choice),
- ("(%s del %s)" % (app.get_membro_display(), app.sede)) if app else '',
- )
+
+class CreateDirettoreDelegaAutocompletamento(AutocompletamentoBasePersonaModelMixin):
+ model = Persona
+ search_fields = ['nome', 'cognome', 'codice_fiscale',]
+
+ def choices_for_request(self):
+ self.choices = self.choices.filter(Q(Appartenenza.query_attuale(
+ membro__in=Appartenenza.MEMBRO_CORSO).via("appartenenze")
+ )).distinct('nome', 'cognome', 'codice_fiscale')
+ return super().choices_for_request()
autocomplete_light.register(EstensioneLivelloRegionaleTitolo)
autocomplete_light.register(EstensioneLivelloRegionaleSede)
autocomplete_light.register(DocenteLezioniCorso)
autocomplete_light.register(InvitaCorsoNuovoAutocompletamento)
+autocomplete_light.register(CreateDirettoreDelegaAutocompletamento)
From d7fb52f3cfc3c365ad0fe818d119b1b08e03d3a7 Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Fri, 30 Nov 2018 10:48:53 +0100
Subject: [PATCH 065/291] NEW: form for
formazione.viste.aspirante_corso_estensioni_informa.
---
formazione/forms.py | 28 +++++++++++++++++++
.../aspirante_corso_informa_persone.html | 1 +
formazione/viste.py | 23 +++++++++++++++
3 files changed, 52 insertions(+)
diff --git a/formazione/forms.py b/formazione/forms.py
index 5671c8111..c3a0bf680 100644
--- a/formazione/forms.py
+++ b/formazione/forms.py
@@ -1,3 +1,5 @@
+from datetime import datetime
+
from django import forms
from django.core.exceptions import ValidationError
from django.forms import ModelForm, modelformset_factory
@@ -308,3 +310,29 @@ def __init__(self, *args, **kwargs):
if attr in kwargs:
setattr(self, attr, kwargs.pop(attr))
super().__init__(*args, **kwargs)
+
+
+class InformCourseParticipantsForm(forms.Form):
+ ALL = '1'
+ UNCONFIRMED_REQUESTS = '2'
+ CONFIRMED_REQUESTS = '3'
+ INVIA_QUESTIONARIO = '4'
+
+ CHOICES = (
+ (ALL, "A tutti (già iscritti + chi ha fatto richiesta)"),
+ (UNCONFIRMED_REQUESTS, 'Solo a chi ha fatto richieste'),
+ (CONFIRMED_REQUESTS, 'Partecipanti confermati'),
+ )
+
+ recipient_type = forms.ChoiceField(choices=CHOICES, label='Destinatari')
+ message = forms.CharField(label='Messaggio', required=True)
+
+ def __init__(self, *args, **kwargs):
+ self.instance = kwargs.pop('instance')
+ super().__init__(*args, **kwargs)
+
+ # Append INVIA_QUESTIONARIO option to select if data_esame is reached
+ if datetime.now() > self.instance.data_esame:
+ self.fields['recipient_type'].widget.choices.append(
+ (self.INVIA_QUESTIONARIO, "Invia questionario di gradimento ai partecipanti")
+ )
\ No newline at end of file
diff --git a/formazione/templates/aspirante_corso_informa_persone.html b/formazione/templates/aspirante_corso_informa_persone.html
index da306bfbe..aee0a62ac 100644
--- a/formazione/templates/aspirante_corso_informa_persone.html
+++ b/formazione/templates/aspirante_corso_informa_persone.html
@@ -12,6 +12,7 @@
Informare persone di
diff --git a/formazione/viste.py b/formazione/viste.py
index 95f03d8e2..8de48fdb2 100644
--- a/formazione/viste.py
+++ b/formazione/viste.py
@@ -805,13 +805,36 @@ def aspirante_corso_estensioni_modifica(request, me, pk):
@pagina_privata
def aspirante_corso_estensioni_informa(request, me, pk):
+ from .forms import InformCourseParticipantsForm
+
course = get_object_or_404(CorsoBase, pk=pk)
if not me.permessi_almeno(course, MODIFICA):
return redirect(ERRORE_PERMESSI)
+ qs = Persona.objects.filter()
+ form_data = {
+ 'instance': course,
+ }
+ form = InformCourseParticipantsForm(request.POST or None, **form_data)
+ if form.is_valid():
+ cd = form.cleaned_data
+ recipients = cd['recipients']
+ if recipients == form.UNCONFIRMED_REQUESTS:
+ pass
+ elif recipients == form.CONFIRMED_REQUESTS:
+ pass
+ elif recipients == form.INVIA_QUESTIONARIO:
+ pass
+ elif recipients == form.ALL:
+ pass
+ else:
+ # todo: something went wrong ...
+ pass
+
context = {
'corso': course,
+ 'form': form,
'puo_modificare': True,
}
return 'aspirante_corso_informa_persone.html', context
From 12349cd153a47f718dc170ad7c077c2df83ce511 Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Fri, 30 Nov 2018 10:50:31 +0100
Subject: [PATCH 066/291] FIXED: formazione/templates (small fixes)
---
formazione/models.py | 3 +-
.../aspirante_corso_base_scheda.html | 2 +-
...irante_corso_base_scheda_informazioni.html | 2 +-
.../aspirante_corso_base_scheda_iscritti.html | 2 +-
.../templates/formazione_corsi_base_fine.html | 33 ++++---------------
5 files changed, 12 insertions(+), 30 deletions(-)
diff --git a/formazione/models.py b/formazione/models.py
index 69e358364..8195ea7dc 100755
--- a/formazione/models.py
+++ b/formazione/models.py
@@ -24,6 +24,7 @@
from social.models import ConCommenti, ConGiudizio
from .validators import course_file_directory_path
+
class Corso(ModelloSemplice, ConDelegati, ConMarcaTemporale,
ConGeolocalizzazione, ConCommenti, ConGiudizio):
# Tipologia di corso
@@ -781,7 +782,7 @@ class Meta:
)
def __str__(self):
- return "Invit di part. di %s a %s" % (self.persona, self.corso)
+ return "Invito di part. di <%s> a <%s>" % (self.persona, self.corso)
class PartecipazioneCorsoBase(ModelloSemplice, ConMarcaTemporale,
diff --git a/formazione/templates/aspirante_corso_base_scheda.html b/formazione/templates/aspirante_corso_base_scheda.html
index 9ab192cad..e60189aeb 100644
--- a/formazione/templates/aspirante_corso_base_scheda.html
+++ b/formazione/templates/aspirante_corso_base_scheda.html
@@ -131,7 +131,7 @@
Il corso non è ancora attivo
{% checkbox corso.descrizione %}:
- Inserisci una descrizione del Corso per gli Aspiranti dalla scheda "Gestione corso";
+ Inserisci una descrizione del Corso per i Corsisti dalla scheda "Gestione corso";
-
-
-
-
-{% endblock %}
\ No newline at end of file
+{% endblock %}
From 0b16ebfc82f609b4350fa632537c059649685a8b Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Fri, 30 Nov 2018 11:40:09 +0100
Subject: [PATCH 067/291] FIXED: Appartenenza QuerySet in
CorsoBase.get_volunteers_by_only_sede(), get_volunteers_by_ext_sede();
renamed recepients-> recipients
---
formazione/models.py | 26 +++++++++++++++-----------
1 file changed, 15 insertions(+), 11 deletions(-)
diff --git a/formazione/models.py b/formazione/models.py
index 8195ea7dc..a866d4fec 100755
--- a/formazione/models.py
+++ b/formazione/models.py
@@ -397,16 +397,16 @@ def attiva(self, rispondi_a=None):
self.stato = self.ATTIVO
self.save()
- def _corso_activation_recepients_for_email(self):
+ def _corso_activation_recipients_for_email(self):
if self.is_nuovo_corso:
- recepients = self.get_volunteers_by_course_requirements()
+ recipients = self.get_volunteers_by_course_requirements()
else:
- recepients = self.aspiranti_nelle_vicinanze()
- return recepients
+ recipients = self.aspiranti_nelle_vicinanze()
+ return recipients
def _invia_email_agli_aspiranti(self, rispondi_a=None):
- for recepient in self._corso_activation_recepients_for_email():
- persona = recepient if self.is_nuovo_corso else recepient.persona
+ for recipient in self._corso_activation_recipients_for_email():
+ persona = recipient if self.is_nuovo_corso else recipient.persona
email_data = dict(
oggetto="Nuovo Corso per Volontari CRI",
modello="email_aspirante_corso.html",
@@ -421,7 +421,7 @@ def _invia_email_agli_aspiranti(self, rispondi_a=None):
if self.is_nuovo_corso:
# If course tipo is CORSO_NUOVO to send to volunteers only
Messaggio.costruisci_e_accoda(**email_data)
- elif not self.is_nuovo_corso and not recepient.persona.volontario:
+ elif not self.is_nuovo_corso and not recipient.persona.volontario:
# to send to only
Messaggio.costruisci_e_accoda(**email_data)
@@ -483,13 +483,17 @@ def get_firmatario_sede(self):
return course_created_by.firmatario.sede_riferimento()
def get_volunteers_by_only_sede(self):
- app = Appartenenza.objects.filter(sede=self.get_firmatario_sede,
- membro=Appartenenza.VOLONTARIO)
+ app_attuali = Appartenenza.query_attuale(membro=Appartenenza.VOLONTARIO).q
+ app = Appartenenza.objects.filter(app_attuali,
+ sede=self.get_firmatario_sede,
+ confermata=True)
return self._query_get_volunteers_by_sede(app)
def get_volunteers_by_ext_sede(self):
- app = Appartenenza.objects.filter(sede__in=self.get_extensions_sede(),
- membro=Appartenenza.VOLONTARIO)
+ app_attuali = Appartenenza.query_attuale(membro=Appartenenza.VOLONTARIO).q
+ app = Appartenenza.objects.filter(app_attuali,
+ sede__in=self.get_extensions_sede(),
+ confermata=True)
return self._query_get_volunteers_by_sede(app)
def _query_get_volunteers_by_sede(self, appartenenze):
From 3b30603629307da4ee75135614761113f2568551 Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Fri, 30 Nov 2018 16:22:37 +0100
Subject: [PATCH 068/291] FIXED: various
---
anagrafica/admin.py | 2 +-
formazione/templates/aspirante_corso_base_scheda.html | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/anagrafica/admin.py b/anagrafica/admin.py
index 96ef5ea7c..4c72cb902 100755
--- a/anagrafica/admin.py
+++ b/anagrafica/admin.py
@@ -242,7 +242,7 @@ class AdminAppartenenza(ReadonlyAdminMixin, admin.ModelAdmin):
search_fields = ["membro", "persona__nome", "persona__cognome", "persona__codice_fiscale",
"persona__utenza__email", "sede__nome"]
list_display = ("persona", "sede", "attuale", "inizio", "fine", "creazione")
- list_filter = ("membro", "inizio", "fine")
+ list_filter = ('confermata', "membro", "inizio", "fine")
raw_id_fields = RAW_ID_FIELDS_APPARTENENZA
inlines = [InlineAutorizzazione]
diff --git a/formazione/templates/aspirante_corso_base_scheda.html b/formazione/templates/aspirante_corso_base_scheda.html
index e60189aeb..d8bf76b72 100644
--- a/formazione/templates/aspirante_corso_base_scheda.html
+++ b/formazione/templates/aspirante_corso_base_scheda.html
@@ -124,8 +124,8 @@
Non c'è tempo da perdere!
{% if puo_modificare and corso.stato == corso.PREPARAZIONE %}
Il corso non è ancora attivo
-
Questo corso è ancora una bozza ("In Preparazione"). È necessario
- attivare il corso affinché questo possa essere trovato dagli aspiranti.
+
Questo corso è ancora una bozza ("In Preparazione").
+ È necessario attivare il corso affinché questo possa essere trovato {% if corso.is_nuovo_corso %}dai volontari{%else%}dagli aspiranti{%endif%}.
Completa i passi necessari e clicca sul pulsante per attivare il corso:
+
+
+
{% endblock %}
\ No newline at end of file
From 3941834f2c71995fae7f69edee0e2cf4899aa26e Mon Sep 17 00:00:00 2001
From: Arkady Karlkvist
Date: Fri, 30 Nov 2018 17:49:25 +0100
Subject: [PATCH 070/291] NEW: working on CorsoNuovo termina (changes made in
forms/models/viste). Bulk_create TitoloPersonale for participants.
---
formazione/forms.py | 12 +++--
formazione/models.py | 51 +++++++++++++++----
formazione/viste.py | 37 ++++++++------
.../email_volontario_corso_esito.html | 34 +++++++++++++
4 files changed, 103 insertions(+), 31 deletions(-)
create mode 100644 posta/templates/email_volontario_corso_esito.html
diff --git a/formazione/forms.py b/formazione/forms.py
index b7d1fbb89..246174d08 100644
--- a/formazione/forms.py
+++ b/formazione/forms.py
@@ -231,7 +231,6 @@ class ModuloConfermaIscrizioneCorsoBase(forms.Form):
class ModuloVerbaleAspiranteCorsoBase(ModelForm):
-
GENERA_VERBALE = 'genera_verbale'
SALVA_SOLAMENTE = 'salva'
@@ -247,7 +246,11 @@ class Meta:
def __init__(self, *args, generazione_verbale=False, **kwargs):
self.generazione_verbale = generazione_verbale
- super(ModuloVerbaleAspiranteCorsoBase, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
+
+ if not self.instance.corso.is_nuovo_corso:
+ # This field is not required if Corso Nuovo
+ self.fields.pop('destinazione')
def clean(self):
"""
@@ -307,7 +310,6 @@ def clean(self):
# Se sto generando il verbale, controlla che tutti i campi
# obbligatori siano stati riempiti.
if self.generazione_verbale:
-
if not destinazione:
self.add_error('destinazione',
"È necessario selezionare la Sede presso la quale il Volontario "
@@ -342,7 +344,7 @@ class InformCourseParticipantsForm(forms.Form):
)
recipient_type = forms.ChoiceField(choices=CHOICES, label='Destinatari')
- message = forms.CharField(label='Messaggio', required=True)
+ message = forms.CharField(label='Messaggio', required=True) #widget=WYSIWYGSemplice())
def __init__(self, *args, **kwargs):
self.instance = kwargs.pop('instance')
@@ -352,4 +354,4 @@ def __init__(self, *args, **kwargs):
if datetime.now() > self.instance.data_esame:
self.fields['recipient_type'].widget.choices.append(
(self.INVIA_QUESTIONARIO, "Invia questionario di gradimento ai partecipanti")
- )
\ No newline at end of file
+ )
diff --git a/formazione/models.py b/formazione/models.py
index aa2987b45..23308fe0b 100755
--- a/formazione/models.py
+++ b/formazione/models.py
@@ -522,11 +522,9 @@ def ha_verbale(self):
return self.stato == self.TERMINATO and self.partecipazioni_confermate().exists()
def termina(self, mittente=None):
- """
- Termina il corso base, genera il verbale e volontarizza.
- """
-
+ """ Termina il corso base, genera il verbale e volontarizza. """
from django.db import transaction
+
with transaction.atomic():
# Per maggiore sicurezza, questa cosa viene eseguita in una transazione.
@@ -540,9 +538,14 @@ def termina(self, mittente=None):
# Comunica il risultato all'aspirante/volontario.
partecipante.notifica_esito_esame(mittente=mittente)
- if partecipante.idoneo: # Se idoneo, volontarizza.
- partecipante.persona.da_aspirante_a_volontario(sede=partecipante.destinazione,
- mittente=mittente)
+ # Actions required only for CorsoBase (Aspirante as participant)
+ if not self.is_nuovo_corso:
+ # Se idoneo, volontarizza
+ if partecipante.idoneo:
+ partecipante.persona.da_aspirante_a_volontario(
+ sede=partecipante.destinazione,
+ mittente=mittente)
+
# Cancella tutte le eventuali partecipazioni in attesa.
PartecipazioneCorsoBase.con_esito_pending(corso=self).delete()
@@ -551,6 +554,30 @@ def termina(self, mittente=None):
self.stato = Corso.TERMINATO
self.save()
+ if self.is_nuovo_corso:
+ self.set_titolo_cri_to_participants()
+
+ def set_titolo_cri_to_participants(self):
+ """ Sets in Persona's Curriculum (TitoloPersonale) """
+
+ # todo: unfinished
+ objs = [
+ TitoloPersonale(
+ confermata=True,
+ titolo=self.titolo_cri,
+ persona=persona,
+ # data_ottenimento='',
+ # luogo_ottenimento='',
+ data_scadenza='',
+ # codice='',
+ # codice_corso='',
+ # certificato='',
+ # certificato_da='',
+ )
+ for persona in self.partecipazioni_confermate()
+ ]
+ TitoloPersonale.objects.bulk_create(objs)
+
def non_idonei(self):
return self.partecipazioni_confermate().filter(esito_esame=PartecipazioneCorsoBase.NON_IDONEO)
@@ -892,12 +919,14 @@ def idoneo(self):
)
def notifica_esito_esame(self, mittente=None):
- """
- Invia una e-mail al partecipante con l'esito del proprio esame.
- """
+ """ Invia una e-mail al partecipante con l'esito del proprio esame. """
+
+ template = "email_%s_corso_esito.html"
+ template = template % 'volontario' if self.corso.is_nuovo_corso else 'aspirante'
+
Messaggio.costruisci_e_accoda(
oggetto="Esito del Corso Base: %s" % self.corso,
- modello="email_aspirante_corso_esito.html",
+ modello=template,
corpo={
"partecipazione": self,
"corso": self.corso,
diff --git a/formazione/viste.py b/formazione/viste.py
index 8de48fdb2..2404a215c 100644
--- a/formazione/viste.py
+++ b/formazione/viste.py
@@ -445,26 +445,27 @@ def aspirante_corso_base_termina(request, me, pk):
azione = request.POST.get('azione', default=ModuloVerbaleAspiranteCorsoBase.SALVA_SOLAMENTE)
generazione_verbale = azione == ModuloVerbaleAspiranteCorsoBase.GENERA_VERBALE
-
termina_corso = generazione_verbale
for partecipante in corso.partecipazioni_confermate():
- modulo = ModuloVerbaleAspiranteCorsoBase(
+ form = ModuloVerbaleAspiranteCorsoBase(
request.POST or None, prefix="part_%d" % partecipante.pk,
instance=partecipante,
generazione_verbale=generazione_verbale
)
- modulo.fields['destinazione'].queryset = corso.possibili_destinazioni()
- modulo.fields['destinazione'].initial = corso.sede
-
- if modulo.is_valid():
- modulo.save()
+ if corso.is_nuovo_corso:
+ pass
+ else:
+ form.fields['destinazione'].queryset = corso.possibili_destinazioni()
+ form.fields['destinazione'].initial = corso.sede
+ if form.is_valid():
+ form.save()
elif generazione_verbale:
termina_corso = False
- partecipanti_moduli += [(partecipante, modulo)]
+ partecipanti_moduli += [(partecipante, form)]
if termina_corso: # Se il corso può essere terminato.
corso.termina(mittente=me)
@@ -474,14 +475,14 @@ def aspirante_corso_base_termina(request, me, pk):
torna_titolo="Vai al Report del Corso Base",
torna_url=corso.url_report)
- contesto = {
+ context = {
"corso": corso,
"puo_modificare": True,
"partecipanti_moduli": partecipanti_moduli,
"azione_genera_verbale": ModuloVerbaleAspiranteCorsoBase.GENERA_VERBALE,
"azione_salva_solamente": ModuloVerbaleAspiranteCorsoBase.SALVA_SOLAMENTE,
}
- return 'aspirante_corso_base_scheda_termina.html', contesto
+ return 'aspirante_corso_base_scheda_termina.html', context
@pagina_privata
@@ -806,6 +807,7 @@ def aspirante_corso_estensioni_modifica(request, me, pk):
@pagina_privata
def aspirante_corso_estensioni_informa(request, me, pk):
from .forms import InformCourseParticipantsForm
+ from django.contrib import messages
course = get_object_or_404(CorsoBase, pk=pk)
@@ -818,20 +820,25 @@ def aspirante_corso_estensioni_informa(request, me, pk):
}
form = InformCourseParticipantsForm(request.POST or None, **form_data)
if form.is_valid():
+ sent_with_success = True
cd = form.cleaned_data
- recipients = cd['recipients']
- if recipients == form.UNCONFIRMED_REQUESTS:
+ recipient_type = cd['recipient_type']
+ if recipient_type == form.UNCONFIRMED_REQUESTS:
pass
- elif recipients == form.CONFIRMED_REQUESTS:
+ elif recipient_type == form.CONFIRMED_REQUESTS:
pass
- elif recipients == form.INVIA_QUESTIONARIO:
+ elif recipient_type == form.INVIA_QUESTIONARIO:
pass
- elif recipients == form.ALL:
+ elif recipient_type == form.ALL:
pass
else:
# todo: something went wrong ...
pass
+ if sent_with_success:
+ messages.success(request, "Il messaggio ai volontari è stato inviato con successo. ")
+ return redirect(reverse('aspirante:informa', args=[pk]))
+
context = {
'corso': course,
'form': form,
diff --git a/posta/templates/email_volontario_corso_esito.html b/posta/templates/email_volontario_corso_esito.html
new file mode 100644
index 000000000..9b32064eb
--- /dev/null
+++ b/posta/templates/email_volontario_corso_esito.html
@@ -0,0 +1,34 @@
+{% extends 'email.html' %}
+
+{% block corpo %}
+
Ciao {{ persona.nome }}!
+
Il Direttore del {{ corso }} ha appena compilato il verbale di fine corso su Gaia.
+
+ {% if partecipazione.esito_esame == partecipazione.IDONEO %}
+
Congratulazioni!
+
_______ NEW TEXT _________
+
Da ora in avanti accedendo a Gaia potrai vedere le attività che si svolgono
+ in comitato e partecipare, oltre a una serie di altre importanti funzionalità
+ che da oggi puoi utilizzare!
- Seleziona una o più persone da iscrivere al
- corso base. Qui vedrai l'esito della procedura
- di iscrizione.
+ Seleziona una o più persone da iscrivere al corso{% if not corso.is_nuovo_corso %} base{%endif%}. Qui vedrai l'esito della procedura di iscrizione.
-
{% endfor %}
-
-
-
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/formazione/viste.py b/formazione/viste.py
index 2e19ff2f2..b4e9539ab 100644
--- a/formazione/viste.py
+++ b/formazione/viste.py
@@ -572,10 +572,14 @@ def aspirante_corso_base_iscritti_aggiungi(request, me, pk):
partecipazione.richiedi()
ok = PartecipazioneCorsoBase.IN_ATTESA_ASPIRANTE
else:
- partecipazione = PartecipazioneCorsoBase.objects.create(persona=persona, corso=corso)
+ partecipazione = PartecipazioneCorsoBase.objects.create(
+ persona=persona,
+ corso=corso
+ )
ok = PartecipazioneCorsoBase.ISCRITTO
+ subject = "Iscrizione a Corso %s"
Messaggio.costruisci_e_invia(
- oggetto="Iscrizione a Corso Base",
+ oggetto=subject % '' if corso.is_nuovo_corso else 'Base',
modello="email_corso_base_iscritto.html",
corpo={
"persona": persona,
@@ -865,6 +869,10 @@ def aspirante_corso_estensioni_informa(request, me, pk):
messages.success(request, "Il messaggio ai volontari è stato inviato con successo. ")
return redirect(reverse('aspirante:informa', args=[pk]))
+ if not recipients:
+ messages.success(request, "Il messaggio non è stato inviato a nessuno.")
+ return redirect(reverse('aspirante:informa', args=[pk]))
+
context = {
'corso': course,
'form': form,
diff --git a/posta/templates/email_corso_base_iscritto.html b/posta/templates/email_corso_base_iscritto.html
index 727526eea..306924a3f 100644
--- a/posta/templates/email_corso_base_iscritto.html
+++ b/posta/templates/email_corso_base_iscritto.html
@@ -1,17 +1,10 @@
{% extends 'email.html' %}
{% block corpo %}
-
Ciao {{ persona.nome }}!
-
Ricevi questo messaggio perché sei stato iscritto ad un Corso Base per
- diventare Volontari{{ persona.genere_o_a }}!
-
Il corso è organizzato dal {{ corso.sede.link|safe }} e avrà inizio
- il {{ corso.data_inizio }}.
Ricevi questo messaggio perché sei stato iscritto ad un Corso{% if corso.is_nuovo_corso %} Base{%endif%} per diventare Volontari{{ persona.genere_o_a }}!
+
Il corso è organizzato dal {{ corso.sede.link|safe }} e avrà inizio il {{ corso.data_inizio }}.
Ricevi questo messaggio perché sei stato iscritto ad un Corso{% if corso.is_nuovo_corso %} Base{%endif%} per diventare Volontari{{ persona.genere_o_a }}!
+
Ricevi questo messaggio perché sei stato iscritto ad un Corso{% if not corso.is_nuovo_corso %} Base{%endif%} per diventare Volontari{{ persona.genere_o_a }}!
Il corso è organizzato dal {{ corso.sede.link|safe }} e avrà inizio il {{ corso.data_inizio }}.
Clicca qui per aprire la pagina del Corso con maggiori informazioni.
diff --git a/survey/templates/corso_questionario_di_gradimento.html b/survey/templates/corso_questionario_di_gradimento.html
index 41c00dfb3..778406309 100644
--- a/survey/templates/corso_questionario_di_gradimento.html
+++ b/survey/templates/corso_questionario_di_gradimento.html
@@ -22,7 +22,12 @@
{% endif %}
- {% if me.volontario or me.dipendente %}
-
-
-
- Portale convenzioni
-
-
-
-
-
-
- Siamo lieti di presentarti il nuovo portale di convenzioni riservate esclusivamente ai volontari
-
- e ai lavoratori di Croce Rossa Italiana. Potrai accedere al portale corporate benefits per scoprire tutte le convenzioni, sconti e offerte su un´ampia gamma di prodotti e servizi dei più
-
- prestigiosi marchi e delle migliori aziende. Potrai beneficiare dei numerosi vantaggi in modo esclusivo e riservato.
-
- Continua a leggere...
-
-
-
-
-
-
-
- La prima volta che accederai al sito, ti verrà chiesto di effettuare una registrazione iniziale per poter entrare
- con la tua mail con estensione "cri.it". Se non dovessi avere un indirizzo email con estensione "cri.it"
-
- per effettuare la registrazione ti preghiamo di fornire il tuo indirizzo email personale con cui entri in GAIA e il codice di
- registrazione: CRI2018. Le volte successive è sufficiente effettuare l´accesso con l´indirizzo email e password forniti durante la registrazione.
-
- Ogni mese, il portale si arricchisce con nuove convenzioni, offerte e sconti messi a disposizione da prestigiosi fornitori.
- Puoi rimanere sempre aggiornato sulle novità iscrivendoti alla newsletter mensile direttamente sul portale.
-
- Scopri tutte le convenzioni e le offerte in esclusiva per te sul portale:
-
-
- corporate benefits
-
-
- Per i tuoi viaggi in treno invece, potrai accedere al portale per le prenotazioni dei biglietti, in esclusiva per te con sconti dedicati:
-
-
- vai a Italo
-
-
- Scopri un mondo di convenzioni per te, con Croce Rossa Italiana.
-
Sei iscritt{{ me.genere_o_a }} a questo c
{% elif puoi_partecipare == corso.NON_PUOI_SEI_ASPIRANTE %}
Non puoi iscriverti a questo corso.
{% elif puoi_partecipare == corso.NON_HAI_CARICATO_DOCUMENTI_PERSONALI %}
- Per iscriverti a questo corso inserisci la copia di un documento di riconoscimento in corso di validità.
+ Per iscriverti a questo corso inserisci la copia di un documento di riconoscimento in corso di validità (CDI o Patente Civile).
+
+