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: IP-basierter Zugang

Mit Hilfe des AutoRole Plugins kann man für bestimmte IP-Bereiche automatisch Rechte-Rollen vergeben und so Inhalte leicht einer bestimmten Gruppe von Personen zugänglich machen.

Nginx config

Damit wir auf Plone Seite die Externen Absender-IP-Adressen sehen können, müssen wir dem vorgeschalteten Webserver z.B. Nginx mitteilen, dass er entsprechende HTTP-Header setzt.

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;

Zope config Parameter

Da Plone nicht blind jedem Header trauen kann, müssen vertrauenswürdige Proxies in der zope.conf konfiguriert werden. Hierfür fügen wir in unsere Buildout-Konfiguration folgenden Eintrag ein:

zope-conf-additional =
  trusted-proxy 127.0.0.1
  trusted-proxy loadbalancer.lan

AutoRole Konfiguration

Wir installieren das Paket Products.AutoRole und konfigurieren es unter /acl_users/auto_role > Properties wie folgt:

Einzelne IP konfigurieren

192.168.0.100:Authenticated,Member

Greift nur für eine IP Adresse, hier 192.168.1.100. Da wir hier keine Bitmaske angegeben aben, gilt der Default von /32 was exakt eine IP-Adresse zulässt.

IP-Bereich konfigurieren

192.168.0.0/16:Authenticated,Member

Greift für alle IP's die mit 192.168. beginnen, also z.B. 192.168.1.100 oder auch 192.168.56.1. Alternativ könnte man hier mit 192.168.0.0/24 auch nur Adressen von 192.168.0.1 bis 192.168.0.255 zulassen.

Zu Beachten

Das AutoRole Plugin funktioniert zur Zeit nicht mit Products.PluggableAuthService Versionen > 1.8.0! Daher muss dieses in der Buildout-Konfiguration auf 1.8.0 gepinned werden.

Plone 5: hide icons for external links

Plone 5 marks external links with a font icon. If you don't want that, youcan hide them with CSS.

Just use the following CSS:

/* disable icon for external links */
.glyphicon.link-external {
  display: none;
}

Plone 5: hide navigation icons

In Plone 5 you have by default icons in the navigation which shows you the type of the content item. If the layout requires no icons, you can easily hide them as follow.

in your less file add the following:

body#visual-portal-wrapper.pat-plone .outer-wrapper {
    [class*="contenttype-"]:before{
        content: none;
    }
}

Ansible: playbooks und inventories in Unterordner organisieren

Wenn ein Ansible-Projekt wächst, dann steigt auch schnell die Anzahl der Playbooks und Inventories in einem Projektordner. Damit man den Überblick behält, kann man diese in Unterordner verschieben. Was dabei zu beachten ist, wird im folgenden erläutert.

Playbook-Dateien in Unterordnern

Seine Playbooks kann man recht einfach in Unterordner verschieben. Man sollte dabei beachten, dass man Ansible sagt, wo das roles-Verzeichnis ist. Entweder ist das z.B. /etc/ansible/roles oder flexibler ein roles-Verzeichnis innerhalb des jeweiligen Projektverzeichnisses. Dieses kann man Ansible explizit über einen Eintrag in der ansible.cfg im Projektverzeichnis definieren.

$ cat ansible.cfg
[defaults]
roles_path = ./roles

Wir legen nun z.B. 2 Unterordner für Playbooks an:

$ mkdir provisioning
$ mkdir deploy

In diese legen wir unsere jeweiligen Playbooks:

$ tree provisioning/
provisioning/
├── plone.yml
└── monitoring.yml

$ tree deploy/
deploy/
├── derico.de
└── l-tango.de

Diese können wie folgt verwendet werden.

Provisioning:

$ ansible-playbook -i live provisioning/plone.yml

Deployment:

$ ansible-playbook -i live deploy/l-tango.de

Inventory-Dateien in Unterordnern

Nun verschieben wir auch unsere Inventory-Dateien in einen Unterordner inventory:

$ tree inventory/
inventory/
├── dev
└── live

