Added features to duplicate a shared template
authorThierry Florac <thierry.florac@onf.fr>
Tue, 03 Jul 2018 15:51:19 +0200
changeset 147 7fb2efbf0b63
parent 146 6e4da3ad6a67
child 148 b9dd84d35128
Added features to duplicate a shared template
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/zmi/layout.py
src/pyams_portal/zmi/template.py
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	Tue Jul 03 15:19:50 2018 +0200
+++ b/src/pyams_portal/locales/fr/LC_MESSAGES/pyams_portal.po	Tue Jul 03 15:51:19 2018 +0200
@@ -5,7 +5,7 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2018-06-26 08:42+0200\n"
+"POT-Creation-Date: 2018-07-03 15:29+0200\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"
@@ -20,11 +20,11 @@
 msgid "Portal template"
 msgstr "Modèle de présentation"
 
-#: src/pyams_portal/__init__.py:43
+#: src/pyams_portal/__init__.py:46
 msgid "Manage portal templates"
 msgstr "Gérer les modèles de présentation"
 
-#: src/pyams_portal/__init__.py:47
+#: src/pyams_portal/__init__.py:50
 msgid "Portal templates manager"
 msgstr "Gestionnaire des modèles"
 
@@ -110,12 +110,12 @@
 msgid "Add template"
 msgstr "Ajouter un modèle"
 
-#: src/pyams_portal/zmi/template.py:144 src/pyams_portal/zmi/container.py:64
+#: src/pyams_portal/zmi/template.py:143 src/pyams_portal/zmi/container.py:64
 #: src/pyams_portal/zmi/container.py:93
 msgid "Portal templates"
 msgstr "Modèles de présentation"
 
-#: src/pyams_portal/zmi/template.py:145
+#: src/pyams_portal/zmi/template.py:144
 msgid "Add shared template"
 msgstr "Ajout d'un modèle de présentation"
 
@@ -123,12 +123,13 @@
 msgid "Presentation template"
 msgstr "Présentation"
 
-#: src/pyams_portal/zmi/template.py:169 src/pyams_portal/zmi/layout.py:271
+#: src/pyams_portal/zmi/template.py:167 src/pyams_portal/zmi/layout.py:275
+#: src/pyams_portal/zmi/layout.py:644
 msgid "Specified name is already used!"
 msgstr "Le nom indiqué est déjà utilisé !"
 
-#: src/pyams_portal/zmi/template.py:111 src/pyams_portal/zmi/layout.py:235
-#: src/pyams_portal/zmi/layout.py:316 src/pyams_portal/zmi/layout.py:400
+#: src/pyams_portal/zmi/template.py:111 src/pyams_portal/zmi/layout.py:239
+#: src/pyams_portal/zmi/layout.py:320 src/pyams_portal/zmi/layout.py:404
 #, python-format
 msgid "« {0} »  portal template"
 msgstr "Modèle de présentation « {0} »"
@@ -138,47 +139,67 @@
 msgid "{0} (local template)"
 msgstr "{0} (modèle local)"
 
-#: src/pyams_portal/zmi/layout.py:79
+#: src/pyams_portal/zmi/layout.py:83
 msgid "Properties"
 msgstr "Propriétés"
 
-#: src/pyams_portal/zmi/layout.py:177
+#: src/pyams_portal/zmi/layout.py:181
 msgid "Add row..."
 msgstr "Ajouter une ligne"
 
-#: src/pyams_portal/zmi/layout.py:219
+#: src/pyams_portal/zmi/layout.py:223
 msgid "Add slot..."
 msgstr "Ajouter un panneau"
 
-#: src/pyams_portal/zmi/layout.py:237
+#: src/pyams_portal/zmi/layout.py:241
 msgid "Add slot"
 msgstr "Ajouter un panneau"
 
-#: src/pyams_portal/zmi/layout.py:384
+#: src/pyams_portal/zmi/layout.py:388
 msgid "Add portlet..."
 msgstr "Ajouter un composant"
 
