Updated templates registration and added option to rename templates
authorThierry Florac <tflorac@ulthar.net>
Tue, 24 Nov 2020 10:43:30 +0100
changeset 279 c025abc00397
parent 278 e4b77545c2f1
child 280 2d92ff1ae78f
Updated templates registration and added option to rename templates
src/pyams_portal/generations/__init__.py
src/pyams_portal/generations/evolve1.py
src/pyams_portal/locales/fr/LC_MESSAGES/pyams_portal.mo
src/pyams_portal/locales/fr/LC_MESSAGES/pyams_portal.po
src/pyams_portal/locales/pyams_portal.pot
src/pyams_portal/site.py
src/pyams_portal/template.py
src/pyams_portal/zmi/layout.py
src/pyams_portal/zmi/template.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_portal/generations/__init__.py	Tue Nov 24 10:43:30 2020 +0100
@@ -0,0 +1,46 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+import logging
+from importlib import import_module
+
+from pyams_portal.interfaces import IPortalTemplateContainer
+from pyams_portal.template import PortalTemplateContainer
+from pyams_utils.interfaces.site import ISiteGenerations
+from pyams_utils.registry import utility_config
+from pyams_utils.site import check_required_utilities
+
+
+logger = logging.getLogger("PyAMS (portal)")
+
+
+REQUIRED_UTILITIES = ((IPortalTemplateContainer, '', PortalTemplateContainer, 'Portal templates'), )
+
+
+@utility_config(name='PyAMS portal', provides=ISiteGenerations)
+class PortalGenerationsChecker(object):
+    """PyAMS portal package generations checker"""
+
+    order = 70
+    generation = 2
+
+    def evolve(self, site, current=None):
+        """Check for required utilities, tables and tools"""
+        if not current:
+            current = 1
+        check_required_utilities(site, REQUIRED_UTILITIES)
+        for generation in range(current, self.generation):
+            module_name = 'pyams_portal.generations.evolve{}'.format(generation)
+            module = import_module(module_name)
+            module.evolve(site)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_portal/generations/evolve1.py	Tue Nov 24 10:43:30 2020 +0100
@@ -0,0 +1,43 @@
+#
+# Copyright (c) 2008-2018 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+from pyams_content.component.paragraph import IParagraphContainer
+from pyams_content.shared.logo.interfaces import ILogosParagraph
+from pyams_i18n.interfaces import II18n
+from pyams_portal.interfaces import IPortalContext, IPortalPage, IPortalTemplate
+from pyams_utils.container import find_objects_providing
+from pyams_utils.interfaces.intids import IUniqueID
+from pyams_utils.registry import get_local_registry, get_utilities_for, set_local_registry
+
+
+def evolve(site):
+    """Evolve 2: rename resource annotations"""
+    old_registry = get_local_registry()
+    try:
+        registry = site.getSiteManager()
+        set_local_registry(registry)
+        # Update templates registry
+        renames = {}
+        for name, template in get_utilities_for(IPortalTemplate):
+            new_name = IUniqueID(template).oid
+            renames[name] = new_name
+            registry.unregisterUtility(template, IPortalTemplate, name=name)
+            registry.registerUtility(template, IPortalTemplate, name=new_name)
+        # Update templates references
+        for context in find_objects_providing(site, IPortalContext):
+            page = IPortalPage(context)
+            page._shared_template = renames.get(page._shared_template)
+            print("Upgrading template name for « {} »".format(context))
+    finally:
+        set_local_registry(old_registry)
Binary file src/pyams_portal/locales/fr/LC_MESSAGES/pyams_portal.mo has changed
--- a/src/pyams_portal/locales/fr/LC_MESSAGES/pyams_portal.po	Mon Nov 23 17:20:28 2020 +0100
+++ b/src/pyams_portal/locales/fr/LC_MESSAGES/pyams_portal.po	Tue Nov 24 10:43:30 2020 +0100
@@ -5,7 +5,7 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2019-09-24 11:54+0200\n"
+"POT-Creation-Date: 2020-11-24 09:57+0100\n"
 "PO-Revision-Date: 2015-05-12 12:10+0200\n"
 "Last-Translator: Thierry Florac <tflorac@ulthar.net>\n"
 "Language-Team: French <traduc@traduc.org>\n"
@@ -16,7 +16,7 @@
 "Generated-By: Lingua 3.10.dev0\n"
 "Plural-Forms: nplurals=2; plural=(n > 1);\n"
 
-#: src/pyams_portal/portlet.py:177
+#: src/pyams_portal/portlet.py:180
 msgid "Renderer:"
 msgstr "Mode de rendu :"
 
@@ -201,7 +201,7 @@
 msgid "Template used for this page"
 msgstr "Modèle de présentation utilisé pour cette page"
 
-#: src/pyams_portal/template.py:81
+#: src/pyams_portal/template.py:84
 msgid "Portal template"
 msgstr "Modèle de présentation"
 
@@ -277,76 +277,56 @@
 "Vous avez choisi un nouveau mode de rendu, pensez à vérifier son "
 "paramétrage !"
 
-#: src/pyams_portal/zmi/layout.py:81
+#: src/pyams_portal/zmi/layout.py:75
 msgid "Properties"
 msgstr "Propriétés"
 
-#: src/pyams_portal/zmi/layout.py:182
+#: src/pyams_portal/zmi/layout.py:168
 msgid "Add row..."
 msgstr "Ajouter une ligne"
 
-#: src/pyams_portal/zmi/layout.py:224
+#: src/pyams_portal/zmi/layout.py:210
 msgid "Add slot..."
 msgstr "Ajouter un panneau"
 
-#: src/pyams_portal/zmi/layout.py:242
+#: src/pyams_portal/zmi/layout.py:228
 msgid "Add slot"
 msgstr "Ajouter un panneau"
 
