Requisiti

sudo apt install xmlsec1 mariadb-server libmariadbclient-dev python3-dev python3-pip libssl-dev libmariadb-dev-compat libsasl2-dev libldap2-dev

pip3 install virtualenv
virtualenv -ppython3 helpdesk.env
source helpdesk.env/bin/activate

Download del software e dipendenze

git clone https://github.com/UniversitaDellaCalabria/uniTicket.git
cd uniTicket
pip3 install -r requirements

Convert HTML to PDF using Webkit (QtWebKit)

wget https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.buster_amd64.deb
sudo dpkg -i wkhtmltox_0.12.5-1.buster_amd64.deb
sudo apt install -f

Setup parametri

cd uni_ticket_project

# copy and modify as your needs
cp settingslocal.py.example settingslocal.py

Nel file di configurazione generale uni_ticket_project/settingslocal.py è possibile:

  • Aggiungere/disabilitare applicationi django in INSTALLED_APPS

  • Definire il model da utilizzare per la gestione degli utenti

# user model fpr auth
AUTH_USER_MODEL = "accounts.User"
  • Definire i formati delle date da utilizzare

DEFAULT_DATE_FORMAT = '%Y-%m-%d'
DEFAULT_DATETIME_FORMAT = '{} %H:%M'.format(DEFAULT_DATE_FORMAT)
DATE_INPUT_FORMATS = [DEFAULT_DATE_FORMAT, '%d/%m/%Y']

# override globals
DATE_INPUT_FORMATS = [DEFAULT_DATE_FORMAT, '%Y-%m-%d']
DATETIME_INPUT_FORMATS = [DEFAULT_DATETIME_FORMAT, f'%Y-%m-%d {DEFAULT_TIME_FORMAT}']

# for javascript datepickers
# BootstrapItalia datepicker
JS_DEFAULT_DATE_FORMAT = "dd/MM/yyyy"
# Cutstom datetimepicker
JS_DEFAULT_DATETIME_FORMAT = 'DD/MM/Y hh:mm'
  • Definire l’ADMIN_PATH

  • Definire i database

DATABASES = {
  'default': {
    'ENGINE': 'django.db.backends.mysql',
    'NAME': 'uniauth',
    'HOST': 'localhost',
    'USER': 'that-user',
    'PASSWORD': 'that-password',
    'PORT': '',
    'OPTIONS': {'init_command': "SET sql_mode='STRICT_TRANS_TABLES'"}
  },
}
  • Selezionare i widget da applicare ai campi dei form

CUSTOM_WIDGETS = {
    'BaseDateField': 'bootstrap_italia_template.widgets.BootstrapItaliaDateWidget',
    # 'BaseDateTimeField': 'bootstrap_italia_template.widgets.BootstrapItaliaTimeWidget',
    #'CustomSelectBoxField': 'bootstrap_italia_template.widgets.BootstrapItaliaSelectWidget',
    'CustomRadioBoxField': 'bootstrap_italia_template.widgets.BootstrapItaliaRadioWidget',
    # 'BaseDateField': 'django.forms.widgets.DateInput',
    # 'DateField': 'django.forms.widgets.DateInput',
    # 'CustomSelectBoxField': 'django.forms.widgets.Select',
    # 'CustomRadioBoxField': 'django.forms.widgets.RadioSelect',
}
  • Definire parametri relativi alla configurazione delle app «chat» e «channels»

# chat: message to load in a conversation from history
MESSAGES_TO_LOAD = 1500

if "channels" in INSTALLED_APPS:
ASGI_APPLICATION = 'uni_ticket_project.routing.application'
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            "hosts": [('127.0.0.1', 6379)],
        },
    },
}
  • Definire i parametri per la criptazione basata su RSA dei token che viaggiano negli URL

# UNITICKET JWE support
UNITICKET_JWE_RSA_KEY_PATH = 'saml2_sp/saml2_config/certificates/key.pem'
UNITICKET_JWE_ALG = "RSA-OAEP"
UNITICKET_JWE_ENC = "A128CBC-HS256"
# end JWE support
  • Definire secret_key e salt per la criptazione del codice CAPTCHA