-#: src/pyams_portal/zmi/layout.py:402
+#: src/pyams_portal/zmi/layout.py:406
 msgid "Add portlet"
 msgstr "Ajouter un composant"
 
-#: src/pyams_portal/zmi/layout.py:69
+#: src/pyams_portal/zmi/layout.py:583
+msgid "Duplicate template..."
+msgstr "Dupliquer le modèle"
+
+#: src/pyams_portal/zmi/layout.py:603
+msgid "Duplicate template"
+msgstr "Dupliquer le modèle"
+
+#: src/pyams_portal/zmi/layout.py:73
 msgid "Template management"
 msgstr "Ce modèle"
 
-#: src/pyams_portal/zmi/layout.py:103
+#: src/pyams_portal/zmi/layout.py:107
 msgid "Template configuration"
 msgstr "Configuration d'un modèle"
 
-#: src/pyams_portal/zmi/layout.py:98
+#: src/pyams_portal/zmi/layout.py:593
+msgid "Cancel"
+msgstr "Annuler"
+
+#: src/pyams_portal/zmi/layout.py:594
+msgid "Duplicate this template"
+msgstr "Dupliquer ce modèle"
+
+#: src/pyams_portal/zmi/layout.py:614
+msgid "New template name"
+msgstr "Nom du nouveau modèle"
+
+#: src/pyams_portal/zmi/layout.py:102
 msgid "Local template configuration"
 msgstr "Configuration d'un modèle local"
 
-#: src/pyams_portal/zmi/layout.py:146
+#: src/pyams_portal/zmi/layout.py:150
 msgid "{{ missing portlet }}"
 msgstr "{{ composant indisponible }}"
 
-#: src/pyams_portal/zmi/layout.py:137
+#: src/pyams_portal/zmi/layout.py:141
 #, python-format
 msgid ""
 "Add component: {0}<br />Drag and drop button to page template to position "
@@ -187,12 +208,12 @@
 "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:321
+#: src/pyams_portal/zmi/layout.py:325
 #, python-format
 msgid "Edit « {0} » slot properties"
 msgstr "Propriétés du panneau « {0} »"
 
-#: src/pyams_portal/zmi/layout.py:101
+#: src/pyams_portal/zmi/layout.py:105
 #, python-format
 msgid "Shared template configuration ({0})"
 msgstr "Configuration d'un modèle partagé ({0})"
@@ -270,27 +291,27 @@
 msgid "Large desktop device (> 1170px)"
 msgstr "Écrans de grande taille (> 1170 px)"
 
-#: src/pyams_portal/zmi/templates/layout.pt:114
+#: src/pyams_portal/zmi/templates/layout.pt:116
 msgid "Override parent or template settings"
 msgstr "Remplacer le paramétrage du parent ou du modèle"
 
-#: src/pyams_portal/zmi/templates/layout.pt:133
+#: src/pyams_portal/zmi/templates/layout.pt:135
 msgid "Delete row..."
 msgstr "Supprimer la ligne"
 
-#: src/pyams_portal/zmi/templates/layout.pt:142
+#: src/pyams_portal/zmi/templates/layout.pt:144
 msgid "Edit slot properties..."
 msgstr "Propriétés"
 
-#: src/pyams_portal/zmi/templates/layout.pt:149
+#: src/pyams_portal/zmi/templates/layout.pt:151
 msgid "Delete slot..."
 msgstr "Supprimer le panneau"
 
-#: src/pyams_portal/zmi/templates/layout.pt:157
+#: src/pyams_portal/zmi/templates/layout.pt:159
 msgid "Edit portlet properties..."
 msgstr "Propriétés"
 
-#: src/pyams_portal/zmi/templates/layout.pt:165
+#: src/pyams_portal/zmi/templates/layout.pt:167
 msgid "Delete portlet..."
 msgstr "Supprimer le composant"
 