-#: src/pyams_portal/zmi/layout.py:405
+#: src/pyams_portal/zmi/layout.py:392
 msgid "Add portlet..."
 msgstr "Ajouter un composant"
 
-#: src/pyams_portal/zmi/layout.py:423
+#: src/pyams_portal/zmi/layout.py:410
 msgid "Add portlet"
 msgstr "Ajouter un composant"
 
-#: src/pyams_portal/zmi/layout.py:600
-msgid "Duplicate template..."
-msgstr "Dupliquer le modèle"
-
-#: src/pyams_portal/zmi/layout.py:620
-msgid "Duplicate template"
-msgstr "Dupliquer le modèle"
-
-#: src/pyams_portal/zmi/layout.py:71
+#: src/pyams_portal/zmi/layout.py:65
 msgid "Template management"
 msgstr "Ce modèle"
 
-#: src/pyams_portal/zmi/layout.py:108
+#: src/pyams_portal/zmi/layout.py:102
 msgid "Template configuration"
 msgstr "Configuration d'un modèle"
 
-#: src/pyams_portal/zmi/layout.py:610
-msgid "Cancel"
-msgstr "Annuler"
-
-#: src/pyams_portal/zmi/layout.py:611
-msgid "Duplicate this template"
-msgstr "Dupliquer ce modèle"
-
-#: src/pyams_portal/zmi/layout.py:631
-msgid "New template name"
-msgstr "Nom du nouveau modèle"
-
-#: src/pyams_portal/zmi/layout.py:100
+#: src/pyams_portal/zmi/layout.py:94
 msgid "Local template configuration"
 msgstr "Configuration d'un modèle local"
 
-#: src/pyams_portal/zmi/layout.py:151
+#: src/pyams_portal/zmi/layout.py:145
 msgid "{{ missing portlet }}"
 msgstr "{{ composant indisponible }}"
 
-#: src/pyams_portal/zmi/layout.py:281 src/pyams_portal/zmi/layout.py:661
-#: src/pyams_portal/zmi/template.py:167
+#: src/pyams_portal/zmi/layout.py:267 src/pyams_portal/zmi/template.py:167
+#: src/pyams_portal/zmi/template.py:316
 msgid "Specified name is already used!"
 msgstr "Le nom indiqué est déjà utilisé !"
 
-#: src/pyams_portal/zmi/layout.py:103
+#: src/pyams_portal/zmi/layout.py:97
 msgid "Inherited local template configuration"
 msgstr "Configuration d'un modèle local hérité"
 
-#: src/pyams_portal/zmi/layout.py:142
+#: src/pyams_portal/zmi/layout.py:136
 #, python-format
 msgid ""
 "Add component: {0}<br />Drag and drop button to page template to position "
@@ -355,27 +335,27 @@
 "Ajouter un composant : <strong>{0}</strong><br />Faire un glisser/déposer du "
 "bouton dans le modèle de présentation pour positionner le nouveau composant."
 
-#: src/pyams_portal/zmi/layout.py:240 src/pyams_portal/zmi/layout.py:337
-#: src/pyams_portal/zmi/layout.py:421 src/pyams_portal/zmi/template.py:111
+#: src/pyams_portal/zmi/layout.py:226 src/pyams_portal/zmi/layout.py:323
+#: src/pyams_portal/zmi/layout.py:408 src/pyams_portal/zmi/template.py:111
 #, python-format
 msgid "« {0} »  portal template"
 msgstr "Modèle de présentation « {0} »"
 
-#: src/pyams_portal/zmi/layout.py:342
+#: src/pyams_portal/zmi/layout.py:328
 #, python-format
 msgid "Edit « {0} » slot properties"
 msgstr "Propriétés du panneau « {0} »"
 
-#: src/pyams_portal/zmi/layout.py:287
+#: src/pyams_portal/zmi/layout.py:273
 msgid "Row ID must be an integer value!"
 msgstr "Le numéro de ligne doit être un nombre entier !"
 
-#: src/pyams_portal/zmi/layout.py:106
+#: src/pyams_portal/zmi/layout.py:100
 #, python-format
 msgid "Shared template configuration ({0})"
 msgstr "Configuration d'un modèle partagé ({0})"
 
-#: src/pyams_portal/zmi/layout.py:291
+#: src/pyams_portal/zmi/layout.py:277
 #, python-format
 msgid "Row ID must be between 1 and {0}!"
 msgstr "Le numéro de ligne doit être compris entre 1 et {0}"
@@ -388,10 +368,38 @@
 msgid "Add shared template"
 msgstr "Ajout d'un modèle de présentation"
 
+#: src/pyams_portal/zmi/template.py:186
+msgid "Rename template..."
+msgstr "Renommer le modèle"
+
+#: src/pyams_portal/zmi/template.py:206 src/pyams_portal/zmi/template.py:197
+msgid "Rename template"
+msgstr "Renommer le modèle"
+
+#: src/pyams_portal/zmi/template.py:255
+msgid "Duplicate template..."
+msgstr "Dupliquer le modèle"
+
+#: src/pyams_portal/zmi/template.py:275
+msgid "Duplicate template"
+msgstr "Dupliquer le modèle"
+
 #: src/pyams_portal/zmi/template.py:60
 msgid "Presentation template"
 msgstr "Présentation"
 
+#: src/pyams_portal/zmi/template.py:196 src/pyams_portal/zmi/template.py:265
+msgid "Cancel"
+msgstr "Annuler"
+
+#: src/pyams_portal/zmi/template.py:266
+msgid "Duplicate this template"
+msgstr "Dupliquer ce modèle"
+
+#: src/pyams_portal/zmi/template.py:286
+msgid "New template name"
+msgstr "Nom du nouveau modèle"
+
 #: src/pyams_portal/zmi/template.py:87
 #, python-format
 msgid "{0} (local template)"
