[CMF-checkins] CVS: CMF - MetadataTool.py:1.1 __init__.py:1.4

tseaver@digicool.com tseaver@digicool.com
Sat, 28 Apr 2001 18:57:31 -0400 (EDT)


Update of /cvs-repository/CMF/CMFDefault
In directory korak:/tmp/cvs-serv2558/CMFDefault

Modified Files:
	__init__.py 
Added Files:
	MetadataTool.py 
Log Message:
 - Implement 'portal_metadata' tool proposal


--- Added File MetadataTool.py in package CMF ---
##############################################################################
# 
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
# 
# Copyright (c) Digital Creations.  All rights reserved.
# 
# This license has been certified as Open Source(tm).
# 
# 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 above copyright
#    notice, this list of conditions, and the following disclaimer.
# 
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions, and the following disclaimer in
#    the documentation and/or other materials provided with the
#    distribution.
# 
# 3. Digital Creations requests that attribution be given to Zope
#    in any manner possible. Zope includes a "Powered by Zope"
#    button that is installed by default. While it is not a license
#    violation to remove this button, it is requested that the
#    attribution remain. A significant investment has been put
#    into Zope, and this effort will continue if the Zope community
#    continues to grow. This is one way to assure that growth.
# 
# 4. All advertising materials and documentation mentioning
#    features derived from or use of this software must display
#    the following acknowledgement:
# 
#      "This product includes software developed by Digital Creations
#      for use in the Z Object Publishing Environment
#      (http://www.zope.org/)."
# 
#    In the event that the product being advertised includes an
#    intact Zope distribution (with copyright and license included)
#    then this clause is waived.
# 
# 5. Names associated with Zope or Digital Creations must not be used to
#    endorse or promote products derived from this software without
#    prior written permission from Digital Creations.
# 
# 6. Modified redistributions of any form whatsoever must retain
#    the following acknowledgment:
# 
#      "This product includes software developed by Digital Creations
#      for use in the Z Object Publishing Environment
#      (http://www.zope.org/)."
# 
#    Intact (re-)distributions of any official Zope release do not
#    require an external acknowledgement.
# 
# 7. Modifications are encouraged but must be packaged separately as
#    patches to official Zope releases.  Distributions that do not
#    clearly separate the patches from the original work must be clearly
#    labeled as unofficial distributions.  Modifications which do not
#    carry the name Zope may be packaged in any form, as long as they
#    conform to all of the clauses above.
# 
# 
# Disclaimer
# 
#   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``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 DIGITAL CREATIONS OR ITS
#   CONTRIBUTORS 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.
# 
# 
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations.  Specific
# attributions are listed in the accompanying credits file.
# 
##############################################################################

"""\
CMFDefault portal_metadata tool.
"""

import os
from OFS.SimpleItem import SimpleItem
from Globals import InitializeClass, HTMLFile, package_home
from AccessControl import getSecurityManager, ClassSecurityInfo
from Products.CMFCore.CMFCorePermissions import View, ManagePortal
from Products.CMFCore.utils import UniqueObject

_dtmldir = os.path.join( package_home( globals() ), 'dtml' )

class MetadataElementPolicy:
    """
        Represent a type-specific policy about a particular DCMI element.
    """
    #
    #   Default values.
    #
    is_required         = 0
    supply_default      = 0
    default_value       = ''
    enforce_vocabulary  = 0
    allowed_vocabulary  = ()

    def __init__( self, is_multi_valued=0 ):
        self.is_multi_valued    = not not is_multi_valued

    #
    #   Mutator.
    #
    def edit( self
            , is_required
            , supply_default
            , default_value
            , enforce_vocabulary
            , allowed_vocabulary
            ):
        self.is_required        = not not is_required
        self.supply_default     = not not supply_default
        self.default_value      = default_value
        self.enforce_vocabulary = not not enforce_vocabulary
        self.allowed_vocabulary = tuple( allowed_vocabulary )

    #
    #   Query interface
    #
    def isMultiValued( self ):
        """
            Can this element hold multiple values?
        """
        return self.is_multi_valued

    def isRequired( self ):
        """
            Must this element be supplied?
        """
        return self.is_required
    
    def supplyDefault( self ):
        """
            Should the tool supply a default?
        """
        return self.supply_default

    def defaultValue( self ):
        """
            If so, what is the default?
        """
        return self.default_value

    def enforceVocabulary( self ):
        """
        """
        return self.enforce_vocabulary

    def allowedVocabulary( self ):
        """
        """
        return self.allowed_vocabulary