@@ -342,32 +363,32 @@
 msgid "Hidden portlet"
 msgstr "Composant non affiché"
 
-#: src/pyams_portal/interfaces/__init__.py:65
+#: src/pyams_portal/interfaces/__init__.py:68
 msgid "Portlet"
 msgstr "Composant"
 
-#: src/pyams_portal/interfaces/__init__.py:68
-#: src/pyams_portal/interfaces/__init__.py:195
+#: src/pyams_portal/interfaces/__init__.py:71
+#: src/pyams_portal/interfaces/__init__.py:198
 msgid "Slot name"
 msgstr "Nom du panneau"
 
-#: src/pyams_portal/interfaces/__init__.py:69
+#: src/pyams_portal/interfaces/__init__.py:72
 msgid "Slot name to which this configuration applies"
 msgstr "Nom du panneau correspondant à la configuration"
 
-#: src/pyams_portal/interfaces/__init__.py:82
+#: src/pyams_portal/interfaces/__init__.py:85
 msgid "Portlet renderer"
 msgstr "Mode de rendu"
 
-#: src/pyams_portal/interfaces/__init__.py:83
+#: src/pyams_portal/interfaces/__init__.py:86
 msgid "Name of renderer used to render this portlet"
 msgstr "Mode de rendu utilisé pour restituer ce composant"
 
-#: src/pyams_portal/interfaces/__init__.py:113
+#: src/pyams_portal/interfaces/__init__.py:116
 msgid "Inherit parent configuration?"
 msgstr "Hériter de la configuration du parent ?"
 
-#: src/pyams_portal/interfaces/__init__.py:114
+#: src/pyams_portal/interfaces/__init__.py:117
 msgid ""
 "This option is only available if context's parent is using the same "
 "template..."
@@ -375,27 +396,27 @@
 "Cette option n'est disponible que si le parent utilise le même modèle de "
 "présentation..."
 
-#: src/pyams_portal/interfaces/__init__.py:196
+#: src/pyams_portal/interfaces/__init__.py:199
 msgid "This name must be unique in a given template"
 msgstr "Ce nom doit être unique au sein d'un modèle de présentation"
 
-#: src/pyams_portal/interfaces/__init__.py:199
+#: src/pyams_portal/interfaces/__init__.py:202
 msgid "Row ID"
 msgstr "ID de la ligne"
 
-#: src/pyams_portal/interfaces/__init__.py:213
+#: src/pyams_portal/interfaces/__init__.py:216
 msgid "Visible slot?"
 msgstr "Panneau visible ?"
 
-#: src/pyams_portal/interfaces/__init__.py:214
+#: src/pyams_portal/interfaces/__init__.py:217
 msgid "Select 'no' to hide this slot..."
 msgstr "Choisir 'non' pour masquer ce panneau..."
 
-#: src/pyams_portal/interfaces/__init__.py:218
+#: src/pyams_portal/interfaces/__init__.py:221
 msgid "Extra small device width"
 msgstr "Largeur sur très petits périphériques"
 
-#: src/pyams_portal/interfaces/__init__.py:219
+#: src/pyams_portal/interfaces/__init__.py:222
 msgid ""
 "Slot width, in columns count, on extra small devices (phones...); set to 0 "
 "to hide the portlet"
@@ -403,11 +424,11 @@
 "Largeur du panneau, en nombre de colonnes, sur les très petits périphériques "
 "(téléphones...) ; indiquez une valeur de 0 pour masquer ce composant"
 
-#: src/pyams_portal/interfaces/__init__.py:225
+#: src/pyams_portal/interfaces/__init__.py:228
 msgid "Small device width"
 msgstr "Largeur sur petits périphériques"
 
-#: src/pyams_portal/interfaces/__init__.py:226
+#: src/pyams_portal/interfaces/__init__.py:229
 msgid ""
 "Slot width, in columns count, on small devices (tablets...); set to 0 to "
 "hide the portlet"