@@ -467,56 +475,52 @@
 msgstr "Type de périphérique sélectionné :"
 
 #: src/pyams_portal/zmi/templates/layout.pt:62
-msgid "Current device"
-msgstr "Périphérique actuel"
-
-#: src/pyams_portal/zmi/templates/layout.pt:63
 msgid "Extra small device (phone)"
 msgstr "Très petits périphériques (téléphone)"
 
-#: src/pyams_portal/zmi/templates/layout.pt:64
+#: src/pyams_portal/zmi/templates/layout.pt:63
 msgid "Small device (tablet)"
 msgstr "Petits périphériques (tablette)"
 
-#: src/pyams_portal/zmi/templates/layout.pt:65
+#: src/pyams_portal/zmi/templates/layout.pt:64
 msgid "Medium desktop device (> 970px)"
 msgstr "Écrans de taille moyenne (> 970 px)"
 
-#: src/pyams_portal/zmi/templates/layout.pt:66
+#: src/pyams_portal/zmi/templates/layout.pt:65
 msgid "Large desktop device (> 1170px)"
 msgstr "Écrans de grande taille (> 1170 px)"
 
-#: src/pyams_portal/zmi/templates/layout.pt:115
+#: src/pyams_portal/zmi/templates/layout.pt:114
 msgid "Reduce/restore portlet"
 msgstr "Réduire/restaurer le composant"
 
-#: src/pyams_portal/zmi/templates/layout.pt:120
+#: src/pyams_portal/zmi/templates/layout.pt:119
 msgid "Edit portlet properties"
 msgstr "Propriétés"
 
-#: src/pyams_portal/zmi/templates/layout.pt:122
+#: src/pyams_portal/zmi/templates/layout.pt:121
 msgid "Portlet settings are not those of original template"
 msgstr ""
 "Les propriétés du composant ne sont pas celles héritées du modèle de "
 "présentation"
 
-#: src/pyams_portal/zmi/templates/layout.pt:140
+#: src/pyams_portal/zmi/templates/layout.pt:139
 msgid "Delete row..."
 msgstr "Supprimer la ligne"
 
-#: src/pyams_portal/zmi/templates/layout.pt:149
+#: src/pyams_portal/zmi/templates/layout.pt:148
 msgid "Edit slot properties..."
 msgstr "Propriétés"
 
-#: src/pyams_portal/zmi/templates/layout.pt:156
+#: src/pyams_portal/zmi/templates/layout.pt:155
 msgid "Delete slot..."
 msgstr "Supprimer le panneau"
 
-#: src/pyams_portal/zmi/templates/layout.pt:164
+#: src/pyams_portal/zmi/templates/layout.pt:163
 msgid "Edit portlet properties..."
 msgstr "Propriétés"
 
-#: src/pyams_portal/zmi/templates/layout.pt:172
+#: src/pyams_portal/zmi/templates/layout.pt:171
 msgid "Delete portlet..."
 msgstr "Supprimer le composant"
 
@@ -628,6 +632,9 @@
 msgid "Responsive image renderer"
 msgstr "Image responsive (par défaut)"
 
+#~ msgid "Current device"
+#~ msgstr "Périphérique actuel"
+
 #~ msgid "Double spacer with horizontal ruler"
 #~ msgstr "Espace double avec trait horizontal"
 
--- a/src/pyams_portal/locales/pyams_portal.pot	Mon Nov 23 17:20:28 2020 +0100
+++ b/src/pyams_portal/locales/pyams_portal.pot	Tue Nov 24 10:43:30 2020 +0100
@@ -1,12 +1,12 @@
 #
 # SOME DESCRIPTIVE TITLE
 # This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2019.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
 #, fuzzy
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2019-09-24 11:54+0200\n"
+"POT-Creation-Date: 2020-11-24 09:57+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -16,7 +16,7 @@
 "Content-Transfer-Encoding: 8bit\n"
 "Generated-By: Lingua 3.10.dev0\n"
 
-#: ./src/pyams_portal/portlet.py:177
+#: ./src/pyams_portal/portlet.py:180
 msgid "Renderer:"
 msgstr ""
 
@@ -185,7 +185,7 @@
 msgid "Template used for this page"
 msgstr ""
 
-#: ./src/pyams_portal/template.py:81
+#: ./src/pyams_portal/template.py:84
 msgid "Portal template"
 msgstr ""
 
@@ -257,103 +257,83 @@
 "properties..."
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:81
+#: ./src/pyams_portal/zmi/layout.py:75
 msgid "Properties"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:182
+#: ./src/pyams_portal/zmi/layout.py:168
 msgid "Add row..."
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:224
+#: ./src/pyams_portal/zmi/layout.py:210
 msgid "Add slot..."
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:242
+#: ./src/pyams_portal/zmi/layout.py:228
 msgid "Add slot"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:405
+#: ./src/pyams_portal/zmi/layout.py:392
 msgid "Add portlet..."
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:423
+#: ./src/pyams_portal/zmi/layout.py:410
 msgid "Add portlet"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:600
-msgid "Duplicate template..."
-msgstr ""
-
-#: ./src/pyams_portal/zmi/layout.py:620
-msgid "Duplicate template"
-msgstr ""
-
-#: ./src/pyams_portal/zmi/layout.py:71
+#: ./src/pyams_portal/zmi/layout.py:65
 msgid "Template management"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:108
+#: ./src/pyams_portal/zmi/layout.py:102
 msgid "Template configuration"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:610
