--- a/src/pyams_portal/__init__.py Thu Oct 08 12:26:42 2015 +0200
+++ b/src/pyams_portal/__init__.py Mon Jan 18 18:09:46 2016 +0100
@@ -19,6 +19,7 @@
from pyramid.i18n import TranslationStringFactory
_ = TranslationStringFactory('pyams_portal')
+from pyams_portal.interfaces import MANAGE_TEMPLATE_PERMISSION
from pyams_utils.interfaces import VIEW_PERMISSION, VIEW_SYSTEM_PERMISSION
@@ -29,10 +30,10 @@
include_package(config)
# register custom permissions
- config.register_permission({'id': 'portal.templates.manage',
+ config.register_permission({'id': MANAGE_TEMPLATE_PERMISSION,
'title': _("Manage portal templates")})
# register custom roles
config.register_role({'id': 'portal.TemplatesManager',
'title': _("Portal templates manager"),
- 'permissions': {'portal.templates.manage', VIEW_PERMISSION, VIEW_SYSTEM_PERMISSION}})
+ 'permissions': {MANAGE_TEMPLATE_PERMISSION, VIEW_PERMISSION, VIEW_SYSTEM_PERMISSION}})
--- a/src/pyams_portal/interfaces/__init__.py Thu Oct 08 12:26:42 2015 +0200
+++ b/src/pyams_portal/interfaces/__init__.py Mon Jan 18 18:09:46 2016 +0100
@@ -16,10 +16,10 @@
# import standard library
# import interfaces
-from pyams_workflow.interfaces import IWorkflowManagedContent
from zope.annotation.interfaces import IAttributeAnnotatable
from zope.container.interfaces import IContainer
from zope.contentprovider.interfaces import IContentProvider
+from zope.location.interfaces import ILocation, IContained
# import packages
from pyams_security.schema import PermissionField
@@ -31,21 +31,33 @@
from pyams_portal import _
+MANAGE_TEMPLATE_PERMISSION = 'pyams_portal.manage_template'
+
+
+#
+# Portlet interfaces
+#
+
class IPortlet(Interface):
- """Portlet interface"""
+ """Portlet utility interface
+
+ Portlets are registered utilities providing IPortlet
+ """
name = Attribute("Portlet internal name")
label = Attribute("Portlet visible name")
permission = PermissionField(title="Portlet permission",
- description="Permission required to display permission",
+ description="Permission required to display portlet",
required=False)
- toolbar_image = Attribute("Porlet toolbar image")
+ toolbar_image = Attribute("Portlet toolbar image")
toolbar_css_class = Attribute("Portlet toolbar CSS class")
+ settings_class = Attribute("Portlet settings class")
+
class IPortletAddingInfo(Interface):
"""Portlet adding info interface"""
@@ -59,35 +71,53 @@
vocabulary='PyAMS template slots')
-class IPortletConfiguration(Interface):
- """Portlet configuration interface"""
+class IPortletSettings(ILocation, IAttributeAnnotatable):
+ """Portlet settings interface
+
+ Portlet settings is parented to it's configuration
+ """
+
+ configuration = Attribute("Settings parent configuration")
+
+ visible = Bool(title=_("Visible portlet?"),
+ description=_("Select 'no' to hide this portlet..."),
+ required=True,
+ default=True)
+
- template = Attribute("Template to which this configuration applis")
+PORTLETS_CONFIGURATION_KEY = 'pyams_portal.portlets'
+
+
+class IPortletConfiguration(ILocation):
+ """Portlet common configuration interface
- slot_name = TextLine(title=_("Slot name"),
- description=_("Slot name to which this configuration applies"),
- required=True)
+ This is generic configuration settings common to all portlets.
+ Portlet configuration is parented to:
+ - it's template if parent is the template
+ - it's context if parent is a portal context
+ """
+
+ portlet_id = Int(title="Portlet ID",
+ required=True)
portlet_name = Attribute("Portlet name")
- position = Int(title=_("Position"),
- description=_("Portlet position inside slot"),
- required=True,
- min=0)
-
- visible = Bool(title=_("Visible portlet?"),
- description=_("Select 'no' to hide this portlet. This will not break configuration inheritance..."),
- required=True,
- default=True)
-
can_inherit = Attribute("Can inherit parent configuration?")
+ parent = Attribute("Portlet configuration parent")
+
inherit_parent = Bool(title=_("Inherit parent configuration?"),
- description=_("This option is only available if context's parent is using the same template "
- "and if this portlet is also present in the same slot..."),
+ description=_("This option is only available if context's parent is using the same "
+ "template..."),
required=True,
default=True)
+ settings = Object(title="Portlet local settings",
+ schema=IPortletSettings,
+ readonly=True)
+
+ editor_settings = Attribute("Editor settings")
+
class IPortletContentProvider(IContentProvider):
"""Portlet content provider"""
@@ -115,6 +145,26 @@
"""
+class IPortalPortletsConfiguration(IContained):
+ """Portal template portlet configuration interface"""
+
+ def get_portlet_configuration(self, portlet_id):
+ """Get portlet configuration for given slot"""
+
+ def set_portlet_configuration(self, portlet_id, config):
+ """Set portlet configuration"""
+
+ def delete_portlet_configuration(self, portlet_id):
+ """Delete portlet configuration"""
+
+
+#
+# Slot interfaces
+#
+
+PORTAL_SLOTS_KEY = 'pyams_portal.slots'
+
+
class ISlot(Interface):
"""Portal template slot interface"""
@@ -133,19 +183,14 @@
slot_name = TextLine(title="Slot name")
+ portlet_ids = PersistentList(title="Portlet IDs",
+ value_type=Int())
+
visible = Bool(title=_("Visible slot?"),
- description=_("Select 'no' to hide this slot. This will not break configuration inheritance..."),
+ description=_("Select 'no' to hide this slot..."),
required=True,
default=True)
- can_inherit = Attribute("Can inherit parent configuration?")
-
- inherit_parent = Bool(title=_("Inherit parent configuration?"),
- description=_("This option is only available if context's parent template is using a "
- "template containing the same slot..."),
- required=True,
- default=True)
-
xs_width = Int(title=_("Extra small device width"),
description=_("Slot width, in columns count, on extra small devices (phones...); "
"set to 0 to hide the portlet"),
@@ -192,9 +237,14 @@
"""Slot renderer"""
-class IPortalTemplateConfiguration(Interface):
+#
+# Template configuration interfaces
+#
+
+class IPortalTemplateConfiguration(IContained):
"""Portal template configuration interface"""
+ # Rows configuration
rows = Int(title="Rows count",
required=True,
default=1,
@@ -209,6 +259,7 @@
def delete_row(self, row_id):
"""Delete template row"""
+ # Slots configuration
slot_names = PersistentList(title="Slot names",
value_type=TextLine())
@@ -216,14 +267,6 @@
key_type=Int(), # row index
value_type=PersistentList(value_type=TextLine())) # slot name
- slots = PersistentDict(title="Slots portlets",
- description="List of slots associated with a given template",
- key_type=Int(), # row index
- value_type=PersistentDict(key_type=TextLine(), # slot name
- value_type=PersistentList(value_type=Choice(
- vocabulary='PyAMS portal portlets')), # portlet names
- required=False))
-
slot_config = PersistentDict(title="Slots configuration",
key_type=TextLine(), # slot name
value_type=Object(schema=ISlotConfiguration),
@@ -253,33 +296,39 @@
def delete_slot(self, slot_name):
"""Delete template slot"""
-
-# class IPortalPortletsConfiguration(Interface):
-# """Portal template portlets configuration interface"""
-#
- portlet_config = PersistentDict(title="Portlet configuration",
- key_type=TextLine(), # slot name
- value_type=PersistentDict(key_type=Int(min=0), # portlet position inside slot
- value_type=Object(schema=IPortletConfiguration)),
- required=False)
-
+ # Portlets configuration
def add_portlet(self, portlet_name, slot_name):
"""Add portlet to givben slot"""
- def set_portlet_order(self, order):
- """Set template portlets order"""
+ def get_portlet_slot(self, portlet_id):
+ """Get row ID and slot for given portlet"""
- def get_portlet_configuration(self, slot_name, position):
- """Get portlet configuration for given slot"""
+ def set_portlet_order(self, slot_name, order):
+ """Set template portlets order"""
def delete_portlet(self, slot_name, position):
"""Delete template portlet"""
-class IPortalTemplate(IAttributeAnnotatable):
+#
+# Portal templates interfaces
+#
+
+TEMPLATE_CONFIGURATION_KEY = 'pyams_portal.template'
+
+TEMPLATE_CONTAINER_CONFIGURATION_KEY = 'pyams_portal.container.configuration'
+
+PORTAL_PAGE_KEY = 'pyams_portal.page'
+
+
+class ILocalTemplateHandler(IAttributeAnnotatable):
+ """Base interface for local template handler"""
+
+
+class IPortalTemplate(ILocalTemplateHandler):
"""Portal template interface
- A portal template is a named utility providing a name and a set of slots.
+ A portal template is a named utility providing a name and a set of slots containing portlets
"""
name = TextLine(title=_("Template name"),
@@ -287,24 +336,28 @@
required=True)
-class IPortalWfTemplate(IWorkflowManagedContent):
- """Workflow managed portal template interface"""
-
-
class IPortalTemplateContainer(IContainer, IAttributeAnnotatable):
"""Portal template container interface"""
contains(IPortalTemplate)
+ last_portlet_id = Int(title="Last portlet ID",
+ required=True,
+ default=1,
+ min=0)
+
+ def get_portlet_id(self):
+ """Get new portlet ID"""
+
class IPortalTemplateContainerConfiguration(Interface):
"""Portal templates container configuration"""
- selected_portlets = List(title=_("Selected portlets"),
- description=_("These portlets will be directly available in templates configuration "
- "page toolbar"),
- value_type=Choice(vocabulary="PyAMS portal portlets"),
- required=False)
+ toolbar_portlets = List(title=_("Toolbar portlets"),
+ description=_("These portlets will be directly available in templates configuration "
+ "page toolbar"),
+ value_type=Choice(vocabulary="PyAMS portal portlets"),
+ required=False)
class IPortalTemplateRenderer(IContentProvider):
@@ -320,11 +373,13 @@
The page is the highest configuration level.
It defines which template is used (a shared or local one), which gives
- the slots list.
+ the slot and portlet lists.
"""
can_inherit = Attribute("Can inherit parent template?")
+ parent = Attribute("Parent from which to inherit, the real parent or the source template")
+
inherit_parent = Bool(title=_("Inherit parent template?"),
description=_("Should we reuse parent template?"),
required=True,
@@ -347,7 +402,7 @@
raise Invalid(_("You must choose to use a local template or select a shared one!"))
local_template = Object(title=_("Local template"),
- schema=IPortalWfTemplate,
+ schema=IPortalTemplate,
required=False)
template = Attribute("Used template")
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 Thu Oct 08 12:26:42 2015 +0200
+++ b/src/pyams_portal/locales/fr/LC_MESSAGES/pyams_portal.po Mon Jan 18 18:09:46 2016 +0100
@@ -5,7 +5,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2015-05-12 13:59+0200\n"
+"POT-Creation-Date: 2016-01-06 11:55+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,312 +16,202 @@
"Generated-By: Lingua 3.10.dev0\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-#: src/pyams_portal/workflow.py:41
-msgid "Draft"
-msgstr "Brouillon"
-
-#: src/pyams_portal/workflow.py:42
-msgid "Published"
-msgstr "Publié"
-
-#: src/pyams_portal/workflow.py:43
-msgid "Retired"
-msgstr "Retiré"
-
-#: src/pyams_portal/workflow.py:44
-msgid "Archived"
-msgstr "Archivé"
-
-#: src/pyams_portal/workflow.py:45
-msgid "Deleted"
-msgstr "Supprimé"
-
-#: src/pyams_portal/workflow.py:103
-msgid "Initialize"
-msgstr "Initialiser"
-
-#: src/pyams_portal/workflow.py:108
-msgid "Publish..."
-msgstr "Publier..."
-
-#: src/pyams_portal/workflow.py:116
-msgid ""
-"This content is currently in DRAFT mode.\n"
-" Publishing it will make it "
-"publicly visible."
-msgstr ""
-"Ce modèle est actuellement en mode BROUILLON.\n"
-"En le publiant, vous le rendrez visible."
-
-#: src/pyams_portal/workflow.py:120
-msgid "Retire..."
-msgstr "Retirer..."
-
-#: src/pyams_portal/workflow.py:128
-msgid ""
-"This content is actually published.\n"
-" You can retire it to make "
-"it invisible, but contents using this\n"
-" template won't be visible "
-"anymore!"
-msgstr ""
-"Ce modèle est actuellement publié.\n"
-"Vous pouvez le retirer pour le rendre invisible, mais les contenus qui "
-"utilisent ce modèle ne seront plus consultables !"
-
-#: src/pyams_portal/workflow.py:133 src/pyams_portal/workflow.py:181
-msgid "Create new version..."
-msgstr "Créer une nouvelle version..."
-
-#: src/pyams_portal/workflow.py:143
-msgid "Re-publish..."
-msgstr "Re-publier..."
-
-#: src/pyams_portal/workflow.py:151
-msgid ""
-"This content was published and retired.\n"
-" You can re-publish it to "
-"make it visible again."
-msgstr ""
-"Ce modèle a été publié puis retiré.\n"
-"Vous pouvez le re-publier pour le rendre à nouveau disponible."
-
-#: src/pyams_portal/workflow.py:155 src/pyams_portal/workflow.py:168
-msgid "Archive..."
-msgstr "Archiver..."
-
-#: src/pyams_portal/workflow.py:163
-msgid ""
-"This content is currently published.\n"
-" If it is archived, it will "
-"not be possible to make it visible again\n"
-" except by creating a new "
-"version!"
-msgstr ""
-"Ce modèle est actuellement publié.\n"
-"S'il est archivé, il ne sera plus possible de le rendre à nouveau "
-"disponible, sauf en créant une nouvelle version."
-
-#: src/pyams_portal/workflow.py:176
-msgid ""
-"This content has been published but is currently retired.\n"
-" If it is archived, it will "
-"not be possible to make it visible again\n"
-" except by creating a new "
-"version!"
-msgstr ""
-"Ce contenu a été publié mais est actuellement retiré.\n"
-"S'il est archivé, il ne sera plus possible de le rendre à nouveau "
-"disponible, sauf en créant une nouvelle version."
-
-#: src/pyams_portal/workflow.py:191
-msgid "Delete..."
-msgstr "Supprimer..."
-
-#: src/pyams_portal/workflow.py:199
-msgid ""
-"This content has never been published.\n"
-" It can be removed and definitely deleted."
-msgstr ""
-"Ce modèle n'a jamais été publié.\n"
-"Vous pouvez donc le supprimer définitivement."
-
-#: src/pyams_portal/__init__.py:31
+#: src/pyams_portal/__init__.py:34
msgid "Manage portal templates"
msgstr "Gérer les modèles de présentation"
-#: src/pyams_portal/__init__.py:35
+#: src/pyams_portal/__init__.py:38
msgid "Portal templates manager"
msgstr "Gestionnaire des modèles"
-#: src/pyams_portal/zmi/portlet.py:41
-msgid "Edit portlet configuration"
-msgstr "Modifier la configuration d'un modèle"
+#: src/pyams_portal/zmi/portlet.py:49
+msgid "Edit portlet settings"
+msgstr "Propriétés du composant"
+
+#: src/pyams_portal/zmi/portlet.py:85
+msgid "Main properties"
+msgstr "Propriétés"
-#: src/pyams_portal/zmi/portlet.py:38
+#: src/pyams_portal/zmi/portlet.py:64
+msgid "Override parent settings"
+msgstr "Remplacer le paramétrage du parent"
+
+#: src/pyams_portal/zmi/portlet.py:66
+msgid "Override template settings"
+msgstr "Remplacer le paramétrage du modèle"
+
+#: src/pyams_portal/zmi/portlet.py:46
#, python-format
msgid "« {0} » portal template - {1}"
msgstr "Modèle de présentation « {0} » - {1}"
-#: src/pyams_portal/zmi/template/config.py:61
-msgid "Properties"
-msgstr "Propriétés"
-
-#: src/pyams_portal/zmi/template/config.py:72
-msgid "Portal template configuration"
-msgstr "Configuration d'un modèle"
+#: src/pyams_portal/zmi/page.py:60
+msgid "Presentation"
+msgstr "Présentation"
-#: src/pyams_portal/zmi/template/config.py:120
-msgid "Portlets configuration"
-msgstr "Configuration des portlets"
+#: src/pyams_portal/zmi/page.py:75
+msgid "Edit template configuration"
+msgstr "Choix du modèle de présentation"
-#: src/pyams_portal/zmi/template/config.py:133
-msgid "Add row..."
-msgstr "Ajouter une ligne..."
+#: src/pyams_portal/zmi/page.py:119
+msgid "Template properties"
+msgstr "Configuration du modèle"
-#: src/pyams_portal/zmi/template/config.py:175
-msgid "Add slot..."
-msgstr "Ajouter un slot..."
-
-#: src/pyams_portal/zmi/template/config.py:191
-msgid "Add slot"
-msgstr "Ajout d'un slot"
+#: src/pyams_portal/zmi/template.py:83
+msgid "Add template"
+msgstr "Ajouter un modèle"
-#: src/pyams_portal/zmi/template/config.py:265
-msgid "Edit slot properties"
-msgstr "Propriétés d'un slot"
+#: src/pyams_portal/zmi/template.py:93 src/pyams_portal/zmi/container.py:78
+msgid "Portal templates"
+msgstr "Modèles de présentation"
-#: src/pyams_portal/zmi/template/config.py:333
-msgid "Add portlet..."
-msgstr "Ajouter un composant..."
+#: src/pyams_portal/zmi/template.py:94
+msgid "Add shared template"
+msgstr "Ajout d'un modèle de présentation"
-#: src/pyams_portal/zmi/template/config.py:349
-msgid "Add portlet"
-msgstr "Ajouter un composant"
-
-#: src/pyams_portal/zmi/template/config.py:209
-#: src/pyams_portal/zmi/template/__init__.py:269
+#: src/pyams_portal/zmi/template.py:118 src/pyams_portal/zmi/layout.py:246
msgid "Specified name is already used!"
msgstr "Le nom indiqué est déjà utilisé !"
-#: src/pyams_portal/zmi/template/config.py:118
-#: src/pyams_portal/zmi/template/config.py:189
-#: src/pyams_portal/zmi/template/config.py:347
+#: src/pyams_portal/zmi/template.py:62 src/pyams_portal/zmi/layout.py:220
+#: src/pyams_portal/zmi/layout.py:382
#, python-format
msgid "« {0} » portal template"
msgstr "Modèle de présentation « {0} »"
-#: src/pyams_portal/zmi/template/config.py:262
+#: src/pyams_portal/zmi/layout.py:78
+msgid "Properties"
+msgstr "Propriétés"
+
+#: src/pyams_portal/zmi/layout.py:164
+msgid "Add row..."
+msgstr "Ajouter une ligne..."
+
+#: src/pyams_portal/zmi/layout.py:206
+msgid "Add slot..."
+msgstr "Ajouter un panneau..."
+
+#: src/pyams_portal/zmi/layout.py:222
+#: src/pyams_portal/zmi/templates/layout.pt:27
+msgid "Add slot"
+msgstr "Ajout d'un panneau"
+
+#: src/pyams_portal/zmi/layout.py:303
+msgid "Edit slot properties"
+msgstr "Propriétés d'un panneau"
+
+#: src/pyams_portal/zmi/layout.py:368
+msgid "Add portlet..."
+msgstr "Ajouter un composant..."
+
+#: src/pyams_portal/zmi/layout.py:384
+msgid "Add portlet"
+msgstr "Ajouter un composant"
+
+#: src/pyams_portal/zmi/layout.py:68
+msgid "Template management"
+msgstr "Ce modèle"
+
+#: src/pyams_portal/zmi/layout.py:101
+msgid "Template configuration"
+msgstr "Configuration d'un modèle"
+
+#: src/pyams_portal/zmi/layout.py:96
+msgid "Local template configuration"
+msgstr "Configuration d'un modèle local"
+
+#: src/pyams_portal/zmi/layout.py:300
#, python-format
msgid "« {0} » portal template - {1} slot"
-msgstr "Modèle de présentation « {0} » - Slot {1}"
-
-#: src/pyams_portal/zmi/template/workflow.py:109
-msgid "Publish template"
-msgstr "Publier un modèle"
-
-#: src/pyams_portal/zmi/template/workflow.py:151
-msgid "Retire template"
-msgstr "Retirer un modèle"
-
-#: src/pyams_portal/zmi/template/workflow.py:180
-msgid "Archive template"
-msgstr "Archiver un modèle"
-
-#: src/pyams_portal/zmi/template/workflow.py:209
-#: src/pyams_portal/zmi/template/workflow.py:201
-msgid "Create new version"
-msgstr "Créer une nouvelle version"
+msgstr "Modèle de présentation « {0} » - Panneau {1}"
-#: src/pyams_portal/zmi/template/workflow.py:100
-#: src/pyams_portal/zmi/template/workflow.py:142
-#: src/pyams_portal/zmi/template/workflow.py:171
-#: src/pyams_portal/zmi/template/workflow.py:200
-msgid "Close"
-msgstr "Fermer"
-
-#: src/pyams_portal/zmi/template/workflow.py:101
-msgid "Publish"
-msgstr "Publier"
+#: src/pyams_portal/zmi/layout.py:99
+#, python-format
+msgid "Shared template configuration ({0})"
+msgstr "Configuration d'un modèle partagé ({0})"
-#: src/pyams_portal/zmi/template/workflow.py:143
-msgid "Retire"
-msgstr "Retirer"
-
-#: src/pyams_portal/zmi/template/workflow.py:172
-msgid "Archive"
-msgstr "Archiver"
-
-#: src/pyams_portal/zmi/template/__init__.py:88
-#: src/pyams_portal/zmi/template/__init__.py:196
-#: src/pyams_portal/zmi/template/__init__.py:240
-msgid "Portal templates"
-msgstr "Modèles de présentation"
-
-#: src/pyams_portal/zmi/template/__init__.py:97
+#: src/pyams_portal/zmi/container.py:87
msgid "Shared portal templates"
msgstr "Modèles de présentation partagés"
-#: src/pyams_portal/zmi/template/__init__.py:163
+#: src/pyams_portal/zmi/container.py:127
msgid "Delete template"
msgstr "Supprimer le modèle"
-#: src/pyams_portal/zmi/template/__init__.py:195
-msgid "Portal"
-msgstr "Portail"
+#: src/pyams_portal/zmi/container.py:170
+msgid "Selected portlets..."
+msgstr "Composants sélectionnés..."
-#: src/pyams_portal/zmi/template/__init__.py:229
-msgid "Add shared template..."
-msgstr "Ajouter un modèle partagé..."
+#: src/pyams_portal/zmi/container.py:186
+msgid "Portal templates container"
+msgstr "Gestionnaire des modèles"
-#: src/pyams_portal/zmi/template/__init__.py:241
-msgid "Add shared template"
-msgstr "Ajout d'un modèle de présentation"
+#: src/pyams_portal/zmi/container.py:187
+msgid "Edit selected portlets"
+msgstr "Sélection des composants"
-#: src/pyams_portal/zmi/template/__init__.py:153
-msgid "Older versions"
-msgstr "Versions précédentes"
+#: src/pyams_portal/zmi/templates/portlet.pt:129
+#: src/pyams_portal/zmi/templates/portlet.pt:144
+msgid "Title"
+msgstr "Titre"
+
+#: src/pyams_portal/zmi/templates/portlet.pt:159
+msgid "Tab label"
+msgstr "Libellé de l'onglet"
-#: src/pyams_portal/zmi/template/__init__.py:212
-#: src/pyams_portal/zmi/template/__init__.py:146
-#, python-format
-msgid "Version {version} ({state} - last update {date})"
-msgstr "Version {version} ({state} - dernière modification {date})"
+#: src/pyams_portal/zmi/templates/layout.pt:23
+msgid "Add row"
+msgstr "Ajouter une ligne..."
-#: src/pyams_portal/zmi/template/templates/config.pt:15
-#: src/pyams_portal/zmi/template/templates/config.pt:29
-msgid "Version ${version} - ${state}"
-msgstr "Version ${version} - ${state}"
+#: src/pyams_portal/zmi/templates/layout.pt:47
+msgid "Add another portlet..."
+msgstr "Ajouter un composant..."
-#: src/pyams_portal/zmi/template/templates/config.pt:42
+#: src/pyams_portal/zmi/templates/layout.pt:54
msgid "Selected display:"
msgstr "Type de périphérique sélectionné :"
-#: src/pyams_portal/zmi/template/templates/config.pt:47
+#: src/pyams_portal/zmi/templates/layout.pt:59
msgid "Current device"
msgstr "Périphérique actuel"
-#: src/pyams_portal/zmi/template/templates/config.pt:48
+#: src/pyams_portal/zmi/templates/layout.pt:60
msgid "Extra small device (phone)"
msgstr "Très petits périphériques (téléphone)"
-#: src/pyams_portal/zmi/template/templates/config.pt:49
+#: src/pyams_portal/zmi/templates/layout.pt:61
msgid "Small device (tablet)"
msgstr "Petits périphériques (tablette)"
-#: src/pyams_portal/zmi/template/templates/config.pt:50
+#: src/pyams_portal/zmi/templates/layout.pt:62
msgid "Medium desktop device (> 970px)"
msgstr "Écrans de taille moyenne (> 970 px)"
-#: src/pyams_portal/zmi/template/templates/config.pt:51
+#: src/pyams_portal/zmi/templates/layout.pt:63
msgid "Large desktop device (> 1170px)"
msgstr "Écrans de grande taille (> 1170 px)"
-#: src/pyams_portal/zmi/template/templates/config.pt:111
+#: src/pyams_portal/zmi/templates/layout.pt:123
msgid "Delete row..."
msgstr "Supprimer la ligne..."
-#: src/pyams_portal/zmi/template/templates/config.pt:119
+#: src/pyams_portal/zmi/templates/layout.pt:132
msgid "Edit slot properties..."
msgstr "Propriétés..."
-#: src/pyams_portal/zmi/template/templates/config.pt:126
+#: src/pyams_portal/zmi/templates/layout.pt:139
msgid "Delete slot..."
-msgstr "Supprimer le slot..."
+msgstr "Supprimer le panneau..."
-#: src/pyams_portal/zmi/template/templates/config.pt:134
+#: src/pyams_portal/zmi/templates/layout.pt:147
msgid "Edit portlet properties..."
msgstr "Propriétés..."
-#: src/pyams_portal/zmi/template/templates/config.pt:141
+#: src/pyams_portal/zmi/templates/layout.pt:155
msgid "Delete portlet..."
msgstr "Supprimer le composant..."
-#: src/pyams_portal/portlets/context/__init__.py:43
-msgid "Context content"
-msgstr "Contenu du contexte"
-
-#: src/pyams_portal/portlets/image/__init__.py:44
+#: src/pyams_portal/portlets/image/__init__.py:49
msgid "Image"
msgstr "Image"
@@ -329,176 +219,168 @@
msgid "Selected image"
msgstr "Image sélectionnée"
-#: src/pyams_portal/interfaces/__init__.py:49
+#: src/pyams_portal/portlets/content/__init__.py:46
+msgid "Context content"
+msgstr "Contenu du contexte"
+
+#: src/pyams_portal/interfaces/__init__.py:65
msgid "Portlet"
msgstr "Composant"
-#: src/pyams_portal/interfaces/__init__.py:52
-#: src/pyams_portal/interfaces/__init__.py:63
-#: src/pyams_portal/interfaces/__init__.py:117
+#: src/pyams_portal/interfaces/__init__.py:68
+#: src/pyams_portal/interfaces/__init__.py:171
msgid "Slot name"
-msgstr "Nom du slot"
-
-#: src/pyams_portal/interfaces/__init__.py:53
-#: src/pyams_portal/interfaces/__init__.py:64
-msgid "Slot name to which this configuration applies"
-msgstr "Nom du slot correspondant à la configuration"
+msgstr "Nom du panneau"
#: src/pyams_portal/interfaces/__init__.py:69
-msgid "Position"
-msgstr "Position"
+msgid "Slot name to which this configuration applies"
+msgstr "Nom du panneau correspondant à la configuration"
-#: src/pyams_portal/interfaces/__init__.py:70
-msgid "Portlet position inside slot"
-msgstr "Position du composant au sein du slot"
-
-#: src/pyams_portal/interfaces/__init__.py:74
+#: src/pyams_portal/interfaces/__init__.py:82
msgid "Visible portlet?"
msgstr "Composant visible ?"
-#: src/pyams_portal/interfaces/__init__.py:75
-msgid ""
-"Select 'no' to hide this portlet. This will not break configuration "
-"inheritance..."
-msgstr ""
-"Sélectionnez 'non' pour masquer ce composant. Ce paramètre pourra être "
-"surchargé par héritage au sein des pages qui utilisent ce composant."
+#: src/pyams_portal/interfaces/__init__.py:83
+msgid "Select 'no' to hide this portlet..."
+msgstr "Sélectionnez 'non' pour masquer ce composant..."
-#: src/pyams_portal/interfaces/__init__.py:81
-#: src/pyams_portal/interfaces/__init__.py:139
+#: src/pyams_portal/interfaces/__init__.py:109
msgid "Inherit parent configuration?"
msgstr "Hériter de la configuration du parent ?"
-#: src/pyams_portal/interfaces/__init__.py:82
+#: src/pyams_portal/interfaces/__init__.py:110
msgid ""
-"This option is only available if context's parent is using the same template "
-"and if this portlet is also present in the same slot..."
+"This option is only available if context's parent is using the same "
+"template..."
msgstr ""
"Cette option n'est disponible que si le parent utilise le même modèle de "
-"présentation et si ce composant est bien présent dans le même slot..."
+"présentation..."
-#: src/pyams_portal/interfaces/__init__.py:118
+#: src/pyams_portal/interfaces/__init__.py:172
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:121
+#: src/pyams_portal/interfaces/__init__.py:175
msgid "Row ID"
msgstr "ID de la ligne"
-#: src/pyams_portal/interfaces/__init__.py:132
+#: src/pyams_portal/interfaces/__init__.py:189
msgid "Visible slot?"
-msgstr "Slot visible ?"
+msgstr "Panneau visible ?"
-#: src/pyams_portal/interfaces/__init__.py:133
-msgid ""
-"Select 'no' to hide this slot. This will not break configuration "
-"inheritance..."
-msgstr ""
-"Sélectionnez 'non' pour marquer ce slot. Ce paramètre pourra être surchargé "
-"par héritage..."
+#: src/pyams_portal/interfaces/__init__.py:190
+msgid "Select 'no' to hide this slot..."
+msgstr "Choisir 'non' pour masquer ce panneau..."
-#: src/pyams_portal/interfaces/__init__.py:140
-msgid ""
-"This option is only available if context's parent template is using a "
-"template containing the same slot..."
-msgstr ""
-"Cette option n'est disponible que si le parent utilise un modèle contenant "
-"un slot de même nom..."
-
-#: src/pyams_portal/interfaces/__init__.py:145
+#: src/pyams_portal/interfaces/__init__.py:194
msgid "Extra small device width"
msgstr "Largeur sur très petits périphériques"
-#: src/pyams_portal/interfaces/__init__.py:146
+#: src/pyams_portal/interfaces/__init__.py:195
msgid ""
"Slot width, in columns count, on extra small devices (phones...); set to 0 "
"to hide the portlet"
msgstr ""
-"Largeur du slot, en nombre de colonnes, sur les très petits périphériques "
+"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:152
+#: src/pyams_portal/interfaces/__init__.py:201
msgid "Small device width"
msgstr "Largeur sur petits périphériques"
-#: src/pyams_portal/interfaces/__init__.py:153
+#: src/pyams_portal/interfaces/__init__.py:202
msgid ""
"Slot width, in columns count, on small devices (tablets...); set to 0 to "
"hide the portlet"
msgstr ""
-"Largeur du slot, en nombre de colonnes, sur les petits périphériques "
+"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:159
+#: src/pyams_portal/interfaces/__init__.py:208
msgid "Medium devices width"
msgstr "Largeur sur périphériques moyens"
-#: src/pyams_portal/interfaces/__init__.py:160
+#: src/pyams_portal/interfaces/__init__.py:209
msgid ""
"Slot width, in columns count, on medium desktop devices (>= 992 pixels); set "
"to 0 to hide the portlet"
msgstr ""
-"Largeur du slot, en nombre de colonnes, sur les périphériques moyens (>= 992 "
-"pixels) ; indiquez une valeur de 0 pour masquer ce composant"
+"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:166
+#: src/pyams_portal/interfaces/__init__.py:215
msgid "Large devices width"
msgstr "Largeur sur grands périphériques"
-#: src/pyams_portal/interfaces/__init__.py:167
+#: src/pyams_portal/interfaces/__init__.py:216
msgid ""
"Slot width, in columns count, on large desktop devices (>= 1200 pixels); set "
"to 0 to hide the portlet"
msgstr ""
-"Largeur du slot, en nombre de colonnes, sur les grands périphériques (>= "
+"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:173
+#: src/pyams_portal/interfaces/__init__.py:222
msgid "CSS class"
msgstr "Class CSS"
-#: src/pyams_portal/interfaces/__init__.py:174
+#: src/pyams_portal/interfaces/__init__.py:223
msgid "CSS class applied to this slot"
-msgstr "Classe CSS spécifique appliquée à ce slot"
+msgstr "Classe CSS spécifique appliquée à ce panneau"
-#: src/pyams_portal/interfaces/__init__.py:276
+#: src/pyams_portal/interfaces/__init__.py:334
msgid "Template name"
msgstr "Nom du modèle"
-#: src/pyams_portal/interfaces/__init__.py:277
+#: src/pyams_portal/interfaces/__init__.py:335
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:309
+#: src/pyams_portal/interfaces/__init__.py:356
+msgid "Toolbar portlets"
+msgstr "Composants de la barre d'outils"
+
+#: src/pyams_portal/interfaces/__init__.py:357
+msgid ""
+"These portlets will be directly available in templates configuration page "
+"toolbar"
+msgstr ""
+"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:383
msgid "Inherit parent template?"
msgstr "Hériter du modèle du parent ?"
-#: src/pyams_portal/interfaces/__init__.py:310
+#: src/pyams_portal/interfaces/__init__.py:384
msgid "Should we reuse parent template?"
msgstr "Doit-on ré-utiliser le modèle du parent ?"
-#: src/pyams_portal/interfaces/__init__.py:314
+#: src/pyams_portal/interfaces/__init__.py:388
msgid "Use local template?"
msgstr "Utiliser un modèle local ?"
-#: src/pyams_portal/interfaces/__init__.py:315
+#: src/pyams_portal/interfaces/__init__.py:389
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:320
+#: src/pyams_portal/interfaces/__init__.py:394
msgid "Page template"
msgstr "Modèle de page"
-#: src/pyams_portal/interfaces/__init__.py:321
+#: src/pyams_portal/interfaces/__init__.py:395
msgid "Template used for this page"
msgstr "Modèle de présentation utilisé pour cette page"
-#: src/pyams_portal/interfaces/__init__.py:325
+#: src/pyams_portal/interfaces/__init__.py:404
msgid "Local template"
msgstr "Modèle local"
-#~ msgid "Portlet templates"
-#~ msgstr "Modèles de présentation"
+#: src/pyams_portal/interfaces/__init__.py:402
+msgid "You must choose to use a local template or select a shared one!"
+msgstr ""
+"Vous devez choisir un modèle de présentation partagé lorsque vous "
+"n'appliquez pas de modèle local !"
--- a/src/pyams_portal/locales/pyams_portal.pot Thu Oct 08 12:26:42 2015 +0200
+++ b/src/pyams_portal/locales/pyams_portal.pot Mon Jan 18 18:09:46 2016 +0100
@@ -1,12 +1,12 @@
#
# SOME DESCRIPTIVE TITLE
# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2015.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2016.
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2015-05-12 13:59+0200\n"
+"POT-Creation-Date: 2016-01-06 11:55+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,289 +16,202 @@
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Lingua 3.10.dev0\n"
-#: ./src/pyams_portal/workflow.py:41
-msgid "Draft"
-msgstr ""
-
-#: ./src/pyams_portal/workflow.py:42
-msgid "Published"
-msgstr ""
-
-#: ./src/pyams_portal/workflow.py:43
-msgid "Retired"
-msgstr ""
-
-#: ./src/pyams_portal/workflow.py:44
-msgid "Archived"
-msgstr ""
-
-#: ./src/pyams_portal/workflow.py:45
-msgid "Deleted"
-msgstr ""
-
-#: ./src/pyams_portal/workflow.py:103
-msgid "Initialize"
-msgstr ""
-
-#: ./src/pyams_portal/workflow.py:108
-msgid "Publish..."
-msgstr ""
-
-#: ./src/pyams_portal/workflow.py:116
-msgid ""
-"This content is currently in DRAFT mode.\n"
-" Publishing it will make it publicly visible."
-msgstr ""
-
-#: ./src/pyams_portal/workflow.py:120
-msgid "Retire..."
-msgstr ""
-
-#: ./src/pyams_portal/workflow.py:128
-msgid ""
-"This content is actually published.\n"
-" You can retire it to make it invisible, but contents using this\n"
-" template won't be visible anymore!"
-msgstr ""
-
-#: ./src/pyams_portal/workflow.py:133 ./src/pyams_portal/workflow.py:181
-msgid "Create new version..."
-msgstr ""
-
-#: ./src/pyams_portal/workflow.py:143
-msgid "Re-publish..."
-msgstr ""
-
-#: ./src/pyams_portal/workflow.py:151
-msgid ""
-"This content was published and retired.\n"
-" You can re-publish it to make it visible again."
-msgstr ""
-
-#: ./src/pyams_portal/workflow.py:155 ./src/pyams_portal/workflow.py:168
-msgid "Archive..."
-msgstr ""
-
-#: ./src/pyams_portal/workflow.py:163
-msgid ""
-"This content is currently published.\n"
-" If it is archived, it will not be possible to make it visible again\n"
-" except by creating a new version!"
-msgstr ""
-
-#: ./src/pyams_portal/workflow.py:176
-msgid ""
-"This content has been published but is currently retired.\n"
-" If it is archived, it will not be possible to make it visible again\n"
-" except by creating a new version!"
-msgstr ""
-
-#: ./src/pyams_portal/workflow.py:191
-msgid "Delete..."
-msgstr ""
-
-#: ./src/pyams_portal/workflow.py:199
-msgid ""
-"This content has never been published.\n"
-" It can be removed and definitely deleted."
-msgstr ""
-
-#: ./src/pyams_portal/__init__.py:31
+#: ./src/pyams_portal/__init__.py:34
msgid "Manage portal templates"
msgstr ""
-#: ./src/pyams_portal/__init__.py:35
+#: ./src/pyams_portal/__init__.py:38
msgid "Portal templates manager"
msgstr ""
-#: ./src/pyams_portal/zmi/portlet.py:41
-msgid "Edit portlet configuration"
+#: ./src/pyams_portal/zmi/portlet.py:49
+msgid "Edit portlet settings"
+msgstr ""
+
+#: ./src/pyams_portal/zmi/portlet.py:85
+msgid "Main properties"
msgstr ""
-#: ./src/pyams_portal/zmi/portlet.py:38
+#: ./src/pyams_portal/zmi/portlet.py:64
+msgid "Override parent settings"
+msgstr ""
+
+#: ./src/pyams_portal/zmi/portlet.py:66
+msgid "Override template settings"
+msgstr ""
+
+#: ./src/pyams_portal/zmi/portlet.py:46
#, python-format
msgid "« {0} » portal template - {1}"
msgstr ""
-#: ./src/pyams_portal/zmi/template/config.py:61
-msgid "Properties"
+#: ./src/pyams_portal/zmi/page.py:60
+msgid "Presentation"
msgstr ""
-#: ./src/pyams_portal/zmi/template/config.py:72
-msgid "Portal template configuration"
+#: ./src/pyams_portal/zmi/page.py:75
+msgid "Edit template configuration"
msgstr ""
-#: ./src/pyams_portal/zmi/template/config.py:120
-msgid "Portlets configuration"
-msgstr ""
-
-#: ./src/pyams_portal/zmi/template/config.py:133
-msgid "Add row..."
+#: ./src/pyams_portal/zmi/page.py:119
+msgid "Template properties"
msgstr ""
-#: ./src/pyams_portal/zmi/template/config.py:175
-msgid "Add slot..."
-msgstr ""
-
-#: ./src/pyams_portal/zmi/template/config.py:191
-msgid "Add slot"
+#: ./src/pyams_portal/zmi/template.py:83
+msgid "Add template"
msgstr ""
-#: ./src/pyams_portal/zmi/template/config.py:265
-msgid "Edit slot properties"
+#: ./src/pyams_portal/zmi/template.py:93 ./src/pyams_portal/zmi/container.py:78
+msgid "Portal templates"
msgstr ""
-#: ./src/pyams_portal/zmi/template/config.py:333
-msgid "Add portlet..."
+#: ./src/pyams_portal/zmi/template.py:94
+msgid "Add shared template"
msgstr ""
-#: ./src/pyams_portal/zmi/template/config.py:349
-msgid "Add portlet"
-msgstr ""
-
-#: ./src/pyams_portal/zmi/template/config.py:209
-#: ./src/pyams_portal/zmi/template/__init__.py:269
+#: ./src/pyams_portal/zmi/template.py:118 ./src/pyams_portal/zmi/layout.py:246
msgid "Specified name is already used!"
msgstr ""
-#: ./src/pyams_portal/zmi/template/config.py:118
-#: ./src/pyams_portal/zmi/template/config.py:189
-#: ./src/pyams_portal/zmi/template/config.py:347
+#: ./src/pyams_portal/zmi/template.py:62 ./src/pyams_portal/zmi/layout.py:220
+#: ./src/pyams_portal/zmi/layout.py:382
#, python-format
msgid "« {0} » portal template"
msgstr ""
-#: ./src/pyams_portal/zmi/template/config.py:262
+#: ./src/pyams_portal/zmi/layout.py:78
+msgid "Properties"
+msgstr ""
+
+#: ./src/pyams_portal/zmi/layout.py:164
+msgid "Add row..."
+msgstr ""
+
+#: ./src/pyams_portal/zmi/layout.py:206
+msgid "Add slot..."
+msgstr ""
+
+#: ./src/pyams_portal/zmi/layout.py:222
+#: ./src/pyams_portal/zmi/templates/layout.pt:27
+msgid "Add slot"
+msgstr ""
+
+#: ./src/pyams_portal/zmi/layout.py:303
+msgid "Edit slot properties"
+msgstr ""
+
+#: ./src/pyams_portal/zmi/layout.py:368
+msgid "Add portlet..."
+msgstr ""
+
+#: ./src/pyams_portal/zmi/layout.py:384
+msgid "Add portlet"
+msgstr ""
+
+#: ./src/pyams_portal/zmi/layout.py:68
+msgid "Template management"
+msgstr ""
+
+#: ./src/pyams_portal/zmi/layout.py:101
+msgid "Template configuration"
+msgstr ""
+
+#: ./src/pyams_portal/zmi/layout.py:96
+msgid "Local template configuration"
+msgstr ""
+
+#: ./src/pyams_portal/zmi/layout.py:300
#, python-format
msgid "« {0} » portal template - {1} slot"
msgstr ""
-#: ./src/pyams_portal/zmi/template/workflow.py:109
-msgid "Publish template"
-msgstr ""
-
-#: ./src/pyams_portal/zmi/template/workflow.py:151
-msgid "Retire template"
-msgstr ""
-
-#: ./src/pyams_portal/zmi/template/workflow.py:180
-msgid "Archive template"
-msgstr ""
-
-#: ./src/pyams_portal/zmi/template/workflow.py:209
-#: ./src/pyams_portal/zmi/template/workflow.py:201
-msgid "Create new version"
+#: ./src/pyams_portal/zmi/layout.py:99
+#, python-format
+msgid "Shared template configuration ({0})"
msgstr ""
-#: ./src/pyams_portal/zmi/template/workflow.py:100
-#: ./src/pyams_portal/zmi/template/workflow.py:142
-#: ./src/pyams_portal/zmi/template/workflow.py:171
-#: ./src/pyams_portal/zmi/template/workflow.py:200
-msgid "Close"
-msgstr ""
-
-#: ./src/pyams_portal/zmi/template/workflow.py:101
-msgid "Publish"
-msgstr ""
-
-#: ./src/pyams_portal/zmi/template/workflow.py:143
-msgid "Retire"
-msgstr ""
-
-#: ./src/pyams_portal/zmi/template/workflow.py:172
-msgid "Archive"
-msgstr ""
-
-#: ./src/pyams_portal/zmi/template/__init__.py:88
-#: ./src/pyams_portal/zmi/template/__init__.py:196
-#: ./src/pyams_portal/zmi/template/__init__.py:240
-msgid "Portal templates"
-msgstr ""
-
-#: ./src/pyams_portal/zmi/template/__init__.py:97
+#: ./src/pyams_portal/zmi/container.py:87
msgid "Shared portal templates"
msgstr ""
-#: ./src/pyams_portal/zmi/template/__init__.py:163
+#: ./src/pyams_portal/zmi/container.py:127
msgid "Delete template"
msgstr ""
-#: ./src/pyams_portal/zmi/template/__init__.py:195
-msgid "Portal"
+#: ./src/pyams_portal/zmi/container.py:170
+msgid "Selected portlets..."
msgstr ""
-#: ./src/pyams_portal/zmi/template/__init__.py:229
-msgid "Add shared template..."
+#: ./src/pyams_portal/zmi/container.py:186
+msgid "Portal templates container"
msgstr ""
-#: ./src/pyams_portal/zmi/template/__init__.py:241
-msgid "Add shared template"
+#: ./src/pyams_portal/zmi/container.py:187
+msgid "Edit selected portlets"
msgstr ""
-#: ./src/pyams_portal/zmi/template/__init__.py:153
-msgid "Older versions"
+#: ./src/pyams_portal/zmi/templates/portlet.pt:129
+#: ./src/pyams_portal/zmi/templates/portlet.pt:144
+msgid "Title"
+msgstr ""
+
+#: ./src/pyams_portal/zmi/templates/portlet.pt:159
+msgid "Tab label"
msgstr ""
-#: ./src/pyams_portal/zmi/template/__init__.py:212
-#: ./src/pyams_portal/zmi/template/__init__.py:146
-#, python-format
-msgid "Version {version} ({state} - last update {date})"
+#: ./src/pyams_portal/zmi/templates/layout.pt:23
+msgid "Add row"
msgstr ""
-#: ./src/pyams_portal/zmi/template/templates/config.pt:15
-#: ./src/pyams_portal/zmi/template/templates/config.pt:29
-msgid "Version ${version} - ${state}"
+#: ./src/pyams_portal/zmi/templates/layout.pt:47
+msgid "Add another portlet..."
msgstr ""
-#: ./src/pyams_portal/zmi/template/templates/config.pt:42
+#: ./src/pyams_portal/zmi/templates/layout.pt:54
msgid "Selected display:"
msgstr ""
-#: ./src/pyams_portal/zmi/template/templates/config.pt:47
+#: ./src/pyams_portal/zmi/templates/layout.pt:59
msgid "Current device"
msgstr ""
-#: ./src/pyams_portal/zmi/template/templates/config.pt:48
+#: ./src/pyams_portal/zmi/templates/layout.pt:60
msgid "Extra small device (phone)"
msgstr ""
-#: ./src/pyams_portal/zmi/template/templates/config.pt:49
+#: ./src/pyams_portal/zmi/templates/layout.pt:61
msgid "Small device (tablet)"
msgstr ""
-#: ./src/pyams_portal/zmi/template/templates/config.pt:50
+#: ./src/pyams_portal/zmi/templates/layout.pt:62
msgid "Medium desktop device (> 970px)"
msgstr ""
-#: ./src/pyams_portal/zmi/template/templates/config.pt:51
+#: ./src/pyams_portal/zmi/templates/layout.pt:63
msgid "Large desktop device (> 1170px)"
msgstr ""
-#: ./src/pyams_portal/zmi/template/templates/config.pt:111
+#: ./src/pyams_portal/zmi/templates/layout.pt:123
msgid "Delete row..."
msgstr ""
-#: ./src/pyams_portal/zmi/template/templates/config.pt:119
+#: ./src/pyams_portal/zmi/templates/layout.pt:132
msgid "Edit slot properties..."
msgstr ""
-#: ./src/pyams_portal/zmi/template/templates/config.pt:126
+#: ./src/pyams_portal/zmi/templates/layout.pt:139
msgid "Delete slot..."
msgstr ""
-#: ./src/pyams_portal/zmi/template/templates/config.pt:134
+#: ./src/pyams_portal/zmi/templates/layout.pt:147
msgid "Edit portlet properties..."
msgstr ""
-#: ./src/pyams_portal/zmi/template/templates/config.pt:141
+#: ./src/pyams_portal/zmi/templates/layout.pt:155
msgid "Delete portlet..."
msgstr ""
-#: ./src/pyams_portal/portlets/context/__init__.py:43
-msgid "Context content"
-msgstr ""
-
-#: ./src/pyams_portal/portlets/image/__init__.py:44
+#: ./src/pyams_portal/portlets/image/__init__.py:49
msgid "Image"
msgstr ""
@@ -306,155 +219,152 @@
msgid "Selected image"
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:49
+#: ./src/pyams_portal/portlets/content/__init__.py:46
+msgid "Context content"
+msgstr ""
+
+#: ./src/pyams_portal/interfaces/__init__.py:65
msgid "Portlet"
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:52
-#: ./src/pyams_portal/interfaces/__init__.py:63
-#: ./src/pyams_portal/interfaces/__init__.py:117
+#: ./src/pyams_portal/interfaces/__init__.py:68
+#: ./src/pyams_portal/interfaces/__init__.py:171
msgid "Slot name"
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:53
-#: ./src/pyams_portal/interfaces/__init__.py:64
+#: ./src/pyams_portal/interfaces/__init__.py:69
msgid "Slot name to which this configuration applies"
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:69
-msgid "Position"
-msgstr ""
-
-#: ./src/pyams_portal/interfaces/__init__.py:70
-msgid "Portlet position inside slot"
-msgstr ""
-
-#: ./src/pyams_portal/interfaces/__init__.py:74
+#: ./src/pyams_portal/interfaces/__init__.py:82
msgid "Visible portlet?"
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:75
-msgid ""
-"Select 'no' to hide this portlet. This will not break configuration "
-"inheritance..."
+#: ./src/pyams_portal/interfaces/__init__.py:83
+msgid "Select 'no' to hide this portlet..."
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:81
-#: ./src/pyams_portal/interfaces/__init__.py:139
+#: ./src/pyams_portal/interfaces/__init__.py:109
msgid "Inherit parent configuration?"
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:82
+#: ./src/pyams_portal/interfaces/__init__.py:110
msgid ""
-"This option is only available if context's parent is using the same template "
-"and if this portlet is also present in the same slot..."
+"This option is only available if context's parent is using the same "
+"template..."
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:118
+#: ./src/pyams_portal/interfaces/__init__.py:172
msgid "This name must be unique in a given template"
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:121
+#: ./src/pyams_portal/interfaces/__init__.py:175
msgid "Row ID"
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:132
+#: ./src/pyams_portal/interfaces/__init__.py:189
msgid "Visible slot?"
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:133
-msgid ""
-"Select 'no' to hide this slot. This will not break configuration "
-"inheritance..."
+#: ./src/pyams_portal/interfaces/__init__.py:190
+msgid "Select 'no' to hide this slot..."
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:140
-msgid ""
-"This option is only available if context's parent template is using a "
-"template containing the same slot..."
-msgstr ""
-
-#: ./src/pyams_portal/interfaces/__init__.py:145
+#: ./src/pyams_portal/interfaces/__init__.py:194
msgid "Extra small device width"
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:146
+#: ./src/pyams_portal/interfaces/__init__.py:195
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:152
+#: ./src/pyams_portal/interfaces/__init__.py:201
msgid "Small device width"
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:153
+#: ./src/pyams_portal/interfaces/__init__.py:202
msgid ""
"Slot width, in columns count, on small devices (tablets...); set to 0 to hide"
" the portlet"
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:159
+#: ./src/pyams_portal/interfaces/__init__.py:208
msgid "Medium devices width"
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:160
+#: ./src/pyams_portal/interfaces/__init__.py:209
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:166
+#: ./src/pyams_portal/interfaces/__init__.py:215
msgid "Large devices width"
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:167
+#: ./src/pyams_portal/interfaces/__init__.py:216
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:173
+#: ./src/pyams_portal/interfaces/__init__.py:222
msgid "CSS class"
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:174
+#: ./src/pyams_portal/interfaces/__init__.py:223
msgid "CSS class applied to this slot"
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:276
+#: ./src/pyams_portal/interfaces/__init__.py:334
msgid "Template name"
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:277
+#: ./src/pyams_portal/interfaces/__init__.py:335
msgid "Two registered templates can't share the same name..."
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:309
+#: ./src/pyams_portal/interfaces/__init__.py:356
+msgid "Toolbar portlets"
+msgstr ""
+
+#: ./src/pyams_portal/interfaces/__init__.py:357
+msgid ""
+"These portlets will be directly available in templates configuration page "
+"toolbar"
+msgstr ""
+
+#: ./src/pyams_portal/interfaces/__init__.py:383
msgid "Inherit parent template?"
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:310
+#: ./src/pyams_portal/interfaces/__init__.py:384
msgid "Should we reuse parent template?"
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:314
+#: ./src/pyams_portal/interfaces/__init__.py:388
msgid "Use local template?"
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:315
+#: ./src/pyams_portal/interfaces/__init__.py:389
msgid ""
"If 'yes', you can define a custom local template instead of a shared template"
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:320
+#: ./src/pyams_portal/interfaces/__init__.py:394
msgid "Page template"
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:321
+#: ./src/pyams_portal/interfaces/__init__.py:395
msgid "Template used for this page"
msgstr ""
-#: ./src/pyams_portal/interfaces/__init__.py:325
+#: ./src/pyams_portal/interfaces/__init__.py:404
msgid "Local template"
msgstr ""
+
+#: ./src/pyams_portal/interfaces/__init__.py:402
+msgid "You must choose to use a local template or select a shared one!"
+msgstr ""
--- a/src/pyams_portal/page.py Thu Oct 08 12:26:42 2015 +0200
+++ b/src/pyams_portal/page.py Mon Jan 18 18:09:46 2016 +0100
@@ -9,12 +9,6 @@
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
-from pyramid.view import view_config
-from zope.traversing.interfaces import ITraversable
-from pyams_portal.template import PortalWfTemplate, PortalTemplate
-from pyams_skin.layer import IPyAMSLayer
-from pyams_utils.registry import query_utility
-from pyams_workflow.interfaces import IWorkflowInfo, IWorkflowVersions
__docformat__ = 'restructuredtext'
@@ -22,24 +16,29 @@
# import standard library
# import interfaces
-from pyams_portal.interfaces import IPortalPage, IPortalContext, IPortalTemplateRenderer, \
- IPortalTemplateConfiguration, IPortalWfTemplate
+from pyams_portal.interfaces import IPortalPage, IPortalContext, IPortalTemplateConfiguration, \
+ IPortalTemplate, PORTAL_PAGE_KEY, IPortalPortletsConfiguration, PORTLETS_CONFIGURATION_KEY, \
+ ILocalTemplateHandler
from zope.annotation.interfaces import IAnnotations
+from zope.traversing.interfaces import ITraversable
# import packages
from persistent import Persistent
+from pyams_portal.template import PortalTemplate
from pyams_utils.adapter import adapter_config, ContextAdapter
+from pyams_utils.registry import query_utility
from pyramid.threadlocal import get_current_registry
from zope.container.contained import Contained
-from zope.interface import implementer
-from zope.lifecycleevent import ObjectCreatedEvent, ObjectAddedEvent
-from zope.location.location import locate
+from zope.copy import clone
+from zope.interface import implementer, alsoProvides, noLongerProvides
+from zope.lifecycleevent import ObjectCreatedEvent
+from zope.location import locate
from zope.schema.fieldproperty import FieldProperty
@implementer(IPortalPage)
class PortalPage(Persistent, Contained):
- """Portal page"""
+ """Portal page persistent class"""
_inherit_parent = FieldProperty(IPortalPage['inherit_parent'])
_use_local_template = FieldProperty(IPortalPage['use_local_template'])
@@ -56,48 +55,51 @@
@inherit_parent.setter
def inherit_parent(self, value):
- self._inherit_parent = value
+ if (not value) or self.can_inherit:
+ self._inherit_parent = value
+
+ @property
+ def parent(self):
+ parent = self.__parent__
+ page = IPortalPage(parent)
+ while page.inherit_parent:
+ parent = parent.__parent__
+ page = IPortalPage(parent)
+ return parent
@property
def use_local_template(self):
- if self.inherit_parent:
- return IPortalPage(self.__parent__.__parent__).use_local_template
- else:
- return self._use_local_template
+ return self._use_local_template if not self.inherit_parent else False
@use_local_template.setter
def use_local_template(self, value):
self._use_local_template = value
- if value and (self._local_template is None):
+ if value and (self._local_template is None) and not self.inherit_parent:
registry = get_current_registry()
- wf_template = self._local_template = PortalWfTemplate()
- registry.notify(ObjectCreatedEvent(wf_template))
- locate(wf_template, self, '++template++')
template = PortalTemplate()
+ template.name = "local"
+ self._local_template = template
registry.notify(ObjectCreatedEvent(template))
- IWorkflowVersions(wf_template).add_version(template, None)
- IWorkflowInfo(template).fire_transition('init')
+ locate(template, self, '++template++')
+ if self.use_local_template:
+ alsoProvides(self, ILocalTemplateHandler)
+ else:
+ noLongerProvides(self, ILocalTemplateHandler)
@property
def shared_template(self):
- if self.inherit_parent:
- return IPortalPage(self.__parent__.__parent__).shared_template
- else:
- return self._shared_template
+ return IPortalPage(self.parent).shared_template if self.inherit_parent else self._shared_template
@shared_template.setter
def shared_template(self, value):
if not self.inherit_parent:
- if isinstance(value, IPortalWfTemplate):
- value = value.__name__
+ if IPortalTemplate.providedBy(value):
+ value = value.name
self._shared_template = value
@property
def local_template(self):
- if self.inherit_parent:
- return IPortalPage(self.__parent__.__parent__).local_template
- else:
- return self._local_template
+ return IPortalPage(self.parent).local_template if self.inherit_parent else self._local_template
@local_template.setter
def local_template(self, value):
@@ -107,30 +109,17 @@
@property
def template(self):
if self.use_local_template:
- return self.local_template
+ template = self.local_template
else:
template = self.shared_template
- if isinstance(template, str):
- template = query_utility(IPortalWfTemplate, name=template)
- return template
-
-
-PORTAL_PAGE_KEY = 'pyams_portal.page'
-
-
-@adapter_config(name='template', context=IPortalContext, provides=ITraversable)
-class PortalPageTemplateTraverser(ContextAdapter):
- """++template++ portal context traverser"""
-
- def traverse(self, name, furtherpath=None):
- page = IPortalPage(self.context)
- if page.use_local_template:
- return page.template
+ if isinstance(template, str):
+ template = query_utility(IPortalTemplate, name=template)
+ return template
@adapter_config(context=IPortalContext, provides=IPortalPage)
-def PortalPageFactory(context):
- """Portal page factory"""
+def PortalContextPageAdapter(context):
+ """Portal context page factory"""
annotations = IAnnotations(context)
page = annotations.get(PORTAL_PAGE_KEY)
if page is None:
@@ -140,12 +129,49 @@
return page
-@view_config(context=IPortalContext, request_type=IPyAMSLayer)
-def PortalPageRenderer(request):
- page = IPortalPage(request.context)
- template = page.template
- registry = request.registry
- renderer = registry.queryMultiAdapter((request.context, request, template), IPortalTemplateRenderer)
- if renderer is not None:
- configuration = registry.queryMultiAdapter((request.context, template), IPortalTemplateConfiguration)
- return renderer(configuration)
+@adapter_config(name='template', context=IPortalContext, provides=ITraversable)
+class PortalContextTemplateTraverser(ContextAdapter):
+ """++template++ portal context traverser"""
+
+ def traverse(self, name, furtherpath=None):
+ page = IPortalPage(self.context)
+ if page.use_local_template:
+ return page.local_template
+
+
+@adapter_config(context=IPortalContext, provides=IPortalTemplateConfiguration)
+def PortalContextTemplateConfigurationAdapter(context):
+ """Portal context template configuration adapter"""
+ template = IPortalPage(context).template
+ return IPortalTemplateConfiguration(template)
+
+
+@adapter_config(context=IPortalContext, provides=IPortalPortletsConfiguration)
+def PortalContextPortletsConfigurationAdapter(context):
+ """Portal context portlets configuration adapter"""
+ page = IPortalPage(context)
+ if page.inherit_parent:
+ return IPortalPortletsConfiguration(page.parent)
+ else:
+ annotations = IAnnotations(context)
+ config = annotations.get(PORTLETS_CONFIGURATION_KEY)
+ if config is None:
+ config = annotations[PORTLETS_CONFIGURATION_KEY] = clone(IPortalPortletsConfiguration(page.template))
+ get_current_registry().notify(ObjectCreatedEvent(config))
+ locate(config, context)
+ for portlet_id, portlet_config in config.items():
+ portlet_cloned_config = clone(portlet_config)
+ config.set_portlet_configuration(portlet_id, portlet_cloned_config)
+ return config
+
+
+@adapter_config(name='portlet', context=IPortalContext, provides=ITraversable)
+class PortalContextPortletTraverser(ContextAdapter):
+ """++portlet++ portal context traverser"""
+
+ def traverse(self, name, thurtherpath=None):
+ config = IPortalPortletsConfiguration(self.context)
+ if name:
+ return config.get_portlet_configuration(int(name))
+ else:
+ return config
--- a/src/pyams_portal/portlet.py Thu Oct 08 12:26:42 2015 +0200
+++ b/src/pyams_portal/portlet.py Mon Jan 18 18:09:46 2016 +0100
@@ -20,97 +20,42 @@
import venusian
# import interfaces
-from pyams_portal.interfaces import IPortlet, IPortletRenderer, IPortletConfiguration, \
- IPortalPage, IPortletPreviewer
+from pyams_portal.interfaces import IPortlet, IPortletSettings, IPortletConfiguration, IPortletPreviewer, \
+ IPortletRenderer, IPortalPortletsConfiguration, IPortalTemplate, IPortalContext, IPortalPage
from zope.schema.interfaces import IVocabularyFactory
+from zope.traversing.interfaces import ITraversable
# import packages
from persistent import Persistent
+from persistent.mapping import PersistentMapping
+from pyams_utils.adapter import adapter_config, ContextAdapter
from pyams_utils.request import check_request
from pyams_viewlet.viewlet import ContentProvider
from pyramid.exceptions import ConfigurationError
+from pyramid.threadlocal import get_current_registry
from zope.container.contained import Contained
+from zope.copy import clone
from zope.interface import implementer, provider
+from zope.lifecycleevent import ObjectCreatedEvent
+from zope.location import locate
from zope.schema.fieldproperty import FieldProperty
-from zope.schema.vocabulary import getVocabularyRegistry, SimpleVocabulary, SimpleTerm
+from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm, getVocabularyRegistry
-@implementer(IPortletConfiguration)
-class PortletConfiguration(Persistent, Contained):
- """Portlet configuration"""
-
- template = None
- portlet_name = None
- slot_name = FieldProperty(IPortletConfiguration['slot_name'])
- position = FieldProperty(IPortletConfiguration['position'])
- visible = FieldProperty(IPortletConfiguration['visible'])
- _inherit_parent = FieldProperty(IPortletConfiguration['inherit_parent'])
-
- def __init__(self, portlet):
- self.portlet_name = portlet.name
-
- @property
- def can_inherit(self):
- return IPortalPage.providedBy(self.__parent__)
-
- @property
- def inherit_parent(self):
- return self._inherit_parent if self.can_inherit else False
-
- @inherit_parent.setter
- def inherit_parent(self, value):
- self._inherit_parent = value
-
+#
+# Portlets utilities
+#
@implementer(IPortlet)
class Portlet(object):
- """Base portlet content provider"""
+ """Base portlet utility"""
permission = FieldProperty(IPortlet['permission'])
toolbar_image = None
toolbar_css_class = 'fa fa-fw fa-2x fa-edit'
-
-@provider(IVocabularyFactory)
-class PortletVocabulary(SimpleVocabulary):
- """Portlet vocabulary"""
-
- def __init__(self, context):
- request = check_request()
- translate = request.localizer.translate
- utils = request.registry.getUtilitiesFor(IPortlet)
- terms = [SimpleTerm(name, title=translate(util.label))
- for name, util in sorted(utils, key=lambda x: translate(x[1].label))]
- super(PortletVocabulary, self).__init__(terms)
-
-getVocabularyRegistry().register('PyAMS portal portlets', PortletVocabulary)
-
-
-class PortletContentProvider(ContentProvider):
- """Bae portlet content provider"""
-
- def __init__(self, context, request, view, portlet_config):
- super(PortletContentProvider, self).__init__(context, request, view)
- self.__parent__ = view
- self.configuration = portlet_config
- self.portlet = self.request.registry.getUtility(IPortlet, name=portlet_config.portlet_name)
-
- def __call__(self):
- if self.portlet.permission and not self.request.has_permission(self.portlet.permission):
- return ''
- self.update()
- return self.render()
-
-
-@implementer(IPortletPreviewer)
-class PortletPreviewer(PortletContentProvider):
- """Portlet previewer adapter"""
-
-
-@implementer(IPortletRenderer)
-class PortletRenderer(PortletContentProvider):
- """Portlet renderer adapter"""
+ settings_class = None
class portlet_config(object):
@@ -158,3 +103,176 @@
settings['_info'] = info.codeinfo # fbo "action_method"
return wrapped
+
+
+@provider(IVocabularyFactory)
+class PortletVocabulary(SimpleVocabulary):
+ """Portlet vocabulary"""
+
+ def __init__(self, context):
+ request = check_request()
+ translate = request.localizer.translate
+ utils = request.registry.getUtilitiesFor(IPortlet)
+ terms = [SimpleTerm(name, title=translate(util.label))
+ for name, util in sorted(utils, key=lambda x: translate(x[1].label))]
+ super(PortletVocabulary, self).__init__(terms)
+
+getVocabularyRegistry().register('PyAMS portal portlets', PortletVocabulary)
+
+
+#
+# Portlets renderers
+#
+
+class PortletContentProvider(ContentProvider):
+ """Base portlet content provider"""
+
+ def __init__(self, context, request, view, settings):
+ super(PortletContentProvider, self).__init__(context, request, view)
+ self.__parent__ = view
+ self.settings = settings
+ self.portlet = self.request.registry.getUtility(IPortlet, name=settings.configuration.portlet_name)
+
+ def __call__(self):
+ if self.portlet.permission and not self.request.has_permission(self.portlet.permission):
+ return ''
+ self.update()
+ return self.render()
+
+
+@implementer(IPortletPreviewer)
+class PortletPreviewer(PortletContentProvider):
+ """Portlet previewer adapter"""
+
+
+@implementer(IPortletRenderer)
+class PortletRenderer(PortletContentProvider):
+ """Portlet renderer adapter"""
+
+
+#
+# Portlet configuration
+#
+
+@implementer(IPortletSettings)
+class PortletSettings(Persistent, Contained):
+ """Portlet settings persistent class"""
+
+ visible = FieldProperty(IPortletSettings['visible'])
+
+ __name__ = '++settings++'
+
+ def __init__(self, configuration):
+ self.__parent__ = configuration
+
+ @property
+ def configuration(self):
+ return self.__parent__
+
+
+@implementer(IPortletConfiguration)
+class PortletConfiguration(Persistent, Contained):
+ """Portlet configuration persistent class
+
+ PortletConfiguration.__parent__ points to context where configuration is applied
+ PortletConfiguration.parent points to context from where configuration is inherited
+ """
+
+ portlet_id = FieldProperty(IPortletConfiguration['portlet_id'])
+ portlet_name = None
+ _inherit_parent = FieldProperty(IPortletConfiguration['inherit_parent'])
+ _settings = FieldProperty(IPortletConfiguration['settings'])
+
+ def __init__(self, portlet):
+ self.portlet_name = portlet.name
+ self._settings = portlet.settings_class(self)
+
+ @property
+ def can_inherit(self):
+ return not IPortalTemplate.providedBy(self.__parent__)
+
+ @property
+ def inherit_parent(self):
+ return self._inherit_parent if self.can_inherit else False
+
+ @inherit_parent.setter
+ def inherit_parent(self, value):
+ if (not value) or self.can_inherit:
+ self._inherit_parent = value
+
+ @property
+ def parent(self):
+ parent = self.__parent__
+ if IPortalTemplate.providedBy(parent):
+ return parent
+ while IPortalContext.providedBy(parent):
+ configuration = IPortalPortletsConfiguration(parent).get_portlet_configuration(self.portlet_id)
+ if not configuration.inherit_parent:
+ return parent
+ if not IPortalContext.providedBy(parent.__parent__):
+ break
+ parent = parent.__parent__
+ page = IPortalPage(parent, None)
+ if page is not None:
+ return page.template
+
+ @property
+ def settings(self):
+ if self.inherit_parent:
+ return IPortalPortletsConfiguration(self.parent).get_portlet_configuration(self.portlet_id).settings
+ else:
+ return self._settings
+
+ @property
+ def editor_settings(self):
+ return self._settings
+
+
+@adapter_config(context=IPortlet, provides=IPortletConfiguration)
+def PortletConfigurationAdapter(portlet):
+ """Portlet configuration factory"""
+ return PortletConfiguration(portlet)
+
+
+@adapter_config(context=IPortletConfiguration, provides=IPortletSettings)
+def PortletConfigurationSettingsAdapter(configuration):
+ """Portlet configuration settings adapter"""
+ return configuration.settings
+
+
+@adapter_config(name='settings', context=IPortletConfiguration, provides=ITraversable)
+class PortletConfigurationSettingsTraverser(ContextAdapter):
+ """++settings++ portlet configuration traverser"""
+
+ def traverse(self, name, furtherpath=None):
+ return self.context.settings
+
+
+#
+# Template portlets configuration
+#
+
+@implementer(IPortalPortletsConfiguration)
+class PortalPortletsConfiguration(PersistentMapping, Contained):
+ """Portal portlets configuration"""
+
+ def get_portlet_configuration(self, portlet_id):
+ configuration = self.get(portlet_id)
+ if (configuration is None) and not IPortalTemplate.providedBy(self.__parent__):
+ template = IPortalPage(self.__parent__).template
+ portlets = IPortalPortletsConfiguration(template)
+ configuration = clone(portlets.get_portlet_configuration(portlet_id))
+ get_current_registry().notify(ObjectCreatedEvent(configuration))
+ self.set_portlet_configuration(portlet_id, configuration)
+ return configuration
+
+ def set_portlet_configuration(self, portlet_id, config):
+ config.portlet_id = portlet_id
+ self[portlet_id] = config
+ locate(config, self.__parent__, '++portlet++{0}'.format(portlet_id))
+
+ def delete_portlet_configuration(self, portlet_id):
+ if isinstance(portlet_id, int):
+ portlet_id = (portlet_id,)
+ for p_id in portlet_id:
+ del self[p_id]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_portal/portlets/content/__init__.py Mon Jan 18 18:09:46 2016 +0100
@@ -0,0 +1,54 @@
+#
+# 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 .interfaces import IContentPortletSettings
+from pyams_portal.interfaces import IPortletRenderer, IPortalContext
+from pyams_skin.layer import IPyAMSLayer
+from pyams_utils.interfaces import VIEW_PERMISSION
+
+# import packages
+from pyams_portal.portlet import PortletSettings, Portlet, PortletRenderer, portlet_config
+from pyams_template.template import template_config
+from pyams_utils.adapter import adapter_config
+from zope.interface import implementer, Interface
+
+from pyams_portal import _
+
+
+CONTENT_PORTLET_NAME = 'pyams_portal.portlet.content'
+
+
+@implementer(IContentPortletSettings)
+class ContentPortletSettings(PortletSettings):
+ """Content portlet persistent settings"""
+
+
+@portlet_config(permission=VIEW_PERMISSION)
+class ContentPortlet(Portlet):
+ """Content portlet"""
+
+ name = CONTENT_PORTLET_NAME
+ label = _("Context content")
+
+ settings_class = ContentPortletSettings
+
+
+@adapter_config(context=(IPortalContext, IPyAMSLayer, Interface, ContentPortlet), provides=IPortletRenderer)
+@template_config(template='content.pt', layer=IPyAMSLayer)
+class ContentPortletRenderer(PortletRenderer):
+ """Content portlet renderer"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_portal/portlets/content/content.pt Mon Jan 18 18:09:46 2016 +0100
@@ -0,0 +1,1 @@
+<h3>This is my context!!!</h3>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_portal/portlets/content/interfaces.py Mon Jan 18 18:09:46 2016 +0100
@@ -0,0 +1,25 @@
+#
+# 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 IPortletSettings
+
+# import packages
+
+
+class IContentPortletSettings(IPortletSettings):
+ """Content portlet settings interface"""
--- a/src/pyams_portal/portlets/context/__init__.py Thu Oct 08 12:26:42 2015 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +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 .interfaces import IContextPortletConfiguration
-from pyams_portal.interfaces import IPortletRenderer, IPortalContext
-from pyams_skin.layer import IPyAMSLayer
-from pyams_utils.interfaces import VIEW_PERMISSION
-
-# import packages
-from pyams_portal.portlet import Portlet, portlet_config, PortletRenderer, PortletConfiguration
-from pyams_template.template import template_config
-from pyams_utils.adapter import adapter_config
-from zope.interface import implementer, Interface
-
-from pyams_portal import _
-
-
-CONTEXT_PORTLET_NAME = 'pyams_portal.portlet.context'
-
-
-@portlet_config(permission=VIEW_PERMISSION)
-class ContextPortlet(Portlet):
- """Context portlet
-
- The goal of this portlet is to provide context content
- """
-
- name = CONTEXT_PORTLET_NAME
- label = _("Context content")
-
-
-@adapter_config(context=ContextPortlet, provides=IContextPortletConfiguration)
-@implementer(IContextPortletConfiguration)
-class ContextPortletConfiguration(PortletConfiguration):
- """Context portlet configuration"""
-
-
-@adapter_config(context=(IPortalContext, IPyAMSLayer, Interface, ContextPortlet), provides=IPortletRenderer)
-@template_config(template='context.pt', layer=IPyAMSLayer)
-class ContextPortletRenderer(PortletRenderer):
- """Context portlet renderer"""
-
-
--- a/src/pyams_portal/portlets/context/context.pt Thu Oct 08 12:26:42 2015 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-<h3>This is my context!!!</h3>
--- a/src/pyams_portal/portlets/context/interfaces.py Thu Oct 08 12:26:42 2015 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +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 IPortletConfiguration
-
-# import packages
-
-
-class IContextPortletConfiguration(IPortletConfiguration):
- """Context portlet configuration interface"""
--- a/src/pyams_portal/portlets/image/__init__.py Thu Oct 08 12:26:42 2015 +0200
+++ b/src/pyams_portal/portlets/image/__init__.py Mon Jan 18 18:09:46 2016 +0100
@@ -16,14 +16,14 @@
# import standard library
# import interfaces
-from .interfaces import IImagePortletConfiguration
+from .interfaces import IImagePortletSettings
from pyams_portal.interfaces import IPortalContext, IPortletRenderer
from pyams_skin.layer import IPyAMSLayer
from pyams_utils.interfaces import VIEW_PERMISSION
# import packages
from pyams_file.property import FileProperty
-from pyams_portal.portlet import portlet_config, Portlet, PortletConfiguration, PortletRenderer
+from pyams_portal.portlet import portlet_config, Portlet, PortletSettings, PortletRenderer
from pyams_template.template import template_config
from pyams_utils.adapter import adapter_config
from zope.interface import implementer, Interface
@@ -34,12 +34,16 @@
IMAGE_PORTLET_NAME = 'pyams_portal.portlet.image'
+@implementer(IImagePortletSettings)
+class ImagePortletSettings(PortletSettings):
+ """Image portlet settings"""
+
+ image = FileProperty(IImagePortletSettings['image'])
+
+
@portlet_config(permission=VIEW_PERMISSION)
class ImagePortlet(Portlet):
- """Image portlet
-
- The goal of this portlet is to display an image
- """
+ """Image portlet"""
name = IMAGE_PORTLET_NAME
label = _("Image")
@@ -47,13 +51,7 @@
toolbar_image = None
toolbar_css_class = 'fa fa-fw fa-2x fa-picture-o'
-
-@adapter_config(context=ImagePortlet, provides=IImagePortletConfiguration)
-@implementer(IImagePortletConfiguration)
-class ImagePortletConfiguration(PortletConfiguration):
- """Image portlet configuration"""
-
- image = FileProperty(IImagePortletConfiguration['image'])
+ settings_class = ImagePortletSettings
@adapter_config(context=(IPortalContext, IPyAMSLayer, Interface, ImagePortlet), provides=IPortletRenderer)
--- a/src/pyams_portal/portlets/image/interfaces.py Thu Oct 08 12:26:42 2015 +0200
+++ b/src/pyams_portal/portlets/image/interfaces.py Mon Jan 18 18:09:46 2016 +0100
@@ -9,7 +9,6 @@
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
-from pyams_file.schema import ImageField
__docformat__ = 'restructuredtext'
@@ -17,15 +16,16 @@
# import standard library
# import interfaces
-from pyams_portal.interfaces import IPortletConfiguration
+from pyams_file.schema import ImageField
+from pyams_portal.interfaces import IPortletSettings
# import packages
from pyams_portal import _
-class IImagePortletConfiguration(IPortletConfiguration):
- """Image portlet configuration interface"""
+class IImagePortletSettings(IPortletSettings):
+ """Image portlet settings interface"""
image = ImageField(title=_("Selected image"),
required=False)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_portal/resources/css/portal.min.css Mon Jan 18 18:09:46 2016 +0100
@@ -0,0 +1,1 @@
+#portal_config .rows{min-height:15px}#portal_config .row{position:relative;margin:5px 0;padding:2px 4px;border:1px solid rgba(199,81,0,0.4);border-top-width:1.5em;min-height:20px;cursor:move}#portal_config .row>.row_id{position:absolute;right:2px;top:-1.6em}#portal_config .row .slot{margin:3px 0;padding:3px;border:1px solid rgba(98,120,128,0.65);border-bottom-width:6px;min-height:20px!important}#portal_config .row .slot>.header{background-color:rgba(98,120,128,0.6);color:white}#portal_config .row .portlet{margin:3px 0;padding:3px;border:1px solid rgba(98,120,128,0.6);min-height:20px!important}#portal_config .row .portlet>.header{background-color:rgba(92,109,115,0.8);color:white}#portal_config .row-highlight{margin:5px 0;border:1px solid #c75100;min-height:40px}#portal_config .slots{min-height:15px}#portal_config .slot-highlight{margin:3px 0;border:1px solid #7b939c;min-height:40px}#portal_config .portlets{min-height:15px}#portal_config .portlets-hover{background-color:silver}#portal_config .portlets-active{background-color:silver}#portal_config .portlet-highlight{margin:0;border:1px solid #7b939c;min-height:40px}#portal_config.container .col-12{float:left;width:100%!important}#portal_config.container .col-11{float:left;width:91.66666667%!important}#portal_config.container .col-10{float:left;width:83.33333333%!important}#portal_config.container .col-9{float:left;width:75%!important}#portal_config.container .col-8{float:left;width:66.66666667%!important}#portal_config.container .col-7{float:left;width:58.33333333%!important}#portal_config.container .col-6{float:left;width:50%!important}#portal_config.container .col-5{float:left;width:41.66666667%!important}#portal_config.container .col-4{float:left;width:33.33333333%!important}#portal_config.container .col-3{float:left;width:25%!important}#portal_config.container .col-2{float:left;width:16.66666667%!important}#portal_config.container .col-1{float:left;width:8.33333333%!important}#portal_config.container .col-0{float:left;width:100%!important;opacity:.5}#portal_config.container .col-0>.portlets{display:none}#portal_config.container-xs{max-width:750px!important}#portal_config.container-sm{width:750px!important}#portal_config.container-md{width:970px!important}#portal_config.container-lg{width:1170px!important}
\ No newline at end of file
--- a/src/pyams_portal/resources/js/portal.js Thu Oct 08 12:26:42 2015 +0200
+++ b/src/pyams_portal/resources/js/portal.js Mon Jan 18 18:09:46 2016 +0100
@@ -1,6 +1,10 @@
-(function($) {
+(function($, globals) {
+
+ 'use strict';
- window.PyAMS_portal = {
+ var MyAMS = globals.MyAMS;
+
+ var PyAMS_portal = {
/**
* Templates management
@@ -26,37 +30,7 @@
});
$('.rows', config).droppable({
accept: '.btn-row',
- drop: function(event, ui) {
- if (ui.draggable.hasClass('already-dropped'))
- return;
- ui.draggable.addClass('already-dropped');
- MyAMS.ajax.post('add-template-row.json', {}, function(result) {
- var row_id = result.row_id;
- var rows = $('.rows', '#portal_config');
- ui.draggable.removeClassPrefix('btn')
- .removeClassPrefix('ui-')
- .removeClass('already-dropped')
- .removeAttr('style')
- .addClass('row context-menu')
- .attr('data-ams-row-id', row_id)
- .empty()
- .append($('<span></span>').addClass('row_id label label-success pull-right')
- .text(row_id))
- .append($('<div></div>').addClass('slots')
- .sortable({
- placeholder: 'slot-highlight',
- connectWith: '.slots',
- over: PyAMS_portal.template.overSlots,
- stop: PyAMS_portal.template.sortSlots
- }))
- .contextMenu({
- menuSelector: '#rowMenu',
- menuSelected: MyAMS.helpers.contextMenuHandler
- });
- PyAMS_portal.template.sortRows();
- rows.sortable('refresh');
- });
- }
+ drop: PyAMS_portal.template.dropRowButton
});
// Init slot toolbar drag and drop
$('.btn-slot', '.btn-toolbar').draggable({
@@ -67,13 +41,7 @@
});
$('.slots', config).droppable({
accept: '.btn-slot',
- drop: function(event, ui) {
- if (ui.draggable.hasClass('already-dropped'))
- return;
- ui.draggable.addClass('already-dropped');
- var row_id = ui.helper.parents('.row:first').data('ams-row-id');
- MyAMS.dialog.open('add-template-slot.html?form.widgets.row_id=' + row_id);
- }
+ drop: PyAMS_portal.template.dropSlotButton
});
// Init portlets toolbar drag and drop
$('.btn-portlet', '.btn-toolbar').draggable({
@@ -86,20 +54,7 @@
accept: '.btn-portlet',
hoverClass: 'portlets-hover',
activeClass: 'portlets-active',
- drop: function(event, ui) {
- if (ui.draggable.hasClass('already-dropped'))
- return;
- ui.draggable.addClass('already-dropped');
- var source = ui.draggable;
- var target = $(this);
- var slot = target.parents('.slot:first');
- MyAMS.ajax.post('drag-template-portlet.json', {
- portlet_name: source.data('ams-portlet-name'),
- slot_name: slot.data('ams-slot-name')
- }, function(result) {
- MyAMS.ajax.handleJSON(result);
- });
- }
+ drop: PyAMS_portal.template.dropPortletButton
});
}
},
@@ -121,12 +76,18 @@
}
$('.slot', config).removeClassPrefix('col-');
for (var slot_name in result) {
+ if (!result.hasOwnProperty(slot_name)) {
+ continue;
+ }
var widths = result[slot_name];
var slot = $('.slot[data-ams-slot-name="' + slot_name + '"]', config);
if (device) {
slot.addClass('col-' + widths[device]);
} else {
for (var display in widths) {
+ if (!widths.hasOwnProperty(display)) {
+ continue;
+ }
slot.addClass('col-' + display + '-' + widths[display]);
}
}
@@ -154,6 +115,10 @@
connectWith: '.slots',
over: PyAMS_portal.template.overSlots,
stop: PyAMS_portal.template.sortSlots
+ })
+ .droppable({
+ accept: '.btn-slot',
+ drop: PyAMS_portal.template.dropSlotButton
}))
.contextMenu({
menuSelector: '#rowMenu',
@@ -165,6 +130,43 @@
};
},
+ dropRowButton: function(event, ui) {
+ if (ui.draggable.hasClass('already-dropped')) {
+ return;
+ }
+ ui.draggable.addClass('already-dropped');
+ MyAMS.ajax.post('add-template-row.json', {}, function(result) {
+ var row_id = result.row_id;
+ var rows = $('.rows', '#portal_config');
+ ui.draggable.removeClassPrefix('btn')
+ .removeClassPrefix('ui-')
+ .removeClass('already-dropped')
+ .removeAttr('style')
+ .addClass('row context-menu')
+ .attr('data-ams-row-id', row_id)
+ .empty()
+ .append($('<span></span>').addClass('row_id label label-success pull-right')
+ .text(row_id))
+ .append($('<div></div>').addClass('slots')
+ .sortable({
+ placeholder: 'slot-highlight',
+ connectWith: '.slots',
+ over: PyAMS_portal.template.overSlots,
+ stop: PyAMS_portal.template.sortSlots
+ })
+ .droppable({
+ accept: '.btn-slot',
+ drop: PyAMS_portal.template.dropSlotButton
+ }))
+ .contextMenu({
+ menuSelector: '#rowMenu',
+ menuSelected: MyAMS.helpers.contextMenuHandler
+ });
+ PyAMS_portal.template.sortRows();
+ rows.sortable('refresh');
+ });
+ },
+
overRows: function(event, ui) {
$(ui.placeholder).attr('class', $(ui.item).attr('class'))
.removeClassPrefix('ui-')
@@ -173,18 +175,19 @@
},
sortRows: function(event, ui) {
- if (ui && ui.item.hasClass('already-dropped'))
+ if (ui && ui.item.hasClass('already-dropped')) {
return;
+ }
var config = $('#portal_config');
var ids = $('.row', config).listattr('data-ams-row-id');
MyAMS.ajax.post('set-template-row-order.json',
{rows: JSON.stringify(ids)},
function(result) {
- if (result.status == 'success') {
+ if (result.status === 'success') {
$('.row', config).each(function (index) {
$(this).attr('data-ams-row-id', index);
$('span.row_id', $(this)).text(index);
- })
+ });
}
});
},
@@ -196,19 +199,20 @@
content: '<i class="text-danger fa fa-2x fa-bell shake animated"></i> ' + MyAMS.i18n.DELETE_WARNING,
buttons: MyAMS.i18n.BTN_OK_CANCEL
}, function(button) {
- if (button == MyAMS.i18n.BTN_OK) {
- if (!row.hasClass('row'))
+ if (button === MyAMS.i18n.BTN_OK) {
+ if (!row.hasClass('row')) {
row = row.parents('.row');
+ }
MyAMS.ajax.post('delete-template-row.json',
{row_id: row.data('ams-row-id')},
function(result) {
- if (result.status == 'success') {
+ if (result.status === 'success') {
row.remove();
$('.row', '#portal_config').each(function (index) {
$(this).removeData()
.attr('data-ams-row-id', index);
$('span.row_id', $(this)).text(index);
- })
+ });
}
});
}
@@ -224,7 +228,7 @@
addSlotCallback: function(result) {
var slots = $('.slots', '.row[data-ams-row-id="' + result.row_id + '"]');
var slot_name = result.slot_name;
- var new_slot = $('<div></div>').addClass('slot context-menu col col-md-12')
+ var new_slot = $('<div></div>').addClass('slot context-menu col col-xs-12 col-sm-12 col-md-12 col-lg-12 resizable')
.attr('data-ams-slot-name', slot_name)
.append($('<div></div>').addClass('header padding-x-5')
.text(slot_name))
@@ -234,6 +238,12 @@
connectWith: '.portlets',
over: PyAMS_portal.template.overPortlets,
stop: PyAMS_portal.template.sortPortlets
+ })
+ .droppable({
+ accept: '.btn-portlet',
+ hoverClass: 'portlets-hover',
+ activeClass: 'portlets-active',
+ drop: PyAMS_portal.template.dropPortletButton
}))
.append($('<div></div>').addClass('clearfix'))
.contextMenu({
@@ -250,9 +260,23 @@
} else {
new_slot.appendTo(slots);
}
+ new_slot.resizable({
+ start: PyAMS_portal.template.startSlotResize,
+ stop: PyAMS_portal.template.stopSlotResize,
+ handles: 'e'
+ });
slots.sortable('refresh');
},
+ dropSlotButton: function(event, ui) {
+ if (ui.draggable.hasClass('already-dropped')) {
+ return;
+ }
+ ui.draggable.addClass('already-dropped');
+ var row_id = ui.helper.parents('.row:first').data('ams-row-id');
+ MyAMS.dialog.open('add-template-slot.html?form.widgets.row_id=' + row_id);
+ },
+
startSlotResize: function(event, ui) {
var slot = ui.element;
var row = slot.parents('.slots:first');
@@ -273,14 +297,15 @@
var device = $('#device_selector').val();
if (!device) {
var deviceWidth = $('body').width();
- if (deviceWidth > 1170)
+ if (deviceWidth > 1170) {
device = 'lg';
- else if (deviceWidth > 970)
+ } else if (deviceWidth > 970) {
device = 'md';
- else if (deviceWidth > 750)
+ } else if (deviceWidth > 750) {
device = 'sm';
- else
+ } else {
device = 'xs';
+ }
}
MyAMS.ajax.post('set-slot-width.json',
{slot_name: slot.data('ams-slot-name'),
@@ -301,8 +326,9 @@
editSlot: function() {
return function(slot) {
- if (!slot.hasClass('slot'))
+ if (!slot.hasClass('slot')) {
slot = slot.parents('.slot');
+ }
MyAMS.dialog.open('slot-properties.html?form.widgets.slot_name=' + slot.data('ams-slot-name'));
};
},
@@ -311,10 +337,13 @@
var slot = $('.slot[data-ams-slot-name="' + result.slot_name + '"]');
slot.attr('class', 'slot context-menu col');
var device = $('#device_selector').val();
- if (device)
+ if (device) {
slot.addClass('col-' + result.width[device]);
- else {
+ } else {
for (device in result.width) {
+ if (!result.width.hasOwnProperty(device)) {
+ continue;
+ }
slot.addClass('col-' + device + '-' + result.width[device]);
}
}
@@ -328,8 +357,9 @@
},
sortSlots: function(event, ui) {
- if (ui && ui.item.hasClass('already-dropped'))
+ if (ui && ui.item.hasClass('already-dropped')) {
return;
+ }
var config = $('#portal_config');
var order = {};
$('.row', config).each(function() {
@@ -341,10 +371,7 @@
order[parseInt(row.attr('data-ams-row-id'))] = row_config;
});
MyAMS.ajax.post('set-template-slot-order.json',
- {order: JSON.stringify(order)},
- function(result) {
- if (result.status == 'success') {}
- });
+ {order: JSON.stringify(order)});
},
deleteSlot: function() {
@@ -354,13 +381,14 @@
content: '<i class="text-danger fa fa-2x fa-bell shake animated"></i> ' + MyAMS.i18n.DELETE_WARNING,
buttons: MyAMS.i18n.BTN_OK_CANCEL
}, function(button) {
- if (button == MyAMS.i18n.BTN_OK) {
- if (!slot.hasClass('slot'))
+ if (button === MyAMS.i18n.BTN_OK) {
+ if (!slot.hasClass('slot')) {
slot = slot.parents('.slot');
+ }
MyAMS.ajax.post('delete-template-slot.json',
{slot_name: slot.data('ams-slot-name')},
function(result) {
- if (result.status == 'success') {
+ if (result.status === 'success') {
slot.remove();
$('.slot', '#portal_config').each(function() {
$(this).removeData();
@@ -380,9 +408,7 @@
addPortletCallback: function(result) {
var portlets = $('.portlets', '.slot[data-ams-slot-name="' + result.slot_name + '"]');
var portlet = $('<div></div>').addClass('portlet context-menu')
- .attr('data-ams-portlet-name', result.portlet_name)
- .attr('data-ams-portlet-slot', result.slot_name)
- .attr('data-ams-portlet-position', result.position)
+ .attr('data-ams-portlet-id', result.portlet_id)
.append($('<div></div>').addClass('header padding-x-5')
.text(result.label))
. append($('<div></div>').addClass('preview')
@@ -405,22 +431,37 @@
portlets.sortable('refresh');
},
+ dropPortletButton: function(event, ui) {
+ if (ui.draggable.hasClass('already-dropped')) {
+ return;
+ }
+ ui.draggable.addClass('already-dropped');
+ var source = ui.draggable;
+ var target = $(this);
+ var slot = target.parents('.slot:first');
+ MyAMS.ajax.post('drag-template-portlet.json', {
+ portlet_name: source.data('ams-portlet-name'),
+ slot_name: slot.data('ams-slot-name')
+ }, function(result) {
+ MyAMS.ajax.handleJSON(result);
+ });
+ },
+
editPortlet: function() {
return function(portlet) {
- if (!portlet.hasClass('portlet'))
+ if (!portlet.hasClass('portlet')) {
portlet = portlet.parents('.portlet:first');
+ }
var slot = portlet.parents('.slot:first');
var row = slot.parents('.row:first');
- MyAMS.dialog.open('portlet-properties.html?form.widgets.slot_name=' + slot.data('ams-slot-name') +
- '&form.widgets.position=' + portlet.data('ams-portlet-position'));
+ MyAMS.dialog.open('portlet-properties.html?form.widgets.portlet_id=' + portlet.data('ams-portlet-id'));
};
},
editPortletCallback: function(result) {
if (result.preview) {
var config = $('#portal_config');
- var portlet = $('.portlet[data-ams-portlet-slot="' + result.slot_name + '"]' +
- '[data-ams-portlet-position="' + result.position + '"]', config);
+ var portlet = $('.portlet[data-ams-portlet-id="' + result.portlet_id + '"]', config);
$('.preview', portlet).html(result.preview);
MyAMS.initContent($('.preview', portlet));
}
@@ -434,34 +475,17 @@
},
sortPortlets: function(event, ui) {
- if (ui.item.hasClass('already-dropped'))
+ if (ui.item.hasClass('already-dropped')) {
return;
+ }
var portlet = ui.item;
var to_slot = portlet.parents('.slot');
var to_portlets = $('.portlet', to_slot);
- var order = {from: {name: portlet.data('ams-portlet-name'),
- slot: portlet.data('ams-portlet-slot'),
- position: portlet.data('ams-portlet-position')},
+ var order = {from: portlet.data('ams-portlet-id'),
to: {slot: to_slot.data('ams-slot-name'),
- names: to_portlets.listattr('data-ams-portlet-name'),
- slots: to_portlets.listattr('data-ams-portlet-slot'),
- positions: to_portlets.listattr('data-ams-portlet-position')}};
+ portlet_ids: to_portlets.listattr('data-ams-portlet-id')}};
MyAMS.ajax.post('set-template-portlet-order.json',
- {order: JSON.stringify(order)},
- function(result) {
- if (result.status == 'success') {
- var from_slot = $('.slot[data-ams-slot-name="' + portlet.attr('data-ams-portlet-slot') + '"]', '#portal_config');
- $('.portlet', from_slot).each(function(index) {
- $(this).removeData()
- .attr('data-ams-portlet-position', index);
- });
- $('.portlet', to_slot).each(function(index) {
- $(this).removeData()
- .attr('data-ams-portlet-slot', to_slot.attr('data-ams-slot-name'))
- .attr('data-ams-portlet-position', index);
- });
- }
- });
+ {order: JSON.stringify(order)});
},
deletePortlet: function() {
@@ -471,14 +495,14 @@
content: '<i class="text-danger fa fa-2x fa-bell shake animated"></i> ' + MyAMS.i18n.DELETE_WARNING,
buttons: MyAMS.i18n.BTN_OK_CANCEL
}, function(button) {
- if (button == MyAMS.i18n.BTN_OK) {
- if (!portlet.hasClass('portlet'))
+ if (button === MyAMS.i18n.BTN_OK) {
+ if (!portlet.hasClass('portlet')) {
portlet = portlet.parents('.portlet');
+ }
MyAMS.ajax.post('delete-template-portlet.json',
- {slot_name: portlet.data('ams-portlet-slot'),
- position: portlet.data('ams-portlet-position')},
+ {portlet_id: portlet.data('ams-portlet-id')},
function(result) {
- if (result.status == 'success') {
+ if (result.status === 'success') {
portlet.remove();
$('.portlet', '#portal_config').each(function() {
$(this).removeData();
@@ -491,5 +515,6 @@
}
}
};
+ globals.PyAMS_portal = PyAMS_portal;
-})(jQuery);
+})(jQuery, this);
--- a/src/pyams_portal/resources/js/portal.min.js Thu Oct 08 12:26:42 2015 +0200
+++ b/src/pyams_portal/resources/js/portal.min.js Mon Jan 18 18:09:46 2016 +0100
@@ -1,1 +1,1 @@
-(function(a){window.PyAMS_portal={template:{initConfig:function(){var b=a("#portal_config");if(b.data("ams-allowed-change")){a(".rows",b).addClass("sortable");a(".slots",b).addClass("sortable");a(".slot",b).addClass("resizable");a(".portlets",b).addClass("sortable");MyAMS.plugins.enabled.sortable(b);MyAMS.plugins.enabled.resizable(b);a(".btn-row",".btn-toolbar").draggable({cursor:"move",helper:"clone",revert:"invalid",connectToSortable:".rows"});a(".rows",b).droppable({accept:".btn-row",drop:function(c,d){if(d.draggable.hasClass("already-dropped")){return}d.draggable.addClass("already-dropped");MyAMS.ajax.post("add-template-row.json",{},function(e){var f=e.row_id;var g=a(".rows","#portal_config");d.draggable.removeClassPrefix("btn").removeClassPrefix("ui-").removeClass("already-dropped").removeAttr("style").addClass("row context-menu").attr("data-ams-row-id",f).empty().append(a("<span></span>").addClass("row_id label label-success pull-right").text(f)).append(a("<div></div>").addClass("slots").sortable({placeholder:"slot-highlight",connectWith:".slots",over:PyAMS_portal.template.overSlots,stop:PyAMS_portal.template.sortSlots})).contextMenu({menuSelector:"#rowMenu",menuSelected:MyAMS.helpers.contextMenuHandler});PyAMS_portal.template.sortRows();g.sortable("refresh")})}});a(".btn-slot",".btn-toolbar").draggable({cursor:"move",helper:"clone",revert:"invalid",connectToSortable:".slots"});a(".slots",b).droppable({accept:".btn-slot",drop:function(d,e){if(e.draggable.hasClass("already-dropped")){return}e.draggable.addClass("already-dropped");var c=e.helper.parents(".row:first").data("ams-row-id");MyAMS.dialog.open("add-template-slot.html?form.widgets.row_id="+c)}});a(".btn-portlet",".btn-toolbar").draggable({cursor:"move",helper:"clone",revert:"invalid",connectToSortable:".portlets"});a(".portlets",b).droppable({accept:".btn-portlet",hoverClass:"portlets-hover",activeClass:"portlets-active",drop:function(c,e){if(e.draggable.hasClass("already-dropped")){return}e.draggable.addClass("already-dropped");var d=e.draggable;var f=a(this);var g=f.parents(".slot:first");MyAMS.ajax.post("drag-template-portlet.json",{portlet_name:d.data("ams-portlet-name"),slot_name:g.data("ams-slot-name")},function(h){MyAMS.ajax.handleJSON(h)})}})}},selectDisplay:function(){var b=a(this).val();MyAMS.ajax.post("get-slots-width.json",{device:b},function(c){var d=a("#portal_config");d.removeClassPrefix("container-");if(b){d.addClass("container-"+b)}a(".slot",d).removeClassPrefix("col-");for(var e in c){var f=c[e];var h=a('.slot[data-ams-slot-name="'+e+'"]',d);if(b){h.addClass("col-"+f[b])}else{for(var g in f){h.addClass("col-"+g+"-"+f[g])}}}})},addRow:function(){return function(){a(this).parents(".btn-group").removeClass("open");MyAMS.ajax.post("add-template-row.json",{},function(b){var c=b.row_id;var d=a(".rows","#portal_config");a("<div></div>").addClass("row context-menu").attr("data-ams-row-id",c).append(a("<span></span>").addClass("row_id label label-success pull-right").text(c)).append(a("<div></div>").addClass("slots").sortable({placeholder:"slot-highlight",connectWith:".slots",over:PyAMS_portal.template.overSlots,stop:PyAMS_portal.template.sortSlots})).contextMenu({menuSelector:"#rowMenu",menuSelected:MyAMS.helpers.contextMenuHandler}).appendTo(d);d.sortable("refresh")})}},overRows:function(b,c){a(c.placeholder).attr("class",a(c.item).attr("class")).removeClassPrefix("ui-").addClass("row-highlight").css("height",a(c.item).outerHeight())},sortRows:function(d,e){if(e&&e.item.hasClass("already-dropped")){return}var b=a("#portal_config");var c=a(".row",b).listattr("data-ams-row-id");MyAMS.ajax.post("set-template-row-order.json",{rows:JSON.stringify(c)},function(f){if(f.status=="success"){a(".row",b).each(function(g){a(this).attr("data-ams-row-id",g);a("span.row_id",a(this)).text(g)})}})},deleteRow:function(){return function(b){MyAMS.skin.bigBox({title:MyAMS.i18n.WARNING,content:'<i class="text-danger fa fa-2x fa-bell shake animated"></i> '+MyAMS.i18n.DELETE_WARNING,buttons:MyAMS.i18n.BTN_OK_CANCEL},function(c){if(c==MyAMS.i18n.BTN_OK){if(!b.hasClass("row")){b=b.parents(".row")}MyAMS.ajax.post("delete-template-row.json",{row_id:b.data("ams-row-id")},function(d){if(d.status=="success"){b.remove();a(".row","#portal_config").each(function(e){a(this).removeData().attr("data-ams-row-id",e);a("span.row_id",a(this)).text(e)})}})}})}},addSlotCallback:function(b){var e=a(".slots",'.row[data-ams-row-id="'+b.row_id+'"]');var d=b.slot_name;var c=a("<div></div>").addClass("slot context-menu col col-md-12").attr("data-ams-slot-name",d).append(a("<div></div>").addClass("header padding-x-5").text(d)).append(a("<div></div>").addClass("portlets").sortable({placeholder:"portlet-highlight",connectWith:".portlets",over:PyAMS_portal.template.overPortlets,stop:PyAMS_portal.template.sortPortlets})).append(a("<div></div>").addClass("clearfix")).contextMenu({menuSelector:"#slotMenu",menuSelected:MyAMS.helpers.contextMenuHandler});var f=a(".btn-slot",e);if(f.exists()){f.replaceWith(c);a(".slot",e).each(function(){a(this).removeData()});PyAMS_portal.template.sortSlots()}else{c.appendTo(e)}e.sortable("refresh")},startSlotResize:function(c,e){var g=e.element;var f=g.parents(".slots:first");var b=(f.innerWidth()-110)/12;var d=g.height();e.element.resizable("option","grid",[b,d]);e.element.resizable("option","minWidth",b);e.element.resizable("option","minHeight",d);e.element.resizable("option","maxWidth",f.innerWidth());e.element.resizable("option","maxHeight",d)},stopSlotResize:function(e,g){var i=g.element;var h=i.parents(".slots:first");var c=(h.innerWidth()-10)/12;var f=Math.round(a(i).width()/c);var d=a("#device_selector").val();if(!d){var b=a("body").width();if(b>1170){d="lg"}else{if(b>970){d="md"}else{if(b>750){d="sm"}else{d="xs"}}}}MyAMS.ajax.post("set-slot-width.json",{slot_name:i.data("ams-slot-name"),device:d,width:f},function(j){i.removeClassPrefix("col-");i.removeAttr("style");var k=i.data("ams-slot-name");var l=j[k];if(d){i.addClass("col-"+d+"-"+l[d])}else{i.addClass("col-"+l[d])}})},editSlot:function(){return function(b){if(!b.hasClass("slot")){b=b.parents(".slot")}MyAMS.dialog.open("slot-properties.html?form.widgets.slot_name="+b.data("ams-slot-name"))}},editSlotCallback:function(b){var d=a('.slot[data-ams-slot-name="'+b.slot_name+'"]');d.attr("class","slot context-menu col");var c=a("#device_selector").val();if(c){d.addClass("col-"+b.width[c])}else{for(c in b.width){d.addClass("col-"+c+"-"+b.width[c])}}},overSlots:function(b,c){a(c.placeholder).attr("class",a(c.item).attr("class")).removeClassPrefix("ui-").addClass("slot-highlight").css("height",a(c.item).outerHeight())},sortSlots:function(d,e){if(e&&e.item.hasClass("already-dropped")){return}var c=a("#portal_config");var b={};a(".row",c).each(function(){var g=a(this);var f=[];a(".slot",g).each(function(){f.push(a(this).data("ams-slot-name"))});b[parseInt(g.attr("data-ams-row-id"))]=f});MyAMS.ajax.post("set-template-slot-order.json",{order:JSON.stringify(b)},function(f){if(f.status=="success"){}})},deleteSlot:function(){return function(b){MyAMS.skin.bigBox({title:MyAMS.i18n.WARNING,content:'<i class="text-danger fa fa-2x fa-bell shake animated"></i> '+MyAMS.i18n.DELETE_WARNING,buttons:MyAMS.i18n.BTN_OK_CANCEL},function(c){if(c==MyAMS.i18n.BTN_OK){if(!b.hasClass("slot")){b=b.parents(".slot")}MyAMS.ajax.post("delete-template-slot.json",{slot_name:b.data("ams-slot-name")},function(d){if(d.status=="success"){b.remove();a(".slot","#portal_config").each(function(){a(this).removeData()})}})}})}},addPortletCallback:function(b){var c=a(".portlets",'.slot[data-ams-slot-name="'+b.slot_name+'"]');var e=a("<div></div>").addClass("portlet context-menu").attr("data-ams-portlet-name",b.portlet_name).attr("data-ams-portlet-slot",b.slot_name).attr("data-ams-portlet-position",b.position).append(a("<div></div>").addClass("header padding-x-5").text(b.label)).append(a("<div></div>").addClass("preview").html(b.preview||"")).contextMenu({menuSelector:"#portletMenu",menuSelected:MyAMS.helpers.contextMenuHandler});MyAMS.initContent(a(".preview",e));var d=a(".btn-portlet",c);if(d.exists()){d.replaceWith(e);a(".portlet",c).each(function(){a(this).removeData()});PyAMS_portal.template.sortPortlets(null,{item:e})}else{e.appendTo(c)}c.sortable("refresh")},editPortlet:function(){return function(c){if(!c.hasClass("portlet")){c=c.parents(".portlet:first")}var d=c.parents(".slot:first");var b=d.parents(".row:first");MyAMS.dialog.open("portlet-properties.html?form.widgets.slot_name="+d.data("ams-slot-name")+"&form.widgets.position="+c.data("ams-portlet-position"))}},editPortletCallback:function(b){if(b.preview){var c=a("#portal_config");var d=a('.portlet[data-ams-portlet-slot="'+b.slot_name+'"][data-ams-portlet-position="'+b.position+'"]',c);a(".preview",d).html(b.preview);MyAMS.initContent(a(".preview",d))}},overPortlets:function(b,c){a(c.placeholder).attr("class",a(c.item).attr("class")).removeClassPrefix("ui-").addClass("portlet-highlight").css("height",a(c.item).outerHeight())},sortPortlets:function(c,f){if(f.item.hasClass("already-dropped")){return}var g=f.item;var e=g.parents(".slot");var d=a(".portlet",e);var b={from:{name:g.data("ams-portlet-name"),slot:g.data("ams-portlet-slot"),position:g.data("ams-portlet-position")},to:{slot:e.data("ams-slot-name"),names:d.listattr("data-ams-portlet-name"),slots:d.listattr("data-ams-portlet-slot"),positions:d.listattr("data-ams-portlet-position")}};MyAMS.ajax.post("set-template-portlet-order.json",{order:JSON.stringify(b)},function(h){if(h.status=="success"){var i=a('.slot[data-ams-slot-name="'+g.attr("data-ams-portlet-slot")+'"]',"#portal_config");a(".portlet",i).each(function(j){a(this).removeData().attr("data-ams-portlet-position",j)});a(".portlet",e).each(function(j){a(this).removeData().attr("data-ams-portlet-slot",e.attr("data-ams-slot-name")).attr("data-ams-portlet-position",j)})}})},deletePortlet:function(){return function(b){MyAMS.skin.bigBox({title:MyAMS.i18n.WARNING,content:'<i class="text-danger fa fa-2x fa-bell shake animated"></i> '+MyAMS.i18n.DELETE_WARNING,buttons:MyAMS.i18n.BTN_OK_CANCEL},function(c){if(c==MyAMS.i18n.BTN_OK){if(!b.hasClass("portlet")){b=b.parents(".portlet")}MyAMS.ajax.post("delete-template-portlet.json",{slot_name:b.data("ams-portlet-slot"),position:b.data("ams-portlet-position")},function(d){if(d.status=="success"){b.remove();a(".portlet","#portal_config").each(function(){a(this).removeData()})}})}})}}}}})(jQuery);
\ No newline at end of file
+(function(c,b){var d=b.MyAMS;var a={template:{initConfig:function(){var e=c("#portal_config");if(e.data("ams-allowed-change")){c(".rows",e).addClass("sortable");c(".slots",e).addClass("sortable");c(".slot",e).addClass("resizable");c(".portlets",e).addClass("sortable");d.plugins.enabled.sortable(e);d.plugins.enabled.resizable(e);c(".btn-row",".btn-toolbar").draggable({cursor:"move",helper:"clone",revert:"invalid",connectToSortable:".rows"});c(".rows",e).droppable({accept:".btn-row",drop:a.template.dropRowButton});c(".btn-slot",".btn-toolbar").draggable({cursor:"move",helper:"clone",revert:"invalid",connectToSortable:".slots"});c(".slots",e).droppable({accept:".btn-slot",drop:a.template.dropSlotButton});c(".btn-portlet",".btn-toolbar").draggable({cursor:"move",helper:"clone",revert:"invalid",connectToSortable:".portlets"});c(".portlets",e).droppable({accept:".btn-portlet",hoverClass:"portlets-hover",activeClass:"portlets-active",drop:a.template.dropPortletButton})}},selectDisplay:function(){var e=c(this).val();d.ajax.post("get-slots-width.json",{device:e},function(f){var g=c("#portal_config");g.removeClassPrefix("container-");if(e){g.addClass("container-"+e)}c(".slot",g).removeClassPrefix("col-");for(var h in f){if(!f.hasOwnProperty(h)){continue}var i=f[h];var k=c('.slot[data-ams-slot-name="'+h+'"]',g);if(e){k.addClass("col-"+i[e])}else{for(var j in i){if(!i.hasOwnProperty(j)){continue}k.addClass("col-"+j+"-"+i[j])}}}})},addRow:function(){return function(){c(this).parents(".btn-group").removeClass("open");d.ajax.post("add-template-row.json",{},function(e){var f=e.row_id;var g=c(".rows","#portal_config");c("<div></div>").addClass("row context-menu").attr("data-ams-row-id",f).append(c("<span></span>").addClass("row_id label label-success pull-right").text(f)).append(c("<div></div>").addClass("slots").sortable({placeholder:"slot-highlight",connectWith:".slots",over:a.template.overSlots,stop:a.template.sortSlots}).droppable({accept:".btn-slot",drop:a.template.dropSlotButton})).contextMenu({menuSelector:"#rowMenu",menuSelected:d.helpers.contextMenuHandler}).appendTo(g);g.sortable("refresh")})}},dropRowButton:function(e,f){if(f.draggable.hasClass("already-dropped")){return}f.draggable.addClass("already-dropped");d.ajax.post("add-template-row.json",{},function(g){var h=g.row_id;var i=c(".rows","#portal_config");f.draggable.removeClassPrefix("btn").removeClassPrefix("ui-").removeClass("already-dropped").removeAttr("style").addClass("row context-menu").attr("data-ams-row-id",h).empty().append(c("<span></span>").addClass("row_id label label-success pull-right").text(h)).append(c("<div></div>").addClass("slots").sortable({placeholder:"slot-highlight",connectWith:".slots",over:a.template.overSlots,stop:a.template.sortSlots}).droppable({accept:".btn-slot",drop:a.template.dropSlotButton})).contextMenu({menuSelector:"#rowMenu",menuSelected:d.helpers.contextMenuHandler});a.template.sortRows();i.sortable("refresh")})},overRows:function(e,f){c(f.placeholder).attr("class",c(f.item).attr("class")).removeClassPrefix("ui-").addClass("row-highlight").css("height",c(f.item).outerHeight())},sortRows:function(g,h){if(h&&h.item.hasClass("already-dropped")){return}var e=c("#portal_config");var f=c(".row",e).listattr("data-ams-row-id");d.ajax.post("set-template-row-order.json",{rows:JSON.stringify(f)},function(i){if(i.status==="success"){c(".row",e).each(function(j){c(this).attr("data-ams-row-id",j);c("span.row_id",c(this)).text(j)})}})},deleteRow:function(){return function(e){d.skin.bigBox({title:d.i18n.WARNING,content:'<i class="text-danger fa fa-2x fa-bell shake animated"></i> '+d.i18n.DELETE_WARNING,buttons:d.i18n.BTN_OK_CANCEL},function(f){if(f===d.i18n.BTN_OK){if(!e.hasClass("row")){e=e.parents(".row")}d.ajax.post("delete-template-row.json",{row_id:e.data("ams-row-id")},function(g){if(g.status==="success"){e.remove();c(".row","#portal_config").each(function(h){c(this).removeData().attr("data-ams-row-id",h);c("span.row_id",c(this)).text(h)})}})}})}},addSlotCallback:function(e){var h=c(".slots",'.row[data-ams-row-id="'+e.row_id+'"]');var g=e.slot_name;var f=c("<div></div>").addClass("slot context-menu col col-xs-12 col-sm-12 col-md-12 col-lg-12 resizable").attr("data-ams-slot-name",g).append(c("<div></div>").addClass("header padding-x-5").text(g)).append(c("<div></div>").addClass("portlets").sortable({placeholder:"portlet-highlight",connectWith:".portlets",over:a.template.overPortlets,stop:a.template.sortPortlets}).droppable({accept:".btn-portlet",hoverClass:"portlets-hover",activeClass:"portlets-active",drop:a.template.dropPortletButton})).append(c("<div></div>").addClass("clearfix")).contextMenu({menuSelector:"#slotMenu",menuSelected:d.helpers.contextMenuHandler});var i=c(".btn-slot",h);if(i.exists()){i.replaceWith(f);c(".slot",h).each(function(){c(this).removeData()});a.template.sortSlots()}else{f.appendTo(h)}f.resizable({start:a.template.startSlotResize,stop:a.template.stopSlotResize,handles:"e"});h.sortable("refresh")},dropSlotButton:function(f,g){if(g.draggable.hasClass("already-dropped")){return}g.draggable.addClass("already-dropped");var e=g.helper.parents(".row:first").data("ams-row-id");d.dialog.open("add-template-slot.html?form.widgets.row_id="+e)},startSlotResize:function(f,h){var j=h.element;var i=j.parents(".slots:first");var e=(i.innerWidth()-110)/12;var g=j.height();h.element.resizable("option","grid",[e,g]);h.element.resizable("option","minWidth",e);h.element.resizable("option","minHeight",g);h.element.resizable("option","maxWidth",i.innerWidth());h.element.resizable("option","maxHeight",g)},stopSlotResize:function(h,j){var l=j.element;var k=l.parents(".slots:first");var f=(k.innerWidth()-10)/12;var i=Math.round(c(l).width()/f);var g=c("#device_selector").val();if(!g){var e=c("body").width();if(e>1170){g="lg"}else{if(e>970){g="md"}else{if(e>750){g="sm"}else{g="xs"}}}}d.ajax.post("set-slot-width.json",{slot_name:l.data("ams-slot-name"),device:g,width:i},function(m){l.removeClassPrefix("col-");l.removeAttr("style");var n=l.data("ams-slot-name");var o=m[n];if(g){l.addClass("col-"+g+"-"+o[g])}else{l.addClass("col-"+o[g])}})},editSlot:function(){return function(e){if(!e.hasClass("slot")){e=e.parents(".slot")}d.dialog.open("slot-properties.html?form.widgets.slot_name="+e.data("ams-slot-name"))}},editSlotCallback:function(e){var g=c('.slot[data-ams-slot-name="'+e.slot_name+'"]');g.attr("class","slot context-menu col");var f=c("#device_selector").val();if(f){g.addClass("col-"+e.width[f])}else{for(f in e.width){if(!e.width.hasOwnProperty(f)){continue}g.addClass("col-"+f+"-"+e.width[f])}}},overSlots:function(e,f){c(f.placeholder).attr("class",c(f.item).attr("class")).removeClassPrefix("ui-").addClass("slot-highlight").css("height",c(f.item).outerHeight())},sortSlots:function(g,h){if(h&&h.item.hasClass("already-dropped")){return}var f=c("#portal_config");var e={};c(".row",f).each(function(){var j=c(this);var i=[];c(".slot",j).each(function(){i.push(c(this).data("ams-slot-name"))});e[parseInt(j.attr("data-ams-row-id"))]=i});d.ajax.post("set-template-slot-order.json",{order:JSON.stringify(e)})},deleteSlot:function(){return function(e){d.skin.bigBox({title:d.i18n.WARNING,content:'<i class="text-danger fa fa-2x fa-bell shake animated"></i> '+d.i18n.DELETE_WARNING,buttons:d.i18n.BTN_OK_CANCEL},function(f){if(f===d.i18n.BTN_OK){if(!e.hasClass("slot")){e=e.parents(".slot")}d.ajax.post("delete-template-slot.json",{slot_name:e.data("ams-slot-name")},function(g){if(g.status==="success"){e.remove();c(".slot","#portal_config").each(function(){c(this).removeData()})}})}})}},addPortletCallback:function(e){var f=c(".portlets",'.slot[data-ams-slot-name="'+e.slot_name+'"]');var h=c("<div></div>").addClass("portlet context-menu").attr("data-ams-portlet-id",e.portlet_id).append(c("<div></div>").addClass("header padding-x-5").text(e.label)).append(c("<div></div>").addClass("preview").html(e.preview||"")).contextMenu({menuSelector:"#portletMenu",menuSelected:d.helpers.contextMenuHandler});d.initContent(c(".preview",h));var g=c(".btn-portlet",f);if(g.exists()){g.replaceWith(h);c(".portlet",f).each(function(){c(this).removeData()});a.template.sortPortlets(null,{item:h})}else{h.appendTo(f)}f.sortable("refresh")},dropPortletButton:function(e,g){if(g.draggable.hasClass("already-dropped")){return}g.draggable.addClass("already-dropped");var f=g.draggable;var h=c(this);var i=h.parents(".slot:first");d.ajax.post("drag-template-portlet.json",{portlet_name:f.data("ams-portlet-name"),slot_name:i.data("ams-slot-name")},function(j){d.ajax.handleJSON(j)})},editPortlet:function(){return function(f){if(!f.hasClass("portlet")){f=f.parents(".portlet:first")}var g=f.parents(".slot:first");var e=g.parents(".row:first");d.dialog.open("portlet-properties.html?form.widgets.portlet_id="+f.data("ams-portlet-id"))}},editPortletCallback:function(e){if(e.preview){var f=c("#portal_config");var g=c('.portlet[data-ams-portlet-id="'+e.portlet_id+'"]',f);c(".preview",g).html(e.preview);d.initContent(c(".preview",g))}},overPortlets:function(e,f){c(f.placeholder).attr("class",c(f.item).attr("class")).removeClassPrefix("ui-").addClass("portlet-highlight").css("height",c(f.item).outerHeight())},sortPortlets:function(f,i){if(i.item.hasClass("already-dropped")){return}var j=i.item;var h=j.parents(".slot");var g=c(".portlet",h);var e={from:j.data("ams-portlet-id"),to:{slot:h.data("ams-slot-name"),portlet_ids:g.listattr("data-ams-portlet-id")}};d.ajax.post("set-template-portlet-order.json",{order:JSON.stringify(e)})},deletePortlet:function(){return function(e){d.skin.bigBox({title:d.i18n.WARNING,content:'<i class="text-danger fa fa-2x fa-bell shake animated"></i> '+d.i18n.DELETE_WARNING,buttons:d.i18n.BTN_OK_CANCEL},function(f){if(f===d.i18n.BTN_OK){if(!e.hasClass("portlet")){e=e.parents(".portlet")}d.ajax.post("delete-template-portlet.json",{portlet_id:e.data("ams-portlet-id")},function(g){if(g.status==="success"){e.remove();c(".portlet","#portal_config").each(function(){c(this).removeData()})}})}})}}}};b.PyAMS_portal=a})(jQuery,this);
\ No newline at end of file
--- a/src/pyams_portal/slot.py Thu Oct 08 12:26:42 2015 +0200
+++ b/src/pyams_portal/slot.py Mon Jan 18 18:09:46 2016 +0100
@@ -20,29 +20,28 @@
# import packages
from persistent import Persistent
+from persistent.list import PersistentList
from zope.container.contained import Contained
from zope.interface import implementer
from zope.schema.fieldproperty import FieldProperty
-PORTAL_SLOTS_KEY = 'pyams_portal.slots'
-
-
@implementer(ISlotConfiguration)
class SlotConfiguration(Persistent, Contained):
"""Portal slot class"""
slot_name = FieldProperty(ISlotConfiguration['slot_name'])
+ _portlet_ids = FieldProperty(ISlotConfiguration['portlet_ids'])
visible = FieldProperty(ISlotConfiguration['visible'])
- _inherit_parent = FieldProperty(ISlotConfiguration['inherit_parent'])
- _xs_width = FieldProperty(ISlotConfiguration['xs_width'])
- _sm_width = FieldProperty(ISlotConfiguration['sm_width'])
- _md_width = FieldProperty(ISlotConfiguration['md_width'])
- _lg_width = FieldProperty(ISlotConfiguration['lg_width'])
- _css_class = FieldProperty(ISlotConfiguration['css_class'])
+ xs_width = FieldProperty(ISlotConfiguration['xs_width'])
+ sm_width = FieldProperty(ISlotConfiguration['sm_width'])
+ md_width = FieldProperty(ISlotConfiguration['md_width'])
+ lg_width = FieldProperty(ISlotConfiguration['lg_width'])
+ css_class = FieldProperty(ISlotConfiguration['css_class'])
def __init__(self, slot_name, **kwargs):
self.slot_name = slot_name
+ self._portlet_ids = PersistentList()
self.xs_width = 12
self.sm_width = 12
self.md_width = 12
@@ -58,76 +57,17 @@
return IPortalPage(self.__parent__).template
@property
- def can_inherit(self):
- return IPortalPage.providedBy(self.__parent__)
-
- @property
- def inherit_parent(self):
- return self._inherit_parent if self.can_inherit else False
-
- @inherit_parent.setter
- def inherit_parent(self, value):
- self._inherit_parent = value
-
- @property
- def xs_width(self):
- if self.inherit_parent:
- config = IPortalTemplateConfiguration(self.template)
- return config.get_slot_configuration(self.slot_name).xs_width
+ def portlet_ids(self):
+ if IPortalTemplate.providedBy(self.__parent__):
+ return self._portlet_ids
else:
- return self._xs_width
-
- @xs_width.setter
- def xs_width(self, value):
- self._xs_width = value
-
- @property
- def sm_width(self):
- if self.inherit_parent:
config = IPortalTemplateConfiguration(self.template)
- return config.get_slot_configuration(self.slot_name).sm_width
- else:
- return self._sm_width
-
- @sm_width.setter
- def sm_width(self, value):
- self._sm_width = value
+ return config.get_slot_configuration(self.slot_name).portlet_ids
- @property
- def md_width(self):
- if self.inherit_parent:
- config = IPortalTemplateConfiguration(self.template)
- return config.get_slot_configuration(self.slot_name).md_width
- else:
- return self._md_width
-
- @md_width.setter
- def md_width(self, value):
- self._md_width = value
-
- @property
- def lg_width(self):
- if self.inherit_parent:
- config = IPortalTemplateConfiguration(self.template)
- return config.get_slot_configuration(self.slot_name).lg_width
- else:
- return self._lg_width
-
- @lg_width.setter
- def lg_width(self, value):
- self._lg_width = value
-
- @property
- def css_class(self):
- if self.inherit_parent:
- config = IPortalTemplateConfiguration(self.template)
- return config.get_slot_configuration(self.slot_name).css_class
- else:
- return self._css_class
-
- @css_class.setter
- def css_class(self, value):
- self._css_class = value
+ @portlet_ids.setter
+ def portlet_ids(self, value):
+ if IPortalTemplate.providedBy(self.__parent__):
+ self._portlet_ids = value
def get_css_class(self, device=None):
if not device:
--- a/src/pyams_portal/template.py Thu Oct 08 12:26:42 2015 +0200
+++ b/src/pyams_portal/template.py Mon Jan 18 18:09:46 2016 +0100
@@ -16,9 +16,9 @@
# import standard library
# import interfaces
-from pyams_portal.interfaces import IPortalTemplate, IPortalTemplateConfiguration, IPortalContext, IPortalPage, \
- IPortletConfiguration, IPortlet, IPortalTemplateContainer, IPortalWfTemplate, IPortalTemplateContainerConfiguration
-from pyams_workflow.interfaces import IWorkflowVersions
+from pyams_portal.interfaces import IPortalTemplateContainer, IPortalTemplateContainerConfiguration, \
+ IPortalTemplate, IPortalTemplateConfiguration, IPortalPortletsConfiguration, IPortlet, IPortletConfiguration, \
+ PORTLETS_CONFIGURATION_KEY, TEMPLATE_CONTAINER_CONFIGURATION_KEY, TEMPLATE_CONFIGURATION_KEY
from zope.annotation.interfaces import IAnnotations
from zope.lifecycleevent.interfaces import IObjectAddedEvent, IObjectRemovedEvent
from zope.traversing.interfaces import ITraversable
@@ -27,9 +27,10 @@
from persistent import Persistent
from persistent.list import PersistentList
from persistent.mapping import PersistentMapping
+from pyams_portal.portlet import PortalPortletsConfiguration
from pyams_portal.slot import SlotConfiguration
from pyams_utils.adapter import adapter_config, ContextAdapter
-from pyams_utils.registry import get_local_registry
+from pyams_utils.registry import get_local_registry, get_utility
from pyams_utils.request import check_request
from pyramid.events import subscriber
from pyramid.threadlocal import get_current_registry
@@ -39,79 +40,85 @@
from zope.copy import clone
from zope.interface import implementer
from zope.lifecycleevent import ObjectCreatedEvent
-from zope.location.location import locate
+from zope.location import locate
from zope.schema.fieldproperty import FieldProperty
from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm, getVocabularyRegistry
+#
+# Portal templates container
+#
+
@implementer(IPortalTemplateContainer)
class PortalTemplateContainer(Folder):
- """Portal template container"""
+ """Portal templates container"""
+
+ last_portlet_id = FieldProperty(IPortalTemplateContainer['last_portlet_id'])
+
+ def get_portlet_id(self):
+ self.last_portlet_id += 1
+ return self.last_portlet_id
@implementer(IPortalTemplateContainerConfiguration)
class PortalTemplateContainerConfiguration(Persistent, Contained):
"""Portal template container configuration"""
- selected_portlets = FieldProperty(IPortalTemplateContainerConfiguration['selected_portlets'])
-
-
-PORTAL_TEMPLATE_CONTAINER_CONFIGURATION_KEY = 'pyams_portal.container.configuration'
+ toolbar_portlets = FieldProperty(IPortalTemplateContainerConfiguration['toolbar_portlets'])
@adapter_config(context=IPortalTemplateContainer, provides=IPortalTemplateContainerConfiguration)
-def PortalTemplateContainerConfigurationFactory(context):
+def PortalTemplateContainerConfigurationAdapter(context):
"""Portal template container configuration factory"""
annotations = IAnnotations(context)
- config = annotations.get(PORTAL_TEMPLATE_CONTAINER_CONFIGURATION_KEY)
+ config = annotations.get(TEMPLATE_CONTAINER_CONFIGURATION_KEY)
if config is None:
- config = annotations[PORTAL_TEMPLATE_CONTAINER_CONFIGURATION_KEY] = PortalTemplateContainerConfiguration()
+ config = annotations[TEMPLATE_CONTAINER_CONFIGURATION_KEY] = PortalTemplateContainerConfiguration()
get_current_registry().notify(ObjectCreatedEvent(config))
locate(config, context)
return config
+#
+# Portal template base class
+#
+
@implementer(IPortalTemplate)
class PortalTemplate(Persistent, Contained):
- """Portal template persistent class"""
+ """Portal template class"""
name = FieldProperty(IPortalTemplate['name'])
-@implementer(IPortalWfTemplate)
-class PortalWfTemplate(Persistent, Contained):
- """Portal template workflow manager class"""
-
- content_class = PortalTemplate
- workflow_name = 'PyAMS portal template workflow'
- view_permission = None
-
-
-@subscriber(IObjectAddedEvent, context_selector=IPortalWfTemplate)
+@subscriber(IObjectAddedEvent, context_selector=IPortalTemplate)
def handle_added_template(event):
"""Register shared template"""
registry = get_local_registry()
if (registry is not None) and IPortalTemplateContainer.providedBy(event.newParent):
- registry.registerUtility(event.object, IPortalWfTemplate, name=event.object.__name__)
+ registry.registerUtility(event.object, IPortalTemplate, name=event.object.name)
-@subscriber(IObjectRemovedEvent, context_selector=IPortalWfTemplate)
+@subscriber(IObjectRemovedEvent, context_selector=IPortalTemplate)
def handle_removed_template(event):
"""Unregister removed template"""
registry = get_local_registry()
if (registry is not None) and IPortalTemplateContainer.providedBy(event.oldParent):
- registry.unregisterUtility(event.object, IPortalWfTemplate, name=event.object.__name__)
+ registry.unregisterUtility(event.object, IPortalTemplate, name=event.object.name)
class PortalTemplatesVocabulary(UtilityVocabulary):
"""Portal templates vocabulary"""
- interface = IPortalWfTemplate
+ interface = IPortalTemplate
nameOnly = True
getVocabularyRegistry().register('PyAMS portal templates', PortalTemplatesVocabulary)
+#
+# Portal template configuration
+#
+
@implementer(IPortalTemplateConfiguration)
class PortalTemplateConfiguration(Persistent, Contained):
"""Portal template configuration"""
@@ -119,25 +126,21 @@
rows = FieldProperty(IPortalTemplateConfiguration['rows'])
_slot_names = FieldProperty(IPortalTemplateConfiguration['slot_names'])
_slot_order = FieldProperty(IPortalTemplateConfiguration['slot_order'])
- _slots = FieldProperty(IPortalTemplateConfiguration['slots'])
- slot_config = FieldProperty(IPortalTemplateConfiguration['slot_config'])
- portlet_config = FieldProperty(IPortalTemplateConfiguration['portlet_config'])
+ _slot_config = FieldProperty(IPortalTemplateConfiguration['slot_config'])
def __init__(self):
self._slot_names = PersistentList()
self._slot_order = PersistentMapping()
self._slot_order[0] = PersistentList()
- self._slots = PersistentMapping()
- self._slots[0] = PersistentMapping()
self.slot_config = PersistentMapping()
- self.portlet_config = PersistentMapping()
+
+ # rows management
def add_row(self):
"""Add new row and return last row index (0 based)"""
self.rows += 1
last_index = self.rows - 1
self.slot_order[last_index] = PersistentList()
- self.slots[last_index] = PersistentMapping()
return last_index
def set_row_order(self, order):
@@ -145,35 +148,31 @@
if not isinstance(order, (list, tuple)):
order = list(order)
old_slot_order = self.slot_order
- old_slots = self.slots
assert len(order) == self.rows
new_slot_order = PersistentMapping()
- new_slots = PersistentMapping()
for index, row_id in enumerate(order):
new_slot_order[index] = old_slot_order.get(row_id) or PersistentList()
- new_slots[index] = old_slots.get(row_id) or PersistentMapping()
if self.slot_order != new_slot_order:
self.slot_order = new_slot_order
- self.slots = new_slots
def delete_row(self, row_id):
"""Delete template row"""
- assert row_id in self.slots
- for slot_name in self.slots.get(row_id, {}).keys():
+ assert row_id in self.slot_order
+ for slot_name in self.slot_order.get(row_id, ()):
+ config = IPortalPortletsConfiguration(self.__parent__)
+ config.delete_portlet_configuration(self.slot_config[slot_name].portlet_ids)
if slot_name in self.slot_names:
self.slot_names.remove(slot_name)
if slot_name in self.slot_config:
del self.slot_config[slot_name]
- if slot_name in self.portlet_config:
- del self.portlet_config[slot_name]
for index in range(row_id, self.rows-1):
self.slot_order[index] = self.slot_order[index+1]
- self.slots[index] = self.slots[index+1]
if self.rows > 0:
del self.slot_order[self.rows-1]
- del self.slots[self.rows-1]
self.rows -= 1
+ # slots management
+
@property
def slot_names(self):
if IPortalTemplate.providedBy(self.__parent__):
@@ -197,15 +196,15 @@
self._slot_order = value
@property
- def slots(self):
+ def slot_config(self):
if IPortalTemplate.providedBy(self.__parent__):
- return self._slots
+ return self._slot_config
else:
- return IPortalTemplateConfiguration(self.__parent__).slots
+ return IPortalTemplateConfiguration(self.__parent__).slot_config
- @slots.setter
- def slots(self, value):
- self._slots = value
+ @slot_config.setter
+ def slot_config(self, value):
+ self._slot_config = value
def add_slot(self, slot_name, row_id=None):
assert slot_name not in self.slot_names
@@ -216,10 +215,6 @@
if row_id not in self.slot_order:
self.slot_order[row_id] = PersistentList()
self.slot_order[row_id].append(slot_name)
- # init slots portlets
- if row_id not in self.slots:
- self.slots[row_id] = PersistentMapping()
- self.slots[row_id][slot_name] = PersistentList()
# init slots configuration
slot = self.slot_config[slot_name] = SlotConfiguration(slot_name)
locate(slot, self.__parent__)
@@ -228,18 +223,11 @@
def set_slot_order(self, order):
"""Set slots order"""
old_slot_order = self.slot_order
- old_slots = self.slots
new_slot_order = PersistentMapping()
- new_slots = PersistentMapping()
for row_id in sorted(map(int, order.keys())):
new_slot_order[row_id] = PersistentList(order[row_id])
- new_slots[row_id] = PersistentMapping()
- for slot_name in order[row_id]:
- old_row_id = self.get_slot_row(slot_name)
- new_slots[row_id][slot_name] = old_slots[old_row_id][slot_name]
if new_slot_order != old_slot_order:
self.slot_order = new_slot_order
- self.slots = new_slots
def get_slot_row(self, slot_name):
for row_id in self.slot_order:
@@ -280,102 +268,58 @@
"""Delete slot and associated portlets"""
assert slot_name in self.slot_names
row_id = self.get_slot_row(slot_name)
- del self.portlet_config[slot_name]
+ # delete portlet configuration
+ config = IPortalPortletsConfiguration(self.__parent__)
+ config.delete_portlet_configuration(self.slot_config[slot_name].portlet_ids)
+ # delete slot configuration
del self.slot_config[slot_name]
- del self.slots[row_id][slot_name]
self.slot_order[row_id].remove(slot_name)
self.slot_names.remove(slot_name)
+ # portlets management
+
def add_portlet(self, portlet_name, slot_name):
"""Add portlet to given slot"""
assert slot_name in self.slot_names
- row_id = self.get_slot_row(slot_name)
- if slot_name not in self.slots.get(row_id):
- self.slots[row_id][slot_name] = PersistentList()
- self.slots[row_id][slot_name].append(portlet_name)
- if slot_name not in self.portlet_config:
- self.portlet_config[slot_name] = PersistentMapping()
- position = len(self.slots[row_id][slot_name]) - 1
+ # get new portlet configuration
portlet = get_current_registry().getUtility(IPortlet, name=portlet_name)
config = IPortletConfiguration(portlet)
- config.slot_name = slot_name
- config.position = position
- locate(config, self.__parent__, '++portlet++{0}::{1}'.format(slot_name, position))
- self.portlet_config[slot_name][position] = config
+ # store portlet configuration
+ manager = get_utility(IPortalTemplateContainer)
+ IPortalPortletsConfiguration(self.__parent__).set_portlet_configuration(manager.get_portlet_id(), config)
+ # update slots configuration
+ self.slot_config[slot_name].portlet_ids.append(config.portlet_id)
return {'portlet_name': portlet_name,
+ 'portlet_id': config.portlet_id,
'slot_name': slot_name,
- 'position': position,
+ 'position': len(self.slot_config[slot_name].portlet_ids) - 1,
'label': check_request().localizer.translate(portlet.label)}
+ def get_portlet_slot(self, portlet_id):
+ """Get portlet slot"""
+ for slot_name, config in self.slot_config.items():
+ if portlet_id in config.portlet_ids:
+ return self.get_slot_row(slot_name), slot_name
+ return None, None
+
def set_portlet_order(self, order):
"""Set portlet order"""
- source = order['from']
- source_slot = source['slot']
- source_row = self.get_slot_row(source_slot)
- target = order['to']
- target_slot = target['slot']
+ from_row, from_slot = self.get_portlet_slot(order['from'])
+ if from_slot is None:
+ return
+ target_slot = order['to']['slot']
target_row = self.get_slot_row(target_slot)
- portlet_config = self.portlet_config
- old_config = portlet_config[source_slot].pop(source['position'])
- target_config = PersistentMapping()
- for index, (slot_name, portlet_name, position) in enumerate(zip(target['slots'], target['names'],
- target['positions'])):
- if (slot_name == source_slot) and (position == source['position']):
- target_config[index] = old_config
- else:
- target_config[index] = portlet_config[slot_name][position]
- target_config[index].slot_name = target_slot
- target_config[index].position = index
- locate(target_config[index], self.__parent__, '++portlet++{0}::{1}'.format(slot_name, index))
- portlet_config[target_slot] = target_config
- # re-order source portlets
- config = portlet_config[source_slot]
- for index, key in enumerate(sorted(config)):
- if index != key:
- config[index] = config.pop(key)
- config[index].position = index
- locate(config[index], self.__parent__, '++portlet++{0}::{1}'.format(source_slot, index))
- # re-order target portlets
- if target_slot != source_slot:
- config = portlet_config[target_slot]
- for index, key in enumerate(sorted(config)):
- config[index] = config.pop(key)
- config[index].position = index
- locate(config[index], self.__parent__, '++portlet++{0}::{1}'.format(target_slot, index))
- self.portlet_config = portlet_config
- del self.slots[source_row][source_slot][source['position']]
- self.slots[target_row][target_slot] = PersistentList(target['names'])
+ if target_row is None:
+ return
+ self.slot_config[from_slot].portlet_ids.remove(order['from'])
+ self.slot_config[target_slot].portlet_ids = PersistentList(order['to']['portlet_ids'])
- def get_portlet_configuration(self, slot_name, position):
- """Get portlet configuration"""
- if slot_name not in self.slot_names:
- return None
- config = self.portlet_config.get(slot_name, {}).get(position)
- if config is None:
- if IPortalTemplate.providedBy(self.__parent__):
- portlet_name = self.slots[slot_name][position]
- portlet = get_current_registry().queryUtility(IPortlet, name=portlet_name)
- config = IPortletConfiguration(portlet)
- else:
- config = clone(IPortalTemplateConfiguration(self.__parent__).get_portlet_configuration(slot_name,
- position))
- config.inherit_parent = True
- self.portlet_config[slot_name][position] = config
- locate(config, self.__parent__)
- return config
-
- def delete_portlet(self, slot_name, position):
+ def delete_portlet(self, portlet_id):
"""Delete portlet"""
- assert slot_name in self.slot_names
- row_id = self.get_slot_row(slot_name)
- config = self.portlet_config[slot_name]
- del config[position]
- if len(config) and (position < max(tuple(config.keys()))):
- for index, key in enumerate(sorted(config)):
- config[index] = config.pop(key)
- config[index].position = index
- locate(config[index], self.__parent__, '++portlet++{0}::{1}'.format(slot_name, index))
- del self.slots[row_id][slot_name][position]
+ row_id, slot_name = self.get_portlet_slot(portlet_id)
+ if slot_name is not None:
+ self.slot_config[slot_name].portlet_ids.remove(portlet_id)
+ IPortalPortletsConfiguration(self.__parent__).delete_portlet_configuration(portlet_id)
class PortalTemplateSlotsVocabulary(SimpleVocabulary):
@@ -389,25 +333,9 @@
getVocabularyRegistry().register('PyAMS template slots', PortalTemplateSlotsVocabulary)
-@adapter_config(name='portlet', context=IPortalTemplate, provides=ITraversable)
-class PortalTemplatePortletTraverser(ContextAdapter):
- """++portlet++ namespace traverser"""
-
- def traverse(self, name, furtherpath=None):
- config = IPortalTemplateConfiguration(self.context)
- if name:
- slot_name, position = name.split('::')
- return config.get_portlet_configuration(slot_name, int(position))
- else:
- return config
-
-
-TEMPLATE_CONFIGURATION_KEY = 'pyams_portal.template'
-
-
@adapter_config(context=IPortalTemplate, provides=IPortalTemplateConfiguration)
def PortalTemplateConfigurationFactory(context):
- """Portal template configuration factory"""
+ """Portal template configuration adapter"""
annotations = IAnnotations(context)
config = annotations.get(TEMPLATE_CONFIGURATION_KEY)
if config is None:
@@ -417,25 +345,29 @@
return config
-@adapter_config(context=IPortalContext, provides=IPortalTemplateConfiguration)
-def PortalContextConfigurationFactory(context):
- """Portal context configuration factory"""
- page = IPortalPage(context)
- if page.use_local_template:
- template = IWorkflowVersions(page.template).get_last_versions()[0]
- config = IPortalTemplateConfiguration(template)
- else:
- annotations = IAnnotations(context)
- config = annotations.get(TEMPLATE_CONFIGURATION_KEY)
- if config is None:
- # we clone template configuration
- config = annotations[TEMPLATE_CONFIGURATION_KEY] = clone(IPortalTemplateConfiguration(page.template))
- get_current_registry().notify(ObjectCreatedEvent(config))
- locate(config, context)
- return config
+@adapter_config(name='portlet', context=IPortalTemplate, provides=ITraversable)
+class PortalTemplatePortletTraverser(ContextAdapter):
+ """++portlet++ template traverser"""
+
+ def traverse(self, name, furtherpath=None):
+ config = IPortalPortletsConfiguration(self.context)
+ if name:
+ return config.get_portlet_configuration(int(name))
+ else:
+ return config
-@adapter_config(context=IPortletConfiguration, provides=IPortalTemplateConfiguration)
-def PortalPortletConfigurationFactory(context):
- """Portal portlet configuration factory"""
- return IPortalTemplateConfiguration(context.__parent__)
+#
+# Template portlets configuration
+#
+
+@adapter_config(context=IPortalTemplate, provides=IPortalPortletsConfiguration)
+def PortalTemplatePortletsConfigurationAdapter(template):
+ """Portal template portlets configuration adapter"""
+ annotations = IAnnotations(template)
+ config = annotations.get(PORTLETS_CONFIGURATION_KEY)
+ if config is None:
+ config = annotations[PORTLETS_CONFIGURATION_KEY] = PortalPortletsConfiguration()
+ get_current_registry().notify(ObjectCreatedEvent(config))
+ locate(config, template)
+ return config
--- a/src/pyams_portal/workflow.py Thu Oct 08 12:26:42 2015 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,223 +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
-from datetime import datetime
-
-# import interfaces
-from pyams_workflow.interfaces import IWorkflowPublicationInfo, IWorkflowState, IWorkflowVersions, IWorkflowInfo, \
- ObjectClonedEvent, IWorkflow
-
-# import packages
-from pyams_utils.registry import utility_config
-from pyams_workflow.workflow import Transition, Workflow
-from pyramid.threadlocal import get_current_registry
-from zope.copy import copy
-from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
-
-from pyams_portal import _
-
-
-DRAFT = 'draft' # new template
-PUBLISHED = 'published' # published template
-RETIRED = 'retired' # retired template
-ARCHIVED = 'archived' # archive done
-DELETED = 'deleted' # deleted template
-
-
-STATUS_IDS = ('draft', 'published', 'retired', 'archived', 'deleted')
-STATUS_LABELS = (_("Draft"),
- _("Published"),
- _("Retired"),
- _("Archived"),
- _("Deleted"))
-
-STATUS_VOCABULARY = SimpleVocabulary([SimpleTerm(STATUS_IDS[i], STATUS_IDS[i], t)
- for i, t in enumerate(STATUS_LABELS)])
-
-
-def publish_action(wf, context):
- """Publish template"""
- now = datetime.utcnow()
- wf_content = IWorkflowPublicationInfo(context)
- if wf_content.first_publication_date is None:
- wf_content.first_publication_date = now
- IWorkflowPublicationInfo(context).publication_date = now
- version_id = IWorkflowState(context).version_id
- for version in IWorkflowVersions(context).get_versions(('published', 'retired')):
- if version is not context:
- IWorkflowInfo(version).fire_transition_toward('archived',
- comment="Published version {0}".format(version_id))
-
-
-def clone_action(wf, context):
- """Duplicate template"""
- result = copy(context)
- registry = get_current_registry()
- registry.notify(ObjectClonedEvent(result, context))
- return result
-
-
-def retire_action(wf, context):
- """Archive template"""
- now = datetime.utcnow()
- IWorkflowPublicationInfo(context).publication_expiration_date = now
-
-
-def archive_action(wf, context):
- """Archive template"""
- now = datetime.utcnow()
- content = IWorkflowPublicationInfo(context)
- content.publication_expiration_date = min(content.publication_expiration_date or now, now)
-
-
-def can_delete(wf, context):
- content = IWorkflowPublicationInfo(context)
- return content.publication_effective_date is None
-
-
-def delete_action(wf, context):
- """Delete draft version"""
- parent = context.__parent__
- name = context.__name__
- del parent[name]
-
-
-#
-# Workflow transitions
-#
-
-init = Transition(transition_id='init',
- title=_("Initialize"),
- source=None,
- destination=DRAFT)
-
-draft_to_published = Transition('draft_to_published',
- title=_("Publish..."),
- source=DRAFT,
- destination=PUBLISHED,
- permission='portal.templates.manage',
- action=publish_action,
- order=1,
- menu_css_class='fa fa-fw fa-play',
- view_name='wf-publish.html',
- html_help=_('''This content is currently in DRAFT mode.
- Publishing it will make it publicly visible.'''))
-
-published_to_retired = Transition('published_to_retired',
- title=_("Retire..."),
- source=PUBLISHED,
- destination=RETIRED,
- permission='portal.templates.manage',
- action=retire_action,
- order=2,
- menu_css_class='fa fa-fw fa-stop',
- view_name='wf-retire.html',
- html_help=_('''This content is actually published.
- You can retire it to make it invisible, but contents using this
- template won't be visible anymore!'''))
-
-published_to_draft = Transition('published_to_draft',
- title=_("Create new version..."),
- source=PUBLISHED,
- destination=DRAFT,
- permission='portal.templates.manage',
- action=clone_action,
- order=99,
- menu_css_class='fa fa-fw fa-copy',
- view_name='wf-clone.html')
-
-retired_to_published = Transition('retired_to_published',
- title=_("Re-publish..."),
- source=RETIRED,
- destination=PUBLISHED,
- permission='portal.templates.manage',
- action=publish_action,
- order=1,
- menu_css_class='fa fa-fw fa-play',
- view_name='wf-publish.html',
- html_help=_('''This content was published and retired.
- You can re-publish it to make it visible again.'''))
-
-published_to_archived = Transition('published_to_archived',
- title=_("Archive..."),
- source=PUBLISHED,
- destination=ARCHIVED,
- permission='portal.templates.manage',
- action=archive_action,
- order=3,
- menu_css_class='fa fa-fw fa-archive',
- view_name='wf-archive.html',
- html_help=_('''This content is currently published.
- If it is archived, it will not be possible to make it visible again
- except by creating a new version!'''))
-
-retired_to_archived = Transition('retired_to_archived',
- title=_("Archive..."),
- source=RETIRED,
- destination=ARCHIVED,
- permission='portal.templates.manage',
- action=archive_action,
- order=3,
- menu_css_class='fa fa-fw fa-archive',
- view_name='wf-archive.html',
- html_help=_('''This content has been published but is currently retired.
- If it is archived, it will not be possible to make it visible again
- except by creating a new version!'''))
-
-archived_to_draft = Transition('archived_to_draft',
- title=_("Create new version..."),
- source=ARCHIVED,
- destination=DRAFT,
- permission='portal.templates.manage',
- action=clone_action,
- order=99,
- menu_css_class='fa fa-fw fa-copy',
- view_name='wf-clone.html')
-
-deleted = Transition('delete',
- title=_("Delete..."),
- source=DRAFT,
- destination=DELETED,
- condition=can_delete,
- action=delete_action,
- order=6,
- menu_css_class='fa fa-fw fa-trash',
- view_name='wf-delete.html',
- html_help=_('''This content has never been published.
- It can be removed and definitely deleted.'''))
-
-wf_transitions = [init,
- draft_to_published,
- published_to_retired,
- published_to_draft,
- retired_to_published,
- published_to_archived,
- retired_to_archived,
- archived_to_draft,
- deleted]
-
-
-wf = Workflow(wf_transitions,
- states=STATUS_VOCABULARY,
- published_states=(PUBLISHED,))
-
-
-@utility_config(name='PyAMS portal template workflow', provides=IWorkflow)
-class WorkflowUtility(object):
- """Workflow utility registration"""
-
- def __new__(cls):
- return wf
--- a/src/pyams_portal/zmi/container.py Thu Oct 08 12:26:42 2015 +0200
+++ b/src/pyams_portal/zmi/container.py Mon Jan 18 18:09:46 2016 +0100
@@ -16,13 +16,13 @@
# import standard library
# import interfaces
-from pyams_portal.interfaces import IPortalTemplateContainer, IPortalWfTemplate, IPortalTemplateContainerConfiguration
+from pyams_portal.interfaces import IPortalTemplateContainer, IPortalTemplate, IPortalTemplateContainerConfiguration, \
+ MANAGE_TEMPLATE_PERMISSION
from pyams_portal.zmi.interfaces import IPortalTemplateContainerMenu
from pyams_skin.interfaces import IInnerPage, IPageHeader
from pyams_skin.interfaces.container import ITable, ITableElementEditor
from pyams_skin.layer import IPyAMSLayer
from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION, MANAGE_SYSTEM_PERMISSION
-from pyams_workflow.interfaces import IWorkflowVersions
from pyams_zmi.interfaces.menu import IControlPanelMenu
from pyams_zmi.layer import IAdminLayer
from z3c.table.interfaces import IColumn, IValues
@@ -33,7 +33,7 @@
from pyams_pagelet.pagelet import pagelet_config
from pyams_skin.container import ContainerView
from pyams_skin.page import DefaultPageHeaderAdapter
-from pyams_skin.table import DefaultElementEditorAdapter, BaseTable, TrashColumn
+from pyams_skin.table import DefaultElementEditorAdapter, BaseTable, NameColumn, TrashColumn
from pyams_skin.viewlet.menu import MenuItem
from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter
from pyams_utils.registry import query_utility
@@ -41,7 +41,6 @@
from pyams_utils.url import absolute_url
from pyams_viewlet.manager import viewletmanager_config
from pyams_viewlet.viewlet import viewlet_config
-from pyams_workflow.zmi.workflow import WorkflowContentNameColumn
from pyams_zmi.form import AdminDialogEditForm
from pyams_zmi.view import AdminView
from pyramid.url import resource_url
@@ -103,7 +102,7 @@
return attributes
-@adapter_config(context=(IPortalWfTemplate, IAdminLayer, PortalTemplateContainerTable), provides=ITableElementEditor)
+@adapter_config(context=(IPortalTemplate, IAdminLayer, PortalTemplateContainerTable), provides=ITableElementEditor)
class PortalTemplateTableElementEditor(DefaultElementEditorAdapter):
"""Portal template table element editor"""
@@ -111,18 +110,14 @@
@property
def url(self):
- wf_versions = IWorkflowVersions(self.context).get_last_versions(count=1)
- if wf_versions:
- return resource_url(wf_versions[0], self.request, 'admin.html#{0}'.format(self.view_name))
- else:
- return None
+ return resource_url(self.context, self.request, 'admin.html#{0}'.format(self.view_name))
@adapter_config(name='name', context=(Interface, IAdminLayer, PortalTemplateContainerTable), provides=IColumn)
-class PortalTemplateContainerNameColumn(WorkflowContentNameColumn):
+class PortalTemplateContainerNameColumn(NameColumn):
"""Portal template container name column"""
- name_field = 'name'
+ attrName = 'name'
@adapter_config(name='trash', context=(Interface, IAdminLayer, PortalTemplateContainerTable), provides=IColumn)
@@ -130,7 +125,7 @@
"""Portal template container trash column"""
icon_hint = _("Delete template")
- permission = 'portal.templates.manage'
+ permission = MANAGE_TEMPLATE_PERMISSION
@adapter_config(context=(ISite, IAdminLayer, PortalTemplateContainerTable), provides=IValues)
@@ -161,8 +156,6 @@
"""Portal template container header adapter"""
icon_class = 'fa fa-fw fa-columns'
- title = _("Portal")
- subtitle = _("Portal templates")
#
@@ -198,6 +191,9 @@
ajax_handler = 'properties.json'
edit_permission = MANAGE_SYSTEM_PERMISSION
+ label_css_class = 'control-label col-md-4'
+ input_css_class = 'col-md-8'
+
@view_config(name='properties.json', context=IPortalTemplateContainer, request_type=IPyAMSLayer,
permission=MANAGE_SYSTEM_PERMISSION, renderer='json', xhr=True)
--- a/src/pyams_portal/zmi/interfaces.py Thu Oct 08 12:26:42 2015 +0200
+++ b/src/pyams_portal/zmi/interfaces.py Mon Jan 18 18:09:46 2016 +0100
@@ -26,5 +26,9 @@
"""Portal template container menu interface"""
+class IPortalContextTemplatePropertiesMenu(IMenuItem):
+ """Portal template properties menu interface"""
+
+
class IPortletConfigurationEditor(IForm):
"""Portlet configuration editor interface"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_portal/zmi/layout.py Mon Jan 18 18:09:46 2016 +0100
@@ -0,0 +1,519 @@
+#
+# 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 json
+
+# import interfaces
+from pyams_pagelet.interfaces import IPagelet, PageletCreatedEvent
+from pyams_portal.interfaces import IPortalTemplate, IPortalTemplateConfiguration, ISlot, \
+ IPortletAddingInfo, IPortlet, ISlotConfiguration, IPortletPreviewer, IPortalTemplateContainer, \
+ 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.layer import IPyAMSLayer
+from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
+from pyams_zmi.interfaces.menu import IPropertiesMenu, IContentManagementMenu
+from pyams_zmi.layer import IAdminLayer
+from transaction.interfaces import ITransactionManager
+from z3c.form.interfaces import IDataExtractedEvent, HIDDEN_MODE
+
+# import packages
+from pyams_form.form import AJAXAddForm, AJAXEditForm
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_portal.zmi.template import PortalTemplateHeaderAdapter
+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 query_utility
+from pyams_utils.traversing import get_parent
+from pyams_viewlet.manager import viewletmanager_config
+from pyams_viewlet.viewlet import viewlet_config
+from pyams_zmi.form import AdminDialogAddForm, AdminDialogEditForm
+from pyams_zmi.view import AdminView
+from pyramid.decorator import reify
+from pyramid.events import subscriber
+from pyramid.exceptions import NotFound
+from pyramid.view import view_config
+from z3c.form import field
+from zope.interface import implementer, Invalid, Interface
+
+from pyams_portal import _
+
+
+@adapter_config(context=(IPortalTemplate, IContentManagementMenu), provides=IMenuHeader)
+class PortalTemplateMenuHeader(object):
+ """Portal template menu header"""
+
+ def __init__(self, context, menu):
+ self.context = context
+ self.menu = menu
+
+ @property
+ def header(self):
+ return _("Template management")
+
+
+@viewlet_config(name='template-properties.menu', context=IPortalTemplate, layer=IAdminLayer,
+ manager=IContentManagementMenu, permission=VIEW_SYSTEM_PERMISSION, weight=1)
+@viewletmanager_config(name='template-properties.menu', layer=IAdminLayer, provides=IPropertiesMenu)
+@implementer(IPropertiesMenu)
+class PortalTemplatePropertiesMenu(MenuItem):
+ """Portal template properties menu"""
+
+ label = _("Properties")
+ icon_class = 'fa-twitch'
+ url = '#properties.html'
+
+
+@pagelet_config(name='properties.html', context=IPortalTemplate, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
+@template_config(template='templates/layout.pt', layer=IAdminLayer)
+@implementer(IInnerPage)
+class PortalTemplateLayoutView(AdminView):
+ """Portal template main layout configuration view"""
+
+ @property
+ def title(self):
+ container = get_parent(self.context, IPortalTemplateContainer)
+ if container is None:
+ context = get_parent(self.context, IPortalContext)
+ page = IPortalPage(context)
+ if page.use_local_template:
+ return _("Local template configuration")
+ else:
+ translate = self.request.localizer.translate
+ return translate(_("Shared template configuration ({0})")).format(page.template.name)
+ else:
+ return _("Template configuration")
+
+ def get_template(self):
+ return self.context
+
+ def get_context(self):
+ return self.context
+
+ @property
+ def can_change(self):
+ return self.request.has_permission(MANAGE_TEMPLATE_PERMISSION)
+
+ @reify
+ def template_configuration(self):
+ return IPortalTemplateConfiguration(self.get_template())
+
+ @reify
+ def portlet_configuration(self):
+ return IPortalPortletsConfiguration(self.get_context())
+
+ @property
+ def selected_portlets(self):
+ container = query_utility(IPortalTemplateContainer)
+ configuration = IPortalTemplateContainerConfiguration(container)
+ return filter(lambda x: x is not None,
+ [query_utility(IPortlet, name=portlet_name)
+ for portlet_name in configuration.toolbar_portlets or ()])
+
+ def get_portlet(self, name):
+ return self.request.registry.getUtility(IPortlet, name=name)
+
+ def get_portlet_label(self, name):
+ return self.request.localizer.translate(self.get_portlet(name).label)
+
+ def get_portlet_preview(self, portlet_id):
+ settings = self.portlet_configuration.get_portlet_configuration(portlet_id).settings
+ previewer = self.request.registry.queryMultiAdapter((self.get_context(), self.request, self, settings),
+ IPortletPreviewer)
+ if previewer is not None:
+ previewer.update()
+ return previewer.render()
+ else:
+ return ''
+
+
+@adapter_config(context=(IPortalTemplate, IAdminLayer, Interface), provides=IPageHeader)
+class PortalTemplateLayoutHeaderAdapter(PortalTemplateHeaderAdapter):
+ """Portal template configuration header adapter"""
+
+ back_url = '/admin.html#portal-templates.html'
+ back_target = None
+
+
+#
+# Rows views
+#
+
+@viewlet_config(name='add-template-row.menu', context=IPortalTemplate, layer=IAdminLayer,
+ view=PortalTemplateLayoutView, manager=IToolbarAddingMenu,
+ permission=MANAGE_TEMPLATE_PERMISSION, weight=1)
+class PortalTemplateRowAddMenu(JsToolbarMenuItem):
+ """Portal template row add menu"""
+
+ label = _("Add row...")
+ label_css_class = 'fa fa-fw fa-indent'
+ url = 'PyAMS_portal.template.addRow'
+
+
+@view_config(name='add-template-row.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+def add_template_row(request):
+ """Add template raw"""
+ config = IPortalTemplateConfiguration(request.context)
+ return {'row_id': config.add_row()}
+
+
+@view_config(name='set-template-row-order.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+def set_template_row_order(request):
+ """Set template rows order"""
+ config = IPortalTemplateConfiguration(request.context)
+ row_ids = map(int, json.loads(request.params.get('rows')))
+ config.set_row_order(row_ids)
+ return {'status': 'success'}
+
+
+@view_config(name='delete-template-row.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+def delete_template_row(request):
+ """Delete template row"""
+ config = IPortalTemplateConfiguration(request.context)
+ config.delete_row(int(request.params.get('row_id')))
+ return {'status': 'success'}
+
+
+#
+# Slots views
+#
+
+@viewlet_config(name='add-template-slot.menu', context=IPortalTemplate, layer=IAdminLayer,
+ view=PortalTemplateLayoutView, manager=IToolbarAddingMenu,
+ permission=MANAGE_TEMPLATE_PERMISSION, weight=2)
+class PortalTemplateSlotAddMenu(ToolbarMenuItem):
+ """Portal template slot add menu"""
+
+ label = _("Add slot...")
+ label_css_class = 'fa fa-fw fa-columns'
+ url = 'add-template-slot.html'
+ modal_target = True
+
+
+@pagelet_config(name='add-template-slot.html', context=IPortalTemplate, layer=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION)
+class PortalTemplateSlotAddForm(AdminDialogAddForm):
+ """Portal template slot add form"""
+
+ @property
+ def title(self):
+ translate = self.request.localizer.translate
+ return translate(_("« {0} » portal template")).format(self.context.name)
+
+ legend = _("Add slot")
+ icon_css_class = 'fa fa-fw fa-columns'
+
+ fields = field.Fields(ISlot)
+ ajax_handler = 'add-template-slot.json'
+ edit_permission = None
+
+ def updateWidgets(self, prefix=None):
+ super(PortalTemplateSlotAddForm, self).updateWidgets()
+ self.widgets['row_id'].value = self.request.params.get('form.widgets.row_id')
+ if self.widgets['row_id'].value:
+ self.widgets['row_id'].mode = HIDDEN_MODE
+
+ def createAndAdd(self, data):
+ config = IPortalTemplateConfiguration(self.context)
+ return config.add_slot(data.get('name'), data.get('row_id'))
+
+
+@subscriber(IDataExtractedEvent, form_selector=PortalTemplateSlotAddForm)
+def handle_new_slot_data_extraction(event):
+ """Handle new slot form data extraction"""
+ config = IPortalTemplateConfiguration(event.form.context)
+ name = event.data.get('name')
+ if name in config.slot_names:
+ event.form.widgets.errors += (Invalid(_("Specified name is already used!")),)
+
+
+@view_config(name='add-template-slot.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+class PortalTemplateSlotAJAXAddForm(AJAXAddForm, PortalTemplateSlotAddForm):
+ """Portal template slot add form, AJAX handler"""
+
+ def get_ajax_output(self, changes):
+ return {'status': 'callback',
+ 'callback': 'PyAMS_portal.template.addSlotCallback',
+ 'options': {'row_id': changes[0],
+ 'slot_name': changes[1]}}
+
+
+@view_config(name='set-template-slot-order.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+def set_template_slot_order(request):
+ """Set template slots order"""
+ config = IPortalTemplateConfiguration(request.context)
+ order = json.loads(request.params.get('order'))
+ for key in order.copy().keys():
+ order[int(key)] = order.pop(key)
+ config.set_slot_order(order)
+ return {'status': 'success'}
+
+
+@view_config(name='get-slots-width.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=VIEW_SYSTEM_PERMISSION, renderer='json', xhr=True)
+def get_template_slots_width(request):
+ """Get template slots width"""
+ config = IPortalTemplateConfiguration(request.context)
+ return config.get_slots_width(request.params.get('device'))
+
+
+@view_config(name='set-slot-width.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+def set_template_slot_width(request):
+ """Set template slot width"""
+ config = IPortalTemplateConfiguration(request.context)
+ config.set_slot_width(request.params.get('slot_name'),
+ request.params.get('device'),
+ int(request.params.get('width')))
+ return config.get_slots_width(request.params.get('device'))
+
+
+@pagelet_config(name='slot-properties.html', context=IPortalTemplate, layer=IPyAMSLayer,
+ permission=VIEW_SYSTEM_PERMISSION)
+class PortalTemplateSlotPropertiesEditForm(AdminDialogEditForm):
+ """Slot properties edit form"""
+
+ @property
+ def title(self):
+ translate = self.request.localizer.translate
+ return translate(_("« {0} » portal template - {1} slot")).format(self.context.name,
+ self.getContent().slot_name)
+
+ legend = _("Edit slot properties")
+ fields = field.Fields(ISlotConfiguration).omit('portlet_ids')
+
+ label_css_class = 'control-label col-md-5'
+ input_css_class = 'col-md-7'
+
+ ajax_handler = 'slot-properties.json'
+ edit_permission = MANAGE_TEMPLATE_PERMISSION
+
+ def __init__(self, context, request):
+ super(PortalTemplateSlotPropertiesEditForm, self).__init__(context, request)
+ self.config = IPortalTemplateConfiguration(context)
+
+ def getContent(self):
+ slot_name = self.request.params.get('form.widgets.slot_name')
+ return self.config.slot_config[slot_name]
+
+ def updateWidgets(self, prefix=None):
+ super(PortalTemplateSlotPropertiesEditForm, self).updateWidgets(prefix)
+ self.widgets['slot_name'].mode = HIDDEN_MODE
+
+
+@view_config(name='slot-properties.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+class PortalTemplateSlotPropertiesAJAXEditForm(AJAXEditForm, PortalTemplateSlotPropertiesEditForm):
+ """Slot properties edit form, AJAX renderer"""
+
+ def get_ajax_output(self, changes):
+ if changes:
+ slot_name = self.widgets['slot_name'].value
+ slot_config = self.config.slot_config[slot_name]
+ return {'status': 'success',
+ 'callback': 'PyAMS_portal.template.editSlotCallback',
+ 'options': {'slot_name': slot_name,
+ 'width': slot_config.get_width()}}
+ else:
+ return super(PortalTemplateSlotPropertiesAJAXEditForm, self).get_ajax_output(changes)
+
+
+@view_config(name='delete-template-slot.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+def delete_template_slot(request):
+ """Delete template slot"""
+ config = IPortalTemplateConfiguration(request.context)
+ config.delete_slot(request.params.get('slot_name'))
+ return {'status': 'success'}
+
+
+#
+# Portlet views
+#
+
+@viewlet_config(name='add-template-portlet.divider', context=IPortalTemplate, layer=IAdminLayer,
+ view=PortalTemplateLayoutView, manager=IToolbarAddingMenu,
+ permission=MANAGE_TEMPLATE_PERMISSION, weight=10)
+class PortalTemplateAddMenuDivider(ToolbarMenuDivider):
+ """Portal template menu divider"""
+
+
+@viewlet_config(name='add-template-portlet.menu', context=IPortalTemplate, layer=IAdminLayer,
+ view=PortalTemplateLayoutView, manager=IToolbarAddingMenu,
+ permission=MANAGE_TEMPLATE_PERMISSION, weight=20)
+class PortalTemplatePortletAddMenu(ToolbarMenuItem):
+ """Portal template portlet add menu"""
+
+ label = _("Add portlet...")
+ label_css_class = 'fa fa-fw fa-columns'
+ url = 'add-template-portlet.html'
+ modal_target = True
+
+
+@pagelet_config(name='add-template-portlet.html', context=IPortalTemplate, layer=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION)
+class PortalTemplatePortletAddForm(AdminDialogAddForm):
+ """Portal template portlet add form"""
+
+ @property
+ def title(self):
+ translate = self.request.localizer.translate
+ return translate(_("« {0} » portal template")).format(self.context.name)
+
+ legend = _("Add portlet")
+ icon_css_class = 'fa fa-fw fa-columns'
+
+ fields = field.Fields(IPortletAddingInfo)
+ ajax_handler = 'add-template-portlet.json'
+ edit_permission = None
+
+ def createAndAdd(self, data):
+ config = IPortalTemplateConfiguration(self.context)
+ return config.add_portlet(data.get('portlet_name'), data.get('slot_name'))
+
+
+@view_config(name='add-template-portlet.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+class PortalTemplatePortletAJAXAddForm(AJAXAddForm, PortalTemplatePortletAddForm):
+ """Portal template portlet add form, AJAX handler"""
+
+ def get_ajax_output(self, changes):
+ configuration = IPortalPortletsConfiguration(self.context)
+ settings = configuration.get_portlet_configuration(changes['portlet_id']).settings
+ previewer = self.request.registry.queryMultiAdapter((self.context, self.request, self, settings),
+ IPortletPreviewer)
+ if previewer is not None:
+ previewer.update()
+ changes['preview'] = previewer.render()
+ return {'status': 'callback',
+ 'callback': 'PyAMS_portal.template.addPortletCallback',
+ 'options': changes}
+
+
+@view_config(name='drag-template-portlet.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+def drag_template_portlet(request):
+ """Drag portlet icon to slot"""
+ tmpl_config = IPortalTemplateConfiguration(request.context)
+ portlets_config = IPortalPortletsConfiguration(request.context)
+ portlet_name = request.params.get('portlet_name')
+ slot_name = request.params.get('slot_name')
+ changes = tmpl_config.add_portlet(portlet_name, slot_name)
+ settings = portlets_config.get_portlet_configuration(changes['portlet_id']).settings
+ previewer = request.registry.queryMultiAdapter((request.context, request, request, settings),
+ IPortletPreviewer)
+ if previewer is not None:
+ previewer.update()
+ changes['preview'] = previewer.render()
+ return {'status': 'callback',
+ 'close_form': False,
+ 'callback': 'PyAMS_portal.template.addPortletCallback',
+ 'options': changes}
+
+
+@view_config(name='set-template-portlet-order.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+def set_template_portlet_order(request):
+ """Set template portlet order"""
+ order = json.loads(request.params.get('order'))
+ order['from'] = int(order['from'])
+ order['to']['portlet_ids'] = list(map(int, order['to']['portlet_ids']))
+ IPortalTemplateConfiguration(request.context).set_portlet_order(order)
+ return {'status': 'success'}
+
+
+@view_config(name='portlet-properties.html', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=VIEW_SYSTEM_PERMISSION)
+class PortalTemplatePortletEditForm(AdminDialogEditForm):
+ """Portal template portlet edit form"""
+
+ dialog_class = 'modal-large'
+
+ def __call__(self):
+ request = self.request
+ request.registry.notify(PageletCreatedEvent(self))
+ portlet_id = int(request.params.get('form.widgets.portlet_id'))
+ portlet_config = IPortalPortletsConfiguration(self.context).get_portlet_configuration(portlet_id)
+ if portlet_config is None:
+ raise NotFound()
+ editor = self.request.registry.queryMultiAdapter((portlet_config.editor_settings, request),
+ IPagelet, name='properties.html')
+ if editor is None:
+ raise NotFound()
+ request.registry.notify(PageletCreatedEvent(editor))
+ editor.ajax_handler = 'portlet-properties.json'
+ editor.update()
+ return editor()
+
+
+@view_config(name='portlet-properties.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+class PortalTemplatePortletAJAXEditForm(AJAXEditForm, PortalTemplatePortletEditForm):
+ """Portal template portlet edit form, AJAX renderer"""
+
+ def __call__(self):
+ request = self.request
+ request.registry.notify(PageletCreatedEvent(self))
+ # load portlet config
+ portlet_id = int(request.params.get('form.widgets.portlet_id'))
+ portlet_config = IPortalPortletsConfiguration(self.context).get_portlet_configuration(portlet_id)
+ if portlet_config is None:
+ raise NotFound()
+ # check inheritance
+ old_override = portlet_config.inherit_parent
+ new_override = request.params.get('form.widgets.override_parent')
+ if new_override:
+ portlet_config.inherit_parent = False
+ else:
+ portlet_config.inherit_parent = True
+ changed_override = portlet_config.inherit_parent != old_override
+ # update settings
+ editor = request.registry.queryMultiAdapter((portlet_config.editor_settings, request),
+ IPagelet, name='properties.json')
+ if editor is None:
+ raise NotFound()
+ changes = editor()
+ translate = self.request.localizer.translate
+ if changed_override or changes:
+ # we commit before loading previewer to avoid BLOBs "uncommited changes" error
+ ITransactionManager(self.context).commit()
+ previewer = request.registry.queryMultiAdapter((self.context, request, self, portlet_config.settings),
+ IPortletPreviewer)
+ if previewer is not None:
+ previewer.update()
+ changes.update({'status': 'success',
+ 'message': translate(self.successMessage),
+ 'callback': 'PyAMS_portal.template.editPortletCallback',
+ 'options': {'portlet_id': portlet_id,
+ 'preview': previewer.render()}})
+ return changes
+
+
+@view_config(name='delete-template-portlet.json', context=IPortalTemplate, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+def delete_template_portlet(request):
+ """Delete template portlet"""
+ config = IPortalTemplateConfiguration(request.context)
+ config.delete_portlet(int(request.params.get('portlet_id')))
+ return {'status': 'success'}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_portal/zmi/page.py Mon Jan 18 18:09:46 2016 +0100
@@ -0,0 +1,178 @@
+#
+# 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.
+#
+from pyams_zmi.site import PropertiesEditFormHeaderAdapter
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_form.interfaces.form import IWidgetForm
+from pyams_portal.interfaces import IPortalContext, IPortalPage, IPortalTemplateConfiguration
+from pyams_portal.zmi.interfaces import IPortalContextTemplatePropertiesMenu
+from pyams_skin.interfaces import IPageHeader, IInnerPage
+from pyams_skin.layer import IPyAMSLayer
+from pyams_utils.interfaces import MANAGE_PERMISSION, VIEW_SYSTEM_PERMISSION
+from pyams_zmi.interfaces.menu import ISiteManagementMenu
+from pyams_zmi.layer import IAdminLayer
+
+# import packages
+from pyramid.view import view_config
+from pyams_form.form import AJAXEditForm
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_portal.zmi.layout import PortalTemplateLayoutView, PortalTemplatePortletEditForm, \
+ PortalTemplatePortletAJAXEditForm
+from pyams_portal.zmi.template import PortalTemplateHeaderAdapter
+from pyams_skin.viewlet.menu import MenuItem
+from pyams_utils.adapter import adapter_config
+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 AdminEditForm
+from z3c.form import field
+from zope.interface import implementer, Interface
+
+from pyams_portal import _
+
+
+#
+# Portal context template configuration
+#
+
+@viewlet_config(name='template-properties.menu', context=IPortalContext, layer=IAdminLayer,
+ manager=ISiteManagementMenu, permission=MANAGE_PERMISSION, weight=10)
+@viewletmanager_config(name='template-properties.menu', layer=IAdminLayer, context=IPortalContext,
+ provides=IPortalContextTemplatePropertiesMenu)
+@implementer(IPortalContextTemplatePropertiesMenu)
+class PortalContextTemplatePropertiesMenu(MenuItem):
+ """Portal context template properties menu"""
+
+ label = _("Presentation")
+ icon_class = 'fa-columns'
+ url = '#template-properties.html'
+
+
+@pagelet_config(name='template-properties.html', context=IPortalContext, layer=IPyAMSLayer,
+ permission=MANAGE_PERMISSION)
+@implementer(IWidgetForm, IInnerPage)
+class PortalContextTemplatePropertiesEditForm(AdminEditForm):
+ """Portal context template properties edit form"""
+
+ @property
+ def title(self):
+ return self.context.title
+
+ legend = _("Edit template configuration")
+
+ @property
+ def fields(self):
+ fields = field.Fields(IPortalPage).select('inherit_parent', 'use_local_template', 'shared_template')
+ if not self.getContent().can_inherit:
+ fields = fields.omit('inherit_parent')
+ return fields
+
+ ajax_handler = 'template-properties.json'
+ edit_permission = MANAGE_PERMISSION
+
+ def getContent(self):
+ return IPortalPage(self.context)
+
+
+@view_config(name='template-properties.json', context=IPortalContext, request_type=IPyAMSLayer,
+ permission=MANAGE_PERMISSION, renderer='json', xhr=True)
+class PortalContextTemplatePropertiesAJAXEditForm(AJAXEditForm, PortalContextTemplatePropertiesEditForm):
+ """Portal context template properties edit form, JSON renderer"""
+
+ def get_ajax_output(self, changes):
+ if 'use_local_template' in changes.get(IPortalPage, ()):
+ return {'status': 'redirect'}
+ else:
+ return super(PortalContextTemplatePropertiesAJAXEditForm, self).get_ajax_output(changes)
+
+
+@adapter_config(context=(Interface, IPyAMSLayer, PortalContextTemplatePropertiesEditForm), provides=IPageHeader)
+class PortalContextPropertiesEditFormHeaderAdapter(PropertiesEditFormHeaderAdapter):
+ """Portal context template properties edit form header adapter"""
+
+ icon_class = 'fa fa-fw fa-columns'
+
+
+#
+#
+#
+
+@viewlet_config(name='template-config.menu', context=IPortalContext, layer=IAdminLayer,
+ manager=IPortalContextTemplatePropertiesMenu, permission=MANAGE_PERMISSION, weight=50)
+class PortalContextTemplateConfigMenu(MenuItem):
+ """Portal context template configuration menu"""
+
+ label = _("Template properties")
+
+ url = '#template-config.html'
+
+ def __new__(cls, context, request, view, manager=None):
+ page = IPortalPage(context)
+ if page.template is None:
+ return None
+ return MenuItem.__new__(cls)
+
+ def get_url(self):
+ page = IPortalPage(self.context)
+ if page.use_local_template:
+ return absolute_url(page.template, self.request, 'admin.html#properties.html')
+ else:
+ return super(PortalContextTemplateConfigMenu, self).get_url()
+
+
+@pagelet_config(name='template-config.html', context=IPortalContext, layer=IPyAMSLayer,
+ permission=MANAGE_PERMISSION)
+class PortalContextTemplateLayoutView(PortalTemplateLayoutView):
+ """Portal context template configuration view"""
+
+ def get_template(self):
+ return IPortalPage(self.context).template
+
+ @property
+ def can_change(self):
+ if not IPortalPage(self.context).use_local_template:
+ return False
+ return self.request.has_permission(MANAGE_PERMISSION)
+
+
+@adapter_config(context=(IPortalContext, IAdminLayer, PortalContextTemplateLayoutView), provides=IPageHeader)
+class PortalContextTemplateLayoutHeaderAdapter(PortalTemplateHeaderAdapter):
+ """Portal context template configuration header adapter"""
+
+
+#
+# Template management views
+#
+
+@view_config(name='get-slots-width.json', context=IPortalContext, request_type=IPyAMSLayer,
+ permission=VIEW_SYSTEM_PERMISSION, renderer='json', xhr=True)
+def get_template_slots_width(request):
+ """Get template slots width"""
+ config = IPortalTemplateConfiguration(request.context)
+ return config.get_slots_width(request.params.get('device'))
+
+
+@view_config(name='portlet-properties.html', context=IPortalContext, request_type=IPyAMSLayer,
+ permission=MANAGE_PERMISSION)
+class PortalContextTemplatePortletEditForm(PortalTemplatePortletEditForm):
+ """Portal context template portlet edit form"""
+
+
+@view_config(name='portlet-properties.json', context=IPortalContext, request_type=IPyAMSLayer,
+ permission=MANAGE_PERMISSION, renderer='json', xhr=True)
+class PortalContextTemplatePortletAJAXEditForm(PortalTemplatePortletAJAXEditForm):
+ """Portal context template portlet edit form, JSON renderer"""
--- a/src/pyams_portal/zmi/portlet.py Thu Oct 08 12:26:42 2015 +0200
+++ b/src/pyams_portal/zmi/portlet.py Mon Jan 18 18:09:46 2016 +0100
@@ -16,48 +16,76 @@
# import standard library
# import interfaces
-from pyams_portal.interfaces import IPortlet
-from z3c.form.interfaces import HIDDEN_MODE
+from pyams_form.interfaces.form import IInnerTabForm
+from pyams_portal.interfaces import IPortlet, IPortalTemplate, IPortalPage, IPortalContext, MANAGE_TEMPLATE_PERMISSION
+from pyams_skin.layer import IPyAMSLayer
# import packages
-from pyams_zmi.form import AdminDialogEditForm
-from pyramid.url import resource_url
+from pyams_template.template import template_config
+from pyams_utils.adapter import adapter_config
+from pyams_utils.url import absolute_url
+from pyams_zmi.form import AdminDialogEditForm, InnerAdminEditForm
+from pyramid.decorator import reify
from z3c.form import field
+from zope.interface import Interface
from pyams_portal import _
-class PortletConfigurationEditor(AdminDialogEditForm):
- """Base portlet configuration editor"""
+@template_config(template='templates/portlet.pt', layer=IPyAMSLayer)
+class PortletSettingsEditor(AdminDialogEditForm):
+ """Portlet settings edit form"""
@property
def title(self):
translate = self.request.localizer.translate
- registry = self.request.registry
- portlet = registry.queryUtility(IPortlet, name=self.context.portlet_name)
- return translate(_("« {0} » portal template - {1}")).format(self.context.__parent__.name,
- translate(portlet.label))
+ parent = self.configuration.__parent__
+ if not IPortalTemplate.providedBy(parent):
+ parent = IPortalPage(parent).template
+ return translate(_("« {0} » portal template - {1}")).format(parent.name,
+ translate(self.portlet.label))
- legend = _("Edit portlet configuration")
+ legend = _("Edit portlet settings")
dialog_class = 'modal-large'
- interface = None
- edit_permission = 'portal.templates.manage'
+ settings = None
+ fields = field.Fields(Interface)
+ edit_permission = MANAGE_TEMPLATE_PERMISSION
+
+ @reify
+ def configuration(self):
+ return self.context.configuration
+
+ @property
+ def override_label(self):
+ translate = self.request.localizer.translate
+ if IPortalContext.providedBy(self.configuration.__parent__.__parent__):
+ return translate(_("Override parent settings"))
+ else:
+ return translate(_("Override template settings"))
def get_form_action(self):
- return resource_url(self.context.__parent__, self.request, self.request.view_name)
+ return absolute_url(self.configuration.__parent__, self.request, self.request.view_name)
def get_ajax_handler(self):
- return resource_url(self.context.__parent__, self.request, self.ajax_handler)
+ return absolute_url(self.configuration.__parent__, self.request, self.ajax_handler)
+
+ @reify
+ def portlet(self):
+ registry = self.request.registry
+ return registry.queryUtility(IPortlet, name=self.configuration.portlet_name)
+
+
+@adapter_config(name='properties', context=(Interface, IPyAMSLayer, PortletSettingsEditor), provides=IInnerTabForm)
+class PortletSettingsPropertiesEditor(InnerAdminEditForm):
+ """Portlet settings properties editor"""
+
+ id = 'properties_form'
+ tab_label = _("Main properties")
+ legend = None
@property
def fields(self):
- fields = field.Fields(self.interface)
- if not self.getContent().can_inherit:
- fields = fields.omit('inherit_parent')
- return fields
+ return field.Fields(self.parent_form.settings).omit('__name__')
- def updateWidgets(self, prefix=None):
- super(PortletConfigurationEditor, self).updateWidgets(prefix)
- self.widgets['slot_name'].mode = HIDDEN_MODE
- self.widgets['position'].mode = HIDDEN_MODE
+ weight = 10
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_portal/zmi/portlets/content.py Mon Jan 18 18:09:46 2016 +0100
@@ -0,0 +1,52 @@
+#
+# 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_pagelet.interfaces import IPagelet
+from pyams_portal.interfaces import IPortletPreviewer
+from pyams_portal.portlets.content.interfaces import IContentPortletSettings
+from pyams_skin.layer import IPyAMSLayer
+from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
+
+# import packages
+from pyams_form.form import AJAXEditForm
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_portal.portlet import PortletPreviewer
+from pyams_portal.zmi.portlet import PortletSettingsEditor
+from pyams_template.template import template_config
+from pyams_utils.adapter import adapter_config
+from zope.interface import Interface
+
+
+@pagelet_config(name='properties.html', context=IContentPortletSettings, request_type=IPyAMSLayer,
+ permission=VIEW_SYSTEM_PERMISSION)
+class ContentPortletSettingsEditor(PortletSettingsEditor):
+ """Content portlet settings editor"""
+
+ settings = IContentPortletSettings
+
+
+@adapter_config(name='properties.json', context=(IContentPortletSettings, IPyAMSLayer), provides=IPagelet)
+class ContentPortletConfigurationAJAXEditor(AJAXEditForm, ContentPortletSettingsEditor):
+ """Content portlet settings editor, AJAX renderer"""
+
+
+@adapter_config(context=(Interface, IPyAMSLayer, Interface, IContentPortletSettings),
+ provides=IPortletPreviewer)
+@template_config(template='templates/context-preview.pt', layer=IPyAMSLayer)
+class ContentPortletPreviewer(PortletPreviewer):
+ """Content portlet previewer"""
--- a/src/pyams_portal/zmi/portlets/context.py Thu Oct 08 12:26:42 2015 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +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_pagelet.interfaces import IPagelet
-from pyams_portal.interfaces import IPortletPreviewer
-from pyams_portal.portlets.context.interfaces import IContextPortletConfiguration
-from pyams_skin.layer import IPyAMSLayer
-from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
-
-# import packages
-from pyams_form.form import AJAXEditForm
-from pyams_pagelet.pagelet import pagelet_config
-from pyams_portal.portlet import PortletPreviewer
-from pyams_portal.zmi.portlet import PortletConfigurationEditor
-from pyams_template.template import template_config
-from pyams_utils.adapter import adapter_config
-from zope.interface import Interface
-
-
-@pagelet_config(name='properties.html', context=IContextPortletConfiguration, request_type=IPyAMSLayer,
- permission=VIEW_SYSTEM_PERMISSION)
-class ContextPortletConfigurationEditor(PortletConfigurationEditor):
- """Context portlet configuration editor"""
-
- interface = IContextPortletConfiguration
-
-
-@adapter_config(name='properties.json', context=(IContextPortletConfiguration, IPyAMSLayer), provides=IPagelet)
-class ContextPortletConfigurationAJAXEditor(AJAXEditForm, ContextPortletConfigurationEditor):
- """Context portlet configuration editor, AJAX renderer"""
-
-
-@adapter_config(context=(Interface, IPyAMSLayer, Interface, IContextPortletConfiguration),
- provides=IPortletPreviewer)
-@template_config(template='templates/context-preview.pt', layer=IPyAMSLayer)
-class ContextPortletPreviewer(PortletPreviewer):
- """Context portlet previewer"""
--- a/src/pyams_portal/zmi/portlets/image.py Thu Oct 08 12:26:42 2015 +0200
+++ b/src/pyams_portal/zmi/portlets/image.py Mon Jan 18 18:09:46 2016 +0100
@@ -18,7 +18,7 @@
# import interfaces
from pyams_pagelet.interfaces import IPagelet
from pyams_portal.interfaces import IPortletPreviewer
-from pyams_portal.portlets.image.interfaces import IImagePortletConfiguration
+from pyams_portal.portlets.image.interfaces import IImagePortletSettings
from pyams_skin.layer import IPyAMSLayer
from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
@@ -26,26 +26,26 @@
from pyams_form.form import AJAXEditForm
from pyams_pagelet.pagelet import pagelet_config
from pyams_portal.portlet import PortletPreviewer
-from pyams_portal.zmi.portlet import PortletConfigurationEditor
+from pyams_portal.zmi.portlet import PortletSettingsEditor
from pyams_template.template import template_config
from pyams_utils.adapter import adapter_config
from zope.interface import Interface
-@pagelet_config(name='properties.html', context=IImagePortletConfiguration, request_type=IPyAMSLayer,
+@pagelet_config(name='properties.html', context=IImagePortletSettings, request_type=IPyAMSLayer,
permission=VIEW_SYSTEM_PERMISSION)
-class ImagePortletConfigurationEditor(PortletConfigurationEditor):
- """Image portlet configuration editor"""
+class ImagePortletSettingsEditor(PortletSettingsEditor):
+ """Image portlet settings editor"""
- interface = IImagePortletConfiguration
+ settings = IImagePortletSettings
-@adapter_config(name='properties.json', context=(IImagePortletConfiguration, IPyAMSLayer), provides=IPagelet)
-class ImagePortletConfigurationAJAXEditor(AJAXEditForm, ImagePortletConfigurationEditor):
- """Image portlet configuration editor, AJAX renderer"""
+@adapter_config(name='properties.json', context=(IImagePortletSettings, IPyAMSLayer), provides=IPagelet)
+class ImagePortletConfigurationAJAXEditor(AJAXEditForm, ImagePortletSettingsEditor):
+ """Image portlet settings editor, AJAX renderer"""
-@adapter_config(context=(Interface, IPyAMSLayer, Interface, IImagePortletConfiguration),
+@adapter_config(context=(Interface, IPyAMSLayer, Interface, IImagePortletSettings),
provides=IPortletPreviewer)
@template_config(template='templates/image-preview.pt', layer=IPyAMSLayer)
class ImagePortletPreviewer(PortletPreviewer):
--- a/src/pyams_portal/zmi/portlets/templates/image-preview.pt Thu Oct 08 12:26:42 2015 +0200
+++ b/src/pyams_portal/zmi/portlets/templates/image-preview.pt Mon Jan 18 18:09:46 2016 +0100
@@ -1,18 +1,18 @@
-<tal:var define="config view.configuration">
- <tal:if condition="config.visible">
- <tal:if condition="config.image">
+<tal:var define="settings view.settings">
+ <tal:if condition="settings.visible">
+ <tal:if condition="settings.image">
<a class="fancybox margin-left-5" data-toggle
data-ams-fancybox-type="image"
- tal:define="image config.image;
+ tal:define="image settings.image;
thumbnails extension:thumbnails(image);
target python:thumbnails.get_thumbnail('800x600', 'jpeg');"
- tal:attributes="href extension:absolute_url(target);">
+ tal:attributes="href extension:absolute_url(target)">
<img class="thumbnail padding-5 margin-5"
- tal:define="thumbnail python:thumbnails.get_thumbnail('128x128', 'jpeg');"
+ tal:define="thumbnail python:thumbnails.get_thumbnail('128x128', 'jpeg')"
tal:attributes="src extension:absolute_url(thumbnail)" src="" alt="" />
</a>
</tal:if>
- <tal:if condition="not:config.image">
+ <tal:if condition="not settings.image">
<div class="text-center padding-y-5">
<span class="fa-stack fa-lg">
<i class="fa fa-picture-o fa-stack-1x"></i>
@@ -21,7 +21,7 @@
</div>
</tal:if>
</tal:if>
- <tal:if condition="not:config.visible">
+ <tal:if condition="not settings.visible">
<div class="text-center padding-y-5">
<span class="fa-stack fa-lg">
<i class="fa fa-eye fa-stack-1x"></i>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_portal/zmi/template.py Mon Jan 18 18:09:46 2016 +0100
@@ -0,0 +1,132 @@
+#
+# 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.
+#
+from pyams_utils.unicode import translate_string
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_portal.interfaces import IPortalTemplateContainer, IPortalTemplate, IPortalContext, \
+ MANAGE_TEMPLATE_PERMISSION
+from pyams_skin.interfaces import IPageHeader, IContentTitle
+from pyams_skin.interfaces.viewlet import IToolbarAddingMenu, IWidgetTitleViewletManager
+from pyams_skin.layer import IPyAMSLayer
+from pyams_zmi.layer import IAdminLayer
+from z3c.form.interfaces import IDataExtractedEvent
+from zope.component.interfaces import ISite
+
+# import packages
+from pyams_form.form import AJAXAddForm
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_portal.template import PortalTemplate
+from pyams_portal.zmi.container import PortalTemplateContainerTable
+from pyams_skin.container import delete_container_element
+from pyams_skin.page import DefaultPageHeaderAdapter
+from pyams_skin.viewlet.toolbar import ToolbarMenuItem, ToolbarAction
+from pyams_utils.adapter import adapter_config
+from pyams_utils.registry import query_utility
+from pyams_utils.traversing import get_parent
+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 pyramid.view import view_config
+from z3c.form import field
+from zope.interface import Interface, Invalid
+
+from pyams_portal import _
+
+
+@adapter_config(context=(IPortalTemplate, IPyAMSLayer, Interface), provides=IPageHeader)
+class PortalTemplateHeaderAdapter(DefaultPageHeaderAdapter):
+ """Portal template header adapter"""
+
+ icon_class = 'fa fa-fw fa-columns'
+
+ @property
+ def title(self):
+ translate = self.request.localizer.translate
+ # check for templates container
+ container = get_parent(self.context, IPortalTemplateContainer)
+ if container is not None:
+ return translate(_("« {0} » portal template")).format(self.context.name)
+ # check for portal context
+ context = get_parent(self.context, IPortalContext)
+ if context is not None:
+ adapter = self.request.registry.queryMultiAdapter((context, self.request, self.view), IContentTitle)
+ if adapter is None:
+ adapter = IContentTitle(context, None)
+ if adapter is not None:
+ return adapter.title
+
+
+#
+# Template views
+#
+
+@viewlet_config(name='add-portal-template.action', context=ISite, layer=IAdminLayer,
+ view=PortalTemplateContainerTable, manager=IWidgetTitleViewletManager,
+ permission=MANAGE_TEMPLATE_PERMISSION, weight=1)
+class PortalTemplateAddAction(ToolbarAction):
+ """Portal template add action"""
+
+ label = _("Add template")
+ url = 'add-portal-template.html'
+ modal_target = True
+
+
+@pagelet_config(name='add-portal-template.html', context=ISite, layer=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION)
+class PortalTemplateAddForm(AdminDialogAddForm):
+ """Portal template add form"""
+
+ title = _("Portal templates")
+ legend = _("Add shared template")
+ icon_css_class = 'fa fa-fw fa-columns'
+
+ fields = field.Fields(IPortalTemplate)
+ ajax_handler = 'add-portal-template.json'
+ edit_permission = None
+
+ def create(self, data):
+ return PortalTemplate()
+
+ def add(self, template):
+ context = query_utility(IPortalTemplateContainer)
+ context[translate_string(template.name, spaces='-')] = template
+
+ def nextURL(self):
+ return absolute_url(self.context, self.request, 'portal-templates.html')
+
+
+@subscriber(IDataExtractedEvent, form_selector=PortalTemplateAddForm)
+def handle_new_template_data_extraction(event):
+ """Handle new template form data extraction"""
+ container = query_utility(IPortalTemplateContainer)
+ name = event.data.get('name')
+ if name in container:
+ event.form.widgets.errors += (Invalid(_("Specified name is already used!")),)
+
+
+@view_config(name='add-portal-template.json', context=ISite, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+class PortalTemplateAJAXAddForm(AJAXAddForm, PortalTemplateAddForm):
+ """Portal template add form, AJAX handler"""
+
+
+@view_config(name='delete-element.json', context=IPortalTemplateContainer, request_type=IPyAMSLayer,
+ permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+def delete_portal_template(request):
+ """Delete template from portal"""
+ return delete_container_element(request)
--- a/src/pyams_portal/zmi/template/__init__.py Thu Oct 08 12:26:42 2015 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,122 +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.
-#
-from pyams_skin.interfaces import IContentTitle
-from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter
-
-__docformat__ = 'restructuredtext'
-
-
-# import standard library
-
-# import interfaces
-from pyams_portal.interfaces import IPortalTemplateContainer, IPortalTemplate
-from pyams_skin.interfaces.viewlet import IToolbarAddingMenu
-from pyams_skin.layer import IPyAMSLayer
-from pyams_workflow.interfaces import IWorkflowVersions, IWorkflowInfo
-from pyams_zmi.layer import IAdminLayer
-from z3c.form.interfaces import IDataExtractedEvent
-from zope.component.interfaces import ISite
-
-# import packages
-from pyams_form.form import AJAXAddForm
-from pyams_pagelet.pagelet import pagelet_config
-from pyams_portal.template import PortalTemplate, PortalWfTemplate
-from pyams_portal.zmi.container import PortalTemplateContainerTable
-from pyams_skin.container import delete_container_element
-from pyams_skin.viewlet.toolbar import ToolbarMenuItem
-from pyams_utils.registry import query_utility
-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 pyramid.view import view_config
-from z3c.form import field
-from zope.interface import Interface, Invalid
-from zope.lifecycleevent import ObjectCreatedEvent
-
-from pyams_portal import _
-
-
-@adapter_config(context=(IPortalTemplate, IPyAMSLayer, Interface), provides=IContentTitle)
-class PortalTemplateTitleAdapter(ContextRequestViewAdapter):
- """Portal template title adapter"""
-
- @property
- def title(self):
- translate = self.request.localizer.translate
- return translate(_("« {0} » portal template")).format(self.context.name)
-
-
-#
-# Template views
-#
-
-@viewlet_config(name='add-portal-template.menu', context=ISite, layer=IAdminLayer,
- view=PortalTemplateContainerTable, manager=IToolbarAddingMenu,
- permission='portal.templates.manage', weight=20)
-class PortalTemplateAddMenu(ToolbarMenuItem):
- """Portal template add menu"""
-
- label = _("Add shared template...")
- label_css_class = 'fa fa-fw fa-columns'
- url = 'add-portal-template.html'
- modal_target = True
-
-
-@pagelet_config(name='add-portal-template.html', context=ISite, layer=IPyAMSLayer,
- permission='portal.templates.manage')
-class PortalTemplateAddForm(AdminDialogAddForm):
- """Portal template add form"""
-
- title = _("Portal templates")
- legend = _("Add shared template")
- icon_css_class = 'fa fa-fw fa-columns'
-
- fields = field.Fields(IPortalTemplate)
- ajax_handler = 'add-portal-template.json'
- edit_permission = None
-
- def create(self, data):
- return PortalTemplate()
-
- def add(self, template):
- wf_template = PortalWfTemplate()
- self.request.registry.notify(ObjectCreatedEvent(wf_template))
- context = query_utility(IPortalTemplateContainer)
- context[template.name] = wf_template
- IWorkflowVersions(wf_template).add_version(template, None)
- IWorkflowInfo(template).fire_transition('init')
-
- def nextURL(self):
- return absolute_url(self.context, self.request, 'portal-templates.html')
-
-
-@subscriber(IDataExtractedEvent, form_selector=PortalTemplateAddForm)
-def handle_new_template_data_extraction(event):
- """Handle new template form data extraction"""
- container = query_utility(IPortalTemplateContainer)
- name = event.data.get('name')
- if name in container:
- event.form.widgets.errors += (Invalid(_("Specified name is already used!")),)
-
-
-@view_config(name='add-portal-template.json', context=ISite, request_type=IPyAMSLayer,
- permission='portal.templates.manage', renderer='json', xhr=True)
-class PortalTemplateAJAXAddForm(AJAXAddForm, PortalTemplateAddForm):
- """Portal template add form, AJAX handler"""
-
-
-@view_config(name='delete-element.json', context=IPortalTemplateContainer, request_type=IPyAMSLayer,
- permission='portal.templates.manage', renderer='json', xhr=True)
-def delete_portal_template(request):
- """Delete template from portal"""
- return delete_container_element(request)
--- a/src/pyams_portal/zmi/template/config.py Thu Oct 08 12:26:42 2015 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,485 +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 json
-
-# import interfaces
-from pyams_pagelet.interfaces import IPagelet, PageletCreatedEvent
-from pyams_portal.interfaces import IPortalTemplate, IPortalTemplateConfiguration, ISlot, \
- IPortletAddingInfo, IPortlet, ISlotConfiguration, IPortletPreviewer, IPortalTemplateContainer, \
- IPortalTemplateContainerConfiguration
-from pyams_skin.interfaces import IInnerPage, IPageHeader
-from pyams_skin.interfaces.viewlet import IToolbarAddingMenu
-from pyams_skin.layer import IPyAMSLayer
-from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
-from pyams_workflow.interfaces import IWorkflowState
-from pyams_zmi.interfaces.menu import ISiteManagementMenu, IPropertiesMenu
-from pyams_zmi.layer import IAdminLayer
-from transaction.interfaces import ITransactionManager
-from z3c.form.interfaces import IDataExtractedEvent, HIDDEN_MODE
-
-# import packages
-from pyams_form.form import AJAXAddForm, AJAXEditForm
-from pyams_pagelet.pagelet import pagelet_config
-from pyams_portal.workflow import PUBLISHED, ARCHIVED
-from pyams_skin.page import DefaultPageHeaderAdapter
-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 query_utility
-from pyams_viewlet.manager import viewletmanager_config
-from pyams_viewlet.viewlet import viewlet_config
-from pyams_zmi.form import AdminDialogAddForm, AdminDialogEditForm
-from pyams_zmi.view import AdminView
-from pyramid.decorator import reify
-from pyramid.events import subscriber
-from pyramid.exceptions import NotFound
-from pyramid.view import view_config
-from z3c.form import field
-from zope.interface import implementer, Invalid
-
-from pyams_portal import _
-
-
-@viewlet_config(name='template-properties.menu', context=IPortalTemplate, layer=IAdminLayer,
- manager=ISiteManagementMenu, permission=VIEW_SYSTEM_PERMISSION, weight=1)
-@viewletmanager_config(name='template-properties.menu', layer=IAdminLayer, provides=IPropertiesMenu)
-@implementer(IPropertiesMenu)
-class PortalTemplatePropertiesMenu(MenuItem):
- """Portal template properties menu"""
-
- label = _("Properties")
- icon_class = 'fa-twitch'
- url = '#properties.html'
-
-
-@pagelet_config(name='properties.html', context=IPortalTemplate, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
-@template_config(template='templates/config.pt', layer=IAdminLayer)
-@implementer(IInnerPage)
-class PortalTemplateConfigView(AdminView):
- """Portal template configuration view"""
-
- title = _("Shared portal template configuration")
-
- def get_context(self):
- return self.context
-
- @property
- def can_change(self):
- return self.request.has_permission('portal.templates.manage') and \
- IWorkflowState(self.get_context()).state not in (PUBLISHED, ARCHIVED)
-
- @reify
- def configuration(self):
- return IPortalTemplateConfiguration(self.get_context())
-
- @property
- def selected_portlets(self):
- container = query_utility(IPortalTemplateContainer)
- configuration = IPortalTemplateContainerConfiguration(container)
- return [query_utility(IPortlet, name=portlet_name) for portlet_name in configuration.selected_portlets or ()]
-
- def get_portlet(self, name):
- return self.request.registry.getUtility(IPortlet, name=name)
-
- def get_portlet_label(self, name):
- return self.request.localizer.translate(self.get_portlet(name).label)
-
- def get_portlet_preview(self, slot_name, position):
- portlet_config = self.configuration.get_portlet_configuration(slot_name, position)
- previewer = self.request.registry.queryMultiAdapter((self.get_context(), self.request, self, portlet_config),
- IPortletPreviewer)
- if previewer is not None:
- previewer.update()
- return previewer.render()
- else:
- return ''
-
-
-@adapter_config(context=(IPortalTemplate, IAdminLayer, PortalTemplateConfigView), provides=IPageHeader)
-class PortalTemplateConfigHeaderAdapter(DefaultPageHeaderAdapter):
- """Portal template configuration header adapter"""
-
- back_url = '/admin.html#portal-templates.html'
- back_target = None
-
- icon_class = 'fa fa-fw fa-columns'
- subtitle = _("Portlets configuration")
-
-
-#
-# Rows views
-#
-
-@viewlet_config(name='add-template-row.menu', context=IPortalTemplate, layer=IAdminLayer,
- view=PortalTemplateConfigView, manager=IToolbarAddingMenu,
- permission='portal.templates.manage', weight=1)
-class PortalTemplateRowAddMenu(JsToolbarMenuItem):
- """Portal template row add menu"""
-
- label = _("Add row...")
- label_css_class = 'fa fa-fw fa-indent'
- url = 'PyAMS_portal.template.addRow'
-
-
-@view_config(name='add-template-row.json', context=IPortalTemplate, request_type=IPyAMSLayer,
- permission='portal.templates.manage', renderer='json', xhr=True)
-def add_template_row(request):
- """Add template raw"""
- config = IPortalTemplateConfiguration(request.context)
- return {'row_id': config.add_row()}
-
-
-@view_config(name='set-template-row-order.json', context=IPortalTemplate, request_type=IPyAMSLayer,
- permission='portal.templates.manage', renderer='json', xhr=True)
-def set_template_row_order(request):
- """Set template rows order"""
- config = IPortalTemplateConfiguration(request.context)
- row_ids = map(int, json.loads(request.params.get('rows')))
- config.set_row_order(row_ids)
- return {'status': 'success'}
-
-
-@view_config(name='delete-template-row.json', context=IPortalTemplate, request_type=IPyAMSLayer,
- permission='portal.templates.manage', renderer='json', xhr=True)
-def delete_template_row(request):
- """Delete template row"""
- config = IPortalTemplateConfiguration(request.context)
- config.delete_row(int(request.params.get('row_id')))
- return {'status': 'success'}
-
-
-#
-# Slots views
-#
-
-@viewlet_config(name='add-template-slot.menu', context=IPortalTemplate, layer=IAdminLayer,
- view=PortalTemplateConfigView, manager=IToolbarAddingMenu,
- permission='portal.templates.manage', weight=2)
-class PortalTemplateSlotAddMenu(ToolbarMenuItem):
- """Portal template slot add menu"""
-
- label = _("Add slot...")
- label_css_class = 'fa fa-fw fa-columns'
- url = 'add-template-slot.html'
- modal_target = True
-
-
-@pagelet_config(name='add-template-slot.html', context=IPortalTemplate, layer=IPyAMSLayer,
- permission='portal.templates.manage')
-class PortalTemplateSlotAddForm(AdminDialogAddForm):
- """Portal template slot add form"""
-
- @property
- def title(self):
- translate = self.request.localizer.translate
- return translate(_("« {0} » portal template")).format(self.context.name)
-
- legend = _("Add slot")
- icon_css_class = 'fa fa-fw fa-columns'
-
- fields = field.Fields(ISlot)
- ajax_handler = 'add-template-slot.json'
- edit_permission = None
-
- def updateWidgets(self, prefix=None):
- super(PortalTemplateSlotAddForm, self).updateWidgets()
- self.widgets['row_id'].value = self.request.params.get('form.widgets.row_id')
-
- def createAndAdd(self, data):
- config = IPortalTemplateConfiguration(self.context)
- return config.add_slot(data.get('name'), data.get('row_id'))
-
-
-@subscriber(IDataExtractedEvent, form_selector=PortalTemplateSlotAddForm)
-def handle_new_slot_data_extraction(event):
- """Handle new slot form data extraction"""
- config = IPortalTemplateConfiguration(event.form.context)
- name = event.data.get('name')
- if name in config.slot_names:
- event.form.widgets.errors += (Invalid(_("Specified name is already used!")),)
-
-
-@view_config(name='add-template-slot.json', context=IPortalTemplate, request_type=IPyAMSLayer,
- permission='portal.templates.manage', renderer='json', xhr=True)
-class PortalTemplateSlotAJAXAddForm(AJAXAddForm, PortalTemplateSlotAddForm):
- """Portal template slot add form, AJAX handler"""
-
- def get_ajax_output(self, changes):
- return {'status': 'callback',
- 'callback': 'PyAMS_portal.template.addSlotCallback',
- 'options': {'row_id': changes[0],
- 'slot_name': changes[1]}}
-
-
-@view_config(name='set-template-slot-order.json', context=IPortalTemplate, request_type=IPyAMSLayer,
- permission='portal.templates.manage', renderer='json', xhr=True)
-def set_template_slot_order(request):
- """Set template slots order"""
- config = IPortalTemplateConfiguration(request.context)
- order = json.loads(request.params.get('order'))
- for key in order.copy().keys():
- order[int(key)] = order.pop(key)
- config.set_slot_order(order)
- return {'status': 'success'}
-
-
-@view_config(name='get-slots-width.json', context=IPortalTemplate, request_type=IPyAMSLayer,
- permission=VIEW_SYSTEM_PERMISSION, renderer='json', xhr=True)
-def get_template_slots_width(request):
- """Get template slots width"""
- config = IPortalTemplateConfiguration(request.context)
- return config.get_slots_width(request.params.get('device'))
-
-
-@view_config(name='set-slot-width.json', context=IPortalTemplate, request_type=IPyAMSLayer,
- permission='portal.templates.manage', renderer='json', xhr=True)
-def set_template_slot_width(request):
- """Set template slot width"""
- config = IPortalTemplateConfiguration(request.context)
- config.set_slot_width(request.params.get('slot_name'),
- request.params.get('device'),
- int(request.params.get('width')))
- return config.get_slots_width(request.params.get('device'))
-
-
-@pagelet_config(name='slot-properties.html', context=IPortalTemplate, layer=IPyAMSLayer,
- permission=VIEW_SYSTEM_PERMISSION)
-class PortalTemplateSlotPropertiesEditForm(AdminDialogEditForm):
- """Slot properties edit form"""
-
- @property
- def title(self):
- translate = self.request.localizer.translate
- return translate(_("« {0} » portal template - {1} slot")).format(self.context.name,
- self.getContent().slot_name)
-
- legend = _("Edit slot properties")
-
- label_css_class = 'control-label col-md-5'
- input_css_class = 'col-md-7'
-
- @property
- def fields(self):
- fields = field.Fields(ISlotConfiguration)
- if not self.getContent().can_inherit:
- fields = fields.omit('inherit_parent')
- return fields
-
- ajax_handler = 'slot-properties.json'
- edit_permission = 'portal.templates.manage'
-
- def __init__(self, context, request):
- super(PortalTemplateSlotPropertiesEditForm, self).__init__(context, request)
- self.config = IPortalTemplateConfiguration(context)
-
- def getContent(self):
- slot_name = self.request.params.get('form.widgets.slot_name')
- return self.config.slot_config[slot_name]
-
- def updateWidgets(self, prefix=None):
- super(PortalTemplateSlotPropertiesEditForm, self).updateWidgets(prefix)
- self.widgets['slot_name'].mode = HIDDEN_MODE
-
-
-@view_config(name='slot-properties.json', context=IPortalTemplate, request_type=IPyAMSLayer,
- permission='portal.templates.manage', renderer='json', xhr=True)
-class PortalTemplateSlotPropertiesAJAXEditForm(AJAXEditForm, PortalTemplateSlotPropertiesEditForm):
- """Slot properties edit form, AJAX renderer"""
-
- def get_ajax_output(self, changes):
- if changes:
- slot_name = self.widgets['slot_name'].value
- slot_config = self.config.slot_config[slot_name]
- return {'status': 'success',
- 'callback': 'PyAMS_portal.template.editSlotCallback',
- 'options': {'slot_name': slot_name,
- 'width': slot_config.get_width()}}
- else:
- return super(PortalTemplateSlotPropertiesAJAXEditForm, self).get_ajax_output(changes)
-
-
-@view_config(name='delete-template-slot.json', context=IPortalTemplate, request_type=IPyAMSLayer,
- permission='portal.templates.manage', renderer='json', xhr=True)
-def delete_template_slot(request):
- """Delete template slot"""
- config = IPortalTemplateConfiguration(request.context)
- config.delete_slot(request.params.get('slot_name'))
- return {'status': 'success'}
-
-
-#
-# Portlet views
-#
-
-@viewlet_config(name='add-template-portlet.divider', context=IPortalTemplate, layer=IAdminLayer,
- view=PortalTemplateConfigView, manager=IToolbarAddingMenu,
- permission='portal.templates.manage', weight=10)
-class PortalTemplateAddMenuDivider(ToolbarMenuDivider):
- """Portal template menu divider"""
-
-
-@viewlet_config(name='add-template-portlet.menu', context=IPortalTemplate, layer=IAdminLayer,
- view=PortalTemplateConfigView, manager=IToolbarAddingMenu,
- permission='portal.templates.manage', weight=20)
-class PortalTemplatePortletAddMenu(ToolbarMenuItem):
- """Portal template portlet add menu"""
-
- label = _("Add portlet...")
- label_css_class = 'fa fa-fw fa-columns'
- url = 'add-template-portlet.html'
- modal_target = True
-
-
-@pagelet_config(name='add-template-portlet.html', context=IPortalTemplate, layer=IPyAMSLayer,
- permission='portal.templates.manage')
-class PortalTemplatePortletAddForm(AdminDialogAddForm):
- """Portal template portlet add form"""
-
- @property
- def title(self):
- translate = self.request.localizer.translate
- return translate(_("« {0} » portal template")).format(self.context.name)
-
- legend = _("Add portlet")
- icon_css_class = 'fa fa-fw fa-columns'
-
- fields = field.Fields(IPortletAddingInfo)
- ajax_handler = 'add-template-portlet.json'
- edit_permission = None
-
- def createAndAdd(self, data):
- config = IPortalTemplateConfiguration(self.context)
- return config.add_portlet(data.get('portlet_name'), data.get('slot_name'))
-
-
-@view_config(name='add-template-portlet.json', context=IPortalTemplate, request_type=IPyAMSLayer,
- permission='portal.templates.manage', renderer='json', xhr=True)
-class PortalTemplatePortletAJAXAddForm(AJAXAddForm, PortalTemplatePortletAddForm):
- """Portal template portlet add form, AJAX handler"""
-
- def get_ajax_output(self, changes):
- config = IPortalTemplateConfiguration(self.context)
- portlet_config = config.get_portlet_configuration(changes['slot_name'], changes['position'])
- previewer = self.request.registry.queryMultiAdapter((self.context, self.request, self, portlet_config),
- IPortletPreviewer)
- if previewer is not None:
- previewer.update()
- changes['preview'] = previewer.render()
- return {'status': 'callback',
- 'callback': 'PyAMS_portal.template.addPortletCallback',
- 'options': changes}
-
-
-@view_config(name='drag-template-portlet.json', context=IPortalTemplate, request_type=IPyAMSLayer,
- permission='portal.templates.manage', renderer='json', xhr=True)
-def drag_template_portlet(request):
- """Drag portlet icon to slot"""
- config = IPortalTemplateConfiguration(request.context)
- portlet_name = request.params.get('portlet_name')
- slot_name = request.params.get('slot_name')
- changes = config.add_portlet(portlet_name, slot_name)
- portlet_config = config.get_portlet_configuration(changes['slot_name'], changes['position'])
- previewer = request.registry.queryMultiAdapter((request.context, request, request, portlet_config),
- IPortletPreviewer)
- if previewer is not None:
- previewer.update()
- changes['preview'] = previewer.render()
- return {'status': 'callback',
- 'close_form': False,
- 'callback': 'PyAMS_portal.template.addPortletCallback',
- 'options': changes}
-
-
-@view_config(name='set-template-portlet-order.json', context=IPortalTemplate, request_type=IPyAMSLayer,
- permission='portal.templates.manage', renderer='json', xhr=True)
-def set_template_portlet_order(request):
- """Set template portlet order"""
- config = IPortalTemplateConfiguration(request.context)
- order = json.loads(request.params.get('order'))
- order['from']['position'] = int(order['from']['position'])
- order['to']['positions'] = list(map(int, order['to']['positions']))
- config.set_portlet_order(order)
- return {'status': 'success'}
-
-
-@view_config(name='portlet-properties.html', context=IPortalTemplate, request_type=IPyAMSLayer,
- permission=VIEW_SYSTEM_PERMISSION)
-class PortalTemplatePortletEditForm(AdminDialogEditForm):
- """Portal template portlet edit form"""
-
- dialog_class = 'modal-large'
-
- def __call__(self):
- request = self.request
- request.registry.notify(PageletCreatedEvent(self))
- slot_name = request.params.get('form.widgets.slot_name')
- position = int(request.params.get('form.widgets.position'))
- config = IPortalTemplateConfiguration(self.context)
- portlet_config = config.get_portlet_configuration(slot_name, position)
- if portlet_config is None:
- raise NotFound()
- editor = self.request.registry.queryMultiAdapter((portlet_config, request),
- IPagelet, name='properties.html')
- if editor is None:
- raise NotFound()
- editor.ajax_handler = 'portlet-properties.json'
- editor.update()
- return editor()
-
-
-@view_config(name='portlet-properties.json', context=IPortalTemplate, request_type=IPyAMSLayer,
- permission='portal.templates.manage', renderer='json', xhr=True)
-class PortalTemplatePortletAJAXEditForm(AJAXEditForm, PortalTemplatePortletEditForm):
- """Portal template portlet edit form, AJAX renderer"""
-
- def __call__(self):
- request = self.request
- request.registry.notify(PageletCreatedEvent(self))
- slot_name = request.params.get('form.widgets.slot_name')
- position = int(request.params.get('form.widgets.position'))
- config = IPortalTemplateConfiguration(self.context)
- portlet_config = config.get_portlet_configuration(slot_name, position)
- if portlet_config is None:
- raise NotFound()
- editor = request.registry.queryMultiAdapter((portlet_config, request),
- IPagelet, name='properties.json')
- if editor is None:
- raise NotFound()
- changes = editor()
- if changes:
- # we commit before loading previewer to avoid BLOBs "uncommited changes" error
- ITransactionManager(self.context).commit()
- previewer = request.registry.queryMultiAdapter((self.context, request, self, portlet_config),
- IPortletPreviewer)
- if previewer is not None:
- previewer.update()
- changes.update({'status': 'callback',
- 'callback': 'PyAMS_portal.template.editPortletCallback',
- 'options': {'slot_name': slot_name,
- 'position': position,
- 'preview': previewer.render()}})
- return changes
-
-
-@view_config(name='delete-template-portlet.json', context=IPortalTemplate, request_type=IPyAMSLayer,
- permission='portal.templates.manage', renderer='json', xhr=True)
-def delete_template_portlet(request):
- """Delete template portlet"""
- config = IPortalTemplateConfiguration(request.context)
- config.delete_portlet(request.params.get('slot_name'), int(request.params.get('position')))
- return {'status': 'success'}
--- a/src/pyams_portal/zmi/template/page.py Thu Oct 08 12:26:42 2015 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,153 +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_pagelet.interfaces import PageletCreatedEvent, IPagelet
-from pyams_portal.interfaces import IPortalContext, IPortalPage, IPortalTemplateConfiguration
-from pyams_skin.layer import IPyAMSLayer
-from pyams_utils.interfaces import MANAGE_PERMISSION
-from pyams_workflow.interfaces import IWorkflowVersions, IWorkflowState
-from pyams_zmi.interfaces.menu import ISiteManagementMenu, IPropertiesMenu
-from pyams_zmi.layer import IAdminLayer
-
-# import packages
-from pyramid.exceptions import NotFound
-from pyramid.view import view_config
-from pyams_form.form import AJAXEditForm
-from pyams_pagelet.pagelet import pagelet_config
-from pyams_portal.workflow import PUBLISHED, ARCHIVED
-from pyams_portal.zmi.template.config import PortalTemplateConfigView
-from pyams_skin.viewlet.menu import MenuItem
-from pyams_utils.url import absolute_url
-from pyams_viewlet.viewlet import viewlet_config
-from pyams_zmi.form import AdminDialogEditForm
-from z3c.form import field
-
-from pyams_portal import _
-
-
-
-@viewlet_config(name='template-properties.menu', context=IPortalContext, layer=IAdminLayer,
- manager=IPropertiesMenu, permission=MANAGE_PERMISSION, weight=5)
-class PortalContextTemplatePropertiesMenu(MenuItem):
- """Portal context template properties menu"""
-
- label = _("Presentation template...")
- icon_class = 'fa-columns'
-
- url = 'template-properties.html'
- modal_target = True
-
-
-@pagelet_config(name='template-properties.html', context=IPortalContext, layer=IPyAMSLayer,
- permission=MANAGE_PERMISSION)
-class PortalContextTemplatePropertiesEditForm(AdminDialogEditForm):
- """Portal context template properties edit form"""
-
- @property
- def title(self):
- return self.context.title
-
- legend = _("Edit template configuration")
-
- @property
- def fields(self):
- fields = field.Fields(IPortalPage).select('inherit_parent', 'use_local_template', 'shared_template')
- if not self.getContent().can_inherit:
- fields = fields.omit('inherit_parent')
- return fields
-
- ajax_handler = 'template-properties.json'
- edit_permission = MANAGE_PERMISSION
-
- def getContent(self):
- return IPortalPage(self.context)
-
-
-@view_config(name='template-properties.json', context=IPortalContext, request_type=IPyAMSLayer,
- permission=MANAGE_PERMISSION, renderer='json', xhr=True)
-class PortalContextTemplatePropertiesAJAXEditForm(AJAXEditForm, PortalContextTemplatePropertiesEditForm):
- """Portal context template properties edit form, JSON renderer"""
-
-
-@viewlet_config(name='template-config.menu', context=IPortalContext, layer=IAdminLayer,
- manager=ISiteManagementMenu, permission=MANAGE_PERMISSION, weight=20)
-class PortalContextTemplateConfigMenu(MenuItem):
- """Portal context template configuration menu"""
-
- label = _("Template properties")
- icon_class = 'fa-columns'
-
- url = '#template-config.html'
-
- def __new__(cls, context, request, view, manager=None):
- page = IPortalPage(context)
- if page.template is None:
- return None
- return MenuItem.__new__(cls)
-
- def get_url(self):
- page = IPortalPage(self.context)
- if page.use_local_template:
- template = IWorkflowVersions(page.template).get_last_versions()[0]
- return absolute_url(template, self.request, 'admin.html#properties.html')
- else:
- return super(PortalContextTemplateConfigMenu, self).get_url()
-
-
-@pagelet_config(name='template-config.html', context=IPortalContext, layer=IPyAMSLayer,
- permission=MANAGE_PERMISSION)
-class PortalContextTemplateConfigView(PortalTemplateConfigView):
- """Portal context template configuration view"""
-
- title = _("Local portal template configuration")
-
- def get_context(self):
- template = IPortalPage(self.context).template
- return IWorkflowVersions(template).get_last_versions()[0]
-
- @property
- def can_change(self):
- if not IPortalPage(self.context).use_local_template:
- return False
- return self.request.has_permission(MANAGE_PERMISSION) and \
- IWorkflowState(self.get_context()).state not in (PUBLISHED, ARCHIVED)
-
-
-@view_config(name='portlet-properties.html', context=IPortalContext, request_type=IPyAMSLayer,
- permission=MANAGE_PERMISSION)
-class PortalContextTemplatePortletEditForm(AdminDialogEditForm):
- """Portal context template portlet edit form"""
-
- dialog_class = 'modal-large'
-
- def __call__(self):
- request = self.request
- request.registry.notify(PageletCreatedEvent(self))
- slot_name = request.params.get('form.widgets.slot_name')
- position = int(request.params.get('form.widgets.position'))
- config = IPortalTemplateConfiguration(self.context)
- portlet_config = config.get_portlet_configuration(slot_name, position)
- if portlet_config is None:
- raise NotFound()
- editor = self.request.registry.queryMultiAdapter((portlet_config, request),
- IPagelet, name='properties.html')
- if editor is None:
- raise NotFound()
- editor.ajax_handler = 'portlet-properties.json'
- editor.update()
- return editor()
--- a/src/pyams_portal/zmi/template/templates/config.pt Thu Oct 08 12:26:42 2015 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,160 +0,0 @@
-<tal:var define="config view.configuration" i18n:domain="pyams_portal">
- <div class="ams-widget"
- data-ams-plugins="pyams_portal"
- data-ams-plugin-pyams_portal-src="/--static--/pyams_portal/js/portal{MyAMS.devext}.js"
- data-ams-plugin-pyams_portal-css="/--static--/pyams_portal/css/portal{MyAMS.devext}.css"
- data-ams-plugin-pyams_portal-callback="PyAMS_portal.template.initConfig">
- <header>
- <span tal:condition="view.widget_icon_class | nothing"
- class="widget-icon"><i tal:attributes="class view.widget_icon_class"></i>
- </span>
- <h2 tal:content="view.title">Title</h2>
- <tal:var content="structure provider:pyams.widget_title" />
- <tal:var content="structure provider:pyams.toolbar" />
- </header>
- <div class="widget-body" tal:define="can_change view.can_change">
- <div class="btn-toolbar" role="toolbar"
- tal:condition="can_change">
- <div class="btn-group" role="group">
- <div class="btn btn-default btn-row hint" title="Add row" i18n:attributes="title"
- data-ams-hint-gravity="n">
- <i class="fa fa-fw fa-2x fa-indent"></i>
- </div>
- <div class="btn btn-default btn-slot hint" title="Add slot" i18n:attributes="title"
- data-ams-hint-gravity="n">
- <i class="fa fa-fw fa-2x fa-columns"></i>
- </div>
- </div>
- <div class="btn-group" role="group">
- <div tal:repeat="portlet view.selected_portlets"
- class="btn btn-default btn-portlet hint"
- data-ams-hint-gravity="n"
- tal:attributes="data-ams-portlet-name portlet.name;
- title portlet.label;">
- <img tal:condition="portlet.toolbar_image"
- tal:attributes="src portlet.toolbar_image" />
- <i tal:condition="portlet.toolbar_css_class"
- tal:attributes="class portlet.toolbar_css_class"></i>
- </div>
- </div>
- <div class="btn-group" role="group">
- <div class="btn btn-default hint" data-ams-url="add-template-portlet.html" data-toggle="modal"
- data-ams-hint-gravity="n"
- title="Add another portlet..." i18n:attributes="title">
- <i class="fa fa-fw fa-2x fa-plus"></i>
- </div>
- </div>
- </div>
- <div class="clearfix">
- <div class="ams-form form-horizontal margin-bottom-10">
- <label class="control-label col-md-6 padding-right-5" i18n:translate="">Selected display:</label>
- <div class="col-md-5">
- <select id="device_selector" class="select2"
- data-ams-select2-width="300px"
- data-ams-change-handler="PyAMS_portal.template.selectDisplay">
- <option value="" selected i18n:translate="">Current device</option>
- <option value="xs" i18n:translate="">Extra small device (phone)</option>
- <option value="sm" i18n:translate="">Small device (tablet)</option>
- <option value="md" i18n:translate="">Medium desktop device (> 970px)</option>
- <option value="lg" i18n:translate="">Large desktop device (> 1170px)</option>
- </select>
- </div>
- </div>
- </div>
- <div id="portal_config" class="container"
- tal:attributes="data-ams-allowed-change can_change">
- <div class="rows"
- data-ams-sortable-placeholder="row-highlight"
- data-ams-sortable-items="> .row"
- data-ams-sortable-over="PyAMS_portal.template.overRows"
- data-ams-sortable-stop="PyAMS_portal.template.sortRows">
- <div class="row context-menu"
- data-ams-contextmenu-selector="#rowMenu"
- tal:repeat="row range(config.rows)"
- tal:attributes="data-ams-row-id row;">
- <span class="row_id label label-success pull-right"
- tal:content="row"></span>
- <div class="slots"
- data-ams-sortable-placeholder="slot-highlight"
- data-ams-sortable-connectwith=".slots"
- data-ams-sortable-over="PyAMS_portal.template.overSlots"
- data-ams-sortable-stop="PyAMS_portal.template.sortSlots">
- <div class="slot context-menu col col-md-12 no-padding"
- data-ams-contextmenu-selector="#slotMenu"
- data-ams-resizable-start="PyAMS_portal.template.startSlotResize"
- data-ams-resizable-stop="PyAMS_portal.template.stopSlotResize"
- data-ams-resizable-handles="e"
- tal:repeat="slot_name config.get_slots(row)"
- tal:attributes="class string:slot context-menu col ${config.get_slot_configuration(slot_name).get_css_class()};
- data-ams-slot-name slot_name;">
- <div class="header padding-x-5"
- tal:content="slot_name"></div>
- <div class="portlets"
- data-ams-sortable-placeholder="portlet-highlight"
- data-ams-sortable-connectwith=".portlets"
- data-ams-sortable-over="PyAMS_portal.template.overPortlets"
- data-ams-sortable-stop="PyAMS_portal.template.sortPortlets">
- <div class="portlet context-menu"
- data-ams-contextmenu-selector="#portletMenu"
- tal:repeat="portlet_name config.slots.get(row,{}).get(slot_name, ())"
- tal:attributes="data-ams-portlet-name portlet_name;
- data-ams-portlet-slot slot_name;
- data-ams-portlet-position repeat['portlet_name'].index();">
- <div class="header padding-x-5"
- tal:content="string:${view.get_portlet_label(portlet_name)}"></div>
- <div class="preview"
- tal:content="structure view.get_portlet_preview(slot_name, repeat['portlet_name'].index())"></div>
- </div>
- </div>
- <div class="clearfix"></div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <ul id="rowMenu" class="dropdown-menu" role="menu" style="display:none;"
- tal:condition="can_change">
- <li class="small">
- <a tabindex="-1" data-ams-url="PyAMS_portal.template.deleteRow">
- <i class="fa fa-fw fa-trash"></i>
- <i18n:var translate="">Delete row...</i18n:var>
- </a>
- </li>
- </ul>
- <ul id="slotMenu" class="dropdown-menu" role="menu" style="display:none;" >
- <li class="small">
- <a tabindex="-1" data-ams-url="PyAMS_portal.template.editSlot">
- <i class="fa fa-fw fa-edit"></i>
- <i18n:var translate="">Edit slot properties...</i18n:var>
- </a>
- </li>
- <tal:if condition="can_change">
- <li class="divider"></li>
- <li class="small" tal:condition="can_change">
- <a tabindex="-1" data-ams-url="PyAMS_portal.template.deleteSlot">
- <i class="fa fa-fw fa-trash"></i>
- <i18n:var translate="">Delete slot...</i18n:var>
- </a>
- </li>
- </tal:if>
- </ul>
- <ul id="portletMenu" class="dropdown-menu" role="menu" style="display:none;" >
- <li class="small">
- <a tabindex="-1" data-ams-url="PyAMS_portal.template.editPortlet">
- <i class="fa fa-fw fa-edit"></i>
- <i18n:var translate="">Edit portlet properties...</i18n:var>
- </a>
- </li>
- <tal:if condition="can_change">
- <li class="divider"></li>
- <li class="small">
- <a tabindex="-1" data-ams-url="PyAMS_portal.template.deletePortlet">
- <i class="fa fa-fw fa-trash"></i>
- <i18n:var translate="">Delete portlet...</i18n:var>
- </a>
- </li>
- </tal:if>
- </ul>
- </div>
- </div>
-</tal:var>
--- a/src/pyams_portal/zmi/template/workflow.py Thu Oct 08 12:26:42 2015 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,176 +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 IPortalTemplate
-from pyams_skin.layer import IPyAMSLayer
-from pyams_workflow.interfaces import IWorkflowPublicationInfo, IWorkflowCommentInfo, IWorkflowInfo, \
- IWorkflowTransitionInfo
-
-# import packages
-from pyams_form.form import AJAXAddForm
-from pyams_form.schema import CloseButton
-from pyams_pagelet.pagelet import pagelet_config
-from pyams_utils.url import absolute_url
-from pyams_workflow.zmi.transition import WorkflowContentTransitionForm
-from pyramid.view import view_config
-from z3c.form import field, button
-from zope.interface import Interface
-from zope.lifecycleevent import ObjectModifiedEvent
-
-from pyams_portal import _
-
-
-#
-# Base workflow form
-#
-
-class PortalTemplateWorkflowForm(WorkflowContentTransitionForm):
- """Base portal template workflow form"""
-
-
-#
-# Publish forms
-#
-
-class IPortalTemplatePublishButtons(Interface):
- """Portal template publish buttons"""
-
- close = CloseButton(name='close', title=_("Close"))
- action = button.Button(name='action', title=_("Publish"))
-
-
-@pagelet_config(name='wf-publish.html', context=IPortalTemplate, layer=IPyAMSLayer,
- permission='portal.templates.manage')
-class PortalTemplatePublishForm(PortalTemplateWorkflowForm):
- """Portal template publish form"""
-
- legend = _("Publish template")
-
- fields = field.Fields(IWorkflowTransitionInfo) + \
- field.Fields(IWorkflowPublicationInfo).select('publication_effective_date',
- 'publication_expiration_date') + \
- field.Fields(IWorkflowCommentInfo)
- buttons = button.Buttons(IPortalTemplatePublishButtons)
- ajax_handler = 'wf-publish.json'
-
- def createAndAdd(self, data):
- pub_info = IWorkflowPublicationInfo(self.context)
- pub_info.publication_effective_date = data.get('publication_effective_date')
- pub_info.publication_expiration_date = data.get('publication_expiration_date')
- info = IWorkflowInfo(self.context)
- info.fire_transition_toward('published', comment=data.get('comment'))
- self.request.registry.notify(ObjectModifiedEvent(self.context))
- return info
-
-
-@view_config(name='wf-publish.json', context=IPortalTemplate, request_type=IPyAMSLayer,
- permission='portal.templates.manage', renderer='json', xhr=True)
-class PortalTemplateAJAXPublishForm(AJAXAddForm, PortalTemplatePublishForm):
- """Portal template publish form, AJAX renderer"""
-
-
-#
-# Retire form
-#
-
-class IPortalTemplateRetireButtons(Interface):
- """Portal template retire buttons"""
-
- close = CloseButton(name='close', title=_("Close"))
- action = button.Button(name='action', title=_("Retire"))
-
-
-@pagelet_config(name='wf-retire.html', context=IPortalTemplate, layer=IPyAMSLayer,
- permission='portal.templates.manage')
-class PortalTemplateRetireForm(PortalTemplateWorkflowForm):
- """Portal template retire form"""
-
- legend = _("Retire template")
-
- buttons = button.Buttons(IPortalTemplateRetireButtons)
- ajax_handler = 'wf-retire.json'
-
-
-@view_config(name='wf-retire.json', context=IPortalTemplate, request_type=IPyAMSLayer,
- permission='portal.templates.manage', renderer='json', xhr=True)
-class PortalTemplateAJAXRetireForm(AJAXAddForm, PortalTemplateRetireForm):
- """Portal template retire form, AJAX renderer"""
-
-
-#
-# Archive form
-#
-
-class IPortalTemplateArchiveButtons(Interface):
- """Portal template archive buttons"""
-
- close = CloseButton(name='close', title=_("Close"))
- action = button.Button(name='action', title=_("Archive"))
-
-
-@pagelet_config(name='wf-archive.html', context=IPortalTemplate, layer=IPyAMSLayer,
- permission='portal.templates.manage')
-class PortalTemplateArchiveForm(PortalTemplateWorkflowForm):
- """Portal template archive form"""
-
- legend = _("Archive template")
-
- buttons = button.Buttons(IPortalTemplateArchiveButtons)
- ajax_handler = 'wf-archive.json'
-
-
-@view_config(name='wf-archive.json', context=IPortalTemplate, request_type=IPyAMSLayer,
- permission='portal.templates.manage', renderer='json', xhr=True)
-class PortalTemplateAJAXArchiveForm(AJAXAddForm, PortalTemplateArchiveForm):
- """Portal template archive form, AJAX renderer"""
-
-
-#
-# Clone forms
-#
-
-class IPortalTemplateCloneButtons(Interface):
- """Portal template clone buttons"""
-
- close = CloseButton(name='close', title=_("Close"))
- action = button.Button(name='action', title=_("Create new version"))
-
-
-@pagelet_config(name='wf-clone.html', context=IPortalTemplate, layer=IPyAMSLayer,
- permission='portal.templates.manage')
-class PortalTemplateCloneForm(PortalTemplateWorkflowForm):
- """Portal template clone form"""
-
- legend = _("Create new version")
-
- buttons = button.Buttons(IPortalTemplateCloneButtons)
- ajax_handler = 'wf-clone.json'
-
- def createAndAdd(self, data):
- info = IWorkflowInfo(self.context)
- return info.fire_transition_toward('draft', comment=data.get('comment'))
-
-
-@view_config(name='wf-clone.json', context=IPortalTemplate, request_type=IPyAMSLayer,
- permission='portal.templates.manage', renderer='json', xhr=True)
-class PortalTemplateAJAXCloneForm(AJAXAddForm, PortalTemplateCloneForm):
- """Portal template clone form, AJAX renderer"""
-
- def get_ajax_output(self, changes):
- return {'status': 'redirect',
- 'location': absolute_url(changes, self.request, 'admin.html#properties.html')}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_portal/zmi/templates/layout.pt Mon Jan 18 18:09:46 2016 +0100
@@ -0,0 +1,162 @@
+<tal:var define="template_config view.template_configuration;
+ portlet_config view.portlet_configuration;
+ can_change view.can_change;"
+ i18n:domain="pyams_portal">
+ <div class="ams-widget"
+ data-ams-focus-target
+ data-ams-plugins="pyams_portal"
+ data-ams-plugin-pyams_portal-src="/--static--/pyams_portal/js/portal{MyAMS.devext}.js"
+ data-ams-plugin-pyams_portal-css="/--static--/pyams_portal/css/portal{MyAMS.devext}.css"
+ data-ams-plugin-pyams_portal-callback="PyAMS_portal.template.initConfig">
+ <header>
+ <span tal:condition="view.widget_icon_class | nothing"
+ class="widget-icon"><i tal:attributes="class view.widget_icon_class"></i>
+ </span>
+ <h2 tal:content="view.title">Title</h2>
+ <tal:var content="structure provider:pyams.widget_title" />
+ <tal:var content="structure provider:pyams.toolbar" />
+ </header>
+ <div class="widget-body">
+ <div class="btn-toolbar" role="toolbar"
+ tal:condition="can_change">
+ <div class="btn-group" role="group">
+ <div class="btn btn-default btn-row hint" title="Add row" i18n:attributes="title"
+ data-ams-hint-gravity="n">
+ <i class="fa fa-fw fa-2x fa-indent"></i>
+ </div>
+ <div class="btn btn-default btn-slot hint" title="Add slot" i18n:attributes="title"
+ data-ams-hint-gravity="n">
+ <i class="fa fa-fw fa-2x fa-columns"></i>
+ </div>
+ </div>
+ <div class="btn-group" role="group">
+ <div tal:repeat="portlet view.selected_portlets"
+ class="btn btn-default btn-portlet hint"
+ data-ams-hint-gravity="n"
+ tal:attributes="data-ams-portlet-name portlet.name;
+ title portlet.label;">
+ <img tal:condition="portlet.toolbar_image"
+ tal:attributes="src portlet.toolbar_image" />
+ <i tal:condition="portlet.toolbar_css_class"
+ tal:attributes="class portlet.toolbar_css_class"></i>
+ </div>
+ </div>
+ <div class="btn-group" role="group">
+ <div class="btn btn-default hint" data-ams-url="add-template-portlet.html" data-toggle="modal"
+ data-ams-hint-gravity="n"
+ title="Add another portlet..." i18n:attributes="title">
+ <i class="fa fa-fw fa-2x fa-plus"></i>
+ </div>
+ </div>
+ </div>
+ <div class="clearfix">
+ <div class="ams-form form-horizontal margin-bottom-10">
+ <label class="control-label col-md-6 padding-right-5" i18n:translate="">Selected display:</label>
+ <div class="col-md-5">
+ <select id="device_selector" class="select2"
+ data-ams-select2-width="300px"
+ data-ams-change-handler="PyAMS_portal.template.selectDisplay">
+ <option value="" selected i18n:translate="">Current device</option>
+ <option value="xs" i18n:translate="">Extra small device (phone)</option>
+ <option value="sm" i18n:translate="">Small device (tablet)</option>
+ <option value="md" i18n:translate="">Medium desktop device (> 970px)</option>
+ <option value="lg" i18n:translate="">Large desktop device (> 1170px)</option>
+ </select>
+ </div>
+ </div>
+ </div>
+ <div id="portal_config" class="container"
+ tal:attributes="data-ams-allowed-change can_change">
+ <div class="rows"
+ data-ams-sortable-placeholder="row-highlight"
+ data-ams-sortable-items="> .row"
+ data-ams-sortable-over="PyAMS_portal.template.overRows"
+ data-ams-sortable-stop="PyAMS_portal.template.sortRows">
+ <div class="row context-menu"
+ data-ams-contextmenu-selector="#rowMenu"
+ tal:repeat="row range(template_config.rows)"
+ tal:attributes="data-ams-row-id row;">
+ <span class="row_id label label-success pull-right"
+ tal:content="row"></span>
+ <div class="slots"
+ data-ams-sortable-placeholder="slot-highlight"
+ data-ams-sortable-connectwith=".slots"
+ data-ams-sortable-over="PyAMS_portal.template.overSlots"
+ data-ams-sortable-stop="PyAMS_portal.template.sortSlots">
+ <div class="slot context-menu col col-md-12 no-padding"
+ data-ams-contextmenu-selector="#slotMenu"
+ data-ams-resizable-start="PyAMS_portal.template.startSlotResize"
+ data-ams-resizable-stop="PyAMS_portal.template.stopSlotResize"
+ data-ams-resizable-handles="e"
+ tal:repeat="slot_name template_config.get_slots(row)"
+ tal:attributes="class string:slot context-menu col ${template_config.get_slot_configuration(slot_name).get_css_class()};
+ data-ams-slot-name slot_name;">
+ <div class="header padding-x-5"
+ tal:content="slot_name"></div>
+ <div class="portlets"
+ data-ams-sortable-placeholder="portlet-highlight"
+ data-ams-sortable-connectwith=".portlets"
+ data-ams-sortable-over="PyAMS_portal.template.overPortlets"
+ data-ams-sortable-stop="PyAMS_portal.template.sortPortlets">
+ <div class="portlet context-menu"
+ data-ams-contextmenu-selector="#portletMenu"
+ tal:repeat="portlet_id template_config.slot_config[slot_name].portlet_ids"
+ tal:attributes="data-ams-portlet-id portlet_id;">
+ <div class="header padding-x-5"
+ tal:define="portlet_name portlet_config.get_portlet_configuration(portlet_id).portlet_name"
+ tal:content="view.get_portlet_label(portlet_name)"></div>
+ <div class="preview"
+ tal:content="structure view.get_portlet_preview(portlet_id)"></div>
+ </div>
+ </div>
+ <div class="clearfix"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <ul id="rowMenu" class="dropdown-menu" role="menu" style="display:none;"
+ tal:condition="can_change">
+ <li class="small">
+ <a tabindex="-1" data-ams-url="PyAMS_portal.template.deleteRow">
+ <i class="fa fa-fw fa-trash"></i>
+ <i18n:var translate="">Delete row...</i18n:var>
+ </a>
+ </li>
+ </ul>
+ <ul id="slotMenu" class="dropdown-menu" role="menu" style="display:none;"
+ tal:condition="can_change">
+ <li class="small">
+ <a tabindex="-1" data-ams-url="PyAMS_portal.template.editSlot">
+ <i class="fa fa-fw fa-edit"></i>
+ <i18n:var translate="">Edit slot properties...</i18n:var>
+ </a>
+ </li>
+ <li class="divider"></li>
+ <li class="small">
+ <a tabindex="-1" data-ams-url="PyAMS_portal.template.deleteSlot">
+ <i class="fa fa-fw fa-trash"></i>
+ <i18n:var translate="">Delete slot...</i18n:var>
+ </a>
+ </li>
+ </ul>
+ <ul id="portletMenu" class="dropdown-menu" role="menu" style="display:none;" >
+ <li class="small">
+ <a tabindex="-1" data-ams-url="PyAMS_portal.template.editPortlet">
+ <i class="fa fa-fw fa-edit"></i>
+ <i18n:var translate="">Edit portlet properties...</i18n:var>
+ </a>
+ </li>
+ <tal:if condition="can_change">
+ <li class="divider"></li>
+ <li class="small">
+ <a tabindex="-1" data-ams-url="PyAMS_portal.template.deletePortlet">
+ <i class="fa fa-fw fa-trash"></i>
+ <i18n:var translate="">Delete portlet...</i18n:var>
+ </a>
+ </li>
+ </tal:if>
+ </ul>
+ </div>
+ </div>
+</tal:var>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_portal/zmi/templates/portlet.pt Mon Jan 18 18:09:46 2016 +0100
@@ -0,0 +1,184 @@
+<div class="modal-content" i18n:domain="pyams_portal">
+ <div class="modal-header"
+ tal:define="header provider:form_header">
+ <tal:if condition="header">
+ <tal:var replace="structure header" />
+ </tal:if>
+ <tal:if condition="not:header">
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true"
+ tal:condition="view.is_dialog">
+ <i class="fa fa-fw fa-times-circle"></i>
+ </button>
+ <h3 class="modal-title"
+ tal:define="config extension:configuration;">
+ <span class="title" tal:content="view.title">Title</span>
+ </h3>
+ <tal:var replace="structure provider:form_toolbar" />
+ </tal:if>
+ </div>
+ <div class="modal-body no-padding">
+ <div class="form-prefix"
+ tal:define="prefix provider:form_prefix"
+ tal:condition="prefix"
+ tal:content="structure prefix">Form prefix</div>
+ <form method="post"
+ data-async
+ tal:attributes="id view.id;
+ name view.name;
+ action view.get_form_action();
+ method view.method;
+ enctype view.enctype;
+ acceptCharset view.acceptCharset;
+ accept view.accept;
+ autocomplete view.autocomplete;
+ class view.css_class;
+ data-ams-data extension:view_data;
+ data-ams-form-handler view.get_ajax_handler() | nothing;
+ data-ams-form-options view.get_form_options() | nothing;
+ data-ams-form-submit-target view.form_target | nothing;
+ data-ams-form-download-target view.download_target | nothing;
+ data-ams-warn-on-change view.warn_on_change;">
+ <div class="modal-viewport">
+ <fieldset tal:attributes="class view.fieldset_class | default">
+ <legend tal:define="legend view.legend"
+ tal:condition="legend">
+ <i tal:attributes="class view.icon_css_class | nothing"></i>
+ <tal:var content="legend">Legend</tal:var>
+ <tal:var condition="python:getattr(view, 'show_widget_title', False)"
+ content="structure provider:pyams.widget_title" />
+ </legend>
+ <tal:var content="structure provider:form_help" />
+ <fieldset class="bordered"
+ tal:define="configuration view.getContent().configuration;
+ can_inherit configuration.can_inherit;"
+ tal:omit-tag="not can_inherit">
+ <input type="hidden" name="form.widgets.portlet_id"
+ tal:attributes="value configuration.portlet_id" />
+ <legend tal:condition="can_inherit"
+ class="inner checker"
+ data-ams-checker-value="selected"
+ data-ams-checker-mode="disable"
+ data-ams-checker-fieldname="form.widgets.override_parent"
+ tal:attributes="data-ams-checker-state 'off' if configuration.inherit_parent else 'on';">
+ <label tal:content="view.override_label">Override parent settings</label>
+ </legend>
+ <div class="widgets-prefix"
+ tal:define="prefix provider:widgets_prefix"
+ tal:condition="prefix"
+ tal:content="structure prefix">Widgets prefix</div>
+ <tal:loop repeat="group view.groups">
+ <fieldset tal:define="legend group.legend"
+ tal:omit-tag="not:legend"
+ tal:attributes="class 'bordered' if group.bordered else None">
+ <tal:if condition="group.checkbox_switch">
+ <legend data-ams-checker-value="selected"
+ tal:condition="legend"
+ tal:attributes="class group.css_class;
+ data-ams-checker-fieldname '{0}:list'.format(group.checkbox_widget.name);
+ data-ams-checker-readonly 'readonly' if group.checkbox_widget.mode == 'display' else None;
+ data-ams-checker-mode 'disable' if group.checkbox_mode == 'disable' else None;
+ data-ams-checker-marker '{0}-empty-marker'.format(group.checkbox_widget.name);
+ data-ams-checker-state group.checker_state;">
+ <label tal:content="legend">Legend</label>
+ </legend>
+ </tal:if>
+ <tal:if condition="not:group.checkbox_switch">
+ <legend tal:condition="legend"
+ tal:content="legend"
+ tal:attributes="class group.css_class;
+ data-ams-switcher-state group.switcher_state;">Legend</legend>
+ </tal:if>
+ <tal:var define="help group.help" condition="help">
+ <div class="alert alert-info padding-5"
+ tal:define="html import:pyams_utils.text.text_to_html;
+ i18n_help html(request.localizer.translate(help));"
+ tal:content="structure i18n_help"></div>
+ </tal:var>
+ <tal:loop repeat="widget group.visible_widgets">
+ <input type="hidden"
+ tal:condition="widget.mode == 'hidden'"
+ tal:replace="structure widget.render()" />
+ <tal:if condition="widget.mode != 'hidden'">
+ <div tal:define="required 'required-field' if widget.required and (widget.mode != 'display') else ''"
+ tal:attributes="class string:form-group ${required}">
+ <label tal:attributes="class group.label_css_class | view.label_css_class">
+ <span>
+ <tal:var content="widget.label" />
+ <i class="fa fa-question-circle hint" title="Input hint"
+ tal:define="description widget.field.description"
+ tal:condition="description"
+ tal:attributes="title description;
+ data-ams-hint-html '<' in description;"></i>
+ </span>
+ </label>
+ <div tal:attributes="class widget.input_css_class | group.input_css_class | view.input_css_class">
+ <label class="input"
+ tal:attributes="class widget.label_css_class | default;
+ data-ams-data extension:object_data(widget);
+ data-ams-form-validator view.get_widget_callback(widget.field.getName())">
+ <input tal:replace="structure widget.render()" />
+ </label>
+ </div>
+ </div>
+ </tal:if>
+ </tal:loop>
+ </fieldset>
+ <div class="subforms"
+ tal:condition="group.subforms">
+ <fieldset tal:define="title group.subforms_legend"
+ tal:omit-tag="not:title">
+ <legend tal:condition="title" tal:content="title" i18n:translate="">Title</legend>
+ <tal:loop repeat="subform group.subforms">
+ <tal:var replace="structure subform.render()" />
+ </tal:loop>
+ </fieldset>
+ </div>
+ </tal:loop>
+ <div class="widgets-suffix"
+ tal:define="suffix provider:widgets_suffix"
+ tal:condition="suffix"
+ tal:content="structure suffix">Widgets suffix</div>
+ <div class="subforms"
+ tal:condition="view.subforms">
+ <fieldset tal:define="title view.subforms_legend"
+ tal:omit-tag="not:title">
+ <legend tal:condition="title" tal:content="title" i18n:translate="">Title</legend>
+ <tal:loop repeat="subform view.subforms">
+ <tal:var replace="structure subform.render()" />
+ </tal:loop>
+ </fieldset>
+ </div>
+ <div class="tabforms"
+ tal:condition="view.tabforms">
+ <ul class="nav nav-tabs">
+ <li tal:repeat="tabform view.tabforms"
+ tal:attributes="class 'small {active} {errors}'.format(active='active' if repeat['tabform'].start() else '',
+ errors='state-error' if tabform.widgets.errors else '')">
+ <a data-toggle="tab"
+ tal:attributes="href string:#${tabform.id};
+ data-ams-url python:getattr(tabform, 'tab_target', None);"
+ tal:content="tabform.tab_label" i18n:translate="">Tab label</a>
+ </li>
+ </ul>
+ <div class="tab-content bordered padding-x-10">
+ <div class="tab-pane fade in"
+ tal:repeat="tabform view.tabforms"
+ tal:attributes="id tabform.id;
+ class 'tab-pane {active} fade in'.format(active='active' if repeat['tabform'].start() else '');"
+ tal:content="structure tabform.render()"></div>
+ </div>
+ </div>
+ </fieldset>
+ </fieldset>
+ </div>
+ <footer tal:condition="view.actions and (view.is_dialog or (view.mode == 'input'))">
+ <button tal:repeat="action view.actions.values()"
+ tal:replace="structure action.render()">Action</button>
+ </footer>
+ </form>
+ <div class="form-suffix"
+ tal:define="suffix provider:form_suffix"
+ tal:condition="suffix"
+ tal:content="structure suffix">Form suffix</div>
+ </div>
+</div>