# HG changeset patch # User Thierry Florac # Date 1453136986 -3600 # Node ID 670b7956c6896a9ec5730f9d417b6087f9bf0f8d # Parent a5f118662d872a7ea4bb5fefd11b01e9e5659018 Rewritten base portal engine! diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/__init__.py --- 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}}) diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/interfaces/__init__.py --- 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") diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/locales/fr/LC_MESSAGES/pyams_portal.mo Binary file src/pyams_portal/locales/fr/LC_MESSAGES/pyams_portal.mo has changed diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/locales/fr/LC_MESSAGES/pyams_portal.po --- 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 \n" "Language-Team: French \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 !" diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/locales/pyams_portal.pot --- 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 , 2015. +# FIRST AUTHOR , 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 \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 "" diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/page.py --- 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 diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/portlet.py --- 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] diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/portlets/content/__init__.py --- /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 +# 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""" diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/portlets/content/content.pt --- /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 @@ +

This is my context!!!

diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/portlets/content/interfaces.py --- /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 +# 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""" diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/portlets/context/__init__.py --- 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 -# 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""" - - diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/portlets/context/context.pt --- 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 @@ -

This is my context!!!

diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/portlets/context/interfaces.py --- 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 -# 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""" diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/portlets/image/__init__.py --- 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) diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/portlets/image/interfaces.py --- 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) diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/resources/css/portal.min.css --- /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 diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/resources/js/portal.js --- 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($('').addClass('row_id label label-success pull-right') - .text(row_id)) - .append($('
').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($('').addClass('row_id label label-success pull-right') + .text(row_id)) + .append($('
').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: '  ' + 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 = $('
').addClass('slot context-menu col col-md-12') + var new_slot = $('
').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($('
').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($('
').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: '  ' + 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 = $('
').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($('
').addClass('header padding-x-5') .text(result.label)) . append($('
').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: '  ' + 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); diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/resources/js/portal.min.js --- 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("").addClass("row_id label label-success pull-right").text(f)).append(a("
").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("
").addClass("row context-menu").attr("data-ams-row-id",c).append(a("").addClass("row_id label label-success pull-right").text(c)).append(a("
").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:'  '+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("
").addClass("slot context-menu col col-md-12").attr("data-ams-slot-name",d).append(a("
").addClass("header padding-x-5").text(d)).append(a("
").addClass("portlets").sortable({placeholder:"portlet-highlight",connectWith:".portlets",over:PyAMS_portal.template.overPortlets,stop:PyAMS_portal.template.sortPortlets})).append(a("
").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:'  '+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("
").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("
").addClass("header padding-x-5").text(b.label)).append(a("
").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:'  '+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("
").addClass("row context-menu").attr("data-ams-row-id",f).append(c("").addClass("row_id label label-success pull-right").text(f)).append(c("
").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("").addClass("row_id label label-success pull-right").text(h)).append(c("
").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:'  '+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("
").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("
").addClass("header padding-x-5").text(g)).append(c("
").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("
").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:'  '+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("
").addClass("portlet context-menu").attr("data-ams-portlet-id",e.portlet_id).append(c("
").addClass("header padding-x-5").text(e.label)).append(c("
").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:'  '+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 diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/slot.py --- 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: diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/template.py --- 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 diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/workflow.py --- 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 -# 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 diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/zmi/container.py --- 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) diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/zmi/interfaces.py --- 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""" diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/zmi/layout.py --- /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 +# 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'} diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/zmi/page.py --- /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 +# 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""" diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/zmi/portlet.py --- 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 diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/zmi/portlets/content.py --- /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 +# 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""" diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/zmi/portlets/context.py --- 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 -# 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""" diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/zmi/portlets/image.py --- 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): diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/zmi/portlets/templates/image-preview.pt --- 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:attributes="href extension:absolute_url(target)"> - +
@@ -21,7 +21,7 @@
- +
diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/zmi/template.py --- /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 +# 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) diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/zmi/template/__init__.py --- 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 -# 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) diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/zmi/template/config.py --- 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 -# 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'} diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/zmi/template/page.py --- 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 -# 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() diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/zmi/template/templates/config.pt --- 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 @@ - -
-
- - -

Title

- - -
-
- -
-
- -
- -
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - - -
-
-
diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/zmi/template/workflow.py --- 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 -# 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')} diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/zmi/templates/layout.pt --- /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 @@ + +
+
+ + +

Title

+ + +
+
+ +
+
+ +
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + +
+
+
diff -r a5f118662d87 -r 670b7956c689 src/pyams_portal/zmi/templates/portlet.pt --- /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 @@ +