# CAPTCHA encryption
CAPTCHA_SECRET = b'secret'
CAPTCHA_SALT = b'salt'
# end CAPTCHA encryption
  • Definire la validità del CAPTCHA (in millisecondi)

CAPTCHA_EXPIRATION_TIME = 45000 # milliseconds
  • Configurare le impostazioni del protocollo informatico (ArchiPRO)

# PROTOCOLLO, questi valori possono variare sulla base di come
# vengono istruite le pratiche all'interno del sistema di protocollo di riferimento

CLASSE_PROTOCOLLO = 'archipro_ws.protocollo'

# XML flusso
PROTOCOL_XML = """<Segnatura xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Intestazione>
<Oggetto>{oggetto}</Oggetto>
<Identificatore>
<CodiceAmministrazione>UNICAL</CodiceAmministrazione>
<CodiceAOO>{aoo}</CodiceAOO>
<Flusso>E</Flusso>
</Identificatore>
<Mittente>

<Dipendente id="{identificativo_dipendente}">
<Denominazione>{denominazione_persona}</Denominazione>
</Dipendente>

<Studente id="{identificativo_utente}">
<Denominazione>{denominazione_persona}</Denominazione>
</Studente>

<Persona id="{id_persona}">
<Nome>{nome_persona}</Nome>
<Cognome>{cognome_persona}</Cognome>
<Denominazione>{denominazione_persona}</Denominazione>
</Persona>

</Mittente>
<Destinatario>
<Amministrazione>
<Denominazione>UNICAL</Denominazione>
<CodiceAmministrazione>UNICAL</CodiceAmministrazione>
<IndirizzoTelematico tipo="smtp">amministrazione@pec.unical.it</IndirizzoTelematico>
<UnitaOrganizzativa id=""/>
</Amministrazione>
</Destinatario>
<Classifica>
<CodiceTitolario>{id_titolario}</CodiceTitolario>
</Classifica>
<!--  Informazioni sul fascicolo  -->
<Fascicolo numero="{fascicolo_numero}" anno="{fascicolo_anno}"/>
</Intestazione>
<Descrizione>
<Documento id="1" nome="{nome_doc}">
<DescrizioneDocumento>{nome_doc}</DescrizioneDocumento>
<TipoDocumento>{tipo_doc}</TipoDocumento>
</Documento>
<Allegati>
<!-- Allegati -->
</Allegati>
</Descrizione>
<ApplicativoProtocollo nome="ArchiPRO">
<Parametro nome="agd" valore="{agd}"/>
<Parametro nome="uo" valore="{uo}"/>
</ApplicativoProtocollo>
</Segnatura>
"""

# DEFAULT EMAIL
PROT_EMAIL_DEFAULT = 'default@email.com'

# TEST
PROT_TEST_AOO = 'default_aoo'
PROT_FASCICOLO_DEFAULT = 'default_fascicolo'
PROT_FASCICOLO_ANNO_DEFAULT = 'default_year'
PROT_AGD_DEFAULT = 'default_agd'
PROT_UO_DEFAULT = 'default_uo'
# PROT_UO_ID_DEFAULT = 'default_uo_id'
PROT_TITOLARIO_DEFAULT = 'default_titolario'

PROT_URL = 'url_test'
PROT_TEST_LOGIN = 'test_login'
PROT_TEST_PASSW = 'test_passw'
  • Consentire ai super utenti Django di accedere a tutte le strutture in frontend

# superusers view all
SUPER_USER_VIEW_ALL = True
  • Definire i parametri per la localizzazione

# Internationalization
# Set to False to avoid problems with javascript datepickers
# (that use the DATE_INPUT_FORMATS and DATETIME_INPUT_FORMATS)
# The template uses {% localize on %} tag to localize dates
USE_L10N = False

# localization
LANGUAGES = (
  ('it', _('Italiano')),
  ('en', _('Inglese')),
)

