Passing dynamic settings to Javascript in Plone

Here is described a way to pass data from site or context object to a Javascripts easily. For each page, we create a <script> section which will include all the options in a Javascript filled in by Python code. The object is automatically created from a Python dict using JSON library.

We create the script tag in <head> section using a Grok viewlet registered there.

viewlet.py:

# -*- coding: utf-8 -*-
"""

    Viewlets related to application logic.

"""

# Python imports
import json

# Zope imports
from Acquisition import aq_inner
from zope.interface import Interface
from five import grok
from zope.component import getMultiAdapter

# Plone imports
from plone.app.layout.viewlets.interfaces import IHtmlHead

# The viewlets in this file are rendered on every content item type
grok.context(Interface)

# Use templates directory to search for templates.
grok.templatedir('templates')

# The generated HTML snippet going to <head>
TEMPLATE = u"""
<script type="text/javascript">
    var %(name)s = %(json)s;
</script>
"""

class JavascriptSettingsSnippet(grok.Viewlet):
    """ Include dynamic Javascript code in <head>.

    Include some code in <head> section which initializes
    Javascript variables. Later this code can be used
    by various scripts.

    Useful for settings.
    """

    grok.viewletmanager(IHtmlHead)

    def getSettings(self):
        """
        @return: Python dictionary of settings
        """

        context = aq_inner(self.context)
        portal_state = getMultiAdapter((context, self.request), name=u'plone_portal_state')

        return {
            # Pass dynamically allocated site URL to the Javascripts (virtual host monster thing)
            "staticMediaURL" : portal_state.portal_url() + "/++resource++yourcompany.app",
            # Some other example parameters
            "schoolId" : 3,
            "restService" : "http://yourserver.com:8080/rest"
        }

    def render(self):
        """
        Render the settings as inline Javascript object in HTML <head>
        """
        settings = self.getSettings()
        json_snippet = json.dumps(settings)

        # Use Python string template facility to produce the code
        html = TEMPLATE % { "name" : "youroptions", "json" : json_snippet }

        return html

\"\" Subscribe to RSS feed Follow me on Twitter Follow me on Facebook Follow me Google+

Integrating Facebook with Plone

This blog post shows how to integrate some of Facebook features to your Plone site programmatically.

See the add-on

for non-programming integration.

1. Like button

Here is an example which creates a Like button pointing to the current page.

Page template code:

<iframe tal:attributes="src string:${view/getFBURL}" scrolling="no" frameborder="0" style="border:none; overflow:hidden; width:227px; height:50px;" allowTransparency="true"></iframe>

View code

import urllib

...

class YourView(BrowserView):

    ...

        def getQuotedURL(self):
            url = self.context.absolute_url()
            url = urllib.quote(url)
            return url

        def getFBURL(self):
            base = "http://www.facebook.com/plugins/like.php?href=%(url)s&layout=standardt&show_faces=false&width=227&action=like&colorscheme=light&height=50"
            url = base % {"url" : self.getQuotedURL() }
            return url

Note

If you are using Like button you should also add OpenGraph metadata to your pages as described below.

More info

2. OpenGraph metadata

OpenGraph is Facebook page metadata protocol. You’ll insert extra <meta> tags on the page which will give additional information about the page to be displayed with Facebook links.

Below is an example of filling in Facebook metadata

  • Using content description in Facebook
  • Having main image
  • Having location
  • Having contact info

Example

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
    lang="en"
    metal:use-macro="here/main_template/macros/master"
    i18n:domain="saariselka.app"
    >

     <tal:comment replace="nothing">
     <!--

              We will insert this HTML to <head> section,
              "head_slot", defined by Plone's main_template.pt

     -->
    </tal:comment>

    <tal:facebook-opengraph metal:fill-slot="head_slot" >

        <meta property="og:description" tal:attributes="content context/Description"/>
        <meta property="og:type" content="hotel"/>

        <tal:comment replace="nothing">
             <!--

                      Fill in geo info if available.
             -->
        </tal:comment>
        <tal:has-location omit-tag="" tal:define="lat view/data/Latitude|nothing; long view/data/Longitude|nothing;" tal:condition="lat">
              <meta property="og:latitude" tal:attributes="content lat"/>
              <meta property="og:longitude" tal:attributes="content long"/>
        </tal:has-location>

        <tal:comment replace="nothing">
             <!--

                      Fill in contact info.
             -->
        </tal:comment>
        <meta property="og:email" content="xxx@yoursite.com"/>
        <meta property="og:phone_number" content="+ 358 123 1234"/>

        <tal:comment replace="nothing">
             <!--

                      URL to 70 px wide image used by Facebook as the news item splash image.

                      Note: Facebook resized the image automatically.

             -->
        </tal:comment>
        <tal:has-image omit-tag="" condition="view/main_image">
              <meta property="og:image" tal:attributes="content view/main_image"/>
        </tal:has-image>

        <tal:comment replace="nothing">
             <!-- Facebook admins is a compulsory field. Put here the side admin Facebook id(s), comma separated

                  http://apps.facebook.com/whatismyid
             -->
        </tal:comment>
        <meta property="fb:admins" content="123123" />

    </tal:facebook-opengraph>

