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: 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: AutoRename für Dexterity content types

Im folgenden wird beschrieben wie man Dexterity Objekte in Plone, nach dem Erzeugen automatisch umbenennen kann.

Ein Weg dies zu erreichen, ist es einen EventHandler zu verwenden

Wir erzeugen eine Datei mit dem Namen "subscribers.py" in unserem Package.

from plone import api
from zope.container.interfaces import INameChooser
from .myobject import IMyObject

def autorename_my_object(obj, event):
    if not IMyObject.providedBy(obj):
        log.error("provides not IMyObject")
        return
    parent = event.newParent
    new_id = INameChooser(parent).chooseName(obj.my_object_name, obj)
    api.content.rename(obj=obj, new_id=new_id)

Der Parameter obj.my_object_name stellt dabei die Vorlage für den Namen dar. Dieser wird normalisiert und es wird sicher gestellt, dass er nicht mit vorhandenen Objekten kollidiert.

Dann registrieren wir den EvendHandler via zcml in der configure.zcml

<subscriber
  for="my.example.myobject.IMyObject
       zope.lifecycleevent.interfaces.IObjectAddedEvent"
  handler=".subscribers.autorename_my_object"
  />

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']
    )