Möchte man auch seine inventory-Dateien in einen Unterordner verschieben, muss man beachten, dass Ansible die groub_vars und host_vars unter anderem relativ zum inventory sucht. Damit das auch weiterhin klappt, verschieben wir unsere groub_vars und host_vars ebenfalls Verzeichnisse in das inventory-Verzeichnis:

$ tree inventory/
inventory/
├── dev
├── group_vars
├── host_vars
│   ├── derico.de
│   └── l-tango.de
└── live

Weiterhin bietet sich an, Ansible ein default inventory zu definieren. Dies können wir in der ansible.cfg wie folgt definieren:

$ cat ansible.cfg
[defaults]
roles_path = ./roles
hostfile = inventory/dev

Die Inventories kann man nun wie folgt verwenden.

explizit:

$ ansible-playbook -i inventory/live deploy/l-tango.de

oder implizit z.B. lokal zum testen auf Vagrant-VM's:

$ ansible-playbook deploy/l-tango.de

hier wird das default inventory "inventory/dev" verwendet und auf den lokalen Test-VM's deployed.

Zentrale retry-Dateien

Möchte man jetzt noch Ansible abgewöhnen die retry-Dateien im Projektverzeichnis zu erzeugen, fügt man folgende Zeilen zur ansible.cfg hinzu:

retry_files_enabled = True
retry_files_save_path = "~/.ansible/retry"

Plone: die Funktion einer BrowserView in einem unit test prüfen

Um die Funktion einer Browser View zu testen, muss diese gebaut und mit den richtigen Request-Parametern gefüttert werden.
import unittest2 as unittest
from zope.component import getMultiAdapter


class ConsultantViewTest(unittest.TestCase):

    layer = MY_INTEGRATION_TESTING

    def setUp(self):
        self.portal = self.layer['portal']
        self.request = self.layer['request']

    def test_my_view(self):
        self.request.form.update({'my_param': '0815'})
        view = getMultiAdapter(
            (self.portal, self.request),
            name="my_view"
        )
        view = view.__of__(self.portal)

        # test if the string "This is my view" is in the rendered output:
        self.assertTrue('This is my view' in view())

Plone: Multiple NameChooser for same Container

To have nice id's, one can register NameChooser adapter for a container in Plone. Here we show how one can have multiple NameChooser in the same container, like for different objekt types in a folder.

Here we register one NameChooser for a container named Library and use the chooserName method to dispatch to the right nameChooser for our current obj.

@implementer(INameChooser)
class LibraryNameChooser(object):
    """A name chooser for the Library"""

    def __init__(self, context):
        self.context = context

    def chooseName(self, name, obj):
        """ NameChooserFactory which dispatch to different nameChooser methods
        """
        if name:
            return name
        if IOutline.providedBy(obj):
            name = self.chooseOutlineName(name, obj)
        elif ISignature.providedBy(obj):
            name = self.chooseSignatureName(name, obj)
        else:
            name = self.chooseDefaultName(name, obj)
        return name

    def chooseOutlineName(self, name, obj):
        """ NameChooser based on sigel attribute
        """
        container = aq_inner(self.context)
        normalizer = queryUtility(IURLNormalizer)
        base_name = normalizer.normalize(obj.sigel)
        name = base_name
        i = 1
        while name in container.objectIds():
            name = base_name + '-' + str(i)
            i += 1
        return name

    def chooseSignatureName(self, name, obj):
        """ NameChooser based on signature attribute
        """
        container = aq_inner(self.context)
        normalizer = queryUtility(IURLNormalizer)
        base_name = normalizer.normalize(obj.signature)
        name = base_name
        i = 1
        while name in container.objectIds():
            name = base_name + '-' + str(i)
            i += 1
        return name

    def chooseDefaultName(self, name, obj):
        """ NameChooser based on title attribute
        """
        container = aq_inner(self.context)
        normalizer = queryUtility(IURLNormalizer)
        base_name = normalizer.normalize(obj.title)
        name = base_name
        i = 1
        while name in container.objectIds():
            name = base_name + '-' + str(i)
            i += 1
        return name

To activate this, we need to register our nameChooser:

<!-- Register a name chooser which handles objects inside library. -->
<adapter
    for=".library.ILibrary"
    factory=".library.LibraryNameChooser"
    provides="zope.container.interfaces.INameChooser"
    />

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

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.