DEFAULT_ELEMENT_SPECS = ( ( 'Title', 0 )
                        , ( 'Description', 0 )
                        , ( 'Subject', 1 )
                        , ( 'Format', 0 )
                        , ( 'Language', 0 )
                        , ( 'Rights', 0 )
                        )

class ElementSpec:
    """
        Represent all the tool knows about a single metadata element.
    """
    #
    #   Default values.
    #
    is_multi_valued = 0

    def __init__( self, is_multi_valued ):
        self.is_multi_valued  = is_multi_valued
        self.policies         = {}                  # XXX: use PersistentDict?
        self.policies[ None ] = self._makePolicy()  # set default policy
        
    
    def _makePolicy( self ):
        return MetadataElementPolicy( self.is_multi_valued )

    def isMultiValued( self ):
        """
            Is this element multi-valued?
        """
        return self.is_multi_valued

    def getPolicy( self, typ=None ):
        """
            Find the policy this element for objects whose type
            object name is 'typ';  return a default, if none found.
        """
        try:
            return self.policies[ typ ]
        except KeyError:
            return self.policies[ None ]

    def listPolicies( self ):
        """
            Return a list of all policies for this element.
        """
        return self.policies.items()

    def addPolicy( self, typ ):
        """
            Add a policy to this element for objects whose type
            object name is 'typ'.
        """
        if typ is None:
            raise MetadataError, "Can't replace default policy."

        if self.policies.has_key( typ ):
            raise MetadataError, "Existing policy for content type:" + typ

        self.policies[ typ ] = self._makePolicy()

    def removePolicy( self, typ ):
        """
            Remove the policy from this element for objects whose type
            object name is 'typ' (*not* the default, however).
        """
        if typ is None:
            raise MetadataError, "Can't remove default policy."
        del self.policies[ typ ]

class MetadataError( Exception ):
    pass

