Skip to main content

Add content from existing content templates

Project description

The collective.contemplate package allows site administrators to designate content items as the template from which new items of that type will be created.

When creating content from a template, the initial edit form is rendered and validation performed on the template after changing the owner of the template to the current user within a transaction.savepoint() which is rolled back after rendering. As a result, portal_factory is not involved and indexing occurs only on the final copy of the template. This may result in performance gains though this has not been tested.

While designed to be Archetypes agnostic, only an Archetypes implementation is currently provided. Templates may currently be designated using Archetype UIDs for the global templates or references for the context specific templates.

A reserved_id property can also be set on type information objects in portal_types. If set and an object with that ID already exists in the container, then the type is not allowed to be added.

Installation

To use collective.contemplate for the Plone content types, include the collective.contemplate configure.zcml in your instance and install “Content Templates” in the “Add-on Products” control panel, or in the ZMI through portal_setup. This will replace the Plone content type information with template versions.

To install for other content types, register a template add form for the content type and use the TemplateDynamicViewTypeInfo meta_type for the content type information.

In the ZCML for the browser views:

<contemplate:formControllerPage
     name="addFoo"
     type_name="Foo"
     for="zope.app.container.interfaces.IAdding"
     permission="foo.AddFoo" />

Note that the “foo.AddFoo” permission must be registered and the name “addFoo” must be the same as your content type constructor. If you’re using Archetypes, then the constructor may auto-generated by prefixing “add” to the content class name.

In the GenericSetup profile types.xml file:

<object name="Foo" meta_type="TemplateDynamicViewTypeInfo "/>

In the GenericSetup profile types/Foo.xml file:

<?xml version="1.0"?>
<object name="Foo"
   meta_type="TemplateDynamicViewTypeInfo">

Usage

This package is currently incomplete until a UI for designating templates is included. In the mean time, you may set the global templates in the ZMI or context specific templates using the references GenericSetup import handler provided by collective.gsqi.

You can use a given content item as the global template by setting the global_uid property of the content type information under portal_types in the ZMI. Set global_uid to the Archetypes UID of the template.

You can use a content item as the template in the context of a specific folder by setting a reference from the folder to the item with the relationship of “contemplate.${type_info/getId}” where “${type_info/getId}” is the id of the content type.

A reserved id can be set using the reserved_id property of the content type information under portal_types in the ZMI.

Context Templates

Open a browser and log in as a user who is allowed to administer templates.

>>> from Products.Five.testbrowser import Browser
>>> from Products.PloneTestCase import ptc
>>> owner_browser = Browser()
>>> owner_browser.handleErrors = False
>>> owner_browser.open(portal.absolute_url())
>>> owner_browser.getLink('Log in').click()
>>> owner_browser.getControl(
...     'Login Name').value = ptc.portal_owner
>>> owner_browser.getControl(
...     'Password').value = ptc.default_password
>>> owner_browser.getControl('Log in').click()

Before we’ve added a template, adding content proceeds as before with fields empty.

>>> owner_browser.open(portal.Members.absolute_url())
>>> owner_browser.getLink(url='/+/addATDocument').click()
>>> owner_browser.url
'http://nohost/plone/Members/portal_factory/Document/document.../edit'
>>> owner_browser.getControl('Title').value
''
>>> owner_browser.getControl('Description').value
''

Finish creating the page to use as a template.

>>> owner_browser.getControl('Title').value = 'Foo Template Title'
>>> owner_browser.getControl(
...     'Description').value = 'Foo Template Description'
>>> owner_browser.getControl('Save').click()
>>> print owner_browser.contents
<...
...Changes saved...
...Foo Template Title...
...Foo Template Description...

Make sure the template is visible to users that will use it as a template.

>>> self.loginAsPortalOwner()
>>> portal.portal_workflow.doActionFor(
...     portal.Members['foo-template-title'], 'publish')
>>> self.logout()

A user with rights to administer templates may designate the page as a template for the Page content type in that folder and below using “Make template” in the actions menu.

>>> portal.Members.addReference(
...     portal.Members['foo-template-title'],
...     relationship='contemplate.Document')
<Reference sid:... tid:... rel:contemplate.Document>