LANGUAGE_CODE = 'it'
LOCALE_PATHS = (
    os.path.join(BASE_DIR, "locale"),
)

TIME_ZONE = 'Europe/Rome'

Nel file di configurazione uni_ticket/settings.py è possibile individuare (ed eventualmente sovrascrivere in settingslocal.py):

  • I nomi delle cartelle nelle quali verranno conservati gli allegati

# system attachments folders
LOGOS_FOLDER = 'logos'
STRUCTURES_FOLDER = 'structures'
TICKET_ATTACHMENT_FOLDER = 'ticket'
TICKET_CATEGORIES_FOLDER = 'categories'
TICKET_MESSAGES_ATTACHMENT_SUBFOLDER = 'messages'
TICKET_TASK_ATTACHMENT_SUBFOLDER = 'task'
CATEGORY_CONDITIONS_ATTACHMENT_SUBFOLDER = 'conditions'
  • Il parametro che consente di mostrare la priorità dei ticket agli utenti

# show ticket priority to simple userse
SIMPLE_USER_SHOW_PRIORITY = False
  • ID e Label del checkbox di accettazione delle clausole obbligatorie

# category conditions form field
TICKET_CONDITIONS_FIELD_ID = 'condizioni_field_id'
TICKET_CONDITIONS_TEXT = _('Dichiara altresì di aver letto '
                           'e compreso quanto scritto sopra '
                           'e di assumere ogni responsabilità '
                           'su quanto di seguito dichiarato')
  • La denominazione dei campi oggetto e descrizione dei form per la creazione dei ticket

# new ticket heading text (user informations)
SHOW_HEADING_TEXT = True
TICKET_HEADING_TEXT = _('Soggetto richiedente: <b>{user}</b>'
                        '<br><span class="x-small">[{taxpayer}]</span>')

# new ticket static form fields
# ticket subject
TICKET_SUBJECT_ID = 'ticket_subject'
TICKET_SUBJECT_LABEL = _('Oggetto della Richiesta')
TICKET_SUBJECT_HELP_TEXT = _("Ulteriore specificazione o "
                             "personalizzazione dell'Oggetto della Richiesta")

# ticket description
TICKET_DESCRIPTION_ID = 'ticket_description'
TICKET_DESCRIPTION_LABEL = _('Descrizione')
TICKET_DESCRIPTION_HELP_TEXT = ('Ulteriore Descrizione della Richiesta, '
                                'eventuali note del Richiedente')
  • I livelli di priorità da assegnare ai ticket

PRIORITY_LEVELS = (
                    ('-2',_('Molto alta')),
                    ('-1',_('Alta')),
                    ('0',_('Normale')),
                    ('1',_('Bassa')),
                    ('2',_('Molto bassa')),
                  )
  • La soglia massima di ticket giornalieri per utente

# 0 = unlimited
MAX_DAILY_TICKET_PER_USER = 10
  • La denominazione di ogni tipologia di utente per la definizione degli URL

# user contexts
CONTEXT_SIMPLE_USER = _('Utente')

# To change the URLs prefix for every user type
MANAGER_PREFIX = 'Manager'
OPERATOR_PREFIX = 'Operatore'
USER_PREFIX = 'user'

# Do not edit! - START
MANAGEMENT_URL_PREFIX = {'manager': MANAGER_PREFIX,
                         'operator': OPERATOR_PREFIX,
                         'user': USER_PREFIX}
# Do not edit! - END
  • Le definizioni per competenza abbandonata/sola lettura

# ticket competence abandoned
NO_MORE_COMPETENCE_OVER_TICKET = _("Nessuna competenza sul ticket")
# ticket readonly access
READONLY_COMPETENCE_OVER_TICKET = _("Hai accesso al ticket in sola lettura")
  • Il numero minimo di digits per la compressione del contenuto di un ticket

# min ticket content length (digits) to compress
TICKET_MIN_DIGITS_TO_COMPRESS = 90
  • La definizione degli utenti «employee» e «internal user» in base al tipo di organizzazione (università o altro)