@@ -415,11 +436,11 @@
 "Largeur du panneau, en nombre de colonnes, sur les petits périphériques "
 "(tablettes...) ; indiquez une valeur de 0 pour masquer ce composant"
 
-#: src/pyams_portal/interfaces/__init__.py:232
+#: src/pyams_portal/interfaces/__init__.py:235
 msgid "Medium devices width"
 msgstr "Largeur sur périphériques moyens"
 
-#: src/pyams_portal/interfaces/__init__.py:233
+#: src/pyams_portal/interfaces/__init__.py:236
 msgid ""
 "Slot width, in columns count, on medium desktop devices (>= 992 pixels); set "
 "to 0 to hide the portlet"
@@ -427,11 +448,11 @@
 "Largeur du panneau, en nombre de colonnes, sur les périphériques moyens (>= "
 "992 pixels) ; indiquez une valeur de 0 pour masquer ce composant"
 
-#: src/pyams_portal/interfaces/__init__.py:239
+#: src/pyams_portal/interfaces/__init__.py:242
 msgid "Large devices width"
 msgstr "Largeur sur grands périphériques"
 
-#: src/pyams_portal/interfaces/__init__.py:240
+#: src/pyams_portal/interfaces/__init__.py:243
 msgid ""
 "Slot width, in columns count, on large desktop devices (>= 1200 pixels); set "
 "to 0 to hide the portlet"
@@ -439,27 +460,27 @@
 "Largeur du panneau, en nombre de colonnes, sur les grands périphériques (>= "
 "1200 pixels) ; indiquez une valeur de 0 pour masquer ce composant"
 
-#: src/pyams_portal/interfaces/__init__.py:246
+#: src/pyams_portal/interfaces/__init__.py:249
 msgid "CSS class"
 msgstr "Class CSS"
 
-#: src/pyams_portal/interfaces/__init__.py:247
+#: src/pyams_portal/interfaces/__init__.py:250
 msgid "CSS class applied to this slot"
 msgstr "Classe CSS spécifique appliquée à ce panneau"
 
-#: src/pyams_portal/interfaces/__init__.py:360
+#: src/pyams_portal/interfaces/__init__.py:363
 msgid "Template name"
 msgstr "Nom du modèle"
 
-#: src/pyams_portal/interfaces/__init__.py:361
+#: src/pyams_portal/interfaces/__init__.py:364
 msgid "Two registered templates can't share the same name..."
 msgstr "Deux modèles partagés ne peuvent pas utiliser le même nom..."
 
-#: src/pyams_portal/interfaces/__init__.py:382
+#: src/pyams_portal/interfaces/__init__.py:385
 msgid "Toolbar portlets"
 msgstr "Composants de la barre d'outils"
 
-#: src/pyams_portal/interfaces/__init__.py:383
+#: src/pyams_portal/interfaces/__init__.py:386
 msgid ""
 "These portlets will be directly available in templates configuration page "
 "toolbar"
@@ -467,52 +488,52 @@
 "Ces composants seront directement accessibles dans la page de configuration "
 "des modèles de présentation sous la forme d'une barre d'icônes"
 
-#: src/pyams_portal/interfaces/__init__.py:412
+#: src/pyams_portal/interfaces/__init__.py:415
 msgid "Inherit parent template?"
 msgstr "Hériter du modèle du parent ?"
 
-#: src/pyams_portal/interfaces/__init__.py:413
+#: src/pyams_portal/interfaces/__init__.py:416
 msgid "Should we reuse parent template?"
 msgstr "Doit-on ré-utiliser le modèle du parent ?"
 
-#: src/pyams_portal/interfaces/__init__.py:417
+#: src/pyams_portal/interfaces/__init__.py:420
 msgid "Override parent template?"
 msgstr "Ne pas hériter du modèle du parent ?"
 
