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

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;
      }
    }
  }
}

New Markup Editor for Plone

With collective.markitup, we got a nice markup editor for Plone, which we can use if we want edit contents in reStructured Text or Markdown format, instead of HTML. collective.markitup brings us a seamless integration of MarkItUp Javascript Editor.

If we edit HTML we get a fallback to TinyMCE editor. So we can just set MarkItUp as default editor and edit everythink we want with the proper editor.

MarkItUp Editor with reStructured Text and preview on bottom

MarkItUp Editor with reStructured Text and preview on bottom

By now Markdown and ReStructuredText works fine. And it has nice integration Plone to put in images and links like in TinyMCE.

collective.markitup does not provide any additional markup languages. It is just a editor you can use to edit Markdown, ReStructuredText or possibly any other markup you can choose in Plone's Markup settings control panel. By now Markdown (text/x-web-markdown) and ReStructuredText (text/restructured) works fine. And it has nice integration Plone to put in images and links like in TinyMCE.

MarkItUp Editor with finder integration, to choose images from Plone.

MarkItUp Editor with finder integration, to choose images from Plone.

The new release of collective.markitup is out now: https://pypi.python.org/pypi/collective.markitup

Manage nodejs, grunt and bower with buildout

How you can manage nodejs, grunt and bower including npm and bower dependencies with zc.buildout.

Buildout config

We can use gp.recipe.node recipe, for details see: https://pypi.python.org/pypi/gp.recipe.node

[buildout]
parts +=
    nodejs
    bower
    node_modules

[nodejs]
recipe = gp.recipe.node
version = 0.10.22
npms = grunt-cli bower less jshint csslint blueimp-tmpl uglify-js
scripts = grunt bower lessc jshint csslint tmpl.js uglifyjs

[bower]
recipe = collective.recipe.cmd
shell = /bin/bash
on_install = true
on_update = true
cmds = ./bin/bower install --config.interactive=false; echo $'\nBower modules installed.\n'

[node_modules]
recipe = collective.recipe.cmd
shell = /bin/bash
on_install = true
on_update = true
cmds = NODE_PATH="" bin/npm install .; echo $'\nNodeJS modules installed.\n'

Plone: Mit Diazo eine HTML-Liste in eine Selection verwandeln

Wenn mann statt einer HTML-Liste (<ul>) eine Auswahlliste (<select>) benötigt, kann man dies leicht mit Diazo umwandeln, ohne tief ins System eingreifen zu müssen. Im folgenden wird die Aktion anhand eines Beispiels erläutert. Wir wandeln hier ein einfaches flaches Navigations-Portlet von Plone in eine Auswahl-Liste (<select>) um.

Ausgangsstruktur

Das HTML des Portlets, welches nur die Unterordner anzeigt, also aus einer Ebene besteht, sieht wie folgt aus:

<dl class="portlet portletNavigationTree">
  <dt class="portletHeader">
    <span class="portletTopLeft"></span>
    <a href="http://127.0.0.1:8081/Plone/sitemap" class="tile">SubNavi</a>
    <span class="portletTopRight"></span>
  </dt>

  <dd class="portletItem lastItem">
    <ul class="navTree navTreeLevel0">
      <li class="navTreeItem visualNoMarker navTreeFolderish section-etwas-ueber-plone">
        <a title="" class="state-published navTreeFolderish contenttype-folder"
          href="etwas-ueber-plone">
          <span>Etwas über Plone</span>
        </a>
      </li>
      <li class="navTreeItem visualNoMarker navTreeFolderish section-etwas-ueber-python">
        <a title="" class="state-published navTreeFolderish contenttype-folder"
          href="etwas-ueber-python">
          <span>Etwas über Python</span>
        </a>
      </li>
      <li class="navTreeItem visualNoMarker navTreeFolderish section-etwas-ueber-zope">
        <a title="" class="state-published navTreeFolderish contenttype-folder"
          href="etwas-ueber-zope">
          <span>Etwas über Zope</span>
        </a>
      </li>
    </ul>
     <span class="portletBottomLeft"></span>
     <span class="portletBottomRight"></span>
  </dd>
</dl>

Zielstruktur

Die HTML-Zielstruktur sieht wie folgt aus:

