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

Plone: recompile static resources every time

If you develop Mockup paterns or other static resources in Plone, you eventually want to see your changes in the filesystem on browser reload.

There is a hidden parameter which you can use to start a Plone instance, so that every less or javascript file gets recompiled any time you reload the browser.

Just start your instance as follow:

$ FEDEV=true ./bin/instance fg

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!")

Plone: load a single GenericSetup import step

Some times you only need to load a single import step in a Plone upgradestep and not the whole profile with all steps.

Usually you would do something like this, in your upgrade step:

def upgrade_to_1_1(context):
    loadMigrationProfile(
        context,
        'profile-my.example:default'
    )

But if you only need to reload the rolemap config, you can also do:

def to_1200(context):
    loadMigrationProfile(
        context,
        'profile-my.example:default',
        ['rolemap']
    )

SQLAlchemy / Alembic: check if table has column

Some times you want to check if a table already has a column befor try to add or drop the column in an alembic migration file.

Helper file

We define us a little helper function for this use case:

from alembic import op
from sqlalchemy import engine_from_config
from sqlalchemy.engine import reflection


def table_has_column(table, column):
    config = op.get_context().config
    engine = engine_from_config(
        config.get_section(config.config_ini_section), prefix='sqlalchemy.')
    insp = reflection.Inspector.from_engine(engine)
    has_column = False
    for col in insp.get_columns(table):
        if column not in col['name']:
            continue
        has_column = True
    return has_column

This code we put in a file alembic_helpers.py inside the alembic directory, where the env.py file is.

Using the helper function table_has_column

In the alembic version file for our upgrade step, we use it like this to chekc if a table has a column before we try to drop that column:

from alembic import op
import imp
import os

alembic_helpers = imp.load_source('alembic_helpers', (
    os.getcwd() + '/' + op.get_context().script.dir + '/alembic_helpers.py'))


def upgrade():
    if alembic_helpers.table_has_column('reference', 'sections'):
        op.drop_column('reference', 'sections')

Plone: prevent unwanted indexing of child objects thru Zope acquisition

The Zope catalog will index child objects for attributes which are exist on parent objects but not on the child it self. Most of the time this is unwanted and can result in errors. We can use indexers to prevent that.

First we define two indexers, a dummy indexer and the more specific real indexer for our index we want:

from plone.indexer import indexer


@indexer(IDexterityContent)
def dummy_index(obj):
    """ dummy to prevent indexing child objects
    """
    raise AttributeError("This field should not indexed here!")


@indexer(ILibrary)
def my_parent_index(obj):
    """
    :return: value of field my_parent_field
    """
    return obj.my_parent_field

For these indexers we register two adapters:

<configure
    xmlns="http://namespaces.zope.org/zope">

    <adapter factory=".library.dummy_index" name="my_parent_field" />
    <adapter factory=".library.my_parent_index" name="my_parent_field" />

</configure>

Both adapters have the same name. The catalog now lookup for these adapters and will use the more specific adapter for the current object. So for the parent object with the interface ILibrary it will be the second and it will result the index value. But for a child object the catalog will get the adapter which calls the dummy_index index and this will raise an AttributeError and the catalog will ignore that.

Plone: RelationChoice & ContentTreeFieldWidget mit dynamischem Path-Filter

Im folgenden werden mittels eigenem ObjPathSourceBinder für ein RelationChoice field die anzuzeigenden Elemente im ContentTreeFieldWidget gefiltert.

Im einfachen Fall, dass man den Pfad festlegen kann, funktioniert auch folgende Konfiguration des ObjPathSourceBinder:

related_obj = RelationChoice(
    title=_(u"Referenziertes Objekt"),
    source=ObjPathSourceBinder(
        portal_type="MyRelatedObjType",
        navigation_tree_query=dict(
            portal_type=["MyRelatedObjType",],
            path={ "query": '/ub/ub-db' })
    ),
    required=False,
)

Will man aber den Pfad zur Laufzeit ermitteln, dann kann man nicht wie oben beschrieben vorgehen. Wir bauen uns deshalb einen angepassten ObjPathSourceBinder welcher

 from Acquisition import aq_parent, aq_inner
 from plone.formwidget.contenttree import ObjPathSourceBinder
 from plone.formwidget.contenttree.utils import closest_content
 from zope.interface import implementer
 from zope.schema.interfaces import IVocabularyFactory


 def get_librarydb(context):
     """Get the most local 'Library Database'."""
     parent = context
     if not parent:
         return None
     while parent.portal_type != 'LibraryDB':
         parent = aq_parent(aq_inner(parent))
         if getattr(parent, 'portal_type', None) is None:
             return None
     return parent


@implementer(IVocabularyFactory)
class RelatedObjPathSourceBinder(ObjPathSourceBinder):
    """Provide available related objects."""

    def __call__(self, context):
        if 'path' not in self.navigation_tree_query:
            db = get_librarydb(context)
            if db:
                path = '/'.join(db.getPhysicalPath())
                self.navigation_tree_query["path"] = {"query": path}
        return self.path_source(
            closest_content(context),
            selectable_filter=self.selectable_filter,
            navigation_tree_query=self.navigation_tree_query)

