# HG changeset patch # User Thierry Florac # Date 1434527913 -7200 # Node ID 6f99128c6d48924b6a80da09d136acadbce29222 Version 0.1.0 diff -r 000000000000 -r 6f99128c6d48 .hgignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,19 @@ + +syntax: regexp +^develop-eggs$ +syntax: regexp +^parts$ +syntax: regexp +^bin$ +syntax: regexp +^\.installed\.cfg$ +syntax: regexp +^\.settings$ +syntax: regexp +^build$ +syntax: regexp +^dist$ +syntax: regexp +^\.idea$ +syntax: regexp +.*\.pyc$ diff -r 000000000000 -r 6f99128c6d48 .installed.cfg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.installed.cfg Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,103 @@ +[buildout] +installed_develop_eggs = /home/tflorac/Dropbox/src/PyAMS/pyams_portal/develop-eggs/pyams-portal.egg-link +parts = package i18n pyflakes test + +[package] +__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_portal/bin/pcreate + /home/tflorac/Dropbox/src/PyAMS/pyams_portal/bin/pdistreport + /home/tflorac/Dropbox/src/PyAMS/pyams_portal/bin/ptweens + /home/tflorac/Dropbox/src/PyAMS/pyams_portal/bin/pshell + /home/tflorac/Dropbox/src/PyAMS/pyams_portal/bin/pserve + /home/tflorac/Dropbox/src/PyAMS/pyams_portal/bin/prequest + /home/tflorac/Dropbox/src/PyAMS/pyams_portal/bin/pviews + /home/tflorac/Dropbox/src/PyAMS/pyams_portal/bin/proutes +__buildout_signature__ = zc.recipe.egg-2.0.1-py3.4.egg setuptools-15.1-py3.4.egg zc.buildout-2.3.1-py3.4.egg +_b = /home/tflorac/Dropbox/src/PyAMS/pyams_portal/bin +_d = /home/tflorac/Dropbox/src/PyAMS/pyams_portal/develop-eggs +_e = /var/local/env/pyams/eggs +bin-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_portal/bin +develop-eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_portal/develop-eggs +eggs = pyams_portal + pyramid + zope.component + zope.interface +eggs-directory = /var/local/env/pyams/eggs +recipe = zc.recipe.egg + +[i18n] +__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_portal/bin/pybabel + /home/tflorac/Dropbox/src/PyAMS/pyams_portal/bin/pot-create + /home/tflorac/Dropbox/src/PyAMS/pyams_portal/bin/polint +__buildout_signature__ = zc.recipe.egg-2.0.1-py3.4.egg setuptools-15.1-py3.4.egg zc.buildout-2.3.1-py3.4.egg +_b = /home/tflorac/Dropbox/src/PyAMS/pyams_portal/bin +_d = /home/tflorac/Dropbox/src/PyAMS/pyams_portal/develop-eggs +_e = /var/local/env/pyams/eggs +bin-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_portal/bin +develop-eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_portal/develop-eggs +eggs = babel + lingua +eggs-directory = /var/local/env/pyams/eggs +recipe = zc.recipe.egg + +[pyflakes] +__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_portal/bin/pyflakes + /home/tflorac/Dropbox/src/PyAMS/pyams_portal/bin/pyflakes +__buildout_signature__ = zc.recipe.egg-2.0.1-py3.4.egg setuptools-15.1-py3.4.egg zc.buildout-2.3.1-py3.4.egg +_b = /home/tflorac/Dropbox/src/PyAMS/pyams_portal/bin +_d = /home/tflorac/Dropbox/src/PyAMS/pyams_portal/develop-eggs +_e = /var/local/env/pyams/eggs +bin-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_portal/bin +develop-eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_portal/develop-eggs +eggs = pyflakes +eggs-directory = /var/local/env/pyams/eggs +entry-points = pyflakes=pyflakes.scripts.pyflakes:main +initialization = if not sys.argv[1:]: sys.argv[1:] = ["src"] +recipe = zc.recipe.egg +scripts = pyflakes + +[test] +__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_portal/parts/test + /home/tflorac/Dropbox/src/PyAMS/pyams_portal/bin/test +__buildout_signature__ = zc.recipe.testrunner-2.0.0-py3.4.egg zc.recipe.egg-2.0.1-py3.4.egg setuptools-15.1-py3.4.egg zope.testrunner-4.4.6-py3.4.egg zc.buildout-2.3.1-py3.4.egg zope.interface-4.1.2-py3.4-linux-x86_64.egg zope.exceptions-4.0.7-py3.4.egg six-e6b62e54b4df360c40dfcbb76c1ecf1a +_b = /home/tflorac/Dropbox/src/PyAMS/pyams_portal/bin +_d = /home/tflorac/Dropbox/src/PyAMS/pyams_portal/develop-eggs +_e = /var/local/env/pyams/eggs +bin-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_portal/bin +develop-eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_portal/develop-eggs +eggs = pyams_portal [test] +eggs-directory = /var/local/env/pyams/eggs +location = /home/tflorac/Dropbox/src/PyAMS/pyams_portal/parts/test +recipe = zc.recipe.testrunner +script = /home/tflorac/Dropbox/src/PyAMS/pyams_portal/bin/test + +[buildout] +installed_develop_eggs = /home/tflorac/Dropbox/src/PyAMS/pyams_portal/develop-eggs/lingua.egg-link + /home/tflorac/Dropbox/src/PyAMS/pyams_portal/develop-eggs/pyams-portal.egg-link + +[buildout] +parts = i18n pyflakes test package + +[buildout] +parts = pyflakes test package i18n + +[buildout] +parts = test package i18n pyflakes + +[buildout] +parts = package i18n pyflakes test + +[buildout] +installed_develop_eggs = /home/tflorac/Dropbox/src/PyAMS/pyams_portal/develop-eggs/lingua.egg-link + /home/tflorac/Dropbox/src/PyAMS/pyams_portal/develop-eggs/pyams-portal.egg-link + +[buildout] +parts = i18n pyflakes test package + +[buildout] +parts = pyflakes test package i18n + +[buildout] +parts = test package i18n pyflakes + +[buildout] +parts = package i18n pyflakes test diff -r 000000000000 -r 6f99128c6d48 LICENSE --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/LICENSE Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,42 @@ +Zope Public License (ZPL) Version 2.1 +===================================== + +A copyright notice accompanies this license document that identifies +the copyright holders. + +This license has been certified as open source. It has also been designated +as GPL compatible by the Free Software Foundation (FSF). + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions in source code must retain the accompanying copyright + notice, this list of conditions, and the following disclaimer. + 2. Redistributions in binary form must reproduce the accompanying copyright + notice, this list of conditions, and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Names of the copyright holders must not be used to endorse or promote + products derived from this software without prior written permission + from the copyright holders. + 4. The right to distribute this software or to use it for any purpose does + not give you the right to use Servicemarks (sm) or Trademarks (tm) of the + copyright holders. Use of them is covered by separate agreement with the + copyright holders. + 5. If any files are modified, you must cause the modified files to carry + prominent notices stating that you changed the files and the date of any + change. + + +Disclaimer +========== + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff -r 000000000000 -r 6f99128c6d48 MANIFEST.in --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MANIFEST.in Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,5 @@ +include *.txt +recursive-include docs * +recursive-include src * +global-exclude *.pyc +global-exclude *.*~ diff -r 000000000000 -r 6f99128c6d48 bootstrap.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bootstrap.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,178 @@ +############################################################################## +# +# Copyright (c) 2006 Zope Foundation and Contributors. +# 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. +# +############################################################################## +"""Bootstrap a buildout-based project + +Simply run this script in a directory containing a buildout.cfg. +The script accepts buildout command-line options, so you can +use the -c option to specify an alternate configuration file. +""" + +import os +import shutil +import sys +import tempfile + +from optparse import OptionParser + +tmpeggs = tempfile.mkdtemp() + +usage = '''\ +[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] + +Bootstraps a buildout-based project. + +Simply run this script in a directory containing a buildout.cfg, using the +Python that you want bin/buildout to use. + +Note that by using --find-links to point to local resources, you can keep +this script from going over the network. +''' + +parser = OptionParser(usage=usage) +parser.add_option("-v", "--version", help="use a specific zc.buildout version") + +parser.add_option("-t", "--accept-buildout-test-releases", + dest='accept_buildout_test_releases', + action="store_true", default=False, + help=("Normally, if you do not specify a --version, the " + "bootstrap script and buildout gets the newest " + "*final* versions of zc.buildout and its recipes and " + "extensions for you. If you use this flag, " + "bootstrap and buildout will get the newest releases " + "even if they are alphas or betas.")) +parser.add_option("-c", "--config-file", + help=("Specify the path to the buildout configuration " + "file to be used.")) +parser.add_option("-f", "--find-links", + help=("Specify a URL to search for buildout releases")) +parser.add_option("--allow-site-packages", + action="store_true", default=False, + help=("Let bootstrap.py use existing site packages")) + + +options, args = parser.parse_args() + +###################################################################### +# load/install setuptools + +try: + if options.allow_site_packages: + import setuptools + import pkg_resources + from urllib.request import urlopen +except ImportError: + from urllib2 import urlopen + +ez = {} +exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez) + +if not options.allow_site_packages: + # ez_setup imports site, which adds site packages + # this will remove them from the path to ensure that incompatible versions + # of setuptools are not in the path + import site + # inside a virtualenv, there is no 'getsitepackages'. + # We can't remove these reliably + if hasattr(site, 'getsitepackages'): + for sitepackage_path in site.getsitepackages(): + sys.path[:] = [x for x in sys.path if sitepackage_path not in x] + +setup_args = dict(to_dir=tmpeggs, download_delay=0) +ez['use_setuptools'](**setup_args) +import setuptools +import pkg_resources + +# This does not (always?) update the default working set. We will +# do it. +for path in sys.path: + if path not in pkg_resources.working_set.entries: + pkg_resources.working_set.add_entry(path) + +###################################################################### +# Install buildout + +ws = pkg_resources.working_set + +cmd = [sys.executable, '-c', + 'from setuptools.command.easy_install import main; main()', + '-mZqNxd', tmpeggs] + +find_links = os.environ.get( + 'bootstrap-testing-find-links', + options.find_links or + ('http://downloads.buildout.org/' + if options.accept_buildout_test_releases else None) + ) +if find_links: + cmd.extend(['-f', find_links]) + +setuptools_path = ws.find( + pkg_resources.Requirement.parse('setuptools')).location + +requirement = 'zc.buildout' +version = options.version +if version is None and not options.accept_buildout_test_releases: + # Figure out the most recent final version of zc.buildout. + import setuptools.package_index + _final_parts = '*final-', '*final' + + def _final_version(parsed_version): + for part in parsed_version: + if (part[:1] == '*') and (part not in _final_parts): + return False + return True + index = setuptools.package_index.PackageIndex( + search_path=[setuptools_path]) + if find_links: + index.add_find_links((find_links,)) + req = pkg_resources.Requirement.parse(requirement) + if index.obtain(req) is not None: + best = [] + bestv = None + for dist in index[req.project_name]: + distv = dist.parsed_version + if _final_version(distv): + if bestv is None or distv > bestv: + best = [dist] + bestv = distv + elif distv == bestv: + best.append(dist) + if best: + best.sort() + version = best[-1].version +if version: + requirement = '=='.join((requirement, version)) +cmd.append(requirement) + +import subprocess +if subprocess.call(cmd, env=dict(os.environ, PYTHONPATH=setuptools_path)) != 0: + raise Exception( + "Failed to execute command:\n%s" % repr(cmd)[1:-1]) + +###################################################################### +# Import and run buildout + +ws.add_entry(tmpeggs) +ws.require(requirement) +import zc.buildout.buildout + +if not [a for a in args if '=' not in a]: + args.append('bootstrap') + +# if -c was provided, we push it back into args for buildout' main function +if options.config_file is not None: + args[0:0] = ['-c', options.config_file] + +zc.buildout.buildout.main(args) +shutil.rmtree(tmpeggs) diff -r 000000000000 -r 6f99128c6d48 buildout.cfg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/buildout.cfg Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,61 @@ +[buildout] +eggs-directory = /var/local/env/pyams/eggs + +socket-timeout = 3 +show-picked-versions = true +newest = false + +allow-hosts = + bitbucket.org + *.python.org + *.sourceforge.net + github.com + +#extends = http://download.ztfy.org/webapp/ztfy.webapp.dev.cfg +versions = versions +newest = false +#allow-picked-versions = false + +src = src +develop = + . + /var/local/src/pyams/ext/lingua + +parts = + package + i18n + pyflakes + test + +[package] +recipe = zc.recipe.egg +eggs = + pyams_portal + pyramid + zope.component + zope.interface + +[i18n] +recipe = zc.recipe.egg +eggs = + babel + lingua + +[pyflakes] +recipe = zc.recipe.egg +eggs = pyflakes +scripts = pyflakes +entry-points = pyflakes=pyflakes.scripts.pyflakes:main +initialization = if not sys.argv[1:]: sys.argv[1:] = ["${buildout:src}"] + +[pyflakesrun] +recipe = collective.recipe.cmd +on_install = true +cmds = ${buildout:develop}/bin/${pyflakes:scripts} + +[test] +recipe = zc.recipe.testrunner +eggs = pyams_portal [test] + +[versions] +pyams_portal = 0.1.0 diff -r 000000000000 -r 6f99128c6d48 docs/HISTORY.txt diff -r 000000000000 -r 6f99128c6d48 docs/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/README.txt Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,68 @@ + + +Portal template configuration storage +===================================== + + + + name = 'template1' + __annotations__['pyams_portal.template'] = PortalTemplateConfiguration: + __parent__ = mon_template + rows = 2 + slot_names = ['slot1', 'slot2', 'slot3'] + slot_order = {0: ['slot1', 'slot2], + 1: ['slot3']} + slots = {0: {'slot1': [1, 2], + 'slot2': [3, 4, 5]}, + 1: {'slot3': []}}, + slot_config = {'slot1': , + 'slot2': , + 'slot3': }, + portlets = {1: 'portlet1', + 2: 'portlet2', + 3: 'portlet1', + 4: 'portlet3', + 5: 'portlet1'}, + __annotations__['pyams_portal.portlets'] = PortalPortletsConfiguration: + portlet_config = {1: , + 2: }, + 3: , + 4: , + 5: }} + + + + shared_template = 'template1' + template = mon_template + __annotations__['pyams_portal.template'] = PortalTemplateConfiguration: + __parent__ = mon_contexte + slots = {0: {'slot1': [1, 2], + 'slot2': [3, 4, 5]}, + 1: {'slot3': []}}, + slot_config = {'slot1': , + 'slot2': , + 'slot3': }, + __annotations__['pyams_portal.portlets'] = PortalPortletsConfiguration: + portlet_config = {1: , + 2: }, + 3: , + 4: , + 5: }} + + +: + + __parent__ = context1 + inherit_parent = False + use_local_template = False + shared_template = 'template1' + + +: + + __parent__ = context2 + inherit_parent = False + use_local_template = True + local_template = + __annotations__['pyams_portal.portlets'] = PortalPortletsConfiguration: + __parent__ = ++template++ diff -r 000000000000 -r 6f99128c6d48 setup.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,69 @@ +# +# 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. +# + +""" +This module contains pyams_portal package +""" +import os +from setuptools import setup, find_packages + +DOCS = os.path.join(os.path.dirname(__file__), + 'docs') + +README = os.path.join(DOCS, 'README.txt') +HISTORY = os.path.join(DOCS, 'HISTORY.txt') + +version = '0.1.0' +long_description = open(README).read() + '\n\n' + open(HISTORY).read() + +tests_require = [] + +setup(name='pyams_portal', + version=version, + description="PyAMS portal and portlets interfaces and classes", + long_description=long_description, + classifiers=[ + "License :: OSI Approved :: Zope Public License", + "Development Status :: 4 - Beta", + "Programming Language :: Python", + "Framework :: Pyramid", + "Topic :: Software Development :: Libraries :: Python Modules", + ], + keywords='Pyramid PyAMS portal portlets', + author='Thierry Florac', + author_email='tflorac@ulthar.net', + url='http://hg.ztfy.org/pyams/pyams_portal', + license='ZPL', + packages=find_packages('src'), + package_dir={'': 'src'}, + namespace_packages=[], + include_package_data=True, + package_data={'': ['*.zcml', '*.txt', '*.pt', '*.pot', '*.po', '*.mo', + '*.png', '*.gif', '*.jpeg', '*.jpg', '*.css', '*.js']}, + zip_safe=False, + # uncomment this to be able to run tests with setup.py + test_suite="pyams_portal.tests.test_utilsdocs.test_suite", + tests_require=tests_require, + extras_require=dict(test=tests_require), + install_requires=[ + 'setuptools', + # -*- Extra requirements: -*- + 'fanstatic', + 'pyramid', + 'zope.component', + 'zope.interface', + ], + entry_points={ + 'fanstatic.libraries': [ + 'pyams_portal = pyams_portal:library' + ] + }) diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal.egg-info/PKG-INFO --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal.egg-info/PKG-INFO Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,86 @@ +Metadata-Version: 1.1 +Name: pyams-portal +Version: 0.1.0 +Summary: PyAMS portal and portlets interfaces and classes +Home-page: http://hg.ztfy.org/pyams/pyams_portal +Author: Thierry Florac +Author-email: tflorac@ulthar.net +License: ZPL +Description: + + Portal template configuration storage + ===================================== + + + + name = 'template1' + __annotations__['pyams_portal.template'] = PortalTemplateConfiguration: + __parent__ = mon_template + rows = 2 + slot_names = ['slot1', 'slot2', 'slot3'] + slot_order = {0: ['slot1', 'slot2], + 1: ['slot3']} + slots = {0: {'slot1': [1, 2], + 'slot2': [3, 4, 5]}, + 1: {'slot3': []}}, + slot_config = {'slot1': , + 'slot2': , + 'slot3': }, + portlets = {1: 'portlet1', + 2: 'portlet2', + 3: 'portlet1', + 4: 'portlet3', + 5: 'portlet1'}, + __annotations__['pyams_portal.portlets'] = PortalPortletsConfiguration: + portlet_config = {1: , + 2: }, + 3: , + 4: , + 5: }} + + + + shared_template = 'template1' + template = mon_template + __annotations__['pyams_portal.template'] = PortalTemplateConfiguration: + __parent__ = mon_contexte + slots = {0: {'slot1': [1, 2], + 'slot2': [3, 4, 5]}, + 1: {'slot3': []}}, + slot_config = {'slot1': , + 'slot2': , + 'slot3': }, + __annotations__['pyams_portal.portlets'] = PortalPortletsConfiguration: + portlet_config = {1: , + 2: }, + 3: , + 4: , + 5: }} + + + : + + __parent__ = context1 + inherit_parent = False + use_local_template = False + shared_template = 'template1' + + + : + + __parent__ = context2 + inherit_parent = False + use_local_template = True + local_template = + __annotations__['pyams_portal.portlets'] = PortalPortletsConfiguration: + __parent__ = ++template++ + + + +Keywords: Pyramid PyAMS portal portlets +Platform: UNKNOWN +Classifier: License :: OSI Approved :: Zope Public License +Classifier: Development Status :: 4 - Beta +Classifier: Programming Language :: Python +Classifier: Framework :: Pyramid +Classifier: Topic :: Software Development :: Libraries :: Python Modules diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal.egg-info/SOURCES.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal.egg-info/SOURCES.txt Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,54 @@ +MANIFEST.in +setup.py +docs/HISTORY.txt +docs/README.txt +src/pyams_portal/__init__.py +src/pyams_portal/include.py +src/pyams_portal/page.py +src/pyams_portal/portlet.py +src/pyams_portal/site.py +src/pyams_portal/slot.py +src/pyams_portal/template.py +src/pyams_portal/workflow.py +src/pyams_portal.egg-info/PKG-INFO +src/pyams_portal.egg-info/SOURCES.txt +src/pyams_portal.egg-info/dependency_links.txt +src/pyams_portal.egg-info/entry_points.txt +src/pyams_portal.egg-info/namespace_packages.txt +src/pyams_portal.egg-info/not-zip-safe +src/pyams_portal.egg-info/requires.txt +src/pyams_portal.egg-info/top_level.txt +src/pyams_portal/doctests/README.txt +src/pyams_portal/interfaces/__init__.py +src/pyams_portal/locales/pyams_portal.pot +src/pyams_portal/locales/fr/LC_MESSAGES/pyams_portal.mo +src/pyams_portal/locales/fr/LC_MESSAGES/pyams_portal.po +src/pyams_portal/portlets/__init__.py +src/pyams_portal/portlets/context/__init__.py +src/pyams_portal/portlets/context/context.pt +src/pyams_portal/portlets/context/interfaces.py +src/pyams_portal/portlets/image/__init__.py +src/pyams_portal/portlets/image/image.pt +src/pyams_portal/portlets/image/interfaces.py +src/pyams_portal/resources/css/portal.css +src/pyams_portal/resources/css/portal.min.css +src/pyams_portal/resources/js/portal.js +src/pyams_portal/resources/js/portal.min.js +src/pyams_portal/resources/less/portal.less +src/pyams_portal/tests/__init__.py +src/pyams_portal/tests/test_utilsdocs.py +src/pyams_portal/tests/test_utilsdocstrings.py +src/pyams_portal/zmi/__init__.py +src/pyams_portal/zmi/container.py +src/pyams_portal/zmi/interfaces.py +src/pyams_portal/zmi/portlet.py +src/pyams_portal/zmi/portlets/__init__.py +src/pyams_portal/zmi/portlets/context.py +src/pyams_portal/zmi/portlets/image.py +src/pyams_portal/zmi/portlets/templates/context-preview.pt +src/pyams_portal/zmi/portlets/templates/image-preview.pt +src/pyams_portal/zmi/template/__init__.py +src/pyams_portal/zmi/template/config.py +src/pyams_portal/zmi/template/page.py +src/pyams_portal/zmi/template/workflow.py +src/pyams_portal/zmi/template/templates/config.pt \ No newline at end of file diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal.egg-info/dependency_links.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal.egg-info/dependency_links.txt Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,1 @@ + diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal.egg-info/entry_points.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal.egg-info/entry_points.txt Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,3 @@ +[fanstatic.libraries] +pyams_portal = pyams_portal:library + diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal.egg-info/namespace_packages.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal.egg-info/namespace_packages.txt Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,1 @@ + diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal.egg-info/not-zip-safe --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal.egg-info/not-zip-safe Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,1 @@ + diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal.egg-info/requires.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal.egg-info/requires.txt Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,7 @@ +setuptools +fanstatic +pyramid +zope.component +zope.interface + +[test] diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal.egg-info/top_level.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal.egg-info/top_level.txt Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,1 @@ +pyams_portal diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/__init__.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,36 @@ +# +# 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' + + +from fanstatic import Library +library = Library('pyams_portal', 'resources') + +from pyramid.i18n import TranslationStringFactory +_ = TranslationStringFactory('pyams_portal') + + +def includeme(config): + """Pyramid include""" + + from .include import include_package + include_package(config) + + # register custom permissions + config.register_permission({'id': 'portal.templates.manage', + 'title': _("Manage portal templates")}) + + # register custom roles + config.register_role({'id': 'portal.TemplatesManager', + 'title': _("Portal templates manager"), + 'permissions': {'portal.templates.manage', 'view', 'system.view'}}) diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/doctests/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/doctests/README.txt Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,3 @@ +==================== +pyams_portal package +==================== diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/include.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/include.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,35 @@ +# +# 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 + +# import packages + + +def include_package(config): + """Pyramid include""" + + # add translations + config.add_translation_dirs('pyams_portal:locales') + + # load registry components + try: + import pyams_zmi + except ImportError: + config.scan(ignore='pyams_portal.zmi') + else: + config.scan() diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/interfaces/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/interfaces/__init__.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,357 @@ +# +# 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_workflow.interfaces import IWorkflowManagedContent +from zope.annotation.interfaces import IAttributeAnnotatable +from zope.container.interfaces import IContainer +from zope.contentprovider.interfaces import IContentProvider + +# import packages +from pyams_security.schema import PermissionField +from pyams_utils.schema import PersistentDict, PersistentList +from zope.container.constraints import contains +from zope.interface import invariant, Interface, Attribute, Invalid +from zope.schema import List, TextLine, Object, Int, Bool, Choice + +from pyams_portal import _ + + +class IPortlet(Interface): + """Portlet interface""" + + name = Attribute("Portlet internal name") + + label = Attribute("Portlet visible name") + + permission = PermissionField(title="Portlet permission", + description="Permission required to display permission", + required=False) + + toolbar_image = Attribute("Porlet toolbar image") + + toolbar_css_class = Attribute("Portlet toolbar CSS class") + + +class IPortletAddingInfo(Interface): + """Portlet adding info interface""" + + portlet_name = Choice(title=_("Portlet"), + vocabulary="PyAMS portal portlets") + + slot_name = Choice(title=_("Slot name"), + description=_("Slot name to which this configuration applies"), + required=True, + vocabulary='PyAMS template slots') + + +class IPortletConfiguration(Interface): + """Portlet configuration interface""" + + template = Attribute("Template to which this configuration applis") + + slot_name = TextLine(title=_("Slot name"), + description=_("Slot name to which this configuration applies"), + 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?") + + 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..."), + required=True, + default=True) + + +class IPortletContentProvider(IContentProvider): + """Portlet content provider""" + + portlet = Object(title="Portlet utility", + schema=IPortlet) + + configuration = Object(title="Portlet renderer configuration", + schema=IPortletConfiguration) + + +class IPortletPreviewer(IPortletContentProvider): + """Portlet previewer interface + + A portlet previewer should be defined as an adapter for a context, + a request, a view and a portlet + """ + + +class IPortletRenderer(IPortletContentProvider): + """Portlet renderer interface + + A portlet renderer should be defined as an adapter for a context, + a request, a view and a portlet + """ + + +class ISlot(Interface): + """Portal template slot interface""" + + name = TextLine(title=_("Slot name"), + description=_("This name must be unique in a given template"), + required=True) + + row_id = Int(title=_("Row ID"), + required=False) + + +class ISlotConfiguration(Interface): + """Portal slot configuration""" + + template = Attribute("Slot template") + + slot_name = TextLine(title="Slot name") + + visible = Bool(title=_("Visible slot?"), + description=_("Select 'no' to hide this slot. This will not break configuration inheritance..."), + 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"), + required=False, + min=0, + max=12) + + sm_width = Int(title=_("Small device width"), + description=_("Slot width, in columns count, on small devices (tablets...); " + "set to 0 to hide the portlet"), + required=False, + min=0, + max=12) + + md_width = Int(title=_("Medium devices width"), + description=_("Slot width, in columns count, on medium desktop devices (>= 992 pixels); " + "set to 0 to hide the portlet"), + required=False, + min=0, + max=12) + + lg_width = Int(title=_("Large devices width"), + description=_("Slot width, in columns count, on large desktop devices (>= 1200 pixels); " + "set to 0 to hide the portlet"), + required=False, + min=0, + max=12) + + css_class = TextLine(title=_("CSS class"), + description=_("CSS class applied to this slot"), + required=False) + + def get_css_class(self, device=None): + """Get current CSS class""" + + def get_width(self, device=None): + """Get slot width for each or given device""" + + def set_width(self, width, device=None): + """Set width in columns count for given device""" + + +class ISlotRenderer(IContentProvider): + """Slot renderer""" + + +class IPortalTemplateConfiguration(Interface): + """Portal template configuration interface""" + + rows = Int(title="Rows count", + required=True, + default=1, + min=0) + + def add_row(self): + """Add new row""" + + def set_row_order(self, order): + """Change template rows order""" + + def delete_row(self, row_id): + """Delete template row""" + + slot_names = PersistentList(title="Slot names", + value_type=TextLine()) + + slot_order = PersistentDict(title="Solts order", + 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), + required=False) + + def add_slot(self, slot_name, row_id=None): + """Add slot with given name""" + + def set_slot_order(self, order): + """Change template slots order""" + + def get_slot_row(self, slot_name): + """Get row containing given slot""" + + def get_slots(self, row_id): + """Get ordered list of slots for given row ID""" + + def get_slots_width(self, device=None): + """Get slots width for given or all device(s)""" + + def set_slot_width(self, slot_name, device, width): + """Set slot width for given device""" + + def get_slot_configuration(self, slot_name): + """Get slot configuration for given slot""" + + 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) + + 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_configuration(self, slot_name, position): + """Get portlet configuration for given slot""" + + def delete_portlet(self, slot_name, position): + """Delete template portlet""" + + +class IPortalTemplate(IAttributeAnnotatable): + """Portal template interface + + A portal template is a named utility providing a name and a set of slots. + """ + + name = TextLine(title=_("Template name"), + description=_("Two registered templates can't share the same name..."), + required=True) + + +class IPortalWfTemplate(IWorkflowManagedContent): + """Workflow managed portal template interface""" + + +class IPortalTemplateContainer(IContainer, IAttributeAnnotatable): + """Portal template container interface""" + + contains(IPortalTemplate) + + +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) + + +class IPortalTemplateRenderer(IContentProvider): + """Portal template renderer + + A portal template renderer should be implemented as an adapter for a context, a request + and a template + """ + + +class IPortalPage(Interface): + """Portal page interface + + The page is the highest configuration level. + It defines which template is used (a shared or local one), which gives + the slots list. + """ + + can_inherit = Attribute("Can inherit parent template?") + + inherit_parent = Bool(title=_("Inherit parent template?"), + description=_("Should we reuse parent template?"), + required=True, + default=True) + + use_local_template = Bool(title=_("Use local template?"), + description=_("If 'yes', you can define a custom local template instead of " + "a shared template"), + required=True, + default=False) + + shared_template = Choice(title=_("Page template"), + description=_("Template used for this page"), + vocabulary='PyAMS portal templates', + required=False) + + @invariant + def check_template(self): + if not (self.use_local_template or self.shared_template): + raise Invalid(_("You must choose to use a local template or select a shared one!")) + + local_template = Object(title=_("Local template"), + schema=IPortalWfTemplate, + required=False) + + template = Attribute("Used template") + + +class IPortalContext(IAttributeAnnotatable): + """Portal context marker interface""" diff -r 000000000000 -r 6f99128c6d48 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 000000000000 -r 6f99128c6d48 src/pyams_portal/locales/fr/LC_MESSAGES/pyams_portal.po --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/locales/fr/LC_MESSAGES/pyams_portal.po Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,504 @@ +# +# French translations for PACKAGE package +# This file is distributed under the same license as the PACKAGE package. +# Thierry Florac , 2015. +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE 1.0\n" +"POT-Creation-Date: 2015-05-12 13:59+0200\n" +"PO-Revision-Date: 2015-05-12 12:10+0200\n" +"Last-Translator: Thierry Florac \n" +"Language-Team: French \n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"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 +msgid "Manage portal templates" +msgstr "Gérer les modèles de présentation" + +#: src/pyams_portal/__init__.py:35 +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:38 +#, 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/template/config.py:120 +msgid "Portlets configuration" +msgstr "Configuration des portlets" + +#: src/pyams_portal/zmi/template/config.py:133 +msgid "Add row..." +msgstr "Ajouter une ligne..." + +#: 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/config.py:265 +msgid "Edit slot properties" +msgstr "Propriétés d'un slot" + +#: src/pyams_portal/zmi/template/config.py:333 +msgid "Add portlet..." +msgstr "Ajouter un composant..." + +#: 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 +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 +#, python-format +msgid "« {0} » portal template" +msgstr "Modèle de présentation « {0} »" + +#: src/pyams_portal/zmi/template/config.py:262 +#, 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" + +#: 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/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 +msgid "Shared portal templates" +msgstr "Modèles de présentation partagés" + +#: src/pyams_portal/zmi/template/__init__.py:163 +msgid "Delete template" +msgstr "Supprimer le modèle" + +#: src/pyams_portal/zmi/template/__init__.py:195 +msgid "Portal" +msgstr "Portail" + +#: src/pyams_portal/zmi/template/__init__.py:229 +msgid "Add shared template..." +msgstr "Ajouter un modèle partagé..." + +#: 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/template/__init__.py:153 +msgid "Older versions" +msgstr "Versions précédentes" + +#: 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/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/template/templates/config.pt:42 +msgid "Selected display:" +msgstr "Type de périphérique sélectionné :" + +#: src/pyams_portal/zmi/template/templates/config.pt:47 +msgid "Current device" +msgstr "Périphérique actuel" + +#: src/pyams_portal/zmi/template/templates/config.pt:48 +msgid "Extra small device (phone)" +msgstr "Très petits périphériques (téléphone)" + +#: src/pyams_portal/zmi/template/templates/config.pt:49 +msgid "Small device (tablet)" +msgstr "Petits périphériques (tablette)" + +#: src/pyams_portal/zmi/template/templates/config.pt:50 +msgid "Medium desktop device (> 970px)" +msgstr "Écrans de taille moyenne (> 970 px)" + +#: src/pyams_portal/zmi/template/templates/config.pt:51 +msgid "Large desktop device (> 1170px)" +msgstr "Écrans de grande taille (> 1170 px)" + +#: src/pyams_portal/zmi/template/templates/config.pt:111 +msgid "Delete row..." +msgstr "Supprimer la ligne..." + +#: src/pyams_portal/zmi/template/templates/config.pt:119 +msgid "Edit slot properties..." +msgstr "Propriétés..." + +#: src/pyams_portal/zmi/template/templates/config.pt:126 +msgid "Delete slot..." +msgstr "Supprimer le slot..." + +#: src/pyams_portal/zmi/template/templates/config.pt:134 +msgid "Edit portlet properties..." +msgstr "Propriétés..." + +#: src/pyams_portal/zmi/template/templates/config.pt:141 +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 +msgid "Image" +msgstr "Image" + +#: src/pyams_portal/portlets/image/interfaces.py:30 +msgid "Selected image" +msgstr "Image sélectionnée" + +#: src/pyams_portal/interfaces/__init__.py:49 +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 +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" + +#: src/pyams_portal/interfaces/__init__.py:69 +msgid "Position" +msgstr "Position" + +#: 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 +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:81 +#: src/pyams_portal/interfaces/__init__.py:139 +msgid "Inherit parent configuration?" +msgstr "Hériter de la configuration du parent ?" + +#: src/pyams_portal/interfaces/__init__.py:82 +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..." +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..." + +#: src/pyams_portal/interfaces/__init__.py:118 +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 +msgid "Row ID" +msgstr "ID de la ligne" + +#: src/pyams_portal/interfaces/__init__.py:132 +msgid "Visible slot?" +msgstr "Slot 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: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 +msgid "Extra small device width" +msgstr "Largeur sur très petits périphériques" + +#: src/pyams_portal/interfaces/__init__.py:146 +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 " +"(téléphones...) ; indiquez une valeur de 0 pour masquer ce composant" + +#: src/pyams_portal/interfaces/__init__.py:152 +msgid "Small device width" +msgstr "Largeur sur petits périphériques" + +#: src/pyams_portal/interfaces/__init__.py:153 +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 " +"(tablettes...) ; indiquez une valeur de 0 pour masquer ce composant" + +#: src/pyams_portal/interfaces/__init__.py:159 +msgid "Medium devices width" +msgstr "Largeur sur périphériques moyens" + +#: src/pyams_portal/interfaces/__init__.py:160 +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" + +#: src/pyams_portal/interfaces/__init__.py:166 +msgid "Large devices width" +msgstr "Largeur sur grands périphériques" + +#: src/pyams_portal/interfaces/__init__.py:167 +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 (>= " +"1200 pixels) ; indiquez une valeur de 0 pour masquer ce composant" + +#: src/pyams_portal/interfaces/__init__.py:173 +msgid "CSS class" +msgstr "Class CSS" + +#: src/pyams_portal/interfaces/__init__.py:174 +msgid "CSS class applied to this slot" +msgstr "Classe CSS spécifique appliquée à ce slot" + +#: src/pyams_portal/interfaces/__init__.py:276 +msgid "Template name" +msgstr "Nom du modèle" + +#: src/pyams_portal/interfaces/__init__.py:277 +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 +msgid "Inherit parent template?" +msgstr "Hériter du modèle du parent ?" + +#: src/pyams_portal/interfaces/__init__.py:310 +msgid "Should we reuse parent template?" +msgstr "Doit-on ré-utiliser le modèle du parent ?" + +#: src/pyams_portal/interfaces/__init__.py:314 +msgid "Use local template?" +msgstr "Utiliser un modèle local ?" + +#: src/pyams_portal/interfaces/__init__.py:315 +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 +msgid "Page template" +msgstr "Modèle de page" + +#: src/pyams_portal/interfaces/__init__.py:321 +msgid "Template used for this page" +msgstr "Modèle de présentation utilisé pour cette page" + +#: src/pyams_portal/interfaces/__init__.py:325 +msgid "Local template" +msgstr "Modèle local" + +#~ msgid "Portlet templates" +#~ msgstr "Modèles de présentation" diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/locales/pyams_portal.pot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/locales/pyams_portal.pot Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,460 @@ +# +# SOME DESCRIPTIVE TITLE +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , 2015. +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE 1.0\n" +"POT-Creation-Date: 2015-05-12 13:59+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"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 +msgid "Manage portal templates" +msgstr "" + +#: ./src/pyams_portal/__init__.py:35 +msgid "Portal templates manager" +msgstr "" + +#: ./src/pyams_portal/zmi/portlet.py:41 +msgid "Edit portlet configuration" +msgstr "" + +#: ./src/pyams_portal/zmi/portlet.py:38 +#, python-format +msgid "« {0} » portal template - {1}" +msgstr "" + +#: ./src/pyams_portal/zmi/template/config.py:61 +msgid "Properties" +msgstr "" + +#: ./src/pyams_portal/zmi/template/config.py:72 +msgid "Portal 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..." +msgstr "" + +#: ./src/pyams_portal/zmi/template/config.py:175 +msgid "Add slot..." +msgstr "" + +#: ./src/pyams_portal/zmi/template/config.py:191 +msgid "Add slot" +msgstr "" + +#: ./src/pyams_portal/zmi/template/config.py:265 +msgid "Edit slot properties" +msgstr "" + +#: ./src/pyams_portal/zmi/template/config.py:333 +msgid "Add portlet..." +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 +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 +#, python-format +msgid "« {0} » portal template" +msgstr "" + +#: ./src/pyams_portal/zmi/template/config.py:262 +#, 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" +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 +msgid "Shared portal templates" +msgstr "" + +#: ./src/pyams_portal/zmi/template/__init__.py:163 +msgid "Delete template" +msgstr "" + +#: ./src/pyams_portal/zmi/template/__init__.py:195 +msgid "Portal" +msgstr "" + +#: ./src/pyams_portal/zmi/template/__init__.py:229 +msgid "Add shared template..." +msgstr "" + +#: ./src/pyams_portal/zmi/template/__init__.py:241 +msgid "Add shared template" +msgstr "" + +#: ./src/pyams_portal/zmi/template/__init__.py:153 +msgid "Older versions" +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})" +msgstr "" + +#: ./src/pyams_portal/zmi/template/templates/config.pt:15 +#: ./src/pyams_portal/zmi/template/templates/config.pt:29 +msgid "Version ${version} - ${state}" +msgstr "" + +#: ./src/pyams_portal/zmi/template/templates/config.pt:42 +msgid "Selected display:" +msgstr "" + +#: ./src/pyams_portal/zmi/template/templates/config.pt:47 +msgid "Current device" +msgstr "" + +#: ./src/pyams_portal/zmi/template/templates/config.pt:48 +msgid "Extra small device (phone)" +msgstr "" + +#: ./src/pyams_portal/zmi/template/templates/config.pt:49 +msgid "Small device (tablet)" +msgstr "" + +#: ./src/pyams_portal/zmi/template/templates/config.pt:50 +msgid "Medium desktop device (> 970px)" +msgstr "" + +#: ./src/pyams_portal/zmi/template/templates/config.pt:51 +msgid "Large desktop device (> 1170px)" +msgstr "" + +#: ./src/pyams_portal/zmi/template/templates/config.pt:111 +msgid "Delete row..." +msgstr "" + +#: ./src/pyams_portal/zmi/template/templates/config.pt:119 +msgid "Edit slot properties..." +msgstr "" + +#: ./src/pyams_portal/zmi/template/templates/config.pt:126 +msgid "Delete slot..." +msgstr "" + +#: ./src/pyams_portal/zmi/template/templates/config.pt:134 +msgid "Edit portlet properties..." +msgstr "" + +#: ./src/pyams_portal/zmi/template/templates/config.pt:141 +msgid "Delete portlet..." +msgstr "" + +#: ./src/pyams_portal/portlets/context/__init__.py:43 +msgid "Context content" +msgstr "" + +#: ./src/pyams_portal/portlets/image/__init__.py:44 +msgid "Image" +msgstr "" + +#: ./src/pyams_portal/portlets/image/interfaces.py:30 +msgid "Selected image" +msgstr "" + +#: ./src/pyams_portal/interfaces/__init__.py:49 +msgid "Portlet" +msgstr "" + +#: ./src/pyams_portal/interfaces/__init__.py:52 +#: ./src/pyams_portal/interfaces/__init__.py:63 +#: ./src/pyams_portal/interfaces/__init__.py:117 +msgid "Slot name" +msgstr "" + +#: ./src/pyams_portal/interfaces/__init__.py:53 +#: ./src/pyams_portal/interfaces/__init__.py:64 +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 +msgid "Visible portlet?" +msgstr "" + +#: ./src/pyams_portal/interfaces/__init__.py:75 +msgid "" +"Select 'no' to hide this portlet. This will not break configuration " +"inheritance..." +msgstr "" + +#: ./src/pyams_portal/interfaces/__init__.py:81 +#: ./src/pyams_portal/interfaces/__init__.py:139 +msgid "Inherit parent configuration?" +msgstr "" + +#: ./src/pyams_portal/interfaces/__init__.py:82 +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..." +msgstr "" + +#: ./src/pyams_portal/interfaces/__init__.py:118 +msgid "This name must be unique in a given template" +msgstr "" + +#: ./src/pyams_portal/interfaces/__init__.py:121 +msgid "Row ID" +msgstr "" + +#: ./src/pyams_portal/interfaces/__init__.py:132 +msgid "Visible slot?" +msgstr "" + +#: ./src/pyams_portal/interfaces/__init__.py:133 +msgid "" +"Select 'no' to hide this slot. This will not break configuration " +"inheritance..." +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 +msgid "Extra small device width" +msgstr "" + +#: ./src/pyams_portal/interfaces/__init__.py:146 +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 +msgid "Small device width" +msgstr "" + +#: ./src/pyams_portal/interfaces/__init__.py:153 +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 +msgid "Medium devices width" +msgstr "" + +#: ./src/pyams_portal/interfaces/__init__.py:160 +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 +msgid "Large devices width" +msgstr "" + +#: ./src/pyams_portal/interfaces/__init__.py:167 +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 +msgid "CSS class" +msgstr "" + +#: ./src/pyams_portal/interfaces/__init__.py:174 +msgid "CSS class applied to this slot" +msgstr "" + +#: ./src/pyams_portal/interfaces/__init__.py:276 +msgid "Template name" +msgstr "" + +#: ./src/pyams_portal/interfaces/__init__.py:277 +msgid "Two registered templates can't share the same name..." +msgstr "" + +#: ./src/pyams_portal/interfaces/__init__.py:309 +msgid "Inherit parent template?" +msgstr "" + +#: ./src/pyams_portal/interfaces/__init__.py:310 +msgid "Should we reuse parent template?" +msgstr "" + +#: ./src/pyams_portal/interfaces/__init__.py:314 +msgid "Use local template?" +msgstr "" + +#: ./src/pyams_portal/interfaces/__init__.py:315 +msgid "" +"If 'yes', you can define a custom local template instead of a shared template" +msgstr "" + +#: ./src/pyams_portal/interfaces/__init__.py:320 +msgid "Page template" +msgstr "" + +#: ./src/pyams_portal/interfaces/__init__.py:321 +msgid "Template used for this page" +msgstr "" + +#: ./src/pyams_portal/interfaces/__init__.py:325 +msgid "Local template" +msgstr "" diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/page.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/page.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,151 @@ +# +# 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 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' + + +# import standard library + +# import interfaces +from pyams_portal.interfaces import IPortalPage, IPortalContext, IPortalTemplateRenderer, \ + IPortalTemplateConfiguration, IPortalWfTemplate +from zope.annotation.interfaces import IAnnotations + +# import packages +from persistent import Persistent +from pyams_utils.adapter import adapter_config, ContextAdapter +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.schema.fieldproperty import FieldProperty + + +@implementer(IPortalPage) +class PortalPage(Persistent, Contained): + """Portal page""" + + _inherit_parent = FieldProperty(IPortalPage['inherit_parent']) + _use_local_template = FieldProperty(IPortalPage['use_local_template']) + _shared_template = FieldProperty(IPortalPage['shared_template']) + _local_template = FieldProperty(IPortalPage['local_template']) + + @property + def can_inherit(self): + return IPortalContext.providedBy(self.__parent__.__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 use_local_template(self): + if self.inherit_parent: + return IPortalPage(self.__parent__.__parent__).use_local_template + else: + return self._use_local_template + + @use_local_template.setter + def use_local_template(self, value): + self._use_local_template = value + if value and (self._local_template is None): + registry = get_current_registry() + wf_template = self._local_template = PortalWfTemplate() + registry.notify(ObjectCreatedEvent(wf_template)) + locate(wf_template, self, '++template++') + template = PortalTemplate() + registry.notify(ObjectCreatedEvent(template)) + IWorkflowVersions(wf_template).add_version(template, None) + IWorkflowInfo(template).fire_transition('init') + + @property + def shared_template(self): + if self.inherit_parent: + return IPortalPage(self.__parent__.__parent__).shared_template + else: + return self._shared_template + + @shared_template.setter + def shared_template(self, value): + if not self.inherit_parent: + if isinstance(value, IPortalWfTemplate): + 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 + + @local_template.setter + def local_template(self, value): + if not self.inherit_parent: + self._local_template = value + + @property + def template(self): + if self.use_local_template: + return 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 + + +@adapter_config(context=IPortalContext, provides=IPortalPage) +def PortalPageFactory(context): + """Portal page factory""" + annotations = IAnnotations(context) + page = annotations.get(PORTAL_PAGE_KEY) + if page is None: + page = annotations[PORTAL_PAGE_KEY] = PortalPage() + get_current_registry().notify(ObjectCreatedEvent(page)) + locate(page, context) + 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) diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/portlet.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/portlet.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,160 @@ +# +# 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 logging +logger = logging.getLogger('PyAMS (portal)') + +import venusian + +# import interfaces +from pyams_portal.interfaces import IPortlet, IPortletRenderer, IPortletConfiguration, \ + IPortalPage, IPortletPreviewer +from zope.schema.interfaces import IVocabularyFactory + +# import packages +from persistent import Persistent +from pyams_utils.request import check_request +from pyams_viewlet.viewlet import ContentProvider +from pyramid.exceptions import ConfigurationError +from zope.container.contained import Contained +from zope.interface import implementer, provider +from zope.schema.fieldproperty import FieldProperty +from zope.schema.vocabulary import getVocabularyRegistry, SimpleVocabulary, SimpleTerm + + +@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 + + +@implementer(IPortlet) +class Portlet(object): + """Base portlet content provider""" + + 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""" + + +class portlet_config(object): + """Class decorator used to declare a portlet""" + + venusian = venusian # for testing injection + + def __init__(self, **settings): + self.__dict__.update(settings) + + def __call__(self, wrapped): + settings = self.__dict__.copy() + depth = settings.pop('_depth', 0) + + def callback(context, name, ob): + name = settings.get('name') or getattr(ob, 'name', None) + if name is None: + raise ConfigurationError("You must provide a name for a portlet") + + permission = settings.get('permission') + if permission is not None: + ob.permission = permission + + if type(ob) is type: + factory = ob + component = None + else: + factory = None + component = ob + + config = context.config.with_package(info.module) + logger.debug("Registering portlet {0} named '{1}'".format(str(component) if component else str(factory), + name)) + config.registry.registerUtility(component=component, factory=factory, + provided=IPortlet, name=name) + + info = self.venusian.attach(wrapped, callback, category='pyams_portal', + depth=depth + 1) + if info.scope == 'class': + # if the decorator was attached to a method in a class, or + # otherwise executed at class scope, we need to set an + # 'attr' into the settings if one isn't already in there + if settings.get('attr') is None: + settings['attr'] = wrapped.__name__ + + settings['_info'] = info.codeinfo # fbo "action_method" + return wrapped diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/portlets/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/portlets/__init__.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,20 @@ +# +# 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 + +# import packages diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/portlets/context/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/portlets/context/__init__.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,57 @@ +# +# 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 + +# 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') +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 000000000000 -r 6f99128c6d48 src/pyams_portal/portlets/context/context.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/portlets/context/context.pt Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,1 @@ +