class MetadataTool( UniqueObject, SimpleItem ):

    id              = 'portal_metadata'
    meta_type       = 'Default Metadata Tool'

    security = ClassSecurityInfo()

    #
    #   Default values.
    #
    publisher           = ''
    element_specs       = None
    #initial_values_hook = None
    #validation_hook     = None

    def __init__( self
                , publisher=None
               #, initial_values_hook=None
               #, validation_hook=None
                , element_specs=DEFAULT_ELEMENT_SPECS
                ):

        self.editProperties( publisher
                          #, initial_values_hook
                          #, validation_hook
                           )

        self.element_specs = {}     # XXX: use PersistentDict?

        for name, is_multi_valued in element_specs:
            self.element_specs[ name ] = ElementSpec( is_multi_valued )

    #
    #   ZMI methods
    #
    manage_options = ( ( { 'label'      : 'Properties'
                         , 'action'     : 'propertiesForm'
                         }
                       , { 'label'      : 'Elements'
                         , 'action'     : 'elementPoliciesForm'
                         }
            # TODO     , { 'label'      : 'Types'
            #            , 'action'     : 'typesForm'
            #            }
                       )
                     + SimpleItem.manage_options
                     )

    security.declareProtected( ManagePortal , 'propertiesForm' )
    propertiesForm = HTMLFile( 'metadataProperties', _dtmldir )

    security.declareProtected( ManagePortal , 'editProperties' )
    def editProperties( self
                      , publisher=None
               # TODO , initial_values_hook=None
               # TODO , validation_hook=None
                      , REQUEST=None
                      ):
        """
            Form handler for "tool-wide" properties (including list of
            metadata elements).
        """
        if publisher is not None:
            self.publisher = publisher

        # TODO self.initial_values_hook = initial_values_hook
        # TODO self.validation_hook = validation_hook

        if REQUEST is not None:
            REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
                                        + '/propertiesForm'
                                        + '?manage_tabs_message=Tool+updated.'
                                        )

    security.declareProtected( ManagePortal , 'elementPoliciesForm' )
    elementPoliciesForm = HTMLFile( 'metadataElementPolicies', _dtmldir )

    security.declareProtected( ManagePortal , 'addElementPolicy' )
    def addElementPolicy( self
                        , element
                        , content_type
                        , is_required
                        , supply_default
                        , default_value
                        , enforce_vocabulary
                        , allowed_vocabulary
                        , REQUEST
                        ):
        """
            Add a type-specific policy for one of our elements.
        """
        if content_type == '<default>':
            content_type = None

        spec = self.getElementSpec( element )
        spec.addPolicy( content_type )
        policy = spec.getPolicy( content_type )
        policy.edit( is_required
                   , supply_default
                   , default_value
                   , enforce_vocabulary
                   , allowed_vocabulary
                   )
        if REQUEST is not None:
            REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
               + '/elementPoliciesForm'
               + '?element=' + element
               + '&manage_tabs_message=Policy+added.'
               )

    security.declareProtected( ManagePortal , 'removeElementPolicy' )
    def removeElementPolicy( self
                           , element
                           , content_type
                           , REQUEST
                           ):
        """
            Remvoe a type-specific policy for one of our elements.
        """
        if content_type == '<default>':
            content_type = None

        spec = self.getElementSpec( element )
        spec.removePolicy( content_type )
        if REQUEST is not None:
            REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
               + '/elementPoliciesForm'
               + '?element=' + element
               + '&manage_tabs_message=Policy+removed.'
               )

    security.declareProtected( ManagePortal , 'updateElementPolicy' )
    def updateElementPolicy( self
                           , element
                           , content_type
                           , is_required
                           , supply_default
                           , default_value
                           , enforce_vocabulary
                           , allowed_vocabulary
                           , REQUEST=None
                           ):
        """
            Update a policy for one of our elements ('content_type'
            will be '<default>' when we edit the default).
        """
        if content_type == '<default>':
            content_type = None
        spec = self.getElementSpec( element )
        policy = spec.getPolicy( content_type )
        policy.edit( is_required
                   , supply_default
                   , default_value
                   , enforce_vocabulary
                   , allowed_vocabulary
                   )
        if REQUEST is not None:
            REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
               + '/elementPoliciesForm'
               + '?element=' + element
               + '&manage_tabs_message=Policy+updated.'
               )


    #
    #   Element spec manipulation.
    #
    security.declareProtected( ManagePortal , 'listElementSpecs' )
    def listElementSpecs( self ):
        """
            Return a list of ElementSpecs representing
            the elements managed by the tool.
        """
        return tuple( self.element_specs.items() )

    security.declareProtected( ManagePortal , 'getElementSpec' )
    def getElementSpec( self, element ):
        """
            Return an ElementSpec representing the tool's knowledge
            of 'element'.
        """
        return self.element_specs[ element ]

    security.declareProtected( ManagePortal , 'addElementSpec' )
    def addElementSpec( self, element, is_multi_valued ):
        """
            Add 'element' to our list of managed elements.
        """
        # Don't replace.
        if self.element_specs.has_key( element ):
           return

        self.element_specs[ element ] = ElementSpec( is_multi_valued )

    security.declareProtected( ManagePortal , 'removeElementSpec' )
    def removeElementSpec( self, element ):
        """
            Remove 'element' from our list of managed elements.
        """
        del self.element_specs[ element ]

    security.declareProtected( 'listPolicies' )
    def listPolicies( self, typ=None ):
        """
            Show all policies for a given content type, or the default
            if None.
        """
        result = []
        for element, spec in self.listElementSpecs():
            result.append( ( element, spec.getPolicy( typ ) ) )
        return result

    #
    #   Site-wide queries.
    #
    security.declarePrivate( 'getFullName' )
    def getFullName( self, userid ):
        """
            Convert an internal userid to a "formal" name, if
            possible, perhaps using the 'portal_membership' tool.

            Used to map userid's for Creator, Contributor DCMI
            queries.
        """
        return userid   # TODO: do lookup here

    security.declarePublic( 'getPublisher' )
    def getPublisher( self ):
        """
            Return the "formal" name of the publisher of the
            portal.
        """
        return self.publisher

    #
    #   Content-specific queries.
    #
    security.declarePrivate( '_listAllowedVocabulary' )
    def _listAllowedVocabulary( self, element, content=None ):
        """
            List allowed keywords for a given meta_type, or all
            possible keywords if none supplied.
        """
        spec = self.getElementSpec( element )
        typ  = content and content.Type() or None
        return spec.getPolicy( typ ).allowedVocabulary()

    security.declarePublic( 'listAllowedSubjects' )
    def listAllowedSubjects( self, content=None ):
        """
            List allowed keywords for a given meta_type, or all
            possible keywords if none supplied.
        """
        return self._listAllowedVocabulary( 'Subject', content )

    security.declarePublic( 'listAllowedFormats' )
    def listAllowedFormats( self, content=None ):
        """
            List the allowed 'Content-type' values for a particular
            meta_type, or all possible formats if none supplied.
        """
        return self._listAllowedVocabulary( 'Format', content )

    security.declarePublic( 'listAllowedLanguages' )
    def listAllowedLanguages( self, content=None ):
        """
            List the allowed language values.
        """
        return self._listAllowedVocabulary( 'Language', content )

    security.declarePublic( 'listAllowedRights' )
    def listAllowedRights( self, content=None ):
        """
            List the allowed values for a "Rights"
            selection list;  this gets especially important where
            syndication is involved.
        """
        return self._listAllowedVocabulary( 'Rights', content )


    #
    #   Validation policy hooks.
    #
    security.declarePrivate( 'setInitialMetadata' )
    def setInitialMetadata( self, content ):
        """
            Set initial values for content metatdata, supplying
            any site-specific defaults.
        """
        for element, policy in self.listPolicies( content.Type() ):

            if not getattr( content, element )():

                if policy.supplyDefault():
                    setter = getattr( content, 'set%s' % element )
                    setter( policy.defaultValue() )
                elif policy.isRequired():
                    raise MetadataError, \
                          'Metadata element %s is required.' % element

        # TODO:  Call initial_values_hook, if present


    security.declarePrivate( 'validateMetadata' )
    def validateMetadata( self, content ):
        """
            Enforce portal-wide policies about DCI, e.g.,
            requiring non-empty title/description, etc.  Called
            by the CMF immediately before saving changes to the
            metadata of an object.
        """
        for element, policy in self.listPolicies( content.Type() ):

            value = getattr( content, element )()
            if not value and policy.isRequired():
                raise MetadataError, \
                        'Metadata element %s is required.' % element

            if policy.enforceVocabulary():
                values = policy.isMultiValued() and value or [ value ]
                for value in values:
                    if not value in policy.allowedValues():
                        raise MetadataError, \
                        'Value %s is not in allowed vocabulary for' \
                        'metadata element %s.' % ( value, element )

        # TODO:  Call validation_hook, if present

InitializeClass( MetadataTool )

--- Updated File __init__.py in package CMF --
--- __init__.py	2001/04/07 23:15:10	1.3
+++ __init__.py	2001/04/28 22:56:59	1.4
@@ -90,7 +90,7 @@
 import Portal
 import Document, Link, NewsItem, File, Image, Favorite
 import Discussions, DiscussionItem
-import PropertiesTool, MembershipTool
+import PropertiesTool, MembershipTool, MetadataTool
 import RegistrationTool, URLTool, DublinCore, DiscussionTool
 from Products.CMFCore import utils
 import Products.CMFCore
@@ -156,6 +156,7 @@
         , RegistrationTool.RegistrationTool
         , PropertiesTool.PropertiesTool
         , URLTool.URLTool
+        , MetadataTool.MetadataTool
         )
 
 import sys