-#: src/pyams_portal/interfaces/__init__.py:418
+#: src/pyams_portal/interfaces/__init__.py:421
 msgid "Should we override parent template?"
 msgstr "Doit-on remplacer le modèle de présentation du parent ?"
 
-#: src/pyams_portal/interfaces/__init__.py:422
+#: src/pyams_portal/interfaces/__init__.py:425
 msgid "Use local template?"
 msgstr "Utiliser un modèle local ?"
 
-#: src/pyams_portal/interfaces/__init__.py:423
+#: src/pyams_portal/interfaces/__init__.py:426
 msgid ""
 "If 'yes', you can define a custom local template instead of a shared template"
 msgstr ""
 "Si 'oui', vous pouvez définir un modèle de présentation local au lieu d'un "
 "modèle partagé"
 
-#: src/pyams_portal/interfaces/__init__.py:428
+#: src/pyams_portal/interfaces/__init__.py:431
 msgid "Local template"
 msgstr "Modèle local"
 
-#: src/pyams_portal/interfaces/__init__.py:433
+#: src/pyams_portal/interfaces/__init__.py:436
 msgid "Use shared template?"
 msgstr "Utiliser un modèle partagé"
 
-#: src/pyams_portal/interfaces/__init__.py:434
+#: src/pyams_portal/interfaces/__init__.py:437
 msgid "If 'yes', you can select a shared template"
 msgstr ""
 "Si 'oui', vous pouvez sélectionner un modèle de présentation partagé au lieu "
 "d'un modèle local"
 
-#: src/pyams_portal/interfaces/__init__.py:438
+#: src/pyams_portal/interfaces/__init__.py:441
 msgid "Page template"
 msgstr "Modèle de page"
 
-#: src/pyams_portal/interfaces/__init__.py:439
+#: src/pyams_portal/interfaces/__init__.py:442
 msgid "Template used for this page"
 msgstr "Modèle de présentation utilisé pour cette page"
 
--- a/src/pyams_portal/locales/pyams_portal.pot	Tue Jul 03 15:19:50 2018 +0200
+++ b/src/pyams_portal/locales/pyams_portal.pot	Tue Jul 03 15:51:19 2018 +0200
@@ -6,7 +6,7 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2018-06-26 08:42+0200\n"
+"POT-Creation-Date: 2018-07-03 15:29+0200\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"
@@ -20,11 +20,11 @@
 msgid "Portal template"
 msgstr ""
 
-#: ./src/pyams_portal/__init__.py:43
+#: ./src/pyams_portal/__init__.py:46
 msgid "Manage portal templates"
 msgstr ""
 
-#: ./src/pyams_portal/__init__.py:47
+#: ./src/pyams_portal/__init__.py:50
 msgid "Portal templates manager"
 msgstr ""
 
@@ -94,13 +94,13 @@
 msgid "Add template"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/template.py:144
+#: ./src/pyams_portal/zmi/template.py:143
 #: ./src/pyams_portal/zmi/container.py:64
 #: ./src/pyams_portal/zmi/container.py:93
 msgid "Portal templates"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/template.py:145
+#: ./src/pyams_portal/zmi/template.py:144
 msgid "Add shared template"
 msgstr ""
 
@@ -108,12 +108,13 @@
 msgid "Presentation template"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/template.py:169 ./src/pyams_portal/zmi/layout.py:271
+#: ./src/pyams_portal/zmi/template.py:167 ./src/pyams_portal/zmi/layout.py:275
+#: ./src/pyams_portal/zmi/layout.py:644
 msgid "Specified name is already used!"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/template.py:111 ./src/pyams_portal/zmi/layout.py:235
-#: ./src/pyams_portal/zmi/layout.py:316 ./src/pyams_portal/zmi/layout.py:400
+#: ./src/pyams_portal/zmi/template.py:111 ./src/pyams_portal/zmi/layout.py:239
+#: ./src/pyams_portal/zmi/layout.py:320 ./src/pyams_portal/zmi/layout.py:404
 #, python-format
 msgid "« {0} »  portal template"
 msgstr ""
