This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.
plone.api/src/plone/api/content.py

369 lines
12 KiB
Python
Raw Normal View History

2012-10-14 17:29:04 +02:00
# -*- coding: utf-8 -*-
"""Module that provides functionality for content manipulation."""
2012-07-10 21:06:27 +02:00
from Products.Archetypes.interfaces.base import IBaseObject
from Products.CMFCore.interfaces import ISiteRoot
from Products.CMFCore.WorkflowCore import WorkflowException
from plone.api import portal
from plone.api.exc import InvalidParameterError
from plone.api.validation import at_least_one_of
from plone.api.validation import mutually_exclusive_parameters
from plone.api.validation import required_parameters
from plone.app.uuid.utils import uuidToObject
from plone.uuid.interfaces import IUUID
from zope.component import getMultiAdapter
from zope.component import getSiteManager
2013-01-25 14:04:58 +01:00
from zope.container.interfaces import INameChooser
from zope.interface import Interface
from zope.interface import providedBy
2012-07-08 23:02:49 +02:00
import random
import transaction
2012-07-10 23:00:05 +02:00
2019-10-25 10:06:11 +02:00
_marker = []
2012-02-26 21:29:25 +01:00
@required_parameters('container', 'type')
@at_least_one_of('id', 'title')
def create(
container=None,
type=None,
id=None,
title=None,
safe_id=False,
**kwargs
):
"""Create a new content item.
2012-02-26 21:29:25 +01:00
:param container: [required] Container object in which to create the new
object.
:type container: Folderish content object
:param type: [required] Type of the object.
:type type: string
2012-02-27 01:04:59 +01:00
:param id: Id of the object. If the id conflicts with another object in
the container, a suffix will be added to the new object's id. If no id
is provided, automatically generate one from the title. If there is no
id or title provided, raise a ValueError.
2012-02-26 21:29:25 +01:00
:type id: string
:param title: Title of the object. If no title is provided, use id as
the title.
:type title: string
:param safe_id: When False, the given id will be enforced. If the id is
2013-02-15 15:36:06 +01:00
conflicting with another object in the target container, raise an
InvalidParameterError. When True, choose a new, non-conflicting id.
:type safe_id: boolean
2012-02-26 21:29:25 +01:00
:returns: Content object
2012-08-26 21:57:27 +02:00
:raises:
KeyError,
2012-09-03 19:44:39 +02:00
:class:`~plone.api.exc.MissingParameterError`,
:class:`~plone.api.exc.InvalidParameterError`
2012-07-11 17:31:43 +02:00
:Example: :ref:`content_create_example`
2012-02-26 21:29:25 +01:00
"""
2012-07-09 16:51:43 +02:00
# Create a temporary id if the id is not given
content_id = not safe_id and id or str(random.randint(0, 99999999))
2012-07-09 16:51:43 +02:00
if title:
kwargs['title'] = title
try:
container.invokeFactory(type, content_id, **kwargs)
except UnicodeDecodeError:
# UnicodeDecodeError is a subclass of ValueError,
# so will be swallowed below unless we re-raise it here
raise
2013-11-26 10:57:42 +01:00
except ValueError as e:
if ISiteRoot.providedBy(container):
allowed_types = container.allowedContentTypes()
types = [allowed_type.id for allowed_type in allowed_types]
else:
try:
types = container.getLocallyAllowedTypes()
except AttributeError:
raise InvalidParameterError(
"Cannot add a '%s' object to the container." % type
)
raise InvalidParameterError(
2013-01-26 16:40:10 +01:00
"Cannot add a '{0}' object to the container.\n"
"Allowed types are:\n"
2013-10-05 18:43:39 +02:00
"{1}\n"
"{2}".format(type, '\n'.join(sorted(types)), e.message)
)
2012-07-09 16:51:43 +02:00
content = container[content_id]
2012-07-08 23:02:49 +02:00
# Archetypes specific code
if IBaseObject.providedBy(content):
# Will finish Archetypes content item creation process,
# rename-after-creation and such
content.processForm()
if not id or (safe_id and id):
2012-07-09 16:51:43 +02:00
# Create a new id from title
chooser = INameChooser(container)
2012-07-09 20:04:16 +02:00
derived_id = id or title
new_id = chooser.chooseName(derived_id, content)
# kacee: we must do a partial commit, else the renaming fails because
2012-07-10 23:00:05 +02:00
# the object isn't in the zodb.
# Thus if it is not in zodb, there's nothing to move. We should
# choose a correct id when
2012-07-09 16:51:43 +02:00
# the object is created.
# maurits: tests run fine without this though.
transaction.savepoint(optimistic=True)
2012-07-09 16:51:43 +02:00
content.aq_parent.manage_renameObject(content_id, new_id)
2012-02-26 21:29:25 +01:00
2012-07-08 23:02:49 +02:00
return content
2012-02-26 21:29:25 +01:00
@mutually_exclusive_parameters('path', 'UID')
@at_least_one_of('path', 'UID')
def get(path=None, UID=None):
2012-02-27 01:04:59 +01:00
"""Get an object.
2012-02-26 21:29:25 +01:00
:param path: Path to the object we want to get, relative to
the portal root.
2012-02-26 21:29:25 +01:00
:type path: string
:param UID: UID of the object we want to get.
:type UID: string
:returns: Content object
2012-08-26 21:57:27 +02:00
:raises:
ValueError,
2012-07-11 17:31:43 +02:00
:Example: :ref:`content_get_example`
2012-02-26 21:29:25 +01:00
"""
2012-07-09 16:51:43 +02:00
if path:
site = portal.get()
site_absolute_path = '/'.join(site.getPhysicalPath())
if not path.startswith('{0}'.format(site_absolute_path)):
path = '{0}{1}'.format(site_absolute_path, path)
try:
return site.restrictedTraverse(path)
except (KeyError, AttributeError):
return None # When no object is found don't raise an error
2012-07-09 16:51:43 +02:00
elif UID:
return uuidToObject(UID)
2012-02-26 21:29:25 +01:00
@required_parameters('source')
@at_least_one_of('target', 'id')
def move(source=None, target=None, id=None, safe_id=False):
2012-02-27 01:04:59 +01:00
"""Move the object to the target container.
2012-02-26 21:29:25 +01:00
:param source: [required] Object that we want to move.
:type source: Content object
:param target: Target container to which the source object will
be moved. If no target is specified, the source object's container will
2012-02-27 03:36:35 +01:00
be used as a target, effectively making this operation a rename
(:ref:`content_rename_example`).
2012-02-26 21:29:25 +01:00
:type target: Folderish content object
:param id: Pass this parameter if you want to change the id of the moved
object on the target location. If the new id conflicts with another
object in the target container, a suffix will be added to the moved
object's id.
:type id: string
:param safe_id: When False, the given id will be enforced. If the id is
2012-02-27 01:04:59 +01:00
conflicting with another object in the target container, raise a
InvalidParameterError. When True, choose a new, non-conflicting id.
:type safe_id: boolean
2013-09-04 16:04:37 +02:00
:returns: Content object that was moved to the target location
2012-08-26 21:57:27 +02:00
:raises:
KeyError
ValueError
2012-07-11 17:31:43 +02:00
:Example: :ref:`content_move_example`
2012-02-26 21:29:25 +01:00
"""
2012-07-09 16:51:43 +02:00
source_id = source.getId()
2012-07-11 17:55:10 +02:00
# If no target is given the object is probably renamed
if target:
target.manage_pasteObjects(
source.aq_parent.manage_cutObjects(source_id))
2012-07-11 17:55:10 +02:00
else:
target = source
2012-07-09 16:51:43 +02:00
if id:
2013-09-04 16:04:37 +02:00
return rename(obj=target[source_id], new_id=id, safe_id=safe_id)
else:
return target[source_id]
2012-02-26 21:29:25 +01:00
@required_parameters('obj', 'new_id')
def rename(obj=None, new_id=None, safe_id=False):
"""Rename the object.
:param obj: [required] Object that we want to rename.
:type obj: Content object
:param new_id: New id of the object.
2012-09-08 15:20:37 +02:00
:type new_id: string
:param safe_id: When False, the given id will be enforced. If the id is
conflicting with another object in the container, raise a
InvalidParameterError. When True, choose a new, non-conflicting id.
:type safe_id: boolean
2013-09-04 16:04:37 +02:00
:returns: Content object that was renamed
:Example: :ref:`content_rename_example`
"""
obj_id = obj.getId()
if safe_id:
try:
chooser = INameChooser(obj)
except TypeError:
chooser = INameChooser(obj.aq_parent)
new_id = chooser.chooseName(new_id, obj)
obj.aq_parent.manage_renameObject(obj_id, new_id)
2013-09-04 16:04:37 +02:00
return obj.aq_parent[new_id]
@required_parameters('source')
@at_least_one_of('target', 'id')
def copy(source=None, target=None, id=None, safe_id=False):
2012-02-27 01:04:59 +01:00
"""Copy the object to the target container.
2012-02-26 21:29:25 +01:00
:param source: [required] Object that we want to copy.
:type source: Content object
:param target: Target container to which the source object will
be moved. If no target is specified, the source object's container will
be used as a target.
:type target: Folderish content object
:param id: Id of the copied object on the target location. If no id is
provided, the copied object will have the same id as the source object
- however, if the new object's id conflicts with another object in the
target container, a suffix will be added to the new object's id.
:type id: string
:param safe_id: When True, the given id will be enforced. If the id is
2012-02-27 01:04:59 +01:00
conflicting with another object in the target container, raise a
InvalidParameterError. When True, choose a new, non-conflicting id.
:type safe_id: boolean
2013-09-04 16:04:37 +02:00
:returns: Content object that was created in the target location
2012-08-26 21:57:27 +02:00
:raises:
KeyError,
ValueError
2012-07-11 17:31:43 +02:00
:Example: :ref:`content_copy_example`
2012-02-26 21:29:25 +01:00
"""
2012-07-10 14:30:38 +02:00
source_id = source.getId()
if target is None:
target = source.aq_parent
target.manage_pasteObjects(source.aq_parent.manage_copyObjects(source_id))
2012-07-10 14:30:38 +02:00
if id:
2013-09-04 16:04:37 +02:00
return rename(obj=target[source_id], new_id=id, safe_id=safe_id)
else:
return target[source_id]
2012-02-26 21:29:25 +01:00
@required_parameters('obj')
def delete(obj=None):
2012-02-27 01:04:59 +01:00
"""Delete the object.
2012-02-26 21:29:25 +01:00
:param obj: [required] Object that we want to delete.
:type obj: Content object
2012-08-26 21:57:27 +02:00
:raises:
ValueError
2012-07-11 17:31:43 +02:00
:Example: :ref:`content_delete_example`
2012-02-26 21:29:25 +01:00
"""
obj.aq_parent.manage_delObjects([obj.getId()])
2012-02-26 21:29:25 +01:00
@required_parameters('obj')
2019-10-25 10:06:11 +02:00
def get_state(obj=None, default=_marker):
2012-02-27 01:04:59 +01:00
"""Get the current workflow state of the object.
2012-02-26 21:29:25 +01:00
:param obj: [required] Object that we want to get the state for.
:type obj: Content object
2019-10-25 10:06:11 +02:00
:param default: Returned if no workflow is defined for the object.
:returns: Object's current workflow state, or `default`.
2012-02-26 21:29:25 +01:00
:rtype: string
2012-08-26 21:57:27 +02:00
:raises:
2019-10-25 10:06:11 +02:00
Products.CMFCore.WorkflowCore.WorkflowException
2012-07-11 17:31:43 +02:00
:Example: :ref:`content_get_state_example`
2012-02-26 21:29:25 +01:00
"""
workflow = portal.get_tool('portal_workflow')
2019-10-25 10:06:11 +02:00
if default is not _marker and not workflow.getWorkflowsFor(obj):
return default
# This still raises WorkflowException when the workflow state is broken,
# ie 'review_state' is absent
return workflow.getInfoFor(ob=obj, name='review_state')
2012-02-26 21:29:25 +01:00
@required_parameters('obj', 'transition')
def transition(obj=None, transition=None):
2012-02-27 01:04:59 +01:00
"""Perform a workflow transition for the object.
2012-02-26 21:29:25 +01:00
:param obj: [required] Object for which we want to perform the workflow
transition.
:type obj: Content object
:param transition: [required] Name of the workflow transition.
:type transition: string
2012-08-26 21:57:27 +02:00
:raises:
2012-09-03 19:44:39 +02:00
:class:`~plone.api.exc.MissingParameterError`,
:class:`~plone.api.exc.InvalidParameterError`
2012-07-11 17:31:43 +02:00
:Example: :ref:`content_transition_example`
2012-02-26 21:29:25 +01:00
"""
workflow = portal.get_tool('portal_workflow')
try:
workflow.doActionFor(obj, transition)
except WorkflowException:
transitions = [
action['id'] for action in workflow.listActions(object=obj)
]
raise InvalidParameterError(
2013-01-26 16:40:10 +01:00
"Invalid transition '{0}'.\n"
"Valid transitions are:\n"
2013-01-26 16:40:10 +01:00
"{1}".format(transition, '\n'.join(sorted(transitions)))
)
@required_parameters('name', 'context', 'request')
def get_view(name=None, context=None, request=None):
"""Get a BrowserView object.
:param name: [required] Name of the view.
:type name: string
:param context: [required] Context on which to get view.
:type context: context object
:param request: [required] Request on which to get view.
:type request: request object
2012-08-26 21:57:27 +02:00
:raises:
2012-09-03 19:44:39 +02:00
:class:`~plone.api.exc.MissingParameterError`,
:class:`~plone.api.exc.InvalidParameterError`
2012-07-11 17:31:43 +02:00
:Example: :ref:`content_get_view_example`
"""
try:
return getMultiAdapter((context, request), name=name)
except:
# get a list of all views so we can display their names in the error
# msg
sm = getSiteManager()
views = sm.adapters.lookupAll(
required=(providedBy(context), providedBy(request)),
provided=Interface,
)
views_names = [view[0] for view in views]
raise InvalidParameterError(
2013-01-26 16:40:10 +01:00
"Cannot find a view with name '{0}'.\n"
"Available views are:\n"
2013-01-26 16:40:10 +01:00
"{1}".format(name, '\n'.join(sorted(views_names)))
)
2012-08-26 02:04:51 +02:00
@required_parameters('obj')
2012-09-03 12:59:38 +02:00
def get_uuid(obj=None):
"""Get the object's Universally Unique IDentifier (UUID).
2012-08-26 02:04:51 +02:00
:param obj: [required] Object we want its UUID.
:type obj: Content object
:returns: Object's UUID
:rtype: string
2012-08-26 21:57:27 +02:00
:raises:
ValueError
2012-09-03 12:59:38 +02:00
:Example: :ref:`content_get_uuid_example`
2012-08-26 02:04:51 +02:00
"""
return IUUID(obj)