From bc197038caa6e7852fb921109cd999b1f013813d Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Mon, 5 Nov 2018 10:31:16 +0100 Subject: [PATCH 001/291] MODIFIED: formazione urls moved from jorvik.urls to formazione.urls; added name labels to each url. --- formazione/urls.py | 29 +++++++++++++++++++++++++++++ jorvik/urls.py | 40 ++++++++++++---------------------------- 2 files changed, 41 insertions(+), 28 deletions(-) create mode 100644 formazione/urls.py diff --git a/formazione/urls.py b/formazione/urls.py new file mode 100644 index 000000000..ff5310bdb --- /dev/null +++ b/formazione/urls.py @@ -0,0 +1,29 @@ +from django.conf.urls import url +from .viste import (formazione, formazione_corsi_base_elenco, + formazione_corsi_base_domanda, formazione_corsi_base_nuovo, + formazione_corsi_base_direttori, formazione_corsi_base_fine) + +app_label = 'formazione' +urlpatterns = [ + url(r'^$', formazione, name='index'), + url(r'^corsi-base/elenco/$', + formazione_corsi_base_elenco, + name='list_courses' + ), + url(r'^corsi-base/domanda/$', + formazione_corsi_base_domanda, + name='domanda' + ), + url(r'^corsi-base/nuovo/$', + formazione_corsi_base_nuovo, + name='new_course' + ), + url(r'^corsi-base/(?P[0-9]+)/direttori/$', + formazione_corsi_base_direttori, + name='director' + ), + url(r'^corsi-base/(?P[0-9]+)/fine/$', + formazione_corsi_base_fine, + name='end' + ), +] diff --git a/jorvik/urls.py b/jorvik/urls.py index 4a0ebec85..263812af9 100755 --- a/jorvik/urls.py +++ b/jorvik/urls.py @@ -1,15 +1,18 @@ -""" -Questo modulo contiene la configurazione per il routing degli URL. - -(c)2015 Croce Rossa Italiana -""" -import django, django.views, django.views.static, django.contrib.auth.views +# import django +# import django.views +# import django.views.static +import django.contrib.auth.views from django.conf.urls import include, url from django.contrib import admin from django.contrib.auth.views import password_change, password_change_done -from django.shortcuts import redirect +# from django.shortcuts import redirect from django.views.i18n import javascript_catalog from oauth2_provider import views as oauth2_provider_views +from autenticazione.funzioni import pagina_privata_no_cambio_firma # pagina_privata +from autenticazione.two_factor.urls import urlpatterns as tf_urls +from anagrafica.forms import ModuloModificaPassword + +from .settings import MEDIA_ROOT, DEBUG import anagrafica.viste import articoli.viste @@ -24,11 +27,6 @@ import social.viste import ufficio_soci.viste import veicoli.viste -from anagrafica.forms import ModuloModificaPassword -from autenticazione.funzioni import pagina_privata, pagina_privata_no_cambio_firma -from jorvik.settings import MEDIA_ROOT, DEBUG - -from autenticazione.two_factor.urls import urlpatterns as tf_urls handler404 = base.errori.non_trovato @@ -270,7 +268,6 @@ url(r'^veicolo/(?P.*)/collocazioni/$', veicoli.viste.veicoli_collocazioni), url(r'^veicolo/dettagli/(?P.*)/$', veicoli.viste.veicolo_dettagli), - url(r'^aspirante/$', formazione.viste.aspirante_home), url(r'^aspirante/impostazioni/$', formazione.viste.aspirante_impostazioni), url(r'^aspirante/impostazioni/cancella/$', formazione.viste.aspirante_impostazioni_cancella), @@ -292,33 +289,21 @@ url(r'^aspirante/corso-base/(?P[0-9]+)/lezioni/$', formazione.viste.aspirante_corso_base_lezioni), url(r'^aspirante/corso-base/(?P[0-9]+)/lezioni/(?P[0-9]+)/cancella/$', formazione.viste.aspirante_corso_base_lezioni_cancella), - url(r'^formazione/$', formazione.viste.formazione), - url(r'^formazione/corsi-base/elenco/$', formazione.viste.formazione_corsi_base_elenco), - url(r'^formazione/corsi-base/domanda/$', formazione.viste.formazione_corsi_base_domanda), - url(r'^formazione/corsi-base/nuovo/$', formazione.viste.formazione_corsi_base_nuovo), - url(r'^formazione/corsi-base/(?P[0-9]+)/direttori/$', formazione.viste.formazione_corsi_base_direttori), - url(r'^formazione/corsi-base/(?P[0-9]+)/fine/$', formazione.viste.formazione_corsi_base_fine), + url(r'^formazione/', include('formazione.urls')), url(r'^supporto/$', base.viste.supporto), - url(r'^geo/localizzatore/imposta/$', base.viste.geo_localizzatore_imposta), url(r'^geo/localizzatore/$', base.viste.geo_localizzatore), url(r'^strumenti/delegati/$', anagrafica.viste.strumenti_delegati), url(r'^strumenti/delegati/(?P[0-9]+)/termina/$', anagrafica.viste.strumenti_delegati_termina), - url(r'^social/commenti/nuovo/', social.viste.commenti_nuovo), url(r'^social/commenti/cancella/(?P[0-9]+)/', social.viste.commenti_cancella), - url(r'^media/(?P.*)$', django.views.static.serve, {"document_root": MEDIA_ROOT}), - url(r'^pdf/(?P.*)/(?P.*)/(?P[0-9]+)/$', base.viste.pdf), - url(r'^token-sicuro/(?P.*)/$', base.viste.verifica_token), - url(r'^password-dimenticata/$', base.viste.redirect_semplice, {"nuovo_url": "/recupera_password/"}), # Amministrazione - url(r'^admin/import/volontari/$', anagrafica.viste.admin_import_volontari), url(r'^admin/import/presidenti/$', anagrafica.viste.admin_import_presidenti), url(r'^admin/pulisci/email/$', anagrafica.viste.admin_pulisci_email), @@ -331,7 +316,7 @@ # Autocompletamento url(r'^autocomplete/', include('autocomplete_light.urls')), - #Filer + # Filer url(r'^filer/', include('filer.urls')), url(r'^filebrowser_filer/', include('ckeditor_filebrowser_filer.urls')), url(r'^jsi18n/$', javascript_catalog, js_info_dict, name='javascript-catalog'), @@ -349,4 +334,3 @@ if DEBUG: urlpatterns += [url(r'^api-auth/', include('rest_framework.urls')),] - From 54a75793e4d596a047104586bf7c3007388c1f92 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Mon, 5 Nov 2018 10:59:49 +0100 Subject: [PATCH 002/291] FIXED: namespace to formazione.urls include; base.menu urls rendered by reverse function. --- base/menu.py | 25 ++++++++++++++++--------- jorvik/urls.py | 2 +- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/base/menu.py b/base/menu.py index 5ac3454c5..2c0eafd89 100755 --- a/base/menu.py +++ b/base/menu.py @@ -1,6 +1,9 @@ +__author__ = 'alfioemanuele' from django.contrib.contenttypes.models import ContentType +from django.core.urlresolvers import reverse -from anagrafica.costanti import REGIONALE, TERRITORIALE, LOCALE +from base.utils import remove_none +from anagrafica.costanti import REGIONALE, TERRITORIALE #, LOCALE from anagrafica.models import Sede from anagrafica.permessi.applicazioni import DELEGATO_OBIETTIVO_1, DELEGATO_OBIETTIVO_2, DELEGATO_OBIETTIVO_3, DELEGATO_OBIETTIVO_4, \ DELEGATO_OBIETTIVO_5, DELEGATO_OBIETTIVO_6, PRESIDENTE, \ @@ -9,9 +12,9 @@ RESPONSABILE_AUTOPARCO, DELEGATO_CO, REFERENTE_GRUPPO, RUBRICHE_TITOLI, COMMISSARIO from anagrafica.permessi.costanti import GESTIONE_CORSI_SEDE, GESTIONE_ATTIVITA, GESTIONE_ATTIVITA_AREA, ELENCHI_SOCI, \ GESTIONE_AREE_SEDE, GESTIONE_ATTIVITA_SEDE, EMISSIONE_TESSERINI, GESTIONE_POTERI_CENTRALE_OPERATIVA_SEDE -from base.utils import remove_none -__author__ = 'alfioemanuele' + + """ Questa pagina contiene i vari menu che vengono mostrati nella barra laterale dei template. @@ -70,6 +73,8 @@ def menu(request): RUBRICA_BASE )) + + elementi = { "utente": ( (("Persona", ( @@ -199,12 +204,14 @@ def menu(request): )), ), "formazione": ( - ("Corsi Base", ( - ("Elenco Corsi Base", "fa-list", "/formazione/corsi-base/elenco/"), - ("Domanda formativa", "fa-area-chart", "/formazione/corsi-base/domanda/") - if gestione_corsi_sede else None, - ("Pianifica nuovo", "fa-asterisk", "/formazione/corsi-base/nuovo/") - if gestione_corsi_sede else None, + ("I tuoi Corsi", ( + ("Pianifica nuovo", + "fa-asterisk", reverse('formazione:new_course') + if gestione_corsi_sede else None), + ("Elenco Corsi", "fa-list", reverse('formazione:list_courses')), + ("Domanda formativa", + "fa-area-chart", reverse('formazione:domanda')) + if gestione_corsi_sede else None, )), ("Corsi di Formazione", ( ("Elenco Corsi di Formazione", "fa-list", "/formazione/corsi-formazione/"), diff --git a/jorvik/urls.py b/jorvik/urls.py index 263812af9..686715649 100755 --- a/jorvik/urls.py +++ b/jorvik/urls.py @@ -289,7 +289,7 @@ url(r'^aspirante/corso-base/(?P[0-9]+)/lezioni/$', formazione.viste.aspirante_corso_base_lezioni), url(r'^aspirante/corso-base/(?P[0-9]+)/lezioni/(?P[0-9]+)/cancella/$', formazione.viste.aspirante_corso_base_lezioni_cancella), - url(r'^formazione/', include('formazione.urls')), + url(r'^formazione/', include('formazione.urls', namespace='formazione')), url(r'^supporto/$', base.viste.supporto), url(r'^geo/localizzatore/imposta/$', base.viste.geo_localizzatore_imposta), From a193fa2c9c80da95df40331cf360c100876ba8f9 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Mon, 5 Nov 2018 11:37:23 +0100 Subject: [PATCH 003/291] FIXED: ordered imports in formazione.forms and ModuloCreazioneCorsoBase.clean_sede() --- formazione/forms.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/formazione/forms.py b/formazione/forms.py index 2bdd73421..82be2231a 100644 --- a/formazione/forms.py +++ b/formazione/forms.py @@ -1,17 +1,14 @@ -from autocomplete_light import shortcuts as autocomplete_light from django import forms from django.core.exceptions import ValidationError from django.forms import ModelForm +from autocomplete_light import shortcuts as autocomplete_light from base.wysiwyg import WYSIWYGSemplice -from formazione.models import CorsoBase, LezioneCorsoBase, PartecipazioneCorsoBase +from .models import (CorsoBase, LezioneCorsoBase, PartecipazioneCorsoBase) -class ModuloCreazioneCorsoBase(ModelForm): - class Meta: - model = CorsoBase - fields = ['data_inizio', 'sede',] +class ModuloCreazioneCorsoBase(ModelForm): PRESSO_SEDE = "PS" ALTROVE = "AL" LOCAZIONE = ( @@ -20,15 +17,22 @@ class Meta: ) locazione = forms.ChoiceField(choices=LOCAZIONE, initial=PRESSO_SEDE, - help_text="La posizione del Corso è importante per " - "aiutare gli aspiranti a trovare i Corsi " - "che si svolgono vicino a loro.") + help_text="La posizione del Corso è importante per " + "aiutare gli aspiranti a trovare i Corsi " + "che si svolgono vicino a loro.") def clean_sede(self): - if self.cleaned_data['sede'].locazione is None: - raise forms.ValidationError("La Sede CRI selezionata non ha alcun indirizzo impostato. " - "Il Presidente può modificare i dettagli della Sede, tra cui l'indirizzo della stessa.") - return self.cleaned_data['sede'] + sede = self.cleaned_data['sede'] + if sede.locazione is None: + raise forms.ValidationError( + "La Sede CRI selezionata non ha alcun indirizzo impostato. " + "Il Presidente può modificare i dettagli della Sede, "" + "tra cui l'indirizzo della stessa.") + return sede + + class Meta: + model = CorsoBase + fields = ['data_inizio', 'sede',] class ModuloModificaLezione(ModelForm): From 0d0ef1091b9f1f8228e15f14d35f279ea0233bb2 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Mon, 5 Nov 2018 14:02:34 +0100 Subject: [PATCH 004/291] FIXED: cosmetif refactoring (renamed variables) of formazione.viste.formazione_corsi_base_nuovo view. --- formazione/viste.py | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/formazione/viste.py b/formazione/viste.py index 35671231e..b14704e11 100644 --- a/formazione/viste.py +++ b/formazione/viste.py @@ -55,30 +55,32 @@ def formazione_corsi_base_domanda(request, me): @pagina_privata def formazione_corsi_base_nuovo(request, me): - modulo = ModuloCreazioneCorsoBase(request.POST or None, initial={"data_inizio": - datetime.now() + timedelta(days=14)}) - modulo.fields['sede'].queryset = me.oggetti_permesso(GESTIONE_CORSI_SEDE) - - if modulo.is_valid(): - corso = CorsoBase.nuovo( - anno=modulo.cleaned_data['data_inizio'].year, - sede=modulo.cleaned_data['sede'], - data_inizio=modulo.cleaned_data['data_inizio'], - data_esame=modulo.cleaned_data['data_inizio'], + data_inizio = datetime.now() + timedelta(days=14) + form = ModuloCreazioneCorsoBase( + request.POST or None, initial={"data_inizio": data_inizio} + ) + form.fields['sede'].queryset = me.oggetti_permesso(GESTIONE_CORSI_SEDE) + + if form.is_valid(): + cd = form.cleaned_data + course = CorsoBase.nuovo( + anno=cd['data_inizio'].year, + sede=cd['sede'], + data_inizio=cd['data_inizio'], + data_esame=cd['data_inizio'], ) - if modulo.cleaned_data['locazione'] == modulo.PRESSO_SEDE: - corso.locazione = corso.sede.locazione - corso.save() + if cd['locazione'] == form.PRESSO_SEDE: + course.locazione = course.sede.locazione + course.save() - request.session['corso_base_creato'] = corso.pk + request.session['corso_base_creato'] = course.pk + return redirect(course.url_direttori) - return redirect(corso.url_direttori) - - contesto = { - "modulo": modulo + context = { + 'modulo': form } - return 'formazione_corsi_base_nuovo.html', contesto + return 'formazione_corsi_base_nuovo.html', context @pagina_privata From 07e2f1da0c39da0817a7cf50f7bbd8a221a94384 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Mon, 5 Nov 2018 14:04:59 +0100 Subject: [PATCH 005/291] ADDED: (in formazione.models.Corso) TIPO_CHOICES to be selectable in formazione.forms.ModuloCreazioneCorsoBase; TIPO_CHOICES were moved from CorsoBase to Corso class. --- formazione/forms.py | 10 +++++++--- formazione/models.py | 28 ++++++++++++++-------------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/formazione/forms.py b/formazione/forms.py index 82be2231a..2cbdd510f 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, LezioneCorsoBase, PartecipazioneCorsoBase) +from .models import Corso, CorsoBase, LezioneCorsoBase, PartecipazioneCorsoBase class ModuloCreazioneCorsoBase(ModelForm): @@ -26,13 +26,17 @@ def clean_sede(self): if sede.locazione is None: raise forms.ValidationError( "La Sede CRI selezionata non ha alcun indirizzo impostato. " - "Il Presidente può modificare i dettagli della Sede, "" + "Il Presidente può modificare i dettagli della Sede, " "tra cui l'indirizzo della stessa.") return sede class Meta: model = CorsoBase - fields = ['data_inizio', 'sede',] + fields = ['tipo', 'data_inizio', 'sede',] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.order_fields(('tipo', 'data_inizio', 'sede', 'locazione')) class ModuloModificaLezione(ModelForm): diff --git a/formazione/models.py b/formazione/models.py index 569c4e8b8..db6c2ff28 100755 --- a/formazione/models.py +++ b/formazione/models.py @@ -28,12 +28,13 @@ class Corso(ModelloSemplice, ConDelegati, ConMarcaTemporale, ConGeolocalizzazione, ConCommenti, ConGiudizio): - - class Meta: - abstract = True - permissions = ( - ("view_corso", "Can view corso"), - ) + # Tipologia di corso + CORSO_NUOVO = 'C1' + BASE = 'BA' + TIPO_CHOICES = ( + (CORSO_NUOVO, 'Corso'), + (BASE, 'Corso Base'), + ) # Stato del corso PREPARAZIONE = 'P' @@ -48,17 +49,16 @@ class Meta: ) stato = models.CharField('Stato', choices=STATO, max_length=1, default=PREPARAZIONE) sede = models.ForeignKey(Sede, related_query_name='%(class)s_corso', help_text="La Sede organizzatrice del Corso.") + # tipo = models.CharField('Tipo', choices=TIPO_CHOICES, max_length=4) + class Meta: + abstract = True + permissions = ( + ("view_corso", "Can view corso"), + ) -class CorsoBase(Corso, ConVecchioID, ConPDF): - - ## Tipologia di corso - #BASE = 'BA' - #TIPO = ( - # (BASE, 'Corso Base'), - #) - #tipo = models.CharField('Tipo', choices=TIPO, max_length=2, default=BASE) +class CorsoBase(Corso, ConVecchioID, ConPDF): MAX_PARTECIPANTI = 30 class Meta: From 144331b951200d69a28edf39a78bc6c88caf3646 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Mon, 5 Nov 2018 14:08:43 +0100 Subject: [PATCH 006/291] MODIFIED: titles, static url's were replaced with template-tag-url in formazione.templates (direttori/domanda/elenco/fine/nuovo/vuota). --- .../formazione_corsi_base_direttori.html | 16 ++------ .../formazione_corsi_base_domanda.html | 11 ++---- .../formazione_corsi_base_elenco.html | 38 ++++++------------- .../templates/formazione_corsi_base_fine.html | 4 +- .../formazione_corsi_base_nuovo.html | 25 +----------- formazione/templates/formazione_vuota.html | 16 +------- 6 files changed, 22 insertions(+), 88 deletions(-) diff --git a/formazione/templates/formazione_corsi_base_direttori.html b/formazione/templates/formazione_corsi_base_direttori.html index 3c756330b..bf986110a 100644 --- a/formazione/templates/formazione_corsi_base_direttori.html +++ b/formazione/templates/formazione_corsi_base_direttori.html @@ -4,16 +4,10 @@ {% load mptt_tags %} {% load utils %} -{% block pagina_titolo %} - Seleziona Direttore/i Corso Base -{% endblock %} +{% block pagina_titolo %}Seleziona Direttore/i Corso Base{% endblock %} {% block app_contenuto %} - -

- Seleziona Direttore/i Corso Base -

- +

Seleziona Direttore/i Corso Base

Chi è il direttore?

    @@ -29,8 +23,4 @@

    Chi è il direttore?

{% delegati delega corso continua_url=continua_url almeno=1 %} - - - - -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/formazione/templates/formazione_corsi_base_domanda.html b/formazione/templates/formazione_corsi_base_domanda.html index 1ac050a21..155d55972 100644 --- a/formazione/templates/formazione_corsi_base_domanda.html +++ b/formazione/templates/formazione_corsi_base_domanda.html @@ -3,15 +3,10 @@ {% load bootstrap3 %} {% load mptt_tags %} -{% block pagina_titolo %} - Domanda Formativa per Corsi Base -{% endblock %} +{% block pagina_titolo %}Domanda Formativa per Corsi{% endblock %} {% block app_contenuto %} - -

- Domanda Formativa per Corsi Base -

+

Domanda Formativa per Corsi

Cosa è la domanda formativa?

@@ -102,7 +97,7 @@

Cosa è la domanda formativa?

Vuoi pianificare un nuovo corso?

Se vuoi pianificare un nuovo corso base, clicca su - Pianifica un Nuovo Corso.

+ Pianifica un Nuovo Corso.

Potrai assegnare un Direttore del Corso che si occuperà di organizzarne i particolari.

diff --git a/formazione/templates/formazione_corsi_base_elenco.html b/formazione/templates/formazione_corsi_base_elenco.html index 1b1c9328d..4b67c96f2 100644 --- a/formazione/templates/formazione_corsi_base_elenco.html +++ b/formazione/templates/formazione_corsi_base_elenco.html @@ -3,15 +3,10 @@ {% load bootstrap3 %} {% load mptt_tags %} -{% block pagina_titolo %} - Corsi Base -{% endblock %} +{% block pagina_titolo %}Corsi{% endblock %} {% block app_contenuto %} - -

- Corsi Base -

+

Corsi

@@ -22,11 +17,8 @@

{% for corso in corsi %} - - - - {% empty %} @@ -76,24 +64,20 @@

- {% endfor %} -
{{ corso.get_stato_display }} {{ corso.link|safe }}
@@ -57,18 +49,14 @@

- {{ corso.partecipazioni_confermate_o_in_attesa.count }} richieste -
+
- {{ corso.partecipazioni_confermate.count }} confermate
- {{ corso.partecipazioni_in_attesa.count }} in attesa
- {{ corso.partecipazioni_negate.count }} neg./rit.
- - + {{ corso.partecipazioni_confermate.count }} confermate
+ {{ corso.partecipazioni_in_attesa.count }} in attesa
+ {{ corso.partecipazioni_negate.count }} neg./rit.

Ancora nessun corso pianificato.

Puoi controllare la domanda formativa della zona e valutare l'attivazione di un - nuovo corso base.

+ nuovo corso.

- {% if puo_pianificare %}

Vuoi pianificare un nuovo corso?

-

Se vuoi pianificare un nuovo corso base, clicca su - Pianifica un Nuovo Corso.

+

Se vuoi pianificare un nuovo corso, clicca su + Pianifica un Nuovo Corso.

Potrai assegnare un Direttore del Corso che si occuperà di organizzarne i particolari.

{% endif %} - - -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/formazione/templates/formazione_corsi_base_fine.html b/formazione/templates/formazione_corsi_base_fine.html index 313fd427a..7d7643f76 100644 --- a/formazione/templates/formazione_corsi_base_fine.html +++ b/formazione/templates/formazione_corsi_base_fine.html @@ -5,7 +5,7 @@ {% load utils %} {% block pagina_titolo %} - Seleziona Direttore/i Corso Base + Seleziona Direttore/i Corso {% endblock %} {% block app_contenuto %} @@ -38,7 +38,7 @@

Che succederà ora?

- + Torna all'elenco dei Corsi Base diff --git a/formazione/templates/formazione_corsi_base_nuovo.html b/formazione/templates/formazione_corsi_base_nuovo.html index 24cb73d31..8114db519 100644 --- a/formazione/templates/formazione_corsi_base_nuovo.html +++ b/formazione/templates/formazione_corsi_base_nuovo.html @@ -3,24 +3,18 @@ {% load bootstrap3 %} {% load mptt_tags %} -{% block pagina_titolo %} - Pianifica un Corso Base -{% endblock %} +{% block pagina_titolo %}Pianifica un corso{% endblock %} {% block app_contenuto %} -

-
-

- Pianifica un Corso Base + Pianifica un nuovo corso

-
{% csrf_token %} @@ -31,15 +25,9 @@

Pianifica il Corso - -

- -
-
-
Data ed ora di inizio: @@ -47,7 +35,6 @@

effettuata la presentazione del Corso. Questa data sarà comunicata agli aspiranti nella tua zona come data di inziio del Corso.

-
Nota bene: Dopo questo step potrai nominare uno o più direttori del corso. @@ -55,14 +42,6 @@

come le date delle lezioni, la descrizione, nonché gestire interamente il corso per te.

- -
- -
- - - - {% endblock %} \ No newline at end of file diff --git a/formazione/templates/formazione_vuota.html b/formazione/templates/formazione_vuota.html index da0564a51..85816f193 100644 --- a/formazione/templates/formazione_vuota.html +++ b/formazione/templates/formazione_vuota.html @@ -2,38 +2,24 @@ {% load bootstrap3 %} -{% block pagina_titolo %} - Benvenuto in Gaia -{% endblock %} - +{% block pagina_titolo %}Benvenuto in Gaia{% endblock %} {% block menu_laterale %}
- - -
{% endblock %} From 10c0fd95d02ce2ecd0429897cb00ce9032a77271 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Mon, 5 Nov 2018 14:18:06 +0100 Subject: [PATCH 007/291] FIXED: imports reordering in formazione.viste --- formazione/viste.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/formazione/viste.py b/formazione/viste.py index b14704e11..822026511 100644 --- a/formazione/viste.py +++ b/formazione/viste.py @@ -1,28 +1,29 @@ from datetime import datetime, timedelta -from django.conf import settings -from django.core.exceptions import ObjectDoesNotExist -from django.db.transaction import atomic +# from django.conf import settings +# from django.core.exceptions import ObjectDoesNotExist +# from django.db.transaction import atomic +# from django.template import Context +from django.utils import timezone from django.shortcuts import redirect, get_object_or_404 -from django.template import Context from django.template.loader import get_template from anagrafica.models import Persona from anagrafica.permessi.applicazioni import DIRETTORE_CORSO -from anagrafica.permessi.costanti import GESTIONE_CORSI_SEDE, GESTIONE_CORSO, ERRORE_PERMESSI, COMPLETO, MODIFICA +from anagrafica.permessi.costanti import (GESTIONE_CORSI_SEDE, + GESTIONE_CORSO, ERRORE_PERMESSI, COMPLETO, MODIFICA) from autenticazione.funzioni import pagina_privata, pagina_pubblica -from base.errori import ci_siamo_quasi, errore_generico, messaggio_generico +from base.errori import errore_generico, messaggio_generico # ci_siamo_quasi from base.files import Zip from base.models import Log from base.utils import poco_fa -from formazione.elenchi import ElencoPartecipantiCorsiBase -from formazione.forms import ModuloCreazioneCorsoBase, ModuloModificaLezione, ModuloModificaCorsoBase, \ - ModuloIscrittiCorsoBaseAggiungi, ModuloVerbaleAspiranteCorsoBase -from formazione.models import CorsoBase, AssenzaCorsoBase, LezioneCorsoBase, PartecipazioneCorsoBase, Aspirante, \ - InvitoCorsoBase -from django.utils import timezone - from posta.models import Messaggio +from .elenchi import ElencoPartecipantiCorsiBase +from .models import (CorsoBase, AssenzaCorsoBase, LezioneCorsoBase, + PartecipazioneCorsoBase, Aspirante, InvitoCorsoBase) +from .forms import (ModuloCreazioneCorsoBase, ModuloModificaLezione, + ModuloModificaCorsoBase, ModuloIscrittiCorsoBaseAggiungi, + ModuloVerbaleAspiranteCorsoBase) @pagina_privata From 2f8461a76106347cd14b82017bc1f22e6f73ba4e Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Mon, 5 Nov 2018 17:26:31 +0100 Subject: [PATCH 008/291] NEW: field in Corso with migration (0019); formazione.models tiny cosmetic refactoring. --- formazione/migrations/0019_corsobase_tipo.py | 20 ++++++++ formazione/models.py | 54 ++++++++++---------- 2 files changed, 48 insertions(+), 26 deletions(-) create mode 100644 formazione/migrations/0019_corsobase_tipo.py diff --git a/formazione/migrations/0019_corsobase_tipo.py b/formazione/migrations/0019_corsobase_tipo.py new file mode 100644 index 000000000..9407369a7 --- /dev/null +++ b/formazione/migrations/0019_corsobase_tipo.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2018-11-05 14:29 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('formazione', '0018_auto_20180408_1952'), + ] + + operations = [ + migrations.AddField( + model_name='corsobase', + name='tipo', + field=models.CharField(blank=True, choices=[('C1', 'Corso'), ('BA', 'Corso Base')], max_length=4, verbose_name='Tipo'), + ), + ] diff --git a/formazione/models.py b/formazione/models.py index db6c2ff28..5df0a8cd6 100755 --- a/formazione/models.py +++ b/formazione/models.py @@ -1,14 +1,10 @@ -# coding=utf-8 - -""" -Questo modulo definisce i modelli del modulo di Formazione di Gaia. -""" import datetime from django.conf import settings -from django.contrib.contenttypes.models import ContentType +from django.db import models from django.db.models import Q from django.db.transaction import atomic +from django.contrib.contenttypes.models import ContentType from django.shortcuts import get_object_or_404 from django.utils import timezone from django.utils.timezone import now @@ -24,10 +20,10 @@ from base.utils import concept, poco_fa from posta.models import Messaggio from social.models import ConCommenti, ConGiudizio -from django.db import models -class Corso(ModelloSemplice, ConDelegati, ConMarcaTemporale, ConGeolocalizzazione, ConCommenti, ConGiudizio): +class Corso(ModelloSemplice, ConDelegati, ConMarcaTemporale, + ConGeolocalizzazione, ConCommenti, ConGiudizio): # Tipologia di corso CORSO_NUOVO = 'C1' BASE = 'BA' @@ -48,8 +44,10 @@ class Corso(ModelloSemplice, ConDelegati, ConMarcaTemporale, ConGeolocalizzazion (ANNULLATO, 'Annullato'), ) stato = models.CharField('Stato', choices=STATO, max_length=1, default=PREPARAZIONE) - sede = models.ForeignKey(Sede, related_query_name='%(class)s_corso', help_text="La Sede organizzatrice del Corso.") - # tipo = models.CharField('Tipo', choices=TIPO_CHOICES, max_length=4) + sede = models.ForeignKey(Sede, related_query_name='%(class)s_corso', + help_text="La Sede organizzatrice del Corso.") + tipo = models.CharField('Tipo', choices=TIPO_CHOICES, max_length=4, + blank=True) class Meta: abstract = True @@ -61,16 +59,9 @@ class Meta: class CorsoBase(Corso, ConVecchioID, ConPDF): MAX_PARTECIPANTI = 30 - class Meta: - verbose_name = "Corso Base" - verbose_name_plural = "Corsi Base" - ordering = ['-anno', '-progressivo'] - permissions = ( - ("view_corsobase", "Can view corso base"), - ) - - data_inizio = models.DateTimeField(blank=False, null=False, help_text="La data di inizio del corso. " - "Utilizzata per la gestione delle iscrizioni.") + data_inizio = models.DateTimeField(blank=False, null=False, + help_text="La data di inizio del corso. " + "Utilizzata per la gestione delle iscrizioni.") data_esame = models.DateTimeField(blank=False, null=False) progressivo = models.SmallIntegerField(blank=False, null=False, db_index=True) anno = models.SmallIntegerField(blank=False, null=False, db_index=True) @@ -157,9 +148,6 @@ def possibile_aggiungere_iscritti(self): def possibile_cancellare_iscritti(self): return self.stato in [Corso.ATTIVO, Corso.PREPARAZIONE] - def __str__(self): - return self.nome - @property def url(self): return "/aspirante/corso-base/%d/" % (self.pk,) @@ -419,8 +407,20 @@ def key_cognome(elem): ) return pdf + class Meta: + verbose_name = "Corso Base" + verbose_name_plural = "Corsi Base" + ordering = ['-anno', '-progressivo'] + permissions = ( + ("view_corsobase", "Can view corso base"), + ) + + def __str__(self): + return str(self.nome) + -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) @@ -519,7 +519,8 @@ def disiscrivi(self, mittente=None): ) -class PartecipazioneCorsoBase(ModelloSemplice, ConMarcaTemporale, ConAutorizzazioni, ConPDF): +class PartecipazioneCorsoBase(ModelloSemplice, ConMarcaTemporale, + ConAutorizzazioni, ConPDF): persona = models.ForeignKey(Persona, related_name='partecipazioni_corsi', on_delete=models.CASCADE) corso = models.ForeignKey(CorsoBase, related_name='partecipazioni', on_delete=models.PROTECT) @@ -733,7 +734,8 @@ 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) From 16cbeacb2805cb34d2c2d2e6fc891c20a057fac3 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Mon, 5 Nov 2018 17:38:34 +0100 Subject: [PATCH 009/291] FIXED: validation (JO-754) check if volontario (code execution flow: is_course=True in formazione_corsi_base.html -> anagrafica.utils.delegati tag -> *.viste.strumengi_delegati() -> *.forms.ModuloCreazioneDelega) --- anagrafica/forms.py | 53 +++++++++++++------ anagrafica/templatetags/utils.py | 4 +- anagrafica/viste.py | 33 +++++++----- .../formazione_corsi_base_direttori.html | 10 ++-- 4 files changed, 67 insertions(+), 33 deletions(-) diff --git a/anagrafica/forms.py b/anagrafica/forms.py index 0e2eff476..4b21661d9 100755 --- a/anagrafica/forms.py +++ b/anagrafica/forms.py @@ -398,20 +398,23 @@ class Meta: fields = ['persona',] def __init__(self, *args, **kwargs): - self.me = kwargs.pop('me') + # These attrs are passed in anagrafica.viste.strumenti_delegati() + for attr in ['me', 'is_course']: + setattr(self, attr, kwargs.pop(attr)) super().__init__(*args, **kwargs) - def clean_persona(self): - me_sede = self.me.sede_riferimento() # Authorized user's (myself) - persona = self.cleaned_data['persona'] # Selected user whom to be given in + def _validate_delega_per_corso(self, persona): + """ + Validate only Volontario membership on url: + /formazione/corsi-base//direttori/ - # Queries for possible cases - persona_appartenenze = persona.appartenenze_attuali( - membro__in=Appartenenza.MEMBRO_ATTIVITA) - persona_estesa = persona_appartenenze.filter(sede=me_sede).count() - persona_volontario = persona_appartenenze.filter(membro=Appartenenza.VOLONTARIO) - stesse_sedi = me_sede == persona.sede_riferimento() + Task: https://jira.gaia.cri.it/browse/JO-754 + """ + if not self.persona_volontario: + raise forms.ValidationError("Questa persona non è Volontario.") + return persona + def _validate_delega(self, me_sede, persona): """ Possible cases: 1) [OK] Persona è estesa (ES) nel mio comitato. @@ -422,19 +425,39 @@ def clean_persona(self): 5) [OK] Persona come Volontario (VO), è Presidente nella mia sede. """ CASES = ( - persona_estesa, - stesse_sedi, - stesse_sedi and persona_estesa, - any([a.appartiene_a(me_sede) for a in persona_volontario]), - any([a for a in persona_volontario if a.sede.presidente() == self.me]) + self.persona_estesa, + self.stesse_sedi, + self.stesse_sedi and self.persona_estesa, + any([a.appartiene_a(me_sede) for a in self.persona_volontario]), + any([a for a in self.persona_volontario if + a.sede.presidente() == self.me]) ) if not any(CASES): # All CASES return False, so the form returns validation error. raise forms.ValidationError( "Il volontario non è appartenente alla tua sede." ) + # Some case returned True, so can proceed with creating new delega. return persona + + def clean_persona(self): + me_sede = self.me.sede_riferimento() # Authorized user's (myself) + persona = self.cleaned_data['persona'] # Selected user whom to be given in + + # Queries for possible cases + persona_appartenenze = persona.appartenenze_attuali( + membro__in=Appartenenza.MEMBRO_ATTIVITA) + self.persona_estesa = persona_appartenenze.filter( + sede=me_sede).count() + self.persona_volontario = persona_appartenenze.filter( + membro=Appartenenza.VOLONTARIO) + self.stesse_sedi = me_sede == persona.sede_riferimento() + + if self.is_course: + return self._validate_delega_per_corso(persona) + else: + return self._validate_delega(me_sede, persona) def clean_inizio(self): """ Impedisce inizio passato """ diff --git a/anagrafica/templatetags/utils.py b/anagrafica/templatetags/utils.py index 68e92e4f2..e6496eef1 100755 --- a/anagrafica/templatetags/utils.py +++ b/anagrafica/templatetags/utils.py @@ -94,7 +94,8 @@ def localizzatore(context, oggetto_localizzatore=None, continua_url=None, solo_i @register.simple_tag(takes_context=True) -def delegati(context, delega=UFFICIO_SOCI, oggetto=None, continua_url=None, almeno=0): +def delegati(context, delega=UFFICIO_SOCI, oggetto=None, continua_url=None, + almeno=0, *args, **kwargs): if not isinstance(oggetto, ConDelegati): raise ValueError("Il tag delegati puo' solo essere usato con un oggetto ConDelegati, ma e' stato usato con un oggetto %s." % (oggetto_localizzatore.__class__.__name__,)) @@ -105,6 +106,7 @@ def delegati(context, delega=UFFICIO_SOCI, oggetto=None, continua_url=None, alme context.request.session['continua_url'] = continua_url context.request.session['delega'] = delega context.request.session['almeno'] = almeno + context.request.session['is_course'] = kwargs.get('is_course', False) url = "/strumenti/delegati/" context.update({ 'iframe_url': url, diff --git a/anagrafica/viste.py b/anagrafica/viste.py index fd2878781..44d59015b 100755 --- a/anagrafica/viste.py +++ b/anagrafica/viste.py @@ -1023,19 +1023,28 @@ def profilo_turni_foglio(request, me, pk=None): @pagina_privata def strumenti_delegati(request, me): - app_label = request.session['app_label'] - model = request.session['model'] - pk = int(request.session['pk']) - continua_url = request.session['continua_url'] - almeno = request.session['almeno'] - delega = request.session['delega'] - oggetto = apps.get_model(app_label, model) - oggetto = oggetto.objects.get(pk=pk) - - modulo = ModuloCreazioneDelega(request.POST or None, initial={ - "inizio": datetime.date.today(), - }, me=me) + session = request.session + + # Get values store in the session + app_label = session['app_label'] + model = session['model'] + pk = int(session['pk']) + continua_url = session['continua_url'] + almeno = session['almeno'] + delega = session['delega'] + + # Get kind of object + oggetto = apps.get_model(app_label, model).objects.get(pk=pk) + + # Instantiate a new form + modulo = ModuloCreazioneDelega( + request.POST or None, + initial={'inizio': datetime.date.today()}, + me=me, + is_course=session['is_course'] + ) + # Check form is valid if modulo.is_valid(): d = modulo.save(commit=False) diff --git a/formazione/templates/formazione_corsi_base_direttori.html b/formazione/templates/formazione_corsi_base_direttori.html index bf986110a..8f19213e0 100644 --- a/formazione/templates/formazione_corsi_base_direttori.html +++ b/formazione/templates/formazione_corsi_base_direttori.html @@ -4,23 +4,23 @@ {% load mptt_tags %} {% load utils %} -{% block pagina_titolo %}Seleziona Direttore/i Corso Base{% endblock %} +{% block pagina_titolo %}Seleziona Direttore/i Corso{% endblock %} {% block app_contenuto %} -

