Direkt zum Inhalt | Direkt zur Navigation

Benutzerspezifische Werkzeuge

Sie sind hier: Startseite / Tech-Blog

Tech-Blog

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

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

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

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)

ZODB: repair PosKeyErrors in Plone/Zope

Some times you have a broken ZODB database because of harddisk problems and sadly no suitable backup exist. Then we can try to repair the ZODB database. Below you can find the needed steps to get a running ZODB database and a script for repairing PosKeyErrors in ZODB.

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

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: Annotations zum speichern von Daten an Objekten

Annotations sind ein gutes Mittel um Komplexe Datenstrukturen an Objekten zu speichern, von denen das Objekt selbst keine Kenntnis haben muss.

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.

Weitere Informationen sind hier zu finden:

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

Plone: Password encrypt

Plone verschlüsselt Passwörter intern, im folgenden wird gezeigt, wie man die gleichen Mechanismen für eigene Funktionen verwenden kann.

Beispiel

Password verschlüsseln

from AccessControl import AuthEncoding

def _encryptPassword(self, pw):
    return AuthEncoding.pw_encrypt(pw, 'SSHA')

Password überprüfen

from AccessControl import AuthEncoding

AuthEncoding.pw_validate(saved_pw, entered_pw)

Plone: PloneFormGen readonly fields

Manchmal möchte man Felder nur anzeigen aber nicht änderbar machen. Nachfolgend wird eine Möglichkeit beschrieben, wie dies mit Hilfe von einem HiddenField und einem FomRichLabelField realisiert werden kann.

Ein verstecktes Feld anlegen

Wir legen ein FormStringField (Zeichenkettenfeld) an, füllen den Default wert mit dem Inhalt der angezeigt werden soll und markieren das Feld als versteckt.

Damit ist das Feld im Formular enthalten und wird beim versenden mit übertragen.

Ein Feld zur Anzeige des versteckten Feldes anlegen

Wir legen ein FormRichLabelField (Bezeichnungsfeld) an und geben in den overrides den folgenden TALES-Ausdruck ein:

string: <strong>My hidden field text:</strong> ${folder/myhiddenfield/getFgDefault}

Das Feld zeigt nun immer den Inhalt des Default-wertes des Feldes myhiddenfield an, kann aber selbst nicht editiert werden.

Text per CSS rotieren mit Compass

Im folgenden wird gezeigt, wie man mit dem CSS-Framework Compass, einen vertikalen Text erzeugt.

HTML Markup

<div id="box">
  <h1 id="parent-fieldname-title"
      class="documentFirstHeading">
    Spielplatzinspektion
  </h1>
</div>

Compass SCSS

@import "compass/css3/transform";

#box{
  width: 400px;
  height: 400px;
  border: 1px solid black;
  margin: 0 auto;
}
.verticalHeadline{
  font-size: 12pt;
  margin: 0;
  text-align: center;
  border: 1px solid green;
  @include rotate(270deg);
  @include apply-origin(top right, false);
  position: relative;
  right: 2em;
}

Resultat

vertical-headline