Direkt zum Inhalt | Direkt zur Navigation

Benutzerspezifische Werkzeuge

Sie sind hier: Startseite / Tech-Blog

Tech-Blog

Technische Artikel zu Themen: Zope, Plone, Python, Pyramid, Django, Exim, Dovecot, Nagios, Shinken, Bacula und weiteren Open Source Themen...

In Plone html nach speichern modifizieren

Plone verwendet sogenannte Tranforms um Inhalte von einem format ins andere zu übertragen. Z.B. wird der eingegebene HTML-Inhalt von text/html nach text/x-plone-outputfilters-html transformiert, wobei alle verbotenen Konstrukte entfernt werden.

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"
    />

Plone: einen SecureMailHost mit Python anlegen

Im Nachfolgenden wird Beschrieben wie man einen SecureMailHost programmatisch mit Python anlegen kann.

Ziel (Kontext) für den SecureMailHost bestimmen

factory_dispatcher = container.manage_addProduct['SecureMailHost']

SecureMailHost erzeugen

factory_dispatcher.manage_addMailHost(
    'MySecureMailHost',
    title='My SecureMailHost',
    smtp_host='mail.example.com',
    smtp_port=25,
    smtp_userid='jon@example.com',
    smtp_pass=None,
    smtp_notls=None,
    REQUEST=None)

Plone: zum MIME-Type passende icons anzeigen

Plone hat eine mimetype_registry in der für viele MIME-Types auch icons hinterlegt sind. Diese können wie folgt zur Anzeige verwendet werden.

Icon für eine Datei ermitteln

Plone stellt für solche Aufgaben eine BrowserView mit dem Namen "contenttype_utils" zur Verfügung. Wir holen uns die View und verwenden die Methode "getMimeTypeIcon" um uns den passenden icon URL zu ermitteln.

contenttype_utils = context.restrictedTraverse('contenttype_utils')
icon_url = contenttype_utils.getMimeTypeIcon(context.file)

Context ist hier unser Dateiobjekt und file das entsprechende NamedBlobFile field.

Vorschaubilder (contentleadimage) für Dexterity basierte Ordner aktivieren

In älteren Versionen konnte man mit dem Add-on collective.contentleadimage einigen Inhalttypen Vorschaubilder ähnlich dem der Nachrichten verpassen. Für die neuen plone.app.contenttypes, welche auf Dexterity basieren funktioniert die Erweiterung nicht. Aber es gibt einen anderen eleganteren Weg.

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.

Plone: Überschreiben von Views, Viewlets und resource Einträgen

Plone bietet vielfältige Möglichkeiten, vorhandene Elemente zu überschreiben. Unter anderem können wir mit Hilfe einfacher ZCML-Anweisungen leicht Views, Viewlets, Resource-Einträge usw von Plone oder Plone-Erweiterungen überschreiben.

Ü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.

System Message: ERROR/3 (<string>, line 27)

Unexpected indentation.
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