Open another browser and log in as a normal user.

>>> from Products.Five.testbrowser import Browser
>>> from Products.PloneTestCase import ptc
>>> contributor_browser = Browser()
>>> contributor_browser.handleErrors = False
>>> contributor_browser.open(portal.absolute_url())
>>> contributor_browser.getLink('Log in').click()
>>> contributor_browser.getControl(
...     'Login Name').value = ptc.default_user
>>> contributor_browser.getControl(
...     'Password').value = ptc.default_password
>>> contributor_browser.getControl('Log in').click()

Once a template has been designated, adding an item of the same content type in that folder or below will use the template.

>>> contributor_browser.open(folder.absolute_url())
>>> contributor_browser.getLink(url='/+/addATDocument').click()
>>> contributor_browser.getControl('Title').value
'Foo Template Title'
>>> contributor_browser.getControl('Description').value
'Foo Template Description'

The edit page will be rendered and validated against the template without copying or otherwise instantiating new content.

>>> contributor_browser.getControl('Title').value = ''
>>> contributor_browser.getControl('Save').click()
>>> print contributor_browser.contents
<...
...Please correct the indicated errors...
...Title is required...
>>> contributor_browser.url
'http://nohost/plone/Members/test_user_1_/+/addATDocument'
>>> portal.Members.contentValues()
[<ATDocument at /plone/Members/foo-template-title>,
 <ATFolder at /plone/Members/test_user_1_>]
>>> folder.contentValues()
[]

Successfully saving the form will copy the template and modify it with the submitted form data.

>>> contributor_browser.getControl('Title').value = 'Foo Page Title'
>>> contributor_browser.getControl('Save').click()
>>> contributor_browser.url
'http://nohost/plone/Members/test_user_1_/foo-page-title'
>>> print contributor_browser.contents
<...
...Changes saved...
Foo Page Title...
Foo Template Description...
>>> portal.Members.contentValues()
[<ATDocument at /plone/Members/foo-template-title>,
 <ATFolder at /plone/Members/test_user_1_>]
>>> folder.contentValues()
[<ATDocument at /plone/Members/test_user_1_/foo-page-title>]

The content added from the template behaves as other content and is editable by the owner.

>>> contributor_browser.getLink('Edit')
<Link text='Edit' url='http://nohost/plone/Members/test_user_1_/foo-page-title/edit'>

A user without rights to administer templates may not designate content as a template.

>>> contributor_browser.getLink('Make template')
Traceback (most recent call last):
LinkNotFoundError

The template’s permissions and field values have not been changed.

>>> owner_browser.open(
...     portal.Members['foo-template-title'].absolute_url())
>>> print owner_browser.contents
<...
...Foo Template Title...
...Foo Template Description...
>>> contributor_browser.open(
...     portal.Members['foo-template-title'].absolute_url())
>>> contributor_browser.getLink('Edit')
Traceback (most recent call last):
LinkNotFoundError

The template for a given content type may be replaced using the “Make template” action on the new template.

>>> portal.Members.deleteReference(
...     portal.Members['foo-template-title'],
...     relationship='contemplate.Document')
>>> portal.Members.addReference(
...     folder['foo-page-title'],
...     relationship='contemplate.Document')
<Reference sid:... tid:... rel:contemplate.Document>
>>> contributor_browser.open(folder.absolute_url())
>>> contributor_browser.getLink(url='/+/addATDocument').click()
>>> contributor_browser.getControl('Title').value
'Foo Page Title'

The template may also be removed using the “Remove template” action on the template based add form.

>>> portal.Members.deleteReference(
...     folder['foo-page-title'],
...     relationship='contemplate.Document')
>>> contributor_browser.open(folder.absolute_url())
>>> contributor_browser.getLink(url='/+/addATDocument').click()
>>> contributor_browser.url
'http://nohost/plone/Members/test_user_1_/portal_factory/Document/document.../edit'
>>> contributor_browser.getControl('Title').value
''
>>> contributor_browser.getControl('Description').value
''

Global Templates