@@ -123,59 +124,79 @@
 msgid "{0} (local template)"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:79
+#: ./src/pyams_portal/zmi/layout.py:83
 msgid "Properties"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:177
+#: ./src/pyams_portal/zmi/layout.py:181
 msgid "Add row..."
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:219
+#: ./src/pyams_portal/zmi/layout.py:223
 msgid "Add slot..."
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:237
+#: ./src/pyams_portal/zmi/layout.py:241
 msgid "Add slot"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:384
+#: ./src/pyams_portal/zmi/layout.py:388
 msgid "Add portlet..."
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:402
+#: ./src/pyams_portal/zmi/layout.py:406
 msgid "Add portlet"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:69
+#: ./src/pyams_portal/zmi/layout.py:583
+msgid "Duplicate template..."
+msgstr ""
+
+#: ./src/pyams_portal/zmi/layout.py:603
+msgid "Duplicate template"
+msgstr ""
+
+#: ./src/pyams_portal/zmi/layout.py:73
 msgid "Template management"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:103
+#: ./src/pyams_portal/zmi/layout.py:107
 msgid "Template configuration"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:98
+#: ./src/pyams_portal/zmi/layout.py:593
+msgid "Cancel"
+msgstr ""
+
+#: ./src/pyams_portal/zmi/layout.py:594
+msgid "Duplicate this template"
+msgstr ""
+
+#: ./src/pyams_portal/zmi/layout.py:614
+msgid "New template name"
+msgstr ""
+
+#: ./src/pyams_portal/zmi/layout.py:102
 msgid "Local template configuration"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:146
+#: ./src/pyams_portal/zmi/layout.py:150
 msgid "{{ missing portlet }}"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:137
+#: ./src/pyams_portal/zmi/layout.py:141
 #, 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:321
+#: ./src/pyams_portal/zmi/layout.py:325
 #, python-format
 msgid "Edit « {0} » slot properties"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/layout.py:101
+#: ./src/pyams_portal/zmi/layout.py:105
 #, python-format
 msgid "Shared template configuration ({0})"
 msgstr ""
@@ -249,27 +270,27 @@
 msgid "Large desktop device (> 1170px)"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/templates/layout.pt:114
+#: ./src/pyams_portal/zmi/templates/layout.pt:116
 msgid "Override parent or template settings"
 msgstr ""
 
-#: ./src/pyams_portal/zmi/templates/layout.pt:133
+#: ./src/pyams_portal/zmi/templates/layout.pt:135
 msgid "Delete row..."
 msgstr ""
 
-#: ./src/pyams_portal/zmi/templates/layout.pt:142
+#: ./src/pyams_portal/zmi/templates/layout.pt:144
 msgid "Edit slot properties..."
 msgstr ""
 
-#: ./src/pyams_portal/zmi/templates/layout.pt:149
+#: ./src/pyams_portal/zmi/templates/layout.pt:151
 msgid "Delete slot..."
 msgstr ""
 
-#: ./src/pyams_portal/zmi/templates/layout.pt:157
+#: ./src/pyams_portal/zmi/templates/layout.pt:159
 msgid "Edit portlet properties..."
 msgstr ""
 
-#: ./src/pyams_portal/zmi/templates/layout.pt:165
+#: ./src/pyams_portal/zmi/templates/layout.pt:167
 msgid "Delete portlet..."
 msgstr ""
 
@@ -319,160 +340,160 @@
 msgid "Hidden portlet"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:65
+#: ./src/pyams_portal/interfaces/__init__.py:68
 msgid "Portlet"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:68
-#: ./src/pyams_portal/interfaces/__init__.py:195
+#: ./src/pyams_portal/interfaces/__init__.py:71
+#: ./src/pyams_portal/interfaces/__init__.py:198
 msgid "Slot name"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:69