-msgid "Cancel"
-msgstr ""
-
-#: ./src/pyams_portal/zmi/layout.py:611
-msgid "Duplicate this template"
-msgstr ""
-
-#: ./src/pyams_portal/zmi/layout.py:631
-msgid "New template name"
-msgstr ""
-
-#: ./src/pyams_portal/zmi/layout.py:100
+#: ./src/pyams_portal/zmi/layout.py:94
 msgid "Local template configuration"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:151
+#: ./src/pyams_portal/zmi/layout.py:145
 msgid "{{ missing portlet }}"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:281 ./src/pyams_portal/zmi/layout.py:661
-#: ./src/pyams_portal/zmi/template.py:167
+#: ./src/pyams_portal/zmi/layout.py:267 ./src/pyams_portal/zmi/template.py:167
+#: ./src/pyams_portal/zmi/template.py:316
 msgid "Specified name is already used!"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:103
+#: ./src/pyams_portal/zmi/layout.py:97
 msgid "Inherited local template configuration"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:142
+#: ./src/pyams_portal/zmi/layout.py:136
 #, python-format
 msgid ""
 "Add component: {0}<br />Drag and drop button to page template to position new"
 " row"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:240 ./src/pyams_portal/zmi/layout.py:337
-#: ./src/pyams_portal/zmi/layout.py:421 ./src/pyams_portal/zmi/template.py:111
+#: ./src/pyams_portal/zmi/layout.py:226 ./src/pyams_portal/zmi/layout.py:323
+#: ./src/pyams_portal/zmi/layout.py:408 ./src/pyams_portal/zmi/template.py:111
 #, python-format
 msgid "« {0} »  portal template"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:342
+#: ./src/pyams_portal/zmi/layout.py:328
 #, python-format
 msgid "Edit « {0} » slot properties"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:287
+#: ./src/pyams_portal/zmi/layout.py:273
 msgid "Row ID must be an integer value!"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:106
+#: ./src/pyams_portal/zmi/layout.py:100
 #, python-format
 msgid "Shared template configuration ({0})"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:291
+#: ./src/pyams_portal/zmi/layout.py:277
 #, python-format
 msgid "Row ID must be between 1 and {0}!"
 msgstr ""
@@ -366,10 +346,40 @@
 msgid "Add shared template"
 msgstr ""
 
+#: ./src/pyams_portal/zmi/template.py:186
+msgid "Rename template..."
+msgstr ""
+
+#: ./src/pyams_portal/zmi/template.py:206
+#: ./src/pyams_portal/zmi/template.py:197
+msgid "Rename template"
+msgstr ""
+
+#: ./src/pyams_portal/zmi/template.py:255
+msgid "Duplicate template..."
+msgstr ""
+
+#: ./src/pyams_portal/zmi/template.py:275
+msgid "Duplicate template"
+msgstr ""
+
 #: ./src/pyams_portal/zmi/template.py:60
 msgid "Presentation template"
 msgstr ""
 
+#: ./src/pyams_portal/zmi/template.py:196
+#: ./src/pyams_portal/zmi/template.py:265
+msgid "Cancel"
+msgstr ""
+
+#: ./src/pyams_portal/zmi/template.py:266
+msgid "Duplicate this template"
+msgstr ""
+
+#: ./src/pyams_portal/zmi/template.py:286
+msgid "New template name"
+msgstr ""
+
 #: ./src/pyams_portal/zmi/template.py:87
 #, python-format
 msgid "{0} (local template)"
@@ -432,54 +442,50 @@
 msgstr ""
 
 #: ./src/pyams_portal/zmi/templates/layout.pt:62
-msgid "Current device"
+msgid "Extra small device (phone)"
 msgstr ""
 
 #: ./src/pyams_portal/zmi/templates/layout.pt:63
-msgid "Extra small device (phone)"
+msgid "Small device (tablet)"
 msgstr ""
 
 #: ./src/pyams_portal/zmi/templates/layout.pt:64
-msgid "Small device (tablet)"
+msgid "Medium desktop device (> 970px)"
 msgstr ""
 
 #: ./src/pyams_portal/zmi/templates/layout.pt:65
-msgid "Medium desktop device (> 970px)"
-msgstr ""
-
-#: ./src/pyams_portal/zmi/templates/layout.pt:66
 msgid "Large desktop device (> 1170px)"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/templates/layout.pt:115
+#: ./src/pyams_portal/zmi/templates/layout.pt:114
 msgid "Reduce/restore portlet"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/templates/layout.pt:120
+#: ./src/pyams_portal/zmi/templates/layout.pt:119
 msgid "Edit portlet properties"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/templates/layout.pt:122
+#: ./src/pyams_portal/zmi/templates/layout.pt:121
 msgid "Portlet settings are not those of original template"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/templates/layout.pt:140
+#: ./src/pyams_portal/zmi/templates/layout.pt:139
 msgid "Delete row..."
 msgstr ""
 
-#: ./src/pyams_portal/zmi/templates/layout.pt:149
+#: ./src/pyams_portal/zmi/templates/layout.pt:148
 msgid "Edit slot properties..."
 msgstr ""
 
-#: ./src/pyams_portal/zmi/templates/layout.pt:156
+#: ./src/pyams_portal/zmi/templates/layout.pt:155
 msgid "Delete slot..."
 msgstr ""
 
-#: ./src/pyams_portal/zmi/templates/layout.pt:164
+#: ./src/pyams_portal/zmi/templates/layout.pt:163
 msgid "Edit portlet properties..."
 msgstr ""
 
-#: ./src/pyams_portal/zmi/templates/layout.pt:172
+#: ./src/pyams_portal/zmi/templates/layout.pt:171
 msgid "Delete portlet..."
 msgstr ""
 