A template can be designated as the global template for a given portal type. To do so set, the “Global Template UID” property of the type info in the portal_types tool to the UID of the template object.

Create an event as the template.

>>> self.loginAsPortalOwner()
>>> foo_event = portal[portal.invokeFactory(
...     type_name='Event', id='event-template-title',
...     title='Event Template Title',
...     description='Event template description')]

Set the type info property to the UID for the event template.

>>> portal.portal_types.Event.manage_changeProperties(
...     global_uid=foo_event.UID())

Now when an event is added through the browser, it will be created from the template.

>>> contributor_browser.open(folder.absolute_url())
>>> contributor_browser.getLink(url='/+/addATEvent').click()
>>> contributor_browser.getControl('Title').value
'Event Template Title'
>>> contributor_browser.getControl('Description').value
'Event template description'

Reserved IDs

A reserved_id property can also be set on type information objects in portal_types. If set and an object with that ID already exists in the container, then the type is not allowed to be added.

>>> self.login()
>>> folder.allowedContentTypes()
[<TemplateDynamicViewTypeInfo at /plone/portal_types/Document>,
 <TemplateDynamicViewTypeInfo at /plone/portal_types/Event>,
 <TemplateDynamicViewTypeInfo at /plone/portal_types/Favorite>,
 <TemplateDynamicViewTypeInfo at /plone/portal_types/File>,
 <TemplateDynamicViewTypeInfo at /plone/portal_types/Folder>,
 <TemplateDynamicViewTypeInfo at /plone/portal_types/Image>,
 <TemplateDynamicViewTypeInfo at /plone/portal_types/Link>,
 <TemplateDynamicViewTypeInfo at /plone/portal_types/News Item>]
>>> portal.portal_types.Document.reserved_id = 'foo-page-title'
>>> folder.allowedContentTypes()
[<TemplateDynamicViewTypeInfo at /plone/portal_types/Event>,
 <TemplateDynamicViewTypeInfo at /plone/portal_types/Favorite>,
 <TemplateDynamicViewTypeInfo at /plone/portal_types/File>,
 <TemplateDynamicViewTypeInfo at /plone/portal_types/Folder>,
 <TemplateDynamicViewTypeInfo at /plone/portal_types/Image>,
 <TemplateDynamicViewTypeInfo at /plone/portal_types/Link>,
 <TemplateDynamicViewTypeInfo at /plone/portal_types/News Item>]

Changelog

0.1 - Unreleased

  • Initial release

TODO

  • Add UI to the types control for selecting a global template

  • Add UI to folders for specifying context templates

    >>> owner_browser.getLink('Make template').click()
    >>> print owner_browser.contents
    <...
    ...Item designated as the template...
    
    >>> owner_browser.open(folder['foo-page-title'].absolute_url())
    >>> owner_browser.getLink('Make template').click()
    >>> print owner_browser.contents
    <...
    ...Item designated as the template...
    
    >>> owner_browser.open(folder.absolute_url())
    >>> owner_browser.getLink(url='/+/addATDocument').click()
    >>> owner_browser.getLink('Remove template').click()
    >>> print owner_browser.contents
    <...
    ...Item removed as the template...
    >>> contributor_browser.url
    'http://nohost/plone/Members/foo-template-title'
    
  • Further avoid redundant indexing

    Implement manage_pasteObjects and manage_renameObject, such that no indexing is performed and leave indexing to the edit form handling.

    This might cause problems with programmatic use. Look at experimental.contentcreation.

Project details


Release history Release notifications | RSS feed

This version

0.1

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

collective.contemplate-0.1.tar.gz (19.3 kB view details)

Uploaded Source

File details

Details for the file collective.contemplate-0.1.tar.gz.

File metadata

File hashes

Hashes for collective.contemplate-0.1.tar.gz
Algorithm Hash digest
SHA256 5985cea5ed6067a464387c6ec68949c5ffc7a1dcc0bbbf0a9122388bc676c66c
MD5 2fcdde526d2cd0515f62277a67f2201b
BLAKE2b-256 d86c7b4eb24c1406ca594d7a1ea2308df70e52df1ef3136b5ab390665d35049b

See more details on using hashes here.

Provenance

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page