+#: ./src/pyams_portal/interfaces/__init__.py:72
 msgid "Slot name to which this configuration applies"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:82
+#: ./src/pyams_portal/interfaces/__init__.py:85
 msgid "Portlet renderer"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:83
+#: ./src/pyams_portal/interfaces/__init__.py:86
 msgid "Name of renderer used to render this portlet"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:113
+#: ./src/pyams_portal/interfaces/__init__.py:116
 msgid "Inherit parent configuration?"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:114
+#: ./src/pyams_portal/interfaces/__init__.py:117
 msgid ""
 "This option is only available if context's parent is using the same "
 "template..."
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:196
+#: ./src/pyams_portal/interfaces/__init__.py:199
 msgid "This name must be unique in a given template"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:199
+#: ./src/pyams_portal/interfaces/__init__.py:202
 msgid "Row ID"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:213
+#: ./src/pyams_portal/interfaces/__init__.py:216
 msgid "Visible slot?"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:214
+#: ./src/pyams_portal/interfaces/__init__.py:217
 msgid "Select 'no' to hide this slot..."
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:218
+#: ./src/pyams_portal/interfaces/__init__.py:221
 msgid "Extra small device width"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:219
+#: ./src/pyams_portal/interfaces/__init__.py:222
 msgid ""
 "Slot width, in columns count, on extra small devices (phones...); set to 0 to"
 " hide the portlet"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:225
+#: ./src/pyams_portal/interfaces/__init__.py:228
 msgid "Small device width"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:226
+#: ./src/pyams_portal/interfaces/__init__.py:229
 msgid ""
 "Slot width, in columns count, on small devices (tablets...); set to 0 to hide"
 " the portlet"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:232
+#: ./src/pyams_portal/interfaces/__init__.py:235
 msgid "Medium devices width"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:233
+#: ./src/pyams_portal/interfaces/__init__.py:236
 msgid ""
 "Slot width, in columns count, on medium desktop devices (>= 992 pixels); set "
 "to 0 to hide the portlet"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:239
+#: ./src/pyams_portal/interfaces/__init__.py:242
 msgid "Large devices width"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:240
+#: ./src/pyams_portal/interfaces/__init__.py:243
 msgid ""
 "Slot width, in columns count, on large desktop devices (>= 1200 pixels); set "
 "to 0 to hide the portlet"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:246
+#: ./src/pyams_portal/interfaces/__init__.py:249
 msgid "CSS class"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:247
+#: ./src/pyams_portal/interfaces/__init__.py:250
 msgid "CSS class applied to this slot"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:360
+#: ./src/pyams_portal/interfaces/__init__.py:363
 msgid "Template name"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:361
+#: ./src/pyams_portal/interfaces/__init__.py:364
 msgid "Two registered templates can't share the same name..."
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:382
+#: ./src/pyams_portal/interfaces/__init__.py:385
 msgid "Toolbar portlets"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:383
+#: ./src/pyams_portal/interfaces/__init__.py:386
 msgid ""
 "These portlets will be directly available in templates configuration page "
 "toolbar"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:412
+#: ./src/pyams_portal/interfaces/__init__.py:415
 msgid "Inherit parent template?"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:413
+#: ./src/pyams_portal/interfaces/__init__.py:416
 msgid "Should we reuse parent template?"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:417
+#: ./src/pyams_portal/interfaces/__init__.py:420
 msgid "Override parent template?"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:418
+#: ./src/pyams_portal/interfaces/__init__.py:421
 msgid "Should we override parent template?"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:422
+#: ./src/pyams_portal/interfaces/__init__.py:425
 msgid "Use local template?"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:423
+#: ./src/pyams_portal/interfaces/__init__.py:426
 msgid ""
 "If 'yes', you can define a custom local template instead of a shared template"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:428
+#: ./src/pyams_portal/interfaces/__init__.py:431
 msgid "Local template"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:433
