Back to index

plone3  3.1.7
MetadataTool.py
Go to the documentation of this file.
00001 ##############################################################################
00002 #
00003 # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
00004 #
00005 # This software is subject to the provisions of the Zope Public License,
00006 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
00007 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
00008 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00009 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
00010 # FOR A PARTICULAR PURPOSE.
00011 #
00012 ##############################################################################
00013 """ CMFDefault portal_metadata tool.
00014 
00015 $Id: MetadataTool.py 77004 2007-06-24 08:57:54Z yuppie $
00016 """
00017 
00018 from AccessControl import ClassSecurityInfo
00019 from Globals import DTMLFile
00020 from Globals import InitializeClass
00021 from Globals import PersistentMapping
00022 from OFS.Folder import Folder
00023 from OFS.SimpleItem import SimpleItem
00024 from zope.interface import implements
00025 
00026 from Products.CMFCore.interfaces import IMetadataTool
00027 from Products.CMFCore.interfaces.portal_metadata \
00028         import portal_metadata as z2IMetadataTool
00029 from Products.CMFCore.utils import registerToolInterface
00030 from Products.CMFCore.utils import UniqueObject
00031 
00032 from exceptions import MetadataError
00033 from permissions import ManagePortal
00034 from permissions import ModifyPortalContent
00035 from permissions import View
00036 from utils import _dtmldir
00037 
00038 
00039 class MetadataElementPolicy( SimpleItem ):
00040 
00041     """ Represent a type-specific policy about a particular metadata element.
00042     """
00043 
00044     security = ClassSecurityInfo()
00045     #
00046     #   Default values.
00047     #
00048     is_required         = 0
00049     supply_default      = 0
00050     default_value       = ''
00051     enforce_vocabulary  = 0
00052     allowed_vocabulary  = ()
00053 
00054     def __init__(self, is_multi_valued=False):
00055         self.is_multi_valued = bool(is_multi_valued)
00056 
00057     #
00058     #   Mutator.
00059     #
00060     security.declareProtected(ManagePortal , 'edit')
00061     def edit( self
00062             , is_required
00063             , supply_default
00064             , default_value
00065             , enforce_vocabulary
00066             , allowed_vocabulary
00067             ):
00068         self.is_required        = bool(is_required)
00069         self.supply_default     = bool(supply_default)
00070         self.default_value      = default_value
00071         self.enforce_vocabulary = bool(enforce_vocabulary)
00072         self.allowed_vocabulary = tuple(allowed_vocabulary)
00073 
00074     #
00075     #   Query interface
00076     #
00077     security.declareProtected(View , 'isMultiValued')
00078     def isMultiValued( self ):
00079         """ Can this element hold multiple values?
00080         """
00081         return self.is_multi_valued
00082 
00083     security.declareProtected(View , 'isRequired')
00084     def isRequired( self ):
00085         """ Must this element be supplied?
00086         """
00087         return self.is_required
00088 
00089     security.declareProtected(View , 'supplyDefault')
00090     def supplyDefault( self ):
00091         """ Should the tool supply a default?
00092         """
00093         return self.supply_default
00094 
00095     security.declareProtected(View , 'defaultValue')
00096     def defaultValue( self ):
00097         """ If so, what is the default?
00098         """
00099         return self.default_value
00100 
00101     security.declareProtected(View , 'enforceVocabulary')
00102     def enforceVocabulary( self ):
00103         """ Should the tool enforce the policy's vocabulary?
00104         """
00105         return self.enforce_vocabulary
00106 
00107     security.declareProtected(View , 'allowedVocabulary')
00108     def allowedVocabulary( self ):
00109         """ What are the allowed values?
00110         """
00111         return self.allowed_vocabulary
00112 
00113 InitializeClass( MetadataElementPolicy )
00114 
00115 
00116 
00117 class ElementSpec( SimpleItem ):
00118 
00119     """ Represent all the tool knows about a single metadata element.
00120     """
00121 
00122     security = ClassSecurityInfo()
00123 
00124     #
00125     #   Default values.
00126     #
00127     is_multi_valued = 0
00128 
00129     def __init__( self, is_multi_valued ):
00130         self.is_multi_valued  = is_multi_valued
00131         self.policies         = PersistentMapping()
00132         self.policies[ None ] = self._makePolicy()  # set default policy
00133 
00134     security.declarePrivate( '_makePolicy' )
00135     def _makePolicy( self ):
00136         return MetadataElementPolicy( self.is_multi_valued )
00137 
00138     security.declareProtected(View , 'isMultiValued')
00139     def isMultiValued( self ):
00140         """
00141             Is this element multi-valued?
00142         """
00143         return self.is_multi_valued
00144 
00145     security.declareProtected(View , 'getPolicy')
00146     def getPolicy( self, typ=None ):
00147         """ Find the policy for this element for objects of the given type.
00148 
00149         o Return a default, if none found.
00150         """
00151         try:
00152             return self.policies[ typ ].__of__(self)
00153         except KeyError:
00154             return self.policies[ None ].__of__(self)
00155 
00156     security.declareProtected(View , 'listPolicies')
00157     def listPolicies( self ):
00158         """ Return a list of all policies for this element.
00159         """
00160         res = []
00161         for k, v in self.policies.items():
00162             res.append((k, v.__of__(self)))
00163         return res
00164 
00165     security.declareProtected(ManagePortal , 'addPolicy')
00166     def addPolicy( self, typ ):
00167         """ Add a policy to this element for objects of the given type.
00168         """
00169         if typ is None:
00170             raise MetadataError, "Can't replace default policy."
00171 
00172         if self.policies.has_key( typ ):
00173             raise MetadataError, "Existing policy for content type:" + typ
00174 
00175         self.policies[ typ ] = self._makePolicy()
00176 
00177     security.declareProtected(ManagePortal, 'removePolicy')
00178     def removePolicy( self, typ ):
00179         """ Remove the policy from this element for objects of the given type.
00180 
00181         o Do *not* remvoe the default, however.
00182         """
00183         if typ is None:
00184             raise MetadataError, "Can't remove default policy."
00185         del self.policies[ typ ]
00186 
00187 InitializeClass( ElementSpec )
00188 
00189 
00190 class MetadataSchema( SimpleItem ):
00191 
00192     """ Describe a metadata schema.
00193     """
00194 
00195     security = ClassSecurityInfo()
00196 
00197     meta_type = 'Metadata Schema'
00198     publisher = ''
00199 
00200     def __init__( self, id, element_specs=() ):
00201         self._setId( id )
00202         self.element_specs = PersistentMapping()
00203         for name, is_multi_valued in element_specs:
00204             self.element_specs[ name ] = ElementSpec( is_multi_valued )
00205 
00206 
00207     #
00208     #   ZMI methods
00209     #
00210     manage_options = ( ( { 'label'      : 'Elements'
00211                          , 'action'     : 'elementPoliciesForm'
00212                          }
00213                        ,
00214                        )
00215                      + SimpleItem.manage_options
00216                      )
00217 
00218     security.declareProtected(ManagePortal, 'elementPoliciesForm')
00219     elementPoliciesForm = DTMLFile( 'metadataElementPolicies', _dtmldir )
00220 
00221     security.declareProtected(ManagePortal, 'addElementPolicy')
00222     def addElementPolicy( self
00223                         , element
00224                         , content_type
00225                         , is_required
00226                         , supply_default
00227                         , default_value
00228                         , enforce_vocabulary
00229                         , allowed_vocabulary
00230                         , REQUEST=None
00231                         ):
00232         """ Add a type-specific policy for one of our elements.
00233         """
00234         if content_type == '<default>':
00235             content_type = None
00236 
00237         spec = self.getElementSpec( element )
00238         spec.addPolicy( content_type )
00239         policy = spec.getPolicy( content_type )
00240         policy.edit( is_required
00241                    , supply_default
00242                    , default_value
00243                    , enforce_vocabulary
00244                    , allowed_vocabulary
00245                    )
00246         if REQUEST is not None:
00247             REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
00248                + '/elementPoliciesForm'
00249                + '?element=' + element
00250                + '&manage_tabs_message=Policy+added.'
00251                )
00252 
00253     security.declareProtected(ManagePortal, 'removeElementPolicy')
00254     def removeElementPolicy( self
00255                            , element
00256                            , content_type
00257                            , REQUEST=None
00258                            ):
00259         """ Remvoe a type-specific policy for one of our elements.
00260         """
00261         if content_type == '<default>':
00262             content_type = None
00263 
00264         spec = self.getElementSpec( element )
00265         spec.removePolicy( content_type )
00266         if REQUEST is not None:
00267             REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
00268                + '/elementPoliciesForm'
00269                + '?element=' + element
00270                + '&manage_tabs_message=Policy+removed.'
00271                )
00272 
00273     security.declareProtected(ManagePortal, 'updateElementPolicy')
00274     def updateElementPolicy( self
00275                            , element
00276                            , content_type
00277                            , is_required
00278                            , supply_default
00279                            , default_value
00280                            , enforce_vocabulary
00281                            , allowed_vocabulary
00282                            , REQUEST=None
00283                            ):
00284         """ Update a policy for one of our elements 
00285 
00286         o 'content_type' will be '<default>' when we edit the default.
00287         """
00288         if content_type == '<default>':
00289             content_type = None
00290         spec = self.getElementSpec( element )
00291         policy = spec.getPolicy( content_type )
00292         policy.edit( is_required
00293                    , supply_default
00294                    , default_value
00295                    , enforce_vocabulary
00296                    , allowed_vocabulary
00297                    )
00298         if REQUEST is not None:
00299             REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
00300                + '/elementPoliciesForm'
00301                + '?element=' + element
00302                + '&manage_tabs_message=Policy+updated.'
00303                )
00304 
00305 
00306     #
00307     #   Element spec manipulation.
00308     #
00309     security.declareProtected(ManagePortal, 'listElementSpecs')
00310     def listElementSpecs( self ):
00311         """ Return a list of ElementSpecs representing the elements we manage.
00312         """
00313         res = []
00314         for k, v in self.element_specs.items():
00315             res.append((k, v.__of__(self)))
00316         return res
00317 
00318     security.declareProtected(ManagePortal, 'getElementSpec')
00319     def getElementSpec( self, element ):
00320         """ Return an ElementSpec for the given 'element'.
00321         """
00322         return self.element_specs[ element ].__of__( self )
00323 
00324     security.declareProtected(ManagePortal, 'addElementSpec')
00325     def addElementSpec( self, element, is_multi_valued, REQUEST=None ):
00326         """ Add 'element' to our list of managed elements.
00327         """
00328         # Don't replace.
00329         if self.element_specs.has_key( element ):
00330            return
00331 
00332         self.element_specs[ element ] = ElementSpec( is_multi_valued )
00333 
00334         if REQUEST is not None:
00335             REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
00336                + '/propertiesForm'
00337                + '?manage_tabs_message=Element+' + element + '+added.'
00338                )
00339 
00340     security.declareProtected(ManagePortal, 'removeElementSpec')
00341     def removeElementSpec( self, element, REQUEST=None ):
00342         """ Remove 'element' from our list of managed elements.
00343         """
00344         del self.element_specs[ element ]
00345 
00346         if REQUEST is not None:
00347             REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
00348                + '/propertiesForm'
00349                + '?manage_tabs_message=Element+' + element + '+removed.'
00350                )
00351 
00352     security.declareProtected(ManagePortal, 'listPolicies')
00353     def listPolicies( self, typ=None ):
00354         """ Show all policies for a given content type
00355 
00356         o If 'typ' is none, return the list of default policies.
00357         """
00358         result = []
00359         for element, spec in self.listElementSpecs():
00360             result.append( ( element, spec.getPolicy( typ ) ) )
00361         return result
00362 
00363 InitializeClass(MetadataSchema)
00364 
00365 
00366 _DCMI_ELEMENT_SPECS = ( ( 'Title', 0 )
00367                       , ( 'Description', 0 )
00368                       , ( 'Subject', 1 )
00369                       , ( 'Format', 0 )
00370                       , ( 'Language', 0 )
00371                       , ( 'Rights', 0 )
00372                       )
00373 
00374 class MetadataTool(UniqueObject, Folder):
00375 
00376     implements(IMetadataTool)
00377     __implements__ = (z2IMetadataTool, )
00378 
00379     id = 'portal_metadata'
00380     meta_type = 'Default Metadata Tool'
00381 
00382     _DCMI = None
00383     def _get_DCMI( self ):
00384 
00385         if self._DCMI is None:
00386             dcmi = self._DCMI = MetadataSchema( 'DCMI', _DCMI_ELEMENT_SPECS )
00387 
00388             old_specs = getattr( self, 'element_specs', None )
00389             if old_specs is not None:
00390                 del self.element_specs
00391                 for element_id, old_spec in old_specs.items():
00392                     new_spec = dcmi.getElementSpec( element_id )
00393                     for typ, policy in old_spec.listPolicies():
00394                         if typ is not None:
00395                             new_spec.addPolicy( typ )
00396                         tp = new_spec.getPolicy( typ )
00397                         tp.edit( is_required=policy.isRequired()
00398                                , supply_default=policy.supplyDefault()
00399                                , default_value=policy.defaultValue()
00400                                , enforce_vocabulary=policy.enforceVocabulary()
00401                                , allowed_vocabulary=policy.allowedVocabulary()
00402                                )
00403 
00404         return self._DCMI
00405 
00406     DCMI = property(_get_DCMI, None)
00407 
00408     #
00409     #   Default values.
00410     #
00411     publisher           = ''
00412 
00413     security = ClassSecurityInfo()
00414 
00415     def __init__( self, publisher=None ):
00416 
00417         self.editProperties( publisher )
00418 
00419     #
00420     #   ZMI methods
00421     #
00422     manage_options = ( ( { 'label'      : 'Schema'
00423                          , 'action'     : 'propertiesForm'
00424                          }
00425                        , { 'label'      : 'Overview'
00426                          , 'action'     : 'manage_overview'
00427                          }
00428                        )
00429                      + Folder.manage_options
00430                      )
00431 
00432     security.declareProtected(ManagePortal, 'manage_overview')
00433     manage_overview = DTMLFile( 'explainMetadataTool', _dtmldir )
00434 
00435     security.declareProtected(ManagePortal, 'propertiesForm')
00436     propertiesForm = DTMLFile( 'metadataProperties', _dtmldir )
00437 
00438     security.declareProtected(ManagePortal, 'editProperties')
00439     def editProperties( self
00440                       , publisher=None
00441                       , REQUEST=None
00442                       ):
00443         """ Form handler for "tool-wide" properties 
00444         """
00445         if publisher is not None:
00446             self.publisher = publisher
00447 
00448         if REQUEST is not None:
00449             REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
00450                                         + '/propertiesForm'
00451                                         + '?manage_tabs_message=Tool+updated.'
00452                                         )
00453 
00454     security.declareProtected(ManagePortal, 'manage_addSchema')
00455     def manage_addSchema( self, schema_id, elements, REQUEST ):
00456         """ ZMI wrapper around addSchema
00457         """
00458         massaged = []
00459         for element in elements:
00460             if isinstance(element, basestring):
00461                 element = element.split(',')
00462                 if len( element ) < 2:
00463                     element.append(0)
00464             massaged.append( element )
00465         self.addSchema( schema_id, massaged )
00466 
00467         REQUEST['RESPONSE'].redirect( self.absolute_url()
00468                                     + '/propertiesForm'
00469                                     + '?manage_tabs_message=Schema+added.'
00470                                     )
00471 
00472     security.declareProtected(ManagePortal, 'manage_removeSchemas')
00473     def manage_removeSchemas( self, schema_ids, REQUEST ):
00474         """ ZMI wrapper around removeSchema
00475         """
00476         if not schema_ids:
00477             raise ValueError, 'No schemas selected!'
00478 
00479         for schema_id in schema_ids:
00480             self.removeSchema( schema_id )
00481 
00482         REQUEST['RESPONSE'].redirect( self.absolute_url()
00483                                     + '/propertiesForm'
00484                                     + '?manage_tabs_message=Schemas+removed.'
00485                                     )
00486 
00487     security.declarePrivate( 'getFullName' )
00488     def getFullName( self, userid ):
00489         """ See IMetadataTool.
00490         """
00491         return userid   # TODO: do lookup here
00492 
00493     security.declarePublic( 'getPublisher' )
00494     def getPublisher( self ):
00495         """ See IMetadataTool.
00496         """
00497         return self.publisher
00498 
00499     security.declarePublic( 'listAllowedSubjects' )
00500     def listAllowedSubjects( self, content=None, content_type=None ):
00501         """ See IMetadataTool.
00502         """
00503         return self.listAllowedVocabulary( 'DCMI'
00504                                          , 'Subject'
00505                                          , content
00506                                          , content_type
00507                                          )
00508 
00509     security.declarePublic( 'listAllowedFormats' )
00510     def listAllowedFormats( self, content=None, content_type=None ):
00511         """ See IMetadataTool.
00512         """
00513         return self.listAllowedVocabulary( 'DCMI'
00514                                          , 'Format'
00515                                          , content
00516                                          , content_type
00517                                          )
00518 
00519     security.declarePublic( 'listAllowedLanguages' )
00520     def listAllowedLanguages( self, content=None, content_type=None ):
00521         """ See IMetadataTool.
00522         """
00523         return self.listAllowedVocabulary( 'DCMI'
00524                                          , 'Language'
00525                                          , content
00526                                          , content_type
00527                                          )
00528 
00529     security.declarePublic( 'listAllowedRights' )
00530     def listAllowedRights( self, content=None, content_type=None ):
00531         """ See IMetadata Tool.
00532         """
00533         return self.listAllowedVocabulary( 'DCMI'
00534                                          , 'Rights'
00535                                          , content
00536                                          , content_type
00537                                          )
00538 
00539     security.declarePublic( 'listAllowedVocabulary' )
00540     def listAllowedVocabulary( self
00541                              , schema
00542                              , element
00543                              , content=None
00544                              , content_type=None
00545                              ):
00546         """ See IMetadataTool.
00547         """
00548         schema_def = getattr( self, schema )
00549         spec = schema_def.getElementSpec( element )
00550         if content_type is None and content:
00551             content_type = content.getPortalTypeName()
00552         return spec.getPolicy( content_type ).allowedVocabulary()
00553 
00554     security.declarePublic( 'listSchemas' )
00555     def listSchemas( self ):
00556         """ See IMetadataTool.
00557         """
00558         result = [ ( 'DCMI', self.DCMI ) ]
00559         result.extend( self.objectItems( [ MetadataSchema.meta_type ] ) )
00560         return result
00561 
00562     security.declareProtected(ModifyPortalContent, 'addSchema')
00563     def addSchema( self, schema_id, elements=() ):
00564         """ See IMetadataTool.
00565         """
00566         if schema_id == 'DCMI' or schema_id in self.objectIds():
00567             raise KeyError, 'Duplicate schema ID: %s' % schema_id
00568 
00569         schema = MetadataSchema( schema_id, elements )
00570         self._setObject( schema_id, schema )
00571 
00572         return self._getOb( schema_id )
00573 
00574     security.declareProtected(ModifyPortalContent, 'removeSchema')
00575     def removeSchema( self, schema_id ):
00576         """ See IMetadataTool.
00577         """
00578         if schema_id == 'DCMI' or schema_id not in self.objectIds():
00579             raise KeyError, 'Invalid schema ID: %s' % schema_id
00580 
00581         self._delObject( schema_id )
00582 
00583     security.declareProtected(ModifyPortalContent, 'setInitialMetadata')
00584     def setInitialMetadata( self, content ):
00585         """ See IMetadataTool.
00586         """
00587         for schema_id, schema in self.listSchemas():
00588             for element, policy in schema.listPolicies(
00589                                     content.getPortalTypeName()):
00590 
00591                 if not getattr( content, element )():
00592 
00593                     if policy.supplyDefault():
00594                         setter = getattr( content, 'set%s' % element )
00595                         setter( policy.defaultValue() )
00596                     elif policy.isRequired():
00597                         raise MetadataError, \
00598                             'Metadata element %s is required.' % element
00599 
00600         # TODO:  Call initial_values_hook, if present
00601 
00602     security.declareProtected(View, 'validateMetadata')
00603     def validateMetadata( self, content ):
00604         """ See IMetadataTool.
00605         """
00606         for schema_id, schema in self.listSchemas():
00607             for element, policy in schema.listPolicies(
00608                                     content.getPortalTypeName()):
00609 
00610                 value = getattr( content, element )()
00611                 if not value and policy.isRequired():
00612                     raise MetadataError, \
00613                             'Metadata element %s is required.' % element
00614 
00615                 if value and policy.enforceVocabulary():
00616                     values = policy.isMultiValued() and value or [ value ]
00617                     for value in values:
00618                         if not value in policy.allowedVocabulary():
00619                             raise MetadataError, \
00620                             'Value %s is not in allowed vocabulary for ' \
00621                             'metadata element %s.' % ( value, element )
00622 
00623 InitializeClass( MetadataTool )
00624 registerToolInterface('portal_metadata', IMetadataTool)