Seleziona Direttore/i Corso Base

+

Seleziona Direttore/i Corso

Chi è il direttore?

  • Punto di riferimento per gli aspiranti volontari che vogliono partecipare - al corso base e per i docenti;
  • + al corso e per i docenti;
  • I suoi contatti verranno divulgati agli aspiranti volontari interessati al corso;
  • Generalmente è presente durante le lezioni e conosce i docenti;
  • - Potrà accettare o negare le iscrizioni al Corso Base.
  • + Potrà accettare o negare le iscrizioni al Corso.
- {% delegati delega corso continua_url=continua_url almeno=1 %} +{% delegati delega corso continua_url=continua_url almeno=1 is_course=True %} {% endblock %} From 20417a625f9bc63c336d19c560cb30e8d86dedb0 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Tue, 6 Nov 2018 10:27:28 +0100 Subject: [PATCH 010/291] FIXED: (JO-754) changes made in previous commit (16cbeacb) --- anagrafica/forms.py | 23 ++++++++++--------- anagrafica/templatetags/utils.py | 1 - anagrafica/viste.py | 6 ++--- .../formazione_corsi_base_direttori.html | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/anagrafica/forms.py b/anagrafica/forms.py index 4b21661d9..1f50bfd4b 100755 --- a/anagrafica/forms.py +++ b/anagrafica/forms.py @@ -3,26 +3,26 @@ import stdnum from dateutil.parser import parse -from django import forms from django.conf import settings +from django import forms +from django.forms import ModelForm, ChoiceField from django.contrib.admin.templatetags.admin_static import static from django.contrib.admin.widgets import AdminDateWidget from django.contrib.auth.forms import PasswordChangeForm from django.core.exceptions import ValidationError -from django.db.models import QuerySet -from django.forms import ModelForm, ChoiceField from django.utils.safestring import mark_safe from django.utils.timezone import now +# from django.db.models import QuerySet -from anagrafica.models import Sede, Persona, Appartenenza, Documento, Estensione, ProvvedimentoDisciplinare, Delega, \ - Fototessera, Trasferimento, Riserva -from anagrafica.validators import valida_almeno_14_anni, valida_data_nel_passato -from autenticazione.models import Utenza from autocomplete_light import shortcuts as autocomplete_light - +from autenticazione.models import Utenza from base.forms import ModuloMotivoNegazione from curriculum.models import TitoloPersonale from sangue.models import Donatore, Donazione +from formazione.models import Corso +from .models import (Sede, Persona, Appartenenza, Documento, Estensione, + ProvvedimentoDisciplinare, Delega, Fototessera, Trasferimento, Riserva) +from .validators import valida_almeno_14_anni, valida_data_nel_passato class ModuloSpostaPersone(object): @@ -399,8 +399,9 @@ class Meta: def __init__(self, *args, **kwargs): # These attrs are passed in anagrafica.viste.strumenti_delegati() - for attr in ['me', 'is_course']: - setattr(self, attr, kwargs.pop(attr)) + for attr in ['me', 'course']: + if attr in kwargs: + setattr(self, attr, kwargs.pop(attr)) super().__init__(*args, **kwargs) def _validate_delega_per_corso(self, persona): @@ -454,7 +455,7 @@ def clean_persona(self): membro=Appartenenza.VOLONTARIO) self.stesse_sedi = me_sede == persona.sede_riferimento() - if self.is_course: + if self.course.tipo == Corso.CORSO_NUOVO: return self._validate_delega_per_corso(persona) else: return self._validate_delega(me_sede, persona) diff --git a/anagrafica/templatetags/utils.py b/anagrafica/templatetags/utils.py index e6496eef1..0720cc804 100755 --- a/anagrafica/templatetags/utils.py +++ b/anagrafica/templatetags/utils.py @@ -106,7 +106,6 @@ def delegati(context, delega=UFFICIO_SOCI, oggetto=None, continua_url=None, context.request.session['continua_url'] = continua_url context.request.session['delega'] = delega context.request.session['almeno'] = almeno - context.request.session['is_course'] = kwargs.get('is_course', False) url = "/strumenti/delegati/" context.update({ 'iframe_url': url, diff --git a/anagrafica/viste.py b/anagrafica/viste.py index 44d59015b..255203858 100755 --- a/anagrafica/viste.py +++ b/anagrafica/viste.py @@ -1025,7 +1025,7 @@ def profilo_turni_foglio(request, me, pk=None): def strumenti_delegati(request, me): session = request.session - # Get values store in the session + # Get values stored in the session app_label = session['app_label'] model = session['model'] pk = int(session['pk']) @@ -1033,7 +1033,7 @@ def strumenti_delegati(request, me): almeno = session['almeno'] delega = session['delega'] - # Get kind of object + # Get object oggetto = apps.get_model(app_label, model).objects.get(pk=pk) # Instantiate a new form @@ -1041,7 +1041,7 @@ def strumenti_delegati(request, me): request.POST or None, initial={'inizio': datetime.date.today()}, me=me, - is_course=session['is_course'] + course=oggetto ) # Check form is valid diff --git a/formazione/templates/formazione_corsi_base_direttori.html b/formazione/templates/formazione_corsi_base_direttori.html index 8f19213e0..5cb6047b9 100644 --- a/formazione/templates/formazione_corsi_base_direttori.html +++ b/formazione/templates/formazione_corsi_base_direttori.html @@ -22,5 +22,5 @@

Chi è il direttore?

-{% delegati delega corso continua_url=continua_url almeno=1 is_course=True %} + {% delegati delega corso continua_url=continua_url almeno=1 %} {% endblock %} From 4cec328614e818b683bc1406b1c95b0fd4f3faaf Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Tue, 6 Nov 2018 11:08:15 +0100 Subject: [PATCH 011/291] MODIFIED: aspirante urls moved from jorvik.urls to formazione.urls_aspirante. --- formazione/urls_aspirante.py | 43 ++++++++++++++++++++++++++++++++++++ jorvik/urls.py | 24 +++----------------- 2 files changed, 46 insertions(+), 21 deletions(-) create mode 100644 formazione/urls_aspirante.py diff --git a/formazione/urls_aspirante.py b/formazione/urls_aspirante.py new file mode 100644 index 000000000..2a7fb1837 --- /dev/null +++ b/formazione/urls_aspirante.py @@ -0,0 +1,43 @@ +from django.conf.urls import url +from . import viste + + +app_label = 'aspirante' +urlpatterns = [ + url(r'^$', viste.aspirante_home), + url(r'^impostazioni/$', viste.aspirante_impostazioni), + url(r'^impostazioni/cancella/$', viste.aspirante_impostazioni_cancella), + url(r'^corsi-base/$', viste.aspirante_corsi_base), + url(r'^sedi/$', viste.aspirante_sedi), + url(r'^corso-base/(?P[0-9]+)/$', + viste.aspirante_corso_base_informazioni), + url(r'^corso-base/(?P[0-9]+)/mappa/$', + viste.aspirante_corso_base_mappa), + url(r'^corso-base/(?P[0-9]+)/iscritti/$', + viste.aspirante_corso_base_iscritti), + url(r'^corso-base/(?P[0-9]+)/iscritti/aggiungi/$', + viste.aspirante_corso_base_iscritti_aggiungi), + url(r'^corso-base/(?P[0-9]+)/iscritti/cancella/(?P[0-9]+)/$', + viste.aspirante_corso_base_iscritti_cancella, + name='formazione_iscritti_cancella'), + url(r'^corso-base/(?P[0-9]+)/iscriviti/$', + viste.aspirante_corso_base_iscriviti), + url(r'^corso-base/(?P[0-9]+)/ritirati/$', + viste.aspirante_corso_base_ritirati), + url(r'^corso-base/(?P[0-9]+)/report/$', + viste.aspirante_corso_base_report), + url(r'^corso-base/(?P[0-9]+)/report/schede/$', + viste.aspirante_corso_base_report_schede), + url(r'^corso-base/(?P[0-9]+)/firme/$', + viste.aspirante_corso_base_firme), + url(r'^corso-base/(?P[0-9]+)/modifica/$', + viste.aspirante_corso_base_modifica), + url(r'^corso-base/(?P[0-9]+)/attiva/$', + viste.aspirante_corso_base_attiva), + url(r'^corso-base/(?P[0-9]+)/termina/$', + viste.aspirante_corso_base_termina), + url(r'^corso-base/(?P[0-9]+)/lezioni/$', + viste.aspirante_corso_base_lezioni), + url(r'^corso-base/(?P[0-9]+)/lezioni/(?P[0-9]+)/cancella/$', + viste.aspirante_corso_base_lezioni_cancella), +] diff --git a/jorvik/urls.py b/jorvik/urls.py index 686715649..33527331e 100755 --- a/jorvik/urls.py +++ b/jorvik/urls.py @@ -27,6 +27,7 @@ import social.viste import ufficio_soci.viste import veicoli.viste +from formazione import urls_aspirante as formazione_urls_aspirante handler404 = base.errori.non_trovato @@ -268,27 +269,8 @@ url(r'^veicolo/(?P.*)/collocazioni/$', veicoli.viste.veicoli_collocazioni), url(r'^veicolo/dettagli/(?P.*)/$', veicoli.viste.veicolo_dettagli), - url(r'^aspirante/$', formazione.viste.aspirante_home), - url(r'^aspirante/impostazioni/$', formazione.viste.aspirante_impostazioni), - url(r'^aspirante/impostazioni/cancella/$', formazione.viste.aspirante_impostazioni_cancella), - url(r'^aspirante/corsi-base/$', formazione.viste.aspirante_corsi_base), - url(r'^aspirante/sedi/$', formazione.viste.aspirante_sedi), - url(r'^aspirante/corso-base/(?P[0-9]+)/$', formazione.viste.aspirante_corso_base_informazioni), - url(r'^aspirante/corso-base/(?P[0-9]+)/mappa/$', formazione.viste.aspirante_corso_base_mappa), - url(r'^aspirante/corso-base/(?P[0-9]+)/iscritti/$', formazione.viste.aspirante_corso_base_iscritti), - url(r'^aspirante/corso-base/(?P[0-9]+)/iscritti/aggiungi/$', formazione.viste.aspirante_corso_base_iscritti_aggiungi), - url(r'^aspirante/corso-base/(?P[0-9]+)/iscritti/cancella/(?P[0-9]+)/$', formazione.viste.aspirante_corso_base_iscritti_cancella, name='formazione-iscritti-cancella'), - url(r'^aspirante/corso-base/(?P[0-9]+)/iscriviti/$', formazione.viste.aspirante_corso_base_iscriviti), - url(r'^aspirante/corso-base/(?P[0-9]+)/ritirati/$', formazione.viste.aspirante_corso_base_ritirati), - url(r'^aspirante/corso-base/(?P[0-9]+)/report/$', formazione.viste.aspirante_corso_base_report), - url(r'^aspirante/corso-base/(?P[0-9]+)/report/schede/$', formazione.viste.aspirante_corso_base_report_schede), - url(r'^aspirante/corso-base/(?P[0-9]+)/firme/$', formazione.viste.aspirante_corso_base_firme), - url(r'^aspirante/corso-base/(?P[0-9]+)/modifica/$', formazione.viste.aspirante_corso_base_modifica), - url(r'^aspirante/corso-base/(?P[0-9]+)/attiva/$', formazione.viste.aspirante_corso_base_attiva), - url(r'^aspirante/corso-base/(?P[0-9]+)/termina/$', formazione.viste.aspirante_corso_base_termina), - url(r'^aspirante/corso-base/(?P[0-9]+)/lezioni/$', formazione.viste.aspirante_corso_base_lezioni), - url(r'^aspirante/corso-base/(?P[0-9]+)/lezioni/(?P[0-9]+)/cancella/$', formazione.viste.aspirante_corso_base_lezioni_cancella), - + # Formazione + url(r'^aspirante/', include(formazione_urls_aspirante, namespace='aspirante')), url(r'^formazione/', include('formazione.urls', namespace='formazione')), url(r'^supporto/$', base.viste.supporto), From e2655e304506871d416091c4251b1c77d685aaa4 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Tue, 6 Nov 2018 11:14:04 +0100 Subject: [PATCH 012/291] MODIFIED: reverse strings in formazione.template/tests -> formazione-iscritti-cancella url name that was renamed in previous commit (4cec3286) --- .../formazione_elenchi_inc_iscritti.html | 10 ++++++-- formazione/tests.py | 24 ++++++++++--------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/formazione/templates/formazione_elenchi_inc_iscritti.html b/formazione/templates/formazione_elenchi_inc_iscritti.html index 33e07d458..ab171e33d 100644 --- a/formazione/templates/formazione_elenchi_inc_iscritti.html +++ b/formazione/templates/formazione_elenchi_inc_iscritti.html @@ -5,7 +5,13 @@ {% endblock %} {% block elenco_riga_extra %} - {% if not persona.aspirante or elenco.args.0.0.pk not in persona.aspirante.inviti_attivi %}Iscritto{% else %}Invitato{% endif %} + + {% if not persona.aspirante or elenco.args.0.0.pk not in persona.aspirante.inviti_attivi %} + Iscritto + {% else %} + Invitato + {% endif %} + {% endblock %} {% block elenco_riga_azioni %} @@ -14,7 +20,7 @@ {% permessi_almeno persona "modifica" as puo_modificare %} {% permessi_almeno persona "lettura" as puo_leggere %} - + Cancella {% endblock %} diff --git a/formazione/tests.py b/formazione/tests.py index 684b12147..a92f36372 100644 --- a/formazione/tests.py +++ b/formazione/tests.py @@ -242,30 +242,30 @@ def test_cancellazione_partecipante(self): # Test del controllo di cancellazione nei 4 stati del corso corso.stato = CorsoBase.TERMINATO corso.save() - response = self.client.get(reverse('formazione-iscritti-cancella', args=(corso.pk, sostenitore.pk))) + response = self.client.get(reverse('aspirante:formazione_iscritti_cancella', args=(corso.pk, sostenitore.pk))) self.assertContains(response, "stadio della vita del corso base") corso.stato = CorsoBase.ANNULLATO corso.save() - response = self.client.get(reverse('formazione-iscritti-cancella', args=(corso.pk, sostenitore.pk))) + response = self.client.get(reverse('aspirante:formazione_iscritti_cancella', args=(corso.pk, sostenitore.pk))) self.assertContains(response, "stadio della vita del corso base") corso.stato = CorsoBase.PREPARAZIONE corso.save() - response = self.client.get(reverse('formazione-iscritti-cancella', args=(corso.pk, sostenitore.pk))) + response = self.client.get(reverse('aspirante:formazione_iscritti_cancella', args=(corso.pk, sostenitore.pk))) self.assertContains(response, "Conferma cancellazione") corso.stato = CorsoBase.ATTIVO corso.save() - response = self.client.get(reverse('formazione-iscritti-cancella', args=(corso.pk, sostenitore.pk))) + response = self.client.get(reverse('aspirante:formazione_iscritti_cancella', args=(corso.pk, sostenitore.pk))) self.assertContains(response, "Conferma cancellazione") # GET chiede conferma - response = self.client.get(reverse('formazione-iscritti-cancella', args=(corso.pk, sostenitore.pk))) + response = self.client.get(reverse('aspirante:formazione_iscritti_cancella', args=(corso.pk, sostenitore.pk))) self.assertContains(response, "Conferma cancellazione") self.assertContains(response, force_text(sostenitore)) # POST cancella - response = self.client.post(reverse('formazione-iscritti-cancella', args=(corso.pk, sostenitore.pk))) + response = self.client.post(reverse('aspirante:formazione_iscritti_cancella', args=(corso.pk, sostenitore.pk))) self.assertContains(response, "Iscritto cancellato") self.assertEqual(corso.partecipazioni_confermate_o_in_attesa().count(), 2) @@ -285,12 +285,12 @@ def test_cancellazione_partecipante(self): mail.outbox = [] # Cancellare utente non esistente ritorna errore - response = self.client.post(reverse('formazione-iscritti-cancella', args=(corso.pk, altro.pk + 10000))) + response = self.client.post(reverse('aspirante:formazione_iscritti_cancella', args=(corso.pk, altro.pk + 10000))) self.assertContains(response, "La persona cercata non è iscritta") # Cancellare utente non associato al corso non ritorna errore -per evitare information leak- ma non cambia # i dati - response = self.client.post(reverse('formazione-iscritti-cancella', args=(corso.pk, altro.pk))) + response = self.client.post(reverse('aspirante:formazione_iscritti_cancella', args=(corso.pk, altro.pk))) self.assertContains(response, "Iscritto cancellato") self.assertEqual(corso.partecipazioni_confermate_o_in_attesa().count(), 2) @@ -299,7 +299,7 @@ def test_cancellazione_partecipante(self): self.assertEqual(len(mail.outbox), 0) # Cancellare invitato confermato - response = self.client.post(reverse('formazione-iscritti-cancella', args=(corso.pk, aspirante1.pk))) + response = self.client.post(reverse('aspirante:formazione_iscritti_cancella', args=(corso.pk, aspirante1.pk))) self.assertContains(response, "Iscritto cancellato") self.assertEqual(corso.partecipazioni_confermate_o_in_attesa().count(), 1) @@ -319,7 +319,7 @@ def test_cancellazione_partecipante(self): mail.outbox = [] # Cancellare invitato in attesa - response = self.client.post(reverse('formazione-iscritti-cancella', args=(corso.pk, aspirante2.pk))) + response = self.client.post(reverse('aspirante:formazione_iscritti_cancella', args=(corso.pk, aspirante2.pk))) self.assertContains(response, "Iscritto cancellato") self.assertEqual(corso.partecipazioni_confermate_o_in_attesa().count(), 1) @@ -333,7 +333,9 @@ def test_cancellazione_partecipante(self): mail.outbox = [] # Cancellare partecipante in attesa - response = self.client.post(reverse('formazione-iscritti-cancella', args=(corso.pk, aspirante3.pk))) + response = self.client.post(reverse( + 'aspirante:formazione_iscritti_cancella', args=(corso.pk, + aspirante3.pk))) self.assertContains(response, "Iscritto cancellato") self.assertEqual(corso.partecipazioni_confermate_o_in_attesa().count(), 0) From db43bbe26bc84aff0df8d6d9fa0f2f573d062c8c Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Tue, 6 Nov 2018 11:28:28 +0100 Subject: [PATCH 013/291] FIXED: name of fields op_attivazione/convocazione in CorsoBase model. --- formazione/models.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/formazione/models.py b/formazione/models.py index 5df0a8cd6..95ef547b1 100755 --- a/formazione/models.py +++ b/formazione/models.py @@ -69,8 +69,10 @@ class CorsoBase(Corso, ConVecchioID, ConPDF): data_attivazione = models.DateField(blank=True, null=True) data_convocazione = models.DateField(blank=True, null=True) - op_attivazione = models.CharField(max_length=255, blank=True, null=True) - op_convocazione = models.CharField(max_length=255, blank=True, null=True) + op_attivazione = models.CharField('Ordinanza presidenziale attivazione', + max_length=255, blank=True, null=True) + op_convocazione = models.CharField('Ordinanza presidenziale convocazione', + max_length=255, blank=True, null=True) PUOI_ISCRIVERTI_OK = "IS" PUOI_ISCRIVERTI = (PUOI_ISCRIVERTI_OK,) From 7976cd1c8e224cecfa696222abe92be27638149e Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Wed, 7 Nov 2018 10:29:31 +0100 Subject: [PATCH 014/291] MODIFIED: some urls formazione.urls_aspirante with reverse() in base.menu --- base/menu.py | 31 +++++++++++++++---------------- formazione/urls.py | 18 ++++++------------ formazione/urls_aspirante.py | 16 ++++++++++------ 3 files changed, 31 insertions(+), 34 deletions(-) diff --git a/base/menu.py b/base/menu.py index 2c0eafd89..b3b372a55 100755 --- a/base/menu.py +++ b/base/menu.py @@ -1,19 +1,18 @@ -__author__ = 'alfioemanuele' from django.contrib.contenttypes.models import ContentType from django.core.urlresolvers import reverse -from base.utils import remove_none +from .utils import remove_none from anagrafica.costanti import REGIONALE, TERRITORIALE #, LOCALE +from anagrafica.permessi.applicazioni import (DELEGATO_OBIETTIVO_1, + DELEGATO_OBIETTIVO_2, DELEGATO_OBIETTIVO_3, DELEGATO_OBIETTIVO_4, + DELEGATO_OBIETTIVO_5, DELEGATO_OBIETTIVO_6, PRESIDENTE, UFFICIO_SOCI, + UFFICIO_SOCI_UNITA, DELEGATO_AREA, RESPONSABILE_AREA, REFERENTE, + RESPONSABILE_FORMAZIONE, DIRETTORE_CORSO, RESPONSABILE_AUTOPARCO, DELEGATO_CO, + REFERENTE_GRUPPO, RUBRICHE_TITOLI, COMMISSARIO) +from anagrafica.permessi.costanti import (GESTIONE_CORSI_SEDE, GESTIONE_ATTIVITA, + GESTIONE_ATTIVITA_AREA, ELENCHI_SOCI, GESTIONE_AREE_SEDE, GESTIONE_ATTIVITA_SEDE, + EMISSIONE_TESSERINI, GESTIONE_POTERI_CENTRALE_OPERATIVA_SEDE) from anagrafica.models import Sede -from anagrafica.permessi.applicazioni import DELEGATO_OBIETTIVO_1, DELEGATO_OBIETTIVO_2, DELEGATO_OBIETTIVO_3, DELEGATO_OBIETTIVO_4, \ - DELEGATO_OBIETTIVO_5, DELEGATO_OBIETTIVO_6, PRESIDENTE, \ - UFFICIO_SOCI, UFFICIO_SOCI_UNITA, DELEGATO_AREA, RESPONSABILE_AREA, \ - REFERENTE, RESPONSABILE_FORMAZIONE, DIRETTORE_CORSO, \ - RESPONSABILE_AUTOPARCO, DELEGATO_CO, REFERENTE_GRUPPO, RUBRICHE_TITOLI, COMMISSARIO -from anagrafica.permessi.costanti import GESTIONE_CORSI_SEDE, GESTIONE_ATTIVITA, GESTIONE_ATTIVITA_AREA, ELENCHI_SOCI, \ - GESTIONE_AREE_SEDE, GESTIONE_ATTIVITA_SEDE, EMISSIONE_TESSERINI, GESTIONE_POTERI_CENTRALE_OPERATIVA_SEDE - - """ @@ -220,7 +219,7 @@ def menu(request): ), "aspirante": ( ("Aspirante", ( - ("Home page", "fa-home", "/aspirante/"), + ("Home page", "fa-home", reverse('aspirante:home')), ("Anagrafica", "fa-edit", "/utente/anagrafica/"), ("Storico", "fa-clock-o", "/utente/storico/"), ("Contatti", "fa-envelope", "/utente/contatti/"), @@ -230,9 +229,9 @@ def menu(request): ("Titoli di Studio", "fa-graduation-cap", "/utente/curriculum/TS/"), )), ("Nelle vicinanze", ( - ("Impostazioni", "fa-gears", "/aspirante/impostazioni/"), - ("Corsi Base", "fa-list", "/aspirante/corsi-base/"), - ("Sedi CRI", "fa-list", "/aspirante/sedi/"), + ("Corsi", "fa-list", reverse('aspirante:corsi_base')), + ("Sedi CRI", "fa-list", reverse('aspirante:sedi')), + ("Impostazioni", "fa-gears", reverse('aspirante:settings')), )), ("Sicurezza", ( ("Cambia password", "fa-key", "/utente/cambia-password/"), @@ -241,7 +240,7 @@ def menu(request): ), ) if me and hasattr(me, 'aspirante') else ( ("Gestione Corsi", ( - ("Elenco Corsi Base", "fa-list", "/formazione/corsi-base/elenco/"), + ("Elenco Corsi Base", "fa-list", reverse('formazione:list_courses')), )), ), } diff --git a/formazione/urls.py b/formazione/urls.py index ff5310bdb..506c5dc7c 100644 --- a/formazione/urls.py +++ b/formazione/urls.py @@ -1,23 +1,17 @@ from django.conf.urls import url from .viste import (formazione, formazione_corsi_base_elenco, - formazione_corsi_base_domanda, formazione_corsi_base_nuovo, - formazione_corsi_base_direttori, formazione_corsi_base_fine) + formazione_corsi_base_domanda, formazione_corsi_base_nuovo, + formazione_corsi_base_direttori, formazione_corsi_base_fine +) app_label = 'formazione' urlpatterns = [ url(r'^$', formazione, name='index'), - url(r'^corsi-base/elenco/$', - formazione_corsi_base_elenco, + url(r'^corsi-base/domanda/$', formazione_corsi_base_domanda, name='domanda'), + url(r'^corsi-base/nuovo/$', formazione_corsi_base_nuovo, name='new_course'), + url(r'^corsi-base/elenco/$', formazione_corsi_base_elenco, name='list_courses' ), - url(r'^corsi-base/domanda/$', - formazione_corsi_base_domanda, - name='domanda' - ), - url(r'^corsi-base/nuovo/$', - formazione_corsi_base_nuovo, - name='new_course' - ), url(r'^corsi-base/(?P[0-9]+)/direttori/$', formazione_corsi_base_direttori, name='director' diff --git a/formazione/urls_aspirante.py b/formazione/urls_aspirante.py index 2a7fb1837..d505edd70 100644 --- a/formazione/urls_aspirante.py +++ b/formazione/urls_aspirante.py @@ -1,14 +1,18 @@ from django.conf.urls import url from . import viste +from .viste import (aspirante_home, aspirante_impostazioni, aspirante_sedi, + aspirante_corsi, aspirante_impostazioni_cancella, + aspirante_corso_base_iscritti_cancella) app_label = 'aspirante' urlpatterns = [ - url(r'^$', viste.aspirante_home), - url(r'^impostazioni/$', viste.aspirante_impostazioni), - url(r'^impostazioni/cancella/$', viste.aspirante_impostazioni_cancella), - url(r'^corsi-base/$', viste.aspirante_corsi_base), - url(r'^sedi/$', viste.aspirante_sedi), + url(r'^$', aspirante_home, name='home'), + url(r'^impostazioni/$', aspirante_impostazioni, name='settings'), + url(r'^impostazioni/cancella/$', aspirante_impostazioni_cancella), + url(r'^sedi/$', aspirante_sedi, name='sedi'), + url(r'^corsi/$', aspirante_corsi, name='corsi_base'), + url(r'^corso-base/(?P[0-9]+)/$', viste.aspirante_corso_base_informazioni), url(r'^corso-base/(?P[0-9]+)/mappa/$', @@ -18,7 +22,7 @@ url(r'^corso-base/(?P[0-9]+)/iscritti/aggiungi/$', viste.aspirante_corso_base_iscritti_aggiungi), url(r'^corso-base/(?P[0-9]+)/iscritti/cancella/(?P[0-9]+)/$', - viste.aspirante_corso_base_iscritti_cancella, + aspirante_corso_base_iscritti_cancella, name='formazione_iscritti_cancella'), url(r'^corso-base/(?P[0-9]+)/iscriviti/$', viste.aspirante_corso_base_iscriviti), From 8b9d4c493e5b7fb65df638cee8fc897e79ed5a82 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Wed, 7 Nov 2018 10:55:34 +0100 Subject: [PATCH 015/291] MODIFIED: refactoring formazione.urls_aspirante --- formazione/urls_aspirante.py | 74 +++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/formazione/urls_aspirante.py b/formazione/urls_aspirante.py index d505edd70..8254db14c 100644 --- a/formazione/urls_aspirante.py +++ b/formazione/urls_aspirante.py @@ -1,10 +1,18 @@ from django.conf.urls import url -from . import viste -from .viste import (aspirante_home, aspirante_impostazioni, aspirante_sedi, - aspirante_corsi, aspirante_impostazioni_cancella, - aspirante_corso_base_iscritti_cancella) +# from . import viste +from .viste import (aspirante_home, aspirante_sedi, aspirante_corsi, + aspirante_corso_base_mappa, aspirante_impostazioni, + aspirante_impostazioni_cancella, aspirante_corso_base_informazioni, + aspirante_corso_base_iscritti, aspirante_corso_base_iscritti_cancella, + aspirante_corso_base_iscritti_aggiungi, aspirante_corso_base_iscriviti, + aspirante_corso_base_ritirati, aspirante_corso_base_report, + 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) +url_shortcut = 'corso-base/(?P[0-9]+)' app_label = 'aspirante' urlpatterns = [ url(r'^$', aspirante_home, name='home'), @@ -12,36 +20,34 @@ url(r'^impostazioni/cancella/$', aspirante_impostazioni_cancella), url(r'^sedi/$', aspirante_sedi, name='sedi'), url(r'^corsi/$', aspirante_corsi, name='corsi_base'), - - url(r'^corso-base/(?P[0-9]+)/$', - viste.aspirante_corso_base_informazioni), - url(r'^corso-base/(?P[0-9]+)/mappa/$', - viste.aspirante_corso_base_mappa), - url(r'^corso-base/(?P[0-9]+)/iscritti/$', - viste.aspirante_corso_base_iscritti), - url(r'^corso-base/(?P[0-9]+)/iscritti/aggiungi/$', - viste.aspirante_corso_base_iscritti_aggiungi), - url(r'^corso-base/(?P[0-9]+)/iscritti/cancella/(?P[0-9]+)/$', + url(rf'^{url_shortcut}/$', aspirante_corso_base_informazioni, name='info'), + url(rf'^{url_shortcut}/mappa/$', aspirante_corso_base_mappa, name='map'), + url(rf'^{url_shortcut}/firme/$', aspirante_corso_base_firme, name='firme'), + url(rf'^{url_shortcut}/ritirati/$', aspirante_corso_base_ritirati, + name='retired'), + url(rf'^{url_shortcut}/report/$', aspirante_corso_base_report, + name='report'), + url(rf'^{url_shortcut}/report/schede/$', aspirante_corso_base_report_schede, + name='report_schede'), + url(rf'^{url_shortcut}/modifica/$', aspirante_corso_base_modifica, + name='modify'), + url(rf'^{url_shortcut}/attiva/$', aspirante_corso_base_attiva, + name='activate'), + url(rf'^{url_shortcut}/termina/$', aspirante_corso_base_termina, + name='terminate'), + url(rf'^{url_shortcut}/lezioni/$', aspirante_corso_base_lezioni, + name='lessons'), + url(rf'^{url_shortcut}/lezioni/(?P[0-9]+)/cancella/$', + aspirante_corso_base_lezioni_cancella, + name='lessons_cancel'), + url(rf'^{url_shortcut}/iscritti/$', aspirante_corso_base_iscritti, + name='subscribed'), + url(rf'^{url_shortcut}/iscritti/aggiungi/$', + aspirante_corso_base_iscritti_aggiungi, + name='add_to_subscribed'), + url(rf'^{url_shortcut}/iscritti/cancella/(?P[0-9]+)/$', aspirante_corso_base_iscritti_cancella, name='formazione_iscritti_cancella'), - url(r'^corso-base/(?P[0-9]+)/iscriviti/$', - viste.aspirante_corso_base_iscriviti), - url(r'^corso-base/(?P[0-9]+)/ritirati/$', - viste.aspirante_corso_base_ritirati), - url(r'^corso-base/(?P[0-9]+)/report/$', - viste.aspirante_corso_base_report), - url(r'^corso-base/(?P[0-9]+)/report/schede/$', - viste.aspirante_corso_base_report_schede), - url(r'^corso-base/(?P[0-9]+)/firme/$', - viste.aspirante_corso_base_firme), - url(r'^corso-base/(?P[0-9]+)/modifica/$', - viste.aspirante_corso_base_modifica), - url(r'^corso-base/(?P[0-9]+)/attiva/$', - viste.aspirante_corso_base_attiva), - url(r'^corso-base/(?P[0-9]+)/termina/$', - viste.aspirante_corso_base_termina), - url(r'^corso-base/(?P[0-9]+)/lezioni/$', - viste.aspirante_corso_base_lezioni), - url(r'^corso-base/(?P[0-9]+)/lezioni/(?P[0-9]+)/cancella/$', - viste.aspirante_corso_base_lezioni_cancella), + url(rf'^{url_shortcut}/iscriviti/$', aspirante_corso_base_iscriviti, + name='subscribe'), ] From 1bc28d48cde435c1b286f553ce379fc5594decbe Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Wed, 7 Nov 2018 11:53:13 +0100 Subject: [PATCH 016/291] NEW: decorator to handle access to new courses [formazione.decorators.access_to_courses] -- applied for views.aspirante_corsi. --- formazione/decorators.py | 36 ++++++++++++++++++++++++++++ formazione/viste.py | 52 ++++++++++++++++++++-------------------- 2 files changed, 62 insertions(+), 26 deletions(-) create mode 100644 formazione/decorators.py diff --git a/formazione/decorators.py b/formazione/decorators.py new file mode 100644 index 000000000..f8c1b1638 --- /dev/null +++ b/formazione/decorators.py @@ -0,0 +1,36 @@ +def access_to_courses(function): + from django.shortcuts import redirect + from .models import Corso + from anagrafica.permessi.costanti import ERRORE_PERMESSI + + def wrapper(request, *args, **kwargs): + me = request.me + + if not me.ha_aspirante: + return redirect(ERRORE_PERMESSI) + + r = function(request, *args, **kwargs) + try: + context = r[1] # response context stored in the view + except IndexError: + return r + else: + context = r[1] if len(r) >= 2 else None + 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) + return r + + wrapper.__doc__ = function.__doc__ + wrapper.__name__ = function.__name__ + return wrapper \ No newline at end of file diff --git a/formazione/viste.py b/formazione/viste.py index 822026511..ce708238a 100644 --- a/formazione/viste.py +++ b/formazione/viste.py @@ -19,7 +19,8 @@ from base.utils import poco_fa from posta.models import Messaggio from .elenchi import ElencoPartecipantiCorsiBase -from .models import (CorsoBase, AssenzaCorsoBase, LezioneCorsoBase, +from .decorators import access_to_courses +from .models import (Corso, CorsoBase, AssenzaCorsoBase, LezioneCorsoBase, PartecipazioneCorsoBase, Aspirante, InvitoCorsoBase) from .forms import (ModuloCreazioneCorsoBase, ModuloModificaLezione, ModuloModificaCorsoBase, ModuloIscrittiCorsoBaseAggiungi, @@ -101,7 +102,6 @@ def formazione_corsi_base_direttori(request, me, pk): "corso": corso, "continua_url": continua_url } - return 'formazione_corsi_base_direttori.html', contesto @@ -119,10 +119,8 @@ 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) puo_modificare = me and me.permessi_almeno(corso, MODIFICA) puoi_partecipare = corso.persona(me) if me else None @@ -268,21 +266,20 @@ def aspirante_corso_base_lezioni_cancella(request, me, pk, lezione_pk): @pagina_privata def aspirante_corso_base_modifica(request, me, pk): - - corso = get_object_or_404(CorsoBase, pk=pk) - if not me.permessi_almeno(corso, MODIFICA): + course = get_object_or_404(CorsoBase, pk=pk) + if not me.permessi_almeno(course, MODIFICA): return redirect(ERRORE_PERMESSI) - modulo = ModuloModificaCorsoBase(request.POST or None, instance=corso) - if modulo.is_valid(): - modulo.save() + form = ModuloModificaCorsoBase(request.POST or None, instance=course) + if form.is_valid(): + form.save() - contesto = { - "corso": corso, + context = { + "corso": course, "puo_modificare": True, - "modulo": modulo, + "modulo": form, } - return 'aspirante_corso_base_scheda_modifica.html', contesto + return 'aspirante_corso_base_scheda_modifica.html', context @pagina_privata @@ -570,14 +567,13 @@ def aspirante_home(request, me): @pagina_privata -def aspirante_corsi_base(request, me): - if not me.ha_aspirante: - return redirect(ERRORE_PERMESSI) - - contesto = { - "corsi": me.aspirante.corsi(), +@access_to_courses +def aspirante_corsi(request, me): + """ url: /aspirante/corsi/ """ + context = { + 'corsi': me.aspirante.corsi(), } - return 'aspirante_corsi_base.html', contesto + return 'aspirante_corsi_base.html', context @pagina_privata @@ -606,12 +602,16 @@ def aspirante_impostazioni_cancella(request, me): return redirect(ERRORE_PERMESSI) if not me.cancellabile: - return errore_generico(request, me, titolo="Impossibile cancellare automaticamente il profilo da Gaia", - messaggio="E' necessario richiedere la cancellazione manuale al personale di supporto.") + return errore_generico(request, me, + titolo="Impossibile cancellare automaticamente il profilo da Gaia", + messaggio="E' necessario richiedere la cancellazione manuale al personale di supporto." + ) # 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 esitare a iscriverti nuovamente! " + ) From c1e458c0bec5c7a7913fdc746bfe1ebe6b0b0923 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Wed, 7 Nov 2018 12:03:49 +0100 Subject: [PATCH 017/291] FIXED: decorator access_to_courses --- formazione/decorators.py | 1 - 1 file changed, 1 deletion(-) diff --git a/formazione/decorators.py b/formazione/decorators.py index f8c1b1638..fa8d205d1 100644 --- a/formazione/decorators.py +++ b/formazione/decorators.py @@ -15,7 +15,6 @@ def wrapper(request, *args, **kwargs): except IndexError: return r else: - context = r[1] if len(r) >= 2 else None if context and ('corsi' in context): is_aspirante = me.ha_aspirante is_volontario = me.volontario From e37bdb2168879de0c698224ffc62e2340cb14fe1 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Wed, 7 Nov 2018 12:07:13 +0100 Subject: [PATCH 018/291] NEW: migration related to commit (db43bbe) --- .../migrations/0020_auto_20181107_1204.py | 25 +++++++++++++++++++ formazione/models.py | 15 +++++------ 2 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 formazione/migrations/0020_auto_20181107_1204.py diff --git a/formazione/migrations/0020_auto_20181107_1204.py b/formazione/migrations/0020_auto_20181107_1204.py new file mode 100644 index 000000000..2d6a6e991 --- /dev/null +++ b/formazione/migrations/0020_auto_20181107_1204.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2018-11-07 12:04 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('formazione', '0019_corsobase_tipo'), + ] + + operations = [ + migrations.AlterField( + model_name='corsobase', + name='op_attivazione', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Ordinanza presidenziale attivazione'), + ), + migrations.AlterField( + model_name='corsobase', + name='op_convocazione', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Ordinanza presidenziale convocazione'), + ), + ] diff --git a/formazione/models.py b/formazione/models.py index 95ef547b1..f40f48152 100755 --- a/formazione/models.py +++ b/formazione/models.py @@ -814,12 +814,6 @@ def sedi(self, tipo=Sede.COMITATO, **kwargs): def comitati(self): return self.sedi().filter(estensione__in=[LOCALE, PROVINCIALE, TERRITORIALE]) - def corso(self): - return CorsoBase.objects.filter( - PartecipazioneCorsoBase.con_esito_ok(persona=self.persona).via("partecipazioni"), - stato=Corso.ATTIVO, - ).first() - def richiesta_corso(self): return CorsoBase.objects.filter( PartecipazioneCorsoBase.con_esito_pending(persona=self.persona).via("partecipazioni"), @@ -831,7 +825,14 @@ def corsi(self, **kwargs): Ritorna un elenco di Corsi (Base) nelle vicinanze dell'Aspirante. :return: Un elenco di Corsi. """ - return self.nel_raggio(CorsoBase.pubblici().filter(**kwargs)) + corsi = CorsoBase.pubblici().filter(**kwargs) + return self.nel_raggio(corsi) + + def corso(self): + partecipazione = PartecipazioneCorsoBase.con_esito_ok(persona=self.persona) + partecipazione = partecipazione.via("partecipazioni") + corso = CorsoBase.objects.filter(partecipazione, stato=Corso.ATTIVO) + return corso.first() def calcola_raggio(self): """ From 58ed065af06c8bc4a379a01b95a552f112e7da5c Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Fri, 9 Nov 2018 11:25:16 +0100 Subject: [PATCH 019/291] NEW: (JO-756) new models in formazione - CorsoFile, CorsoLink. Modified view aspirante_corso_base_modifica, introduced formsets, migration file (0021) --- formazione/admin.py | 23 +++++-- formazione/forms.py | 38 ++++++++++- .../migrations/0021_corsofile_corsolink.py | 36 ++++++++++ formazione/models.py | 32 ++++++++- .../aspirante_corso_base_scheda_modifica.html | 19 +++--- formazione/validators.py | 10 +++ formazione/viste.py | 68 ++++++++++++++++--- 7 files changed, 197 insertions(+), 29 deletions(-) create mode 100644 formazione/migrations/0021_corsofile_corsolink.py create mode 100644 formazione/validators.py diff --git a/formazione/admin.py b/formazione/admin.py index d2b0e7f37..51f3e8b34 100755 --- a/formazione/admin.py +++ b/formazione/admin.py @@ -1,11 +1,12 @@ -from anagrafica.admin import RAW_ID_FIELDS_DELEGA -from anagrafica.models import Delega from django.contrib import admin +from django.contrib.contenttypes.admin import GenericTabularInline +from anagrafica.admin import RAW_ID_FIELDS_DELEGA +from anagrafica.models import Delega from base.admin import InlineAutorizzazione -from django.contrib.contenttypes.admin import GenericTabularInline -from formazione.models import CorsoBase, PartecipazioneCorsoBase, AssenzaCorsoBase, Aspirante, LezioneCorsoBase, InvitoCorsoBase from gruppi.readonly_admin import ReadonlyAdminMixin +from .models import (CorsoBase, CorsoFile, CorsoLink, Aspirante, + PartecipazioneCorsoBase, AssenzaCorsoBase, LezioneCorsoBase, InvitoCorsoBase) __author__ = 'alfioemanuele' @@ -68,6 +69,20 @@ class AdminCorsoBase(ReadonlyAdminMixin, admin.ModelAdmin): actions = [admin_corsi_base_attivi_invia_messaggi] +@admin.register(CorsoFile) +class AdminCorsoFile(admin.ModelAdmin): + list_display = ['__str__', 'file', 'is_enabled', 'corso',] + list_filter = ['is_enabled',] + raw_id_fields = ('corso',) + + +@admin.register(CorsoLink) +class AdminCorsoLink(admin.ModelAdmin): + list_display = ['link', 'is_enabled', 'corso',] + list_filter = ['is_enabled', ] + raw_id_fields = ('corso',) + + @admin.register(PartecipazioneCorsoBase) class AdminPartecipazioneCorsoBase(ReadonlyAdminMixin, admin.ModelAdmin): search_fields = ['persona__nome', 'persona__cognome', 'persona__codice_fiscale', 'corso__progressivo', ] diff --git a/formazione/forms.py b/formazione/forms.py index 2cbdd510f..7d848432a 100644 --- a/formazione/forms.py +++ b/formazione/forms.py @@ -1,11 +1,12 @@ from django import forms from django.core.exceptions import ValidationError -from django.forms import ModelForm +from django.forms import ModelForm, modelformset_factory from autocomplete_light import shortcuts as autocomplete_light from base.wysiwyg import WYSIWYGSemplice -from .models import Corso, CorsoBase, LezioneCorsoBase, PartecipazioneCorsoBase +from .models import (CorsoBase, CorsoLink, CorsoFile, LezioneCorsoBase, + PartecipazioneCorsoBase) class ModuloCreazioneCorsoBase(ModelForm): @@ -69,6 +70,39 @@ class Meta: } +class CorsoLinkForm(ModelForm): + class Meta: + model = CorsoFile + fields = ['file',] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + acceptable_extensions = [ + 'application/pdf', + 'application/msword', # .doc + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', # .docx + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'application/vnd.ms-excel', # xls + 'application/vnd.ms-powerpoint', + 'application/x-rar-compressed', 'application/octet-stream', # rar + 'application/zip', 'application/octet-stream', + 'application/x-zip-compressed', 'multipart/x-zip', + 'image/jpeg', + 'image/png', + 'text/csv', + 'application/rtf', + ] + self.fields['file'].widget.attrs = {'accept': ", ".join( + acceptable_extensions)} + +CorsoFileFormSet = modelformset_factory(CorsoFile, form=CorsoLinkForm, extra=1, + max_num=2) + + +CorsoLinkFormSet = modelformset_factory(CorsoLink, fields=('link',), extra=1, + max_num=2) + + class ModuloIscrittiCorsoBaseAggiungi(forms.Form): persone = autocomplete_light.ModelMultipleChoiceField("IscrivibiliCorsiAutocompletamento", help_text="Ricerca per Codice Fiscale " diff --git a/formazione/migrations/0021_corsofile_corsolink.py b/formazione/migrations/0021_corsofile_corsolink.py new file mode 100644 index 000000000..d297236de --- /dev/null +++ b/formazione/migrations/0021_corsofile_corsolink.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2018-11-09 11:21 +from __future__ import unicode_literals + +import anagrafica.validators +from django.db import migrations, models +import django.db.models.deletion +import formazione.validators + + +class Migration(migrations.Migration): + + dependencies = [ + ('formazione', '0020_auto_20181107_1204'), + ] + + operations = [ + migrations.CreateModel( + name='CorsoFile', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('is_enabled', models.BooleanField(default=True)), + ('file', models.FileField(blank=True, null=True, upload_to='corsi/', validators=[anagrafica.validators.valida_dimensione_file_8mb, formazione.validators.validate_file_extension], verbose_name='FIle')), + ('corso', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='formazione.CorsoBase')), + ], + ), + migrations.CreateModel( + name='CorsoLink', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('is_enabled', models.BooleanField(default=True)), + ('link', models.URLField(blank=True, null=True, verbose_name='Link')), + ('corso', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='formazione.CorsoBase')), + ], + ), + ] diff --git a/formazione/models.py b/formazione/models.py index f40f48152..0b521c1c4 100755 --- a/formazione/models.py +++ b/formazione/models.py @@ -11,7 +11,8 @@ from anagrafica.costanti import PROVINCIALE, TERRITORIALE, LOCALE from anagrafica.models import Sede, Persona, Appartenenza -from anagrafica.permessi.incarichi import INCARICO_GESTIONE_CORSOBASE_PARTECIPANTI, INCARICO_ASPIRANTE +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 @@ -56,6 +57,35 @@ class Meta: ) +class CorsoFile(models.Model): + from anagrafica.validators import valida_dimensione_file_8mb + from .validators import validate_file_extension + + is_enabled = models.BooleanField(default=True) + corso = models.ForeignKey('CorsoBase') + file = models.FileField('FIle', null=True, blank=True, + upload_to='corsi/', + validators=[valida_dimensione_file_8mb, validate_file_extension], + # help_text="Formati dei file supportati: doc, xls, pdf, zip, " + # "jpg (max 8mb))", + ) + + def __str__(self): + file = self.file if self.file else '' + corso = self.corso if hasattr(self, 'corso') else '' + return '<%s> of %s' % (file, corso) + + +class CorsoLink(models.Model): + is_enabled = models.BooleanField(default=True) + corso = models.ForeignKey('CorsoBase') + link = models.URLField('Link', null=True, blank=True) + + def __str__(self): + corso = self.corso if hasattr(self, 'corso') else '' + return '<%s> of %s' % (self.link, corso) + + class CorsoBase(Corso, ConVecchioID, ConPDF): MAX_PARTECIPANTI = 30 diff --git a/formazione/templates/aspirante_corso_base_scheda_modifica.html b/formazione/templates/aspirante_corso_base_scheda_modifica.html index 3de378f55..f6dd92426 100644 --- a/formazione/templates/aspirante_corso_base_scheda_modifica.html +++ b/formazione/templates/aspirante_corso_base_scheda_modifica.html @@ -1,14 +1,10 @@ {% extends 'aspirante_corso_base_scheda.html' %} - -{% block scheda_titolo %} - Modifica -{% endblock %} - {% load utils %} {% load bootstrap3 %} -{% block scheda_contenuto %} +{% block scheda_titolo %}Modifica{% endblock %} +{% block scheda_contenuto %}