# This parameters define the roles of users to open ticket
# If True, an employee is a user that has this parameter filled (in user model)
# If False, an employee is a user that is mapped as OrganizationalStructureOfficeEmployee
EMPLOYEE_ATTRIBUTE_NAME = 'identificativo_dipendente'
EMPLOYEE_ATTRIBUTE_LABEL = 'Matricola dipendente'
# Label
ORGANIZATION_EMPLOYEE_LABEL = 'Dipendenti'
# If True, an internal user (not guest) is a user that has this filled (in user model)
# If False, an internal user is a user that is mapped as OrganizationalStructureOfficeEmployee
USER_ATTRIBUTE_NAME = 'identificativo_utente'
USER_ATTRIBUTE_LABEL = 'Matricola studente'
# Label
ORGANIZATION_USER_LABEL = 'Studenti'
  • I testi delle email che il sistema invia agli utenti

  • Disabilitare la modalità DEBUG per la messa in produzione (Attenzione: il servizio in produzione richiede HTTPS)

Creazione Database

# create your MysqlDB
export USER='that-user'
export PASS='that-password'
export HOST='%'
export DB='uniauth'

# tested on Debian 10
sudo mysql -u root -e "\
CREATE USER IF NOT EXISTS '${USER}'@'${HOST}' IDENTIFIED BY '${PASS}';\
CREATE DATABASE IF NOT EXISTS ${DB} CHARACTER SET = 'utf8' COLLATE = 'utf8_general_ci';\
GRANT ALL PRIVILEGES ON ${DB}.* TO '${USER}'@'${HOST}';"

Creazione tabelle e superuser

./manage.py migrate
./manage.py createsuperuser

Template Bootstrap Italia

Di default, il sistema si presenta con il template customizzato per l’Università della Calabria. Per utilizzare la versione standard di Bootstrap Italia basta modificare la riga 6 del file uniTicket/uni_ticket_bootstrap_italia_template/base.html come segue

{% extends 'bootstrap-italia-base.html' %}

e, se si desidera, commentare l’app django_unical_bootstrap_italia dalle INSTALLED_APPS in settingslocal.py.

Run

./manage.py runserver

Produzione

Ricorda di eseguire compilemessages per attuare la localizzazione e compilescss/collectstatic per compilare e copiare tutti i file statici nelle cartelle di produzione:

./manage.py compilemessages
./manage.py compilescss
./manage.py collectstatic

Per un ulteriore controllo in fase di debug è possibile utilizzare i comandi seguenti con uwsgi:

/etc/init.d/uni_ticket stop
uwsgi --ini /opt/uni_ticket/uwsgi_setup/uwsgi.ini.debug

Migrazione dalla v1.x alla v2.0.0

Questa semplice guida consente di aggiornare agevolmente una istanza di uniTicket v1.x alla versione 2.

01 - Stoppare il servizio

/etc/init.d/uni_ticket stop

02 - Export dei ContentType

E” necessario ricostruire la tabella dei ContentType, generata automaticamente da Django all’applicazione delle migrazioni, per mantenere la consistenza delle FK utilizzate dai log.

# CLI Django

from django.contrib.contenttypes.models import ContentType

ct = ContentType.objects.all()

# old_conf sarà quindi una lista di dizionari
old_conf = []
for cct in ct:
   old_conf.append({'pk': cct.pk,
                      'app_label': cct.app_label,
                      'model': cct.model})
print(old_conf)

Copiare old_conf su un file di testo, ci servirà dopo

02 - DB Backup

# CLI Django

./manage.py dumpdata --exclude auth.permission --exclude contenttypes --exclude sessions --indent 2 > path_to_your_file.json

03 - Path del progetto

  • Rinominare la folder del progetto [path]/uniticket in [path]/uniticket_tmp

  • Creare [path]/uniticket (è preferibile che si usi l’utente linux proprietario di [path]/uniticket)

04 - Download repository

# In [path]/uniticket