\"\" Subscribe to RSS feed Follow me on Twitter Follow me on Facebook Follow me Google+

Encrypted folders on Ubuntu Linux using eCryptfs on an external hard drive

This blog post continues my Ubuntu encryption tools testing. Previously there was an example for losetup. However, with the latest Ubuntus eCryptfs is recommended instead.

eCrypfs makes one directory in a file-system crypted. Since it does not work on a partition level, you do not need to worry about extending or shrinking the encrypted partition inside the uncrypted partition. Instead, file system works normally and only the content of the files are encrypted. This should also add some more fault tolerance in the case of disk failure – it is less unlikely to loose the whole encrypted partition.

Here we create an encrypted directory on an external hard drive

  • First format the drive with ext4 file-system (mkfs.ext4)

Prepare a passphrase in a .TXT file (you won’t be asked to type mistyped passphrase again).

Then go to the mounted disk

cd /media/fbf0a2c3-0631-4a00-ad1b-a34e449c8b2a/
mkdir crypted
chmod 700 crypted/
sudo mount -t ecryptfs crypted/ crypted/

Copy-paste in the passphrase and otherwise use the default settings given by ecryptfs.

Voilá. Now your encrypted folder is ready. It is not accessible if you do not mount it with eCryptfs and enter the passphrase.

We can test it with umount and mounting it again. It will ask passphrase and  format options again:

echo "foobar" > test.txt
umount /media/fbf0a2c3-0631-4a00-ad1b-a34e449c8b2a/crypted
cd crypted
cat test.txt

You will see garbled output instead of the file contents. But after you remount it it works again:

mount -t ecryptfs crypted/ crypted/

Just give the passphrase and hit enter to all options (again).

1. More info

\"\" Subscribe to RSS feed Follow me on Twitter Follow me on Facebook Follow me Google+

Sticky session load balancing with Apache and mod_balancer on Ubuntu Linux

Apache 2.2 can do load balancing with sticky sessions. However, there is a catch. You need to use mod_headers module to set a cookie based on the chosen balancer member for the first request and then route the subsequent requests to this client.

Use cases

The method described here works in every situation and does not rely on client IP address, etc. The only downside is that if one balancer member goes down all subsequent requests for it will die. So this method can be only used for load balancing, not for high availability (I am not sure if BALANCER_ROUTE_CHANGED environment variable is set when a balancer member is lost and would redirect the clients to a new balancer member).

This requests were tested on Ubuntu Linux, but may as well work in other environments.

1. Setting route configution in virtual host

Create a balancer

<Proxy balancer://yourlb>
 BalancerMember http://127.0.0.1:13001/ route=1
 BalancerMember http://127.0.0.1:13002/ route=2
 BalancerMember http://127.0.0.1:13003/ route=3
 BalancerMember http://127.0.0.1:13004/ route=4
</Proxy>

Set the cookie using mod_headers. Note that the cookie must be in format [session name].[route id] (the dot is required). It seems to be possible to leave session name empty.

Header add Set-Cookie "ROUTEID=.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED

Make ProxyPass  follow the cookie (Zope virtual host monster style with HTTPS)

ProxyPass / balancer://lbsits/http://localhost/VirtualHostBase/https/yoursite.org:443/yourplonesiteid/VirtualHostRoot/ stickysession=ROUTEID

Note: Hard restart is required. apache2ctl graceful is not enough to make new balancer rules effective.

2. Testing

Use wget

wget -S https://yoursite.org/

See that Set-Cookie: ROUTE_ID is present and it contains a valid value (is not empty)

HTTP/1.1 200 OK
 Date: Wed, 13 Apr 2011 15:21:52 GMT
 Server: Zope/(Zope 2.10.9-final, python 2.4.5, linux2) ZServer/1.1 Plone/3.3.3
 Content-Length: 23197
 Expires: Sat, 01 Jan 2000 00:00:00 GMT
 Content-Type: text/html;charset=utf-8
 Content-Language: en
 Set-Cookie: I18N_LANGUAGE="en"; Path=/
 Set-Cookie: ROUTEID=.1; path=/

3. More info

\"\" Subscribe to RSS feed Follow me on Twitter Follow me on Facebook Follow me Google+