--- a/src/pyams_portal/site.py	Mon Nov 23 17:20:28 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-#
-# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-
-__docformat__ = 'restructuredtext'
-
-
-# import standard library
-
-# import interfaces
-from pyams_portal.interfaces import IPortalTemplateContainer
-from pyams_utils.interfaces.site import ISiteGenerations
-
-# import packages
-from pyams_portal.template import PortalTemplateContainer
-from pyams_utils.registry import utility_config
-from pyams_utils.site import check_required_utilities
-
-
-REQUIRED_UTILITIES = ((IPortalTemplateContainer, '', PortalTemplateContainer, 'Portal templates'), )
-
-
-@utility_config(name='PyAMS portal', provides=ISiteGenerations)
-class PortalGenerationsChecker(object):
-    """Portal generations checker"""
-
-    order = 70
-    generation = 1
-
-    def evolve(self, site, current=None):
-        """Check for required utilities"""
-        check_required_utilities(site, REQUIRED_UTILITIES)
--- a/src/pyams_portal/template.py	Mon Nov 23 17:20:28 2020 +0100
+++ b/src/pyams_portal/template.py	Tue Nov 24 10:43:30 2020 +0100
@@ -10,33 +10,35 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
-
 from persistent import Persistent
 from persistent.list import PersistentList
 from persistent.mapping import PersistentMapping
 from pyramid.events import subscriber
 from pyramid.threadlocal import get_current_registry
-from zope.componentvocabulary.vocabulary import UtilityVocabulary
 from zope.container.contained import Contained
 from zope.container.folder import Folder
 from zope.interface import implementer
-from zope.lifecycleevent.interfaces import IObjectAddedEvent, IObjectRemovedEvent
+from zope.intid.interfaces import IIntIdAddedEvent, IIntIdRemovedEvent
 from zope.location import locate
 from zope.schema.fieldproperty import FieldProperty
 from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
 from zope.traversing.interfaces import ITraversable
 
-from pyams_portal.interfaces import IPortalPortletsConfiguration, IPortalTemplate, IPortalTemplateConfiguration, \
-    IPortalTemplateContainer, IPortalTemplateContainerConfiguration, IPortlet, IPortletConfiguration, \
-    PORTLETS_CONFIGURATION_KEY, TEMPLATE_CONFIGURATION_KEY, TEMPLATE_CONTAINER_CONFIGURATION_KEY
+from pyams_portal.interfaces import IPortalPortletsConfiguration, IPortalTemplate, \
+    IPortalTemplateConfiguration, IPortalTemplateContainer, IPortalTemplateContainerConfiguration, \
+    IPortlet, IPortletConfiguration, PORTLETS_CONFIGURATION_KEY, TEMPLATE_CONFIGURATION_KEY, \
+    TEMPLATE_CONTAINER_CONFIGURATION_KEY
 from pyams_portal.slot import SlotConfiguration
 from pyams_utils.adapter import ContextAdapter, adapter_config, get_annotation_adapter
 from pyams_utils.factory import factory_config
-from pyams_utils.registry import get_local_registry, get_utility
+from pyams_utils.interfaces.intids import IUniqueID
+from pyams_utils.registry import get_local_registry, get_utilities_for, get_utility
 from pyams_utils.request import check_request
 from pyams_utils.vocabulary import vocabulary_config
 
+
+__docformat__ = 'restructuredtext'
+
 from pyams_portal import _
 
 
@@ -65,7 +67,8 @@
 @adapter_config(context=IPortalTemplateContainer, provides=IPortalTemplateContainerConfiguration)
 def portal_template_container_configuration_adapter(context):
     """Portal template container configuration factory"""
-    return get_annotation_adapter(context, TEMPLATE_CONTAINER_CONFIGURATION_KEY, IPortalTemplateContainerConfiguration)
+    return get_annotation_adapter(context, TEMPLATE_CONTAINER_CONFIGURATION_KEY,
+                                  IPortalTemplateContainerConfiguration)
 
 
 #
@@ -81,28 +84,35 @@
     content_name = _("Portal template")
 
 
-@subscriber(IObjectAddedEvent, context_selector=IPortalTemplate)
+@subscriber(IIntIdAddedEvent, context_selector=IPortalTemplate)
 def handle_added_template(event):
     """Register shared template"""
+    template = event.object
     registry = get_local_registry()
-    if (registry is not None) and IPortalTemplateContainer.providedBy(event.newParent):
-        registry.registerUtility(event.object, IPortalTemplate, name=event.object.name)
+    if (registry is not None) and IPortalTemplateContainer.providedBy(template.__parent__):
+        registry.registerUtility(template, IPortalTemplate,
+                                 name=IUniqueID(template).oid)
 
 
-@subscriber(IObjectRemovedEvent, context_selector=IPortalTemplate)
+@subscriber(IIntIdRemovedEvent, context_selector=IPortalTemplate)
 def handle_removed_template(event):
     """Unregister removed template"""
+    template = event.object
     registry = get_local_registry()
-    if (registry is not None) and IPortalTemplateContainer.providedBy(event.oldParent):
-        registry.unregisterUtility(event.object, IPortalTemplate, name=event.object.name)
+    if (registry is not None) and IPortalTemplateContainer.providedBy(template.__parent__):
+        registry.unregisterUtility(template, IPortalTemplate,
+                                   name=IUniqueID(template).oid)
 
 
 @vocabulary_config(name='PyAMS portal templates')
-class PortalTemplatesVocabulary(UtilityVocabulary):
+class PortalTemplatesVocabulary(SimpleVocabulary):
     """Portal templates vocabulary"""
 
-    interface = IPortalTemplate
-    nameOnly = True
+    def __init__(self, context):
+        terms = sorted([SimpleTerm(name, title=util.name)
+                        for (name, util) in get_utilities_for(IPortalTemplate)],
+                       key=lambda x: x.title)
+        super(PortalTemplatesVocabulary, self).__init__(terms)
 
 
 #
