Tech-Blog
Technische Artikel zu Themen: Zope, Plone, Python, Exim, Dovecot, Nagios, Shinken, Bacula und weiteren Open Source Themen...
Vorschaubilder (contentleadimage) für Dexterity basierte Ordner aktivieren
LeadImage Behavior
Die neuen Dexterity basierten Inhaltstypen bringen mehr Flexibilität bei den Grundfunktionen mit. Schon die Grundfunktionen werden aus verschiedenen Behavior zusammen gesetzt. Diese Behavior bringen einzelne Felder oder Funktionalitäten mit und können für alle Inhaltstypen aktiviert werden. Im Normalfall wird dies über eine GenericSetup-Anweisung wie z.B. in ./profiles/default/types/Folder erreicht.
<property name="behaviors" purge="False"> <element value="plone.app.contenttypes.behaviors.leadimage.ILeadImage"/> </property>
Mit dieser Anweisung in unserem eigenen Add-on ermöglichen wir Vorschaubilder für Ordner. Wenn Sie kein eigenes Add-on verwenden möchten, können Sie das Behavior auch in der Plone-Konfiguration und "Dexterity Inhaltstypen" aktivieren.
Bibliotheken zur Konvertierung und Volltextindizierung von Dokumenten
Unter Debian Linux sind folgende Bibliotheken zu installieren
- poppler-utils
- libxslt1-dev
- libxml2-dev
- wv
- xsltproc
- unzip
Plone: Überschreiben von Views, Viewlets und resource Einträgen
Überschreiben von einem Resource-Eintrag einer Plone-erweiterung
Wir haben z.B. einen Originaleintrag einer Plone-Erweiterung, die wir überschreiben möchten.
<browser:resource name="jquery.prettyPhoto.js" file="javascript/jquery.prettyPhoto.js" layer="..interfaces.IPrettyPhotoSpecific" />
Überschreiben mit einem Layer
Wir übernehmen einfach die Originalanweisung in eine configure.zcml in unserem Theme Package und übernehmen ebenfalls die zugehörige Ressource jquery.prettyPhoto.js. Wäre hier jetzt kein Layer verwendet worden, könnten wir einfach die gleiche Anweisung plus einer Layer-Anweisung in unserem Theme verwenden. Aber da hier schon im Original ein Layer definiert ist, müssen wir dafür sorgen das unsere Anweisung spezifischer ist. Dies erreichen wir in dem wir einen eigenen Layer angeben und diesen von dem Original-Layer ableiten.
<browser:resource name="jquery.prettyPhoto.js" file="javascript/jquery.prettyPhoto.js" layer=".interfaces.IThemeSpecific" />
Der Layer IThemeSpecific ist dann wie folgt in unserem Theme definiert.
from collective.prettyphoto.interfaces import IPrettyPhotoSpecific class IThemeSpecific(IPrettyPhotoSpecific): """Marker interface that defines a Zope 3 browser layer. """
Nach einem Neustart von Plone sollte jetzt unsere Version der Resource verwendet werden.
Überschreiben mit overwrites.zcml
Eine etwas gröbere Methode ZCML-Registrierungen zu überschreiben ist es, sie per overwrites.zcml zu überschreiben. Dies kann z.B. sinnvoll sein, wenn man die Registrierung für die ganze Zope-Instanz überschreiben möchte.
hierzu legen wir eine overwrites.zcml in unserem Package an und kopieren einfach die Originalanweisung hinein.
<browser:resource name="jquery.prettyPhoto.js" file="javascript/jquery.prettyPhoto.js" layer="collective.prettyphoto.interfaces.IPrettyPhotoSpecific" />
In der buildout.cfg muss die overwrites für dieses Package dann expliziet geladen werden. Wenn unser Theme Package my.theme heißt, dann lautet der Eintrag wie folgt.
zcml = my.theme-overrides
Zope / Plone: global utilities erzeugen und verwenden
Eigenschaften von global utilities
- global utilities werden beim Start der Zope-Instanz instanziert
- sind nicht persistent und können daher auch keine persistenten Daten enthalten
- können problemlos jeder Zeit entfernt werden, da keien persistente Registrierung in der Datenbank erfolgt
Ein Global Utility erzeugen
Utility Klasse
Um ein global utility zu erzeugen, legen wir uns eine Datei utilities.py mit folgenden Inhalt in unserem Package an:
from zope.site.hooks import getSite class PloneClipboardTool(object): """ provide some useful methods """ def get_plone_portal(self): """ return the portal object """ portal = getSite()
Utility Interface
In der Datei interfaces.py definieren wir ein entsprechendes Interface:
from zope.interface import Interface class IPloneClipboardTool(Interface): def get_plone_portal(self): """ return the portal object """
Utility Registrierung
In der configure.zcml registrieren wir unser global utility wie folgt:
<utility factory=".tool.PloneClipboardTool" provides=".interfaces.IPloneClipboardTool"/>
Ein Global Utility verwenden
Um auf das global utility zuzugreifen, verwenden wir getUtility() oder queryUtility().
getUtility
from zope.component import getUtility my_tool = getUtility(IPloneClipboardTool)
Sollte kein Utility gefunden werden, würde man hier einen ComponentLookupError bekommen.
*** ComponentLookupError: (<InterfaceClass inqbus.plone.clipboard.interfaces.IPloneClipboardTool>, 'test')
queryUtility
from zope.component import queryUtility my_tool = queryUtility(IPloneClipboardTool)
Sollte kein Utility gefunden werden, würde man hier 'None' bekommen.
Der folgende Aufruf bringt uns dann das Portalobjekt zurück.
plone_portal = my_tool.get_plone_portal()
Analog kann man beliebig viele Utilities und Methoden darin erzeugen und verwenden.
Plone: Dexterity Events
Die Dexterity Events sind in plone.dexterity.interfaces definiert:
class IBegunEvent(IObjectEvent): """Base begun event """ class IEditBegunEvent(IBegunEvent): """An edit operation was begun """ class IAddBegunEvent(IBegunEvent): """An add operation was begun. The event context is the folder, since the object does not exist yet. """ class ICancelledEvent(IObjectEvent): """Base cancel event """ class IEditCancelledEvent(ICancelledEvent): """An edit operation was cancelled """ class IAddCancelledEvent(ICancelledEvent): """An add operation was cancelled. The event context is the folder, since the object does not exist yet. """ class IEditFinishedEvent(IObjectEvent): """Edit was finished and contents are saved. This event is fired even when no changes happen (and no modified event is fired.) """
In Plone html nach speichern modifizieren
Dieses Konzept kann man auch für andere Transformationen verwenden. Nachfolgend werden Platzhalter durch HTML-Code ersetzt.
Citations Transform erzeugen
Alls erstes erstellen wir einen Ordner transforms in unserem Package und erstellen darin eine Datei __init__.py mit folgendem Inhalt.
from Products.PortalTransforms.libtransforms.utils import MissingBinary modules = [ 'html_to_html_citations', ] g = globals() transforms = [] for m in modules: try: ns = __import__(m, g, g, None) transforms.append(ns.register()) except ImportError, e: print "Problem importing module %s : %s" % (m, e) except MissingBinary, e: print e except: import traceback traceback.print_exc() def initialize(engine): for transform in transforms: engine.registerTransform(transform)
Nun erstellen wir die Datei html_to_html_citations.py mit folgendem Inhalt.
class HtmlToHtmlCitations: """Transform which replaces literature citations with special markup""" implements(ITransform) __name__ = "html_to_html_citations" inputs = ('text/html', ) output = "text/x-html-citations" def __init__(self, name=None): self.config_metadata = { 'inputs': ( 'list', 'Inputs', 'Input(s) MIME type. Change with care.' ), } if name: self.__name__ = name self.citationRegexp = re.compile( r'\[\[(?P<refid>[\w\W]+?)\]\]' ) self.citations_counter = 0 self.portal = getSite() self.pcat = self.portal.portal_catalog def name(self): return self.__name__ def convert(self, orig, data, **kwargs): text = orig self.citations_counter = 0 text = self.citationRegexp.sub(self._repl, safe_unicode(text)) data.setData(text.encode(kwargs['encoding'])) return dataclass HtmlToHtmlCitations: def _repl(self, m, **kwargs): self.citations_counter += 1 refid = m.group('refid') cit_out_list = [] refids = refid.split(';') for refid in refids: query = {} query['refid'] = refid query['portal_type'] = 'LiteratureContent' cit_brains = self.pcat(query) if not cit_brains: continue cit_brain = cit_brains[0] cit_obj = cit_brain.getObject() cit_out_list.append(cit_obj.render_output()) cit_out = "" for cit_str in cit_out_list: cit_out += "<p>" + cit_str + "</p>" repl_str = """<a href="#" title="%(cit_out)s" class="literature-citation visualNoPrint" >[%(refid)s]</a><span class="print-only">[%(counter)s]</span>""" % { 'cit_out': cit_out, 'refid': refid, 'counter': self.citations_counter } return repl_str def register(): return HtmlToHtmlCitations()
Mimetype für Citations registrieren
<?xml version="1.0"?> <object name="mimetypes_registry" meta_type="MimeTypes Registry"> <mimetype name="HTML with literature citations" binary="False" extensions="html" globs="" icon_path="text.png" mimetypes="text/x-html-citations"/> </object>
Damit diese Registrierung per GenereicSetup funktioniert wird das Package: collective.mtrsetup benötigt!
Eventhandler erzeugen
Hier legen die Datei events.py in unserem Package an.
from plone.app.textfield.interfaces import ITransformer from plone.app.textfield.value import RichTextValue def transform_citationsmarkers_into_citations(event): """ transform citation markers into citation links if edited and save them as RichTextValue """ obj = event.object if not hasattr(obj, 'text'): return transformer = ITransformer(obj) transformedValue = transformer(obj.text, 'text/x-html-citations') obj.text = RichTextValue( transformedValue, 'text/html', 'text/x-plone-outputfilters-html', )
Wenn man mit dem Transformer arbeitet, ist die Quelle immer der gespeicherte raw-Wert, sollte man also nach den eigentlichen Wandlungen zusätzliche Änderungen vornehmen wollen, dann sollte man folgenden Aufruf zum trasnformieren verwenden.:
pt = getToolByName(self, 'portal_transforms') data = pt.convertTo( 'text/x-html-citations', obj.text.output, encoding=obj.text.encoding, mimetype='text/x-plone-outputfilters-html') transformedValue = '' if data: transformedValue = data.getData() if not transformedValue: transformedValue = obj.text.output
Eventhandler für EditFinishedEvent registrieren
In der configure.zcml in unserem package registrieren wir nun den Eventhandler für das EditFinishedEvent.
<subscriber for="plone.dexterity.interfaces.IEditFinishedEvent" handler=".events.transform_citationsmarkers_into_citations" />
Manager werden im zope debug Modus
Mit Hilfe folgender Anweisungen, macht man sich zum Manager (admin):
from AccessControl.SecurityManagement import newSecurityManager, \ getSecurityManager, setSecurityManager old_sm = getSecurityManager() portal = app["Plone"] newSecurityManager(portal, portal.getOwner()) # do some thing as manager setSecurityManager(old_sm)
ZODB: repair PosKeyErrors in Plone/Zope
First of all we need to recover the ZODB with fsrecover.py script which comes with ZODB.
./bin/zopepy ../buildout-cache/eggs/ZODB3-3.10.5-py2.7-linux-x86_64.egg/ZODB/fsrecover.py -P 0 var/filestorage/broken-Data.fs var/filestorage/Data.fs
Then we need to analyze the ZODB to get a list of known PosKeyErrors. To do this we use fsrefs.py script which comes with ZODB.
./bin/zopepy ../buildout-cache/eggs/ZODB3-3.10.5-py2.7-linux-x86_64.egg/ZODB/scripts/fsrefs.py -v var/filestorage/Data.fs > fsrefs-output.txt
Then we run the following script as follows to fix the broken references with fake objects. After that, we should have a working database and can see what data is still alive and can be saved.
Script: fix-broken-references.py
Save the following code into a script called fix-broken-references.py under your buildout directory and call it as followed.
./bin/instance run fix-broken-references.py
import re import os import transaction as zt from ZODB.utils import p64 from persistent import Persistent refs_file = open('fsrefs-output.txt', 'r') oids = [] for line in refs_file: rematch = re.search(r'oid\s+(?P<oid>[\w\W]+?) .*', line) if not rematch: continue oids.append(rematch.group('oid')) i = 0 for oid in set(oids): zt.begin() i += 1 print("create fake obj [%s] for: %s" % (i, str(oid))) a = Persistent() try: oid_int = int(oid, 16) except ValueError: print("ValueEror on %s, skip!" % oid) zt.abort() continue a._p_oid = p64(oid_int) a._p_jar = app._p_jar app._p_jar._register(a) app._p_jar._added[a._p_oid] = a zt.commit() print("finished!")
PDF-Dateien mit Sphinx unter Debian erzeugen
$ aptitude install texlive-base texlive-latex-extra
Wenn alle benötigten Pakete installiert sind sollte das folgende Komando eine PDF-Datei im Build-Ordner erzeugen.
$ make latexpdf [...] pdflatex finished; the PDF files are in build/latex.
Plone: Annotations zum speichern von Daten an Objekten
Annotations werden in Zope üblicherweise als __annotations__ (BTree) unterhalb des betreffenden Objektes abgelegt. Das Annotations Objekt verhält sich wie ein Dictionary.
Um Konflikte mit anderen Anwendungen zu vermeiden, sollten Informationen unterhalb eines keys der den Namen der Anwendung enthält abgelegt werden.
Annotatable?
Damit annotations zum speichern von Daten, an einem Objekt verwendet werden können, muss das Objekt das Interface IAttributeAnnotatable implementiert haben. Um dies zu erreichen kann man es direkt in der Klasse implementieren oder wenn man selbst keinen Einfluss auf die Klasse hat, dann kann man es ganz einfach per zcml-Anweisung in der configure.zcml wie folgt definieren.
<class class="Products.CMFCore.MemberDataTool.MemberData"> <implements interface="zope.annotation.interfaces.IAttributeAnnotatable" /> </class>
Daten in Annotations speichern
Wenn wir jetzt z.B. an ein MemberData Objekt Daten als annotations hängen möchten, dann können wir dies wie folgt machen.
from zope.annotation.interfaces import IAnnotations from ZODB.PersistentMapping import PersistentMapping admin = portal.portal_membership.getAuthenticatedMember() admin_annotations = IAnnotations(admin) infos = PersistentMapping({'groesse': '184cm'}) admin_annotations["member.datatest"] = infos
Das betreffende Objekt sollte persistent sein, was hier der Fall ist.