Personal tools
Skip to content. | Skip to navigation
Introduction This product helps integrating the collective.geo.* packages and aims to provide some sensible defaults. Besides some integration glue it defines a new interface IGeocodableLocation that can be used to create adapters that knows how to represent the location of a content type with address-like fields as a string suitable for passing to a geocoding API. Purpose Automatic geocoding of IGeoreferenceable content types via an IGeocodableLocation adapter Caching of geocoding responses Only trigger geocoding lookups if location related fields on the content item changed Facilitate doing automatic geocoding based on location fields and still allow for manually setting custom coordinates Usage Automatically geocoding your content types In order for your content types to be automatically geocoded on ObjectEdited or ObjectInitialized events, you need to create an adapter for your content type that implements IGeocodableLocation and knows how to build a geocodable location string from the content type's location related fields. In order to implement the interface you need to define a getLocationString method on your adapter that returns the complete location as a comma separated string, with the location parts getting less specific from left to right.
ftw.jsondump provides JSON representations for Plone objects. By using adapters the JSON representation can easily be customized.
Motivation Developing and maintaining complex Plone workflows is a time-consuming and cumbersome endeavor. Dozens of permissions need to be managed for different roles and different workflow states. Usually, this has to be done directly in the ZMI of Zope by selecting or unselecting thousands of checkboxes. This process has been shown to be very tedious and prone to errors. Furthermore, it is no simple task to document the workflow and the associated design decisions which led to the resulting configuration of permissions and roles. The extension or adaption of an existing workflow becomes very difficult, leading to workflows which are barely maintainable. Another problem poses the communication between workflow integrator and customer. The security system of Zope is based on a role-based access control (RBAC) which is intrinsically complex due to its use of roles, permissions, and workflow states. Experience has shown that these security concepts can be hard to convey to customers. How it works ftw.lawgiver helps solving these problems by using a DSL to describe how a workflow should work. The lawgiver then generates the complete workflow definition (definition.xml) based on this specification. By separating this specification from the resulting workflow definition (which is in XML) the specification does not have to use permissions--handling the permissions is the job of the lawgiver. Using the specification file the workflow can easily be regenerated at any time and will handle additional permissions automatically when regenerated. However, it is still the task of the developer to regenerate the definition.xml when more or other permissions have to be managed. He or she have to make sure that the workflow is properly installed with an upgrade step / reindexing security.
ftw.testbrowser is a browser library for testing Plone web sites and applications. It integrations directly into Plone / Zope and uses lxml for parsing and querying pages. It supports all the basic features such as filling forms.
Browser testing with splinter Splinter is a library which provides a common browser API with a driver for zope.testbrowser. The ftw.testing package provides integration of Splinter with Plone using Page Objects. For using the splinter features, use the splinter extras require: ftw.testing [splinter] Setting a package up for browser tests It’s easy to setup your package for browser tests: Add a test-dependency to ftw.testing in your setup.py: tests_require = [ 'ftw.testing[splinter]', ] setup(name='my.package', ... tests_require=tests_require, extras_require=dict(tests=tests_require), ) In your testing.py use the FunctionalSplinterTesting layer wrapper: from ftw.testing import FunctionalSplinterTesting from plone.app.testing import PLONE_FIXTURE from plone.app.testing import PloneSandboxLayer from plone.app.testing import applyProfile from zope.configuration import xmlconfig class MyPackageLayer(PloneSandboxLayer): defaultBases = (PLONE_FIXTURE,) def setUpZope(self, app, configurationContext): import my.package xmlconfig.file('configure.zcml', my.package) def setUpPloneSite(self, portal): applyProfile(portal, 'my.package:default') MY_PACKAGE_FIXTURE = MyPackageLayer() MY_PACKAGE_FUNCTIONAL_TESTING = FunctionalSplinterTesting( bases=(MY_PACKAGE_FIXTURE, ), name="my.package:functional") Write tests using the Plone Page Objects: from ftw.testing import browser from ftw.testing.pages import Plone from my.package.testing import MY_PACKAGE_FUNCTIONAL_TESTING from plone.app.testing import SITE_OWNER_NAME from plone.app.testing import SITE_OWNER_PASSWORD from unittest2 import TestCase class TestDocument(TestCase): layer = MY_PACKAGE_FUNCTIONAL_TESTING def test_add_document(self): Plone().login(SITE_OWNER_NAME, SITE_OWNER_PASSWORD) Plone().visit_portal() Plone().create_object('Page', {'Title': 'Foo', 'Body Text': '<b>Hello World</b>'}) self.assertTrue(browser().is_text_present('Hello World')) Writing Page Objects Write your own Page Objects for your views and content types. Put a module pages.py in your tests folder: from ftw.testing.pages import Plone class MyContentType(Plone): def create_my_content(self, title, text): self.create_object('MyContent', {'Title': title, 'Body Text': text}) return self The Page Object should have methods for all features of your view. Using the Plone Page Objects The Plone page object provided by ftw.testing already has the most important features built in, such as: portal_url handling Login Accessing Headings, <body>-CSS-classes, status messages Adding content TinyMCE handling Currently it’s best to just look in the page object code. MockTestCase ftw.testing provides an advanced MockTestCase which provides bases on the plone.mocktestcase MockTestCase. from ftw.testing import MockTestCase The following additional methods are available: self.providing_mock(interfaces, *args, **kwargs) Creates a mock which provides interfaces. self.mock_interface(interface, provides=None, *args, **kwargs) Creates a mock object implementing interface. The mock does not only provide interface, but also use it as specification and asserts that the mocked methods do exist on the interface. self.stub(*args, **kwargs) Creates a stub. It acts like a mock but has no assertions. self.providing_stub(interfaces, *args, **kwargs) Creates a stub which provides interfaces. self.stub_interface(interface, provides=None, *args, **kwargs) Does the same as mock_interface, but disables counting of expected method calls and attribute access. See “Mocking vs. stubbing” below. self.set_parent(context, parent_context) Stubs the context so that its acquisition parent is parent_context. Expects at least context to be a mock or a stub. Returns the context. self.stub_request(interfaces=[], stub_response=True, content_type='text/html', status=200) Returns a request stub which can be used for rendering templates. With the stub_response option, you can define if the request should stub a response by itself. The other optional arguments: content_type: Defines the expected output content type of the response. status: Defines the expected status code of the response. self.stub_response(request=None, content_type='text/html', status=200)) Returns a stub response with some headers and options. When a request is given the response is also added to the given request. The other optional arguments: content_type: Defines the expected output content type of the response. status: Defines the expected status code of the response. self.assertRaises(*args, **kwargs) Uses unittest2 implementation of assertRaises instead of unittest implementation. It also fixes a problem in mock_tool, where the getToolByName mock had assertions which is not very useful in some cases. Mocking vs. stubbing A mock is used for testing the communication between two objects. It asserts method calls. This is used when a test should not test if a object has a specific state after doing something (e.g. it has it’s attribute xy set to something), but if the object does something with another object. If for example an object Foo sends an email when method bar is called, we could mock the sendmail object and assert on the send-email method call. On the other hand we often have to test the state of an object (attribute values) after doing something. This can be done without mocks by just calling the method and asserting the attribute values. But then we have to set up an integration test and install plone, which takes very long. For testing an object with dependencies to other parts of plone in a unit test, we can use stubs for faking other (separately tested) parts of plone. Stubs work like mocks: you can “expect” a method call and define a result. The difference between stubs and mocks is that stubs do not assert the expectations, so there will be no errors if something expected does not happen. So when using stubs we can assert the state without asserting the communcation between objects. Component registry layer The MockTestCase is able to mock components (adapters, utilities). It cleans up the component registry after every test. But when we use a ZCML layer, loading the ZCML of the package it should use the same component registry for all tests on the same layer. The ComponentRegistryLayer is a layer superclass for sharing the component registry and speeding up tests. Usage: from ftw.testing.layer import ComponentRegistryLayer class ZCMLLayer(ComponentRegistryLayer): def setUp(self): super(ZCMLLayer, self).setUp() import my.package self.load_zcml_file('configure.zcml', my.package) ZCML_LAYER = ZCMLLayer() Be aware that ComponentRegistryLayer is a base class for creating your own layer (by subclassing ComponentRegistryLayer) and is not usable with defaultBases directly. This allows us to use the functions load_zcml_file and load_zcml_string. Mailing test helper The Mailing helper object mocks the mailhost and captures sent emails. The emails can then be easily used for assertions. Usage: from ftw.testing.mailing import Mailing import transaction class MyTest(TestCase): layer = MY_FUNCTIONAL_TESTING def setUp(self): Mailing(self.layer['portal']).set_up() transaction.commit() def tearDown(self): Mailing(self.layer['portal']).tear_down() def test_mail_stuff(self): portal = self.layer['portal'] do_send_email() mail = Mailing(portal).pop() self.assertEquals('Subject: ...', mail) Freezing datetime.now() When testing code which depends on the current time, it is necessary to set the current time to a specific time. The freeze context manager makes that really easy: from ftw.testing import freeze from datetime import datetime with freeze(datetime(2014, 5, 7, 12, 30)): The freeze context manager patches the datetime module, the time module and supports the Zope DateTime module. It removes the patches when exiting the context manager. Updating the freezed time from ftw.testing import freeze from datetime import datetime with freeze(datetime(2014, 5, 7, 12, 30)) as clock: clock.forward(days=2) clock.backward(minutes=15) You can use the timedelta arguments`(https://docs.python.org/2/library/datetime.html#datetime.timedelta)_ for “forward` and backward. Static UUIDS When asserting UUIDs it can be annoying that they change at each test run. The staticuid decorator helps to fix that by using static uuids which are prefixed and counted within a scope, usually a test case: from ftw.testing import staticuid from plone.app.testing import PLONE_INTEGRATION_TESTING from unittest2 import TestCase class MyTest(TestCase): layer = PLONE_INTEGRATION_TESTING @staticuid() def test_all_the_things(self): doc = self.portal.get(self.portal.invokeFactory('Document', 'the-document')) self.assertEquals('testallthethings0000000000000001', IUUID(doc)) @staticuid('MyUIDS') def test_a_prefix_can_be_set(self): doc = self.portal.get(self.portal.invokeFactory('Document', 'the-document')) self.assertEquals('MyUIDS00000000000000000000000001', IUUID(doc)) Generic Setup uninstall test ftw.testing provides a test superclass for testing uninstall profiles. The test makes a Generic Setup snapshot before installing the package, then installs and uninstalls the package, creates another snapshot and diffs it. The package is installed without installing its dependencies, because it should not include uninstalling dependencies in the uninstall profile. Appropriate testing layer setup is included and the test runs on a seperate layer which should not interfere with other tests. Simple example: from ftw.testing.genericsetup import GenericSetupUninstallMixin from ftw.testing.genericsetup import apply_generic_setup_layer from unittest2 import TestCase @apply_generic_setup_layer class TestGenericSetupUninstall(TestCase, GenericSetupUninstallMixin): package = 'my.package' The my.package is expected to have a Generic Setup profile profile-my.package:default for installing the package and a profile-my.package:uninstall for uninstalling the package. It is expected to use z3c.autoinclude entry points for loading its ZCML. The options are configured as class variables: package The dotted name of the package as string, which is used for things such as guessing the Generic Setup profile names. This is mandatory. autoinclude (True) This makes the testing fixture load ZCML using the z3c.autoinclude entry points registered for the target plone. additional_zcml_packages (()) Use this if needed ZCML is not loaded using the autoinclude option, e.g. when you need to load testing zcml. Pass in an iterable of dottednames of packages, which contain a configure.zcml. additional_products (()) A list of additional Zope products to install. install_profile_name (default) The Generic Setup install profile name postfix. skip_files (()) An iterable of Generic Setup files (e.g. ("viewlets.xml",)) to be ignored in the diff. This is sometimes necessary, because not all components can and should be uninstalled properly. For example viewlet orders cannot be removed using Generic Setup - but this is not a problem they do no longer take effect when the viewlets / viewlet managers are no longer registered. Full example: from ftw.testing.genericsetup import GenericSetupUninstallMixin from ftw.testing.genericsetup import apply_generic_setup_layer from unittest2 import TestCase @apply_generic_setup_layer class TestGenericSetupUninstall(TestCase, GenericSetupUninstallMixin): package = 'my.package' autoinclude = False additional_zcml_packages = ('my.package', 'my.package.tests') additional_products = ('another.package', ) install_profile_name = 'default' skip_files = ('viewlets.xml', 'rolemap.xml') Disabling quickinstaller snapshots Quickinstaller normally makes a complete Generic Setup (GS) snapshot before and after installing each GS profile, in order to be able to uninstall the profile afterwards. In tests we usually don’t need this feature and want to disable it to speed up tests. The ftw.testing.quickinstaller module provides a patcher for replacing the quickinstaller event handlers to skip creating snapshots. Usually we want to do this early (when loading testing.py), so that all the tests are speeding up. However, some tests which involve quickinstaller rely on having the snapshots made (see previous section about uninstall tests). Therefore the snapshot patcher object provides context managers for temporarily enabling / disabling the snapshot feature. Usage: Disable snapshots early, so that everything is fast. Usually this is done in the testing.py in module scope, so that it happens already when the testrunner imports the tests: from ftw.testing.quickinstaller import snapshots from plone.app.testing import PloneSandboxLayer snapshots.disable() class MyPackageLayer(PloneSandboxLayer): ... When testing quickinstaller snapshot related things, such as uninstalling, the snapshots can be re-enabled for a context manager or in general: from ftw.testing.quickinstaller import snapshots snapshots.disable() with snapshots.enabled(): snapshots.enable() with snapshots.disabled(): Testing Layers Component registry isolation layer plone.app.testing’s default testing layers (such as PLONE_FIXTURE) do not isolate the component registry for each test. ftw.testing’s COMPONENT_REGISTRY_ISOLATION testing layer isolates the component registry for each test, provides a stacked ZCML configuration context and provides the methods load_zcml_string and load_zcml_file for loading ZCML. Example: from ftw.testing.layer import COMPONENT_REGISTRY_ISOLATION from plone.app.testing import IntegrationTesting from plone.app.testing import PloneSandboxLayer from zope.configuration import xmlconfig class MyPackageLayer(PloneSandboxLayer): defaultBases = (COMPONENT_REGISTRY_ISOLATION,) def setUpZope(self, app, configurationContext): import my.package xmlconfig.file('configure.zcml', ftw.package, context=configurationContext) MY_PACKAGE_FIXTURE = MyPackageLayer() MY_PACKAGE_INTEGRATION = IntegrationTesting( bases=(MY_PACKAGE_FIXTURE, COMPONENT_REGISTRY_ISOLATION), name='my.package:integration') from unittest2 import TestCase class TestSomething(TestCase): layer = MY_PACKAGE_INTEGRATION def test(self): self.layer['load_zcml_string']('<configure>...</configure>') Temp directory layer The TEMP_DIRECTORY testing layer creates an empty temp directory for each test and removes it recursively on tear down. The path to the directory can be accessed with the temp_directory key. Usage example: from unittest2 import TestCase from ftw.testing.layer import TEMP_DIRECTORY class TestSomething(TestCase): layer = TEMP_DIRECTORY def test(self): path = self.layer['temp_directory'] Console script testing layer The console script layer helps testing console scripts. On layer setup it creates and executes an isolated buildout with the package under development, which creates all console scripts of this package. This makes it easy to test console scripts by really executing them. Usage example: from ftw.testing.layer import ConsoleScriptLayer CONSOLE_SCRIPT_TESTING = ConsoleScriptLayer('my.package') from my.package.testing import CONSOLE_SCRIPT_TESTING from unittest2 import TestCase class TestConsoleScripts(TestCase): layer = CONSOLE_SCRIPT_TESTING def test_executing_command(self): exitcode, output = self.layer['execute_script']('my-command args') self.assertEqual('something\n', output) Be aware that the dependency zc.recipe.egg is required for building the console scripts. You may put the dependency into your tests extras require.
Adds a navigation treeview widget to plone.
This product aims to simplify running and writing third-party Generic Setup upgrade steps in Plone. It provides a control panel for running multiple upgrades at once, based on the upgrade mechanism of Generic Setup (portal_setup). Further a base class for writing upgrade steps with a variety of helpers for common tasks is provided. Features ======== * **Managing upgrades**: Provides an advanced view for upgrading third-party Plone packages using Generic Setup. It allows to upgrade multiple packages at once with an easy to use user interface. By resolving the dependency graph it is able to optimize the upgrade step order so that the upgrade is hassle free. * **Import profile upgrade steps**: Some times an upgrade step does simply import an upgrade step generic setup profile, especially made for this upgrade step. A new ZCML directive makes this much simpler. * **Writing upgrades**: The package provides a base upgrade class with various helpers for tasks often done in upgrades. Manage upgrades =============== The ``@@manage-upgrades`` view allows to upgrade multiple packages at once: Fallback view ------------- The ``@@manage-upgrades-plain`` view acts as a fallback view for ``@@manage-upgrades``. It does not include plone`s main template and thus might be able to render when the default view fails for some reason. Import-Profile Upgrade Steps ============================ Sometimes an upgrade simply imports a little generic setup profile, which is only made for this upgrade step. Doing such upgrade steps are often much simpler than doing the change in python, because one can simply copy the necessary parts of the new default generic setup profile into the upgrade step profile. Normally, for doing this, one has to register an upgrade step and a generic setup profile and write an upgrade step handler importing the profile. ftw.upgrade makes this much simpler by providing an ``importProfile`` ZCML direvtive especially for this specific use case. Example ``configure.zcml`` meant to be placed in your ``upgrades`` sub-package: .. code:: xml <configure xmlns="http://namespaces.zope.org/zope" xmlns:upgrade-step="http://namespaces.zope.org/ftw.upgrade" i18n_domain="my.package"> <include package="ftw.upgrade" file="meta.zcml" /> <upgrade-step:importProfile title="Update email_from_address" profile="my.package:default" source="1007" destination="1008" directory="profiles/1008" /> </configure> This example upgrade steps updates the ``email_from_address`` property. A generic setup profile is automatically registered and hooked up with the generated upgrade step handler. Simply put a ``properties.xml`` in the folder ``profiles/1008`` relative to the above ``configure.zcml`` and the upgrade step is ready for deployment. Upgrade step helpers ==================== The ``UpgradeStep`` base class provides various tools and helpers useful when writing upgrade steps. It can be used by registering the classmethod directly. Be aware that the class is very special: it acts like a function and calls itself automatically. Example upgrade step definition (defined in a ``upgrades.py``): .. code:: python from ftw.upgrade import UpgradeStep class UpdateFooIndex(UpgradeStep): """The index ``foo`` is a ``FieldIndex`` instead of a ``KeywordIndex``. This upgrade step changes the index type and reindexes the objects. """ def __call__(self): index_name = 'foo' if self.catalog_has_index(index_name): self.catalog_remove_index(index_name) self.catalog_add_index(index_name, 'KeywordIndex') self.catalog_rebuild_index(index_name) Registration in ``configure.zcml`` (assume its in the same directory): .. code:: xml <configure xmlns="http://namespaces.zope.org/zope" xmlns:genericsetup="http://namespaces.zope.org/genericsetup" i18n_domain="my.package"> <genericsetup:upgradeStep profile="my.package:default" source="4" destination="5" title="Update index 'foo'." handler=".upgrades.UpdateFooIndex" /> </configure> Updating objects with progress logging -------------------------------------- Since an upgrade step often updates a set of objects indexed in the catalog, there is a useful helper method combining querying the catalog with the `ProgressLogger` (see below). The catalog is queried unrestricted so that we handle all the objects. Here is an example for updating all objects of a particular type: .. code:: python from ftw.upgrade import ProgressLogger from ftw.upgrade import UpgradeStep class ExcludeFilesFromNavigation(UpgradeStep): def __call__(self): for obj in self.objects({'portal_type': 'File'}, 'Enable exclude from navigation for files'): obj.setExcludeFromNav(True) When running the upgrade step you'll have a progress log:: INFO ftw.upgrade STARTING Enable exclude from navigation for files INFO ftw.upgrade 1 of 10 (10%): Enable exclude from navigation for files INFO ftw.upgrade 5 of 50 (50%): Enable exclude from navigation for files INFO ftw.upgrade 10 of 10 (100%): Enable exclude from navigation for files INFO ftw.upgrade DONE: Enable exclude from navigation for files Methods ------- The ``UpgradeStep`` class has various helper functions: ``self.getToolByName(tool_name)`` Returns the tool with the name ``tool_name`` of the upgraded site. ``self.objects(catalog_query, message, logger=None, savepoints=None)`` Queries the catalog (unrestricted) and an iterator with full objects. The iterator configures and calls a ``ProgressLogger`` with the passed ``message``. If set to a non-zero value, the ``savepoints`` argument causes a transaction savepoint to be created every n items. This can be used to keep memory usage in check when creating large transactions. ``self.catalog_rebuild_index(name)`` Reindex the ``portal_catalog`` index identified by ``name``. ``self.catalog_reindex_objects(query, idxs=None, savepoints=None)`` Reindex all objects found in the catalog with `query`. A list of indexes can be passed as `idxs` for limiting the indexed indexes. The ``savepoints`` argument will be passed to ``self.objects()``. ``self.catalog_has_index(name)`` Returns whether there is a catalog index ``name``. ``self.catalog_add_index(name, type_, extra=None)`` Adds a new index to the ``portal_catalog`` tool. ``self.catalog_remove_index(name)`` Removes an index to from ``portal_catalog`` tool. ``self.actions_remove_action(category, action_id)`` Removes an action identified by ``action_id`` from the ``portal_actions`` tool from a particulary ``category``. ``self.catalog_unrestricted_get_object(brain)`` Returns the unrestricted object of a brain. ``self.catalog_unrestricted_search(query, full_objects=False)`` Searches the catalog without checking security. When `full_objects` is `True`, unrestricted objects are returned instead of brains. Upgrade steps should generally use unrestricted catalog access since all objects should be upgraded - even if the manager running the upgrades has no access on the objects. ``self.actions_remove_type_action(portal_type, action_id)`` Removes a ``portal_types`` action from the type identified by ``portal_type`` with the action id ``action_id``. ``self.set_property(context, key, value, data_type='string')`` Set a property with the key ``value`` and the value ``value`` on the ``context`` safely. The property is created with the type ``data_type`` if it does not exist. ``self.add_lines_to_property(context, key, lines)`` Updates a property with key ``key`` on the object ``context`` adding ``lines``. The property is expected to by of type "lines". If the property does not exist it is created. ``self.setup_install_profile(profileid, steps=None)`` Installs the generic setup profile identified by ``profileid``. If a list step names is passed with ``steps`` (e.g. ['actions']), only those steps are installed. All steps are installed by default. ``self.uninstall_product(product_name)`` Uninstalls a product using the quick installer. ``self.migrate_class(obj, new_class)`` Changes the class of an object. It has a special handling for BTreeFolder2Base based containers. ``self.remove_broken_browserlayer(name, dottedname)`` Removes a browser layer registration whose interface can't be imported any more from the persistent registry. Messages like these on instance boot time can be an indication for this problem: ``WARNING OFS.Uninstalled Could not import class 'IMyProductSpecific' from module 'my.product.interfaces'`` ``self.update_security(obj, reindex_security=True)`` Update the security of a single object (checkboxes in manage_access). This is usefuly in combination with the ``ProgressLogger``. It is possible to not reindex the object security in the catalog (``allowedRolesAndUsers``). This speeds up the update but should only be disabled when there are no changes for the ``View`` permission. ``self.update_workflow_security(workflow_names, reindex_security=True)`` Update all objects which have one of a list of workflows. This is useful when updating a bunch of workflows and you want to make sure that the object security is updated properly. The update is done by doing as few as possibly by only searching for types which might have this workflow. It does support placeful workflow policies. For speeding up you can pass ``reindex_security=False``, but you need to make sure you did not change any security relevant permissions (only ``View`` needs ``reindex_security=True`` for default Plone). Progress logger --------------- When an upgrade step is taking a long time to complete (e.g. while performing a data migration), the administrator needs to have information about the progress of the update. It is also important to have continuous output for avoiding proxy timeouts when accessing Zope through a webserver / proxy. With the ``ProgressLogger`` it is very easy to log the progress: from ftw.upgrade import ProgressLogger from ftw.upgrade import UpgradeStep class MyUpgrade(UpgradeStep): def __call__(self): objects = self.catalog_unrestricted_search( {'portal_type': 'MyType'}, full_objects=True) for obj in ProgressLogger('Migrate my type', objects): self.upgrade_obj(obj) def upgrade_obj(self, obj): do_something_with(obj) The logger will log the current progress every 5 seconds (default). Example log output:: INFO ftw.upgrade STARTING Migrate MyType INFO ftw.upgrade 1 of 10 (10%): Migrate MyType INFO ftw.upgrade 5 of 50 (50%): Migrate MyType INFO ftw.upgrade 10 of 10 (100%): Migrate MyType INFO ftw.upgrade DONE: Migrate MyType Workflow Chain Updater ---------------------- When the workflow is changed for a content type, the workflow state is reset to the init state of new workflow for every existing object of this type. This can be really annoying. The `WorkflowChainUpdater` takes care of setting every object to the right state after changing the chain (the workflow for the type): from ftw.upgrade.workflow import WorkflowChainUpdater from ftw.upgrade import UpgradeStep class UpdateWorkflowChains(UpgradeStep): def __call__(self): query = {'portal_type': ['Document', 'Folder']} objects = self.catalog_unrestricted_search( query, full_objects=True) review_state_mapping={ ('intranet_workflow', 'plone_workflow'): { 'external': 'published', 'pending': 'pending'}} with WorkflowChainUpdater(objects, review_state_mapping): self.setup_install_profile('profile-my.package.upgrades:1002') The workflow chain updater migrates the workflow history by default. The workflow history migration can be disabled by setting ``migrate_workflow_history`` to ``False``: with WorkflowChainUpdater(objects, review_state_mapping, migrate_workflow_history=False): If a transition mapping is provided, the actions in the workflow history entries are migrated according to the mapping so that the translations work for the new workflow: transition_mapping = { ('intranet_workflow', 'new_workflow'): { 'submit': 'submit-for-approval'}} with WorkflowChainUpdater(objects, review_state_mapping, transition_mapping=transition_mapping): Placeful Workflow Policy Activator ---------------------------------- When manually activating a placeful workflow policy all objects with a new workflow might be reset to the initial state of the new workflow. ftw.upgrade has a tool for enabling placeful workflow policies without breaking the review state by mapping it from the old to the new workflows: from ftw.upgrade.placefulworkflow import PlacefulWorkflowPolicyActivator from ftw.upgrade import UpgradeStep class ActivatePlacefulWorkflowPolicy(UpgradeStep): def __call__(self): portal_url = self.getToolByName('portal_url') portal = portal_url.getPortalObject() context = portal.unrestrictedTraverse('path/to/object') activator = PlacefulWorkflowPolicyActivator(context) activator.activate_policy( 'local_policy', review_state_mapping={ ('intranet_workflow', 'plone_workflow'): { 'external': 'published', 'pending': 'pending'}}) The above example activates a placeful workflow policy recursively on the object under "path/to/object", enabling the placeful workflow policy "local_policy". The mapping then maps the "intranet_workflow" to the "plone_workflow" by defining which old states (key, intranet_workflow) should be changed to the new states (value, plone_workflow). **Options** - `activate_in`: Activates the placeful workflow policy for the passed in object (`True` by default). - `activate_below`: Activates the placeful workflow policy for the children of the passed in object, recursively (`True` by default). - `update_security`: Update object security and reindex allowedRolesAndUsers (`True` by default). IPostUpgrade adapter ==================== By registering an ``IPostUpgrade`` adapter it is possible to run custom code after running upgrades. All adapters are executed after each time upgrades were run, not depending on which upgrades are run. The name of the adapters should be the profile of the package, so that ``ftw.upgrade`` is able to execute the adapters in order of the GS dependencies. Example adapter: from ftw.upgrade.interfaces import IPostUpgrade from zope.interface import implements class MyPostUpgradeAdapter(object): implements(IPostUpgrade) def __init__(self, portal, request): self.portal = portal self.request = request def __call__(self): Registration in ZCML: <configure xmlns="http://namespaces.zope.org/zope"> <adapter factory=".adapters.MyPostUpgradeAdapter" provides="ftw.upgrade.interfaces.IPostUpgrade" for="Products.CMFPlone.interfaces.siteroot.IPloneSiteRoot zope.interface.Interface" name="my.package:default" /> </configure>
This package allows users to easily manage users and groups through a new view without having plone's manage portal permission. This is useful when the person who manages users and groups should have full manager access to the site. If the user has the permission "Manage users", a new action is displayed in the user menu, giving him the ability to list and modify users and groups. Features -------- - Manage users - List users - Assign users to groups - Add users - Delete users - Reset password of a user and send a notification - Manage groups - List groups - Add groups - Delete groups
Plone is ideal as an intranet and extranet server, as a document publishing system, a portal server and as a groupware tool for collaboration between separately located entities. A versatile software product like Plone can be used in a myriad of ways. Look through the sites that use Plone section to see a variety of ways people have implemented Plone and Zope solutions. This is a a minimal version of Plone that does not require X and other GUI-based software.
Using vs.org almost any organization including its institutions, departments and employees may be represented within a Plone site. Even complex organizational structures can be made accessible and displayed, structered by business area, specialization and region. Features vs.org offers the following content types: Institution Telephone numbers An institution can hold an arbitrary number of telephone numbers. The vocabulary for descriptions can be assigned such that common declarations telephone, fax and mobile can be used, as well as reception, ward and others. Each number may be marked as externally visible. This way it is possible to administer internal numbers in vs.org, even if the page for the institution is published. Address Any number of addresses may be assigned to an institution. Provide details of street address, P.O. box, a delivery address and others. The first address given is used to generate a map using Google maps services. If the automatic mapping is not precise enough you may provide geo coordinates. With the generated map you may also display directions in RichText. An optional photograph or image of the institution is also helpful. Business area Any institution may show its business area. Editors may administer the business area vocabulary. Employees Employees of an institution are referenced from entries in an employee folder. The order of employees is freely assignable. Department A department can hold an arbitrary number of telephone numbers, described by custom vocabulary. Each number may be marked as externally visible. Building section / floor / room no. Departments are always part of an institution, so have no address of their own. Instead you provide building section, floor or room number details. Specialization Any department may be assigned a specialization. Employee Any employee can be described using Position Salutation Title (academic or other) Firstname Surname Telephone numbers Email URL Portrait Notes Again, any number of telephone number is possible. Each may carry arbitrarily assigned vocabulary for description. For each employee a business card in vcard format is generated that can be imported into address books and contacts. Views The institution homepage view makes specific institutions, departments accessible using their business areas, specializations and regions. Portlets Institution portlet This portlet shows name and image of the institution, the addresses linking to Google maps, the telephone numbers and employees. Department portlet Very similar to the institution portlet, showing building section, floor and room number instead of an address. Similar institutions, similar departments Shown in the context of an institution, this portlet provides other institutions sharing the same business area. In the same way, a portlet shows other departments having the same specialization. Employee search Searching for employees is using the employee's surnames. This search can be adapted to specific institutions. Use cases Websites of organizations having a complex organizational structure. Intranets and extranets that are used by employees and/or partner organizations who need to find contact information quickly.