diff --git a/aula/apps/importaActuacions/__init__.py b/aula/apps/importaActuacions/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/aula/apps/importaActuacions/admin.py b/aula/apps/importaActuacions/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/aula/apps/importaActuacions/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/aula/apps/importaActuacions/apps.py b/aula/apps/importaActuacions/apps.py new file mode 100644 index 00000000..999dffb6 --- /dev/null +++ b/aula/apps/importaActuacions/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ImportaactuacionsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'aula.apps.importaActuacions' diff --git a/aula/apps/importaActuacions/forms.py b/aula/apps/importaActuacions/forms.py new file mode 100644 index 00000000..b8ca772d --- /dev/null +++ b/aula/apps/importaActuacions/forms.py @@ -0,0 +1,6 @@ +from django import forms + +class dades_dboldForm (forms.Form): + nom = forms.CharField(label='Nom de la Base de dades antiga:', max_length=30) + usuari = forms.CharField(label='Usuari per accedir-hi:', max_length=30) + contrasenya = forms.CharField(label="Contrasenya de l'usuari:", max_length=30, widget=forms.PasswordInput) diff --git a/aula/apps/importaActuacions/migrations/__init__.py b/aula/apps/importaActuacions/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/aula/apps/importaActuacions/models.py b/aula/apps/importaActuacions/models.py new file mode 100644 index 00000000..bcd8aee1 --- /dev/null +++ b/aula/apps/importaActuacions/models.py @@ -0,0 +1,3 @@ + +# Create your models here. + diff --git a/aula/apps/importaActuacions/table2_models.py b/aula/apps/importaActuacions/table2_models.py new file mode 100644 index 00000000..442d8d6e --- /dev/null +++ b/aula/apps/importaActuacions/table2_models.py @@ -0,0 +1,43 @@ +# -*- encoding: utf-8 -*- +import django_tables2 as tables +from django_tables2.utils import A + +from aula.apps.tutoria.models import Actuacio + + +class Table2_llista_actuacions(tables.Table): + + id_actuacio = tables.TemplateColumn( + template_code = u"""{{record.id}}""", + order_by=( '-id', ) + ) + + moment_actuacio = tables.TemplateColumn( + template_code = u"""{{record.moment_actuacio}}""", + order_by=( '-moment_actuacio', ) + ) + + alumne = tables.TemplateColumn( + template_code = u"""{{ record.alumne }}""", + order_by=( 'alumne.cognoms', 'alumne.nom', '-moment_actuacio', ) + ) + + + qui = tables.TemplateColumn( + template_code = u"""{{ record.professional }} ( {{record.get_qui_fa_actuacio_display}})""", + order_by=( 'professional', '-moment_actuacio', ), + verbose_name=u"Qui?" + ) + + actuacio = tables.TemplateColumn( + template_code = u"""{{ record.actuacio }}""", + orderable = False + ) + + class Meta: + model = Actuacio + # add class="paleblue" to tag + attrs = {"class": "paleblue table table-striped"} + sequence = ("id_actuacio", "moment_actuacio", "alumne", "qui", "actuacio", ) + fields = sequence + template = 'bootable2.html' \ No newline at end of file diff --git a/aula/apps/importaActuacions/templates/connexioDBold.html b/aula/apps/importaActuacions/templates/connexioDBold.html new file mode 100644 index 00000000..5a0b9f31 --- /dev/null +++ b/aula/apps/importaActuacions/templates/connexioDBold.html @@ -0,0 +1,164 @@ +{% extends "base.html" %} + +{% block extrahead %} {% endblock %} + +{% block content %} + + {% block head %} + {% if head %} +

{{ head }}

+

+

+ {% endif %} + {% endblock %} + + {% block preform %}{% endblock %} + +

+ + + +
+
+
+ +

+

Les actuacions són intervencions pedagògiques fetes entre un professional del centre, normalment tutors, coordinador pedagògic o cap d'estudis, i un alumne concret. No obstant, les actuacions generades en cursos anteriors són inaccessibles en aquest curs per defecte. La decisió de no incorporar-les al nou curs ve donada per raons de disseny del DjAu, que cal ser instal·lat de nou cada curs escolar.

+

Per poder consultar-les cal mantenir les instal·lacions del DjAu dels cursos anteriors actives però inaccessibles per l'ús habitual. D'aquesta manera, determinats usuaris, eventualment els adscrits a Direcció i/o a psicopedagogia, poden encara consultar-les si en fos menester.

+
+

Aquest mòdul del DjAu importa les actuacions dels cursos anteriors al curs actual, integrant-les igual que les actuals actuacions i permetent no haver d'accedir a instal·lacions anteriors per consultar antigues actuacions.

+ +
+ +

+

Per aconseguir importar les actuacions d'un curs anterior a l'actual caldrà:

+ +
+ +
+ +

+

Un cop connectat a la base de dades del curs origen de les actuacions, el sistema du a terme les següents accions:

+
    +
  1. Detecció de l'alumnat amb actuacions a importar que ja no es troba al centre educatiu, típicament l'alumnat desmatriculat.
  2. +
  3. Detecció dels professionals que van crear actuacions a importar que ja no es troben al centre educatiu.
  4. +
  5. Comprovació de no duplicitat de les actuacions per evitar importar actuacions ja existents, típicament per haver-les ja importat amb anterioritat.
  6. +
+
+ +

Abans de dur a terme la importació, el sistema mostrarà la següent informació per facilitar-ne la revisió:

+ +
+

Un cop feta la importació de les actuacions del curs anterior amb èxit se'n mostraran totes les actuacions del curs actual, ja fusionades amb les del curs antic, per facilitar-ne la revisió final.

+
+
+
+
+ + + + {% if form.non_field_errors %} +
+
+ + {% for error in form.non_field_errors %} + {{error}} + {% endfor %} +
+
+ {% endif %} + + {%if titol_formulari %}

{{titol_formulari}}

{%endif%} + + {% csrf_token %} + + {% for hidden in form.hidden_fields %} + {{ hidden }} + {% endfor %} + + {% for label, value in infoForm %} +
+
+ +

+
+
+ {{ value }} +
+
+ {% endfor %} + + {% for field in form.visible_fields %} +
+
+ +

+ {{ field.label_tag }} + {% if field.help_text %} + + {% endif %} +