This is my context!!!

diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/portlets/context/interfaces.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/portlets/context/interfaces.py Wed Jun 17 09:58:33 2015 +0200 @@ -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 IPortletConfiguration + +# import packages + + +class IContextPortletConfiguration(IPortletConfiguration): + """Context portlet configuration interface""" diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/portlets/image/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/portlets/image/__init__.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,61 @@ +# +# 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_file.property import FileProperty + +__docformat__ = 'restructuredtext' + + +# import standard library + +# import interfaces +from .interfaces import IImagePortletConfiguration +from pyams_portal.interfaces import IPortalContext, IPortletRenderer +from pyams_skin.layer import IPyAMSLayer + +# import packages +from pyams_portal.portlet import portlet_config, Portlet, PortletConfiguration, PortletRenderer +from pyams_template.template import template_config +from pyams_utils.adapter import adapter_config +from zope.interface import implementer, Interface + +from pyams_portal import _ + + +IMAGE_PORTLET_NAME = 'pyams_portal.portlet.image' + + +@portlet_config(permission='view') +class ImagePortlet(Portlet): + """Image portlet + + The goal of this portlet is to display an image + """ + + name = IMAGE_PORTLET_NAME + label = _("Image") + + 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']) + + +@adapter_config(context=(IPortalContext, IPyAMSLayer, Interface, ImagePortlet), provides=IPortletRenderer) +@template_config(template='image.pt', layer=IPyAMSLayer) +class ImagePortletRenderer(PortletRenderer): + """Image portlet renderer""" diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/portlets/image/image.pt diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/portlets/image/interfaces.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/portlets/image/interfaces.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,31 @@ +# +# 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_file.schema import ImageField + +__docformat__ = 'restructuredtext' + + +# import standard library + +# import interfaces +from pyams_portal.interfaces import IPortletConfiguration + +# import packages + +from pyams_portal import _ + + +class IImagePortletConfiguration(IPortletConfiguration): + """Image portlet configuration interface""" + + image = ImageField(title=_("Selected image"), + required=False) diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/resources/css/portal.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/resources/css/portal.css Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,133 @@ +#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: 0.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; +} diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/resources/js/portal.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/resources/js/portal.js Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,495 @@ +(function($) { + + window.PyAMS_portal = { + + /** + * Templates management + */ + template: { + + initConfig: function() { + var config = $('#portal_config'); + if (config.data('ams-allowed-change')) { + // Init sortables and resizables + $('.rows', config).addClass('sortable'); + $('.slots', config).addClass('sortable'); + $('.slot', config).addClass('resizable'); + $('.portlets', config).addClass('sortable'); + MyAMS.plugins.enabled.sortable(config); + MyAMS.plugins.enabled.resizable(config); + // Init rows toolbar drag and drop + $('.btn-row', '.btn-toolbar').draggable({ + cursor: 'move', + helper: 'clone', + revert: 'invalid', + connectToSortable: '.rows' + }); + $('.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'); + }); + } + }); + // Init slot toolbar drag and drop + $('.btn-slot', '.btn-toolbar').draggable({ + cursor: 'move', + helper: 'clone', + revert: 'invalid', + connectToSortable: '.slots' + }); + $('.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); + } + }); + // Init portlets toolbar drag and drop + $('.btn-portlet', '.btn-toolbar').draggable({ + cursor: 'move', + helper: 'clone', + revert: 'invalid', + connectToSortable: '.portlets' + }); + $('.portlets', config).droppable({ + 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); + }); + } + }); + } + }, + + + /** + * Display selector + */ + + selectDisplay: function() { + var device = $(this).val(); + MyAMS.ajax.post('get-slots-width.json', + {device: device}, + function(result) { + var config = $('#portal_config'); + config.removeClassPrefix('container-'); + if (device) { + config.addClass('container-' + device); + } + $('.slot', config).removeClassPrefix('col-'); + for (var slot_name in result) { + 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) { + slot.addClass('col-' + display + '-' + widths[display]); + } + } + } + }); + }, + + /** + * Rows management + */ + + addRow: function() { + return function() { + $(this).parents('.btn-group').removeClass('open'); + MyAMS.ajax.post('add-template-row.json', {}, function(result) { + var row_id = result.row_id; + var rows = $('.rows', '#portal_config'); + $('
').addClass('row context-menu') + .attr('data-ams-row-id', row_id) + .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 + }) + .appendTo(rows); + rows.sortable('refresh'); + }); + }; + }, + + overRows: function(event, ui) { + $(ui.placeholder).attr('class', $(ui.item).attr('class')) + .removeClassPrefix('ui-') + .addClass('row-highlight') + .css('height', $(ui.item).outerHeight()); + }, + + sortRows: function(event, ui) { + 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') { + $('.row', config).each(function (index) { + $(this).attr('data-ams-row-id', index); + $('span.row_id', $(this)).text(index); + }) + } + }); + }, + + deleteRow: function() { + return function(row) { + MyAMS.skin.bigBox({ + title: MyAMS.i18n.WARNING, + content: '  ' + MyAMS.i18n.DELETE_WARNING, + buttons: MyAMS.i18n.BTN_OK_CANCEL + }, function(button) { + 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') { + row.remove(); + $('.row', '#portal_config').each(function (index) { + $(this).removeData() + .attr('data-ams-row-id', index); + $('span.row_id', $(this)).text(index); + }) + } + }); + } + }); + }; + }, + + + /** + * Slots management + */ + + 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') + .attr('data-ams-slot-name', slot_name) + .append($('
').addClass('header padding-x-5') + .text(slot_name)) + .append($('
').addClass('portlets') + .sortable({ + placeholder: 'portlet-highlight', + connectWith: '.portlets', + over: PyAMS_portal.template.overPortlets, + stop: PyAMS_portal.template.sortPortlets + })) + .append($('
').addClass('clearfix')) + .contextMenu({ + menuSelector: '#slotMenu', + menuSelected: MyAMS.helpers.contextMenuHandler + }); + var slot_button = $('.btn-slot', slots); + if (slot_button.exists()) { // Slot added via drag & drop + slot_button.replaceWith(new_slot); + $('.slot', slots).each(function() { + $(this).removeData(); + }); + PyAMS_portal.template.sortSlots(); + } else { + new_slot.appendTo(slots); + } + slots.sortable('refresh'); + }, + + startSlotResize: function(event, ui) { + var slot = ui.element; + var row = slot.parents('.slots:first'); + var colWidth = (row.innerWidth() - 110) / 12; + var slotHeight = slot.height(); + ui.element.resizable('option', 'grid', [colWidth, slotHeight]); + ui.element.resizable('option', 'minWidth', colWidth); + ui.element.resizable('option', 'minHeight', slotHeight); + ui.element.resizable('option', 'maxWidth', row.innerWidth()); + ui.element.resizable('option', 'maxHeight', slotHeight); + }, + + stopSlotResize: function(event, ui) { + var slot = ui.element; + var row = slot.parents('.slots:first'); + var colWidth = (row.innerWidth() - 10) / 12; + var slotCols = Math.round($(slot).width() / colWidth); + var device = $('#device_selector').val(); + if (!device) { + var deviceWidth = $('body').width(); + if (deviceWidth > 1170) + device = 'lg'; + else if (deviceWidth > 970) + device = 'md'; + else if (deviceWidth > 750) + device = 'sm'; + else + device = 'xs'; + } + MyAMS.ajax.post('set-slot-width.json', + {slot_name: slot.data('ams-slot-name'), + device: device, + width: slotCols}, + function(result) { + slot.removeClassPrefix('col-'); + slot.removeAttr('style'); + var slot_name = slot.data('ams-slot-name'); + var widths = result[slot_name]; + if (device) { + slot.addClass('col-' + device + '-' + widths[device]); + } else { + slot.addClass('col-' + widths[device]); + } + }); + }, + + editSlot: function() { + return function(slot) { + if (!slot.hasClass('slot')) + slot = slot.parents('.slot'); + MyAMS.dialog.open('slot-properties.html?form.widgets.slot_name=' + slot.data('ams-slot-name')); + }; + }, + + editSlotCallback: function(result) { + var slot = $('.slot[data-ams-slot-name="' + result.slot_name + '"]'); + slot.attr('class', 'slot context-menu col'); + var device = $('#device_selector').val(); + if (device) + slot.addClass('col-' + result.width[device]); + else { + for (device in result.width) { + slot.addClass('col-' + device + '-' + result.width[device]); + } + } + }, + + overSlots: function(event, ui) { + $(ui.placeholder).attr('class', $(ui.item).attr('class')) + .removeClassPrefix('ui-') + .addClass('slot-highlight') + .css('height', $(ui.item).outerHeight()); + }, + + sortSlots: function(event, ui) { + if (ui && ui.item.hasClass('already-dropped')) + return; + var config = $('#portal_config'); + var order = {}; + $('.row', config).each(function() { + var row = $(this); + var row_config = []; + $('.slot', row).each(function() { + row_config.push($(this).data('ams-slot-name')); + }); + 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') {} + }); + }, + + deleteSlot: function() { + return function(slot) { + MyAMS.skin.bigBox({ + title: MyAMS.i18n.WARNING, + content: '  ' + MyAMS.i18n.DELETE_WARNING, + buttons: MyAMS.i18n.BTN_OK_CANCEL + }, function(button) { + 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') { + slot.remove(); + $('.slot', '#portal_config').each(function() { + $(this).removeData(); + }); + } + }); + } + }); + }; + }, + + + /** + * Portlets management + */ + + 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) + .append($('
').addClass('header padding-x-5') + .text(result.label)) + . append($('
').addClass('preview') + .html(result.preview || '')) + .contextMenu({ + menuSelector: '#portletMenu', + menuSelected: MyAMS.helpers.contextMenuHandler + }); + MyAMS.initContent($('.preview', portlet)); + var portlet_button = $('.btn-portlet', portlets); + if (portlet_button.exists()) { // Portlet added via drag & drop + portlet_button.replaceWith(portlet); + $('.portlet', portlets).each(function() { + $(this).removeData(); + }); + PyAMS_portal.template.sortPortlets(null, {item: portlet}); + } else { + portlet.appendTo(portlets); + } + portlets.sortable('refresh'); + }, + + editPortlet: function() { + return function(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')); + }; + }, + + 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); + $('.preview', portlet).html(result.preview); + MyAMS.initContent($('.preview', portlet)); + } + }, + + overPortlets: function(event, ui) { + $(ui.placeholder).attr('class', $(ui.item).attr('class')) + .removeClassPrefix('ui-') + .addClass('portlet-highlight') + .css('height', $(ui.item).outerHeight()); + }, + + sortPortlets: function(event, ui) { + 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')}, + 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')}}; + 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); + }); + } + }); + }, + + deletePortlet: function() { + return function(portlet) { + MyAMS.skin.bigBox({ + title: MyAMS.i18n.WARNING, + content: '  ' + MyAMS.i18n.DELETE_WARNING, + buttons: MyAMS.i18n.BTN_OK_CANCEL + }, function(button) { + 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')}, + function(result) { + if (result.status == 'success') { + portlet.remove(); + $('.portlet', '#portal_config').each(function() { + $(this).removeData(); + }); + } + }); + } + }); + }; + } + } + }; + +})(jQuery); diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/resources/js/portal.min.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/resources/js/portal.min.js Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +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 diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/resources/less/portal.less --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/resources/less/portal.less Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,146 @@ +#portal_config { + + .rows { + min-height: 15px; + } + .row { + position: relative; + margin: 5px 0; + padding: 2px 4px; + border: 1px solid rgba(199, 81, 0, 0.40); + border-top-width: 1.5em; + min-height: 20px; + cursor: move; + + > .row_id { + position: absolute; + right: 2px; + top: -1.6em; + } + .slot { + margin: 3px 0; + padding: 3px; + border: 1px solid rgba(98, 120, 128, 0.65); + border-bottom-width: 6px; + min-height: 20px!important; + + >.header { + background-color: rgba(98, 120, 128, 0.60); + color: white; + } + } + .portlet { + margin: 3px 0; + padding: 3px; + border: 1px solid rgba(98, 120, 128, 0.60); + min-height: 20px!important; + + >.header { + background-color: rgba(92, 109, 115, 0.80); + color: white; + } + } + } + .row-highlight { + margin: 5px 0; + border: 1px solid #c75100; + min-height: 40px; + } + + .slots { + min-height: 15px; + } + .slot-highlight { + margin: 3px 0; + border: 1px solid #7b939c; + min-height: 40px; + } + + .portlets { + min-height: 15px; + + &-hover { + background-color: silver; + } + &-active { + background-color: silver; + } + } + .portlet-highlight { + margin: 0; + border: 1px solid #7b939c; + min-height: 40px; + } + + &.container { + .col-12 { + float: left; + width: 100%!important; + } + .col-11 { + float: left; + width: 91.66666667%!important; + } + .col-10 { + float: left; + width: 83.33333333%!important; + } + .col-9 { + float: left; + width: 75%!important; + } + .col-8 { + float: left; + width: 66.66666667%!important; + } + .col-7 { + float: left; + width: 58.33333333%!important; + } + .col-6 { + float: left; + width: 50%!important; + } + .col-5 { + float: left; + width: 41.66666667%!important; + } + .col-4 { + float: left; + width: 33.33333333%!important; + } + .col-3 { + float: left; + width: 25%!important; + } + .col-2 { + float: left; + width: 16.66666667%!important; + } + .col-1 { + float: left; + width: 8.33333333%!important; + } + .col-0 { + float: left; + width: 100%!important; + opacity: 0.5; + + >.portlets { + display: none; + } + } + } + &.container-xs { + max-width: 750px!important; + } + &.container-sm { + width: 750px!important; + } + &.container-md { + width: 970px!important; + } + &.container-lg { + width: 1170px!important; + } +} diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/site.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/site.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,48 @@ +# +# 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 IPortalTemplateContainer +from pyams_utils.interfaces.site import ISiteGenerations +from zope.site.interfaces import INewLocalSite + +# import packages +from pyams_portal.template import PortalTemplateContainer +from pyams_utils.registry import utility_config +from pyams_utils.site import check_required_utilities +from pyramid.events import subscriber + + +REQUIRED_UTILITIES = ((IPortalTemplateContainer, '', PortalTemplateContainer, 'Portal templates'), ) + + +@subscriber(INewLocalSite) +def handle_new_local_site(event): + """Create a new templates container when a site is created""" + site = event.manager.__parent__ + check_required_utilities(site, REQUIRED_UTILITIES) + + +@utility_config(name='PyAMS portal', provides=ISiteGenerations) +class PortalGenerationsChecker(object): + """Portal generations checker""" + + generation = 1 + + def evolve(self, site, current=None): + """Check for required utilities""" + check_required_utilities(site, REQUIRED_UTILITIES) diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/slot.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/slot.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,159 @@ +# +# 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 ISlotConfiguration, IPortalTemplateConfiguration, IPortalTemplate, IPortalPage + +# import packages +from persistent import Persistent +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']) + 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']) + + def __init__(self, slot_name, **kwargs): + self.slot_name = slot_name + self.xs_width = 12 + self.sm_width = 12 + self.md_width = 12 + self.lg_width = 12 + for key, value in kwargs.items(): + setattr(self, key, value) + + @property + def template(self): + if IPortalTemplate.providedBy(self.__parent__): + return self.__parent__ + else: + 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 + 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 + + @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 + + def get_css_class(self, device=None): + if not device: + device = ('xs', 'sm', 'md', 'lg') + elif isinstance(device, str): + device = (device, ) + result = [self.css_class or ''] + for attr in device: + width = getattr(self, attr + '_width') + result.append('col-{0}-{1}'.format(attr, width)) + return ' '.join(result) + + def get_width(self, device=None): + if not device: + device = ('xs', 'sm', 'md', 'lg') + elif isinstance(device, str): + device = (device, ) + result = {} + for attr in device: + result[attr] = getattr(self, attr + '_width') + return result + + def set_width(self, width, device=None): + if not device: + device = ('xs', 'sm', 'md', 'lg') + elif isinstance(device, str): + device = (device, ) + for attr in device: + setattr(self, attr + '_width', width) diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/template.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/template.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,441 @@ +# +# 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, IPortalTemplateConfiguration, IPortalContext, IPortalPage, \ + IPortletConfiguration, IPortlet, IPortalTemplateContainer, IPortalWfTemplate, IPortalTemplateContainerConfiguration +from pyams_workflow.interfaces import IWorkflowVersions +from zope.annotation.interfaces import IAnnotations +from zope.lifecycleevent.interfaces import IObjectAddedEvent, IObjectRemovedEvent +from zope.traversing.interfaces import ITraversable + +# import packages +from persistent import Persistent +from persistent.list import PersistentList +from persistent.mapping import PersistentMapping +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.request import check_request +from pyramid.events import subscriber +from pyramid.threadlocal import get_current_registry +from zope.componentvocabulary.vocabulary import UtilityVocabulary +from zope.container.contained import Contained +from zope.container.folder import Folder +from zope.copy import clone +from zope.interface import implementer +from zope.lifecycleevent import ObjectCreatedEvent +from zope.location.location import locate +from zope.schema.fieldproperty import FieldProperty +from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm, getVocabularyRegistry + + +@implementer(IPortalTemplateContainer) +class PortalTemplateContainer(Folder): + """Portal template container""" + + +@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' + + +@adapter_config(context=IPortalTemplateContainer, provides=IPortalTemplateContainerConfiguration) +def PortalTemplateContainerConfigurationFactory(context): + """Portal template container configuration factory""" + annotations = IAnnotations(context) + config = annotations.get(PORTAL_TEMPLATE_CONTAINER_CONFIGURATION_KEY) + if config is None: + config = annotations[PORTAL_TEMPLATE_CONTAINER_CONFIGURATION_KEY] = PortalTemplateContainerConfiguration() + get_current_registry().notify(ObjectCreatedEvent(config)) + locate(config, context) + return config + + +@implementer(IPortalTemplate) +class PortalTemplate(Persistent, Contained): + """Portal template persistent 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) +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__) + + +@subscriber(IObjectRemovedEvent, context_selector=IPortalWfTemplate) +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__) + + +class PortalTemplatesVocabulary(UtilityVocabulary): + """Portal templates vocabulary""" + + interface = IPortalWfTemplate + nameOnly = True + +getVocabularyRegistry().register('PyAMS portal templates', PortalTemplatesVocabulary) + + +@implementer(IPortalTemplateConfiguration) +class PortalTemplateConfiguration(Persistent, Contained): + """Portal template configuration""" + + 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']) + + 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() + + 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): + """Change template row order""" + 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(): + 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 + + @property + def slot_names(self): + if IPortalTemplate.providedBy(self.__parent__): + return self._slot_names + else: + return IPortalTemplateConfiguration(self.__parent__).slot_names + + @slot_names.setter + def slot_names(self, value): + self._slot_names = value + + @property + def slot_order(self): + if IPortalTemplate.providedBy(self.__parent__): + return self._slot_order + else: + return IPortalTemplateConfiguration(self.__parent__).slot_order + + @slot_order.setter + def slot_order(self, value): + self._slot_order = value + + @property + def slots(self): + if IPortalTemplate.providedBy(self.__parent__): + return self._slots + else: + return IPortalTemplateConfiguration(self.__parent__).slots + + @slots.setter + def slots(self, value): + self._slots = value + + def add_slot(self, slot_name, row_id=None): + assert slot_name not in self.slot_names + self.slot_names.append(slot_name) + if row_id is None: + row_id = 0 + # init slots order + 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__) + return row_id, slot_name + + 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: + if slot_name in self.slot_order[row_id]: + return row_id + + def get_slots(self, row_id): + """Get ordered slots list""" + return self.slot_order.get(row_id, []) + + def get_slots_width(self, device=None): + """Get slots width""" + result = {} + for slot_name, config in self.slot_config.items(): + result[slot_name] = config.get_width(device) + return result + + def set_slot_width(self, slot_name, device, width): + """Set slot width""" + self.slot_config[slot_name].set_width(width, device) + + def get_slot_configuration(self, slot_name): + """Get slot configuration""" + if slot_name not in self.slot_names: + return None + config = self.slot_config.get(slot_name) + if config is None: + if IPortalTemplate.providedBy(self.__parent__): + config = SlotConfiguration() + else: + config = clone(IPortalTemplateConfiguration(self.__parent__).get_slot_configuration(slot_name)) + config.inherit_parent = True + self.slot_config[slot_name] = config + locate(config, self.__parent__) + return config + + def delete_slot(self, slot_name): + """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] + 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) + + 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 + 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 + return {'portlet_name': portlet_name, + 'slot_name': slot_name, + 'position': position, + 'label': check_request().localizer.translate(portlet.label)} + + 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'] + 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']) + + 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): + """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] + + +class PortalTemplateSlotsVocabulary(SimpleVocabulary): + """Portal template slots vocabulary""" + + def __init__(self, context): + config = IPortalTemplateConfiguration(context) + terms = [SimpleTerm(slot_name) for slot_name in sorted(config.slot_names)] + super(PortalTemplateSlotsVocabulary, self).__init__(terms) + +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""" + annotations = IAnnotations(context) + config = annotations.get(TEMPLATE_CONFIGURATION_KEY) + if config is None: + config = annotations[TEMPLATE_CONFIGURATION_KEY] = PortalTemplateConfiguration() + get_current_registry().notify(ObjectCreatedEvent(config)) + locate(config, context) + 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(context=IPortletConfiguration, provides=IPortalTemplateConfiguration) +def PortalPortletConfigurationFactory(context): + """Portal portlet configuration factory""" + return IPortalTemplateConfiguration(context.__parent__) diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/tests/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/tests/__init__.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,1 @@ + diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/tests/test_utilsdocs.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/tests/test_utilsdocs.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,59 @@ +# +# 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. +# + +""" +Generic Test case for pyams_portal doctest +""" +__docformat__ = 'restructuredtext' + +import unittest +import doctest +import sys +import os + + +current_dir = os.path.dirname(__file__) + +def doc_suite(test_dir, setUp=None, tearDown=None, globs=None): + """Returns a test suite, based on doctests found in /doctest.""" + suite = [] + if globs is None: + globs = globals() + + flags = (doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE | + doctest.REPORT_ONLY_FIRST_FAILURE) + + package_dir = os.path.split(test_dir)[0] + if package_dir not in sys.path: + sys.path.append(package_dir) + + doctest_dir = os.path.join(package_dir, 'doctests') + + # filtering files on extension + docs = [os.path.join(doctest_dir, doc) for doc in + os.listdir(doctest_dir) if doc.endswith('.txt')] + + for test in docs: + suite.append(doctest.DocFileSuite(test, optionflags=flags, + globs=globs, setUp=setUp, + tearDown=tearDown, + module_relative=False)) + + return unittest.TestSuite(suite) + +def test_suite(): + """returns the test suite""" + return doc_suite(current_dir) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') + diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/tests/test_utilsdocstrings.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/tests/test_utilsdocstrings.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,62 @@ +# +# 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. +# + +""" +Generic Test case for pyams_portal doc strings +""" +__docformat__ = 'restructuredtext' + +import unittest +import doctest +import sys +import os + + +current_dir = os.path.abspath(os.path.dirname(__file__)) + +def doc_suite(test_dir, globs=None): + """Returns a test suite, based on doc tests strings found in /*.py""" + suite = [] + if globs is None: + globs = globals() + + flags = (doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE | + doctest.REPORT_ONLY_FIRST_FAILURE) + + package_dir = os.path.split(test_dir)[0] + if package_dir not in sys.path: + sys.path.append(package_dir) + + # filtering files on extension + docs = [doc for doc in + os.listdir(package_dir) if doc.endswith('.py')] + docs = [doc for doc in docs if not doc.startswith('__')] + + for test in docs: + fd = open(os.path.join(package_dir, test)) + content = fd.read() + fd.close() + if '>>> ' not in content: + continue + test = test.replace('.py', '') + location = 'pyams_portal.%s' % test + suite.append(doctest.DocTestSuite(location, optionflags=flags, + globs=globs)) + + return unittest.TestSuite(suite) + +def test_suite(): + """returns the test suite""" + return doc_suite(current_dir) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/workflow.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/workflow.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,223 @@ +# +# 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 000000000000 -r 6f99128c6d48 src/pyams_portal/zmi/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/zmi/__init__.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,20 @@ +# +# 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 + +# import packages diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/zmi/container.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/zmi/container.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,204 @@ +# +# 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 IPortalTemplateContainer, IPortalWfTemplate, IPortalTemplateContainerConfiguration +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_workflow.interfaces import IWorkflowVersions +from pyams_zmi.interfaces.menu import IControlPanelMenu +from pyams_zmi.layer import IAdminLayer +from z3c.table.interfaces import IColumn, IValues +from zope.component.interfaces import ISite + +# import packages +from pyams_form.form import AJAXEditForm +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.viewlet.menu import MenuItem +from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter +from pyams_utils.registry import query_utility +from pyams_utils.traversing import get_parent +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 +from pyramid.view import view_config +from z3c.form import field +from zope.interface import implementer, Interface + +from pyams_portal import _ + + +# +# Template container views +# + +@adapter_config(context=(IPortalTemplateContainer, IAdminLayer, ITable), provides=ITableElementEditor) +class PortalTemplateContainerTableElementEditor(DefaultElementEditorAdapter): + """Portal template container table element editor""" + + view_name = 'portal-templates.html' + modal_target = False + + @property + def url(self): + site = get_parent(self.context, ISite) + return resource_url(site, self.request, 'admin.html#{0}'.format(self.view_name)) + + +@viewlet_config(name='portal-templates.menu', context=ISite, layer=IAdminLayer, manager=IControlPanelMenu, + permission='system.view', weight=20) +@viewletmanager_config(name='portal-templates.menu', context=ISite, layer=IAdminLayer) +@implementer(IPortalTemplateContainerMenu) +class PortalTemplateContainerMenuItem(MenuItem): + """Portal template container menu""" + + label = _("Portal templates") + icon_class = 'fa fa-fw fa-columns' + url = '#portal-templates.html' + + +class PortalTemplateContainerTable(BaseTable): + """Portal template container table""" + + id = 'portal_templates_table' + title = _("Shared portal templates") + + @property + def data_attributes(self): + manager = query_utility(IPortalTemplateContainer) + attributes = super(PortalTemplateContainerTable, self).data_attributes + table_attrs = {'data-ams-location': absolute_url(manager, self.request), + 'data-ams-plugins': 'pyams_workflow pyams_portal', + 'data-ams-plugin-pyams_workflow-src': "/--static--/pyams_workflow/js/workflow{MyAMS.devext}.js", + 'data-ams-plugin-pyams_portal-src': "/--static--/pyams_portal/js/portal{MyAMS.devext}.js", + 'data-ams-plugin-pyams_portal-css': "/--static--/pyams_portal/css/portal{MyAMS.devext}.css"} + if 'table' in attributes: + attributes['table'].update(table_attrs) + else: + attributes['table'] = table_attrs + return attributes + + +@adapter_config(context=(IPortalWfTemplate, IAdminLayer, PortalTemplateContainerTable), provides=ITableElementEditor) +class PortalTemplateTableElementEditor(DefaultElementEditorAdapter): + """Portal template table element editor""" + + modal_target = False + + @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 + + +@adapter_config(name='name', context=(Interface, IAdminLayer, PortalTemplateContainerTable), provides=IColumn) +class PortalTemplateContainerNameColumn(WorkflowContentNameColumn): + """Portal template container name column""" + + name_field = 'name' + + +@adapter_config(name='trash', context=(Interface, IAdminLayer, PortalTemplateContainerTable), provides=IColumn) +class PortalTemplateContainerTrashColumn(TrashColumn): + """Portal template container trash column""" + + icon_hint = _("Delete template") + permission = 'portal.templates.manage' + + +@adapter_config(context=(ISite, IAdminLayer, PortalTemplateContainerTable), provides=IValues) +class PortalTemplateContainerValuesAdapter(ContextRequestViewAdapter): + """Portal template container values adapter""" + + @property + def values(self): + manager = query_utility(IPortalTemplateContainer) + if manager is not None: + return manager.values() + return () + + +@pagelet_config(name='portal-templates.html', context=ISite, layer=IPyAMSLayer, permission='system.view') +@implementer(IInnerPage) +class PortalTemplateContainerView(AdminView, ContainerView): + """Portal template container view""" + + table_class = PortalTemplateContainerTable + + def __init__(self, context, request): + super(PortalTemplateContainerView, self).__init__(context, request) + + +@adapter_config(context=(ISite, IAdminLayer, PortalTemplateContainerView), provides=IPageHeader) +class PortalTemplateContainerHeaderAdapter(DefaultPageHeaderAdapter): + """Portal template container header adapter""" + + icon_class = 'fa fa-fw fa-columns' + title = _("Portal") + subtitle = _("Portal templates") + + +# +# Templates container configuration views +# + +@viewlet_config(name='templates-container-configuration.menu', context=ISite, layer=IAdminLayer, + manager=IPortalTemplateContainerMenu, permission='system.view', weight=1) +class PortalTemplatesContainerPropertiesMenu(MenuItem): + """Portal template container configuration menu""" + + label = _("Selected portlets...") + icon_class = 'fa-thumb-tack' + + url = 'properties.html' + modal_target = True + + def get_url(self): + container = query_utility(IPortalTemplateContainer) + return absolute_url(container, self.request, self.url) + + +@pagelet_config(name='properties.html', context=IPortalTemplateContainer, layer=IPyAMSLayer, + permission='system.view') +class PortalTemplateContainerPropertiesEditForm(AdminDialogEditForm): + """Portal template container properties edit form""" + + title = _("Portal templates container") + legend = _("Edit selected portlets") + icon_css_class = 'fa fa-fw fa-thumb-tack' + + fields = field.Fields(IPortalTemplateContainerConfiguration) + ajax_handler = 'properties.json' + edit_permission = 'system.manage' + + +@view_config(name='properties.json', context=IPortalTemplateContainer, request_type=IPyAMSLayer, + permission='system.manage', renderer='json', xhr=True) +class PortalTemplateContainerPropertiesAJAXEditForm(AJAXEditForm, PortalTemplateContainerPropertiesEditForm): + """Portal template container properties edit form, JSON renderer""" diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/zmi/interfaces.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/zmi/interfaces.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,30 @@ +# +# 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_form.interfaces.form import IForm +from pyams_skin.interfaces.viewlet import IMenuItem + +# import packages + + +class IPortalTemplateContainerMenu(IMenuItem): + """Portal template container menu interface""" + + +class IPortletConfigurationEditor(IForm): + """Portlet configuration editor interface""" diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/zmi/portlet.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/zmi/portlet.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,63 @@ +# +# 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 IPortlet +from z3c.form.interfaces import HIDDEN_MODE + +# import packages +from pyams_zmi.form import AdminDialogEditForm +from pyramid.url import resource_url +from z3c.form import field + +from pyams_portal import _ + + +class PortletConfigurationEditor(AdminDialogEditForm): + """Base portlet configuration editor""" + + @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)) + + legend = _("Edit portlet configuration") + dialog_class = 'modal-large' + + interface = None + edit_permission = 'portal.templates.manage' + + def get_form_action(self): + return resource_url(self.context.__parent__, self.request, self.request.view_name) + + def get_ajax_handler(self): + return resource_url(self.context.__parent__, self.request, self.ajax_handler) + + @property + def fields(self): + fields = field.Fields(self.interface) + if not self.getContent().can_inherit: + fields = fields.omit('inherit_parent') + return fields + + def updateWidgets(self, prefix=None): + super(PortletConfigurationEditor, self).updateWidgets(prefix) + self.widgets['slot_name'].mode = HIDDEN_MODE + self.widgets['position'].mode = HIDDEN_MODE diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/zmi/portlets/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/zmi/portlets/__init__.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,20 @@ +# +# 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 + +# import packages diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/zmi/portlets/context.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/zmi/portlets/context.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,51 @@ +# +# 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 + +# 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='system.view') +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 000000000000 -r 6f99128c6d48 src/pyams_portal/zmi/portlets/image.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/zmi/portlets/image.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,51 @@ +# +# 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_portal.interfaces import IPortletPreviewer +from pyams_portal.portlet import PortletPreviewer +from pyams_template.template import template_config + +__docformat__ = 'restructuredtext' + + +# import standard library + +# import interfaces +from pyams_pagelet.interfaces import IPagelet +from pyams_portal.portlets.image.interfaces import IImagePortletConfiguration +from pyams_skin.layer import IPyAMSLayer + +# import packages +from pyams_form.form import AJAXEditForm +from pyams_pagelet.pagelet import pagelet_config +from pyams_portal.zmi.portlet import PortletConfigurationEditor +from pyams_utils.adapter import adapter_config +from zope.interface import Interface + + +@pagelet_config(name='properties.html', context=IImagePortletConfiguration, request_type=IPyAMSLayer, + permission='system.view') +class ImagePortletConfigurationEditor(PortletConfigurationEditor): + """Image portlet configuration editor""" + + interface = IImagePortletConfiguration + + +@adapter_config(name='properties.json', context=(IImagePortletConfiguration, IPyAMSLayer), provides=IPagelet) +class ImagePortletConfigurationAJAXEditor(AJAXEditForm, ImagePortletConfigurationEditor): + """Image portlet configuration editor, AJAX renderer""" + + +@adapter_config(context=(Interface, IPyAMSLayer, Interface, IImagePortletConfiguration), + provides=IPortletPreviewer) +@template_config(template='templates/image-preview.pt', layer=IPyAMSLayer) +class ImagePortletPreviewer(PortletPreviewer): + """Image portlet previewer""" diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/zmi/portlets/templates/context-preview.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/zmi/portlets/templates/context-preview.pt Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,1 @@ +This is my preview !! diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/zmi/portlets/templates/image-preview.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/zmi/portlets/templates/image-preview.pt Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,32 @@ + + + + + + + + +
+ + + + +
+
+
+ +
+ + + + +
+
+
diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/zmi/template/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/zmi/template/__init__.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,122 @@ +# +# 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 000000000000 -r 6f99128c6d48 src/pyams_portal/zmi/template/config.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/zmi/template/config.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,483 @@ +# +# 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.page import DefaultPageHeaderAdapter + +__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, IContentTitle +from pyams_skin.interfaces.viewlet import IToolbarAddingMenu +from pyams_skin.layer import IPyAMSLayer +from pyams_workflow.interfaces import IWorkflowState, IWorkflowVersions +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 STATUS_LABELS, STATUS_IDS, PUBLISHED, ARCHIVED +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, ContextRequestViewAdapter +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='system.view', 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='system.view') +@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='system.view', 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='system.view') +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='system.view') +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 000000000000 -r 6f99128c6d48 src/pyams_portal/zmi/template/page.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/zmi/template/page.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,153 @@ +# +# 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 pyramid.exceptions import NotFound +from pyramid.view import view_config +from pyams_form.form import AJAXEditForm +from pyams_pagelet.interfaces import PageletCreatedEvent, IPagelet +from pyams_pagelet.pagelet import pagelet_config +from pyams_portal.interfaces import IPortalContext, IPortalPage, IPortalTemplateConfiguration +from pyams_portal.workflow import PUBLISHED, ARCHIVED +from pyams_portal.zmi.template.config import PortalTemplateConfigView +from pyams_skin.interfaces import IInnerPage +from pyams_skin.layer import IPyAMSLayer +from pyams_skin.viewlet.menu import MenuItem +from pyams_template.template import template_config +from pyams_utils.url import absolute_url +from pyams_viewlet.viewlet import viewlet_config +from pyams_workflow.interfaces import IWorkflowVersions, IWorkflowState +from pyams_zmi.form import AdminDialogEditForm +from pyams_zmi.interfaces.menu import ISiteManagementMenu, IPropertiesMenu +from pyams_zmi.layer import IAdminLayer +from pyams_zmi.view import AdminView + +__docformat__ = 'restructuredtext' + + +# import standard library + +# import interfaces + +# import packages +from z3c.form import field +from zope.interface import implementer + +from pyams_portal import _ + + + +@viewlet_config(name='template-properties.menu', context=IPortalContext, layer=IAdminLayer, + manager=IPropertiesMenu, permission='manage', 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') +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' + + def getContent(self): + return IPortalPage(self.context) + + +@view_config(name='template-properties.json', context=IPortalContext, request_type=IPyAMSLayer, + permission='manage', 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', 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') +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') and \ + IWorkflowState(self.get_context()).state not in (PUBLISHED, ARCHIVED) + + +@view_config(name='portlet-properties.html', context=IPortalContext, request_type=IPyAMSLayer, permission='manage') +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 000000000000 -r 6f99128c6d48 src/pyams_portal/zmi/template/templates/config.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/zmi/template/templates/config.pt Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,160 @@ + +
+
+ + +

Title

+ + +
+
+ +
+
+ +
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + +
+
+
diff -r 000000000000 -r 6f99128c6d48 src/pyams_portal/zmi/template/workflow.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_portal/zmi/template/workflow.py Wed Jun 17 09:58:33 2015 +0200 @@ -0,0 +1,176 @@ +# +# 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')}