[Zope-CMF] Flexible Metadata, 2nd edition

Christian Theune ct@gocept.com
Mon, 12 Aug 2002 19:32:42 +0200


--tNQTSEo8WG/FKZ8E
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

Howdi.

I spent some time with the Metadata tool, and finally
implemented a prototype that makes flexible Metadata possible.

I still have some problems (at least the catalogability for things not
implemented by Dublincore isn't working) but it's working, and full
backwards compatible.

What I did:

    - Modify the list of Elements that are metadata
    - Store the Metadata not in the object itself, but in a bag, that's
      attached to the object (PersistentMapping for the ease ...)
    - Created a new policy mechanism that builds up on "Validators".
      Every Element is associated with a validator, that cares for e.g.
      a Vocabulary, a Date Type and such things. I implemented
      Validators for Vocabulary, DateTime and String.
    - Automatic form generation for the edit_metadata_form (zpt skin
      only)

This is a prototype that shouldn't be used with *any* existing
installation. Get a CMF 1.3, apply the patch, then create the portal
and play around.

Who was asking for the proof-of-concept? Has it been you, Tres? Please
check this out, and tell me what you think.

(Yes, the code isn't very clean, and maybe not very self explanatory,
but it should be usable out of the box in a portal ...)

Regards
Christian

-- 
Christian Theune - ct@gocept.com
gocept gmbh & co.kg - schalaunische strasse 6 - 06366 koethen/anhalt
tel.+49 3641 511586 - fax.+49 3496 3099118 mob. - +49 179 7808366

reduce(lambda x,y:x+y,[chr(ord(x)^42) for x in 'zS^BED\nX_FOY\x0b'])

--tNQTSEo8WG/FKZ8E
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="CMFCore.patch"

diff -r CMFCore/CMFCatalogAware.py CMFCore.new/CMFCatalogAware.py
114a115
>         opaques = []
118,119c119,125
<                 return ((talkback.id, talkback),)
<         return ()
---
>                 opaques.append( (talkback.id, talkback) )
> 
>         if hasattr(aq_base(self), 'getMetadataBag'):
>             md = self.getMetadataBag()
>             for key in md:
>                 opaques.append( (key, md[key]) )
>         return tuple(opaques)
diff -r CMFCore/PortalContent.py CMFCore.new/PortalContent.py
24a25
> from ZODB.PersistentMapping import PersistentMapping
33a35,36
> from zLOG import LOG
> 
88a92,106
>     def getMetadataBag(self):
>         """
>             Returns the writeable Metadata bag of this object.
>         """
>         if not hasattr(self, "_metadata"):
>             self._metadata = PersistentMapping()            
> 
>         return self._metadata
> 
>     def getMetadata(self, element, default):
>         """
>             Returns the named metadate element.
>         """            
>         return self._metadata.get(element, default)
> 
diff -r CMFCore/PortalFolder.py CMFCore.new/PortalFolder.py
24a25
> from ZODB.PersistentMapping import PersistentMapping
80a82,90
>     def getMetadataBag(self):
>         """
>             Returns the writeable Metadata bag of this object.
>         """
>         if not hasattr(self, "_metadata"):
>             self._metadata = PersistentMapping()
>             
>         return self._metadata
> 
diff -r CMFCore/interfaces/Contentish.py CMFCore.new/interfaces/Contentish.py
65a66,70
> 
>     def getMetadataBag():
>         """
>         Return a dictionary-like bag, that holds the metadata of this object.
>         """
diff -r CMFCore/interfaces/portal_metadata.py CMFCore.new/interfaces/portal_metadata.py
20a21,78
> class IValidator(Base):
>     """
>         CMF Metadata field validator interface.
>     """
> 
>     def __init__(field):
>         """
>             Constructor. Field is the name of the field. 'Element' in the metadata tools jargon.
>         """
> 
>     def validate(data):
>         """
>             Check the given data. Probably convert it into something usable.
>             If the data was correct this method *must* return the validated/converted
>             data. If an error occurs, it has to raise an 'ValidationError'.
>         """
> 
>     def getPropertyNames():
>         """
>             Returns the names of the editable properties, that are stored with 
>             the updateProperties method.
>         """
> 
>     def updateProperties(**kw):
>         """
>             This is more or less opaque, corresponding to the getPropertyNames method.
>         """
> 
>     def getManagementSnippet():
>         """
>             Returns a piece of HTML, that contains a formular snippet for
>             the ZMI Metadata Tool part.
> 
>             Policy is a MetadataElementPolicy instance.
>         """
> 
>     def getInputSnippet(content):
>         """
>             Returns a piece of HTML, that contains a snippet of the Metadata form.
> 
>             content is a contentish object.
>         """            
> 
> class IValidatorRegistry(Base):
>     """
>         A registry for IValidator objects.
>     """
> 
>     def register(validator, name):
>         """
>             Registers a named validator.
>         """
> 
>     def get(name, default):
>         """
>             Returns a named validator.
>         """                
>             
95a154,159
>     def updateMetadat(content, metadata={}):
>         """
>             Update the set of Metadata on a given object, also validating
>             it. 
>         """
> 

--tNQTSEo8WG/FKZ8E
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="CMFDefault.patch"

diff -r CMFDefault/Document.py CMFDefault.new/Document.py
319,332d318
<     security.declareProtected(CMFCorePermissions.View, 'Description')
<     def Description(self):
<         """ Dublin core description, also important for indexing """
<         return self.description
<     
<     security.declareProtected(CMFCorePermissions.View, 'Format')
<     def Format(self):
<         """ Returns a content-type style format of the underlying source """
<         if self.text_format == 'html':
<             return 'text/html'
<         else:
<             return 'text/plain'
<     
< 
diff -r CMFDefault/DublinCore.py CMFDefault.new/DublinCore.py
27c27
< 
---
> from zLOG import LOG
47a48,50
> 
>         bag = self.getMetadataBag()
>         LOG("CMF", 0, "got bag: %s" % bag)
49,50c52,53
<         self.creation_date = now
<         self.modification_date = now
---
>         bag['creation_date'] = now
>         bag['modification_date'] = now
86,87c89,91
<         if modification_date is None:
<             self.modification_date = DateTime()
---
>         bag = self.getMetadataBag()
>         if bag.get('modification_date', None) is None:
>             bag['modification_date'] = DateTime()
89c93
<             self.modification_date = self._datify(modification_date)
---
>             bag['modification_date'] = self._datify(modification_date)
97c101,102
<         return self.title
---
>         bag = self.getMetadataBag()
>         return bag.get('Title', None)
112c117,118
<         return getattr( self, 'subject', () ) # compensate for *old* content
---
>         bag = self.getMetadataBag()
>         return bag.get('Subject', []) # compensate for *old* content
123c129,131
<         return self.description
---
>         bag = self.getMetadataBag()
> 	LOG("CMF", 0, "Somebody wants the description")
>         return bag.get('Description', None)
129c137,138
<         return self.contributors
---
>         bag = self.getMetadataBag()
>         return bag.get('contributors')
135c144,145
<         date = getattr(self, 'effective_date', None )
---
>         bag = self.getMetadataBag()
>         date = bag.get('effective_date', None)
146c156,157
<         return self.creation_date and self.creation_date.ISO() or 'Unknown'
---
>         bag = self.getMetadataBag()
>         return bag.get('creation_date', None) and bag['creation_date'].ISO() or 'Unknown'
153c164,165
<         ed = getattr( self, 'effective_date', None )
---
>         bag = self.getMetadataBag()
>         ed = bag.get('effective_date', None )
161c173,174
<         ed = getattr( self, 'expiration_date', None )
---
>         bag = self.getMetadataBag()
>         ed = bag.get('expiration_date', None )
185c198,199
<         return self.format
---
>         bag = self.getMetadataBag()
>         return bag.get('Format')
199c213,214
<         return self.language
---
>         bag = self.getMetadataBag()
>         return bag.get('Language')
206c221,222
<         return self.rights
---
>         bag = self.getMetadataBag()
>         return bag.get('Rights')
224,227c240,243
<         pastEffective = ( self.effective_date is None
<                        or self.effective_date <= date )
<         beforeExpiration = ( self.expiration_date is None
<                           or self.expiration_date >= date )
---
>         pastEffective = ( self.EffectiveDate() is None
>                        or self.EffectiveDate() <= date )
>         beforeExpiration = ( self.ExpirationDate() is None
>                           or self.ExpirationDate() >= date )
240c256,257
<         date = getattr( self, 'creation_date', None )
---
>         bag = self.getMetadataBag()
>         date = bag.get('creation_date', None)
248a266
>         bag = self.getMetadataBag()
250c268
<         date = getattr( self, 'effective_date', marker )
---
>         date = bag.get('effective_date', marker )
252c270
<             date = getattr( self, 'creation_date', None )
---
>             date = bag.get('creation_date', None )
263c281,282
<         date = getattr( self, 'expiration_date', None )
---
>         bag = self.getMetadataBag()
>         date = bag.get('expiration_date', None )
272c291,292
<         date = self.modification_date
---
>         bag = self.getMetadataBag()
>         date = bag.get('modification_date', None)
276c296
<             self.modification_date = date
---
>             bag['modification_date'] = date
315c335,336
<         self.title = title
---
>         bag = self.getMetadataBag()
>         bag['Title'] = title
321c342,343
<         self.subject = tuplize( 'subject', subject )
---
>         bag = self.getMetadataBag()
>         bag['Subject'] = tuplize( 'Subject', subject )
327c349,350
<         self.description = description
---
>         bag = self.getMetadataBag()
>         bag['Description'] = description
334c357,358
<         self.contributors = tuplize('contributors', contributors, semi_split)
---
>         bag = self.getMetadataBag()
>         bag['contributors'] = tuplize('contributors', contributors, semi_split)
342c366,367
<         self.effective_date = self._datify( effective_date )
---
>         bag = self.getMetadataBag()
>         bag['effective_date'] = self._datify( effective_date )
350c375,376
<         self.expiration_date = self._datify( expiration_date )
---
>         bag = self.getMetadataBag()
>         bag['expiration_date'] = self._datify( expiration_date )
358c384,385
<         self.format = format
---
>         bag = self.getMetadataBag()
>         bag['Format'] = format
366c393,394
<         self.language = language
---
>         bag = self.getMetadataBag()
>         bag['Language'] = language
374c402,403
<         self.rights = rights
---
>         bag = self.getMetadataBag()
>         bag['Rights'] = rights
diff -r CMFDefault/MetadataTool.py CMFDefault.new/MetadataTool.py
20a21
> from Acquisition import Implicit
22c23
< 
---
> from Products.CMFCore.interfaces import portal_metadata
29c30,62
< class MetadataElementPolicy( SimpleItem ):
---
> from zLOG import LOG
> 
> class ValidatorRegistry:
> 
>     __implements__ = portal_metadata.IValidatorRegistry
> 
>     def __init__(self):
>         self.registry = {}
> 
>     def register(self, validator, name):
>         """
>             Registers a named validator.
>         """
>         LOG("CMF", 0, "Registered validator: %s" % name)
>         self.registry[name] = validator
> 
>     def get(self, name, default=None):
>         """
>             Returns a named validator.
>         """
>         return self.registry.get(name, default)
> 
>     def listNames(self):
>         """
>             Lists all known validator names.
>         """
>         return self.registry.keys()
> 
> # Create a singleton of this Registry
> 
> ValidatorRegistry = ValidatorRegistry()
> 
> class MetadataElementPolicy( Implicit, SimpleItem ):
42a76
>     validator = None
44,45c78,81
<     def __init__( self, is_multi_valued=0 ):
<         self.is_multi_valued    = not not is_multi_valued
---
>     def __init__( self, field, validator = ValidatorRegistry.get('string')):
>         self.validator = validator
>         self.field = field
>         self.validator.updateProperties(self)
46a83,88
>     def getField(self):
>         """
>             Returns the name of the field (element) this policy belongs to.
>         """
>         return self.field
>     
51,56c93,95
<     def edit( self
<             , is_required
<             , supply_default
<             , default_value
<             , enforce_vocabulary
<             , allowed_vocabulary
---
>     def edit( self,
>               validator,
>               **kw
58,62c97,112
<         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 )
---
>             self.validator = validator
> 
>     def getValidator(self):
>         """
>             Returns the handle to the validator.
>         """
>         return self.validator
> 
>     def getInputSnippet(self, content):
>         """
>             Returns the metadata form snippet.
>         """
>         return self.validator.getInputSnippet(self, content)
> 
>     def validate(self, data):
>         return self.validator.validate(self, data)
110,115c160,165
< DEFAULT_ELEMENT_SPECS = ( ( 'Title', 0 )
<                         , ( 'Description', 0 )
<                         , ( 'Subject', 1 )
<                         , ( 'Format', 0 )
<                         , ( 'Language', 0 )
<                         , ( 'Rights', 0 )
---
> DEFAULT_ELEMENT_SPECS = ( ( 'Title', 'string' )
>                         , ( 'Description', 'string' )
>                         , ( 'Subject', 'string' )
>                         , ( 'Format', 'string' )
>                         , ( 'Language', 'string' )
>                         , ( 'Rights', 'string' )
118c168
< class ElementSpec( SimpleItem ):
---
> class ElementSpec( Implicit, SimpleItem ):
127c177
<     is_multi_valued = 0
---
>     validator = 'string'
129,130c179,181
<     def __init__( self, is_multi_valued ):
<         self.is_multi_valued  = is_multi_valued
---
>     def __init__( self, field, validator ):
>         self.validator  = validator
>         self.field = field
133d183
<         
137c187
<         return MetadataElementPolicy( self.is_multi_valued )
---
>         return MetadataElementPolicy( self.field, self.validator )
144c194
<         return self.is_multi_valued
---
>         return 0
196c246
< class MetadataTool( UniqueObject, SimpleItem, ActionProviderBase ):
---
> class MetadataTool( UniqueObject, Implicit, SimpleItem, ActionProviderBase ):
227,228c277,278
<         for name, is_multi_valued in element_specs:
<             self.element_specs[ name ] = ElementSpec( is_multi_valued )
---
>         for name, validator in element_specs:
>             self.element_specs[ name ] = ElementSpec( name, ValidatorRegistry.get(validator) )
298,302c348
<                         , is_required
<                         , supply_default
<                         , default_value
<                         , enforce_vocabulary
<                         , allowed_vocabulary
---
>                         , validator
314,319c360,362
<         policy.edit( is_required
<                    , supply_default
<                    , default_value
<                    , enforce_vocabulary
<                    , allowed_vocabulary
<                    )
---
>         policy.edit( validator )
>         policy.getValidator().updateProperties(policy)
> 
354,358c397
<                            , is_required
<                            , supply_default
<                            , default_value
<                            , enforce_vocabulary
<                            , allowed_vocabulary
---
>                            , validator
369,374c408,416
<         policy.edit( is_required
<                    , supply_default
<                    , default_value
<                    , enforce_vocabulary
<                    , allowed_vocabulary
<                    )
---
>         policy.edit( ValidatorRegistry.get(validator))
>         validator = policy.getValidator()
>         keys = validator.getPropertyNames()
>         kw = {}
>         for key in keys:
>             if hasattr(REQUEST, key):
>                 kw[key] = REQUEST[key]
>         validator.updateProperties(policy, **kw)
>         
409c451
<     def addElementSpec( self, element, is_multi_valued, REQUEST=None ):
---
>     def addElementSpec( self, element, validator, REQUEST=None ):
417c459,460
<         self.element_specs[ element ] = ElementSpec( is_multi_valued )
---
>         validator = ValidatorRegistry.get(validator)
>         self.element_specs[ element ] = ElementSpec( element, validator )
523d565
< 
525,531c567,568
< 
<                 if policy.supplyDefault():
<                     setter = getattr( content, 'set%s' % element )
<                     setter( policy.defaultValue() )
<                 elif policy.isRequired():
<                     raise MetadataError, \
<                           'Metadata element %s is required.' % element
---
>                 md = {}
>                 md[element] = policy.defaultValue()
534a572,583
>     def updateMetadata(self, content, metadata={}):
>         """
>             Update the metadata bag of the content object.
>         """
>         bag = content.getMetadataBag()
> 
>         for key in metadata.keys():
>             if metadata.has_key(key):
>                 bag[key] = metadata[key]
> 
>         self.validateMetadata(content)
> 
544a594,597
>         bag = content.getMetadataBag()
>         
>         LOG("CMF", 0, "Validating: %s" % self.listPolicies(content.getPortalTypeName()))
>             
545a599,601
>             value = bag.get(element, None)
>             LOG("CMF", 0, "Validating: %s (%s)" % (element, value))
>             bag[element] =  policy.validate(value)
547,560c603,604
<             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.allowedVocabulary():
<                         raise MetadataError, \
<                         'Value %s is not in allowed vocabulary for' \
<                         'metadata element %s.' % ( value, element )
< 
<         # TODO:  Call validation_hook, if present
---
>     def listKnownValidators(self):
>         return ValidatorRegistry.listNames()
562a607,608
> 
> 
diff -r CMFDefault/__init__.py CMFDefault.new/__init__.py
35a36
> import md_validators
Only in CMFDefault.new/dtml: input_DateValidator.pt
Only in CMFDefault.new/dtml: input_DictionaryValidator.pt
Only in CMFDefault.new/dtml: input_StringValidator.pt
diff -r CMFDefault/dtml/metadataElementPolicies.dtml CMFDefault.new/dtml/metadataElementPolicies.dtml
32a33
>  
37c38
<            rqd="policy.isRequired() and 'checked' or ''"
---
>            validator="policy.getValidator()"
39,43d39
<            supply="policy.supplyDefault() and 'checked' or ''"
<            rawdef="policy.defaultValue()"
<            defval="(multi and ( _.string.join( rawdef ), ) or ( rawdef, ))[0]"
<            enforce="policy.enforceVocabulary() and 'checked' or ''"
<            vocab="_.string.join( policy.allowedVocabulary(), '\n' )"
69,96c65,78
<   <th> Required? </th>
<   <td>
<     <input type="checkbox" name="is_required:boolean" &dtml-rqd;>
<     <input type="hidden" name="is_required:int:default" value="0">
<   </td>
<  </tr>
<  
<  <tr valign="top">
<   <th> Supply default? </th>
<   <td>
<     <input type="checkbox" name="supply_default:boolean" &dtml-supply;>
<     <input type="hidden" name="supply_default:int:default" value="0">
<   </td>
<   <th> Default </th>
<   <td> <input type="text" name="default_value&dtml-tokenz;"
<               value="&dtml-defval;" size="40"> </td>
<  </tr>
<  
<  <tr valign="top">
<   <th> Enforce vocabulary? </th>
<   <td>
<     <input type="checkbox" name="enforce_vocabulary:boolean" &dtml-enforce;>
<     <input type="hidden" name="enforce_vocabulary:int:default" value="0">
<   </td>
<   <th> Vocabulary </th>
<   <td> <textarea name="allowed_vocabulary:lines"
<                  rows="5" cols="20">&dtml-vocab;</textarea>
<  </tr>
---
> 
> <tr valign="top">
>    <th> Validator: </th>
>      <td> <select name="validator">
>             <dtml-in "portal_metadata.listKnownValidators()">
>                     <option value="&dtml-sequence-item;" <dtml-if "validator and _.hasattr(validator, 'name') and (_['sequence-item'] == validator.name)">selected</dtml-if>>&dtml-sequence-item; </option>
>             </dtml-in>
>            </select>
>      </td>
> </tr>
>                                           
>  <dtml-if "validator and _.hasattr(validator, 'getManagementSnippet')"> 
>  <dtml-var "validator.getManagementSnippet(policy)">
>  </dtml-if>
diff -r CMFDefault/dtml/metadataProperties.dtml CMFDefault.new/dtml/metadataProperties.dtml
35,36c35,41
<   <th width="100" align="right"> Multi-valued? </th>
<   <td> <input type="checkbox" name="is_multi_valued:boolean"> </td>
---
>   <th width="100" align="right"> Validator: </th>
>   <td> <select name="validator">
>        <dtml-in "portal_metadata.listKnownValidators()">
>         <option value="&dtml-sequence-item;">&dtml-sequence-item; </option>
>        </dtml-in>
>        </select>
>        </td>
Only in CMFDefault.new/dtml: zmi_DateValidator.pt
Only in CMFDefault.new/dtml: zmi_DictionaryValidator.pt
Only in CMFDefault.new/dtml: zmi_StringValidator.pt
Only in CMFDefault.new: md_validators.py
diff -r CMFDefault/skins/content/metadata_edit.py CMFDefault.new/skins/content/metadata_edit.py
3c3
< ##parameters=allowDiscussion=None,title=None,subject=None,description=None,contributors=None,effective_date=None,expiration_date=None,format=None,language=None,rights=None
---
> ##parameters=allowDiscussion=None,subject=None,contributors=None,**kw
13,14c13,16
< if title is None:
<     title = context.Title()
---
> if kw is None:
>     kw = {}
> else:    
>     kw.update(context.REQUEST.form)
16,19c18
< if subject is None:
<     subject = context.Subject()
< else:
<     subject = tuplify( subject )
---
> md = {}
21,22c20
< if description is None:
<     description = context.Description()
---
> allowed_keys = [ x[0] for x in context.portal_metadata.listPolicies(context.Type()) ]
24,27c22,24
< if contributors is None:
<     contributors = context.Contributors()
< else:
<     contributors = tuplify( contributors )
---
> for x in allowed_keys:
>     if kw.has_key(x):
>         md[x] = kw[x]
29,30c26,27
< if effective_date is None:
<     effective_date = context.EffectiveDate()
---
> if subject:
>     md['Subject'] = tuplify( subject )
32,42c29,30
< if expiration_date is None:
<     expiration_date = context.expires()
< 
< if format is None:
<     format = context.Format()
< 
< if language is None:
<     language = context.Language()
< 
< if rights is None:
<     rights = context.Rights()
---
> if contributors:
>     md['contributors'] = tuplify( contributors )
47,56c35,36
<     context.editMetadata( title=title
<                         , description=description
<                         , subject=subject
<                         , contributors=contributors
<                         , effective_date=effective_date
<                         , expiration_date=expiration_date
<                         , format=format
<                         , language=language
<                         , rights=rights
<                         )
---
>     context.portal_metadata.updateMetadata(context, md)
>     
68,74c48,56
< except Exception, msg:
<     target_action = context.getTypeInfo().getActionById( 'metadata' )
<     context.REQUEST.RESPONSE.redirect('%s/%s?portal_status_message=%s' % (
<                                                                           context.absolute_url()
<                                                                         , target_action
<                                                                         , msg
<                                                                          ))
---
> finally:
>     pass
> #except Exception, msg:
> #    target_action = context.getTypeInfo().getActionById( 'metadata' )
> #    context.REQUEST.RESPONSE.redirect('%s/%s?portal_status_message=%s' % (
> #                                                                          context.absolute_url()
> #                                                                        , target_action
> #                                                                        , msg
> #                                                                         ))
diff -r CMFDefault/skins/content/metadata_edit_form.dtml CMFDefault.new/skins/content/metadata_edit_form.dtml
33c33
<           name="title"
---
>           name="Title"
44c44
<    <textarea name="description:text" rows="5"
---
>    <textarea name="Description:text" rows="5"
86c86
<   <td> <input type="text" name="format" value="&dtml-Format;">
---
>   <td> <input type="text" name="Format" value="&dtml-Format;">
diff -r CMFDefault/skins/zpt_content/metadata_edit_form.pt CMFDefault.new/skins/zpt_content/metadata_edit_form.pt
21,63c21,23
<  <tr valign="top">
< 
<   <th>
<     Enable Discussion?
<   </th>
<   <td>
<     <select name="allowDiscussion"
<             tal:define="val here/isDiscussable">
<         <option value="None"
<                 tal:attributes="selected python: val == None"> Default </option>
<         <option value="0"
<                 tal:attributes="selected python: val == 0"> Off </option>
<         <option value="1"
<                 tal:attributes="selected python: val == 1"> On </option>
<      </select>
<   </td>
<   <td colspan="2" align="right">
<    <a href="full_metadata_edit_form"
<       tal:attributes="href string:${here/absolute_url}/full_metadata_edit_form"
<      >Edit all metadata</a>
<   </td>
< 
<  </tr>
< 
<  <tr valign="top">
< 
<   <th align="right"> Identifier </th>
<   <td colspan="3"> <span tal:replace="here/Identifier" /> </td>
< 
<  </tr>
< 
<  <tr valign="top">
< 
<   <th align="right"> Title </th>
<   <td colspan="3">
<    <input type="text"
<           name="title"
<           value=""
<           size="65"
< 		  tal:attributes="value here/Title">
<   </td>
< 
<  </tr>
---
>  <span tal:omit-tag=""
>        tal:repeat="policy python:here.portal_metadata.listPolicies(here.getPortalTypeName())"
>        tal:content="structure python:policy[1].getInputSnippet(here)" />
66,71c26,29
< 
<   <th align="right"> Description </th>
<   <td colspan="3">
<    <textarea name="description:text" rows="5"
<              cols="65" wrap="soft"
< 			 tal:content="here/Description"></textarea>
---
>  <td colspan="2" align="center">
>   <input type="submit" name="change" value=" Change ">
>   <input type="submit" name="change_and_edit" value=" Change & Edit ">
>   <input type="submit" name="change_and_view" value=" Change & View ">
73,102d30
< 
<  </tr>
< 
<  <tr valign="top">
<        <th align="right"> Subject </th>
<   <td tal:define="subj_lines python: modules['string'].join(
<                                          here.subjectsList(), '\n' )">
<      <textarea name="subject:lines" rows="3" cols="20"
< 	           tal:content="subj_lines"></textarea>
<  <br> 
<    <select name="subject:list" multiple>
<      <option value=""
< 		   tal:define="items python: here.portal_metadata.listAllowedSubjects(
<                                               here);
<                        subjects here/Subject;
<                       "
< 		   tal:repeat="item items"
< 		   tal:attributes="value item; selected python: item in subjects"
< 		   tal:content="item">
<       </option>
<     </select> 
<   </td>
< 
<   <th align="right"> Format </th>
<   <td> <input type="text" name="format" value="" tal:attributes="value here/Format">
<   <br> <input type="submit" name="change" value=" Change ">
<   <br> <input type="submit" name="change_and_edit" value=" Change & Edit ">
<   <br> <input type="submit" name="change_and_view" value=" Change & View ">
<   </td>
< 

--tNQTSEo8WG/FKZ8E--