11. Python-Hooks#
Neu in Version 4.4v9: Ab UCS@school 4.4 v9 kann vor und nach dem Anlegen, Ändern, Verschieben und Löschen von UCS@school Objekten Python-Code ausgeführt werden. Dies kann z.B. im Rahmen des UCS@school Imports von eingesetzt werden, um in Abhängigkeit von der jeweiligen Umgebung weitere Einstellungen vorzunehmen.
Python-Hooks, im folgenden Abschnitt abgekürzt mit Hooks, erlauben es Objekttypen zu unterscheiden (z.B. Schulklasse und Arbeitsgruppe oder Schüler und Lehrer) und haben Zugriff auf alle Attribute der Objekte.
Die Hooks werden für alle Klassen, von denen Objekte erzeugt werden können und
die von ucsschool.lib.models.base.UCSSchoolHelperAbstractClass
ableiten,
ausgeführt. Diese Klassen finden sich in im Python Paket
ucsschool.lib.models
(z.B. Student
, SchoolClass
, Workgroup
).
Vorsicht
Hooks werden nur auf dem System ausgeführt, auf dem sie installiert sind. In der Regel ist das der Primary Directory Node, sowie alle Backup Directory Node Server. Sollen Hooks auch auf Replica Directory Node Servern ausgeführt werden, so müssen sie auch dort installiert werden. Eine automatische Verteilung der Hook Dateien findet nicht statt.
Hooks für UCS@school Objekte ähneln den bekannten Hooks für den Benutzerimport (siehe UCS@school - Handbuch zur CLI-Import-Schnittstelle [4]), werden jedoch auch ohne den Import zu verwenden ausgeführt und haben einige andere Attribute.
Zur Nutzung der Hook-Funktionalität muss eine eigene Python-Klasse erstellt
werden, die von ucsschool.lib.models.hook.Hook
ableitet. In der
Klasse können Methoden pre_create()
,
post_create()
, etc. definiert werden,
welche zum jeweiligen Zeitpunkt ausgeführt werden. Der Name der Datei mit der
abgeleiteten Klasse muss auf .py
enden und im Verzeichnis
/var/lib/ucs-school-lib/hooks
abgespeichert werden.
Zwei Beispiele finden sich auf Servern der Rolle Primary Directory Node in
hook_example1.py
und hook_example2.py
unter
/usr/share/doc/ucs-school-lib-common/
bzw. online auf
https://github.com/…/hook_example1.py und
https://github.com/…/hook_example2.py.
Im Folgenden wird anhand des Beispiels in hook_example2.py
erklärt, wie
mit Hilfe eines Hooks jeder Schulklasse eine E-Mailadresse zugeordnet werden
kann.
Warnung
Das Beispiel ist lauffähig, aber nicht für den Produktivbetrieb geeignet. Dafür bräuchte es u.a. zusätzlichen Code, um robust mit existierenden E-Mailadressen umzugehen.
Ein Python-Hook ist eine Klasse, die von
ucsschool.lib.models.hook.Hook
ableitet und einige Attribute und
Methoden definiert.
- class MailForSchoolClass#
from ucsschool.lib.models.group import SchoolClass from ucsschool.lib.models.hook import Hook class MailForSchoolClass(Hook): model = SchoolClass priority = { "post_create": 10, "post_modify": 10, } def post_create(self, obj): # type: (SchoolClass) -> None ... def post_modify(self, obj): # type: (SchoolClass) -> None ...
- class ucsschool.lib.models.hook.Hook#
- model#
Das Klassenattribut
model
bestimmt, für welche Objekte welchen Typs der Hook ausgeführt wird. Der Hook wird auch für Objekte von Klassen ausgeführt, die von der angegebenen ableiten. Wäremodel = Teacher
(ausucsschool.lib.models
), so würde der Hook auch für Objekte der KlasseTeachersAndStaff
ausgeführt, nicht aber für solche vom TypStaff
oderStudent
.
- priority#
Das Klassenattribut
priority
bestimmt die Reihenfolge in der Methoden von Hooks des gleichen Typs (gleichesmodel
) ausgeführt werden bzw. deaktiviert sie.Methoden mit höheren Zahlen werden zuerst ausgeführt. Ist der Wert
None
oder die Methode nicht aufgeführt, wird sie deaktiviert.Angenommen es gäbe eine weitere Klasse mit einem Hook mit
model = SchoolClass
und diese würdepriority = {"post_create": 20}
definieren, so würde derenpost_create()
Methode vorMailForSchoolClass.post_create()
ausgeführt.
- pre_create()#
Alle Methoden der Klasse, z.B.
pre_create()
oderpost_create()
, empfangen ein Objekt vom Typ, bzw. des davon abgeleiteten Typs, der inmodel
definiert wurde, als Argumentobj
und geben nichts zurück.
- post_create()#
Siehe
pre_create()
Die post_create()
Methode sieht wie folgt aus:
def post_create(self, obj): # type: (SchoolClass) -> None
"""
Create an email address for the new school class.
:param SchoolClass obj: the SchoolClass instance, that was just created.
:return: None
"""
ml_name = self.name_for_mailinglist(obj)
self.logger.info("Setting email address %r on school class %r...", ml_name, obj.name)
udm_obj = obj.get_udm_object(self.lo) # access the underlying UDM object
udm_obj["mailAddress"] = ml_name
udm_obj.modify()
Die Klasse SchoolClass
bietet kein Attribut an, um eine E-Mailadresse
anzugeben. Die Klassen in ucsschool.lib.models
sind jedoch tatsächlich
eine Abstraktion regulärer Univention Directory Manager Objekte. Um auf die darunter liegenden
Objekte zuzugreifen, wird die Methode get_udm_object()
verwendet. Als
Argument muss ihr ein sogenanntes LDAP Verbindungsobjekt (lo
) mitgegeben
werden.
Die Instanzvariablen self.lo
, self.logger
und
self.ucr
sind nach der Ausführung von __init__()
verfügbar.
Es handelt sich bei ihnen um die Instanz eines LDAP Verbindungsobjekts, einer
Instanz von Python Logger
und einer Instanz von Univention Configuration Registry.
Soll eigener Code zur Initialisierung ausgeführt werden, so sollte
__init__()
folgendermaßen implementiert werden:
class MailForSchoolClass(Hook):
def __init__(self, lo, *args, **kwargs):
super(MailForSchoolClass, self).__init__(lo, *args, **kwargs)
# From here on self.lo, self.logger and self.ucr are available.
# You code here.
Zwei Funktionen helfen dabei, aus dem Namen der Schulklasse und einem Domänennamen, eine E-Mailadresse zu erzeugen:
def name_for_mailinglist(self, obj): # type: (SchoolClass) -> str
return "{}@{}".format(obj.name, self.domainname).lower()
@property
def domainname(self): # type: () -> str
try:
return self.ucr["mail/hosteddomains"].split()[0]
except (AttributeError, IndexError):
return self.ucr["domainname"]
Um E-Mailadresse auch für umbenannte Schulklassen zu ändern, wird
post_modify()
implementiert:
def post_modify(self, obj): # type: (SchoolClass) -> None
"""
Change the email address of an existing school class.
:param SchoolClass obj: the SchoolClass instance, that was just modified.
:return: None
"""
udm_obj = obj.get_udm_object(self.lo)
ml_name = self.name_for_mailinglist(obj)
if udm_obj["mailAddress"] != ml_name:
self.logger.info(
"Changing the email address of school class %r from %r to %r...",
obj.name,
udm_obj["mailAddress"],
ml_name,
)
udm_obj["mailAddress"] = ml_name
udm_obj.modify()
Die Datei mit obigem Python Code kann nun im Verzeichnis
/var/lib/ucs-school-lib/hooks
abgespeichert werden. Soll der Hook von
einem UMC-Modul verwendet werden, muss zuerst der UMC-Server neu gestartet
werden:
$ service univention-management-console-server restart
Um den Hook zu testen, kann eine interaktive Python Shell verwendet werden. Einige Ausgaben wurden im folgenden Beispiel zur Verbesserung der Lesbarkeit gekürzt:
>>> import logging
>>> from ucsschool.lib.models.group import SchoolClass
>>> from univention.admin.uldap import getAdminConnection
>>> logging.basicConfig(level=logging.DEBUG, format="%(message)s", handlers=[logging.StreamHandler()])
>>> lo, _ = getAdminConnection()
>>> sc = SchoolClass(name="DEMOSCHOOL-igel", school="DEMOSCHOOL")
>>> sc.create(lo)
Starting SchoolClass.call_hooks('pre', 'create', lo('cn=admin,dc=exam,dc=ple')) for SchoolClass(
name='DEMOSCHOOL-igel', school='DEMOSCHOOL', dn='cn=DEMOSCHOOL-igel,cn=klassen,cn=schueler,
cn=groups,ou=DEMOSCHOOL,dc=exam,dc=ple').
Searching for hooks of type 'Hook' in: /var/lib/ucs-school-lib/hooks...
Found hook classes: MailForSchoolClass
Loaded hooks: {'post_modify': ['MailForSchoolClass.post_modify'], 'post_create': [
'MailForSchoolClass.post_create']}.
Creating SchoolClass(name='DEMOSCHOOL-igel', school='DEMOSCHOOL', dn='...')
SchoolClass(name='DEMOSCHOOL-igel', school='DEMOSCHOOL', dn='...') successfully created
Starting SchoolClass.call_hooks('post', 'create', lo('cn=admin,dc=uni,dc=dtr')) for SchoolClass(
name='DEMOSCHOOL-igel', school='DEMOSCHOOL', dn='...').
Running post_create hook MailForSchoolClass.post_create for SchoolClass(name='DEMOSCHOOL-igel',
school='DEMOSCHOOL', dn='...')...
Setting email address 'demoschool-igel@uni.dtr' on SchoolClass(name='DEMOSCHOOL-igel',
school='DEMOSCHOOL', dn='...')...
True
>>> sc.name = "DEMOSCHOOL-hase"
>>> sc.modify(lo)
Starting SchoolClass.call_hooks('pre', 'modify', lo('cn=admin,dc=exam,dc=ple')) for SchoolClass(
name='DEMOSCHOOL-hase', school='DEMOSCHOOL', dn='cn=DEMOSCHOOL-hase,...', old_dn='cn=DEMOSCHOOL-igel,...').
Modifying SchoolClass(name='DEMOSCHOOL-hase', school='DEMOSCHOOL', dn='cn=DEMOSCHOOL-hase,...',
old_dn='cn=DEMOSCHOOL-igel,...')
SchoolClass(name='DEMOSCHOOL-hase', school='DEMOSCHOOL', dn='cn=DEMOSCHOOL-hase,...') successfully modified
Starting SchoolClass.call_hooks('post', 'modify', lo('cn=admin,dc=exam,dc=ple')) for SchoolClass(
name='DEMOSCHOOL-hase', school='DEMOSCHOOL', dn='cn=DEMOSCHOOL-hase,...').
Running post_modify hook MailForSchoolClass.post_modify for SchoolClass(name='DEMOSCHOOL-hase',
school='DEMOSCHOOL', dn='cn=DEMOSCHOOL-hase,...')...
Changing the email address of SchoolClass(name='DEMOSCHOOL-hase', school='DEMOSCHOOL', ...)
from 'demoschool-igel@example.com' to 'demoschool-hase@example.com'...
True
Im Verzeichnis /var/lib/ucs-school-lib/hooks/
wird nach Python-Hooks
gesucht und die Klasse MailForSchoolClass
gefunden. Nach dem Laden
aller Hooks wird angezeigt, in welcher Reihenfolge welche Methoden für welche
Phase ausgeführt werden. Da es keine
pre_create()
Hooks gibt, wird nun das
Objekt angelegt. Anschließend werden
post_create()
Hooks ausgeführt. Erneut
wird zuerst nach Hook-Skripten gesucht. Anschließend wird
MailForSchoolClass
.post_create()
ausgeführt. Beim
sc.modify(lo)
passiert das Gleiche.