Ticketsystem mit Moodle-DB und Moodle-API

Vorlauf

Wenn man an seiner Schule bereits ein Moodle nutzt, bietet es sich an auch dort die anfallenden technischen Probleme zentral zu sammeln. Es gibt zwar spezielle Ticketsysteme, die genau für solche Aufgaben konzipiert sind, allerdings bietet eine Integration in Moodle hier meiner Meinung nach ein paar Vorteile:

  • Keine zusätzliche Plattform/URL, also auch für die Kolleginnen und Kollegen alles unter einem Dach
  • keine zusätzliche Pflege einer weiteren Software, kein weiterer Serverdienst
  • nicht so überladen (professionelle Software ist da oft wesentlich umfangreicher, weil eben viele Fälle abgedeckt werden sollen)

Das Vorgehen ist relativ simpel:

  1. Ein Problem wird von einer Kollegin oder einem Kollegen in die Datenbank eingetragen.
  2. Die Betreuer erhalten eine Nachricht und können aktiv werden.
  3. In der Datenbank kann der aktuelle Zustand des Problems vermerkt werden (Offen, In Bearbeitung, Erledigt)

Damit ist das System für alle transparent einsehbar und man hat eine zentrale Stelle, an der man gucken kann, ob noch offene Probleme bestehen.

Ich möchte hier den 2. Punkt etwas näher ausführen, da Moodle mit Hilfe von Eventbeobachtungen zwar automatisiert Nachrichten verschicken kann, das aber ein wenig beschränkt ist. Aus diesem Grund habe ich eine eigene Lösung umgesetzt.

Der Nachteil bei der Eventbeobachtung ist nämlich, dass keine zusätzlichen Informationen mitgeteilt werden, sondern nur, dass ein neuer Eintrag erfolgt ist. Man ist also immer noch gezwungen die Moodle-Seite zu öffnen und nachzuschauen.

Mit Hilfe der Moodle-API ist es möglich auch von außen an die Informationen zu kommen und diese Informationen dann eigenständig weiter zu verarbeiten. Ich habe versucht das Prinzip einmal grafisch darzustellen:

Ein Python-Skript schickt also an den Moodle-Server eine Anfrage, erhält eine Antwort (in meinem Fall alle Einträge einer Moodle-Datenbank), prüft dann, ob neue Einträge dabei sind und schickt für jeden neuen Eintrag eine Mail.

Die API ist nicht auf Datenbanken beschränkt, man bekommt einen Überblick über die Möglichkeiten unter folgendem Link:

https://docs.moodle.org/dev/Web_service_API_functions

Vorarbeiten

Ticketsystem anlegen

Zuerst muss man in Moodle eine Datenbank anlegen. Hier habe ich die Vorlage “Ticketsystem für Schultechnik” von
https://www.taskcards.de/#/board/a29aa0a2-38bb-4a9a-90e1-4c38ec47f9cc/view
genutzt. Wenn man die Datenbank angelegt hat, kann man sie noch an die eigenen Bedürfnisse anpassen und dann an die API-Abfrage gehen.

Notwendige IDs notieren

Für die API-Abfrage benötigen wir noch die ID der Datenbank, wie sie in Moodle verwaltet wird. Die einfachste Möglichkeit diese ID zu finden ist die Datenbank in Moodle einmal in der Listenansicht aufzurufen. In der Adressleiste findet man dann am Ende der URL die ID “view.php?d=23”, in meinem Fall also ist die ID 23.

Zusätzlich zu der ID der Datenbank benötigt man noch die IDs der einzelnen Felder, wenn man nachher die Inhalte der Eintragungen per Mail verschicken möchte. Dazu wählt man in der Datenbank unter “Felder” jedes einzelne Feld aus. In der URL findet man dann unter “fid” die jeweilige Feld-ID (fid), z. B. …/field.php?d=23&fid=78 ergibt die Feld-ID 78 für das “Status”-Feld.

Moodle-API freischalten und Token erstellen