@@ -17,16 +13,19 @@

-
+ {% csrf_token %} {% bootstrap_form modulo %} +

Inserimento link ed allegati

+ {{ file_formset.as_p }} + {{ link_formset.as_p }} +
-
@@ -39,8 +38,6 @@

{% localizzatore corso solo_italia=1 %} -
- -{% 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:

@@ -73,7 +73,6 @@

Il corso non è ancora attivo

{{ corso.aspiranti_nelle_vicinanze.count }})

-
{% endif %} @@ -95,9 +94,7 @@

Genera il verbale e termina il corso

- - {% endif %} @@ -119,9 +116,8 @@

Genera il verbale e termina il corso

- + Iscrizioni @@ -134,21 +130,30 @@

Genera il verbale e termina il corso

-
  • Gestione corso
  • + {% if corso.tipo == 'C1' %} +
  • + + + Estensioni + +
  • {% endif %} + + - +

     

    {% block scheda_contenuto %} diff --git a/formazione/templates/aspirante_corso_estensioni_modifica.html b/formazione/templates/aspirante_corso_estensioni_modifica.html new file mode 100644 index 000000000..403a24915 --- /dev/null +++ b/formazione/templates/aspirante_corso_estensioni_modifica.html @@ -0,0 +1,57 @@ +{% extends 'aspirante_corso_base_scheda.html' %} +{% load utils %} +{% load bootstrap3 %} + +{% block scheda_titolo %}Modifica{% endblock %} + +{% block scheda_contenuto %} +
    +
    +

    + Modifica estensioni

    +
    +
    +
    + {% csrf_token %} + + {% bootstrap_form select_extension_type_form %} +
    + {% bootstrap_formset select_extensions_formset %} +
    + + +
    +
    +
    + + + +{% 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"; -

    -

    - - - Attiva il corso e informa gli aspiranti in zona (più di - {{ corso.aspiranti_nelle_vicinanze.count }}) - -

    -
    - {% 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. -

    -

    - - - Completa i dati per generare il verbale e terminare il corso - -

    -
    - {% endif %} - - - {% endif %} @@ -120,7 +120,7 @@

    Il corso non è ancora attivo

    {% else %} FATTO: {% endif %} - Conferma l'estensione a chi vuoi aprire il Corso dalla scheda "Attivazione - Estensioni" + Conferma l'estensione a chi vuoi aprire il Corso dalla scheda "Attivazione - Area geografica interessata"

    {% endif %} From 735916ae153fb0e69b5758fcebd6181af98dcc3c Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Tue, 7 May 2019 14:48:27 +0200 Subject: [PATCH 242/291] FIXED: (GAIA-93) puo' ritirarsi dal corso anche quando e' confermato --- formazione/models.py | 36 ++++++++------- ...irante_corso_base_scheda_informazioni.html | 19 ++++---- formazione/viste.py | 44 ++++++++++++------- 3 files changed, 56 insertions(+), 43 deletions(-) diff --git a/formazione/models.py b/formazione/models.py index 531691d40..d790b6587 100755 --- a/formazione/models.py +++ b/formazione/models.py @@ -146,8 +146,10 @@ class CorsoBase(Corso, ConVecchioID, ConPDF): PUOI_ISCRIVERTI = (PUOI_ISCRIVERTI_OK,) SEI_ISCRITTO_PUOI_RITIRARTI = "GIA" + SEI_ISCRITTO_CONFERMATO_PUOI_RITIRARTI = "IPC" SEI_ISCRITTO_NON_PUOI_RITIRARTI = "NP" - SEI_ISCRITTO = (SEI_ISCRITTO_PUOI_RITIRARTI, SEI_ISCRITTO_NON_PUOI_RITIRARTI,) + SEI_ISCRITTO = (SEI_ISCRITTO_PUOI_RITIRARTI, + SEI_ISCRITTO_CONFERMATO_PUOI_RITIRARTI,) NON_PUOI_ISCRIVERTI_GIA_VOLONTARIO = "VOL" NON_PUOI_ISCRIVERTI_TROPPO_TARDI = "TAR" @@ -183,9 +185,10 @@ def persona(self, persona): corso__stato=self.ATTIVO).exclude(corso=self).exists(): return self.NON_PUOI_ISCRIVERTI_GIA_ISCRITTO_ALTRO_CORSO - # Controlla se gia' iscritto. + # Controlla se già iscritto. if PartecipazioneCorsoBase.con_esito_ok(persona=persona, corso=self).exists(): - return self.SEI_ISCRITTO_NON_PUOI_RITIRARTI + # UPDATE: (GAIA-93) utente può ritirarsi dal corso in qualsiasi momento. + return self.SEI_ISCRITTO_CONFERMATO_PUOI_RITIRARTI if PartecipazioneCorsoBase.con_esito_pending(persona=persona, corso=self).exists(): return self.SEI_ISCRITTO_PUOI_RITIRARTI @@ -276,7 +279,9 @@ def iniziato(self): @property def troppo_tardi_per_iscriverti(self): - return timezone.now() > (self.data_inizio + datetime.timedelta(days=settings.FORMAZIONE_FINESTRA_CORSI_INIZIATI)) + case_1 = timezone.now() > (self.data_inizio + datetime.timedelta(days=settings.FORMAZIONE_FINESTRA_CORSI_INIZIATI)) + case_2 = self.stato == CorsoBase.ATTIVO + return case_1 or case_2 @property def possibile_aggiungere_iscritti(self): @@ -1000,14 +1005,6 @@ class PartecipazioneCorsoBase(ModelloSemplice, ConMarcaTemporale, help_text="La Sede presso la quale verrà registrato come Volontario l'aspirante " "nel caso di superamento dell'esame.") - class Meta: - verbose_name = "Richiesta di partecipazione" - verbose_name_plural = "Richieste di partecipazione" - ordering = ('persona__cognome', 'persona__nome', 'persona__codice_fiscale',) - permissions = ( - ("view_partecipazionecorsobarse", "Can view corso Richiesta di partecipazione"), - ) - RICHIESTA_NOME = "Iscrizione Corso" def autorizzazione_concessa(self, modulo=None, auto=False, notifiche_attive=True, data=None): @@ -1089,11 +1086,6 @@ def disiscrivi(self, mittente=None): destinatari=[mittente], ) - def __str__(self): - return "Richiesta di part. di %s a %s" % ( - self.persona, self.corso - ) - def autorizzazione_concedi_modulo(self): from formazione.forms import (ModuloConfermaIscrizioneCorsoBase, ModuloConfermaIscrizioneCorso) @@ -1158,6 +1150,16 @@ def richieste_non_processabili(cls, richieste): oggetto_tipo=tipo, oggetto_id__in=partecipazioni_da_bloccare ).values_list('pk', flat=True) + class Meta: + verbose_name = "Richiesta di partecipazione" + verbose_name_plural = "Richieste di partecipazione" + ordering = ('persona__cognome', 'persona__nome', 'persona__codice_fiscale',) + permissions = ( + ("view_partecipazionecorsobarse", "Can view corso Richiesta di partecipazione"), + ) + + def __str__(self): + return "Richiesta di part. di %s a %s" % (self.persona, self.corso) class LezioneCorsoBase(ModelloSemplice, ConMarcaTemporale, ConGiudizio, ConStorico): corso = models.ForeignKey(CorsoBase, related_name='lezioni', on_delete=models.PROTECT) diff --git a/formazione/templates/aspirante_corso_base_scheda_informazioni.html b/formazione/templates/aspirante_corso_base_scheda_informazioni.html index b7f974e6e..0103248a4 100644 --- a/formazione/templates/aspirante_corso_base_scheda_informazioni.html +++ b/formazione/templates/aspirante_corso_base_scheda_informazioni.html @@ -71,11 +71,17 @@

    Hai chiesto di partecipare a questo corso<

    - {% elif puoi_partecipare == corso.SEI_ISCRITTO_NON_PUOI_RITIRARTI %} + {% elif puoi_partecipare == corso.SEI_ISCRITTO_CONFERMATO_PUOI_RITIRARTI %}

    Sei iscritt{{ me.genere_o_a }} a questo corso!

    Meraviglioso! Presentati alle lezioni del corso secondo il programma indicato sotto.

    Per qualsiasi domanda, contatta uno dei direttori del corso, cliccando sul suo nome.

    + +

    + + Non posso più partecipare — voglio ritirarmi + +

    {% endif %} @@ -148,19 +154,14 @@

    Organizzatore

    Data di inizio

    -
    - {{ corso.data_inizio }} -
    +
    {{ corso.data_inizio }}
    -

    - - Data di esame -

    +

    Data di esame

    {{ corso.data_esame }}
    @@ -172,7 +173,7 @@

    Informazioni

    {{ corso.descrizione|default:"

    Ancora non disponibili

    "|safe }} - {% if puoi_partecipare == corso.SEI_ISCRITTO_NON_PUOI_RITIRARTI or puo_modificare %} + {% if puoi_partecipare == corso.SEI_ISCRITTO_CONFERMATO_PUOI_RITIRARTI or puo_modificare %} {% if corso.get_course_links or corso.get_course_files %}

    Materiale didattico

    diff --git a/formazione/viste.py b/formazione/viste.py index 7da740470..bde751a2f 100644 --- a/formazione/viste.py +++ b/formazione/viste.py @@ -225,25 +225,35 @@ def aspirante_corso_base_iscriviti(request, me=None, pk=None): @pagina_privata def aspirante_corso_base_ritirati(request, me=None, pk=None): - corso = get_object_or_404(CorsoBase, pk=pk) - puoi_partecipare = corso.persona(me) - if not puoi_partecipare == corso.SEI_ISCRITTO_PUOI_RITIRARTI: - return errore_generico(request, me, titolo="Non puoi ritirarti da questo corso", - messaggio="Siamo spiacenti, ma non sembra che tu possa ritirarti " - "da questo corso per qualche motivo. ", - torna_titolo="Torna al corso", - torna_url=corso.url) + partecipazione = PartecipazioneCorsoBase.objects.none() - p = PartecipazioneCorsoBase.con_esito_pending(corso=corso, persona=me).first() - p.ritira() - - return messaggio_generico(request, me, titolo="Ti sei ritirato dal corso", - messaggio="Siamo spiacenti che hai deciso di ritirarti da questo corso. " - "La tua partecipazione è stata ritirata correttamente. " - "Non esitare a iscriverti a questo o un altro corso, nel caso cambiassi idea.", - torna_titolo="Torna alla pagina del corso", - torna_url=corso.url) + kwargs = dict(corso=corso, persona=me) + if corso.persona(me) == CorsoBase.SEI_ISCRITTO_CONFERMATO_PUOI_RITIRARTI: + partecipazione = PartecipazioneCorsoBase.con_esito_ok(kwargs).last() + else: + partecipazione = PartecipazioneCorsoBase.con_esito_pending(kwargs).first() + + if partecipazione: + # Caso: vuole ritirasi quando la richiesta non è stata ancora confermata + partecipazione.ritira() + + # Caso: vuole ritirasi quando la richiesta è stata confermata + if partecipazione.confermata: + partecipazione.confermata = False + partecipazione.save() # second save() call + + return messaggio_generico(request, me, titolo="Ti sei ritirato dal corso", + messaggio="Siamo spiacenti che hai deciso di ritirarti da questo corso. " + "La tua partecipazione è stata ritirata correttamente. " + "Non esitare a iscriverti a questo o un altro corso, nel caso cambiassi idea.", + torna_titolo="Torna alla pagina del corso", + torna_url=corso.url) + + return messaggio_generico(request, me, titolo="Non puoi ritirarti da questo corso", + messaggio="Siamo spiacenti, ma non sembra che tu possa ritirarti da questo corso per qualche motivo. ", + torna_titolo="Torna alla pagina del corso", + torna_url=corso.url) @pagina_privata From b6da47bee01ab32b71b55f24c0c96fe279916df5 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Tue, 7 May 2019 14:51:25 +0200 Subject: [PATCH 243/291] =?UTF-8?q?FIXED:=20(GAIA-93)=20togliere=20blocco?= =?UTF-8?q?=20che=20non=20pu=C3=B2=20iscriversi=20a=20pi=C3=B9=20corsi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- formazione/models.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/formazione/models.py b/formazione/models.py index d790b6587..f44b54c96 100755 --- a/formazione/models.py +++ b/formazione/models.py @@ -160,7 +160,7 @@ class CorsoBase(Corso, ConVecchioID, ConPDF): NON_HAI_DOCUMENTO_PERSONALE_VALIDO = 'NHDPV' NON_PUOI_ISCRIVERTI = (NON_PUOI_ISCRIVERTI_GIA_VOLONTARIO, NON_PUOI_ISCRIVERTI_TROPPO_TARDI, - NON_PUOI_ISCRIVERTI_GIA_ISCRITTO_ALTRO_CORSO, + # NON_PUOI_ISCRIVERTI_GIA_ISCRITTO_ALTRO_CORSO, NON_PUOI_SEI_ASPIRANTE, NON_PUOI_ISCRIVERTI_NON_HAI_TITOLI, NON_HAI_CARICATO_DOCUMENTI_PERSONALI, @@ -180,10 +180,11 @@ 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__tipo=self.BASE, - corso__stato=self.ATTIVO).exclude(corso=self).exists(): - return self.NON_PUOI_ISCRIVERTI_GIA_ISCRITTO_ALTRO_CORSO + # UPDATE: (GAIA-93) togliere blocco che non può iscriversi a più corsi + # 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 già iscritto. if PartecipazioneCorsoBase.con_esito_ok(persona=persona, corso=self).exists(): From 80e6a9ca704d7e3e062991b15c24f32db020a163 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Tue, 7 May 2019 14:56:08 +0200 Subject: [PATCH 244/291] RENAMED: contesto in context (formazione.viste) --- formazione/viste.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/formazione/viste.py b/formazione/viste.py index bde751a2f..30c4f3a01 100644 --- a/formazione/viste.py +++ b/formazione/viste.py @@ -31,30 +31,30 @@ @pagina_privata def formazione(request, me): - contesto = { + context = { "sedi": me.oggetti_permesso(GESTIONE_CORSI_SEDE), "corsi": me.oggetti_permesso(GESTIONE_CORSO), } - return 'formazione.html', contesto + return 'formazione.html', context @pagina_privata def formazione_corsi_base_elenco(request, me): - contesto = { + context = { "corsi": me.oggetti_permesso(GESTIONE_CORSO), "puo_pianificare": me.ha_permesso(GESTIONE_CORSI_SEDE), } - return 'formazione_corsi_base_elenco.html', contesto + return 'formazione_corsi_base_elenco.html', context @pagina_privata def formazione_corsi_base_domanda(request, me): - contesto = { + context = { "sedi": me.oggetti_permesso(GESTIONE_CORSI_SEDE), "min_sedi": Aspirante.MINIMO_COMITATI, "max_km": Aspirante.MASSIMO_RAGGIO, } - return 'formazione_corsi_base_domanda.html', contesto + return 'formazione_corsi_base_domanda.html', context @pagina_privata @@ -142,10 +142,10 @@ def formazione_corsi_base_fine(request, me, pk): if me in corso.delegati_attuali(): # Se sono direttore, continuo. redirect(corso.url) - contesto = { + context = { "corso": corso, } - return 'formazione_corsi_base_fine.html', contesto + return 'formazione_corsi_base_fine.html', context @pagina_pubblica From e9b1ed8ca4a741d04d72ec29ca553ac9597d1fdd Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Wed, 8 May 2019 11:09:45 +0200 Subject: [PATCH 245/291] FIXED: formazione.decorators --- formazione/decorators.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/formazione/decorators.py b/formazione/decorators.py index cd7d906f5..286992266 100644 --- a/formazione/decorators.py +++ b/formazione/decorators.py @@ -6,10 +6,17 @@ def can_access_to_course(function): def wrapper(request, *args, **kwargs): me = request.me + proceed = True REDIRECT_ERR = redirect(ERRORE_PERMESSI) - if not hasattr(me, 'ha_aspirante'): - return REDIRECT_ERR - if not me.ha_aspirante and not me.volontario: + + if me.dipendente: + pass + elif not hasattr(me, 'ha_aspirante'): + proceed = False + elif not me.ha_aspirante and not me.volontario: + proceed = False + + if not proceed: return REDIRECT_ERR r = function(request, *args, **kwargs) From 73261bc4c596281554814c41f3f63acc59fb80f0 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Fri, 10 May 2019 12:18:10 +0200 Subject: [PATCH 246/291] NEW: (GAIA-94) model formazione.RelazioneCorso (migration 0035), changed: admin.py --- formazione/admin.py | 9 ++- .../migrations/0035_auto_20190510_1149.py | 36 ++++++++++++ formazione/models.py | 58 ++++++++++++++++--- 3 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 formazione/migrations/0035_auto_20190510_1149.py diff --git a/formazione/admin.py b/formazione/admin.py index e922b01d4..cd5333dc3 100755 --- a/formazione/admin.py +++ b/formazione/admin.py @@ -7,7 +7,7 @@ from gruppi.readonly_admin import ReadonlyAdminMixin from .models import (CorsoBase, CorsoFile, CorsoEstensione, CorsoLink, Aspirante, PartecipazioneCorsoBase, AssenzaCorsoBase, LezioneCorsoBase, - InvitoCorsoBase) + InvitoCorsoBase, RelazioneCorso) RAW_ID_FIELDS_CORSOBASE = ['sede', 'locazione',] @@ -139,9 +139,16 @@ def ricalcola_raggio(modeladmin, request, queryset): a.calcola_raggio() ricalcola_raggio.short_description = "Ricalcola il raggio per gli aspiranti selezionati" + @admin.register(Aspirante) class AdminAspirante(ReadonlyAdminMixin, admin.ModelAdmin): search_fields = ['persona__nome', 'persona__cognome', 'persona__codice_fiscale'] list_display = ['persona', 'creazione', ] raw_id_fields = RAW_ID_FIELDS_ASPIRANTE actions = [ricalcola_raggio,] + + +@admin.register(RelazioneCorso) +class AdminRelazioneCorso(ReadonlyAdminMixin, admin.ModelAdmin): + list_display = ['corso', 'is_completed',] + raw_id_fields = ['corso',] diff --git a/formazione/migrations/0035_auto_20190510_1149.py b/formazione/migrations/0035_auto_20190510_1149.py new file mode 100644 index 000000000..4d2b5f284 --- /dev/null +++ b/formazione/migrations/0035_auto_20190510_1149.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2019-05-10 11:49 +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', '0034_auto_20190408_1047'), + ] + + operations = [ + migrations.CreateModel( + name='RelazioneCorso', + 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)), + ('note_esplicative', models.TextField(help_text='note esplicative in relazione ai cambiamenti effettuati rispetto alla programmazione approvata in fase di pianificazione iniziale del corso.', verbose_name='Note esplicative')), + ('raggiungimento_obiettivi', models.TextField(help_text="Analisi sul raggiungimento degli obiettivi del corso (generali rispetto all'evento e specifici di apprendimento).", verbose_name='Raggiungimento degli obiettivi del corso')), + ('annotazioni_corsisti', models.TextField(verbose_name='Annotazioni relative alla partecipazione dei corsisti')), + ('annotazioni_risorse', models.TextField(help_text='Annotazioni relative a risorse e competenze di particolare rilevanza emerse durante il percorso formativo')), + ('annotazioni_organizzazione_struttura', models.TextField(help_text="Annotazioni e segnalazioni sull'organizzazione e la logistica e della struttura ospitante il corso")), + ('descrizione_attivita', models.TextField(help_text='Descrizione delle eventuali attività di tirocinio/affiancamento con indicazione dei Tutor')), + ('corso', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='formazione.CorsoBase')), + ], + options={ + 'verbose_name': 'Relazione del Direttore', + 'verbose_name_plural': 'Relazioni dei Direttori', + }, + ), + ] diff --git a/formazione/models.py b/formazione/models.py index f44b54c96..4adbe0a98 100755 --- a/formazione/models.py +++ b/formazione/models.py @@ -1162,6 +1162,7 @@ class Meta: def __str__(self): return "Richiesta di part. di %s a %s" % (self.persona, self.corso) + class LezioneCorsoBase(ModelloSemplice, ConMarcaTemporale, ConGiudizio, ConStorico): corso = models.ForeignKey(CorsoBase, related_name='lezioni', on_delete=models.PROTECT) nome = models.CharField(max_length=128) @@ -1174,14 +1175,6 @@ class LezioneCorsoBase(ModelloSemplice, ConMarcaTemporale, ConGiudizio, ConStori help_text="Compilare nel caso il luogo è diverso " "dal comitato che ha organizzato il corso.") - class Meta: - verbose_name = "Lezione di Corso" - verbose_name_plural = "Lezioni di Corsi" - ordering = ['inizio'] - permissions = ( - ("view_lezionecorsobase", "Can view corso Lezione di Corso Base"), - ) - @property def url_cancella(self): return "%s%d/cancella/" % (self.corso.url_lezioni, self.pk) @@ -1198,8 +1191,16 @@ def send_messagge_to_docente(self, me): destinatari=[self.docente] ) + class Meta: + verbose_name = "Lezione di Corso" + verbose_name_plural = "Lezioni di Corsi" + ordering = ['inizio'] + permissions = ( + ("view_lezionecorsobase", "Can view corso Lezione di Corso Base"), + ) + def __str__(self): - return "Lezione: %s" % (self.nome,) + return "Lezione: %s" % self.nome class AssenzaCorsoBase(ModelloSemplice, ConMarcaTemporale): @@ -1356,3 +1357,42 @@ def pulisci_volontari(cls): volontari = cls._anche_volontari() cls._chiudi_partecipazioni(volontari) volontari.delete() + + +class RelazioneCorso(ModelloSemplice, ConMarcaTemporale): + corso = models.ForeignKey(CorsoBase) + note_esplicative = models.TextField( + verbose_name='Note esplicative', + help_text="note esplicative in relazione ai cambiamenti effettuati rispetto " + "alla programmazione approvata in fase di pianificazione iniziale del corso.") + raggiungimento_obiettivi = models.TextField( + verbose_name='Raggiungimento degli obiettivi del corso', + help_text="Analisi sul raggiungimento degli obiettivi del corso " + "(generali rispetto all'evento e specifici di apprendimento).") + annotazioni_corsisti = models.TextField( + verbose_name="Annotazioni relative alla partecipazione dei corsisti") + annotazioni_risorse = models.TextField( + help_text="Annotazioni relative a risorse e competenze di particolare " + "rilevanza emerse durante il percorso formativo") + annotazioni_organizzazione_struttura = models.TextField( + help_text="Annotazioni e segnalazioni sull'organizzazione e " + "la logistica e della struttura ospitante il corso") + descrizione_attivita = models.TextField( + help_text="Descrizione delle eventuali attività di " + "tirocinio/affiancamento con indicazione dei Tutor") + + @property + def is_completed(self): + model_fields = self._meta.get_fields() + super_class_fields_to_exclude = ['id', 'creazione', 'ultima_modifica', 'corso'] + fields = [i.name for i in model_fields if i.name not in super_class_fields_to_exclude] + if '' in [getattr(self, i) for i in fields]: + return False + return True + + def __str__(self): + return 'Relazione Corso <%s>' % self.corso.nome + + class Meta: + verbose_name = 'Relazione del Direttore' + verbose_name_plural = 'Relazioni dei Direttori' From 9e1d4de6387bb7426e45aea6bd2ed5caa61c613d Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Fri, 10 May 2019 17:05:39 +0200 Subject: [PATCH 247/291] NEW: (GAIA-94) view, form, templates for RelazioneCorso Model. --- formazione/forms.py | 16 +++++++++- .../migrations/0036_auto_20190510_1701.py | 31 ++++++++++++++++++ formazione/models.py | 12 +++++-- .../aspirante_corso_base_scheda.html | 32 +++++++++---------- .../course_compila_relazione_direttore.html | 21 ++++++++++++ formazione/urls_courses.py | 7 +++- formazione/viste.py | 27 ++++++++++++++-- 7 files changed, 123 insertions(+), 23 deletions(-) create mode 100644 formazione/migrations/0036_auto_20190510_1701.py create mode 100644 formazione/templates/course_compila_relazione_direttore.html diff --git a/formazione/forms.py b/formazione/forms.py index 1030c70d6..4df76168f 100644 --- a/formazione/forms.py +++ b/formazione/forms.py @@ -11,7 +11,7 @@ from curriculum.models import Titolo from curriculum.areas import OBBIETTIVI_STRATEGICI from .models import (Corso, CorsoBase, CorsoLink, CorsoFile, CorsoEstensione, - LezioneCorsoBase, PartecipazioneCorsoBase) + LezioneCorsoBase, PartecipazioneCorsoBase, RelazioneCorso) class ModuloCreazioneCorsoBase(ModelForm): @@ -317,6 +317,20 @@ class ModuloConfermaIscrizioneCorsoBase(forms.Form): "al resto delle lezioni (questo sarà verbalizzato).") +class FormRelazioneDelDirettoreCorso(ModelForm): + class Meta: + model = RelazioneCorso + exclude = ['corso', 'creazione', 'ultima_modifica',] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + for f in self.fields: + self.fields[f].label = self.fields[f].help_text + self.fields[f].widget.attrs['placeholder'] = '' + self.fields[f].help_text = '' + + class ModuloVerbaleAspiranteCorsoBase(ModelForm): GENERA_VERBALE = 'genera_verbale' SALVA_SOLAMENTE = 'salva' diff --git a/formazione/migrations/0036_auto_20190510_1701.py b/formazione/migrations/0036_auto_20190510_1701.py new file mode 100644 index 000000000..0178d66a3 --- /dev/null +++ b/formazione/migrations/0036_auto_20190510_1701.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2019-05-10 17:01 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('formazione', '0035_auto_20190510_1149'), + ] + + operations = [ + migrations.AlterField( + model_name='relazionecorso', + name='annotazioni_corsisti', + field=models.TextField(help_text='Annotazioni relative alla partecipazione dei corsisti ', verbose_name='Annotazioni relative alla partecipazione dei corsisti'), + ), + migrations.AlterField( + model_name='relazionecorso', + name='corso', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='relazione_corso', to='formazione.CorsoBase'), + ), + migrations.AlterField( + model_name='relazionecorso', + name='note_esplicative', + field=models.TextField(help_text='Note esplicative in relazione ai cambiamenti effettuati rispetto alla programmazione approvata in fase di pianificazione iniziale del corso.', verbose_name='Note esplicative'), + ), + ] diff --git a/formazione/models.py b/formazione/models.py index 4adbe0a98..cfdec9167 100755 --- a/formazione/models.py +++ b/formazione/models.py @@ -783,6 +783,11 @@ def can_activate(self, me): """ Direttori del corso vedono sempre la sezione invece """ return True + @property + def relazione_direttore(self): + relazione, created = RelazioneCorso.objects.get_or_create(corso=self) + return relazione + class Meta: verbose_name = "Corso" verbose_name_plural = "Corsi" @@ -1360,17 +1365,18 @@ def pulisci_volontari(cls): class RelazioneCorso(ModelloSemplice, ConMarcaTemporale): - corso = models.ForeignKey(CorsoBase) + corso = models.ForeignKey(CorsoBase, related_name='relazione_corso') note_esplicative = models.TextField( verbose_name='Note esplicative', - help_text="note esplicative in relazione ai cambiamenti effettuati rispetto " + help_text="Note esplicative in relazione ai cambiamenti effettuati rispetto " "alla programmazione approvata in fase di pianificazione iniziale del corso.") raggiungimento_obiettivi = models.TextField( verbose_name='Raggiungimento degli obiettivi del corso', help_text="Analisi sul raggiungimento degli obiettivi del corso " "(generali rispetto all'evento e specifici di apprendimento).") annotazioni_corsisti = models.TextField( - verbose_name="Annotazioni relative alla partecipazione dei corsisti") + verbose_name="Annotazioni relative alla partecipazione dei corsisti", + help_text="Annotazioni relative alla partecipazione dei corsisti ") annotazioni_risorse = models.TextField( help_text="Annotazioni relative a risorse e competenze di particolare " "rilevanza emerse durante il percorso formativo") diff --git a/formazione/templates/aspirante_corso_base_scheda.html b/formazione/templates/aspirante_corso_base_scheda.html index 2345d729d..322e40ca0 100644 --- a/formazione/templates/aspirante_corso_base_scheda.html +++ b/formazione/templates/aspirante_corso_base_scheda.html @@ -149,22 +149,22 @@

    Il corso non è ancora attivo

    {% 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. -

    -

    - - - Completa i dati per generare il verbale e terminare il corso - -

    +

    Terminazione corso

    +

    Il corso si è concluso, ma è ancora necessario generare il verbale del corso e compilare la relazione.

    +

    Una volta generato il verbale e compilata la relazione, tutti i partecipanti verranno informati dell'esito e, + coloro che saranno stati promossi, verranno trasformati automaticamente in volontari.

    + + + + + + {% if corso.relazione_direttore and corso.relazione_direttore.is_completed %} + + {% endif %} + +
    + Generare il verbale + Inserire la relazione Terminare il corso
    {% endif %} diff --git a/formazione/templates/course_compila_relazione_direttore.html b/formazione/templates/course_compila_relazione_direttore.html new file mode 100644 index 000000000..9db42be07 --- /dev/null +++ b/formazione/templates/course_compila_relazione_direttore.html @@ -0,0 +1,21 @@ +{% extends 'aspirante_corso_base_scheda.html' %} +{% load utils %} +{% load bootstrap3 %} + +{% block scheda_titolo %}Inserimento relazione del Corso{% endblock %} + +{% block scheda_contenuto %} +
    +
    +

    Inserire relazione del Corso

    +
    +
    +
    + {% csrf_token %} + {% bootstrap_form form_relazione %} + + +
    +
    +
    +{% endblock %} diff --git a/formazione/urls_courses.py b/formazione/urls_courses.py index 2898531a7..6062531ca 100644 --- a/formazione/urls_courses.py +++ b/formazione/urls_courses.py @@ -8,8 +8,13 @@ app_label = 'courses' +pk = "(?P[0-9]+)" + urlpatterns = [ - url(r'^(?P[0-9]+)/questionnaire/send-to-participants/$', + url(r'^%s/questionnaire/send-to-participants/$' % pk, viste.course_send_questionnaire_to_participants, name='send_questionnaire_to_participants'), + url(r'^%s/relazione-direttore/$' % pk, + viste.corso_compila_relazione_direttore, + name='compila_relazione_direttore'), ] diff --git a/formazione/viste.py b/formazione/viste.py index 30c4f3a01..df5c1bfd7 100644 --- a/formazione/viste.py +++ b/formazione/viste.py @@ -23,10 +23,10 @@ from .elenchi import ElencoPartecipantiCorsiBase from .decorators import can_access_to_course from .models import (Corso, CorsoBase, CorsoEstensione, AssenzaCorsoBase, - LezioneCorsoBase, PartecipazioneCorsoBase, Aspirante, InvitoCorsoBase) + LezioneCorsoBase, PartecipazioneCorsoBase, Aspirante, InvitoCorsoBase, RelazioneCorso) from .forms import (ModuloCreazioneCorsoBase, ModuloModificaLezione, ModuloModificaCorsoBase, ModuloIscrittiCorsoBaseAggiungi, - ModuloVerbaleAspiranteCorsoBase) + ModuloVerbaleAspiranteCorsoBase, FormRelazioneDelDirettoreCorso) @pagina_privata @@ -548,6 +548,29 @@ def aspirante_corso_base_termina(request, me, pk): return 'aspirante_corso_base_scheda_termina.html', context +@pagina_privata +def corso_compila_relazione_direttore(request, me, pk): + course = get_object_or_404(CorsoBase, pk=pk) + puo_modificare = course.can_modify(me) + if not puo_modificare: + return redirect(ERRORE_PERMESSI) + + relazione, created = RelazioneCorso.objects.get_or_create(corso=course) + if request.method == 'POST': + form_relazione = FormRelazioneDelDirettoreCorso(request.POST, instance=relazione) + if form_relazione.is_valid(): + form_relazione.save() + else: + form_relazione = FormRelazioneDelDirettoreCorso(instance=relazione) + + context = { + "corso": course, + "puo_modificare": puo_modificare, + "form_relazione": form_relazione, + } + return 'course_compila_relazione_direttore.html', context + + @pagina_privata def aspirante_corso_base_iscritti(request, me, pk): corso = get_object_or_404(CorsoBase, pk=pk) From 03b396d93f52707a7463fe74fb7e4807b2dba5ec Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Fri, 10 May 2019 17:11:30 +0200 Subject: [PATCH 248/291] FIXED: (relazione check) formazione.viste.corso_compila_relazione_direttore --- formazione/viste.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/formazione/viste.py b/formazione/viste.py index df5c1bfd7..cac74e620 100644 --- a/formazione/viste.py +++ b/formazione/viste.py @@ -555,6 +555,10 @@ def corso_compila_relazione_direttore(request, me, pk): if not puo_modificare: return redirect(ERRORE_PERMESSI) + if not course.terminabile: + messages.warning(request, 'Il corso non è terminabile.') + return redirect(reverse('aspirante:info', args=[pk,])) + relazione, created = RelazioneCorso.objects.get_or_create(corso=course) if request.method == 'POST': form_relazione = FormRelazioneDelDirettoreCorso(request.POST, instance=relazione) From 5b79bd4531d0728bb2d7dc4726d689ac16426ecc Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Fri, 10 May 2019 17:12:23 +0200 Subject: [PATCH 249/291] FIXED: (GAIA-94) CorsoBase.relazione_direttore method --- formazione/models.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/formazione/models.py b/formazione/models.py index cfdec9167..38a4da78e 100755 --- a/formazione/models.py +++ b/formazione/models.py @@ -785,8 +785,12 @@ def can_activate(self, me): @property def relazione_direttore(self): - relazione, created = RelazioneCorso.objects.get_or_create(corso=self) - return relazione + # Non creare record in db per un corso ancora in preparazione + if self.stato != CorsoBase.PREPARAZIONE: + relazione, created = RelazioneCorso.objects.get_or_create(corso=self) + return relazione + + return RelazioneCorso.objects.none() class Meta: verbose_name = "Corso" From db52a72edf959490ca554a6d5834b03f5ceba6ee Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Fri, 10 May 2019 17:20:55 +0200 Subject: [PATCH 250/291] MOVED: from formazione.forms to formazione.formsets --- formazione/forms.py | 12 ------------ formazione/formsets.py | 14 ++++++++++++++ formazione/viste.py | 5 +++-- 3 files changed, 17 insertions(+), 14 deletions(-) create mode 100644 formazione/formsets.py diff --git a/formazione/forms.py b/formazione/forms.py index 4df76168f..7ad3e5dbd 100644 --- a/formazione/forms.py +++ b/formazione/forms.py @@ -227,14 +227,6 @@ def __init__(self, *args, **kwargs): acceptable_extensions)} -CorsoFileFormSet = modelformset_factory(CorsoFile, form=CorsoLinkForm, extra=1, - max_num=2) - - -CorsoLinkFormSet = modelformset_factory(CorsoLink, fields=('link',), extra=1, - max_num=2) - - class ModuloIscrittiCorsoBaseAggiungi(forms.Form): persone = autocomplete_light.ModelMultipleChoiceField( "IscrivibiliCorsiAutocompletamento", @@ -299,10 +291,6 @@ def __init__(self, *args, **kwargs): # self.corso.titolo_cri.pk]) -CorsoSelectExtensionFormSet = modelformset_factory(CorsoEstensione, extra=1, - max_num=3, form=CorsoExtensionForm, can_delete=True) - - class ModuloConfermaIscrizioneCorso(forms.Form): IS_CORSO_NUOVO = True diff --git a/formazione/formsets.py b/formazione/formsets.py new file mode 100644 index 000000000..bcb67d28b --- /dev/null +++ b/formazione/formsets.py @@ -0,0 +1,14 @@ +from django.forms import modelformset_factory + +from .forms import CorsoLinkForm, CorsoExtensionForm +from .models import CorsoFile, CorsoLink, CorsoEstensione + + +CorsoFileFormSet = modelformset_factory(CorsoFile, + form=CorsoLinkForm, extra=1, max_num=2) + +CorsoLinkFormSet = modelformset_factory(CorsoLink, + fields=('link',), extra=1, max_num=2) + +CorsoSelectExtensionFormSet = modelformset_factory(CorsoEstensione, + extra=1, max_num=3, form=CorsoExtensionForm, can_delete=True) diff --git a/formazione/viste.py b/formazione/viste.py index cac74e620..f02c63925 100644 --- a/formazione/viste.py +++ b/formazione/viste.py @@ -354,7 +354,7 @@ def aspirante_corso_base_lezioni_cancella(request, me, pk, lezione_pk): @pagina_privata def aspirante_corso_base_modifica(request, me, pk): from .models import CorsoFile, CorsoLink - from .forms import CorsoFileFormSet, CorsoLinkFormSet + from .formsets import CorsoFileFormSet, CorsoLinkFormSet course = get_object_or_404(CorsoBase, pk=pk) course_files = CorsoFile.objects.filter(corso=course) @@ -815,7 +815,8 @@ def aspirante_impostazioni_cancella(request, me): @pagina_privata def aspirante_corso_estensioni_modifica(request, me, pk): - from .forms import CorsoSelectExtensionTypeForm, CorsoSelectExtensionFormSet + from .forms import CorsoSelectExtensionTypeForm + from .formsets import CorsoSelectExtensionFormSet SELECT_EXTENSION_TYPE_FORM_PREFIX = 'extension_type' SELECT_EXTENSIONS_FORMSET_PREFIX = 'extensions' From d7b8876e967e66bb3bf6ca49507a32a28ea41e28 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Fri, 10 May 2019 17:23:59 +0200 Subject: [PATCH 251/291] FIXED: formazione.CorsoBase.relazione_direttore() --- formazione/models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/formazione/models.py b/formazione/models.py index 38a4da78e..818325296 100755 --- a/formazione/models.py +++ b/formazione/models.py @@ -787,8 +787,9 @@ def can_activate(self, me): def relazione_direttore(self): # Non creare record in db per un corso ancora in preparazione if self.stato != CorsoBase.PREPARAZIONE: - relazione, created = RelazioneCorso.objects.get_or_create(corso=self) - return relazione + if self.terminabile: + relazione, created = RelazioneCorso.objects.get_or_create(corso=self) + return relazione return RelazioneCorso.objects.none() From d0ec9725f3925f33f1f4727137581ca57b95be08 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Fri, 10 May 2019 17:45:02 +0200 Subject: [PATCH 252/291] =?UTF-8?q?ADDED:=20(GAIA-94)=20blocca=20modifiche?= =?UTF-8?q?=20form=20Relazione=20Direttore=20se=20il=20corso=20=C3=A8=20te?= =?UTF-8?q?rminato.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- formazione/forms.py | 5 +++++ formazione/templates/course_compila_relazione_direttore.html | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/formazione/forms.py b/formazione/forms.py index 7ad3e5dbd..e95f02489 100644 --- a/formazione/forms.py +++ b/formazione/forms.py @@ -313,11 +313,16 @@ class Meta: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + is_stato_terminato = self.instance.corso.stato == CorsoBase.TERMINATO + for f in self.fields: self.fields[f].label = self.fields[f].help_text self.fields[f].widget.attrs['placeholder'] = '' self.fields[f].help_text = '' + if is_stato_terminato: + self.fields[f].widget.attrs['disabled'] = 'disabled' + class ModuloVerbaleAspiranteCorsoBase(ModelForm): GENERA_VERBALE = 'genera_verbale' diff --git a/formazione/templates/course_compila_relazione_direttore.html b/formazione/templates/course_compila_relazione_direttore.html index 9db42be07..be7e0778a 100644 --- a/formazione/templates/course_compila_relazione_direttore.html +++ b/formazione/templates/course_compila_relazione_direttore.html @@ -14,7 +14,9 @@

    Inserire relazione de {% csrf_token %} {% bootstrap_form form_relazione %} - + {% if corso.stato != corso.TERMINATO %} + + {% endif %}

    From 711d7b356410d50eed47edd54f2ab4598e330d38 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Mon, 13 May 2019 12:20:42 +0200 Subject: [PATCH 253/291] FIXED: (GAIA-94) inserire un valore default per campi vuoti (migrazione 0037); modificate views/model/templates --- .../migrations/0037_auto_20190510_1752.py | 45 +++++ formazione/models.py | 15 +- .../aspirante_corso_base_scheda.html | 2 +- .../aspirante_corso_base_scheda_termina.html | 161 +++++++----------- formazione/viste.py | 22 ++- 5 files changed, 140 insertions(+), 105 deletions(-) create mode 100644 formazione/migrations/0037_auto_20190510_1752.py diff --git a/formazione/migrations/0037_auto_20190510_1752.py b/formazione/migrations/0037_auto_20190510_1752.py new file mode 100644 index 000000000..c2e25e6ad --- /dev/null +++ b/formazione/migrations/0037_auto_20190510_1752.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2019-05-10 17:52 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('formazione', '0036_auto_20190510_1701'), + ] + + operations = [ + migrations.AlterField( + model_name='relazionecorso', + name='annotazioni_corsisti', + field=models.TextField(blank=True, help_text='Annotazioni relative alla partecipazione dei corsisti ', null=True, verbose_name='Annotazioni relative alla partecipazione dei corsisti'), + ), + migrations.AlterField( + model_name='relazionecorso', + name='annotazioni_organizzazione_struttura', + field=models.TextField(blank=True, help_text="Annotazioni e segnalazioni sull'organizzazione e la logistica e della struttura ospitante il corso", null=True), + ), + migrations.AlterField( + model_name='relazionecorso', + name='annotazioni_risorse', + field=models.TextField(blank=True, help_text='Annotazioni relative a risorse e competenze di particolare rilevanza emerse durante il percorso formativo', null=True), + ), + migrations.AlterField( + model_name='relazionecorso', + name='descrizione_attivita', + field=models.TextField(blank=True, help_text='Descrizione delle eventuali attività di tirocinio/affiancamento con indicazione dei Tutor', null=True), + ), + migrations.AlterField( + model_name='relazionecorso', + name='note_esplicative', + field=models.TextField(blank=True, help_text='Note esplicative in relazione ai cambiamenti effettuati rispetto alla programmazione approvata in fase di pianificazione iniziale del corso.', null=True, verbose_name='Note esplicative'), + ), + migrations.AlterField( + model_name='relazionecorso', + name='raggiungimento_obiettivi', + field=models.TextField(blank=True, help_text="Analisi sul raggiungimento degli obiettivi del corso (generali rispetto all'evento e specifici di apprendimento).", null=True, verbose_name='Raggiungimento degli obiettivi del corso'), + ), + ] diff --git a/formazione/models.py b/formazione/models.py index 818325296..9c5cc851a 100755 --- a/formazione/models.py +++ b/formazione/models.py @@ -787,9 +787,8 @@ def can_activate(self, me): def relazione_direttore(self): # Non creare record in db per un corso ancora in preparazione if self.stato != CorsoBase.PREPARAZIONE: - if self.terminabile: - relazione, created = RelazioneCorso.objects.get_or_create(corso=self) - return relazione + relazione, created = RelazioneCorso.objects.get_or_create(corso=self) + return relazione return RelazioneCorso.objects.none() @@ -1370,25 +1369,33 @@ def pulisci_volontari(cls): class RelazioneCorso(ModelloSemplice, ConMarcaTemporale): + SENZA_VALORE = "Non ci sono segnalazioni e/o annotazioni" + corso = models.ForeignKey(CorsoBase, related_name='relazione_corso') note_esplicative = models.TextField( + blank=True, null=True, verbose_name='Note esplicative', help_text="Note esplicative in relazione ai cambiamenti effettuati rispetto " "alla programmazione approvata in fase di pianificazione iniziale del corso.") raggiungimento_obiettivi = models.TextField( + blank=True, null=True, verbose_name='Raggiungimento degli obiettivi del corso', help_text="Analisi sul raggiungimento degli obiettivi del corso " "(generali rispetto all'evento e specifici di apprendimento).") annotazioni_corsisti = models.TextField( + blank=True, null=True, verbose_name="Annotazioni relative alla partecipazione dei corsisti", help_text="Annotazioni relative alla partecipazione dei corsisti ") annotazioni_risorse = models.TextField( + blank=True, null=True, help_text="Annotazioni relative a risorse e competenze di particolare " "rilevanza emerse durante il percorso formativo") annotazioni_organizzazione_struttura = models.TextField( + blank=True, null=True, help_text="Annotazioni e segnalazioni sull'organizzazione e " "la logistica e della struttura ospitante il corso") descrizione_attivita = models.TextField( + blank=True, null=True, help_text="Descrizione delle eventuali attività di " "tirocinio/affiancamento con indicazione dei Tutor") @@ -1397,7 +1404,7 @@ def is_completed(self): model_fields = self._meta.get_fields() super_class_fields_to_exclude = ['id', 'creazione', 'ultima_modifica', 'corso'] fields = [i.name for i in model_fields if i.name not in super_class_fields_to_exclude] - if '' in [getattr(self, i) for i in fields]: + if list(filter(lambda x: x in ['', None], [getattr(self, i) for i in fields])): return False return True diff --git a/formazione/templates/aspirante_corso_base_scheda.html b/formazione/templates/aspirante_corso_base_scheda.html index 322e40ca0..191380fc7 100644 --- a/formazione/templates/aspirante_corso_base_scheda.html +++ b/formazione/templates/aspirante_corso_base_scheda.html @@ -161,7 +161,7 @@

    Terminazione corso

    Inserire la relazione {% if corso.relazione_direttore and corso.relazione_direttore.is_completed %} - Terminare il corso + Terminare il corso {% endif %} diff --git a/formazione/templates/aspirante_corso_base_scheda_termina.html b/formazione/templates/aspirante_corso_base_scheda_termina.html index 5319c3c7e..2d11195ce 100644 --- a/formazione/templates/aspirante_corso_base_scheda_termina.html +++ b/formazione/templates/aspirante_corso_base_scheda_termina.html @@ -1,13 +1,10 @@ {% extends 'aspirante_corso_base_scheda.html' %} -{% block scheda_titolo %} - Termina Corso Base -{% endblock %} +{% block scheda_titolo %}Termina Corso Base{% endblock %} {% load bootstrap3 %} {% block scheda_contenuto %} -
    {% csrf_token %} @@ -15,108 +12,76 @@
    -

    - - Compilazione del verbale del corso -

    +

    Compilazione del verbale del corso

    - -
    - - Per terminare il corso, completa le informazioni - necessarie per ogni partecipante, che serviranno a generare - il verbale del corso e finalizzarlo. Invieremo una e-mail - con l'esito a tutti i partecipanti. -
    - -
    - - Ricorda di salvare! - - Nuova funzionalità - - - - Puoi salvare le tue modifiche cliccando sul pulsante Salva, e continuare - in un secondo momento la compilazione del verbale. -
    - -
    - - {% for partecipante, modulo in partecipanti_moduli %} - -
    -
    -

    {{ partecipante.persona.nome_completo }}

    - -

     

    - - - - - - - - - - - - - - - - - - - - - - - - -
    Iscrizione{{ partecipante.creazione|date:"SHORT_DATETIME_FORMAT" }}
    Codice Fiscale{{ partecipante.persona.codice_fiscale }}
    Data di Nascita{{ partecipante.persona.data_nascita }}
    Luogo di Nascita - {{ partecipante.persona.comune_nascita }} - {{ partecipante.persona.provincia_nascita }} -
    Scheda - - - Apri (nuova scheda) - -
    -
    -
    - {% bootstrap_form modulo %} +
    +
    + Per terminare il corso, completa le informazioni necessarie per ogni partecipante, + che serviranno a generare il verbale del corso e finalizzarlo. Invieremo una e-mail con l'esito a tutti i partecipanti.
    -
    - - -
    - - {% endfor %} - +
    + Ricorda di salvare! + Nuova funzionalità + Puoi salvare le tue modifiche cliccando sul pulsante Salva, e continuare in un secondo momento la compilazione del verbale. +
    - +
    + + {% for partecipante, modulo in partecipanti_moduli %} +
    +
    +

    {{ partecipante.persona.nome_completo }}

    + +

     

    + + + + + + + + + + + + + + + + + + + + + + + + +
    Iscrizione{{ partecipante.creazione|date:"SHORT_DATETIME_FORMAT" }}
    Codice Fiscale{{ partecipante.persona.codice_fiscale }}
    Data di Nascita{{ partecipante.persona.data_nascita }}
    Luogo di Nascita + {{ partecipante.persona.comune_nascita }} + {{ partecipante.persona.provincia_nascita }} +
    Scheda Apri (nuova scheda)
    +
    +
    {% bootstrap_form modulo %}
    +
    +
    + {% endfor %} + + +
    + {% if corso.relazione_direttore.is_completed %} + + {% endif %}
    - -
    - - -
    -
    - -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/formazione/viste.py b/formazione/viste.py index f02c63925..964de9533 100644 --- a/formazione/viste.py +++ b/formazione/viste.py @@ -520,7 +520,14 @@ def aspirante_corso_base_termina(request, me, pk): partecipanti_moduli += [(partecipante, form)] - if termina_corso: # Se il corso può essere terminato. + if termina_corso: # Se premuto pulsante "Genera verbale e termina corso" + # Verifica se la relazione è compilata + if not corso.relazione_direttore.is_completed: + messages.error(request, "Il corso non può essere terminato perchè " + "la relazione del direttore non è completata.") + return redirect(reverse('aspirante:terminate', args=(pk,))) + + # Tutto ok, posso procedere corso.termina(mittente=me) if corso.is_nuovo_corso: @@ -563,7 +570,18 @@ def corso_compila_relazione_direttore(request, me, pk): if request.method == 'POST': form_relazione = FormRelazioneDelDirettoreCorso(request.POST, instance=relazione) if form_relazione.is_valid(): - form_relazione.save() + cd = form_relazione.cleaned_data + instance = form_relazione.save(commit=False) + + # Se nei vari il Direttore non ha nulla da inserire valorizzarlo con un valore di default + no_value_fields = [k for k,v in cd.items() if not v] + if no_value_fields: + for k in no_value_fields: + setattr(instance, k, RelazioneCorso.SENZA_VALORE) + instance.save() + + messages.success(request, 'La relazione è stata salvata.') + return redirect(reverse('courses:compila_relazione_direttore', args=(pk,))) else: form_relazione = FormRelazioneDelDirettoreCorso(instance=relazione) From a520fca78585ac52e20b4469c6fe1f710f790858 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Mon, 13 May 2019 17:17:47 +0200 Subject: [PATCH 254/291] =?UTF-8?q?FIXED:=20formazione.viste.aspirante=5Fc?= =?UTF-8?q?orso=5Fbase=5Ftermina=20(verifica=20accesso=20alla=20pagina=20s?= =?UTF-8?q?e=20corso=20=C3=A8=20terminabile)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- formazione/viste.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/formazione/viste.py b/formazione/viste.py index 964de9533..8ed9dd605 100644 --- a/formazione/viste.py +++ b/formazione/viste.py @@ -475,6 +475,10 @@ def aspirante_corso_base_termina(request, me, pk): if not me.permessi_almeno(corso, MODIFICA): return redirect(ERRORE_PERMESSI) + if not corso.terminabile: + messages.warning(request, "Il corso non è terminabile.") + return redirect(reverse('aspirante:info', args=(pk,))) + torna = {"torna_url": corso.url_modifica, "torna_titolo": "Modifica corso"} # if (not corso.op_attivazione) or (not corso.data_attivazione): From e185470024aa1cfc9c0613b55732bd82292d95a6 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Mon, 13 May 2019 17:36:55 +0200 Subject: [PATCH 255/291] FIXED: formazione/templates (scheda/informazioni) --- formazione/templates/aspirante_corso_base_scheda.html | 9 +++++++-- .../aspirante_corso_base_scheda_informazioni.html | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/formazione/templates/aspirante_corso_base_scheda.html b/formazione/templates/aspirante_corso_base_scheda.html index 191380fc7..b5ae05069 100644 --- a/formazione/templates/aspirante_corso_base_scheda.html +++ b/formazione/templates/aspirante_corso_base_scheda.html @@ -11,8 +11,13 @@ {% block app_contenuto %} -

    {{ corso.titolo_cri|default:"" }}

    -

    {{ corso.nome }}

    + + + {% if corso.titolo_cri %}

    {{ corso.titolo_cri|default:"" }}

    {% endif %} + +

    {{ corso.nome }}

    {% if corso.locazione %}{{ corso.locazione }}{% else %} Posizione non impostata{% endif %} — diff --git a/formazione/templates/aspirante_corso_base_scheda_informazioni.html b/formazione/templates/aspirante_corso_base_scheda_informazioni.html index 0103248a4..367b17147 100644 --- a/formazione/templates/aspirante_corso_base_scheda_informazioni.html +++ b/formazione/templates/aspirante_corso_base_scheda_informazioni.html @@ -71,7 +71,7 @@

    Hai chiesto di partecipare a questo corso<

    - {% elif puoi_partecipare == corso.SEI_ISCRITTO_CONFERMATO_PUOI_RITIRARTI %} + {% elif puoi_partecipare == corso.SEI_ISCRITTO_CONFERMATO_PUOI_RITIRARTI and corso.stato != corso.TERMINATO %}

    Sei iscritt{{ me.genere_o_a }} a questo corso!

    Meraviglioso! Presentati alle lezioni del corso secondo il programma indicato sotto.

    From 105a6601499ba58b94dc93286a779f5f60fc570e Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Mon, 13 May 2019 17:47:35 +0200 Subject: [PATCH 256/291] FIXED: formazione.models methods --- formazione/models.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/formazione/models.py b/formazione/models.py index 9c5cc851a..f862fc99d 100755 --- a/formazione/models.py +++ b/formazione/models.py @@ -2,7 +2,7 @@ from django.conf import settings from django.core.urlresolvers import reverse -from django.db import models +from django.db import models, transaction from django.db.models import Q from django.db.transaction import atomic from django.contrib.contenttypes.models import ContentType @@ -280,9 +280,7 @@ def iniziato(self): @property def troppo_tardi_per_iscriverti(self): - case_1 = timezone.now() > (self.data_inizio + datetime.timedelta(days=settings.FORMAZIONE_FINESTRA_CORSI_INIZIATI)) - case_2 = self.stato == CorsoBase.ATTIVO - return case_1 or case_2 + return timezone.now() > (self.data_inizio + datetime.timedelta(days=settings.FORMAZIONE_FINESTRA_CORSI_INIZIATI)) @property def possibile_aggiungere_iscritti(self): @@ -597,34 +595,33 @@ def ha_verbale(self): def termina(self, mittente=None): """ 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. + # Per maggiore sicurezza, questa cosa viene eseguita in una transazione for partecipante in self.partecipazioni_confermate(): - # Calcola e salva l'esito dell'esame. - esito_esame = partecipante.IDONEO if partecipante.idoneo else partecipante.NON_IDONEO + esito_esame = partecipante.IDONEO if partecipante.idoneo \ + else partecipante.NON_IDONEO partecipante.esito_esame = esito_esame partecipante.save() - # Comunica il risultato all'aspirante/volontario. + # Comunica il risultato all'aspirante/volontario partecipante.notifica_esito_esame(mittente=mittente) # Actions required only for CorsoBase (Aspirante as participant) if not self.is_nuovo_corso: - # Se idoneo, volontarizza - if partecipante.idoneo: + if partecipante.idoneo: # Se idoneo, volontarizza partecipante.persona.da_aspirante_a_volontario( + inizio=self.data_esame, sede=partecipante.destinazione, - mittente=mittente) - + mittente=mittente + ) - # Cancella tutte le eventuali partecipazioni in attesa. + # Cancella tutte le eventuali partecipazioni in attesa PartecipazioneCorsoBase.con_esito_pending(corso=self).delete() - # Salva lo stato del corso come terminato. + # Salva lo stato del corso come terminato self.stato = Corso.TERMINATO self.save() @@ -1053,7 +1050,10 @@ def notifica_esito_esame(self, mittente=None): """ 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' + if self.corso.is_nuovo_corso: + template = template % 'volontario' + else: + template = template % 'aspirante' Messaggio.costruisci_e_accoda( oggetto="Esito del Corso: %s" % self.corso, From 5956b4d07abe3e6c548dd57043f3e5ae27704ad4 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Wed, 15 May 2019 10:36:16 +0200 Subject: [PATCH 257/291] FIXED: (bug) verifica ruoli di persona cv.cdf_titolo_json che ha accesso alla view --- anagrafica/models.py | 5 ++--- curriculum/views.py | 5 ++++- formazione/constants.py | 6 ++++++ 3 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 formazione/constants.py diff --git a/anagrafica/models.py b/anagrafica/models.py index 8f3d2795b..06302ae44 100755 --- a/anagrafica/models.py +++ b/anagrafica/models.py @@ -20,9 +20,8 @@ from .costanti import (ESTENSIONE, TERRITORIALE, LOCALE, PROVINCIALE, REGIONALE, NAZIONALE) from .validators import (valida_codice_fiscale, ottieni_genere_da_codice_fiscale, - crea_validatore_dimensione_file, valida_dimensione_file_8mb, - valida_dimensione_file_5mb, valida_almeno_14_anni, valida_partita_iva, - valida_iban, valida_email_personale) + valida_dimensione_file_8mb, valida_partita_iva, valida_dimensione_file_5mb, + valida_iban, valida_email_personale) # valida_almeno_14_anni, crea_validatore_dimensione_file) from attivita.models import Turno, Partecipazione from base.files import PDF, Excel, FoglioExcel from base.geo import ConGeolocalizzazione diff --git a/curriculum/views.py b/curriculum/views.py index 30d4ef33b..549c28160 100644 --- a/curriculum/views.py +++ b/curriculum/views.py @@ -1,11 +1,12 @@ from django.http import JsonResponse from autenticazione.funzioni import pagina_privata from curriculum.models import Titolo +from formazione.constants import FORMAZIONE_ROLES @pagina_privata def cdf_titolo_json(request, me): - if request.is_ajax and me.is_presidente: + if request.is_ajax and me.deleghe_attuali(tipo__in=FORMAZIONE_ROLES).exists(): area_id = request.POST.get('area', None) cdf_livello = request.POST.get('cdf_livello', None) @@ -17,3 +18,5 @@ def cdf_titolo_json(request, me): for option in query.values('id', 'nome', 'description')} return JsonResponse(options_for_select) + + return JsonResponse({}) diff --git a/formazione/constants.py b/formazione/constants.py new file mode 100644 index 000000000..581980f1a --- /dev/null +++ b/formazione/constants.py @@ -0,0 +1,6 @@ +from anagrafica.permessi.applicazioni import PRESIDENTE, DIRETTORE_CORSO, RESPONSABILE_FORMAZIONE + + +# Queste sono le principali tipologie di deleghe di persone che hanno +# accesso/potere sui corsi della formazione +FORMAZIONE_ROLES = [PRESIDENTE, DIRETTORE_CORSO, RESPONSABILE_FORMAZIONE] From 0ffca1f87db2fb86aadbc99fbca9a68685c0b10d Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Wed, 15 May 2019 11:40:23 +0200 Subject: [PATCH 258/291] FIXED: (piccolo refactoring) formazione.viste.aspirante_corso_base_lezioni (+form/template) --- formazione/forms.py | 9 ++-- .../aspirante_corso_base_scheda_lezioni.html | 7 +-- formazione/viste.py | 47 +++++++++++-------- 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/formazione/forms.py b/formazione/forms.py index e95f02489..1c8c73878 100644 --- a/formazione/forms.py +++ b/formazione/forms.py @@ -131,6 +131,7 @@ def __init__(self, *args, **kwargs): class ModuloModificaLezione(ModelForm): docente = autocomplete_light.ModelChoiceField("DocenteLezioniCorso") fine = forms.DateTimeField() + obiettivo = forms.CharField(required=False) def clean(self): cd = self.cleaned_data @@ -151,10 +152,6 @@ def clean(self): self.add_error('fine', err_data_lt_inizio_corso) return cd - - def __init__(self, *args, **kwargs): - self.corso = kwargs.pop('corso') - super().__init__(*args, **kwargs) class Meta: model = LezioneCorsoBase @@ -164,6 +161,10 @@ class Meta: 'obiettivo': 'Argomento', } + def __init__(self, *args, **kwargs): + self.corso = kwargs.pop('corso') + super().__init__(*args, **kwargs) + class ModuloModificaCorsoBase(ModelForm): class Meta: diff --git a/formazione/templates/aspirante_corso_base_scheda_lezioni.html b/formazione/templates/aspirante_corso_base_scheda_lezioni.html index 928df2ba5..e533f3c96 100644 --- a/formazione/templates/aspirante_corso_base_scheda_lezioni.html +++ b/formazione/templates/aspirante_corso_base_scheda_lezioni.html @@ -11,10 +11,7 @@
    -

    - - Lezioni -

    +

    Lezioni

    @@ -63,7 +60,7 @@

    {% endfor %} - + diff --git a/formazione/viste.py b/formazione/viste.py index 8ed9dd605..2607e1e4c 100644 --- a/formazione/viste.py +++ b/formazione/viste.py @@ -276,24 +276,27 @@ def aspirante_corso_base_lezioni(request, me, pk): partecipanti = Persona.objects.filter(partecipazioni_corsi__in=corso.partecipazioni_confermate()) lezioni = corso.lezioni.all() - moduli = [] - partecipanti_lezioni = [] + + moduli = list() + partecipanti_lezioni = list() + + AZIONE_SALVA = request.POST and request.POST['azione'] == 'salva' + AZIONE_NUOVA = request.POST and request.POST['azione'] == 'nuova' + for lezione in lezioni: - form = ModuloModificaLezione( - request.POST if request.POST and request.POST['azione'] == 'salva' else None, - instance=lezione, - corso=corso, - prefix="%s" % (lezione.pk,) - ) - if request.POST and request.POST['azione'] == 'salva' and form.is_valid(): + form = ModuloModificaLezione(request.POST if AZIONE_SALVA else None, + instance=lezione, corso=corso, prefix="%s" % lezione.pk) + + if AZIONE_SALVA and form.is_valid(): form.save() + # Presenze/assenze moduli += [form] partecipanti_lezione = partecipanti.exclude(assenze_corsi_base__lezione=lezione).order_by('nome', 'cognome') - if request.POST and request.POST['azione'] == 'salva': + if AZIONE_SALVA: for partecipante in partecipanti: - if ("%s" % (partecipante.pk,)) in request.POST.getlist('presenze-%s' % (lezione.pk,)): + if ("%s" % partecipante.pk) in request.POST.getlist('presenze-%s' % lezione.pk): # Se presente, rimuovi ogni assenza. AssenzaCorsoBase.objects.filter(lezione=lezione, persona=partecipante).delete() else: @@ -304,12 +307,10 @@ def aspirante_corso_base_lezioni(request, me, pk): partecipanti_lezioni += [partecipanti_lezione] - if request.POST and request.POST['azione'] == 'nuova': - modulo_nuova_lezione = ModuloModificaLezione(request.POST, - prefix="nuova", - corso=corso) - if modulo_nuova_lezione.is_valid(): - lezione = modulo_nuova_lezione.save(commit=False) + if AZIONE_NUOVA: + form_nuova_lezione = ModuloModificaLezione(request.POST, prefix="nuova", corso=corso) + if form_nuova_lezione.is_valid(): + lezione = form_nuova_lezione.save(commit=False) lezione.corso = corso lezione.save() @@ -317,13 +318,19 @@ def aspirante_corso_base_lezioni(request, me, pk): # Informa docente della lezione lezione.send_messagge_to_docente(me) - return redirect("%s#%d" % (corso.url_lezioni, lezione.pk,)) + return redirect("%s#%d" % (corso.url_lezioni, lezione.pk)) else: - modulo_nuova_lezione = ModuloModificaLezione(prefix="nuova", initial={ + form_nuova_lezione = ModuloModificaLezione(prefix="nuova", initial={ "inizio": timezone.now(), "fine": timezone.now() + timedelta(hours=2) }, corso=corso) + try: + if not form.is_valid(): + messages.error(request, 'Verifica tutti i moduli sulla presenza degli errori.') + except: + pass + lezioni = zip(lezioni, moduli, partecipanti_lezioni) context = { @@ -331,7 +338,7 @@ def aspirante_corso_base_lezioni(request, me, pk): "puo_modificare": True, "lezioni": lezioni, "partecipanti": partecipanti, - "modulo_nuova_lezione": modulo_nuova_lezione, + "modulo_nuova_lezione": form_nuova_lezione, } return 'aspirante_corso_base_scheda_lezioni.html', context From dc26d802feb266ed46cc62ae90045edd52319633 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Wed, 15 May 2019 12:28:33 +0200 Subject: [PATCH 259/291] MODIFIED: (formazione) CorsoFileFormset numero di file da caricare --- formazione/formsets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/formazione/formsets.py b/formazione/formsets.py index bcb67d28b..4ad4d5362 100644 --- a/formazione/formsets.py +++ b/formazione/formsets.py @@ -5,7 +5,7 @@ CorsoFileFormSet = modelformset_factory(CorsoFile, - form=CorsoLinkForm, extra=1, max_num=2) + form=CorsoLinkForm, extra=1, max_num=4) CorsoLinkFormSet = modelformset_factory(CorsoLink, fields=('link',), extra=1, max_num=2) From a32da7cb6a7152d50867437368e3c25ca789cbdf Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Thu, 16 May 2019 16:57:08 +0200 Subject: [PATCH 260/291] NEW: (GAIA-96) presenza esonero (migration 0038) (view/classes/admin/model/html/js) --- formazione/admin.py | 4 +- formazione/classes.py | 67 +++++++++++++++++++ .../migrations/0038_auto_20190516_1155.py | 25 +++++++ formazione/models.py | 39 +++++++++-- .../aspirante_corso_base_scheda_lezioni.html | 60 +++++++++++++++-- .../templatetags/formazione_templatetags.py | 10 +++ formazione/viste.py | 25 ++++--- 7 files changed, 207 insertions(+), 23 deletions(-) create mode 100644 formazione/classes.py create mode 100644 formazione/migrations/0038_auto_20190516_1155.py diff --git a/formazione/admin.py b/formazione/admin.py index cd5333dc3..728536865 100755 --- a/formazione/admin.py +++ b/formazione/admin.py @@ -130,7 +130,9 @@ class AdminLezioneCorsoBase(ReadonlyAdminMixin, admin.ModelAdmin): class AdminAssenzaCorsoBase(ReadonlyAdminMixin, admin.ModelAdmin): search_fields = ['persona__nome', 'persona__cognome', 'persona__codice_fiscale', 'lezione__corso__progressivo', 'lezione__corso__sede__nome'] - list_display = ['persona', 'lezione', 'creazione', ] + list_display = ['persona', 'lezione', 'creazione', 'esonero', + 'esonero_motivazione',] + list_filter = ['esonero',] raw_id_fields = RAW_ID_FIELDS_ASSENZACORSOBASE diff --git a/formazione/classes.py b/formazione/classes.py new file mode 100644 index 000000000..73ed0068b --- /dev/null +++ b/formazione/classes.py @@ -0,0 +1,67 @@ +from .models import AssenzaCorsoBase + + +class GestioneAssenza: + def __init__(self, request, lezione, me, partecipanti): + self.request = request + self.lezione = lezione + self.me = me + + self._verifica_presenze(partecipanti) + + @property + def presenze_lezione(self): + return self.request.POST.getlist('presenze-%s' % self.lezione.pk) + + def get_esonero(self): + key = 'esonero-%s-%s' % (self.lezione.pk, self.partecipante.pk) + return self.request.POST.get(key) + + @property + def esonero_checkbox(self): + key = 'esonero-checkbox-%s-%s' % (self.lezione.pk, self.partecipante.pk) + return self.request.POST.get(key) + + def _process_presenza(self): + if not self.esonero or self.esonero_checkbox != 'on': + # Se non è esonero - elimina oggetti (è presente) + q = AssenzaCorsoBase.objects.filter(lezione=self.lezione, + persona=self.partecipante) + q.delete() + self.esonero = None + + def _process_assenza(self): + # Assenza "ESONERO" ha un valore, ma la persona è segnata come assente + if self.esonero: + # Trova assenze che possono avere "esonero" + assenza = AssenzaCorsoBase.objects.filter(lezione=self.lezione, + persona=self.partecipante) + if assenza.exists(): + # Rimuovi "esonero" e fai oggetto come semplice assenza + assenza.update(esonero=False, esonero_motivazione=None) + # Assenza (senza esonero) + else: + assenza = AssenzaCorsoBase.create_assenza(self.lezione, + self.partecipante, + self.me) + + # Per non procedere alla lavorazione dell'esonero (il codice sotto) + self.esonero = None + + def _process_esonero(self): + assenza = AssenzaCorsoBase.create_assenza(self.lezione, + self.partecipante, self.me, esonero=self.esonero) + return assenza + + def _verifica_presenze(self, partecipanti): + for partecipante in partecipanti: + self.partecipante = partecipante + self.esonero = self.get_esonero() + + if "%s" % partecipante.pk in self.presenze_lezione: + self._process_presenza() + else: + self._process_assenza() + + if self.esonero: # Campo "Esonero" Valorizzato (impostata una motivazione) + self._process_esonero() diff --git a/formazione/migrations/0038_auto_20190516_1155.py b/formazione/migrations/0038_auto_20190516_1155.py new file mode 100644 index 000000000..5bebbd1a4 --- /dev/null +++ b/formazione/migrations/0038_auto_20190516_1155.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2019-05-16 11:55 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('formazione', '0037_auto_20190510_1752'), + ] + + operations = [ + migrations.AddField( + model_name='assenzacorsobase', + name='esonero', + field=models.NullBooleanField(default=False), + ), + migrations.AddField( + model_name='assenzacorsobase', + name='esonero_motivazione', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name="Motivazione dell'esonero"), + ), + ] diff --git a/formazione/models.py b/formazione/models.py index f862fc99d..6b5acf286 100755 --- a/formazione/models.py +++ b/formazione/models.py @@ -1213,11 +1213,45 @@ def __str__(self): class AssenzaCorsoBase(ModelloSemplice, ConMarcaTemporale): + """ + NB: valorizzati i campi "is_esonero" e "esonero_motivazione" significa + "Presenza", quindi per ottenere in una queryset solo le persone assenti + bisogna escludere i risultati con questi 2 campi valorizzati. + """ lezione = models.ForeignKey(LezioneCorsoBase, related_name='assenze', on_delete=models.CASCADE) persona = models.ForeignKey(Persona, related_name='assenze_corsi_base', on_delete=models.CASCADE) registrata_da = models.ForeignKey(Persona, related_name='assenze_corsi_base_registrate', null=True, on_delete=models.SET_NULL) + # Se questi 2 campi hanno un valore, Persona sarà considerata "Presente" alla lezione (GAIA-96) + esonero = models.NullBooleanField(default=False) + esonero_motivazione = models.CharField(max_length=255, null=True, blank=True, + verbose_name="Motivazione dell'esonero") + + @classmethod + def create_assenza(cls, lezione, persona, registrata_da, esonero=None): + assenza, created = cls.objects.get_or_create(lezione=lezione, + persona=persona, + registrata_da=registrata_da) + if esonero: + # Scrivi nell'oggetto la motivazione dell'esonero + assenza.esonero = True + assenza.esonero_motivazione = esonero + assenza.save() + + return assenza + + @property + def is_esonero(self): # , lezione, persona + if self.esonero and self.esonero_motivazione: + return True + if self.esonero or self.esonero_motivazione: + return True + return False + + def __str__(self): + return 'Assenza di %s a %s' % (self.persona.codice_fiscale, self.lezione) + class Meta: verbose_name = "Assenza a Corso" verbose_name_plural = "Assenze ai Corsi" @@ -1225,11 +1259,6 @@ class Meta: ("view_assenzacorsobase", "Can view corso Assenza a Corso Base"), ) - def __str__(self): - return "Assenza di %s a %s" % ( - self.persona.codice_fiscale, self.lezione - ) - class Aspirante(ModelloSemplice, ConGeolocalizzazioneRaggio, ConMarcaTemporale): diff --git a/formazione/templates/aspirante_corso_base_scheda_lezioni.html b/formazione/templates/aspirante_corso_base_scheda_lezioni.html index e533f3c96..1d006af74 100644 --- a/formazione/templates/aspirante_corso_base_scheda_lezioni.html +++ b/formazione/templates/aspirante_corso_base_scheda_lezioni.html @@ -1,10 +1,17 @@ {% extends 'aspirante_corso_base_scheda.html' %} {% load bootstrap3 %} +{% load formazione_templatetags %} {% block scheda_titolo %}Lezioni{% endblock %} {% block scheda_contenuto %} +
    {% csrf_token %} @@ -43,11 +50,30 @@

    Lezioni

    {% for partecipante in partecipanti %} - -

    diff --git a/formazione/templatetags/formazione_templatetags.py b/formazione/templatetags/formazione_templatetags.py index 040834e88..c2ba2be48 100644 --- a/formazione/templatetags/formazione_templatetags.py +++ b/formazione/templatetags/formazione_templatetags.py @@ -17,3 +17,13 @@ def titoli_del_corso(persona, cd): 'lista': lista.order_by('-data_scadenza'), 'num_of_titles': init_query.count() } + +@register.simple_tag +def lezione_esonero(lezione, partecipante): + from ..models import AssenzaCorsoBase + + try: + a = AssenzaCorsoBase.objects.get(lezione=lezione, persona=partecipante) + return a if a.is_esonero else None + except AssenzaCorsoBase.DoesNotExist: + return None diff --git a/formazione/viste.py b/formazione/viste.py index 2607e1e4c..c7f9ecac4 100644 --- a/formazione/viste.py +++ b/formazione/viste.py @@ -27,6 +27,7 @@ from .forms import (ModuloCreazioneCorsoBase, ModuloModificaLezione, ModuloModificaCorsoBase, ModuloIscrittiCorsoBaseAggiungi, ModuloVerbaleAspiranteCorsoBase, FormRelazioneDelDirettoreCorso) +from .classes import GestioneAssenza @pagina_privata @@ -283,6 +284,7 @@ def aspirante_corso_base_lezioni(request, me, pk): AZIONE_SALVA = request.POST and request.POST['azione'] == 'salva' AZIONE_NUOVA = request.POST and request.POST['azione'] == 'nuova' + # Presenze/assenze for lezione in lezioni: form = ModuloModificaLezione(request.POST if AZIONE_SALVA else None, instance=lezione, corso=corso, prefix="%s" % lezione.pk) @@ -290,20 +292,16 @@ def aspirante_corso_base_lezioni(request, me, pk): if AZIONE_SALVA and form.is_valid(): form.save() - # Presenze/assenze moduli += [form] - partecipanti_lezione = partecipanti.exclude(assenze_corsi_base__lezione=lezione).order_by('nome', 'cognome') + + # Excludi assenze con esonero + partecipanti_lezione = partecipanti.exclude( + Q(assenze_corsi_base__esonero=False), + assenze_corsi_base__lezione=lezione + ).order_by('nome', 'cognome') if AZIONE_SALVA: - for partecipante in partecipanti: - if ("%s" % partecipante.pk) in request.POST.getlist('presenze-%s' % lezione.pk): - # Se presente, rimuovi ogni assenza. - AssenzaCorsoBase.objects.filter(lezione=lezione, persona=partecipante).delete() - else: - # Assicurati che sia segnato come assente. - if not AssenzaCorsoBase.objects.filter(lezione=lezione, persona=partecipante).exists(): - a = AssenzaCorsoBase(lezione=lezione, persona=partecipante, registrata_da=me) - a.save() + gestione_assenze = GestioneAssenza(request, lezione, me, partecipanti) partecipanti_lezioni += [partecipanti_lezione] @@ -326,8 +324,9 @@ def aspirante_corso_base_lezioni(request, me, pk): }, corso=corso) try: - if not form.is_valid(): - messages.error(request, 'Verifica tutti i moduli sulla presenza degli errori.') + if AZIONE_SALVA or AZIONE_NUOVA: + if not form.is_valid(): + messages.error(request, 'Verifica tutti i moduli sulla presenza degli errori.') except: pass From af60a3e1c64e680fcddda76d0c53dc7651ac1dbb Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Thu, 16 May 2019 17:00:55 +0200 Subject: [PATCH 261/291] FIXED: (GAIA-96) class GestioneAssenza renamed to GestionePresenza --- formazione/classes.py | 2 +- formazione/viste.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/formazione/classes.py b/formazione/classes.py index 73ed0068b..752a0f679 100644 --- a/formazione/classes.py +++ b/formazione/classes.py @@ -1,7 +1,7 @@ from .models import AssenzaCorsoBase -class GestioneAssenza: +class GestionePresenza: def __init__(self, request, lezione, me, partecipanti): self.request = request self.lezione = lezione diff --git a/formazione/viste.py b/formazione/viste.py index c7f9ecac4..99795f403 100644 --- a/formazione/viste.py +++ b/formazione/viste.py @@ -1,5 +1,6 @@ from datetime import datetime, timedelta +from django.db.models import Q from django.utils import timezone from django.shortcuts import redirect, get_object_or_404 from django.core.urlresolvers import reverse @@ -27,7 +28,7 @@ from .forms import (ModuloCreazioneCorsoBase, ModuloModificaLezione, ModuloModificaCorsoBase, ModuloIscrittiCorsoBaseAggiungi, ModuloVerbaleAspiranteCorsoBase, FormRelazioneDelDirettoreCorso) -from .classes import GestioneAssenza +from .classes import GestionePresenza @pagina_privata @@ -301,7 +302,7 @@ def aspirante_corso_base_lezioni(request, me, pk): ).order_by('nome', 'cognome') if AZIONE_SALVA: - gestione_assenze = GestioneAssenza(request, lezione, me, partecipanti) + gestione_presenze = GestionePresenza(request, lezione, me, partecipanti) partecipanti_lezioni += [partecipanti_lezione] From ed3eb81787476c0938b535d6c8336f203a644de6 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Fri, 17 May 2019 10:24:44 +0200 Subject: [PATCH 262/291] ADDED: template tag 'lezione_partecipante_pk_shortcut' (fixed aspirante_corso_base_scheda_lezione.html) --- .../aspirante_corso_base_scheda_lezioni.html | 17 +++++++++-------- .../templatetags/formazione_templatetags.py | 4 ++++ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/formazione/templates/aspirante_corso_base_scheda_lezioni.html b/formazione/templates/aspirante_corso_base_scheda_lezioni.html index 1d006af74..ab8759cc4 100644 --- a/formazione/templates/aspirante_corso_base_scheda_lezioni.html +++ b/formazione/templates/aspirante_corso_base_scheda_lezioni.html @@ -49,12 +49,13 @@

    Lezioni

    {% for partecipante in partecipanti %} + {% lezione_partecipante_pk_shortcut lezione partecipante as lezione_partecipante_pk %} + {% lezione_esonero lezione partecipante as assenza_lezione_esonero %} + - {% lezione_esonero lezione partecipante as assenza_lezione_esonero %} -
    + {# template-tag "delegati" si trova nell'app "anagrafica" #} {% delegati delega corso continua_url=continua_url almeno=1 %} {% endblock %} From ecbb485d48e49e4f25b5ec4a502c0d2c514e1428 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Mon, 20 May 2019 12:05:18 +0200 Subject: [PATCH 265/291] =?UTF-8?q?FIXED:=20(formazione)=20verifiche=20di?= =?UTF-8?q?=20permessi=20in=20views/forms=20chi=20pu=C3=B2=20accedere=20al?= =?UTF-8?q?la=20creazione=20corso?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- formazione/constants.py | 8 ++++++-- formazione/forms.py | 3 ++- formazione/viste.py | 3 +++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/formazione/constants.py b/formazione/constants.py index 581980f1a..d5f2294c8 100644 --- a/formazione/constants.py +++ b/formazione/constants.py @@ -1,6 +1,10 @@ -from anagrafica.permessi.applicazioni import PRESIDENTE, DIRETTORE_CORSO, RESPONSABILE_FORMAZIONE +from anagrafica.permessi import applicazioni # Queste sono le principali tipologie di deleghe di persone che hanno # accesso/potere sui corsi della formazione -FORMAZIONE_ROLES = [PRESIDENTE, DIRETTORE_CORSO, RESPONSABILE_FORMAZIONE] +FORMAZIONE_ROLES = [applicazioni.PRESIDENTE, # Può creare + applicazioni.COMMISSARIO, # Può creare + applicazioni.DIRETTORE_CORSO, # Accesso solo al corso delegato + applicazioni.RESPONSABILE_FORMAZIONE, # Può creare +] diff --git a/formazione/forms.py b/formazione/forms.py index 1c8c73878..a0ea488c6 100644 --- a/formazione/forms.py +++ b/formazione/forms.py @@ -101,7 +101,8 @@ def __init__(self, *args, **kwargs): 'data_esame', 'delibera_file', 'sede', 'locazione')) # GAIA-16 - delega = me.deleghe_attuali().filter(tipo__in=[permessi.PRESIDENTE, + delega = me.deleghe_attuali().filter(tipo__in=[permessi.COMMISSARIO, + permessi.PRESIDENTE, permessi.RESPONSABILE_FORMAZIONE]).last() if delega: estensione_sede = delega.sede.all().first().estensione diff --git a/formazione/viste.py b/formazione/viste.py index faa6218fe..484385926 100644 --- a/formazione/viste.py +++ b/formazione/viste.py @@ -61,6 +61,9 @@ def formazione_corsi_base_domanda(request, me): @pagina_privata def formazione_corsi_base_nuovo(request, me): + if not me.ha_permesso(GESTIONE_CORSI_SEDE): + return redirect(ERRORE_PERMESSI) + now = datetime.now() + timedelta(days=14) form = ModuloCreazioneCorsoBase( request.POST or None, From 5fe0188087f27b3250fbe5ac18766cd1ecfbd665 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Mon, 20 May 2019 12:08:57 +0200 Subject: [PATCH 266/291] FIXED: verifica permessi visualizzazione formazione.menu --- anagrafica/permessi/costanti.py | 24 +++++++++++------------- base/menu.py | 2 +- formazione/__init__.py | 1 - formazione/menus.py | 12 ++++++++++-- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/anagrafica/permessi/costanti.py b/anagrafica/permessi/costanti.py index 6ce86401f..8cbde9e60 100755 --- a/anagrafica/permessi/costanti.py +++ b/anagrafica/permessi/costanti.py @@ -1,18 +1,17 @@ -# coding=utf-8 - +from ..permessi.applicazioni import (PRESIDENTE, DELEGATO_AREA, RESPONSABILE_AREA, + REFERENTE, DIRETTORE_CORSO, RESPONSABILE_AUTOPARCO, REFERENTE_GRUPPO, COMMISSARIO, + CONSIGLIERE, UFFICIO_SOCI) """ -Questo file gestisce i permessi in Gaia. - ============================================================================================ - | ! HEEEEY, TU ! | - ============================================================================================ - Prima di avventurarti da queste parti, assicurati di leggere la documentazione a: - https://github.com/CroceRossaItaliana/jorvik/wiki/Deleghe,-Permessi-e-Livelli-di-Accesso - ============================================================================================ + Questo file gestisce i permessi in Gaia. + =============================================================================== + | ! HEEEEY, TU ! | + =============================================================================== + Prima di avventurarti da queste parti, assicurati di leggere la documentazione: + https://github.com/CroceRossaItaliana/jorvik/wiki/Deleghe,-Permessi-e-Livelli-di-Accesso + =============================================================================== """ -from anagrafica.permessi.applicazioni import PRESIDENTE, DELEGATO_AREA, RESPONSABILE_AREA, REFERENTE, DIRETTORE_CORSO, \ - RESPONSABILE_AUTOPARCO, REFERENTE_GRUPPO, COMMISSARIO, CONSIGLIERE -from anagrafica.permessi.applicazioni import UFFICIO_SOCI + GESTIONE_SEDE = "GESTIONE_SEDE" GESTIONE_SOCI = "GESTIONE_SOCI" @@ -171,4 +170,3 @@ def permesso_minimo(tipo): # Costanti URL ERRORE_PERMESSI = '/errore/permessi/' ERRORE_ORFANO = '/errore/orfano/' - diff --git a/base/menu.py b/base/menu.py index 83c7c0260..8635552f7 100755 --- a/base/menu.py +++ b/base/menu.py @@ -210,7 +210,7 @@ def menu(request): if me and me.oggetti_permesso(GESTIONE_POTERI_CENTRALE_OPERATIVA_SEDE).exists() else None, )), ), - 'formazione': formazione_menu('formazione', gestione_corsi_sede), + 'formazione': formazione_menu('formazione', gestione_corsi_sede, me), 'aspirante': formazione_menu('aspirante', gestione_corsi_sede) \ if me and hasattr(me, 'aspirante') else ( ("Gestione Corsi", ( diff --git a/formazione/__init__.py b/formazione/__init__.py index 6f6872788..e69de29bb 100755 --- a/formazione/__init__.py +++ b/formazione/__init__.py @@ -1 +0,0 @@ -__author__ = 'alfioemanuele' diff --git a/formazione/menus.py b/formazione/menus.py index b8b4b121b..9bc4d884b 100644 --- a/formazione/menus.py +++ b/formazione/menus.py @@ -1,7 +1,15 @@ from django.core.urlresolvers import reverse +from anagrafica.permessi.costanti import GESTIONE_CORSI_SEDE -def formazione_menu(menu_name, gestione_corsi_sede): + +def to_show(me, permission): + if me.ha_permesso(permission): + return True + return False + + +def formazione_menu(menu_name, gestione_corsi_sede, me=None): FORMAZIONE = ( ("Corsi", ( ("Attiva Corso", "fa-asterisk", reverse('formazione:new_course')) if gestione_corsi_sede else None, @@ -9,7 +17,7 @@ def formazione_menu(menu_name, gestione_corsi_sede): ("Domanda formativa", "fa-area-chart", reverse('formazione:domanda')) if gestione_corsi_sede else None, ('Catalogo Corsi', 'fa-list-alt', '/page/catalogo-corsi/'), ('Glossario Corsi', 'fa-book', '/page/glossario-corsi/'), - ('Albo Informatizzato', 'fa-list', reverse('formazione:albo_info')), + ('Albo Informatizzato', 'fa-list', reverse('formazione:albo_info')) if to_show(me, GESTIONE_CORSI_SEDE) else None, )), ) From eb3b0630e9eb88c7b9019266f09d7abd34f78fa9 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Mon, 20 May 2019 12:26:54 +0200 Subject: [PATCH 267/291] FIXED: (formazione) base_geo_localizzatore.html redirect sulla pagina seleziona direttore --- base/templates/base_geo_localizzatore.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/templates/base_geo_localizzatore.html b/base/templates/base_geo_localizzatore.html index 54bb0821d..36019a7ca 100644 --- a/base/templates/base_geo_localizzatore.html +++ b/base/templates/base_geo_localizzatore.html @@ -75,7 +75,7 @@

    Cerca un indirizzo

    if (is_corsobase) { var course_has_locazione = "{{ oggetto.locazione }}"; if (course_has_locazione != 'None' && parent.location.href.indexOf('norefresh') == -1) { - parent.location.href = "{% url 'aspirante:position_change' pk=oggetto.pk %}?norefresh"; + parent.location.href = "{% url 'formazione:director' pk=oggetto.pk %}?norefresh"; } } From 770a2014660ab3ee4ff845c0eae6d67983ca235b Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Mon, 20 May 2019 14:05:49 +0200 Subject: [PATCH 268/291] FIXED: (formazione) spostato elenco corsi in formazione.html --- formazione/menus.py | 4 +- formazione/templates/formazione.html | 86 ++++++++++++++++++++++------ formazione/viste.py | 1 + 3 files changed, 72 insertions(+), 19 deletions(-) diff --git a/formazione/menus.py b/formazione/menus.py index 9bc4d884b..51f2849cb 100644 --- a/formazione/menus.py +++ b/formazione/menus.py @@ -4,7 +4,7 @@ def to_show(me, permission): - if me.ha_permesso(permission): + if me and me.ha_permesso(permission): return True return False @@ -13,7 +13,7 @@ def formazione_menu(menu_name, gestione_corsi_sede, me=None): FORMAZIONE = ( ("Corsi", ( ("Attiva Corso", "fa-asterisk", reverse('formazione:new_course')) if gestione_corsi_sede else None, - ("Elenco Corsi", "fa-list", reverse('formazione:list_courses')), + # ("Elenco Corsi", "fa-list", reverse('formazione:list_courses')), ("Domanda formativa", "fa-area-chart", reverse('formazione:domanda')) if gestione_corsi_sede else None, ('Catalogo Corsi', 'fa-list-alt', '/page/catalogo-corsi/'), ('Glossario Corsi', 'fa-book', '/page/glossario-corsi/'), diff --git a/formazione/templates/formazione.html b/formazione/templates/formazione.html index f0e2a428c..f935baeac 100644 --- a/formazione/templates/formazione.html +++ b/formazione/templates/formazione.html @@ -2,29 +2,20 @@ {% load bootstrap3 %} -{% block pagina_titolo %} - Formazione -{% endblock %} +{% block pagina_titolo %}Formazione{% endblock %} {% block app_contenuto %} - -

    - Formazione -

    - -

    - Emblema CRI -

    - +

    Formazione

    +

    Emblema CRI

    Benvenuto nella sezione dedicata alla Formazione CRI.

    -

    Usa il menù sulla destra che ti permetterà di accedere alle funzioni - relative alla gestione dei Corsi Base e dei Corsi di Formazione CRI.

    +

    Usa il menù sulla destra che ti permetterà di accedere alle funzioni relative alla gestione dei Corsi Base e dei Corsi di Formazione CRI.

    -
    + {% if corsi %} +

    {{ corsi.count }}

    Corsi Gestiti

    @@ -43,9 +34,70 @@

    {{ sedi.count }}

    {% endfor %}
    -
    + {% if puo_pianificare %} +
    +

    Vuoi pianificare un nuovo corso?

    +

    Se vuoi pianificare un nuovo corso, clicca su Attiva un Nuovo Corso.

    +

    Potrai assegnare un Direttore del Corso che si occuperà di organizzarne i particolari.

    +
    + {% endif %} + + + + + + + + + + {% for corso in corsi %} + + + + -{% endblock %} \ No newline at end of file + + + + {% empty %} + + + + {% endfor %} +
    StatoCorso e SedeLuogo e dataIscritti
    {{ corso.get_stato_display }} + {{ corso.link|safe }}
    + {{ corso.sede.link|safe }} + {% if puo_pianificare %} +
    {{ corso.deleghe.count }} direttori + {% endif %} +
    + + {% if corso.locazione %} + {{ corso.locazione }} + {% else %} + (Nessun indirizzo specificato) + {% endif %} +
    + + Inizia: {{ corso.data_inizio }} +
    + + Esami: {{ corso.data_esame }} +
    + {{ corso.partecipazioni_confermate_o_in_attesa.count }} richieste +
    + + {{ corso.partecipazioni_confermate.count }} confermate
    + {{ corso.partecipazioni_in_attesa.count }} in attesa
    + {{ corso.partecipazioni_negate.count }} neg./rit.
    +
    +
    +

    Ancora nessun corso pianificato.

    +

    Puoi controllare la domanda formativa della zona e valutare l'attivazione di un nuovo corso.

    +
    + {% endif %} + +{% endblock %} diff --git a/formazione/viste.py b/formazione/viste.py index 484385926..3a6c822c8 100644 --- a/formazione/viste.py +++ b/formazione/viste.py @@ -36,6 +36,7 @@ def formazione(request, me): context = { "sedi": me.oggetti_permesso(GESTIONE_CORSI_SEDE), "corsi": me.oggetti_permesso(GESTIONE_CORSO), + "puo_pianificare": me.ha_permesso(GESTIONE_CORSI_SEDE), } return 'formazione.html', context From 03da6b9d4eb2b51bed67bb7fe221708345dc4d07 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Mon, 20 May 2019 15:04:39 +0200 Subject: [PATCH 269/291] ADD: (formazione) filtro corsi by stato (formazione.html) --- formazione/templates/formazione.html | 33 +++++++++++-------- .../templatetags/formazione_templatetags.py | 11 +++++++ formazione/viste.py | 15 ++++++++- 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/formazione/templates/formazione.html b/formazione/templates/formazione.html index f935baeac..7aad13592 100644 --- a/formazione/templates/formazione.html +++ b/formazione/templates/formazione.html @@ -1,10 +1,17 @@ {% extends 'formazione_vuota.html' %} {% load bootstrap3 %} +{% load formazione_templatetags %} {% block pagina_titolo %}Formazione{% endblock %} {% block app_contenuto %} + +

    Formazione

    Emblema CRI

    @@ -36,20 +43,12 @@

    {{ sedi.count }}

    - {% if puo_pianificare %} -
    -

    Vuoi pianificare un nuovo corso?

    -

    Se vuoi pianificare un nuovo corso, clicca su Attiva un Nuovo Corso.

    -

    Potrai assegnare un Direttore del Corso che si occuperà di organizzarne i particolari.

    -
    - {% endif %} - - +
    - - - - + + + + {% for corso in corsi %} @@ -100,4 +99,12 @@

    Ancora nessun corso pianificato.

    {% endif %} + {% if puo_pianificare %} +
    +

    Vuoi pianificare un nuovo corso?

    +

    Se vuoi pianificare un nuovo corso, clicca su Attiva un Nuovo Corso.

    +

    Potrai assegnare un Direttore del Corso che si occuperà di organizzarne i particolari.

    +
    + {% endif %} + {% endblock %} diff --git a/formazione/templatetags/formazione_templatetags.py b/formazione/templatetags/formazione_templatetags.py index 0a6288c52..563b9ddd2 100644 --- a/formazione/templatetags/formazione_templatetags.py +++ b/formazione/templatetags/formazione_templatetags.py @@ -1,4 +1,5 @@ from django import template +from django.utils.safestring import mark_safe from base.utils import oggi @@ -18,6 +19,7 @@ def titoli_del_corso(persona, cd): 'num_of_titles': init_query.count() } + @register.simple_tag def lezione_esonero(lezione, partecipante): from ..models import AssenzaCorsoBase @@ -28,6 +30,15 @@ def lezione_esonero(lezione, partecipante): except AssenzaCorsoBase.DoesNotExist: return None + @register.simple_tag def lezione_partecipante_pk_shortcut(lezione, partecipante): return "%s-%s" % (lezione.pk, partecipante.pk) + + +@register.simple_tag +def corsi_filter(): + from ..models import Corso + + href = """%s""" + return mark_safe(' '.join([href % (i[0], i[1]) for i in Corso.STATO])) diff --git a/formazione/viste.py b/formazione/viste.py index 3a6c822c8..a37b38bac 100644 --- a/formazione/viste.py +++ b/formazione/viste.py @@ -33,9 +33,22 @@ @pagina_privata def formazione(request, me): + corsi = me.oggetti_permesso(GESTIONE_CORSO) + + # Filtra corsi by stato + if request.GET.get('stato'): + stato = request.GET.get('stato') + # Verifica che modello ha lo stato impostato in get-request + if stato in [i[0] for i in Corso.STATO]: + filtered = corsi.filter(stato=stato) + if not filtered.exists(): # queryset vuoto + # Rindirizza sulla pagina con tutti i corsi disponibili + return redirect('formazione:index') + corsi = filtered + context = { + "corsi": corsi, "sedi": me.oggetti_permesso(GESTIONE_CORSI_SEDE), - "corsi": me.oggetti_permesso(GESTIONE_CORSO), "puo_pianificare": me.ha_permesso(GESTIONE_CORSI_SEDE), } return 'formazione.html', context From 4d43140c6d5dddb19c6abdda68596114a1e04378 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Tue, 21 May 2019 09:48:25 +0200 Subject: [PATCH 270/291] FIXED: imports in base.admin --- base/admin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/base/admin.py b/base/admin.py index b1d606c4f..b3065954e 100755 --- a/base/admin.py +++ b/base/admin.py @@ -1,15 +1,16 @@ from django.contrib import admin from django.contrib.contenttypes.admin import GenericTabularInline -from base.geo import Locazione -from base.models import Autorizzazione, Token, Allegato, Menu from gruppi.readonly_admin import ReadonlyAdminMixin +from .geo import Locazione +from .models import Autorizzazione, Token, Allegato, Menu @admin.register(Token) class TokenAdmin(ReadonlyAdminMixin, admin.ModelAdmin): pass + @admin.register(Menu) class MenuAdmin(ReadonlyAdminMixin, admin.ModelAdmin): list_display = ['url', 'order', 'is_active', 'name',] From 2af5323d598a29b81f5a265c22afdc9d13621903 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Tue, 21 May 2019 15:13:59 +0200 Subject: [PATCH 271/291] FIXED: (GAIA-114) nomi dei obbiettivi strategici --- curriculum/areas.py | 6 +++--- .../migrations/0039_auto_20190521_1512.py | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 formazione/migrations/0039_auto_20190521_1512.py diff --git a/curriculum/areas.py b/curriculum/areas.py index 1036574a7..3e443a57b 100644 --- a/curriculum/areas.py +++ b/curriculum/areas.py @@ -27,11 +27,11 @@ OBBIETTIVI_STRATEGICI = [ (OBBIETTIVO_STRATEGICO_SALUTE, 'Salute'), - (OBBIETTIVO_STRATEGICO_SOCIALE, 'Sociale'), + (OBBIETTIVO_STRATEGICO_SOCIALE, 'Inclusione Sociale'), (OBBIETTIVO_STRATEGICO_EMERGENZA, 'Emergenza'), (OBBIETTIVO_STRATEGICO_ADVOCACY, 'Principi e Valori'), (OBBIETTIVO_STRATEGICO_GIOVANI, 'Giovani'), - (OBBIETTIVO_STRATEGICO_SVILUPPO, 'Sviluppo'), + (OBBIETTIVO_STRATEGICO_SVILUPPO, 'Sviluppo Organizzativo'), (OBBIETTIVO_STRATEGICO_MIGRAZIONI, 'Migrazioni'), - (OBBIETTIVO_STRATEGICO_COOPERAZIONI_INT, 'Cooperazioni Internazionali'), + (OBBIETTIVO_STRATEGICO_COOPERAZIONI_INT, 'Cooperazione Internazionale'), ] diff --git a/formazione/migrations/0039_auto_20190521_1512.py b/formazione/migrations/0039_auto_20190521_1512.py new file mode 100644 index 000000000..985222d3c --- /dev/null +++ b/formazione/migrations/0039_auto_20190521_1512.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2019-05-21 15:12 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('formazione', '0038_auto_20190516_1155'), + ] + + operations = [ + migrations.AlterField( + model_name='corsobase', + name='cdf_area', + field=models.CharField(blank=True, choices=[('1', 'Salute'), ('2', 'Inclusione Sociale'), ('3', 'Emergenza'), ('4', 'Principi e Valori'), ('5', 'Giovani'), ('6', 'Sviluppo Organizzativo'), ('7', 'Migrazioni'), ('8', 'Cooperazione Internazionale')], max_length=3, null=True), + ), + ] From 2072cb004aba18511b48df2d4fbd6fcac89a0103 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Tue, 21 May 2019 15:29:54 +0200 Subject: [PATCH 272/291] FIXED: (GAIA-115) togliere colonna nome dal report questionario --- survey/models.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/survey/models.py b/survey/models.py index f8646b425..83309cb62 100644 --- a/survey/models.py +++ b/survey/models.py @@ -121,13 +121,11 @@ def generate_report_for_course(cls, course): response['Content-Disposition'] = 'attachment; filename="%s"' % filename writer = csv.writer(response, delimiter=';') - writer.writerow(['Corso', 'Utente', 'Domanda', 'Risposta', 'Creato', - 'Modificato']) + writer.writerow(['Corso', 'Domanda', 'Risposta', 'Creato', 'Modificato']) for result in cls.get_responses_for_course(course): writer.writerow([ course.nome, - result.user, result.question, result.response, result.created_at, From d8de010dd27b63720e0507c4c69abdfbb3116429 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Tue, 21 May 2019 17:06:12 +0200 Subject: [PATCH 273/291] FIXED: (GAIA-112) possibilita' di nominare direttore fuori propria sede appartenenza --- anagrafica/viste.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/anagrafica/viste.py b/anagrafica/viste.py index 4940be58b..1bab4f95f 100755 --- a/anagrafica/viste.py +++ b/anagrafica/viste.py @@ -1073,8 +1073,8 @@ def strumenti_delegati(request, me): } form = ModuloCreazioneDelega(request.POST or None, **form_data) if model == 'corsobase': - if oggetto.is_nuovo_corso: - form = FormCreateDirettoreDelega(request.POST or None, **form_data) + # if oggetto.is_nuovo_corso: + form = FormCreateDirettoreDelega(request.POST or None, **form_data) # Check form is valid if form.is_valid(): From 9e9476ea4815aa07bac403a05021fbdb51a350c4 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Tue, 21 May 2019 17:08:37 +0200 Subject: [PATCH 274/291] FIXED: (GAIA-108) visualizza msg errore se non puoi invitare persona al corso + altre piccole modifiche --- formazione/admin.py | 1 + formazione/templates/aspirante_corso_base_scheda.html | 2 +- .../aspirante_corso_base_scheda_iscritti_aggiungi.html | 3 +++ formazione/templates/formazione_corsi_base_direttori.html | 3 +++ 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/formazione/admin.py b/formazione/admin.py index 728536865..cb347daf4 100755 --- a/formazione/admin.py +++ b/formazione/admin.py @@ -116,6 +116,7 @@ class AdminPartecipazioneCorsoBase(ReadonlyAdminMixin, admin.ModelAdmin): list_filter = ['confermata',] raw_id_fields = RAW_ID_FIELDS_PARTECIPAZIONECORSOBASE inlines = [InlineAutorizzazione] + ordering = ['-creazione',] @admin.register(LezioneCorsoBase) diff --git a/formazione/templates/aspirante_corso_base_scheda.html b/formazione/templates/aspirante_corso_base_scheda.html index b5ae05069..a108cfa6a 100644 --- a/formazione/templates/aspirante_corso_base_scheda.html +++ b/formazione/templates/aspirante_corso_base_scheda.html @@ -37,7 +37,7 @@

    {% if today_ts >= corso.data_esame|date:"U" %}
  • Documentazione Esame
  • {% endif %} {% endif %} - {% if corso.concluso %} + {% if corso.is_nuovo_corso and corso.concluso %}
  • Questionario
  • {% endif %} diff --git a/formazione/templates/aspirante_corso_base_scheda_iscritti_aggiungi.html b/formazione/templates/aspirante_corso_base_scheda_iscritti_aggiungi.html index f5be5d6e5..4372ff752 100644 --- a/formazione/templates/aspirante_corso_base_scheda_iscritti_aggiungi.html +++ b/formazione/templates/aspirante_corso_base_scheda_iscritti_aggiungi.html @@ -82,6 +82,9 @@

    {% elif r.esito == corso.NON_PUOI_ISCRIVERTI_GIA_ISCRITTO_ALTRO_CORSO %} Già iscritt{{ r.persona.genere_o_a }} a un altro corso + {% elif r.esito == corso.NON_HAI_DOCUMENTO_PERSONALE_VALIDO %} + Questo utente non ha un documento di riconoscimento valido/rinnovato + {% endif %} {% endif %} diff --git a/formazione/templates/formazione_corsi_base_direttori.html b/formazione/templates/formazione_corsi_base_direttori.html index 22d4f16ac..0601a5d8b 100644 --- a/formazione/templates/formazione_corsi_base_direttori.html +++ b/formazione/templates/formazione_corsi_base_direttori.html @@ -22,5 +22,8 @@

    Chi è il direttore?

    {# template-tag "delegati" si trova nell'app "anagrafica" #} + {# la view che gestisce selezione direttore: anagrafica.strumenti_delegati #} + {% delegati delega corso continua_url=continua_url almeno=1 %} + {% endblock %} From 8f145d74da9bd5588a62c1d601f48e20a8d7cb8b Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Thu, 23 May 2019 11:48:29 +0200 Subject: [PATCH 275/291] =?UTF-8?q?FIXED:=20(GAIA-108)=20invio=20posta=20a?= =?UTF-8?q?vviso=20agli=20utenti=20che=20un=20direttore=20non=20pu=C3=B2?= =?UTF-8?q?=20iscrivere=20a=20un=20corso?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...e_corso_base_scheda_iscritti_aggiungi.html | 3 ++ formazione/viste.py | 50 +++++++++++++++++-- ...orso_iscritti_aggiungi_esito_negativo.html | 14 ++++++ 3 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 posta/templates/email_corso_iscritti_aggiungi_esito_negativo.html diff --git a/formazione/templates/aspirante_corso_base_scheda_iscritti_aggiungi.html b/formazione/templates/aspirante_corso_base_scheda_iscritti_aggiungi.html index 4372ff752..e8ac38121 100644 --- a/formazione/templates/aspirante_corso_base_scheda_iscritti_aggiungi.html +++ b/formazione/templates/aspirante_corso_base_scheda_iscritti_aggiungi.html @@ -85,6 +85,9 @@

    {% elif r.esito == corso.NON_HAI_DOCUMENTO_PERSONALE_VALIDO %} Questo utente non ha un documento di riconoscimento valido/rinnovato + {% elif r.esito == corso.NON_HAI_CARICATO_DOCUMENTI_PERSONALI %} + Questo utente non ha caricato un documento d'identità + {% endif %} {% endif %} diff --git a/formazione/viste.py b/formazione/viste.py index a37b38bac..e63c4b7ac 100644 --- a/formazione/viste.py +++ b/formazione/viste.py @@ -1,4 +1,4 @@ -from datetime import datetime, timedelta +from datetime import datetime, timedelta, date from django.db.models import Q from django.utils import timezone @@ -688,11 +688,25 @@ def aspirante_corso_base_iscritti_aggiungi(request, me, pk): torna_url=corso.url_iscritti ) - risultati = [] + risultati = list() + persone_con_esito_negativo = dict() + form = ModuloIscrittiCorsoBaseAggiungi(request.POST or None, corso=corso) if form.is_valid(): for persona in form.cleaned_data['persone']: + # esito_negative_message = None esito = corso.persona(persona) + if esito in [CorsoBase.NON_HAI_CARICATO_DOCUMENTI_PERSONALI, CorsoBase.NON_HAI_DOCUMENTO_PERSONALE_VALIDO]: + persone_con_esito_negativo[persona] = esito + + # esito_negative_message = "Questo utente non ha caricato un documento d'identità" + # elif esito == CorsoBase.NON_HAI_DOCUMENTO_PERSONALE_VALIDO: + # esito_negative_message = "Questo utente non ha un documento di riconoscimento valido/rinnovato" + + # # Persone da avvisare via posta della problematica + # if esito_negative_message: + # persone_con_esito_negativo[persona] = esito #, esito_negative_message + ok = PartecipazioneCorsoBase.NON_ISCRITTO partecipazione = None @@ -739,6 +753,31 @@ def aspirante_corso_base_iscritti_aggiungi(request, me, pk): "ok": ok, }] + # Invia avvisi agli utenti con esito negativo + for persona, esito in persone_con_esito_negativo.items(): + already_sent_today = Messaggio.objects.filter( + mittente=me, + oggetti_destinatario__persona=persona, + oggetto__istartswith="Impossibilità di iscriversi a", + creazione__date=date.today(), + ).count() + + # Per evitare che invii lo stesso messaggio più di una volta al di + if not already_sent_today: + posta = Messaggio.costruisci_e_accoda( + oggetto="Impossibilità di iscriversi a %s" % corso.nome, + modello="email_corso_iscritti_aggiungi_esito_negativo.html", + corpo={ + 'corso': corso, + 'esito': esito, + }, + mittente=me, + destinatari=[persona]) + else: + if persone_con_esito_negativo: + utente = 'utenti' if len(persone_con_esito_negativo) > 1 else 'utente' + messages.error(request, "Abbiamo avvisato %s della problematica." % utente) + context = { "corso": corso, "puo_modificare": True, @@ -1067,9 +1106,10 @@ def formazione_corso_position_change(request, me, pk): # Rindirizza sulla pagina selezione direttori del corso. return redirect(course.url_direttori) - return 'formazione_corso_position_change.html', {'corso': course, - 'template': template, - 'puo_modificare': puo_modificare} + context = {'corso': course, + 'template': template, + 'puo_modificare': puo_modificare,} + return 'formazione_corso_position_change.html', context @pagina_privata diff --git a/posta/templates/email_corso_iscritti_aggiungi_esito_negativo.html b/posta/templates/email_corso_iscritti_aggiungi_esito_negativo.html new file mode 100644 index 000000000..63969b51e --- /dev/null +++ b/posta/templates/email_corso_iscritti_aggiungi_esito_negativo.html @@ -0,0 +1,14 @@ +{% extends 'email.html' %} + +{% block corpo %} +

    Ciao, perché il Direttore del {{ corso.nome }} ti inviti è necessario che + {% if esito == corso.NON_HAI_CARICATO_DOCUMENTI_PERSONALI %} carichi + {% elif esito == corso.NON_HAI_DOCUMENTO_PERSONALE_VALIDO %} aggiorni il tuo profilo allegando + {% endif %} + un documento di identità in corso di validità, proseguendo su questo link. +

    + +
    + +

    Clicca qui per aprire la pagina del Corso con maggiori informazioni.

    +{% endblock %} From fe8d96df4181c1828928559c9079fac51e07fe5d Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Thu, 23 May 2019 14:17:37 +0200 Subject: [PATCH 276/291] FIXED: (GAIA-107) Mail di discrizione corso al direttore --- formazione/models.py | 11 ++++++----- formazione/viste.py | 13 +++++++++++++ .../email_corso_utente_ritirato_iscrizione.html | 5 +++++ 3 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 posta/templates/email_corso_utente_ritirato_iscrizione.html diff --git a/formazione/models.py b/formazione/models.py index 2a3ca93a8..7e6174b37 100755 --- a/formazione/models.py +++ b/formazione/models.py @@ -748,11 +748,12 @@ def inform_presidency_with_delibera_file(self): def direttori_corso(self): oggetto_tipo = ContentType.objects.get_for_model(self) - query = Delega.objects.filter(tipo=DIRETTORE_CORSO, - oggetto_tipo=oggetto_tipo.pk, - oggetto_id=self.pk) - persone = Persona.objects.filter(id__in=query.values_list('id', flat=True)) - return persone + deleghe = Delega.objects.filter(tipo=DIRETTORE_CORSO, + oggetto_tipo=oggetto_tipo.pk, + oggetto_id=self.pk) + deleghe_persone_id = deleghe.values_list('persona__id', flat=True) + persone_qs = Persona.objects.filter(id__in=deleghe_persone_id) + return persone_qs def can_modify(self, me): if me and me.permessi_almeno(self, MODIFICA): diff --git a/formazione/viste.py b/formazione/viste.py index e63c4b7ac..0f9fddf4d 100644 --- a/formazione/viste.py +++ b/formazione/viste.py @@ -262,6 +262,19 @@ def aspirante_corso_base_ritirati(request, me=None, pk=None): partecipazione.confermata = False partecipazione.save() # second save() call + # Informa direttore corso + posta = Messaggio.costruisci_e_accoda( + oggetto="Ritiro richiesta di iscrizione a %s da %s" % (corso.nome, partecipazione.persona), + modello="email_corso_utente_ritirato_iscrizione.html", + corpo={ + 'corso': corso, + 'partecipante': partecipazione.persona, + }, + destinatari=corso.direttori_corso()) + + if posta: + messages.success(request, "Il direttore del corso è stato avvisato.") + return messaggio_generico(request, me, titolo="Ti sei ritirato dal corso", messaggio="Siamo spiacenti che hai deciso di ritirarti da questo corso. " "La tua partecipazione è stata ritirata correttamente. " diff --git a/posta/templates/email_corso_utente_ritirato_iscrizione.html b/posta/templates/email_corso_utente_ritirato_iscrizione.html new file mode 100644 index 000000000..2dc4d759e --- /dev/null +++ b/posta/templates/email_corso_utente_ritirato_iscrizione.html @@ -0,0 +1,5 @@ +{% extends 'email.html' %} + +{% block corpo %} +

    Buongiorno direttore, {{ partecipante }} ha ritirato la sua iscrizione dal Corso

    +{% endblock %} From b10a35187fb9f7af0369cfc0b51c4020feb9d50c Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Fri, 24 May 2019 10:07:28 +0200 Subject: [PATCH 277/291] FIXED: formazione.views.aspirante_corsi (aggiustare queryset dei corsi da mostrare al volontario) --- anagrafica/admin.py | 19 ++++-- formazione/models.py | 141 +++++++++++++++++++++++++++---------------- formazione/viste.py | 14 ++++- 3 files changed, 116 insertions(+), 58 deletions(-) diff --git a/anagrafica/admin.py b/anagrafica/admin.py index 8f69ac17d..ed384bbb4 100755 --- a/anagrafica/admin.py +++ b/anagrafica/admin.py @@ -15,9 +15,10 @@ from autenticazione.models import Utenza from base.admin import InlineAutorizzazione from gruppi.readonly_admin import ReadonlyAdminMixin -from .models import Persona, Sede, Appartenenza, Delega, Documento,\ - Fototessera, Estensione, Trasferimento, Riserva, Dimissione, Telefono, \ - ProvvedimentoDisciplinare +from formazione.models import PartecipazioneCorsoBase +from .models import (Persona, Sede, Appartenenza, Delega, Documento, + Fototessera, Estensione, Trasferimento, Riserva, Dimissione, Telefono, + ProvvedimentoDisciplinare) @@ -72,6 +73,14 @@ class InlineTelefonoPersona(ReadonlyAdminMixin, admin.StackedInline): extra = 0 +class InlinePartecipazioneCorsoBase(ReadonlyAdminMixin, admin.TabularInline): + from formazione.admin import RAW_ID_FIELDS_PARTECIPAZIONECORSOBASE + + model = PartecipazioneCorsoBase + raw_id_fields = RAW_ID_FIELDS_PARTECIPAZIONECORSOBASE + extra = 0 + + @admin.register(Persona) class AdminPersona(ReadonlyAdminMixin, admin.ModelAdmin): search_fields = ['nome', 'cognome', 'codice_fiscale', 'utenza__email', 'email_contatto', '=id',] @@ -79,7 +88,9 @@ class AdminPersona(ReadonlyAdminMixin, admin.ModelAdmin): 'ultima_modifica', ) list_filter = ('stato', ) list_display_links = ('nome', 'cognome', 'codice_fiscale',) - inlines = [InlineUtenzaPersona, InlineAppartenenzaPersona, InlineDelegaPersona, InlineDocumentoPersona, InlineTelefonoPersona] + inlines = [InlineUtenzaPersona, InlineAppartenenzaPersona, + InlineDelegaPersona, InlineDocumentoPersona, + InlinePartecipazioneCorsoBase, InlineTelefonoPersona,] actions = ['sposta_persone',] messaggio_spostamento = ungettext_lazy( diff --git a/formazione/models.py b/formazione/models.py index 7e6174b37..3a8a7886c 100755 --- a/formazione/models.py +++ b/formazione/models.py @@ -187,13 +187,13 @@ def persona(self, persona): # return self.NON_PUOI_ISCRIVERTI_GIA_ISCRITTO_ALTRO_CORSO # Controlla se già iscritto. + if PartecipazioneCorsoBase.con_esito_pending(persona=persona, corso=self).exists(): + return self.SEI_ISCRITTO_PUOI_RITIRARTI + if PartecipazioneCorsoBase.con_esito_ok(persona=persona, corso=self).exists(): # UPDATE: (GAIA-93) utente può ritirarsi dal corso in qualsiasi momento. return self.SEI_ISCRITTO_CONFERMATO_PUOI_RITIRARTI - if PartecipazioneCorsoBase.con_esito_pending(persona=persona, corso=self).exists(): - return self.SEI_ISCRITTO_PUOI_RITIRARTI - if self.troppo_tardi_per_iscriverti: return self.NON_PUOI_ISCRIVERTI_TROPPO_TARDI @@ -237,42 +237,90 @@ def pubblici(cls): @classmethod def find_courses_for_volunteer(cls, volunteer): + today = datetime.date.today() sede = volunteer.sedi_attuali(membro=Appartenenza.VOLONTARIO) - if sede: - sede = sede.last() - else: - return cls.objects.none() - - 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('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 volunteer.has_required_titles_for_course(course): - 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) + if not sede: + return cls.objects.none() # corsi non trovati perchè utente non ha sede + + ### + # Trova corsi che hanno uguale alla + ### + qs_estensioni_1 = CorsoEstensione.objects.filter(sede__in=sede, + corso__tipo=Corso.CORSO_NUOVO, + corso__stato=Corso.ATTIVO, + corso__data_inizio__gt=today) + courses_1 = cls.objects.filter(id__in=qs_estensioni_1.values_list('corso__id')) + + ### + # Trova corsi dove la si verifica come sede sottostante + ### + four_weeks_delta = today + datetime.timedelta(weeks=4) + qs_estensioni_2 = CorsoEstensione.objects.filter( + corso__tipo=Corso.CORSO_NUOVO, + corso__stato=Corso.ATTIVO, + corso__data_inizio__gt=today, + corso__data_esame__lt=four_weeks_delta).exclude( + corso__id__in=courses_1.values_list('id', flat=True)) + + courses_2 = list() + for estensione in qs_estensioni_2: + for s in estensione.sede.all(): + sedi_sottostanti = s.esplora() + for _ in sede: + if _ in sedi_sottostanti: + _corso = estensione.corso + if _corso not in courses_2: + courses_2.append(_corso.pk) + + courses_2 = cls.objects.filter(id__in=courses_2) + + return courses_1 | courses_2 + + # @classmethod + # def find_courses_for_volunteer(cls, volunteer): + # """ + # Questo metodo è stato commentato perchè nel task GAIA-97 è stato + # chiesto di togliere i requisiti necessari per partecipare ai corsi + # (quindi anche per rendere i corsi trovabili/visualizzabili in ricerca). + # Il metodo sostituitivo è descritto sopra. + # """ + # + # sede = volunteer.sedi_attuali(membro=Appartenenza.VOLONTARIO) + # if sede: + # sede = sede.last() + # else: + # return cls.objects.none() + # + # 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('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 volunteer.has_required_titles_for_course(course): + # 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): @@ -369,22 +417,13 @@ def nuovo(cls, anno=None, **kwargs): """ anno = anno or datetime.date.today().year - try: # Per il progressivo, cerca ultimo corso - ultimo = CorsoBase.objects.filter(anno=anno).latest('progressivo') + ultimo = cls.objects.filter(anno=anno).latest('progressivo') progressivo = ultimo.progressivo + 1 + except: + progressivo = 1 # Se non esiste, inizia da 1 - except: # Se non esiste, inizia da 1 - progressivo = 1 - - c = CorsoBase( - anno=anno, - progressivo=progressivo, - **kwargs - ) - # if c.tipo == Corso.CORSO_NUOVO and c.titolo_cri: - - + c = CorsoBase(anno=anno, progressivo=progressivo, **kwargs) c.save() return c diff --git a/formazione/viste.py b/formazione/viste.py index 0f9fddf4d..175b568ec 100644 --- a/formazione/viste.py +++ b/formazione/viste.py @@ -23,8 +23,8 @@ from survey.models import Survey from .elenchi import ElencoPartecipantiCorsiBase from .decorators import can_access_to_course -from .models import (Corso, CorsoBase, CorsoEstensione, AssenzaCorsoBase, - LezioneCorsoBase, PartecipazioneCorsoBase, Aspirante, InvitoCorsoBase, RelazioneCorso) +from .models import (Aspirante, Corso, CorsoBase, CorsoEstensione, LezioneCorsoBase, + PartecipazioneCorsoBase, InvitoCorsoBase, RelazioneCorso) from .forms import (ModuloCreazioneCorsoBase, ModuloModificaLezione, ModuloModificaCorsoBase, ModuloIscrittiCorsoBaseAggiungi, ModuloVerbaleAspiranteCorsoBase, FormRelazioneDelDirettoreCorso) @@ -862,7 +862,15 @@ def aspirante_corsi(request, me): if me.ha_aspirante: corsi = me.aspirante.corsi(tipo=Corso.BASE) elif me.volontario: - corsi = CorsoBase.find_courses_for_volunteer(volunteer=me) + # Trova corsi dove l'utente ha già partecipato + partecipazione = PartecipazioneCorsoBase.objects.filter(confermata=True, persona=me) + corsi_confermati = CorsoBase.objects.filter(id__in=partecipazione.values_list('corso', flat=True)) + + # Trova corsi da partecipare + corsi_da_partecipare = CorsoBase.find_courses_for_volunteer(volunteer=me) + + # Unisci 2 categorie di corsi + corsi = corsi_confermati | corsi_da_partecipare context = { 'corsi': corsi From 97a628cbc1c89b357979aa37ea379035a8da7c84 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Fri, 24 May 2019 10:29:50 +0200 Subject: [PATCH 278/291] FIXED: (GAIA-110) genera pdf attestati - il codice spostato in base.classes.pdf --- base/classes/__init__.py | 0 base/classes/pdf.py | 62 ++++++++++++++++++++++++++++++++++++++++ base/viste.py | 49 ++++--------------------------- 3 files changed, 68 insertions(+), 43 deletions(-) create mode 100644 base/classes/__init__.py create mode 100644 base/classes/pdf.py diff --git a/base/classes/__init__.py b/base/classes/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/base/classes/pdf.py b/base/classes/pdf.py new file mode 100644 index 000000000..4a86678a7 --- /dev/null +++ b/base/classes/pdf.py @@ -0,0 +1,62 @@ +import os + +from django.apps import apps +from django.http import HttpResponse +from django.shortcuts import redirect + +from anagrafica.permessi.costanti import ERRORE_PERMESSI, LETTURA +from base.tratti import ConPDF +from base.errori import errore_generico + + +class BaseGeneraPDF: + def __init__(self, request, me, app_label, model, pk): + self.request = request + self.me = me + self.app_label = app_label + self.model = model + self.pk = pk + + def make(self): + oggetto = apps.get_model(self.app_label, self.model).objects.get(pk=self.pk) + + if not isinstance(oggetto, ConPDF): + return errore_generico(self.request, None, + messaggio="Impossibile generare un PDF per il tipo specificato.") + + if 'token' in self.request.GET: + if not oggetto.token_valida(self.request.GET['token']): + return errore_generico(self.request, self.me, + titolo="Token scaduta", + messaggio="Il link usato è scaduto.") + + elif not self.me.permessi_almeno(oggetto, LETTURA): + return redirect(ERRORE_PERMESSI) + + pdf = oggetto.genera_pdf() + + # Se sto scaricando un tesserino, forza lo scaricamento. + if 'tesserini' in pdf.file.path: + return self.pdf_forza_scaricamento(pdf) + + return redirect(pdf.download_url) + + def pdf_forza_scaricamento(self, pdf): + """ + Forza lo scaricamento di un file pdf. + Da usare con cautela, perche' carica il file in memoria + e blocca il thread fino al completamento della richiesta. + :param request: + :param pdf: + :return: + """ + + percorso_completo = pdf.file.path + + with open(percorso_completo, 'rb') as f: + data = f.read() + + response = HttpResponse(data, content_type=mimetypes.guess_type(percorso_completo)[0]) + response['Content-Disposition'] = "attachment; filename={0}".format(pdf.nome) + response['Content-Length'] = os.path.getsize(percorso_completo) + return response diff --git a/base/viste.py b/base/viste.py index ce2d1a362..758e8b9ad 100755 --- a/base/viste.py +++ b/base/viste.py @@ -24,7 +24,7 @@ from anagrafica.costanti import LOCALE, PROVINCIALE, REGIONALE from anagrafica.models import Sede, Persona from anagrafica.permessi.applicazioni import PRESIDENTE, UFFICIO_SOCI, UFFICIO_SOCI_TEMPORANEO, UFFICIO_SOCI_UNITA -from anagrafica.permessi.costanti import ERRORE_PERMESSI, LETTURA, GESTIONE_SEDE +from anagrafica.permessi.costanti import ERRORE_PERMESSI, GESTIONE_SEDE from autenticazione.funzioni import pagina_pubblica, pagina_anonima, pagina_privata from autenticazione.models import Utenza from base import errori @@ -33,7 +33,7 @@ from base.forms_extra import ModuloRichiestaSupportoPersone from base.geo import Locazione from base.models import Autorizzazione, Token -from base.tratti import ConPDF + from base.utils import get_drive_file, rimuovi_scelte from formazione.models import PartecipazioneCorsoBase, Aspirante from jorvik import settings @@ -538,50 +538,13 @@ def geo_localizzatore_imposta(request, me): return redirect("/geo/localizzatore/") + @pagina_privata def pdf(request, me, app_label, model, pk): - oggetto = apps.get_model(app_label, model) - oggetto = oggetto.objects.get(pk=pk) - if not isinstance(oggetto, ConPDF): - return errore_generico(request, None, - messaggio="Impossibile generare un PDF per il tipo specificato.") - - if 'token' in request.GET: - if not oggetto.token_valida(request.GET['token']): - return errore_generico(request, me, titolo="Token scaduta", - messaggio="Il link usato è scaduto.") - - elif not me.permessi_almeno(oggetto, LETTURA): - return redirect(ERRORE_PERMESSI) - - pdf = oggetto.genera_pdf() - - # Se sto scaricando un tesserino, forza lo scaricamento. - if 'tesserini' in pdf.file.path: - return pdf_forza_scaricamento(request, pdf) - - return redirect(pdf.download_url) - - -def pdf_forza_scaricamento(request, pdf): - """ - Forza lo scaricamento di un file pdf. - Da usare con cautela, perche' carica il file in memoria - e blocca il thread fino al completamento della richiesta. - :param request: - :param pdf: - :return: - """ - - percorso_completo = pdf.file.path - - with open(percorso_completo, 'rb') as f: - data = f.read() + from .classes.pdf import BaseGeneraPDF - response = HttpResponse(data, content_type=mimetypes.guess_type(percorso_completo)[0]) - response['Content-Disposition'] = "attachment; filename={0}".format(pdf.nome) - response['Content-Length'] = os.path.getsize(percorso_completo) - return response + pdf = BaseGeneraPDF(request, me, app_label, model, pk) + return pdf.make() @pagina_pubblica From a168530c12d6fb4ab39e30aa5165e6b27c58bd81 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Fri, 24 May 2019 10:34:19 +0200 Subject: [PATCH 279/291] FIXED: base.viste - sistemati imports (no modifiche alla logica) --- base/viste.py | 50 +++++++++++++++++++++++--------------------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/base/viste.py b/base/viste.py index 758e8b9ad..8f56db0e3 100755 --- a/base/viste.py +++ b/base/viste.py @@ -1,8 +1,7 @@ -import mimetypes +import json from datetime import date, timedelta, datetime, time -import os - +from django.apps import apps from django.conf import settings as django_settings from django.contrib.auth import get_user_model, load_backend, login from django.contrib.auth.tokens import default_token_generator @@ -13,32 +12,34 @@ from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import render, render_to_response, get_object_or_404, redirect from django.template.response import TemplateResponse -from django.utils import timezone from django.utils.encoding import force_bytes, force_text from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode -# Le viste base vanno qui. -from django.views.decorators.cache import cache_page -from django.apps import apps from django.views.decorators.clickjacking import xframe_options_exempt +from jorvik import settings from anagrafica.costanti import LOCALE, PROVINCIALE, REGIONALE from anagrafica.models import Sede, Persona from anagrafica.permessi.applicazioni import PRESIDENTE, UFFICIO_SOCI, UFFICIO_SOCI_TEMPORANEO, UFFICIO_SOCI_UNITA from anagrafica.permessi.costanti import ERRORE_PERMESSI, GESTIONE_SEDE from autenticazione.funzioni import pagina_pubblica, pagina_anonima, pagina_privata from autenticazione.models import Utenza -from base import errori -from base.errori import errore_generico, messaggio_generico -from base.forms import ModuloRecuperaPassword, ModuloMotivoNegazione, ModuloLocalizzatore, ModuloLocalizzatoreItalia -from base.forms_extra import ModuloRichiestaSupportoPersone -from base.geo import Locazione -from base.models import Autorizzazione, Token - -from base.utils import get_drive_file, rimuovi_scelte from formazione.models import PartecipazioneCorsoBase, Aspirante -from jorvik import settings from posta.models import Messaggio -import json +from .errori import errore_generico, messaggio_generico +from .forms import ModuloRecuperaPassword, ModuloLocalizzatore, ModuloLocalizzatoreItalia +from .forms_extra import ModuloRichiestaSupportoPersone +from .geo import Locazione +from .models import Autorizzazione, Token +from .utils import get_drive_file, rimuovi_scelte + + +IGNORA_AUTORIZZAZIONI = [ + # ContentType.objects.get_for_model(PartecipazioneCorsoBase).pk +] + +ORDINE_ASCENDENTE = 'creazione' +ORDINE_DISCENDENTE = '-creazione' +ORDINE_DEFAULT = ORDINE_DISCENDENTE @pagina_pubblica @@ -211,6 +212,7 @@ def informazioni(request, me): """ return 'base_informazioni.html' + @pagina_pubblica def informazioni_aggiornamenti(request, me): """ @@ -218,6 +220,7 @@ def informazioni_aggiornamenti(request, me): """ return 'base_informazioni_aggiornamenti.html' + @pagina_pubblica def informazioni_sicurezza(request, me): """ @@ -225,6 +228,7 @@ def informazioni_sicurezza(request, me): """ return 'base_informazioni_sicurezza.html' + @pagina_pubblica def informazioni_condizioni(request, me): """ @@ -232,6 +236,7 @@ def informazioni_condizioni(request, me): """ return 'base_informazioni_condizioni.html' + @pagina_pubblica def informazioni_cookie(request, me): """ @@ -239,6 +244,7 @@ def informazioni_cookie(request, me): """ return 'base_informazioni_cookie.html' + @pagina_pubblica def imposta_cookie(request, me): """ @@ -289,11 +295,6 @@ def informazioni_sede(request, me, slug): return 'base_informazioni_sede.html', contesto -IGNORA_AUTORIZZAZIONI = [ - # ContentType.objects.get_for_model(PartecipazioneCorsoBase).pk -] - - def pulisci_autorizzazioni(richieste): pulite = False for richiesta in richieste: @@ -303,11 +304,6 @@ def pulisci_autorizzazioni(richieste): return pulite -ORDINE_ASCENDENTE = 'creazione' -ORDINE_DISCENDENTE = '-creazione' -ORDINE_DEFAULT = ORDINE_DISCENDENTE - - @pagina_privata def autorizzazioni(request, me, content_type_pk=None): """ Mostra elenco delle autorizzazioni in attesa. """ From 41ba9d8a19079884fe173826b5986417218702d8 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Fri, 24 May 2019 17:05:25 +0200 Subject: [PATCH 280/291] NEW: curriculum migration 0014 (modifiche ai campi unit_reference, area) --- .../migrations/0014_auto_20190524_1251.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 curriculum/migrations/0014_auto_20190524_1251.py diff --git a/curriculum/migrations/0014_auto_20190524_1251.py b/curriculum/migrations/0014_auto_20190524_1251.py new file mode 100644 index 000000000..1b6c20bea --- /dev/null +++ b/curriculum/migrations/0014_auto_20190524_1251.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2019-05-24 12:51 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('curriculum', '0013_titolo_description'), + ] + + operations = [ + migrations.AlterField( + model_name='titlegoal', + name='unit_reference', + field=models.CharField(blank=True, choices=[('1', 'Salute'), ('2', 'Inclusione Sociale'), ('3', 'Emergenza'), ('4', 'Principi e Valori'), ('5', 'Giovani'), ('6', 'Sviluppo Organizzativo'), ('7', 'Migrazioni'), ('8', 'Cooperazione Internazionale')], max_length=3, null=True, verbose_name='Unità riferimento'), + ), + migrations.AlterField( + model_name='titolo', + name='area', + field=models.CharField(blank=True, choices=[('1', 'Salute'), ('2', 'Inclusione Sociale'), ('3', 'Emergenza'), ('4', 'Principi e Valori'), ('5', 'Giovani'), ('6', 'Sviluppo Organizzativo'), ('7', 'Migrazioni'), ('8', 'Cooperazione Internazionale')], db_index=True, max_length=5, null=True), + ), + ] From c5ff2203187d93d4cb0d1d6262dd5ff2435c963f Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Fri, 24 May 2019 17:08:58 +0200 Subject: [PATCH 281/291] FIXED: base/classes/pdf.py --- base/classes/pdf.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/base/classes/pdf.py b/base/classes/pdf.py index 4a86678a7..52a42e9d0 100644 --- a/base/classes/pdf.py +++ b/base/classes/pdf.py @@ -51,12 +51,14 @@ def pdf_forza_scaricamento(self, pdf): :return: """ + from mimetypes import guess_type + percorso_completo = pdf.file.path with open(percorso_completo, 'rb') as f: data = f.read() - response = HttpResponse(data, content_type=mimetypes.guess_type(percorso_completo)[0]) - response['Content-Disposition'] = "attachment; filename={0}".format(pdf.nome) + response = HttpResponse(data, content_type=guess_type(percorso_completo)[0]) + response['Content-Disposition'] = "attachment; filename=%s" % pdf.nome response['Content-Length'] = os.path.getsize(percorso_completo) return response From 3b79169c72283a2e5773b20e8ef06fd3107a5c7f Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Fri, 24 May 2019 17:10:17 +0200 Subject: [PATCH 282/291] NEW: (ref. GAIA-110) field in curriculum.TitoloPersonale (migration 0015); fixed admin.py --- curriculum/admin.py | 10 ++++++-- ...15_titolopersonale_corso_partecipazione.py | 23 +++++++++++++++++++ curriculum/models.py | 13 +++++++++-- 3 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 curriculum/migrations/0015_titolopersonale_corso_partecipazione.py diff --git a/curriculum/admin.py b/curriculum/admin.py index e5dc599d6..e3c3d5ed7 100755 --- a/curriculum/admin.py +++ b/curriculum/admin.py @@ -42,8 +42,14 @@ class AdminTitoloPersonale(ReadonlyAdminMixin, admin.ModelAdmin): search_fields = ["titolo__nome", "persona__nome", "persona__cognome", "persona__codice_fiscale",] list_display = ("titolo", "persona", 'is_course_title', - "data_ottenimento", 'data_scadenza', "certificato", "creazione",) + "data_ottenimento", 'data_scadenza', "certificato", + "creazione", 'corso_partecipazione__corso') list_filter = ("titolo__tipo", "creazione", "data_ottenimento", 'is_course_title') - raw_id_fields = ("persona", "certificato_da", "titolo",) + raw_id_fields = ("persona", "certificato_da", "titolo", 'corso_partecipazione',) inlines = [InlineAutorizzazione] + + def corso_partecipazione__corso(self, obj): + return obj.corso_partecipazione.corso \ + if hasattr(obj.corso_partecipazione, 'corso') else '' + corso_partecipazione__corso.short_description = 'Corso Partecipazione' diff --git a/curriculum/migrations/0015_titolopersonale_corso_partecipazione.py b/curriculum/migrations/0015_titolopersonale_corso_partecipazione.py new file mode 100644 index 000000000..b77f41ab4 --- /dev/null +++ b/curriculum/migrations/0015_titolopersonale_corso_partecipazione.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2019-05-24 14:11 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('formazione', '__first__'), + ('curriculum', '0014_auto_20190524_1251'), + ] + + operations = [ + migrations.AddField( + model_name='titolopersonale', + name='corso_partecipazione', + field=models.ForeignKey(blank=True, null=True, + on_delete=django.db.models.deletion.SET_NULL, related_name='titolo_ottenuto', to='formazione.PartecipazioneCorsoBase'), + ), + ] diff --git a/curriculum/models.py b/curriculum/models.py index 8d3379cb7..69572e09e 100755 --- a/curriculum/models.py +++ b/curriculum/models.py @@ -122,8 +122,7 @@ class TitoloPersonale(ModelloSemplice, ConMarcaTemporale, ConAutorizzazioni): codice = models.CharField(max_length=128, null=True, blank=True, db_index=True, help_text="Codice/Numero identificativo del Titolo o Patente. " "Presente sul certificato o sulla Patente.") - codice_corso = models.CharField(max_length=128, null=True, - blank=True, db_index=True) + codice_corso = models.CharField(max_length=128, null=True, blank=True, db_index=True) certificato = models.BooleanField(default=False,) certificato_da = models.ForeignKey("anagrafica.Persona", @@ -132,6 +131,16 @@ class TitoloPersonale(ModelloSemplice, ConMarcaTemporale, ConAutorizzazioni): on_delete=models.SET_NULL) is_course_title = models.BooleanField(default=False) + + # ValueError: Related model u'app.model' cannot be resolved + # https://stackoverflow.com/questions/33496333 + # curriculum.migrations.0015 was added: " ('formazione', '__first__')," + corso_partecipazione = models.ForeignKey('formazione.PartecipazioneCorsoBase', + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='titolo_ottenuto') + class Meta: verbose_name = "Titolo personale" verbose_name_plural = "Titoli personali" From ad0c3f00ceb6484544f7033cab1d587f450dc2dc Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Fri, 24 May 2019 17:11:41 +0200 Subject: [PATCH 283/291] NEW: formazione migration 0015 (modifiche ai choices del campo --- .../migrations/0040_auto_20190524_1411.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 formazione/migrations/0040_auto_20190524_1411.py diff --git a/formazione/migrations/0040_auto_20190524_1411.py b/formazione/migrations/0040_auto_20190524_1411.py new file mode 100644 index 000000000..e4e0a7837 --- /dev/null +++ b/formazione/migrations/0040_auto_20190524_1411.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2019-05-24 14:11 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('formazione', '0039_auto_20190521_1512'), + ] + + operations = [ + migrations.AlterField( + model_name='corsoestensione', + name='segmento', + field=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'), ('IC', 'Tutti i Commissari'), ('JC', 'Commissari di Comitati Locali'), ('KC', 'Commissari 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), + ), + ] From bc0365e19eb95a5e63ca8ccf3b465a2609864e69 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Fri, 24 May 2019 17:16:34 +0200 Subject: [PATCH 284/291] FIXED: ordine dei titoli in anagrafica.utente_curriculum --- anagrafica/viste.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/anagrafica/viste.py b/anagrafica/viste.py index 1bab4f95f..c5834ecc2 100755 --- a/anagrafica/viste.py +++ b/anagrafica/viste.py @@ -1196,10 +1196,9 @@ def utente_curriculum(request, me, tipo=None): tp.autorizzazione_richiedi_sede_riferimento( me, INCARICO_GESTIONE_TITOLI ) - return redirect("/utente/curriculum/%s/?inserimento=ok" % (tipo,)) - titoli = me.titoli_personali.all().filter(titolo__tipo=tipo) + titoli = me.titoli_personali.all().filter(titolo__tipo=tipo).order_by('-data_scadenza') contesto = { "tipo": tipo, From e23f5bb9f2653f082d17be7b5a2cd7fbf8dda2c4 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Fri, 24 May 2019 17:19:33 +0200 Subject: [PATCH 285/291] =?UTF-8?q?NEW:=20(GAIA-110)=20volontario=20pu?= =?UTF-8?q?=C3=B2=20scaricare=20attestato=20corso=20=20dal=20curriculum?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../anagrafica_utente_curriculum.html | 8 +- formazione/classes.py | 75 +++++++++++++++++++ formazione/models.py | 2 + .../templates/pdf_corso_base_attestato.html | 4 +- formazione/viste.py | 24 ++---- 5 files changed, 95 insertions(+), 18 deletions(-) diff --git a/anagrafica/templates/anagrafica_utente_curriculum.html b/anagrafica/templates/anagrafica_utente_curriculum.html index de44df125..f398bada7 100644 --- a/anagrafica/templates/anagrafica_utente_curriculum.html +++ b/anagrafica/templates/anagrafica_utente_curriculum.html @@ -100,7 +100,13 @@

    {% for t in titoli %}

    StatoCorso e SedeLuogo e dataIscrittiStato
    {% corsi_filter %}
    Corso e SedeLuogo e dataIscritti
    -

    {{ t.titolo.nome }}

    +

    + {{ t.titolo.nome }} + + {% if t.is_course_title and not t.is_expired_course_title %} +
    Scarica attestato + {% endif %} +

    {% if t.certificato %} diff --git a/formazione/classes.py b/formazione/classes.py index 752a0f679..55280bdd8 100644 --- a/formazione/classes.py +++ b/formazione/classes.py @@ -1,3 +1,8 @@ +from django.shortcuts import redirect, HttpResponse +from django.core.urlresolvers import reverse +from django.contrib import messages + +from base.files import Zip from .models import AssenzaCorsoBase @@ -65,3 +70,73 @@ def _verifica_presenze(self, partecipanti): if self.esonero: # Campo "Esonero" Valorizzato (impostata una motivazione) self._process_esonero() + + +class GeneraReport: + ATTESTATO_FILENAME = "%s - Attestato.pdf" + SCHEDA_FILENAME = "%s - Scheda di Valutazione.pdf" + + def __init__(self, request, corso, single_attestato=False): + self.request = request + self.corso = corso + + def download(self): + """ Returns a HTTP response """ + + self.archive = Zip(oggetto=self.corso) + + if self.request.GET.get('download_single_attestato'): + return self._download_single_attestato() + else: + self._generate() + self.archive.comprimi_e_salva(nome="Corso %d-%d.zip" % (self.corso.progressivo, + self.corso.anno)) + return redirect(self.archive.download_url) + + def _download_single_attestato(self): + from .models import PartecipazioneCorsoBase + from curriculum.models import Titolo + + try: + partecipazione = self.corso.partecipazioni_confermate().get( + titolo_ottenuto__pk=self.request.GET.get('download_single_attestato'), + persona=self.request.user.persona) + except PartecipazioneCorsoBase.DoesNotExist: + messages.error(self.request, "Questo attestato non esiste.") + return redirect(reverse('utente:cv_tipo', args=[Titolo.TITOLO_CRI,])) + + attestato = self._attestato(partecipazione) + filename = self.ATTESTATO_FILENAME % partecipazione.titolo_ottenuto.last() + + with open(attestato.file.path, 'rb') as f: + pdf = f.read() + + response = HttpResponse(pdf, content_type='application/pdf') + response['Content-Disposition'] = 'attachment; filename=%s' % '-'.join(filename.split()) + response.write(pdf) + return response + + def _generate(self): + for partecipante in self.corso.partecipazioni_confermate(): + self._schede(partecipante) + + if partecipante.idoneo: # Se idoneo, genera l'attestato + self._attestato(partecipante) + + def _schede(self, partecipante): + """ Genera la scheda di valutazione """ + + scheda = partecipante.genera_scheda_valutazione() + self.archive.aggiungi_file( + scheda.file.path, + self.SCHEDA_FILENAME % partecipante.persona.nome_completo + ) + return scheda + + def _attestato(self, partecipante): + attestato = partecipante.genera_attestato() + self.archive.aggiungi_file( + attestato.file.path, + self.ATTESTATO_FILENAME % partecipante.persona.nome_completo + ) + return attestato diff --git a/formazione/models.py b/formazione/models.py index 3a8a7886c..80fc6c7cf 100755 --- a/formazione/models.py +++ b/formazione/models.py @@ -669,6 +669,7 @@ def termina(self, mittente=None): def set_titolo_cri_to_participants(self): """ Sets in Persona's Curriculum (TitoloPersonale) """ + from curriculum.models import TitoloPersonale objs = [ @@ -679,6 +680,7 @@ def set_titolo_cri_to_participants(self): certificato_da=self.get_firmatario, data_scadenza=timezone.now() + self.titolo_cri.expires_after_timedelta, is_course_title=True, + corso_partecipazione=p, # todo: attending details # data_ottenimento='', diff --git a/formazione/templates/pdf_corso_base_attestato.html b/formazione/templates/pdf_corso_base_attestato.html index f3ab7d603..5a71db558 100644 --- a/formazione/templates/pdf_corso_base_attestato.html +++ b/formazione/templates/pdf_corso_base_attestato.html @@ -22,7 +22,9 @@

    Attestato di
    partecipazione

    {{ persona.nome_completo }}

    -

    Nome del corso {{ corso.titolo_cri }}

    + {% if corso.titolo_cri %} +

    Nome del corso {{ corso.titolo_cri }}

    + {% endif %}

    Data inizio {{ corso.data_inizio|date:"DATE_FORMAT" }}

    Data fine {{ corso.data_esame|date:"DATE_FORMAT" }}

    Sede di svolgimento {{ corso.sede.nome }}

    diff --git a/formazione/viste.py b/formazione/viste.py index 175b568ec..f94472e4c 100644 --- a/formazione/viste.py +++ b/formazione/viste.py @@ -16,7 +16,6 @@ from ufficio_soci.elenchi import ElencoPerTitoliCorso from autenticazione.funzioni import pagina_privata, pagina_pubblica from base.errori import errore_generico, messaggio_generico # ci_siamo_quasi -from base.files import Zip from base.models import Log from base.utils import poco_fa from posta.models import Messaggio @@ -28,7 +27,7 @@ from .forms import (ModuloCreazioneCorsoBase, ModuloModificaLezione, ModuloModificaCorsoBase, ModuloIscrittiCorsoBaseAggiungi, ModuloVerbaleAspiranteCorsoBase, FormRelazioneDelDirettoreCorso) -from .classes import GestionePresenza +from .classes import GestionePresenza, GeneraReport @pagina_privata @@ -826,23 +825,16 @@ def aspirante_corso_base_report(request, me, pk): @pagina_privata def aspirante_corso_base_report_schede(request, me, pk): corso = get_object_or_404(CorsoBase, pk=pk) - if not me.permessi_almeno(corso, MODIFICA): - return redirect(ERRORE_PERMESSI) - - archivio = Zip(oggetto=corso) - for p in corso.partecipazioni_confermate(): - # Genera la scheda di valutazione. - scheda = p.genera_scheda_valutazione() - archivio.aggiungi_file(scheda.file.path, "%s - Scheda di Valutazione.pdf" % p.persona.nome_completo) + can_download = False + if request.GET.get('download_single_attestato') and corso.partecipazioni_confermate().get(persona=me): + can_download = True - # Se idoneo, genera l'attestato. - if p.idoneo: - attestato = p.genera_attestato() - archivio.aggiungi_file(attestato.file.path, "%s - Attestato.pdf" % p.persona.nome_completo) + if not can_download and not me.permessi_almeno(corso, MODIFICA): + return redirect(ERRORE_PERMESSI) - archivio.comprimi_e_salva(nome="Corso %d-%d.zip" % (corso.progressivo, corso.anno)) - return redirect(archivio.download_url) + report = GeneraReport(request, corso) + return report.download() @pagina_privata From c68e0d0b34caeecbf920b71c26fafea0ff4cc695 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Mon, 27 May 2019 10:48:56 +0200 Subject: [PATCH 286/291] FIXED: formazione menus (piccolo refactoring, no logica) --- base/menu.py | 13 +++++-------- formazione/menus.py | 6 +++--- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/base/menu.py b/base/menu.py index 8635552f7..8a5673846 100755 --- a/base/menu.py +++ b/base/menu.py @@ -8,9 +8,9 @@ from anagrafica.costanti import REGIONALE #TERRITORIALE, LOCALE from anagrafica.permessi.applicazioni import (PRESIDENTE, UFFICIO_SOCI, RUBRICHE_TITOLI, COMMISSARIO) -from anagrafica.permessi.costanti import (GESTIONE_CORSI_SEDE, GESTIONE_ATTIVITA, - GESTIONE_ATTIVITA_AREA, ELENCHI_SOCI, GESTIONE_AREE_SEDE, GESTIONE_ATTIVITA_SEDE, - EMISSIONE_TESSERINI, GESTIONE_POTERI_CENTRALE_OPERATIVA_SEDE) +from anagrafica.permessi.costanti import (GESTIONE_ATTIVITA, GESTIONE_ATTIVITA_AREA, + ELENCHI_SOCI, GESTIONE_AREE_SEDE, GESTIONE_ATTIVITA_SEDE, EMISSIONE_TESSERINI, + GESTIONE_POTERI_CENTRALE_OPERATIVA_SEDE) from anagrafica.models import Sede from .utils import remove_none @@ -40,8 +40,6 @@ def menu(request): oggetto_id__in=sedi ).distinct().values_list('tipo', flat=True) - gestione_corsi_sede = me.ha_permesso(GESTIONE_CORSI_SEDE) if me else False - RUBRICA_BASE = [ ("Referenti", "fa-book", "/utente/rubrica/referenti/"), ("Volontari", "fa-book", "/utente/rubrica/volontari/"), @@ -210,9 +208,8 @@ def menu(request): if me and me.oggetti_permesso(GESTIONE_POTERI_CENTRALE_OPERATIVA_SEDE).exists() else None, )), ), - 'formazione': formazione_menu('formazione', gestione_corsi_sede, me), - 'aspirante': formazione_menu('aspirante', gestione_corsi_sede) \ - if me and hasattr(me, 'aspirante') else ( + 'formazione': formazione_menu('formazione', me), + 'aspirante': formazione_menu('aspirante') if me and hasattr(me, 'aspirante') else ( ("Gestione Corsi", ( ("Elenco Corsi", "fa-list", reverse('formazione:list_courses')), )), diff --git a/formazione/menus.py b/formazione/menus.py index 51f2849cb..140e458d2 100644 --- a/formazione/menus.py +++ b/formazione/menus.py @@ -9,12 +9,12 @@ def to_show(me, permission): return False -def formazione_menu(menu_name, gestione_corsi_sede, me=None): +def formazione_menu(menu_name, me=None): FORMAZIONE = ( ("Corsi", ( - ("Attiva Corso", "fa-asterisk", reverse('formazione:new_course')) if gestione_corsi_sede else None, + ("Attiva Corso", "fa-asterisk", reverse('formazione:new_course')) if to_show(me, GESTIONE_CORSI_SEDE) else None, # ("Elenco Corsi", "fa-list", reverse('formazione:list_courses')), - ("Domanda formativa", "fa-area-chart", reverse('formazione:domanda')) if gestione_corsi_sede else None, + ("Domanda formativa", "fa-area-chart", reverse('formazione:domanda')) if to_show(me, GESTIONE_CORSI_SEDE) else None, ('Catalogo Corsi', 'fa-list-alt', '/page/catalogo-corsi/'), ('Glossario Corsi', 'fa-book', '/page/glossario-corsi/'), ('Albo Informatizzato', 'fa-list', reverse('formazione:albo_info')) if to_show(me, GESTIONE_CORSI_SEDE) else None, From 7ee4833889ef569d4c24eda43dc9a78b78320968 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Mon, 27 May 2019 15:47:49 +0200 Subject: [PATCH 287/291] (anagrafica.permessi.*) small refactoring (imports, non logica) --- anagrafica/permessi/applicazioni.py | 4 ---- anagrafica/permessi/delega.py | 6 ++--- anagrafica/permessi/espansioni.py | 27 ++++++----------------- anagrafica/permessi/funzioni.py | 34 ++++++++++++++--------------- anagrafica/permessi/persona.py | 19 +++++----------- 5 files changed, 32 insertions(+), 58 deletions(-) diff --git a/anagrafica/permessi/applicazioni.py b/anagrafica/permessi/applicazioni.py index 5783bed20..ae4a03066 100755 --- a/anagrafica/permessi/applicazioni.py +++ b/anagrafica/permessi/applicazioni.py @@ -1,10 +1,7 @@ -# coding=utf-8 from collections import OrderedDict -__author__ = 'alfioemanuele' # Tipologie di applicativi esistenti - PRESIDENTE = 'PR' VICE_PRESIDENTE = 'VP' COMMISSARIO = 'CM' @@ -96,4 +93,3 @@ ('direttori_corsi', (DIRETTORE_CORSO, 'Direttori Corsi', True)), ('responsabili_autoparco', (RESPONSABILE_AUTOPARCO, 'Responsabili Autoparco', True)), )) - diff --git a/anagrafica/permessi/delega.py b/anagrafica/permessi/delega.py index 230e53211..06652f353 100755 --- a/anagrafica/permessi/delega.py +++ b/anagrafica/permessi/delega.py @@ -1,7 +1,5 @@ -from anagrafica.permessi.funzioni import PERMESSI_FUNZIONI_DICT -from anagrafica.permessi.incarichi import ESPANSIONE_DELEGHE - -__author__ = 'alfioemanuele' +from ..permessi.funzioni import PERMESSI_FUNZIONI_DICT +from ..permessi.incarichi import ESPANSIONE_DELEGHE def delega_permessi(delega, solo_deleghe_attive=True): diff --git a/anagrafica/permessi/espansioni.py b/anagrafica/permessi/espansioni.py index c4294d5d7..bdcf19133 100755 --- a/anagrafica/permessi/espansioni.py +++ b/anagrafica/permessi/espansioni.py @@ -96,29 +96,16 @@ def espandi_elenchi_soci(qs_sedi, al_giorno=None): from ufficio_soci.models import Quota, Tesserino try: return [ - (LETTURA, Persona.objects.filter( - Appartenenza.query_attuale(al_giorno=al_giorno, sede__in=qs_sedi).via("appartenenze"))), - (LETTURA, Persona.objects.filter(Appartenenza.query_attuale(al_giorno=al_giorno, sede__in=qs_sedi, - membro__in=Appartenenza.MEMBRO_DIRETTO).via( - "appartenenze"))), - (LETTURA, Persona.objects.filter(Appartenenza.query_attuale(al_giorno=al_giorno, sede__in=qs_sedi, - membro__in=Appartenenza.MEMBRO_ESTESO).via( - "appartenenze"))), - (LETTURA, Quota.objects.filter(Appartenenza.query_attuale(al_giorno=al_giorno, sede__in=qs_sedi, - membro__in=Appartenenza.MEMBRO_ESTESO).via( - "persona__appartenenze"))), + (LETTURA, Persona.objects.filter(Appartenenza.query_attuale(al_giorno=al_giorno, sede__in=qs_sedi).via("appartenenze"))), + (LETTURA, Persona.objects.filter(Appartenenza.query_attuale(al_giorno=al_giorno, sede__in=qs_sedi, membro__in=Appartenenza.MEMBRO_DIRETTO).via("appartenenze"))), + (LETTURA, Persona.objects.filter(Appartenenza.query_attuale(al_giorno=al_giorno, sede__in=qs_sedi, membro__in=Appartenenza.MEMBRO_ESTESO).via("appartenenze"))), + (LETTURA, Quota.objects.filter(Appartenenza.query_attuale(al_giorno=al_giorno, sede__in=qs_sedi, membro__in=Appartenenza.MEMBRO_ESTESO).via("persona__appartenenze"))), (LETTURA, Quota.objects.filter(Q(Q(sede__in=qs_sedi) | Q(appartenenza__sede__in=qs_sedi)))), - (LETTURA, Persona.objects.filter( - Appartenenza.query_attuale(al_giorno=al_giorno, sede__in=qs_sedi, confermata=True, ritirata=False).via( - "appartenenze"))), + (LETTURA, Persona.objects.filter(Appartenenza.query_attuale(al_giorno=al_giorno, sede__in=qs_sedi, confermata=True, ritirata=False).via("appartenenze"))), (LETTURA, Persona.objects.filter(Appartenenza.con_esito_pending(sede__in=qs_sedi).via("appartenenze"))), (LETTURA, Persona.objects.filter(Appartenenza.con_esito_no(sede__in=qs_sedi).via("appartenenze"))), - (LETTURA, Riserva.objects.filter( - Appartenenza.query_attuale(al_giorno=al_giorno, sede__in=qs_sedi, confermata=True, ritirata=False).via( - "persona__appartenenze"))), - (LETTURA, Tesserino.objects.filter( - Appartenenza.query_attuale(al_giorno=al_giorno, sede__in=qs_sedi, confermata=True, ritirata=False).via( - "persona__appartenenze"))), + (LETTURA, Riserva.objects.filter(Appartenenza.query_attuale(al_giorno=al_giorno, sede__in=qs_sedi, confermata=True, ritirata=False).via("persona__appartenenze"))), + (LETTURA, Tesserino.objects.filter(Appartenenza.query_attuale(al_giorno=al_giorno, sede__in=qs_sedi, confermata=True, ritirata=False).via("persona__appartenenze"))), ] except (AttributeError, ValueError, KeyError): return [] diff --git a/anagrafica/permessi/funzioni.py b/anagrafica/permessi/funzioni.py index 18cfa5a92..118805295 100755 --- a/anagrafica/permessi/funzioni.py +++ b/anagrafica/permessi/funzioni.py @@ -1,20 +1,14 @@ -""" -Questo modulo contiene tutte le funzioni per testare i permessi -a partire da un oggetto sul quale ho una delega ed un oggetto da testare. -""" from datetime import timedelta - from django.db.models import QuerySet, Q -from anagrafica.permessi.applicazioni import PRESIDENTE, DIRETTORE_CORSO, RESPONSABILE_AUTOPARCO, REFERENTE_GRUPPO, COMMISSARIO,\ - UFFICIO_SOCI_UNITA, DELEGATO_OBIETTIVO_1, DELEGATO_OBIETTIVO_2, DELEGATO_OBIETTIVO_3, DELEGATO_OBIETTIVO_4, \ - DELEGATO_OBIETTIVO_5, DELEGATO_OBIETTIVO_6, RESPONSABILE_FORMAZIONE, DELEGATO_CO, CONSIGLIERE, CONSIGLIERE_GIOVANE, VICE_PRESIDENTE -from anagrafica.permessi.applicazioni import UFFICIO_SOCI -from anagrafica.permessi.applicazioni import DELEGATO_AREA -from anagrafica.permessi.applicazioni import RESPONSABILE_AREA -from anagrafica.permessi.applicazioni import REFERENTE - -from anagrafica.permessi.costanti import GESTIONE_SOCI, ELENCHI_SOCI, GESTIONE_ATTIVITA_SEDE, GESTIONE_CORSI_SEDE, \ +from ..permessi.applicazioni import (PRESIDENTE, DIRETTORE_CORSO, RESPONSABILE_AUTOPARCO, + REFERENTE_GRUPPO, COMMISSARIO, UFFICIO_SOCI_UNITA, DELEGATO_OBIETTIVO_1, + DELEGATO_OBIETTIVO_2, DELEGATO_OBIETTIVO_3, DELEGATO_OBIETTIVO_4, DELEGATO_OBIETTIVO_5, + DELEGATO_OBIETTIVO_6, RESPONSABILE_FORMAZIONE, DELEGATO_CO, CONSIGLIERE, + CONSIGLIERE_GIOVANE, VICE_PRESIDENTE, UFFICIO_SOCI, DELEGATO_AREA, + RESPONSABILE_AREA, REFERENTE) +from ..permessi.costanti import (GESTIONE_SOCI, ELENCHI_SOCI, \ + GESTIONE_ATTIVITA_SEDE, GESTIONE_CORSI_SEDE, \ GESTIONE_SEDE, GESTIONE_ATTIVITA_AREA, GESTIONE_ATTIVITA, GESTIONE_CORSO, GESTIONE_AUTOPARCHI_SEDE, \ GESTIONE_GRUPPI_SEDE, GESTIONE_GRUPPO, GESTIONE_GRUPPI, GESTIONE_AREE_SEDE, GESTIONE_REFERENTI_ATTIVITA, \ GESTIONE_CENTRALE_OPERATIVA_SEDE, EMISSIONE_TESSERINI, GESTIONE_POTERI_CENTRALE_OPERATIVA_SEDE, \ @@ -23,7 +17,13 @@ RUBRICA_DELEGATI_OBIETTIVO_3, RUBRICA_DELEGATI_OBIETTIVO_4, RUBRICA_DELEGATI_OBIETTIVO_6, \ RUBRICA_DELEGATI_GIOVANI, RUBRICA_RESPONSABILI_AREA, RUBRICA_REFERENTI_ATTIVITA, \ RUBRICA_REFERENTI_GRUPPI, RUBRICA_CENTRALI_OPERATIVE, RUBRICA_RESPONSABILI_FORMAZIONE, \ - RUBRICA_DIRETTORI_CORSI, RUBRICA_RESPONSABILI_AUTOPARCO, RUBRICA_COMMISSARI + RUBRICA_DIRETTORI_CORSI, RUBRICA_RESPONSABILI_AUTOPARCO, RUBRICA_COMMISSARI) + + +""" +Questo modulo contiene tutte le funzioni per testare i permessi +a partire da un oggetto sul quale ho una delega ed un oggetto da testare. +""" def permessi_persona(persona): @@ -91,6 +91,7 @@ def permessi_presidente(sede): + permessi_delegato_centrale_operativa(sede) \ + _espandi(sede) + def permessi_commissario(sede): """ Permessi della delega di COMMISSARIO. @@ -124,7 +125,6 @@ def permessi_consigliere(sede): return [] - def permessi_ufficio_soci_unita(sede): """ Permessi della delega di UFFICIO SOCI. @@ -185,7 +185,7 @@ def permessi_delegato_obiettivo_1(sede): sede_espansa = sede.espandi(includi_me=True) return [ (RUBRICA_DELEGATI_OBIETTIVO_1, sede.espandi(includi_me=True, pubblici=True)), - ] + permessi_delegato_area(Area.objects.filter(sede__in=sede_espansa, obiettivo=1)) + ] + permessi_delegato_area(Area.objects.filter(sede__in=sede_espansa, obiettivo=1)) def permessi_delegato_obiettivo_2(sede): diff --git a/anagrafica/permessi/persona.py b/anagrafica/permessi/persona.py index 4841aa233..31070cbc1 100755 --- a/anagrafica/permessi/persona.py +++ b/anagrafica/permessi/persona.py @@ -1,11 +1,6 @@ -from datetime import date -from anagrafica.permessi.costanti import permesso_minimo, LETTURA -from anagrafica.permessi.espansioni import ESPANDI_PERMESSI, espandi_persona -from django.utils import timezone - -from anagrafica.permessi.funzioni import permessi_persona - -__author__ = 'alfioemanuele' +from ..permessi.costanti import permesso_minimo, LETTURA +from ..permessi.espansioni import ESPANDI_PERMESSI, espandi_persona +from ..permessi.funzioni import permessi_persona def persona_oggetti_permesso(persona, permesso, al_giorno=None, solo_deleghe_attive=True): @@ -48,8 +43,7 @@ def persona_oggetti_permesso(persona, permesso, al_giorno=None, solo_deleghe_att return qs -def persona_permessi(persona, oggetto, al_giorno=None, - solo_deleghe_attive=True): +def persona_permessi(persona, oggetto, al_giorno=None, solo_deleghe_attive=True): """ Ritorna il livello di permessi che si ha su un qualunque oggetto. @@ -140,8 +134,7 @@ def persona_permessi_almeno(persona, oggetto, minimo=LETTURA, al_giorno=None, return False -def persona_ha_permesso(persona, permesso, al_giorno=None, - solo_deleghe_attive=True): +def persona_ha_permesso(persona, permesso, al_giorno=None, solo_deleghe_attive=True): """ Dato un permesso, ritorna true se il permesso e' posseduto. :param permesso: Permesso singolo. @@ -183,4 +176,4 @@ def persona_ha_permessi(persona, *permessi, solo_deleghe_attive=True): else: # Singolo permesso if not persona.ha_permesso(p, solo_deleghe_attive=solo_deleghe_attive): return False - return True \ No newline at end of file + return True From cb1c66af78231d125b53385c917afb63d7088203 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Mon, 27 May 2019 15:48:54 +0200 Subject: [PATCH 288/291] FIXED: (GAIA-111) albo informatizzato - nuovi permessi per poter vedere la pagina --- formazione/menus.py | 18 +++++++++++++----- formazione/viste.py | 16 +++++++++++++++- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/formazione/menus.py b/formazione/menus.py index 140e458d2..bc947dd7b 100644 --- a/formazione/menus.py +++ b/formazione/menus.py @@ -1,11 +1,19 @@ from django.core.urlresolvers import reverse -from anagrafica.permessi.costanti import GESTIONE_CORSI_SEDE +from anagrafica.permessi.costanti import GESTIONE_CORSI_SEDE, ELENCHI_SOCI -def to_show(me, permission): - if me and me.ha_permesso(permission): - return True +def to_show(me, permissions): + if not me: + return False + + if isinstance(permissions, list or tuple): + for permission in permissions: + if me.ha_permesso(permission): + return True + else: + if me.ha_permesso(permissions): + return True return False @@ -17,7 +25,7 @@ def formazione_menu(menu_name, me=None): ("Domanda formativa", "fa-area-chart", reverse('formazione:domanda')) if to_show(me, GESTIONE_CORSI_SEDE) else None, ('Catalogo Corsi', 'fa-list-alt', '/page/catalogo-corsi/'), ('Glossario Corsi', 'fa-book', '/page/glossario-corsi/'), - ('Albo Informatizzato', 'fa-list', reverse('formazione:albo_info')) if to_show(me, GESTIONE_CORSI_SEDE) else None, + ('Albo Informatizzato', 'fa-list', reverse('formazione:albo_info')) if to_show(me, [ELENCHI_SOCI, GESTIONE_CORSI_SEDE]) else None, )), ) diff --git a/formazione/viste.py b/formazione/viste.py index f94472e4c..5a147f208 100644 --- a/formazione/viste.py +++ b/formazione/viste.py @@ -1070,7 +1070,21 @@ def aspirante_corso_estensioni_informa(request, me, pk): @pagina_privata def formazione_albo_informatizzato(request, me): - sedi = me.oggetti_permesso(ELENCHI_SOCI) + from anagrafica.models import Sede + + sedi_set = set() + + DELEGATO_OBIETTIVO_ALL = ['RUBRICA_DELEGATI_OBIETTIVO_%s' % i for i in range(1,7) if i != 5] + ALL_PERMESSI_TO_CHECK = DELEGATO_OBIETTIVO_ALL + [GESTIONE_CORSI_SEDE] + for permesso in ALL_PERMESSI_TO_CHECK: + ids = me.oggetti_permesso(permesso).values_list('pk', flat=True) + sedi_set.update(ids) + + sedi = Sede.objects.filter(pk__in=sedi_set) + + if not sedi: + return redirect(ERRORE_PERMESSI) + context = { 'elenco_nome': 'Albo Informatizzato', 'elenco_template': None, From 8716113bda52905c5c968b02c298d4e9ae0ed646 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Mon, 27 May 2019 17:11:05 +0200 Subject: [PATCH 289/291] FIXED: (GAIA-111) mostra menu Formazione in alto per i nuovi permessi e piccolo refactoring --- anagrafica/models.py | 8 ++++++-- anagrafica/permessi/costanti.py | 6 ++++++ formazione/menus.py | 5 +++-- formazione/viste.py | 9 +++------ 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/anagrafica/models.py b/anagrafica/models.py index 06302ae44..28bd3c382 100755 --- a/anagrafica/models.py +++ b/anagrafica/models.py @@ -22,6 +22,8 @@ from .validators import (valida_codice_fiscale, ottieni_genere_da_codice_fiscale, valida_dimensione_file_8mb, valida_partita_iva, valida_dimensione_file_5mb, valida_iban, valida_email_personale) # valida_almeno_14_anni, crea_validatore_dimensione_file) +from .permessi.shortcuts import * +from .permessi.costanti import RUBRICA_DELEGATI_OBIETTIVO_ALL from attivita.models import Turno, Partecipazione from base.files import PDF, Excel, FoglioExcel from base.geo import ConGeolocalizzazione @@ -33,7 +35,6 @@ TitleCharField, poco_fa, mezzanotte_24_ieri, mezzanotte_00, mezzanotte_24) from curriculum.models import Titolo, TitoloPersonale from posta.models import Messaggio -from .permessi.shortcuts import * class Persona(ModelloSemplice, ConMarcaTemporale, ConAllegati, ConVecchioID): @@ -523,7 +524,9 @@ def applicazioni_disponibili(self): if self.ha_permesso(GESTIONE_CENTRALE_OPERATIVA_SEDE): lista += [('/centrale-operativa/', "CO", "fa-compass")] - if self.ha_permesso(GESTIONE_CORSO) or self.ha_permesso(GESTIONE_CORSI_SEDE): + if self.ha_permesso(GESTIONE_CORSO) or \ + self.ha_permesso(GESTIONE_CORSI_SEDE) or \ + True in [self.ha_permesso(i) for i in RUBRICA_DELEGATI_OBIETTIVO_ALL]: lista += [('/formazione/', 'Formazione', 'fa-graduation-cap')] tipi = [] @@ -532,6 +535,7 @@ def applicazioni_disponibili(self): continue tipi.append(d.tipo) #lista += [(APPLICAZIONI_SLUG_DICT[d.tipo], PERMESSI_NOMI_DICT[d.tipo])] + lista += [('/articoli/', 'Articoli', 'fa-newspaper-o')] lista += [('/documenti/', 'Documenti', 'fa-folder')] return lista diff --git a/anagrafica/permessi/costanti.py b/anagrafica/permessi/costanti.py index 8cbde9e60..d5863add1 100755 --- a/anagrafica/permessi/costanti.py +++ b/anagrafica/permessi/costanti.py @@ -105,6 +105,12 @@ (REFERENTE_GRUPPO, ('anagrafica', 'Gruppo', 'sede__in')), ) +RUBRICA_DELEGATI_OBIETTIVO_ALL = [RUBRICA_DELEGATI_OBIETTIVO_1, + RUBRICA_DELEGATI_OBIETTIVO_2, + RUBRICA_DELEGATI_OBIETTIVO_3, + RUBRICA_DELEGATI_OBIETTIVO_4, + RUBRICA_DELEGATI_OBIETTIVO_6,] + # Livelli di permesso # IMPORTANTE: Tenere in ordine, sia numerico che di linea. diff --git a/formazione/menus.py b/formazione/menus.py index bc947dd7b..8984d0a17 100644 --- a/formazione/menus.py +++ b/formazione/menus.py @@ -1,6 +1,6 @@ from django.core.urlresolvers import reverse -from anagrafica.permessi.costanti import GESTIONE_CORSI_SEDE, ELENCHI_SOCI +from anagrafica.permessi.costanti import GESTIONE_CORSI_SEDE, ELENCHI_SOCI, RUBRICA_DELEGATI_OBIETTIVO_ALL def to_show(me, permissions): @@ -25,7 +25,8 @@ def formazione_menu(menu_name, me=None): ("Domanda formativa", "fa-area-chart", reverse('formazione:domanda')) if to_show(me, GESTIONE_CORSI_SEDE) else None, ('Catalogo Corsi', 'fa-list-alt', '/page/catalogo-corsi/'), ('Glossario Corsi', 'fa-book', '/page/glossario-corsi/'), - ('Albo Informatizzato', 'fa-list', reverse('formazione:albo_info')) if to_show(me, [ELENCHI_SOCI, GESTIONE_CORSI_SEDE]) else None, + ('Albo Informatizzato', 'fa-list', reverse( + 'formazione:albo_info')) if to_show(me, RUBRICA_DELEGATI_OBIETTIVO_ALL + [GESTIONE_CORSI_SEDE]) else None, )), ) diff --git a/formazione/viste.py b/formazione/viste.py index 5a147f208..07c78927e 100644 --- a/formazione/viste.py +++ b/formazione/viste.py @@ -7,11 +7,11 @@ from django.template.loader import get_template from django.contrib import messages -from anagrafica.models import Persona, Documento +from anagrafica.models import Persona, Documento, Sede from anagrafica.forms import ModuloCreazioneDocumento from anagrafica.permessi.applicazioni import DIRETTORE_CORSO from anagrafica.permessi.costanti import (GESTIONE_CORSI_SEDE, - GESTIONE_CORSO, ERRORE_PERMESSI, COMPLETO, MODIFICA, ELENCHI_SOCI) + GESTIONE_CORSO, ERRORE_PERMESSI, COMPLETO, MODIFICA, RUBRICA_DELEGATI_OBIETTIVO_ALL) from curriculum.models import TitoloPersonale from ufficio_soci.elenchi import ElencoPerTitoliCorso from autenticazione.funzioni import pagina_privata, pagina_pubblica @@ -1070,12 +1070,9 @@ def aspirante_corso_estensioni_informa(request, me, pk): @pagina_privata def formazione_albo_informatizzato(request, me): - from anagrafica.models import Sede - sedi_set = set() - DELEGATO_OBIETTIVO_ALL = ['RUBRICA_DELEGATI_OBIETTIVO_%s' % i for i in range(1,7) if i != 5] - ALL_PERMESSI_TO_CHECK = DELEGATO_OBIETTIVO_ALL + [GESTIONE_CORSI_SEDE] + ALL_PERMESSI_TO_CHECK = RUBRICA_DELEGATI_OBIETTIVO_ALL + [GESTIONE_CORSI_SEDE] for permesso in ALL_PERMESSI_TO_CHECK: ids = me.oggetti_permesso(permesso).values_list('pk', flat=True) sedi_set.update(ids) From fa490657da8c01ebe8ff60944bba26f801039732 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Tue, 28 May 2019 17:17:21 +0200 Subject: [PATCH 290/291] FIXED: (GAIA-116/16) email_corso_base_iscritto.html --- posta/templates/email_corso_base_iscritto.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posta/templates/email_corso_base_iscritto.html b/posta/templates/email_corso_base_iscritto.html index 17d95ba5d..d1961de54 100644 --- a/posta/templates/email_corso_base_iscritto.html +++ b/posta/templates/email_corso_base_iscritto.html @@ -2,7 +2,7 @@ {% block corpo %}

    Ciao {{ persona.nome }}!

    -

    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 }}!

    +

    Ricevi questo messaggio perché ti sei iscritt{{ persona.genere_o_a }} a 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. From a4c62fa7b987be0ed8135b87cbe726435773c8c8 Mon Sep 17 00:00:00 2001 From: Arkady Karlkvist Date: Wed, 29 May 2019 10:18:53 +0200 Subject: [PATCH 291/291] FIXED: (GAIA-116/30) aspirante_corso_base_scheda.html (navigazione tab) --- .../templates/aspirante_corso_base_scheda.html | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/formazione/templates/aspirante_corso_base_scheda.html b/formazione/templates/aspirante_corso_base_scheda.html index a108cfa6a..a9b1c9af9 100644 --- a/formazione/templates/aspirante_corso_base_scheda.html +++ b/formazione/templates/aspirante_corso_base_scheda.html @@ -25,7 +25,7 @@

    {% endif %} @@ -58,6 +58,12 @@

  • Informazioni
  • {% if puo_modificare %}
  • Pubblicizza il corso
  • + + {# link doppione per visualizzarlo solo ai direttore che non vedono la sezione "Attivazione" #} + {% if corso.tipo == 'C1' and not me.is_presidente %} +
  • Area geografica
  • + {% endif %} +
  • Iscrizioni {{ corso.partecipazioni_confermate.count }} @@ -125,7 +131,7 @@

    Il corso non è ancora attivo

    {% else %} FATTO: {% endif %} - Conferma l'estensione a chi vuoi aprire il Corso dalla scheda "Attivazione - Area geografica interessata" + Indica l'area geografica. Seleziona i Comitati ai quali è destinata la formazione

    {% endif %}