--- a/src/pyams_portal/zmi/layout.py	Mon Nov 23 17:20:28 2020 +0100
+++ b/src/pyams_portal/zmi/layout.py	Tue Nov 24 10:43:30 2020 +0100
@@ -13,7 +13,6 @@
 __docformat__ = 'restructuredtext'
 
 
-# import standard library
 import json
 
 from pyramid.decorator import reify
@@ -21,35 +20,28 @@
 from pyramid.exceptions import NotFound
 from pyramid.view import view_config
 from transaction.interfaces import ITransactionManager
-from z3c.form import button, field
+from z3c.form import field
 from z3c.form.interfaces import HIDDEN_MODE, IDataExtractedEvent
-from zope.copy import copy
-from zope.interface import Interface, Invalid, implementer
+from zope.interface import Invalid, implementer
 
-# import packages
 from pyams_cache.beaker import get_cache
 from pyams_form.form import AJAXAddForm, AJAXEditForm, ajax_config
-from pyams_form.schema import CloseButton
-# import interfaces
 from pyams_pagelet.interfaces import IPagelet, PageletCreatedEvent
 from pyams_pagelet.pagelet import pagelet_config
-from pyams_portal import _
-from pyams_portal.interfaces import IPortalContext, IPortalPage, IPortalPortletsConfiguration, IPortalTemplate, \
-    IPortalTemplateConfiguration, IPortalTemplateContainer, IPortalTemplateContainerConfiguration, IPortlet, \
-    IPortletAddingInfo, IPortletPreviewer, ISlot, ISlotConfiguration, LOCAL_TEMPLATE_NAME, MANAGE_TEMPLATE_PERMISSION
+from pyams_portal.interfaces import IPortalContext, IPortalPage, IPortalPortletsConfiguration, \
+    IPortalTemplate, IPortalTemplateConfiguration, IPortalTemplateContainer, \
+    IPortalTemplateContainerConfiguration, IPortlet, IPortletAddingInfo, IPortletPreviewer, \
+    ISlot, ISlotConfiguration, LOCAL_TEMPLATE_NAME, MANAGE_TEMPLATE_PERMISSION
 from pyams_portal.portlet import PORTLETS_CACHE_NAME, PORTLETS_CACHE_REGION
-from pyams_portal.zmi.template import PortalTemplateHeaderAdapter
-from pyams_skin.interfaces import IInnerPage, IPageHeader
-from pyams_skin.interfaces.viewlet import IContextActions, IMenuHeader, IToolbarAddingMenu
+from pyams_skin.interfaces import IInnerPage
+from pyams_skin.interfaces.viewlet import IMenuHeader, IToolbarAddingMenu
 from pyams_skin.layer import IPyAMSLayer
 from pyams_skin.viewlet.menu import MenuItem
 from pyams_skin.viewlet.toolbar import JsToolbarMenuItem, ToolbarMenuDivider, ToolbarMenuItem
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
-from pyams_utils.registry import get_utility, query_utility
+from pyams_utils.registry import query_utility
 from pyams_utils.traversing import get_parent
-from pyams_utils.unicode import translate_string
-from pyams_utils.url import absolute_url
 from pyams_viewlet.manager import viewletmanager_config
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminDialogAddForm, AdminDialogEditForm
@@ -57,6 +49,8 @@
 from pyams_zmi.layer import IAdminLayer
 from pyams_zmi.view import AdminView
 
+from pyams_portal import _
+
 
 @adapter_config(context=(IPortalTemplate, IContentManagementMenu), provides=IMenuHeader)
 class PortalTemplateMenuHeader(object):
@@ -161,14 +155,6 @@
             return ''
 
 
-@adapter_config(context=(IPortalTemplate, IAdminLayer, Interface), provides=IPageHeader)
-class PortalTemplateLayoutHeaderAdapter(PortalTemplateHeaderAdapter):
-    """Portal template configuration header adapter"""
-
-    back_url = '/admin#portal-templates.html'
-    back_target = None
-
-
 #
 # Rows views
 #
@@ -573,94 +559,3 @@
     config = IPortalTemplateConfiguration(request.context)
     config.delete_portlet(int(request.params.get('portlet_id')))
     return {'status': 'success'}