+
+
+ {{ field }} + {% if field.help_text %} + + {% endif %} + {% if field.errors %} +
+ +
    + {% for error in field.errors%} +
  • {{ error }}
  • + {% endfor %} +
+
+ {% endif %} +
+
+ {% endfor %} + +
+
+ +
+
+ + + {% block postform %}{% endblock %} + + + + + +{% endblock %} \ No newline at end of file diff --git a/aula/apps/importaActuacions/templates/errorConnexio.html b/aula/apps/importaActuacions/templates/errorConnexio.html new file mode 100644 index 00000000..9a8dfc8e --- /dev/null +++ b/aula/apps/importaActuacions/templates/errorConnexio.html @@ -0,0 +1,35 @@ +{% extends "base.html" %} + +{% block extrahead %} {% endblock %} + +{% block content %} + + {% block head %} + {% if head %} +

{{ head }}

+

+

+ {% endif %} + {% endblock %} + + {% block preform %}{% endblock %} + +

+ +

Les dades utilitzades per connectar amb la base de dades del curs anterior han sigut:

+ + + + + Tornar-ho a intentar + +{% endblock %} \ No newline at end of file diff --git a/aula/apps/importaActuacions/templates/importacio.html b/aula/apps/importaActuacions/templates/importacio.html new file mode 100644 index 00000000..a6541db0 --- /dev/null +++ b/aula/apps/importaActuacions/templates/importacio.html @@ -0,0 +1,388 @@ +{% extends "base.html" %} + +{% block extrahead %} {% endblock %} + +{% block content %} + + {% block head %} + {% if head %} +

{{ head }}

+

+

+ {% endif %} + {% endblock %} + + {% block preform %}{% endblock %} + + + +

+ + + + + + + + +

Actuacions definitives que seran importades

+ +
+ +

Les actuacions a importar es poden consultar a continuació, amb el benentès que s'han descomptat tant les actuacions associades a l'alumnat que ja no es troba al centre, com aquelles actuacions que, per tal d'evitar actuacions duplicades, ja es troben al curs actual, probablement per haver estat ja importades amb anterioritat.

+ + {% if actuacions_a_importar %} + +

+ + {% if actuacions_a_importar|length == 1 %} + Només s'ha trobat una actuació a importar des del curs anterior al curs actual.

+ {% else %} + La quantitat final d'actuacions a importar des del curs anterior al curs actual serà de {{ actuacions_a_importar|length }}.

+ {% endif %} +
+

+
+ +
{% csrf_token %} + + +
+ + + +
+
+ + + + + + + + + + + + {% for actuacio in actuacions_olddb_list %} + {% if actuacio.alumne_id %} + + + + + {% if actuacio.professional_id %} + + {% else %} + + {% endif %} + + + {% endif %} + {% endfor %} + +
IdAlumneDataProfessionalContingut
{{ actuacio.id }}{{ actuacio.nom_alumne }} {{ actuacio.cognoms_alumne }}{{ actuacio.moment_actuacio }}{{ actuacio.nom_professional }} {{ actuacio.cognoms_professional }} Ja no és al centre.{{ actuacio.actuacio }}
+ + + {% elif not actuacions_ja_importades %} + + {% else %} + + {% endif %} + + + +


+ + + + + + +

Actuacions amb característiques especials

+ +
+ + +

Actuacions associades a alumnat desmatriculat

+ +

L'alumnat desmatriculat és aquell que no ha continuat al centre educatiu, és a dir, va estar matriculat però actualment ja no hi és.

+

Les actuacions associades a aquest alumnat no s'importaran. Si n'hi hagués es mostraran a continuació a títol informatiu.

+ + + + + {% if not alumnes_desmatriculats %} + No s'ha trobat cap alumne desmatriculat. + {% else %} + +
• Alumnat desmatriculat
+ +

+ {% if alumnes_desmatriculats|length == 1 %} + Només s'ha trobat un alumne desmatriculat + {% else %} + S'han trobat {{ alumnes_desmatriculats|length }} alumnes desmatriculats + {% endif %} +

+ + + + + + + + + + {% for alumne in alumnes_desmatriculats %} + + + + + {% endfor %} + +
NomRalc
{{ alumne.nom }} {{ alumne.cognoms }}{{ alumne.ralc }}
+ {% endif %} + + + + {% if not actuacions_sense_alumne_curs_actual %} + No hi ha actuacions associades a alumnat desmatriculat. + {% else %} + +
• Llistat d'actuacions associades a alumnat desmatriculat
+

+ {% if actuacions_sense_alumne_curs_actual|length == 1 %} + Només s'ha trobat una actuació associades a alumnat desmatriculat. + {% else %} + S'han trobat {{ actuacions_sense_alumne_curs_actual|length }} actuacions associades a alumnat desmatriculat. + {% endif %} +

+ + + +
+ + + + + + + + + + + + {% for actuacio in actuacions_olddb_list %} + {% if not actuacio.alumne_id %} + + + + + + + + {% endif %} + {% endfor %} + +
IdProfessionalDataAlumneContingut
{{ actuacio.id }}{{ actuacio.nom_professional }} {{ actuacio.cognoms_professional }}{{ actuacio.moment_actuacio }}{{ actuacio.nom_alumne }} {{ actuacio.cognoms_alumne }}{{ actuacio.actuacio }}
+
+ {% endif %} + +
+
+
+ + + + + + + + +

Actuacions creades per professionals que ja no es troben al centre educatiu

+ +

S'ofereixen dos llistats. El primer fa referència als professionals que ja no es troben al centre i que van fer actuacions amb alumnat que encara hi és, mentre que el segon presenta les actuacions que van crear.

+

Donat que aquestes actuacions sí que seran importades a l'historial de l'alumnat corresponent, es mostren a continuació per tal de facilitar-ne la revisió. No obstant, degut a aquesta circumstància específica, a l'inici del text de l'actuació afectada s'insertarà una frase que dirà:

+ + + + + + {% if not professionals_foracentre %} + Tots els professionals afectats per la importació es troben al centre educatiu. + {% else %} + +
• Professionals que ja no són al centre educatiu
+

+ {% if professionals_foracentre|length == 1 %} + Només s'ha trobat un professional que ja no es al centre educatiu.

+ {% else %} + S'han trobat {{ professionals_foracentre|length }} professionals que ja no són al centre educatiu.

+ {% endif %} +

