Index: /main/waeup.sirp/trunk/src/waeup/sirp/images/__init__.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/images/__init__.py	(revision 6293)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/images/__init__.py	(revision 6293)
@@ -0,0 +1,1 @@
+# Make this a package
Index: /main/waeup.sirp/trunk/src/waeup/sirp/images/container.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/images/container.py	(revision 6293)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/images/container.py	(revision 6293)
@@ -0,0 +1,86 @@
+##
+## container.py
+## Login : <uli@pu.smp.net>
+## Started on  Tue Jun  7 12:02:19 2011 Uli Fouquet
+## $Id$
+## 
+## Copyright (C) 2011 Uli Fouquet
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+## 
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+## 
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""
+Components for storing site images (passport photographs, etc.)
+"""
+import grok
+from waeup.sirp.interfaces import IWAeUPSIRPPluggable, IImagesContainer
+
+class ImagesContainer(grok.Container):
+    grok.implements(IImagesContainer)
+
+
+class ImagesContainerPlugin(grok.GlobalUtility):
+    """A WAeUPSIRPPlugin that creates an images container in portal.
+
+    This plugin should be called by a typical
+    `waeup.sirp.app.Universtiy` instance on creation time. The
+    :meth:`update` method normally can also be triggered manually over
+    the main site configuration.
+
+    Implements :class:`waeup.sirp.interfaces.IWAeUPSIRPPluggable`
+    """
+    grok.name('images')
+    grok.implements(IWAeUPSIRPPluggable)
+    log_prefix = 'ImagesPlugin'
+
+    def setup(self, site, name, logger):
+        """Create a new :class:`ImagesContainer` instance in `site`.
+        """
+        site['images'] = ImagesContainer()
+        logger.info(
+            '%s: Installed images container.' % (self.log_prefix,)
+            )
+        return
+
+    def update(self, site, name, logger):
+        """Update site wide ``images`` container.
+
+        If the site already contains a suitable ``images`` container,
+        leave it that way. If not create one and delete the old one if
+        appropriate.
+        """
+        images_folder = site.get('images', None)
+        site_name = getattr(site, '__name__', '<Unnamed Site>')
+        if IImagesContainer.providedBy(images_folder):
+            # Applicants up to date. Return.
+            logger.info(
+                '%s: Updating site at %s: Nothing to do.' % (
+                    self.log_prefix, site_name,)
+                )
+            return
+        elif images_folder is not None:
+            # Images need update. Remove old instance.
+            logger.warn(
+                '%s: Outdated images folder detected at site %s.'
+                'Removing it.' % (self.log_prefix, site_name)
+                    )
+            del site['images']
+        # Add new images container.
+        logger.info(
+            '%s: Updating site at %s. Installing '
+            'images container.' % (self.log_prefix, site_name,)
+            )
+        self.setup(site, name, logger)
+        return
+
+
Index: /main/waeup.sirp/trunk/src/waeup/sirp/images/tests/__init__.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/images/tests/__init__.py	(revision 6293)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/images/tests/__init__.py	(revision 6293)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/waeup.sirp/trunk/src/waeup/sirp/images/tests/test_container.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/images/tests/test_container.py	(revision 6293)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/images/tests/test_container.py	(revision 6293)
@@ -0,0 +1,122 @@
+##
+## test_container.py
+## Login : <uli@pu.smp.net>
+## Started on  Tue Jun  7 12:11:06 2011 Uli Fouquet
+## $Id$
+## 
+## Copyright (C) 2011 Uli Fouquet
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+## 
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+## 
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+import os
+import logging
+import unittest
+from cStringIO import StringIO
+from waeup.sirp.images.container import ImagesContainer, ImagesContainerPlugin
+
+class FakeSite(dict):
+    pass
+
+class ImagesContainerPluginTests(unittest.TestCase):
+    def create_logger(self):
+        # create a logger suitable for local tests.
+        test_logger = logging.getLogger('waeup.sirp.images.testlogger')
+        log = StringIO()
+        handler = logging.StreamHandler(log)
+        handler.setLevel(logging.DEBUG)
+        test_logger.addHandler(handler)
+        test_logger.setLevel(logging.DEBUG)
+        self.logger = test_logger
+        self.log = log
+        self.handler = handler
+        return self.logger
+
+    def remove_logger(self):
+        del self.handler
+        del self.logger
+        del self.log
+        pass
+
+    def get_log(self):
+        self.log.seek(0)
+        return self.log.read()
+
+    def setUp(self):
+        self.create_logger()
+        return
+
+    def tearDown(self):
+        self.remove_logger()
+        return
+
+    # Real tests start here...
+    def test_pluginsetup(self):
+        # Make sure we can add ImagesContainer to sites.
+        site = FakeSite()
+        plugin = ImagesContainerPlugin()
+        plugin.setup(site, 'blah', self.logger)
+        self.assertTrue('images' in site.keys())
+        log = self.get_log()
+        self.assertTrue('Installed images container.' in log)
+        return
+
+    def test_update_new(self):
+        # Run update on a site without images container.
+        site = FakeSite()
+        plugin = ImagesContainerPlugin()
+        plugin.update(site, 'blah', self.logger)
+        self.assertTrue('images' in site.keys())
+        log = self.get_log()
+        self.assertTrue('Updating site at <Unnamed Site>' in log)
+        self.assertTrue('Installed images container.' in log)
+        return
+
+    def test_update_outdated(self):
+        # Run update on a site with outdated images container.
+        site = FakeSite()
+        root = object() # # This is not a proper images container
+        site['images'] = root
+        plugin = ImagesContainerPlugin()
+        plugin.update(site, 'blah', self.logger)
+        self.assertTrue(site['images'] is not root)
+        self.assertTrue(isinstance(site['images'], ImagesContainer))
+        log = self.get_log()
+        self.assertTrue('Outdated images folder detected' in log)
+        self.assertTrue('Updating site at <Unnamed Site>' in log)
+        self.assertTrue('Installed images container.' in log)
+        return
+
+    def test_update_uptodate(self):
+        # Run update on a site with proper images root.
+        site = FakeSite()
+        root = ImagesContainer()
+        site['images'] = root
+        plugin = ImagesContainerPlugin()
+        plugin.update(site, 'blah', self.logger)
+        self.assertTrue(site['images'] is root)
+        log = self.get_log()
+        self.assertTrue('Updating site at <Unnamed Site>' in log)
+        self.assertTrue('Nothing to do' in log)
+        return
+
+    def test_update_log(self):
+        # Check that sitename is used in log messages on updates.
+        site = FakeSite()
+        site.__name__ = 'my_site'
+        plugin = ImagesContainerPlugin()
+        plugin.update(site, 'blah', self.logger)
+        log = self.get_log()
+        self.assertTrue('Updating site at my_site.' in log)
+        return
+
Index: /main/waeup.sirp/trunk/src/waeup/sirp/interfaces.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/interfaces.py	(revision 6292)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/interfaces.py	(revision 6293)
@@ -418,2 +418,6 @@
         If ``<DATA_DICT>` is non-empty, there were no errors.
         """
+
+class IImagesContainer(IWAeUPObject):
+    """A container that stores images.
+    """