-
-
-#
-# Portal template duplication form
-#
-
-@viewlet_config(name='duplication.divider', context=IPortalTemplate, layer=IPyAMSLayer,
-                view=PortalTemplateLayoutView, manager=IContextActions, permission=MANAGE_TEMPLATE_PERMISSION, weight=99)
-class PortalTemplateDuplicationMenuDivider(ToolbarMenuDivider):
-    """Portal template duplication menu divider"""
-
-    def __new__(cls, context, request, view, manager):
-        container = get_parent(context, IPortalTemplateContainer)
-        if container is None:
-            return None
-        return ToolbarMenuDivider.__new__(cls)
-
-
-@viewlet_config(name='duplication.menu', context=IPortalTemplate, layer=IPyAMSLayer,
-                view=PortalTemplateLayoutView, manager=IContextActions, permission=MANAGE_TEMPLATE_PERMISSION, weight=100)
-class PortalTemplateDuplicationMenu(ToolbarMenuItem):
-    """Portal template duplication menu item"""
-
-    def __new__(cls, context, request, view, manager):
-        container = get_parent(context, IPortalTemplateContainer)
-        if container is None:
-            return None
-        return ToolbarMenuDivider.__new__(cls)
-
-    label = _("Duplicate template...")
-    label_css_class = 'fa fa-fw fa-files-o'
-
-    url = 'duplicate.html'
-    modal_target = True
-
-
-class IPortalTemplateDuplicationButtons(Interface):
-    """Portal template duplication form buttons"""
-
-    close = CloseButton(name='close', title=_("Cancel"))
-    duplicate = button.Button(name='duplicate', title=_("Duplicate this template"))
-
-
-@pagelet_config(name='duplicate.html', context=IPortalTemplate, layer=IPyAMSLayer,
-                permission=MANAGE_TEMPLATE_PERMISSION)
-@ajax_config(name='duplicate.json', context=IPortalTemplate, layer=IPyAMSLayer, base=AJAXAddForm)
-class PortalTemplateDuplicationForm(AdminDialogAddForm):
-    """Portal template duplicate form"""
-
-    legend = _("Duplicate template")
-    icon_css_class = 'fa fa-fw fa-files-o'
-
-    fields = field.Fields(IPortalTemplate)
-    buttons = button.Buttons(IPortalTemplateDuplicationButtons)
-
-    edit_permission = MANAGE_TEMPLATE_PERMISSION
-
-    def updateWidgets(self, prefix=None):
-        super(PortalTemplateDuplicationForm, self).updateWidgets(prefix)
-        if 'name' in self.widgets:
-            self.widgets['name'].label = _("New template name")
-
-    def updateActions(self):
-        super(PortalTemplateDuplicationForm, self).updateActions()
-        if 'duplicate' in self.actions:
-            self.actions['duplicate'].addClass('btn-primary')
-
-    def create(self, data):
-        return copy(self.context)
-
-    def add(self, template):
-        container = get_utility(IPortalTemplateContainer)
-        container[translate_string(template.name, spaces='-')] = template
-
-    def nextURL(self):
-        return absolute_url(self.request.root, self.request, 'admin#portal-templates.html')
-
-    def get_ajax_output(self, changes):
-        return {
-            'status': 'redirect',
-            'location': self.nextURL()
-        }
-
-
-@subscriber(IDataExtractedEvent, form_selector=PortalTemplateDuplicationForm)
-def handle_new_template_data_extraction(event):
-    """Handle new template form data extraction"""
-    container = get_utility(IPortalTemplateContainer)
-    name = translate_string(event.data.get('name'), spaces='-')
-    if name in container:
-        event.form.widgets.errors += (Invalid(_("Specified name is already used!")),)
--- a/src/pyams_portal/zmi/template.py	Mon Nov 23 17:20:28 2020 +0100
+++ b/src/pyams_portal/zmi/template.py	Tue Nov 24 10:43:30 2020 +0100
@@ -10,41 +10,41 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
-
-
-# import standard library
-
-# import interfaces
-from pyams_portal.interfaces import IPortalTemplateContainer, IPortalTemplate, IPortalContext, \
-    MANAGE_TEMPLATE_PERMISSION, LOCAL_TEMPLATE_NAME
-from pyams_skin.interfaces import IPageHeader, IContentTitle
-from pyams_skin.interfaces.container import ITableElementName, ITableElementEditor
-from pyams_skin.interfaces.viewlet import IWidgetTitleViewletManager, IBreadcrumbItem
-from pyams_skin.layer import IPyAMSLayer
-from pyams_zmi.layer import IAdminLayer
+from pyramid.events import subscriber
+from z3c.form import button, field
 from z3c.form.interfaces import IDataExtractedEvent
 from zope.component.interfaces import ISite
+from zope.copy import copy
+from zope.interface import Interface, Invalid
 
-# import packages
 from pyams_form.form import AJAXAddForm, ajax_config
+from pyams_form.schema import CloseButton
 from pyams_pagelet.pagelet import pagelet_config
+from pyams_portal.interfaces import IPortalContext, IPortalTemplate, IPortalTemplateContainer, \
+    LOCAL_TEMPLATE_NAME, MANAGE_TEMPLATE_PERMISSION
 from pyams_portal.template import PortalTemplate
 from pyams_portal.zmi.container import PortalTemplateContainerTable
+from pyams_portal.zmi.layout import PortalTemplateLayoutView
+from pyams_skin.interfaces import IContentTitle, IPageHeader
+from pyams_skin.interfaces.container import ITableElementEditor, ITableElementName
+from pyams_skin.interfaces.viewlet import IBreadcrumbItem, IContextActions, \
+    IWidgetTitleViewletManager
+from pyams_skin.layer import IPyAMSLayer
 from pyams_skin.page import DefaultPageHeaderAdapter
 from pyams_skin.table import DefaultElementEditorAdapter
 from pyams_skin.viewlet.breadcrumb import BreadcrumbAdminLayerItem
-from pyams_skin.viewlet.toolbar import ToolbarAction
-from pyams_utils.adapter import adapter_config, ContextRequestAdapter
-from pyams_utils.registry import query_utility
+from pyams_skin.viewlet.toolbar import ToolbarAction, ToolbarMenuDivider, ToolbarMenuItem
+from pyams_utils.adapter import ContextRequestAdapter, adapter_config
+from pyams_utils.registry import get_utility, query_utility
 from pyams_utils.traversing import get_parent
 from pyams_utils.unicode import translate_string
 from pyams_utils.url import absolute_url
 from pyams_viewlet.viewlet import viewlet_config
-from pyams_zmi.form import AdminDialogAddForm
-from pyramid.events import subscriber
-from z3c.form import field
-from zope.interface import Interface, Invalid
+from pyams_zmi.form import AdminDialogAddForm, AdminDialogEditForm
+from pyams_zmi.layer import IAdminLayer
+
+
+__docformat__ = 'restructuredtext'
 
 from pyams_portal import _
 
@@ -162,6 +162,160 @@
 def handle_new_template_data_extraction(event):
     """Handle new template form data extraction"""
     container = query_utility(IPortalTemplateContainer)
-    name = event.data.get('name')
+    name = translate_string(event.data.get('name', ''), spaces='-')
     if name in container:
         event.form.widgets.errors += (Invalid(_("Specified name is already used!")),)