+ + + + + + + + + + {% for professional in professionals_foracentre %} + + + + + {% endfor %} + +
NomCodi intern
{{ professional.nom }} {{ professional.cognoms }}{{ professional.username }}
+ {% endif %} + + + + + + {% if actuacions_sense_professional %} + +
• Actuacions dels professionals que ja no es troben al centre educatiu
+

+ {% if actuacions_sense_professional|length == 1 %} + Només s'ha trobat una actuació afectada per un professional que no és al centre educatiu.

+ {% else %} + S'han trobat {{ actuacions_sense_professional|length }} actuacions creades per professionals que ja no són al centre educatiu.

+ {% endif %} +

+ + + +
+ + + + + + + + + + + + {% for actuacio in actuacions_olddb_list %} + {% if not actuacio.professional_id %} + + + + + + + {% endif %} + {% endfor %} + +
IdAlumneDataContingut
{{ actuacio.id }}{{ actuacio.nom_alumne }} {{ actuacio.cognoms_alumne }}{{ actuacio.moment_actuacio }}{{ actuacio.actuacio }}
+
+ + {% endif %} + +
+
+
+ + + + + + +

Actuacions que ja es troben al curs actual

+ + {% if not actuacions_ja_importades %} + No s'ha trobat cap actuació duplicada, és a dir, no hi ha cap actuació a importar que ja estigui en el curs actual. + {% else %} + + + +

+ {% if actuacions_ja_importades|length == 1 %} + Concretament se n'ha trobat una, d'actuació, que ja existeix al curs actual. Podria ser que ja hagi estat importada amb anterioritat. Aquesta actuació no s'importarà.

+ {% else %} + Concretament se n'han trobat {{ actuacions_ja_importades|length }}, d'actuacions, que ja existeixen al curs actual. És possible que ja hagin estat importades amb anterioritat. Aquestes actuacions no s'importaran.

+ {% endif %} +

+ + + +
+ + + + + + + + + + + + + {% for actuacio in actuacions_ja_importades %} + + + + + {% if actuacio.professional_id %} + + {% else %} + + {% endif %} + + + {% endfor %} + +
IdAlumneDataProfessionalContingut
{{ actuacio.id }}{{ actuacio.nom_alumne }} {{ actuacio.cognoms_alumne }}{{ actuacio.moment_actuacio }}{{ actuacio.nom_professional }} {{ actuacio.cognoms_professional }} Ja no és al centre.{{ actuacio.actuacio }}
+
+ +
+
+ + {% endif %} + + + + + +
+ +
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/aula/apps/importaActuacions/templates/resultatImportacio.html b/aula/apps/importaActuacions/templates/resultatImportacio.html new file mode 100644 index 00000000..dde6c848 --- /dev/null +++ b/aula/apps/importaActuacions/templates/resultatImportacio.html @@ -0,0 +1,65 @@ +{% extends "table2.html" %} +{% load i18n %} +{% load django_tables2 %} +{% load static %} +{% load humanize %} + + +{% block extrahead %} {% endblock %} + +{% block content %} + + {% block head %} + {% if head %} +

{{ head }}

+

+

+ {% endif %} + {% endblock %} + + {% block preform %}{% endblock %} + + +

A continuació es mostra el resultat de la importació. En primer lloc un llistat de les actuacions finalment importades, mentre que en segon lloc es mostren totes les actuacions del curs actual després d'haver fet la importació.

+
+ + + +

Actuacions importades

+ + + +
+ {% render_table table_actuacions_importades %} +
+
+
+
+ + + + + + + +

Llistat final d'actuacions del curs actual

+ + + +
+ {% render_table table %} +
+
+
+
+ + + +
+ + +{% endblock %} \ No newline at end of file diff --git a/aula/apps/importaActuacions/templates/sincronitzaProfessionals.html b/aula/apps/importaActuacions/templates/sincronitzaProfessionals.html new file mode 100644 index 00000000..e30bf165 --- /dev/null +++ b/aula/apps/importaActuacions/templates/sincronitzaProfessionals.html @@ -0,0 +1,90 @@ +{% extends "base.html" %} + +{% block extrahead %} {% endblock %} + +{% block content %} + + {% block head %} + {% if head %} +

{{ head }}

+

+

+ {% endif %} + {% endblock %} + + {% if professionals_repetits %} + +

+ +

Els noms d'usuari dels professionals repetits en les assignacions són els següents:

+ + + + + {% endif %} + + +