+#: ./src/pyams_portal/interfaces/__init__.py:436
 msgid "Use shared template?"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:434
+#: ./src/pyams_portal/interfaces/__init__.py:437
 msgid "If 'yes', you can select a shared template"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:438
+#: ./src/pyams_portal/interfaces/__init__.py:441
 msgid "Page template"
 msgstr ""
 
-#: ./src/pyams_portal/interfaces/__init__.py:439
+#: ./src/pyams_portal/interfaces/__init__.py:442
 msgid "Template used for this page"
 msgstr ""
--- a/src/pyams_portal/zmi/layout.py	Tue Jul 03 15:19:50 2018 +0200
+++ b/src/pyams_portal/zmi/layout.py	Tue Jul 03 15:51:19 2018 +0200
@@ -9,6 +9,8 @@
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
 #
+from pyams_utils.unicode import translate_string
+from pyams_utils.url import absolute_url
 
 __docformat__ = 'restructuredtext'
 
@@ -23,7 +25,7 @@
     IPortalTemplateContainerConfiguration, IPortalPortletsConfiguration, IPortalContext, IPortalPage, \
     MANAGE_TEMPLATE_PERMISSION
 from pyams_skin.interfaces import IInnerPage, IPageHeader
-from pyams_skin.interfaces.viewlet import IToolbarAddingMenu, IMenuHeader
+from pyams_skin.interfaces.viewlet import IToolbarAddingMenu, IMenuHeader, IContextActions
 from pyams_skin.layer import IPyAMSLayer
 from pyams_zmi.interfaces.menu import IPropertiesMenu, IContentManagementMenu
 from pyams_zmi.layer import IAdminLayer
@@ -33,6 +35,7 @@
 # import packages
 from pyams_cache.beaker import get_cache
 from pyams_form.form import AJAXAddForm, AJAXEditForm, ajax_config
+from pyams_form.schema import CloseButton
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_portal.portlet import PORTLETS_CACHE_REGION, PORTLETS_CACHE_NAME
 from pyams_portal.zmi.template import PortalTemplateHeaderAdapter
@@ -40,7 +43,7 @@
 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 query_utility
+from pyams_utils.registry import query_utility, get_utility
 from pyams_utils.traversing import get_parent
 from pyams_viewlet.manager import viewletmanager_config
 from pyams_viewlet.viewlet import viewlet_config
@@ -50,7 +53,8 @@
 from pyramid.events import subscriber
 from pyramid.exceptions import NotFound
 from pyramid.view import view_config
-from z3c.form import field
+from z3c.form import field, button
+from zope.copy import copy
 from zope.interface import implementer, Invalid, Interface
 
 from pyams_portal import _
@@ -547,3 +551,94 @@
     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	Tue Jul 03 15:19:50 2018 +0200
+++ b/src/pyams_portal/zmi/template.py	Tue Jul 03 15:51:19 2018 +0200
@@ -136,8 +136,7 @@
 
 @pagelet_config(name='add-portal-template.html', context=ISite, layer=IPyAMSLayer,
                 permission=MANAGE_TEMPLATE_PERMISSION)
-@ajax_config(name='add-portal-template.json', context=ISite, layer=IPyAMSLayer,
-             permission=MANAGE_TEMPLATE_PERMISSION, base=AJAXAddForm)
+@ajax_config(name='add-portal-template.json', context=ISite, layer=IPyAMSLayer, base=AJAXAddForm)
 class PortalTemplateAddForm(AdminDialogAddForm):
     """Portal template add form"""
 
@@ -146,8 +145,7 @@
     icon_css_class = 'fa fa-fw fa-columns'
 
     fields = field.Fields(IPortalTemplate)
-    ajax_handler = 'add-portal-template.json'
-    edit_permission = None
+    edit_permission = MANAGE_TEMPLATE_PERMISSION
 
     def create(self, data):
         return PortalTemplate()