Um von außen auf die API zugreifen zu können, müssen in der Moodle-Instanz die Webservices freigeschaltet werden. Das geht unter

Website-Administration => Plugins => Webservices

Dort muss man die Webservices aktivierten und REST als Protokoll nehmen.

Anschließend benötigt man noch einen Token, um sich beim Dienst authentifizieren zu können. Diesen Token kann man ebenfalls in den Einstellungen unter

Website-Administration => Plugins => Webservices => Token verwalten

anlegen.

Dann kann man mit folgendem Skript einen Zugriff auf die API durchführen (URL und Token bitte einsetzen)

import requests
import json

URL = 'https://url-zur-moodle-instanz'
DATABASE_ID = '23'
TOKEN = 'DEIN TOKEN'

params = (
    ('moodlewsrestformat', 'json'),
)

data = {
    'databaseid': DATABASE_ID,
    'wsfunction': 'mod_data_get_entries',
    'wstoken': TOKEN,
    'moodlewssettingfilter': 'true',
    'returncontents': '1'
}

response = requests.post(URL + '/webservice/rest/server.php', params=params, data=data)
entries = response.json()
print(entries.keys())

Wenn der Aufruf funktioniert bekommt man eine Antwort, die in response gespeichert wird und die dann in JSON konvertiert wird. Man erhält ein Dictionary mit folgenden keys:

dict_keys(['entries', 'totalcount', 'totalfilesize', 'listviewcontents', 'warnings'])

Die Einträge der Datenbank findet man unter ‘entries’ als Liste vor, d.h. man kann sich die einzelnen Einträge angucken, hier z. B. den ersten in der Liste

> print(entries['entries'][0])

{'id': 440, 'userid': 1078, 'groupid': 0, 'dataid': 23, 'timecreated': 1655579377, 'timemodified': 1655579377, 'approved': False, 'canmanageentry': True, 'fullname': 'Alan Turing', 'contents': [{'id': 2549, 'fieldid': 78, 'recordid': 440, 'content': 'Offen', 'content1': None, 'content2': None, 'content3': None, 'content4': None, 'files': []}, {'id': 2548, 'fieldid': 79, 'recordid': 440, 'content': 'R203', 'content1': None, 'content2': None, 'content3': None, 'content4': None, 'files': []}, {'id': 2547, 'fieldid': 80, 'recordid': 440, 'content': 'Sonstiges', 'content1': None, 'content2': None, 'content3': None, 'content4': None, 'files': []}, {'id': 2546, 'fieldid': 81, 'recordid': 440, 'content': '', 'content1': None, 'content2': None, 'content3': None, 'content4': None, 'files': []}, {'id': 2545, 'fieldid': 77, 'recordid': 440, 'content': 'Beamer verbindet sich weder mit iPad noch mit Computer', 'content1': None, 'content2': None, 'content3': None, 'content4': None, 'files': []}], 'tags': []}

Man sieht, dass man für jeden Eintrag in der Datenbank ein Dictionary erhält. Für den Versand als E-Mail sind die ‘id’, der ‘fullname’ und die Einträge in ‘contents’ interessant, für die man ja oben die notwendigen IDs notiert hatte.
Mit folgendem Code extrahiert man die notwendigen Informationen:

convert = {79: b'Raum/Bereich',
           78: b'Status',
           80: b'Art',
           81: b'Bemerkung',
           77: b'Beschreibung'}

for entry in entries['entries']:
    data = {}
    data[b'Name'] = entry['fullname']
    data[b'id'] = entry['id']
    for dictionary in entry['contents']:
        data[convert[dictionary['fieldid']]]=dictionary['content']
    mail_text = b'\n'
    for key in [b'Name', b'Raum/Bereich', b'Art', b'Beschreibung']:
        mail_text += b': '.join([key, data[key].encode('utf-8')]) + b'\n'
    send_email(data[b'id'], mail_header + mail_text)

Die Funktion send_email sieht dabei folgendermaßen aus:

def send_email(data_id, message=""):
    try: 
        smtp = smtplib.SMTP('SMTP-SERVER', 587) 
        smtp.starttls() 
        smtp.login("USER","PASSWORD") 

        # pruefen, ob Eintrag bereits gesendet wurde 
        with open("ids.txt", "r") as fp:
            ids = json.load(fp)
        if data_id not in ids:
            smtp.sendmail("FROM-MAIL-ADDRESS", "TO-MAIL-ADRESS",message)
            ids.append(data_id)
            with open("ids.txt", "w") as fp:
                json.dump(ids, fp)
        smtp.quit() 
        print ("Email sent successfully!")
    except Exception as ex: 
        print("Something went wrong....",ex)

Die Mail enthält dann für das Beispiel den folgenden Text:

Name: Alan Turing
Raum/Bereich: R203  
Art: Sonstiges  
Beschreibung: Beamer verbindet sich weder mit iPad noch mit Computer

Das Skript wird dann in regelmäßigen Zeitabständen ausgeführt, z. B. mit Hilfe eines Cron-Jobs.

Es sind noch viele Erweiterungen möglich, z. B. könnte man statt eine Mail zu schicken die API von Nextcloud nutzen, um dort eine Karte in einem Kanban-Board anzulegen oder man könnte je nach gewählter Kategorie im Datenbankeintrag an verschiedene Adressen verschicken, oder, oder, oder, ….

Am Ende ist nochmal der komplette Code, für ein einfaches C&P. Die groß geschriebenen Einträge müssen durch die eigenen Werte ersetzt werden.

Mir ist bewusst, dass bei dem kurzen Skript auch noch Optimierungsbedarf besteht, also Fragen und Verbesserungen gerne in die Kommentare.

import requests
import smtplib
import json

# Moodle
URL = 'DEINE MOODLE-URL'
DATABASE_ID = 'DEINE ID'
TOKEN = 'DEIN TOKEN'

# fuer die API-Abfrage
params = (
    ('moodlewsrestformat', 'json'),
)

data = {
    'databaseid': DATABASE_ID,
    'wsfunction': 'mod_data_get_entries',
    'wstoken': TOKEN,
    'moodlewssettingfilter': 'true',
    'returncontents': '1'
}

# fuer die Mails
mail_header = b'''From: Ticketsystem <noreply@logineonrwlms.de>
To: Dein Name <deine Mailadresse>
Subject: Neues Ticket\n'''

convert = {79: b'Raum/Bereich',
           78: b'Status',
           80: b'Art',
           81: b'Bemerkung',
           77: b'Beschreibung'}

def send_email(data_id, message=''):
    try: 
        with open('ids.txt', 'r') as fp:
            ids = json.load(fp)
        smtp = smtplib.SMTP('SMTP-SERVER', 587) 
        smtp.starttls() 
        smtp.login('USER','PASSWORD') 
        # pruefen, ob Eintrag bereits gesendet wurde
        if data_id not in ids:
            smtp.sendmail('FROM-MAIL-ADDRESS', 'TO-MAIL-ADDRESS',message)
            ids.append(data_id)
            with open('ids.txt', 'w') as fp:
                json.dump(ids, fp)
        smtp.quit() 
        print ('Email sent successfully!')
    except Exception as ex: 
        print('Something went wrong....',ex)

response = requests.post(URL + '/webservice/rest/server.php', params=params, data=data)
entries = response.json()
for entry in entries['entries']:
    data = {}
    data[b'Name'] = entry['fullname']
    data[b'id'] = entry['id']
    for dictionary in entry['contents']:
        data[convert[dictionary['fieldid']]]=dictionary['content']
    mail_text = b'\n'
    for key in [b'Name', b'Raum/Bereich', b'Art', b'Beschreibung']:
        mail_text += b': '.join([key, data[key].encode('utf-8')]) + b'\n'
    send_email(data[b'id'], mail_header + mail_text)

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.