<div id="subNavWrapper">
  <form action="" method="get">
    <select name="subNavSel" size="1">
      <option value="etwas-ueber-plone"Etwas über Plone</option>
      <option value="etwas-ueber-python">Etwas über Python</option>
      <option value="etwas-ueber-zope">Etwas über Zope</option>
    </select>
  </form>
</div>

Diazo-Rules

Diazo-Rules (Regeln) zum umwandeln in die Zielstruktur:

<replace
  content-children="//dl[contains(@class, 'portletNavigationTree') and ./dt/a/text() = 'SubNavi']">

  <select name="subNavSel" size="1">
  <xsl:for-each select=".//ul[contains(@class, 'navTree')]/li/a">
    <option><xsl:attribute
        name="value"><xsl:value-of
          select="./@href" /></xsl:attribute><xsl:copy-of
          select="./span/text()" /></option>
  </xsl:for-each>
  </select>

</replace>

Plone: ContentType mit NamedBlobFile in tests erzeugen

Im folgenden wird beschrieben, wie man einen ContentType mit einen NamedBlobFile field, in einem Unitest erzeugen kann.

Wir benötigen dazu eine Datei z.B. eine PDF-Datei, die wir programmatisch in unseren ContentType einsetzen können. Diese legen wir einfach in unserem tests-Verzeichniss, parallel zu unserem test ab.

Der folgende Code liest die Datei file.pdf ein und fügt sie in das Feld file des Objektes MyContentTypeObj

def getTestFile(filename):
    """ return contents of the file with the given name """
    filename = os.path.join(os.path.dirname(__file__), filename)
    return open(filename, 'r')

self.portal.invokeFactory(
    "MyContentType",
    id="mycontenttypeobj",
    title="My Content Type Obj",
)
MyContentTypeObj = self.portal.get('mycontenttypeobj')

data = getTestFile('file.pdf').read()
MyContentTypeObj.file = NamedBlobFile(
    data=data,
    contentType='',
    filename=u'file.pdf')

Plone: Javascript Links in Texten erlauben

Plone filter unsichere Inhalte aus volltexten. Wenn man aber alle Nutzer seines Portals kennt, kann man diese Regeln aus praktischen Gründen etwas lockern. z.B. ist es manchmal nützlich Links mit Javascript Funktionalität im Text platzieren zu können. Wie dies möglich gemacht wird, wird nachfolgend erklärt.

Das filtern von Javascript deaktivieren

Dazu müssen wir unter die Motorhaube, ins ZMI in /portal_transforms/safe_html gehen. Dort können wir den Wert für remove_javascript von 1 auf 0 ändern.

Danach am besten Plone neu starten, damit die Änderung wirklich sichtbar wird.

Plone: Kontakt-Link (site-action) für 2 Sprachen konfigurieren

Es ist etwas umständlich eine site-action wie die für das Kontaktformular für mehrere Sprachen auf das entsprechende Kontaktformular verweisen zu lassen. Daher sei es hier nochmal anhand eines mit PloneFormGen erstellten Formulares erleutert.

Wir legen uns mit PloneFormGen ein Kontaktformular an und übersetzen dies, so dass wir nun 2 Formulare mit unterschiedlichem Namen (kontakt und contact) haben.

site-action URL Expression

Die folgende URL-Expression tragen wir in die entsprechende site-action in portal_actions ein.

python: plone_portal_state.language() == 'de' and globals_view.navigationRootUrl() + '/kontakt' or globals_view.navigationRootUrl() + '/contact'

Wenn es mehr als 2 Sprachen werden sollten, empfiehlt es sich hier ein ScriptPython zu verwenden welches den entsprechenden String dann zurück gibt.

In Plone html nach speichern modifizieren

Plone verwendet sogenannte Tranforms um Inhalte von einem format ins andere zu übertragen. Z.B. wird der eingegebene HTML-Inhalt von text/html nach text/x-plone-outputfilters-html transformiert, wobei alle verbotenen Konstrukte entfernt werden.

Dieses Konzept kann man auch für andere Transformationen verwenden. Nachfolgend werden Platzhalter durch HTML-Code ersetzt.

Citations Transform erzeugen

Alls erstes erstellen wir einen Ordner transforms in unserem Package und erstellen darin eine Datei __init__.py mit folgendem Inhalt.

from Products.PortalTransforms.libtransforms.utils import MissingBinary
modules = [
    'html_to_html_citations',
    ]