Com a pas previ, cal comprovar que els noms dels usuaris dels professionals del curs anterior (el nom de l'usuari amb el que entra al DjAu) es corresponen exactament amb els noms dels usuaris dels professionals del curs actual.

+
+ +

A continuació es mostren els professionals del curs anterior que sembla que no es troben en el curs actual.

+ +

Com és lògic, un usuari del curs actual no es pot associar a més d'un usuari del curs anterior.

+ + +
{% csrf_token %} + + + + + + + + + + + {% for usuari_olddb in usuaris_orfes_oldb %} + + + + + + {% endfor %} + +
UsernameNom del professional (curs anterior)Professional del curs actual amb el que associar el professional del curs anterior
{{ usuari_olddb.1 }}{{ usuari_olddb.2 }} {{ usuari_olddb.3 }} + +
+ +
+
+ +
+
+ +
+ +
+
+ +{% endblock %} \ No newline at end of file diff --git a/aula/apps/importaActuacions/templates/test.html b/aula/apps/importaActuacions/templates/test.html new file mode 100644 index 00000000..7c604c3c --- /dev/null +++ b/aula/apps/importaActuacions/templates/test.html @@ -0,0 +1,16 @@ +{% extends "base.html" %} + +{% block extrahead %} {% endblock %} + +{% block content %} + +

Template "test.html" per debug visual

+ + +

Debug: {{ debug }}

+

Debug2: {{ debug2 }}

+

Debug3: {{ debug3 }}

+ + +{% endblock %} + diff --git a/aula/apps/importaActuacions/tests.py b/aula/apps/importaActuacions/tests.py new file mode 100644 index 00000000..b0a9ee23 --- /dev/null +++ b/aula/apps/importaActuacions/tests.py @@ -0,0 +1,5 @@ +from django.test import TestCase +from django.shortcuts import render +from django.db import connections, OperationalError, DatabaseError + +# Create your tests here. diff --git a/aula/apps/importaActuacions/urls.py b/aula/apps/importaActuacions/urls.py new file mode 100644 index 00000000..154a7cca --- /dev/null +++ b/aula/apps/importaActuacions/urls.py @@ -0,0 +1,13 @@ +from django.urls import re_path +from aula.apps.importaActuacions import views as importaActuacions_views + +urlpatterns = [ + re_path(r'^connexioDB/$', importaActuacions_views.connexioDBold , + name="importaActuacions__connexio__DB"), + re_path(r'^sincronitzaProfessionals/$', importaActuacions_views.sincronitzaProfessionals , + name="importaActuacions__sincronitzaProfessionals"), + re_path(r'^importantActuacions/$', importaActuacions_views.importantActuacions , + name="importaActuacions__importa__actuacions"), + re_path(r'^resultatImportacio/$', importaActuacions_views.importacio , + name="importaActuacions__resultat__importacio"), +] \ No newline at end of file diff --git a/aula/apps/importaActuacions/views.py b/aula/apps/importaActuacions/views.py new file mode 100644 index 00000000..77968cf8 --- /dev/null +++ b/aula/apps/importaActuacions/views.py @@ -0,0 +1,518 @@ +from django.shortcuts import render, redirect, HttpResponse +from django.db import OperationalError, DatabaseError, connections +from django.contrib.auth.models import User +from copy import deepcopy +from aula.utils.my_paginator import DiggPaginator +from django.template.defaulttags import register + +#auth +from django.contrib.auth.decorators import login_required +from aula.utils.decorators import group_required + +#models +from aula.apps.alumnes.models import Alumne +from aula.apps.tutoria.models import Actuacio + +#forms +from aula.apps.importaActuacions.forms import dades_dboldForm + +#tables +from django_tables2.config import RequestConfig +from aula.apps.importaActuacions.table2_models import Table2_llista_actuacions + +# Per afegir i/o eliminar bases de dades a la configuració +from aula import settings_local + + +############################ +# PROCEDIMENT D'IMPORTACIÓ # +############################ + +# 1r PAS. CONNEXIÓ DB OLD. Cal connectar-se a la base de dades del curs anterior per extreure les dades. +# 2n PAS. SINCRONITZAR PROFESSIONALS. Els usernames dels professionals del curs anterior no tenen per què coincidir + # amb els del curs actual, encara que siguin els mateixos professionals. +# 3r PAS. EXTRACCIÓ DE DADES. Lectura de les dades significatives, tant de la BD antiga com de l'actual. +# 4t PAS. 1r CANVI. Amb les dades de la BD antiga: + # 4.1 Substitució de l'identificador del professional pel seu username antic i substitució dels usernames antics dels professionals pels actuals usernames. + # 4.2 Substitució de l'identificador de l'alumne pel RALC, que és universal. + # POSSIBLE MILLORA: Es podria contemplar el cas que un alumne no tinguin RALC? Només en aquest cas, caldria estirar les dades del nom i cognoms, però sempre és molt més insegur i amb confirmació expressa. +# 5é PAS. 2n CANVI. Amb les dades de la BD actual: + # 5.1 Substitució del RALC de l'alumne pel valor de l'identificador. + # 5.2 Substitució de l'username del professional pel seu identificador. +# 6é PAS. PREPARANT LA INSERCIÓ de les dades tractades a la BD actual. +# 7é PAS. INSERCIÓ de les actuacions antigues a la BD actual. + + +# Variables globals per traspassar informació entre funcions +dades_connexio_dbold = {} # Dades de connexió de la DB origen de les actuacions a importar +usernames_oldb_to_usernames_curs_actual_dict = [] # La correspondència dels usernames dels professionals entre bases de dades +actuacions_a_importar = [] # Les actuacions que finalment seran importades +actuacions_pel_bulkcreate = [] + + +def check_database_connection(request): + global dades_connexio_dbold + nom_dbold = dades_connexio_dbold.get('name_dbold') + usuari_dbold = dades_connexio_dbold.get('user_dbold') + + if nom_dbold in settings_local.DATABASES: + db_conn = connections[nom_dbold] + try: + cursor = db_conn.cursor() # Create a cursor object using the database connection + cursor.execute('SELECT 1') # Run a query to check if the connection is working + result = cursor.fetchone()[0] # Fetch the result of the query + cursor.close() + except OperationalError: + return render( + request, + '../templates/errorConnexio.html', + { + 'nom':nom_dbold, + 'usuari':usuari_dbold, + },) + else: + return render(request,'404.html',{},) + return None + +# Per poder renderitzar en un template un diccionario que té clau variable +@register.filter +def get_value(dictionary, key): + return dictionary.get(key) + + +############################ +# 1r PAS # CONNEXIÓ DB OLD # +############################ + +@login_required +@group_required(['direcció','administradors']) +def connexioDBold(request): + + if request.method == 'POST': + + form = dades_dboldForm(request.POST) + if form.is_valid(): + global dades_connexio_dbold + dades_connexio_dbold = { + 'name_dbold':form.cleaned_data['nom'], + 'user_dbold':form.cleaned_data['usuari'], + 'pass_dbold':form.cleaned_data['contrasenya'], + } + + settings_local.DATABASES[dades_connexio_dbold.get('name_dbold')] = { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': dades_connexio_dbold.get('name_dbold'), + 'USER': dades_connexio_dbold.get('user_dbold'), + 'PASSWORD': dades_connexio_dbold.get('pass_dbold'), + 'HOST': 'localhost', + 'PORT': '5432', + 'ATOMIC_REQUESTS': False, + 'AUTOCOMMIT': True, + 'CONN_MAX_AGE': 0, + 'OPTIONS': {}, + 'TIME_ZONE': None + } + + connexio = check_database_connection(request) + if connexio is None: + return redirect('/importaActuacions/sincronitzaProfessionals') + else: + return connexio + + else: + head = u"Importació de les actuacions d'un altre curs al curs actual" + titol_formulari =u"Paràmetres de connexió amb la base de dades del curs anterior." + form = dades_dboldForm() + + return render( + request, + 'connexioDBold.html', + { + 'form': form, + 'head': head, + 'titol_formulari':titol_formulari, + }, + ) + +####################################### +# 2n PAS # SINCRONITZAR PROFESSIONALS # +####################################### + +@login_required +@group_required(['direcció','administradors']) +def sincronitzaProfessionals(request): + global dades_connexio_dbold + global usernames_oldb_to_usernames_curs_actual_dict + nom_dbold = dades_connexio_dbold.get('name_dbold') + + # Redirecció si hi ha un accès indegut i conseqüent error en la connexió a la BD antiga + connexio = check_database_connection(request) + if connexio is not None: + return connexio + + head = u"Sincronització entre cursos dels professionals del centre educatiu" + + usuaris_olddb_id_username_firstname_lastname_list = list(User.objects. + using(nom_dbold). + values_list('id', 'username', 'first_name', 'last_name'). + exclude(username__startswith = 'almn'). + exclude(username__startswith = 'admin'). + exclude(username__startswith = 'conser'). + exclude(username = 'TP'). + order_by('username')) + + usuaris_id_username_firstname_lastname_list = list(User.objects. + values_list('id', 'username', 'first_name', 'last_name'). + exclude(username__startswith = 'almn'). + exclude(username__startswith = 'admin'). + exclude(username__startswith = 'conser'). + exclude(username = 'TP'). + order_by('username')) + + actuacions_professionalid_olddb_list_tuples = list(Actuacio.objects. + using(nom_dbold). + values_list('professional_id')) + + actuacions_professionalid_olddb_list = [] + actuacions_professionalid_olddb_list = [actuacio[0] for actuacio in actuacions_professionalid_olddb_list_tuples] + del actuacions_professionalid_olddb_list_tuples + + # Netejar la llista per quedar-se amb els valors únics. + professionals_amb_actuacions_olddb_list = list(set(actuacions_professionalid_olddb_list)) + + for i, identificador_professional in enumerate(professionals_amb_actuacions_olddb_list): + for usuari in usuaris_olddb_id_username_firstname_lastname_list: + if identificador_professional == usuari[0]: + professionals_amb_actuacions_olddb_list[i] = usuari + break + + usernames_usuaris = [] # Llista dels usernames dels usuaris actuals + usernames_usuaris_orfes_oldb = [] # Llista dels useranames dels usuaris antics que no es troben al curs actual + professionals_repetits = [] # Per controlar que no s'associen un professional actual amb més d'un profesional antic + + # Només em quedo els usernames. La resta per renderritzar en el template + usernames_usuaris = [usuari[1] for usuari in usuaris_id_username_firstname_lastname_list] + + # Filtre per trobar els usuaris antics que han quedat sense relacionar + usernames_usuaris_orfes_oldb = [usuari_oldb for usuari_oldb in professionals_amb_actuacions_olddb_list if usuari_oldb[1] not in usernames_usuaris] + + if request.method == 'POST': + + # Es recull la relació entre els professionals antics i actuals, eliminant la primera entrada. + usernames_oldb_to_usernames_curs_actual_dict = dict(request.POST) + usernames_oldb_to_usernames_curs_actual_dict.pop('csrfmiddlewaretoken') + usernames_claus = list(dict.keys(request.POST)) + usernames_claus.pop(0) + + # Si algun professinal s'ha deixat sense relacionar, es millor que mantingui el seu propi username. + # Els valors del diccionari son llistes d'un sol element =:-O Aprofito per eliminar aquestes llistes. + for clau in usernames_claus: + username_associat_list = usernames_oldb_to_usernames_curs_actual_dict.get(clau) + if not username_associat_list[0]: username_associat_list[0] = clau + usernames_oldb_to_usernames_curs_actual_dict[clau] = username_associat_list[0] + + # Únic procediment per trobar associacions duplicades que m'ha funcionat + visited = set() + professionals_repetits = [x for x in list(dict.values(usernames_oldb_to_usernames_curs_actual_dict)) if x in visited or (visited.add(x) or False)] + + if not professionals_repetits: + return redirect('/importaActuacions/importantActuacions') + + return render( + request, + '../templates/sincronitzaProfessionals.html', + { + 'head':head, + 'usuaris_orfes_oldb':usernames_usuaris_orfes_oldb, + 'usuaris_curs_actual':usuaris_id_username_firstname_lastname_list, + 'professionals_repetits':professionals_repetits, + 'assignacio_anterior':usernames_oldb_to_usernames_curs_actual_dict, + },) + + +@login_required +@group_required(['direcció','administradors']) +def importantActuacions(request): + + global dades_connexio_dbold + global usernames_oldb_to_usernames_curs_actual_dict + nom_dbold = dades_connexio_dbold.get('name_dbold') + + # Redirecció si hi ha un accès indegut i conseqüent error en la connexió a la BD antiga + connexio = check_database_connection(request) + if connexio is not None: + return connexio + if not usernames_oldb_to_usernames_curs_actual_dict: # Per evitar un accès amb url directe + return render(request,'404.html',{},) + + head = u"Llistat detallat de les actuacions que s'importaran des d'un altre curs." + + # Informació a mostrar en el template. + actuacions_sense_alumne_curs_actual = [] # Actuacions especials donat que NO s'importaran a la BD del curs actual. + actuacions_sense_professional = [] # actuacions especials donat se'n modificarà el seu text i SÍ que s'importaran a la BD del curs actual. + alumnes_desmatriculats = [] # Alumnat que ja no hi és. + professionals_foracentre = [] # Professionals que ja no hi son. + + ############################### + # 3r PAS # EXTRACCIÓ DE DADES # + ############################### + + # Dades de l'alumnat + alumnes_id_ralc_nom_cognoms_list = list(Alumne.objects. + values_list('id', 'ralc', 'nom', 'cognoms'). + order_by('cognoms')) + + alumnes_olddb_id_ralc_nom_cognoms_list = list(Alumne.objects. + using(nom_dbold). + values_list('id', 'ralc', 'nom', 'cognoms'). + order_by('cognoms')) + + # Dades dels professionals + usuaris_username_id = dict(User.objects. + values_list('username','id'). + exclude(username__startswith = 'almn'). + order_by('username')) + + usuaris_olddb_id_username_firstname_lastname_llista_de_tuples = list(User.objects. + using(nom_dbold). + values_list('id', 'username', 'first_name', 'last_name'). + exclude(username__startswith = 'almn'). + exclude(username__startswith = 'admin'). + exclude(username__startswith = 'conser'). + exclude(username = 'TP'). + order_by('username')) + + # Conversió de les tuples en llistes, per poder modificar els valors. + usuaris_olddb_id_username_firstname_lastname_list = [] + usuaris_olddb_id_username_firstname_lastname_list = [list(usuari) for usuari in usuaris_olddb_id_username_firstname_lastname_llista_de_tuples] + del usuaris_olddb_id_username_firstname_lastname_llista_de_tuples + + # Dades de les actuacions + actuacions_olddb_list = list(Actuacio.objects. + using(nom_dbold). + values(). + order_by('id')) + + actuacions_id_data_alumneid_list = list(Actuacio.objects. + values_list('id', 'moment_actuacio', 'alumne_id'). + order_by('id')) + + + ############################## + # 4t PAS # 1r CANVI # OLD BD # + ############################## + + # 4.1 Substitució dels usernames antics dels professionals pels actuals usernames. + for professional in usuaris_olddb_id_username_firstname_lastname_list: + username_oldb_professional = professional[1] + if usernames_oldb_to_usernames_curs_actual_dict.get(username_oldb_professional): + username_professional = usernames_oldb_to_usernames_curs_actual_dict.get(username_oldb_professional) + professional[1] = username_professional + + # Substitucions a les actuacions + for actuacio in actuacions_olddb_list: + + # 4.2 Substitució de l'identificador de l'alumne pel RALC, que és universal. + # El camps alumne_id té foreign_key amb el camp id de la taula alumnes_alumne. + # No obstant es deixa la marca (None) si no es trobés l'alumne al llistat general d'alumnes. + identificador_alumne = actuacio.get('alumne_id') + for alumne in alumnes_olddb_id_ralc_nom_cognoms_list: + if identificador_alumne == alumne[0]: + ralc_alumne = alumne[1] + nom_alumne = alumne[2] + cognoms_alumne = alumne[3] + actuacio.update({'alumne_id':ralc_alumne}) + actuacio.update({'nom_alumne':nom_alumne}) + actuacio.update({'cognoms_alumne':cognoms_alumne}) + + + # 4.3 Substituint l'identificador del professional per l'username, + # El camp professional_id té foreign_key amb algun lloc que no he sabut trobar + # No obstant es deixa la marca (None) si no es trobés el professional al llistat general d'usuaris. + identificador_professional = actuacio.get('professional_id') + for professional in usuaris_olddb_id_username_firstname_lastname_list: + if identificador_professional == professional[0]: + username_professional = professional[1] + nom_professional = professional[2] + cognoms_professional = professional[3] + #Valorar si no fer-ho i afegir-ho per codi al template emprant professionals_foracentre + actuacio.update({'professional_id':username_professional}) + actuacio.update({'nom_professional':nom_professional}) + actuacio.update({'cognoms_professional':cognoms_professional}) + + + ################################# + # 5é PAS # 2n CANVI # BD ACTUAL # + ################################# + + for actuacio in actuacions_olddb_list: + + # Substituint el ralc de l'actuació per l'identificador de l'alumne a la nova BD. + # Si no troba l'alumne és que no s'ha maticulat, se'n guarden algunes dades + # i es deixa la marca (None) per excloure-la més endavant amb facilitat. + + ralc_alumne = actuacio.get('alumne_id') # Després del 1r canvi, el camp alumne_id conté el ralc, no l'alumne_id + identificador_alumne = False + for alumne in alumnes_id_ralc_nom_cognoms_list: + if ralc_alumne == alumne[1]: + identificador_alumne = alumne[0] + + if ralc_alumne and identificador_alumne: + actuacio.update({'alumne_id':identificador_alumne}) + elif ralc_alumne and not identificador_alumne: # Si l'alumne ja no hi és, és un alumne desmatriculat. + + # Guardem les actuacions associades a alumnes desmatriculats en una llista específica. + actuacions_sense_alumne_curs_actual.append(deepcopy(actuacio)) + + # Guardem els ralcs, noms i cognoms dels alumnes ara desmatriculats que tenen actuacions a la BD antiga + for alumne in alumnes_olddb_id_ralc_nom_cognoms_list: + if ralc_alumne == alumne[1]: + nom_alumne = alumne[2] + cognoms_alumne = alumne[3] + dades_alumne_desmatriculat = {'ralc':ralc_alumne,'nom':nom_alumne,'cognoms':cognoms_alumne} + if not alumnes_desmatriculats or dades_alumne_desmatriculat not in alumnes_desmatriculats: + alumnes_desmatriculats.append(dades_alumne_desmatriculat) + + actuacio.update({'alumne_id':None}) # Marcada per a no importar-la a la BD nova + + # Substituint l'username del professional pel seu identificador a la nova BD, + username_professional = actuacio.get('professional_id') # Després del 1r canvi, el camp professional_id conté l'username, no l'identificador del professional + identificador_professional = usuaris_username_id.get(username_professional) + if username_professional and identificador_professional: + actuacio.update({'professional_id':identificador_professional}) + elif username_professional and not identificador_professional: # Si el professional ja no és al centre... + + # Guardem les actuacions sense profesional a la BD nova en una llista específica. + actuacions_sense_professional.append(deepcopy(actuacio)) + + # Guardem els usernames, noms i cognoms, dels professionals que ja no hi son al centre. + for professional in usuaris_olddb_id_username_firstname_lastname_list: + if username_professional == professional[1]: + nom_professional = professional[2] + cognoms_professional = professional[3] + dades_professional_foracentre = {'username':username_professional,'nom':nom_professional,'cognoms':cognoms_professional} + if not professionals_foracentre or dades_professional_foracentre not in professionals_foracentre: + professionals_foracentre.append(dades_professional_foracentre) + + # S'afegeix una frase a l'inici del text de l'actuació per deixar constància de qui i quan es va fer l'actuació. + text_actuacio = u'El o la professional '+nom_professional+' '+cognoms_professional + text_actuacio += ' ('+username_professional+')' + text_actuacio += ' va redactar aquesta actuació però ja no es troba al centre educatiu.' + text_actuacio += ' No obstant, a continuació se\'n mostra el text original: \n\n' + text_actuacio += actuacio.get('actuacio') + + actuacio.update({'actuacio':text_actuacio}) + + + # S'elimina l'username del camp professional_id. Aquest camp pot ser NULL a la BD. + actuacio.update({'professional_id':None}) + + ################################## + # 6é PAS # PREPARANT LA INSERCIÓ # + ################################## + + actuacions_ja_importades = [] + global actuacions_a_importar + actuacions_a_importar = [] + + for actuacio_olddb in actuacions_olddb_list: + if actuacio_olddb.get('alumne_id')!=None: # Només s'hi afegiran totes les actuacions associades a un alumne matriculat en el curs actual. + + # Comprovació para no importar una actuació que ja va ser importada per algú + # la data exacta és gairebé impossible que es pugui repetir perquè haurien d'haver-hi dues actuacions creades en el mateix segon. + data_actuacio_a_importar = actuacio_olddb.get('moment_actuacio') + coincidencia = False + for actuacio in actuacions_id_data_alumneid_list: + data_actuacio_curs_actual = actuacio[1] + if data_actuacio_a_importar == data_actuacio_curs_actual: + coincidencia = True + + if not coincidencia: + actuacions_a_importar.append(actuacio_olddb) + else: + actuacions_ja_importades.append(actuacio_olddb) + + return render( + request, + '../templates/importacio.html', + { + 'nom_dbold':nom_dbold, + 'head':head, + 'alumnes_desmatriculats':alumnes_desmatriculats, + 'actuacions_sense_alumne_curs_actual':actuacions_sense_alumne_curs_actual, + 'professionals_foracentre':professionals_foracentre, + 'actuacions_sense_professional':actuacions_sense_professional, + 'actuacions_olddb_list':actuacions_olddb_list, + 'actuacions_ja_importades': actuacions_ja_importades, + 'actuacions_a_importar': actuacions_a_importar, + },) + + +##################### +# 7é PAS # INSERCIÓ # +##################### + +@login_required +@group_required(['direcció','administradors']) +def importacio(request): + + global actuacions_a_importar + global actuacions_pel_bulkcreate + + head = u"Resultats de la importació de les actuacions d'un curs anterior" + + # Redirecció si hi ha un accès indegut i conseqüent error en la connexió a la BD antiga + connexio = check_database_connection(request) + if connexio is not None: + return connexio + if not actuacions_a_importar: + return render(request,'404.html',{},) + + if request.method == 'POST': + actuacions_pel_bulkcreate.clear() + + # Es decideix afegir-hi l'id per facilitar esborrar les actuacions afegides si quelcom ha anat malament. + # PERFER: Una taula amb checkbox per esborrar les actuacions afegides, habilitant el checkbox només d'aquestes actuacions. + ultim_id_abans_actualitzacio = (Actuacio.objects.last()).id + identificador_nova_actuacio = ultim_id_abans_actualitzacio + 1 + + # Preparació de les dades a inserir + for actuacio in actuacions_a_importar: + act = Actuacio( + id = identificador_nova_actuacio, + moment_actuacio = actuacio.get('moment_actuacio'), + qui_fa_actuacio = actuacio.get('qui_fa_actuacio'), + amb_qui_es_actuacio = actuacio.get('amb_qui_es_actuacio'), + assumpte = actuacio.get('assumpte'), + actuacio = actuacio.get('actuacio'), + alumne_id = actuacio.get('alumne_id'), + professional_id = actuacio.get('professional_id'), + ) + actuacions_pel_bulkcreate.append(act) + identificador_nova_actuacio += 1 + + # Inserció de les actuacions de la BD antiga a la BD actual (per fi, badum txas!) + Actuacio.objects.bulk_create(actuacions_pel_bulkcreate) + + # PER ARREGLAR: Renderitzar dues taules paginades amb table2 al mateix template provoca que quan es pagina una taula, l'altra es pagina igual. + + # Per mostrar, un últim cop, les actuacions que s'han importat. + table_actuacions_importades = Table2_llista_actuacions(actuacions_pel_bulkcreate) + table_actuacions_importades.order_by = '-id_actuacio' + RequestConfig(request, paginate={"paginator_class":DiggPaginator , "per_page": 20}).configure(table_actuacions_importades) + + # Per mostrar cóm ha queat finalment la taula de les actuacions + actuacions = Actuacio.objects.all() + table = Table2_llista_actuacions(actuacions) + table.order_by = '-id_actuacio' + RequestConfig(request, paginate={"paginator_class":DiggPaginator , "per_page": 20}).configure(table) + + return render( + request, + '../templates/resultatImportacio.html', + { + 'head': head, + 'table': table, + 'table_actuacions_importades': table_actuacions_importades, + } + ) diff --git a/aula/apps/tutoria/templates/lesMevesActuacions.html b/aula/apps/tutoria/templates/lesMevesActuacions.html index d3dab70c..c3e4d90d 100644 --- a/aula/apps/tutoria/templates/lesMevesActuacions.html +++ b/aula/apps/tutoria/templates/lesMevesActuacions.html @@ -7,13 +7,34 @@ {% block posttaula %} + {% if not veure_dades_actuacions_antigues %} +
-
+ +
+
+
+ + {% if habilita_link_actuacions_antigues %} + +
+ {% endif %} + + {% else %} + +
+
+ Tornar a les actuacions del present curs +
- +
+ {% endif %} + {% endblock %} diff --git a/aula/apps/tutoria/urls.py b/aula/apps/tutoria/urls.py index 53be4232..e2d6df6b 100644 --- a/aula/apps/tutoria/urls.py +++ b/aula/apps/tutoria/urls.py @@ -4,7 +4,10 @@ urlpatterns = [ re_path(r'^lesMevesActuacions/$', tutoria_views.lesMevesActuacions, name="tutoria__actuacions__list"), - + + re_path(r'^lesMevesActuacions/(?P\w+)/$', tutoria_views.lesMevesActuacions, + name="tutoria__actuacionsantigues__list"), + re_path(r'^lesMevesActuacionsPsico/$', tutoria_views.lesMevesActuacions, name="psico__actuacions__list"), diff --git a/aula/apps/tutoria/views.py b/aula/apps/tutoria/views.py index f6185bd8..21851730 100644 --- a/aula/apps/tutoria/views.py +++ b/aula/apps/tutoria/views.py @@ -34,7 +34,7 @@ from aula.apps.tutoria.models import Actuacio, Tutor, SeguimentTutorialPreguntes,\ SeguimentTutorial, SeguimentTutorialRespostes, ResumAnualAlumne,\ CartaAbsentisme -from aula.apps.alumnes.models import Alumne, Grup, AlumneGrupNom, AlumneGrup +from aula.apps.alumnes.models import Alumne, Grup, AlumneGrupNom, AlumneGrup, Curs from django.forms.models import modelform_factory, modelformset_factory from django import forms from django.db.models import Min, Max, Q @@ -234,30 +234,42 @@ def tutorPosaExpulsioPerAcumulacio(request, pk): @login_required @group_required(['professors','professional']) -def lesMevesActuacions(request): +def lesMevesActuacions(request, incloureActuacionsAntigues=False): credentials = tools.getImpersonateUser(request) (user, _ ) = credentials - professional = User2Professional( user ) + professional = User2Professional( user ) - actuacions = ( Actuacio - .objects - .filter( professional = professional ) - .distinct() - ) + data_inici_curs = list(Curs.objects.values_list('data_inici_curs').order_by('data_inici_curs').first())[0] + + actuacions = Actuacio.objects.filter( professional = professional, moment_actuacio__gte = data_inici_curs ).distinct() + + # Cal fer la cerca, perquè si n' hi ha de velles, s'habilita el botó per veure-les. + actuacions_antigues = Actuacio.objects.filter( professional = professional, moment_actuacio__lte = data_inici_curs ).distinct() + + habilita_link_actuacions_antigues = False + veure_dades_actuacions_antigues = False - table = Table2_Actuacions( list( actuacions ) ) - table.order_by = '-moment_actuacio' + if not incloureActuacionsAntigues or incloureActuacionsAntigues != 'actuacionsAntigues': #Cas normal table = Table2_Actuacions( actuacions ) + if actuacions_antigues: habilita_link_actuacions_antigues = True + else: + table = Table2_Actuacions( actuacions_antigues ) + veure_dades_actuacions_antigues = True + table.order_by = 'moment_actuacio' + RequestConfig(request, paginate={"paginator_class":DiggPaginator , "per_page": 20}).configure(table) - + return render( request, 'lesMevesActuacions.html', {'table': table, + 'habilita_link_actuacions_antigues':habilita_link_actuacions_antigues, + 'veure_dades_actuacions_antigues':veure_dades_actuacions_antigues, } ) + # # -----------------------------### OBSOLET ###----------------------------- # @@ -428,11 +440,15 @@ def editaActuacio(request, pk): professor = User2Professor(user) #seg------------------- - te_permis = (l4 or - actuacio.professional.pk == user.pk or + + te_permis = ( + l4 or professor in actuacio.alumne.tutorsDeLAlumne() or user.groups.filter(name__in= [u'direcció', u'psicopedagog'] ).exists() - ) + ) + if actuacio.professional: # Si l'actuació va ser importada, el professional ja no és al centre. + te_permis = (te_permis or actuacio.professional.pk == user.pk) + if not te_permis: raise Http404() @@ -448,6 +464,12 @@ def editaActuacio(request, pk): formActuacioF = modelform_factory(Actuacio, exclude=['alumne','professional'], widgets = widgets) #formActuacioF.base_fields['moment_actuacio'].widget = forms.DateTimeInput(attrs={'class':'DateTimeAnyTime'} ) formset = [] + + #Si la data de l'actuació és d'un altre curs, no es podrà editar + data_inici_curs = list(Curs.objects.values_list('data_inici_curs').order_by('data_inici_curs').first())[0] + if actuacio.moment_actuacio.date() < data_inici_curs: actuacioNoEditable = True + else: actuacioNoEditable = False + if request.method == 'POST': formActuacio = formActuacioF(request.POST, instance = actuacio ) @@ -484,6 +506,7 @@ def editaActuacio(request, pk): 'infoForm': infoForm, 'head': 'Actuació' , 'titol_formulari': u"Edició d'una actuació", + 'actuacioNoEditable': actuacioNoEditable, }, ) diff --git a/aula/settings_dir/common.py b/aula/settings_dir/common.py index 4eff2ca3..2b8e4577 100644 --- a/aula/settings_dir/common.py +++ b/aula/settings_dir/common.py @@ -219,6 +219,7 @@ 'aula.apps.extUntis', 'aula.apps.matricula', 'aula.apps.extPreinscripcio', + 'aula.apps.importaActuacions', ] #select2 diff --git a/aula/templates/formset.html b/aula/templates/formset.html index d0fb11b1..5e5a869c 100644 --- a/aula/templates/formset.html +++ b/aula/templates/formset.html @@ -113,11 +113,13 @@ {% endif %} {% endfor %} {% if formset %} + {% if not actuacioNoEditable %}

+ {% endif %} {% endif %} {% block postform %} {% endblock %} diff --git a/aula/urls.py b/aula/urls.py index 50d015f5..df898d59 100644 --- a/aula/urls.py +++ b/aula/urls.py @@ -47,6 +47,7 @@ re_path(r'^extUntis/', include('aula.apps.extUntis.urls')), re_path(r'^matricula/', include('aula.apps.matricula.urls', namespace='matricula')), re_path(r'^extPreinscripcio/', include('aula.apps.extPreinscripcio.urls')), + re_path(r'^importaActuacions/', include('aula.apps.importaActuacions.urls')), # ImportaActuacions - rfern26@xtec.cat @rafatecno1 # Uncomment the next line to enable the admin: path('admin/', admin.site.urls), # Login i logout automàtics @@ -57,7 +58,6 @@ re_path(r'^site-css/(?P.*)$', serve,{'document_root': site_media_site_css}), re_path(r'^error500$', TemplateView.as_view(template_name='500.html') ), re_path('^private-media/', include(private_storage.urls)), - ] diff --git a/aula/utils/menu.py b/aula/utils/menu.py index 1f2a54fa..5ee4b0bf 100644 --- a/aula/utils/menu.py +++ b/aula/utils/menu.py @@ -16,6 +16,10 @@ def calcula_menu( user , path, sessioImpersonada ): if not user.is_authenticated: return + + # No permet impersonació com a administrador + if sessioImpersonada and Group.objects.get_or_create(name= 'administradors' )[0] in user.groups.all(): + return #mire a quins grups està aquest usuari: al = Group.objects.get_or_create(name= 'alumne' )[0] in user.groups.all() @@ -266,6 +270,7 @@ def calcula_menu( user , path, sessioImpersonada ): ("Aules", 'gestio__aula__assignacomentari', di, None), ("Material", 'gestio__recurs__assignacomentari', di, None), ("Reprograma", 'administracio__sincronitza__regenerar_horaris', di , None ), + ("Importar actuacions d'un curs antic", 'importaActuacions__connexio__DB', di , None ), ), ), ("Reset Passwd", 'administracio__professorat__reset_passwd', di, None, None ),