Mit Zope Global Utilities kann man Methoden, die man an verschiedenen Stellen in einem Portal benötigt zentral zur Verfügung stellen und an beliebigen Stellen 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.)
    """

Manager werden im zope debug Modus

Im Zope Debug Modus, ist man Standardmäßig kein Manager, was hin und wieder aber benötigt wird.

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)

PDF-Dateien mit Sphinx unter Debian erzeugen

Mit Sphinx kann man nicht nur ansehnliche HTML-Dokumente erzeugen, sondern auch entsprechende PDF-Dokumente. Unter Debian werden ein paar Pakete benötigt, damit dies funktioniert.
$ 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: Zope3 Viewlets

Seit Plone3 finden Zope3-Viewlets immer häufiger Verwendung in Plone. Im folgenden wird gezeigt, wie ein eigenes Viewlet erstellt und dieses an die gewünschte Stelle in Plone eingebaut werden kann.

Was sind Viewlets?

Viewlets sind kleine Snipsel von Funktionen und Templates. Sie können statische Templates oder auch Funktionalitäten enthalten. Viewlets können in Plone für sortierbare ViewletManager registriert und dort angezeigt werden.

Plone Viewlets und ViewletManager

Die vorhandenen Viewlets und Viewletmanager können gut mit der in Plone eingebauten @@manage-viewlets View untersucht werden. In einem gewissen Rahmen können dort auch Veränderungen vorgenommen werden. So können z.B. Viewlets aus oder eingeblendet und innerhalb eines ViewletsManagers umgeordnet werden. Diese Aktionen können aber auch wie unten zu sehen, per GenericSetup in der viewlets.xml konfiguriert werden.

Viewlet code

from plone.app.layout.viewlets.common import ViewletBase

class ExampleViewlet(ViewletBase):

  def update(self):
    """
    """
    self.id = self.context.id

Viewlet page template

Als Page Template braucht es nicht viel, jeder ZPT Schnipsel ohne komplettes XHTML-Gerüst reicht. Das Viewlet wird immer in einem anderen Template gerendert und benötigt daher kein eigenes Grundgerüst.

Hier mal ein Beispiel:

<ul id="enl_actions"
    i18n:domain="EasyNewsletter">
  <li><a tal:attributes="href string:${view/enl_url}/easynewsletter_view"
      i18n:translate="">Archive</a></li>
  <li><a tal:attributes="href string:${view/enl_url}/enl_drafts_view"
      i18n:translate="">Drafts</a></li>
  <li><a tal:attributes="href string:${view/enl_url}/enl_subscribers_view"
      i18n:translate="">Subscribers</a></li>
</ul>

Damit die Lokalisierung funktioniert muss hier die i18n:domain gesetzt werden.

Viewlet per zcml registrieren

<browser:viewlet
  name="derico.viewlet_example"
  manager="plone.app.layout.viewlets.interfaces.IBelowContent"
  template="viewlet_example.pt"
  layer="derico.viewlet_example.interfaces.IAddOnInstalled"
  permission="zope2.View"
  />

Viewlet per GenericSetup konfigurieren

In der Datei viewlets.xml kann man wie folgt die Viewlets konfiguerieren.

<?xml version="1.0"?>
<object>
  <order manager="plone.belowcontent">
    <viewlet name="derico.viewlet_example" insert-before="*" />
  </order>
</object>

Reihenfolge der Viewlets festlegen

<?xml version="1.0"?>
<object>
  <order manager="plone.belowcontent">
    <viewlet name="derico.viewlet_example" insert-before="*" />
    <viewlet name="derico.viewlet_example2" insert-after="plone.belowcontenttitle.keywords" />
  </order>
</object>

Viewlets ein- und ausblenden

<?xml version="1.0"?>
<hidden manager="plone.portalheader" skinname="*">
  <viewlet name="plone.global_sections" />
</hidden>
<?xml version="1.0"?>
<object>
  <hidden manager="plone.portalheader" skinname="*"
          purge="True" />
</object>

Eigenen Viewletmanager definieren

Wenn man das Viewlet nicht in die bestehenden Viewletmanager von Plone einhängen möchte, kann man sich selbst, in einem Template einen content provider definieren und einen Viewletmanager hierfür definieren.

from zope.viewlet.interfaces import IViewletManager

class IExampleViewletmanger(IViewletManager):
    """ A viewlet manager that sits in the template and render the viewlets
    """

In einem Template kann man für den Viewletmanager wie folgt einen Platzhalter definieren. Hier werden die Viewlets dann eingefügt.

<div tal:replace="structure provider:derico.example_viewletmanager" />

Eigenen Viewletmanager registrieren

<browser:viewletManager
 name="derico.exampleviewlet"
 provides=".manager.IExampleViewletmanager"
 permission="zope2.View"
 class="plone.app.viewletmanager.manager.OrderedViewletManager"
 />