g = globals()
transforms = []
for m in modules:
    try:
        ns = __import__(m, g, g, None)
        transforms.append(ns.register())
    except ImportError, e:
        print "Problem importing module %s : %s" % (m, e)
    except MissingBinary, e:
        print e
    except:
        import traceback
        traceback.print_exc()


def initialize(engine):
    for transform in transforms:
        engine.registerTransform(transform)

Nun erstellen wir die Datei html_to_html_citations.py mit folgendem Inhalt.

class HtmlToHtmlCitations:
    """Transform which replaces literature citations with special markup"""

    implements(ITransform)
    __name__ = "html_to_html_citations"
    inputs = ('text/html', )
    output = "text/x-html-citations"

    def __init__(self, name=None):
        self.config_metadata = {
            'inputs': (
                'list',
                'Inputs',
                'Input(s) MIME type. Change with care.'
            ),
        }
        if name:
            self.__name__ = name

        self.citationRegexp = re.compile(
            r'\[\[(?P<refid>[\w\W]+?)\]\]'
        )
        self.citations_counter = 0
        self.portal = getSite()
        self.pcat = self.portal.portal_catalog

    def name(self):
        return self.__name__

    def convert(self, orig, data, **kwargs):
        text = orig
        self.citations_counter = 0
        text = self.citationRegexp.sub(self._repl, safe_unicode(text))
        data.setData(text.encode(kwargs['encoding']))
        return dataclass HtmlToHtmlCitations:

    def _repl(self, m, **kwargs):
        self.citations_counter += 1
        refid = m.group('refid')
        cit_out_list = []
        refids = refid.split(';')
        for refid in refids:
            query = {}
            query['refid'] = refid
            query['portal_type'] = 'LiteratureContent'
            cit_brains = self.pcat(query)
            if not cit_brains:
                continue
            cit_brain = cit_brains[0]
            cit_obj = cit_brain.getObject()
            cit_out_list.append(cit_obj.render_output())

        cit_out = ""
        for cit_str in cit_out_list:
            cit_out += "<p>" + cit_str + "</p>"
        repl_str = """<a href="#"
                    title="%(cit_out)s"
                    class="literature-citation visualNoPrint"
                        >[%(refid)s]</a><span
                        class="print-only">[%(counter)s]</span>""" % {
            'cit_out': cit_out,
            'refid': refid,
            'counter': self.citations_counter
        }
        return repl_str


def register():
    return HtmlToHtmlCitations()

Mimetype für Citations registrieren

<?xml version="1.0"?>
<object name="mimetypes_registry" meta_type="MimeTypes Registry">
  <mimetype name="HTML with literature citations" binary="False"
    extensions="html" globs="" icon_path="text.png"
    mimetypes="text/x-html-citations"/>
</object>

Damit diese Registrierung per GenereicSetup funktioniert wird das Package: collective.mtrsetup benötigt!

Eventhandler erzeugen

Hier legen die Datei events.py in unserem Package an.

from plone.app.textfield.interfaces import ITransformer
from plone.app.textfield.value import RichTextValue

def transform_citationsmarkers_into_citations(event):
    """ transform citation markers into citation links if edited
        and save them as RichTextValue
    """
    obj = event.object
    if not hasattr(obj, 'text'):
        return
    transformer = ITransformer(obj)
    transformedValue = transformer(obj.text, 'text/x-html-citations')
    obj.text = RichTextValue(
        transformedValue,
        'text/html',
        'text/x-plone-outputfilters-html',
    )

Wenn man mit dem Transformer arbeitet, ist die Quelle immer der gespeicherte raw-Wert, sollte man also nach den eigentlichen Wandlungen zusätzliche Änderungen vornehmen wollen, dann sollte man folgenden Aufruf zum trasnformieren verwenden.:

pt = getToolByName(self, 'portal_transforms')
data = pt.convertTo(
    'text/x-html-citations',
    obj.text.output,
    encoding=obj.text.encoding,
    mimetype='text/x-plone-outputfilters-html')
transformedValue = ''
if data:
    transformedValue = data.getData()
if not transformedValue:
    transformedValue = obj.text.output

Eventhandler für EditFinishedEvent registrieren

In der configure.zcml in unserem package registrieren wir nun den Eventhandler für das EditFinishedEvent.

<subscriber for="plone.dexterity.interfaces.IEditFinishedEvent"
    handler=".events.transform_citationsmarkers_into_citations"
    />