+
+
+#
+# Template renaming form
+#
+
+@viewlet_config(name='rename.menu', context=IPortalTemplate, layer=IPyAMSLayer,
+                view=PortalTemplateLayoutView, manager=IContextActions,
+                permission=MANAGE_TEMPLATE_PERMISSION, weight=100)
+class PortalTemplateRenameMenu(ToolbarMenuItem):
+    """Portal template rename menu item"""
+
+    def __new__(cls, context, request, view, manager):
+        container = get_parent(context, IPortalTemplateContainer)
+        if container is None:
+            return None
+        return ToolbarMenuDivider.__new__(cls)
+
+    label = _("Rename template...")
+    label_css_class = 'fa fa-fw fa-edit'
+
+    url = 'rename.html'
+    modal_target = True
+
+
+class IPortalTemplateRenameButtons(Interface):
+    """Portal template rename form buttons"""
+
+    close = CloseButton(name='close', title=_("Cancel"))
+    rename = button.Button(name='rename', title=_("Rename template"))
+
+
+@pagelet_config(name='rename.html', context=IPortalTemplate, layer=IPyAMSLayer,
+                permission=MANAGE_TEMPLATE_PERMISSION)
+@ajax_config(name='rename.json', context=IPortalTemplate, layer=IPyAMSLayer)
+class PortalTemplateRenameForm(AdminDialogEditForm):
+    """Portal template rename form"""
+
+    legend = _("Rename template")
+    icon_css_class = 'fa fa-fw fa-edit'
+
+    fields = field.Fields(IPortalTemplate).select('name')
+    buttons = button.Buttons(IPortalTemplateRenameButtons)
+
+    edit_permission = MANAGE_TEMPLATE_PERMISSION
+
+    _renamed = False
+
+    def updateActions(self):
+        super(PortalTemplateRenameForm, self).updateActions()
+        if 'rename' in self.actions:
+            self.actions['rename'].addClass('btn-primary')
+
+    def update_content(self, content, data):
+        changes = super(PortalTemplateRenameForm, self).update_content(content, data)
+        if changes:
+            data = data.get(self, data)
+            old_name = content.__name__
+            new_name = translate_string(data.get('name'), spaces='-')
+            if old_name != new_name:
+                parent = content.__parent__
+                parent[new_name] = content
+                del parent[old_name]
+                self._renamed = True
+        return changes
+
+    def get_ajax_output(self, changes):
+        if self._renamed:
+            return {
+                'status': 'redirect',
+                'location': absolute_url(self.getContent(), self.request, 'admin#properties.html')
+            }
+        else:
+            return super(PortalTemplateRenameForm, self).get_ajax_output(changes)
+
+
+#
+# Template duplication form
+#
+
+@viewlet_config(name='duplication.menu', context=IPortalTemplate, layer=IPyAMSLayer,
+                view=PortalTemplateLayoutView, manager=IContextActions,
+                permission=MANAGE_TEMPLATE_PERMISSION, weight=110)
+class PortalTemplateDuplicationMenu(ToolbarMenuItem):
+    """Portal template duplication menu item"""
+
+    def __new__(cls, context, request, view, manager):
+        container = get_parent(context, IPortalTemplateContainer)
+        if container is None:
+            return None
+        return ToolbarMenuDivider.__new__(cls)
+
+    label = _("Duplicate template...")
+    label_css_class = 'fa fa-fw fa-files-o'
+
+    url = 'duplicate.html'
+    modal_target = True
+
+
+class IPortalTemplateDuplicationButtons(Interface):
+    """Portal template duplication form buttons"""
+
+    close = CloseButton(name='close', title=_("Cancel"))
+    duplicate = button.Button(name='duplicate', title=_("Duplicate this template"))
+
+
+@pagelet_config(name='duplicate.html', context=IPortalTemplate, layer=IPyAMSLayer,
+                permission=MANAGE_TEMPLATE_PERMISSION)
+@ajax_config(name='duplicate.json', context=IPortalTemplate, layer=IPyAMSLayer, base=AJAXAddForm)
+class PortalTemplateDuplicationForm(AdminDialogAddForm):
+    """Portal template duplicate form"""
+
+    legend = _("Duplicate template")
+    icon_css_class = 'fa fa-fw fa-files-o'
+
+    fields = field.Fields(IPortalTemplate)
+    buttons = button.Buttons(IPortalTemplateDuplicationButtons)
+
+    edit_permission = MANAGE_TEMPLATE_PERMISSION
+
+    def updateWidgets(self, prefix=None):
+        super(PortalTemplateDuplicationForm, self).updateWidgets(prefix)
+        if 'name' in self.widgets:
+            self.widgets['name'].label = _("New template name")
+
+    def updateActions(self):
+        super(PortalTemplateDuplicationForm, self).updateActions()
+        if 'duplicate' in self.actions:
+            self.actions['duplicate'].addClass('btn-primary')
+
+    def create(self, data):
+        return copy(self.context)
+
+    def add(self, template):
+        container = get_utility(IPortalTemplateContainer)
+        container[translate_string(template.name, spaces='-')] = template
+
+    def nextURL(self):
+        return absolute_url(self.request.root, self.request, 'admin#portal-templates.html')
+
+    def get_ajax_output(self, changes):
+        return {
+            'status': 'redirect',
+            'location': self.nextURL()
+        }
+
+
+@subscriber(IDataExtractedEvent, form_selector=PortalTemplateDuplicationForm)
+def handle_new_template_data_extraction(event):
+    """Handle new template form data extraction"""
+    container = get_utility(IPortalTemplateContainer)
+    name = translate_string(event.data.get('name'), spaces='-')
+    if name in container:
+        event.form.widgets.errors += (Invalid(_("Specified name is already used!")),)