git clone https://github.com/UniversitaDellaCalabria/uniTicket.git

05 - Django settings

cp [path]/uniticket/uniticket/uni_ticket_project/settingslocal.py.example [path]/uniticket/uniticket/uni_ticket_project/settingslocal.py

Modificare le variabili opprtune (ignorare la parte dedicata al DB momentaneamente)

06 - Media e statics

Copiare uniticket/data/media e uniticket/data/statics in uniticket/data.

07 - Database

  • Se si intende utilizzare lo stesso DB, eliminare tutte le tabelle presenti altrimenti utilizzare un nuovo DB (opzione consigliata)

  • Aggiornare i dati relativi nel settingslocal.py

08 - Migrazioni

 # CLI Django
./manage.py migrate

09 - Ripristino dei ContentType

Poichè i log dei ticket sono collegati ai ContentType, è necessario sovrascrivere i valori creati da Django nella migrazione iniziale per la consistenza del backup da importare.

Nel seguente script bisogna copiare il contenuto di old_conf stampato al punto 02 - Export dei ContentType

# CLI Django

# in una variabile "old_conf" copiare la lista prodotta allo step 01
old_conf = ...

from django.contrib.contenttypes.models import ContentType

ct = ContentType.objects.all()

# cancella ContentType da aggiornare (quelli presenti nella lista)
to_delete = []
for cct in ct:
    app_label = cct.app_label
    model = cct.model
    for old_ct in old_conf:
        if old_ct['app_label'] == app_label and old_ct['model'] == model:
            to_delete.append(cct.pk)
            break
ct.filter(pk__in=to_delete).delete()

# aggiorna la pk dei ContentType rimasti
# per evitare che questa vada in conflitto con il successivo step di importazione
ct = ContentType.objects.all()
to_delete = []
for cct in ct:
    app_label = cct.app_label
    model = cct.model
    # nuova pk che non vada in conflitto con quelle da importare
    pk = cct.pk + 100
    # cambio il valore dei campi del contenttype di origine
    # con dei valori fake per permettere la creazione di uno nuovo
    cct.app_label = pk
    cct.model = pk
    cct.save()
    to_delete.append(cct.pk)
    # crea nuovo ContentType
    ContentType.objects.create(pk=pk, app_label=app_label, model=model)
ct.filter(pk__in=to_delete).delete()

# ripristina i contenttypes provenienti dal db di origine (lista old_conf)
for old_ct in old_conf:
    ContentType.objects.create(pk=old_ct['pk'],
                               app_label=old_ct['app_label'],
                               model=old_ct['model'])

10 - Load Data

Sostituire nel dump json le seguenti definizioni con nano (https://it.stealthsettings.com/find-replace-nano-linux-os-x-terminal-text-editor.html)

  • matricola_dipendente => identificativo_dipendente

  • matricola_studente => identificativo_utente

Se nel dump sono presenti le tabelle delle app chat e channels abilitarle nelle INSTALLED_APPS del settingslocal e applicare le eventuali migrazioni

# CLI Django

./manage.py loaddata path_to_your_file.json

11 - Campo «ticket.assigned_data»

Questo campo è presente e viene salvato automaticamente nella nuova release quando un ticket viene preso in carico la prima volta. Deve essere inizializzato per tutti i ticket con la data della prima presa in carico. Questo è necessairio solo nel processo di migrazione dalla v1.x alla v2.x.

# CLI Django

from uni_ticket.models import Ticket, TicketAssignment

tickets = Ticket.objects.filter(assigned_date__isnull=True)

for ticket in tickets:
    first_taken = TicketAssignment.objects\
                                  .filter(ticket=ticket,
                                          taken_date__isnull=False)\
                                  .values_list("taken_date", flat=True)\
                                  .first()
    if first_taken:
        ticket.assigned_date = first_taken
        ticket.save()
        print("Assigned data update for ticket ", ticket.code)

12 - Se non ci sono criticità è possibile rimuovere la cartella [path]/uniticket_tmp