RelatedObjPathSourceBinderFactory = RelatedObjPathSourceBinder()

Der neue RelatedObjPathSourceBinder kann wie folgt verwendet werden.

related_obj = RelationChoice(
    title=_(u"Referenziertes Objekt"),
    source=RelatedObjPathSourceBinder(
        portal_type="MyRelatedObjType",
        navigation_tree_query=dict(
            portal_type=["MyRelatedObjType",])
    ),
    required=False,
)

Den Typ von Variablen in Python prüfen

In Python möchte man oft wissen ob eine Variable ein String, eine Liste oder ein Dictionary ist. Es gibt dazu verschiedene Ansätze aber im folgenden soll der flexibelste Weg beschrieben werden. Dieser funktioniert sowohl für normale ASCII-String als auch für die kodierten Varianten (unicode, utf-8) und für alle Typen die von den Standard-Typen wie basestring, ListType, DictType ableiten.

Prüfen ob die Variable ein String ist?

>>> my_var = "huhu"
>>> isinstance(my_var, basestring)
True

>>> my_var = u"huhu"
>>> isinstance(my_var, basestring)
True

>>> my_var = ['huhu']
>>> isinstance(my_var, basestring)
False

Prüfen ob die Variable eine Liste ist?

>>> import types
>>> my_var = ['Hallo', 'Welt']
>>> isinstance(my_var, types.ListType)
True

>>> my_var = u"huhu"
>>> isinstance(my_var, types.ListType)
False

Prüfen ob die Variable ein Dictionary ist?

>>> import types
>>> my_var = {'Hallo': 'Welt'}
>>> isinstance(my_var, types.DictType)
True

>>> my_var = u"huhu"
>>> isinstance(my_var, types.DictType)
False

Plone: Default E-Mail Adresse in PloneFormGen

Wenn man in PloneFormGen Formularen keine Absenderadresse eingibt, wird diese automatisch ermittelt. Da dieser Vorgang mehrstufig ist und nicht immer leicht nachvollzogen werden kann, wird dies hier erläutert.

PloneFormGen ermittlung des Absenders

In folgender Reihenfolge werden alle Stufen durchlaufen, bis eine E-Mail-Adresse gefunden wurde. Sobald eine E-Mail-Adresse ermittelt wurde, wird diese verwendet.

  1. ist für den recipient in den Overrides etwas definiert, wird dieser Wert genommen
  2. ist to_field (Empfänger herauslesen aus) gesetzt, enthält es den Namen eines Feldes, aus dem der Empfänger herausgelesen wird, der Wert des Formularfeldes wird dann verwendet
  3. nun wird recipient_email (E-Mail-Adresse des Empfängers) ausgelesen und verwendet
  4. als nächstes wird versucht die E-Mail-Adresse des Formular-Objekt-Besitzers zu verwenden
  5. falls dies nicht möglich war, wird die email_from_address (Absenderadresse der Website) verwendet

Bemerkung

Die Ermittlung der Standard-Absender-Adresse ist einfacher. Hier fehlt die Stufe mit der E-Mailadresse des Formular-Objekt-Besitzers. So dass, wenn nichts angegeben wurde, die Portal-Adresse (email_from_address) verwendet wird.

Plone 4.3.4 veröffentlicht!

Es wurde eine weitere Version von Plone 4 veröffentlicht, die viele kleine Korrekturen beinhaltet und die Weichen für die kommende Plone 5 Version stellt.

Unter anderem bringt die neue Version folgende Vorteile:

  • die neuen Kollektionen enthalten Korrekturen und können nun auch beim Orts-Kriterium die Tiefe berücksichtigen
  • plone.portlet.static geht nun besser mit relativen URL's um
  • viele JavaScript Bibliotheken wurden aktualisiert um mit jQuery 1.7 bis 1.9 kompatibel zu sein
  • verbessertes caching der Bildskalierungen in plone.app.imaging

Es gibt viele Änderungen mehr, nicht alle sind für den Endnutzer sichtbar, bringen aber Verbesserungen für Entwickler mit sich und erhöhen die Stabilität. Ein Update auf die Aktuelle Version sollte von Plone 4.3.x ohne großen Aufwand möglich sein und wird klar empfohlen.

Die vollständigen release notes finden Sie hier: https://plone.org/products/plone/releases/4.3.4

Bibliotheken zur Konvertierung und Volltextindizierung von Dokumenten

Plone kann out of the box PDF-Dateien, Openoffice/Libreoffice und MSOffice Dokumente wie Dokumente und Tabellen sowie auch Impress und Powerpoint Presentationen konvertieren und für die Volltextsuche indizieren. Hierfür müssen auf dem Server ein paar Bibliotheken installiert werden.

Unter Debian Linux sind folgende Bibliotheken zu installieren

  • poppler-utils
  • libxslt1-dev
  • libxml2-dev
  • wv
  • xsltproc
  • unzip