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-Error: ConfigurationError Couldn't import ZServer.ZPublisher on start

ConfigurationError: ('Invalid value for', 'module', "ImportError: Couldn't import ZServer.ZPublisher, No module named ZPublisher")

In case you get the follwing error on Plone 4.3.x:

File "/home/maik/develop/plone/.buildout/eggs/zope.configuration-3.7.4-py2.7.egg/zope/configuration/config.py", line 705, in finish
  args = toargs(context, *self.argdata)
File "/home/maik/develop/plone/.buildout/eggs/zope.configuration-3.7.4-py2.7.egg/zope/configuration/config.py", line 1397, in toargs
  args[str(name)] = field.fromUnicode(s)
File "/home/maik/develop/plone/.buildout/eggs/zope.configuration-3.7.4-py2.7.egg/zope/configuration/fields.py", line 139, in fromUnicode
  raise schema.ValidationError(v)
  zope.configuration.xmlconfig.ZopeXMLConfigurationError: File "/home/maik/develop/plone/bla/bla/bla.zsbdb/parts/instance/etc/site.zcml", line 16.2-16.23
  ZopeXMLConfigurationError: File "/home/maik/develop/plone/.buildout/eggs/Products.CMFPlone-4.3.18-py2.7.egg/Products/CMFPlone/configure.zcml", line 98.4-102.10
  ZopeXMLConfigurationError: File "/home/maik/develop/plone/.buildout/eggs/Products.PDBDebugMode-1.4-py2.7.egg/Products/PDBDebugMode/configure.zcml", line 46.8-51.13
  ConfigurationError: ('Invalid value for', 'module', "ImportError: Couldn't import ZServer.ZPublisher, No module named ZPublisher")

The solution is, to pin Products.PDBDebugMode = 1.3 in your buildout config.

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.

from Acquisition import aq_inner
from plone.i18n.normalizer.interfaces import IURLNormalizer
from zope.container.interfaces import INameChooser
from zope.component import queryUtility


@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: Synchronizing ZEO Servers with ZRS

With ZRS you can synchronize ZEO servers very easy. Some lines in your buildout configuration and you have a primary/secondary synchronization.

Primary ZEO Server

The primary ZEO server is writeable like a normal ZEO server and provides it's updates to host:port defined in: replicate-to

[zeo]
recipe = plone.recipe.zeoserver[zrs]
eggs =
  Plone
  plone.recipe.zeoserver[zrs]
zeo-address = 8000
replicate-to = 10.0.0.10:9001
replicate-from =
keep-alive-delay = 120

Secondary ZEO Server

The sencondary ZEO server is read only and gets it's update from the primary ZEO server defined in: replicate-from

[zeo]
recipe = plone.recipe.zeoserver[zrs]
eggs =
  Plone
  plone.recipe.zeoserver[zrs]
zeo-address = 8000
replicate-to =
replicate-from = 10.0.0.10:9001
keep-alive-delay = 120

Using secondary ZEO Server

You can use the secondary ZEO server in readly mode, for example to have a readonly copy for a external website, where the writable instances are more protected behind the scene. Or you could use it to distribute the load of ready requests.

Plone: override registry record, if it is not possible to delete them

Sometimes a plone.registry record can not deleted normally because of some exceptions. But it might be possible to override them with somthing new and clean.

Error on delete

Errors like this can happen, if you try to deleted a record and the underlying code is not valid.

TypeError: ('object.__new__(RegistryFieldDefaultFactory) is not safe, use Persistence.Persistent.__new__()', <function _reconstructor at 0x7f341b1c5a28>,
   (<class 'example.behavior.settings.example_settings.RegistryFieldDefaultFactory'>, <type 'object'>, None))

Override a record

You can start Plone in debug shell:

./bin/instance debug
portal = app.Plone
registry = portal.portal_registry
from plone.registry import Record
from plone.registry import field

registry.records['example.behavior.settings.example_settings.ISettings.projects'] = Record(
   field.TextLine(title=u"Projects"), u"Test")

import transaction
transaction.commit()

Delete the record

Now we can delete the record without an error.

del registry.records['example.behavior.settings.example_settings.ISettings.projects']

transaction.commit()

Plone: Transmogrifier sent event

Unfortunately there is no blueprint to send an event after creating objects. So we here is a quick and dirty way to solve this.

Blueprint to send ObjectAddedEvent

[notify]
blueprint = collective.transmogrifier.sections.inserter
key = nothing
condition = python:(
 modules['zope'].event.notify(
     modules['zope'].lifecycleevent.ObjectAddedEvent(
         transmogrifier.context.unrestrictedTraverse(
             item['_path'].lstrip('/')
         )
     )
 ))
value = nothing

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())