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: 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,
)

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

Plone: den Owner (Besitzer) eines Objektes in Python ändern

Man kann den Owner in Plone auch per Python ändern, was z.B. nützlich ist, wenn man viele Objekte manipulieren möchte oder dies in einem event handler tun möchte.

Den Owner eines Objektes ändern

portal.plone_utils.changeOwnershipOf(obj, user_id)
print "change owner of %s to %s" % (obj.id, user_id)

AngularJS: In einem controller einer directive mit isolated scope auf Methoden im parent scope zugreifen

Man kann den scope einer directive vom Rest isolieren, was viele Vorteile hat, wenn man diese wiederverndbar machen möchte. Aber manchmal möchte man aus dieser directive oder dessen controller auf Methoden oder Variablen der parents zugreifen.

Um in einer directive auf Methoden und Variablen zuzugreifen, kann man für diese ein Mapping erstellen: http://www.undefinednull.com/2014/02/11/mastering-the-scope-of-a-directive-in-angularjs/

Will man aber in einem der directive zugewiesenen controller auf Methoden im parent scope zugreifen, kann man dies wie folgt machen.

Im controller der directive mappen wir unsere parent-Methode wie folgt:

$scope.toggleSomeThing =
   $scope.$parent.toggleSomeThing;

Nun können wir im Template der directive darauf zugreifen:

<button ng-click="toggleSomeThing(element)"></button

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

Quick Dexterity 2-Column-Edit-Form with Diazo

With Diazo Theming you have the power to do nice things like, converting one-column edit form into two-column edit form.

In the following we will separate the odd from the even fields into two div container.

Diazo rules and inline-XSLT to separate even from odd fields

In the main rules.xml file, we define a conditional include of our content type rules file my-contenttype.xml:

<rules css:if-content=".template-edit.portaltype-my-contenttype">
  <xi:include href="my-contenttype.xml" />
</rules>

Then we define our rules to do the real work:

<?xml version="1.0" encoding="UTF-8"?>
<rules xmlns="http://namespaces.plone.org/diazo"
    xmlns:css="http://namespaces.plone.org/diazo/css"
    xmlns:xi="http://www.w3.org/2001/XInclude"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <replace css:content="#content-core fieldset">
    <xsl:for-each select=".">
    <fieldset>
      <xsl:copy-of select="@*" />
      <xsl:copy-of css:select="> legend" />

      <div class="left-content-column">
      <xsl:for-each css:select="> div.field:nth-child(even)">
        <xsl:copy-of select="." />
      </xsl:for-each>
      </div>

      <div class="right-content-column">
      <xsl:for-each css:select="> div.field:nth-child(odd)">
        <xsl:copy-of select="." />
      </xsl:for-each>
      </div>

  </fieldset>
  </xsl:for-each>
</replace>
</rules>

CSS to show the two div container in two columns

body.template-edit.portaltype-my-contenttype{
  .left-content-column{
    width: 50%;
    float:left;
    .field{
      padding: 0.5em;
      &:nth-child(even){
        background: lightgray;
      }
    }
  }
  .right-content-column{
    width: 50%;
    float:left;
    .field{
      padding: 0.5em;
      &:nth-child(even){
        background: lightgray;
      }
    }
  }
}