Back to index

plone3  3.1.7
__init__.py
Go to the documentation of this file.
00001 from __future__ import nested_scopes
00002 from types import ListType, TupleType, StringType
00003 from Products.Archetypes.Storage import MetadataStorage
00004 from Products.Archetypes.Layer import DefaultLayerContainer
00005 from Products.Archetypes.interfaces.field import IField
00006 from Products.Archetypes.interfaces.layer import ILayerContainer, \
00007      ILayerRuntime, ILayer
00008 from Products.Archetypes.interfaces.schema import ISchema, ISchemata, \
00009      IManagedSchema
00010 from Products.Archetypes.utils import OrderedDict, mapply, shasattr
00011 from Products.Archetypes.mimetype_utils import getDefaultContentType
00012 from Products.Archetypes.debug import warn
00013 from Products.Archetypes.exceptions import SchemaException
00014 from Products.Archetypes.exceptions import ReferenceException
00015 
00016 from AccessControl import ClassSecurityInfo
00017 from Acquisition import aq_base, Explicit
00018 from ExtensionClass import Base
00019 from Globals import InitializeClass
00020 from Products.CMFCore import permissions
00021 
00022 __docformat__ = 'reStructuredText'
00023 _marker = []
00024 
00025 def getNames(schema):
00026     """Returns a list of all fieldnames in the given schema."""
00027     return [f.getName() for f in schema.fields()]
00028 
00029 def getSchemata(obj):
00030     """Returns an ordered dictionary, which maps all Schemata names to fields
00031     that belong to the Schemata."""
00032 
00033     schema = obj.Schema()
00034     schemata = OrderedDict()
00035     for f in schema.fields():
00036         sub = schemata.get(f.schemata, WrappedSchemata(name=f.schemata))
00037         sub.addField(f)
00038         schemata[f.schemata] = sub.__of__(obj)
00039 
00040     return schemata
00041 
00042 class Schemata(Base):
00043     """Manage a list of fields by grouping them together.
00044 
00045     Schematas are identified by their names.
00046     """
00047 
00048     security = ClassSecurityInfo()
00049     security.setDefaultAccess('allow')
00050 
00051     __implements__ = ISchemata
00052 
00053     def __init__(self, name='default', fields=None):
00054         """Initialize Schemata and add optional fields."""
00055 
00056         self.__name__ = name
00057         self._names = []
00058         self._fields = {}
00059 
00060         if fields is not None:
00061             if type(fields) not in [ListType, TupleType]:
00062                 fields = (fields, )
00063 
00064             for field in fields:
00065                 self.addField(field)
00066 
00067     security.declareProtected(permissions.View, 'getName')
00068     def getName(self):
00069         """Returns the Schemata's name."""
00070         return self.__name__
00071 
00072 
00073     def __add__(self, other):
00074         """Returns a new Schemata object that contains all fields and layers
00075         from ``self`` and ``other``.
00076         """
00077 
00078         c = Schemata()
00079         for field in self.fields():
00080             c.addField(field)
00081         for field in other.fields():
00082             c.addField(field)
00083 
00084         return c
00085 
00086 
00087     security.declareProtected(permissions.View, 'copy')
00088     def copy(self):
00089         """Returns a deep copy of this Schemata.
00090         """
00091         c = Schemata()
00092         for field in self.fields():
00093             c.addField(field.copy())
00094         return c
00095 
00096 
00097     security.declareProtected(permissions.View, 'fields')
00098     def fields(self):
00099         """Returns a list of my fields in order of their indices."""
00100         return [self._fields[name] for name in self._names]
00101 
00102 
00103     security.declareProtected(permissions.View, 'values')
00104     values = fields
00105 
00106     security.declareProtected(permissions.View, 'editableFields')
00107     def editableFields(self, instance, visible_only=False):
00108         """Returns a list of editable fields for the given instance
00109         """
00110         ret = []
00111         for field in self.fields():
00112             if field.writeable(instance, debug=False) and    \
00113                    (not visible_only or
00114                     field.widget.isVisible(instance, 'edit') != 'invisible'):
00115                 ret.append(field)
00116         return ret
00117 
00118     security.declareProtected(permissions.View, 'viewableFields')
00119     def viewableFields(self, instance):
00120         """Returns a list of viewable fields for the given instance
00121         """
00122         return [field for field in self.fields()
00123                 if field.checkPermission('view', instance)]
00124 
00125     security.declareProtected(permissions.View, 'widgets')
00126     def widgets(self):
00127         """Returns a dictionary that contains a widget for
00128         each field, using the field name as key."""
00129 
00130         widgets = {}
00131         for f in self.fields():
00132             widgets[f.getName()] = f.widget
00133         return widgets
00134 
00135     security.declareProtected(permissions.View,
00136                               'filterFields')
00137     def filterFields(self, *predicates, **values):
00138         """Returns a subset of self.fields(), containing only fields that
00139         satisfy the given conditions.
00140 
00141         You can either specify predicates or values or both. If you provide
00142         both, all conditions must be satisfied.
00143 
00144         For each ``predicate`` (positional argument), ``predicate(field)`` must
00145         return 1 for a Field ``field`` to be returned as part of the result.
00146 
00147         Each ``attr=val`` function argument defines an additional predicate:
00148         A field must have the attribute ``attr`` and field.attr must be equal
00149         to value ``val`` for it to be in the returned list.
00150         """
00151 
00152         results = []
00153 
00154         for field in self.fields(): # step through each of my fields
00155 
00156             # predicate failed:
00157             failed = [pred for pred in predicates if not pred(field)]
00158             if failed: continue
00159 
00160             # attribute missing:
00161             missing_attrs = [attr for attr in values.keys() \
00162                              if not shasattr(field, attr)]
00163             if missing_attrs: continue
00164 
00165             # attribute value unequal:
00166             diff_values = [attr for attr in values.keys() \
00167                            if getattr(field, attr) != values[attr]]
00168             if diff_values: continue
00169 
00170             results.append(field)
00171 
00172         return results
00173 
00174     def __setitem__(self, name, field):
00175         assert name == field.getName()
00176         self.addField(field)
00177 
00178     security.declareProtected(permissions.ModifyPortalContent,
00179                               'addField')
00180     def addField(self, field):
00181         """Adds a given field to my dictionary of fields."""
00182         field = aq_base(field)
00183         self._validateOnAdd(field)
00184         name = field.getName()
00185         if name not in self._names:
00186             self._names.append(name)
00187         self._fields[name] = field
00188 
00189     def _validateOnAdd(self, field):
00190         """Validates fields on adding and bootstrapping
00191         """
00192         # interface test
00193         if not IField.isImplementedBy(field):
00194             raise ValueError, "Object doesn't implement IField: %r" % field
00195         name = field.getName()
00196         # two primary fields are forbidden
00197         if getattr(field, 'primary', False):
00198             res = self.hasPrimary()
00199             if res is not False and name != res.getName():
00200                 raise SchemaException(
00201                     "Tried to add '%s' as primary field "
00202                     "but %s already has the primary field '%s'." %
00203                     (name, repr(self), res.getName())
00204                     )
00205         for pname in ('accessor', 'edit_accessor', 'mutator'):
00206             res = self._checkPropertyDupe(field, pname)
00207             if res is not False:
00208                 res, value = res
00209                 raise SchemaException(
00210                     "Tried to add '%s' with property '%s' set "
00211                     "to %s but '%s' has the same value." %
00212                     (name, pname, repr(value), res.getName())
00213                     )
00214         # Do not allowed unqualified references
00215         if field.type in ('reference', ):
00216             relationship = getattr(field, 'relationship', '')
00217             if type(relationship) is not StringType or len(relationship) == 0:
00218                 raise ReferenceException(
00219                     "Unqualified relationship or "
00220                     "unsupported relationship var type in field '%s'. "
00221                     "The relationship qualifer must be a non empty "
00222                     "string." % name
00223                     )
00224 
00225 
00226     def __delitem__(self, name):
00227         if not self._fields.has_key(name):
00228             raise KeyError("Schemata has no field '%s'" % name)
00229         del self._fields[name]
00230         self._names.remove(name)
00231 
00232     def __getitem__(self, name):
00233         return self._fields[name]
00234 
00235     security.declareProtected(permissions.View, 'get')
00236     def get(self, name, default=None):
00237         return self._fields.get(name, default)
00238 
00239     security.declareProtected(permissions.View, 'has_key')
00240     def has_key(self, name):
00241         return self._fields.has_key(name)
00242 
00243     __contains__ = has_key
00244 
00245     security.declareProtected(permissions.View, 'keys')
00246     def keys(self):
00247         return self._names
00248 
00249     security.declareProtected(permissions.ModifyPortalContent,
00250                               'delField')
00251     delField = __delitem__
00252 
00253     security.declareProtected(permissions.ModifyPortalContent,
00254                               'updateField')
00255     updateField = addField
00256 
00257     security.declareProtected(permissions.View, 'searchable')
00258     def searchable(self):
00259         """Returns a list containing names of all searchable fields."""
00260 
00261         return [f.getName() for f in self.fields() if f.searchable]
00262 
00263     def hasPrimary(self):
00264         """Returns the first primary field or False"""
00265         for f in self.fields():
00266             if getattr(f, 'primary', False):
00267                 return f
00268         return False
00269 
00270     def _checkPropertyDupe(self, field, propname):
00271         check_value = getattr(field, propname, _marker)
00272         # None is fine too.
00273         if check_value is _marker or check_value is None:
00274             return False
00275         check_name = field.getName()
00276         for f in self.fields():
00277             got = getattr(f, propname, _marker)
00278             if got == check_value and f.getName() != check_name:
00279                 return f, got
00280         return False
00281 
00282 InitializeClass(Schemata)
00283 
00284 
00285 class WrappedSchemata(Schemata, Explicit):
00286     """
00287     Wrapped Schemata
00288     """
00289 
00290     security = ClassSecurityInfo()
00291     security.setDefaultAccess('allow')
00292 
00293 InitializeClass(WrappedSchemata)
00294 
00295 
00296 class SchemaLayerContainer(DefaultLayerContainer):
00297     """Some layer management for schemas"""
00298 
00299     security = ClassSecurityInfo()
00300     security.setDefaultAccess('allow')
00301 
00302     _properties = {
00303         'marshall' : None
00304         }
00305 
00306     def __init__(self):
00307         DefaultLayerContainer.__init__(self)
00308         #Layer init work
00309         marshall = self._props.get('marshall')
00310         if marshall:
00311             self.registerLayer('marshall', marshall)
00312 
00313     # ILayerRuntime
00314     security.declareProtected(permissions.ModifyPortalContent,
00315                               'initializeLayers')
00316     def initializeLayers(self, instance, item=None, container=None):
00317         # scan each field looking for registered layers optionally
00318         # call its initializeInstance method and then the
00319         # initializeField method
00320         initializedLayers = []
00321         called = lambda x: x in initializedLayers
00322 
00323         for field in self.fields():
00324             if ILayerContainer.isImplementedBy(field):
00325                 layers = field.registeredLayers()
00326                 for layer, obj in layers:
00327                     if ILayer.isImplementedBy(obj):
00328                         if not called((layer, obj)):
00329                             obj.initializeInstance(instance, item, container)
00330                             # Some layers may have the same name, but
00331                             # different classes, so, they may still
00332                             # need to be initialized
00333                             initializedLayers.append((layer, obj))
00334                         obj.initializeField(instance, field)
00335 
00336         # Now do the same for objects registered at this level
00337         if ILayerContainer.isImplementedBy(self):
00338             for layer, obj in self.registeredLayers():
00339                 if (not called((layer, obj)) and
00340                     ILayer.isImplementedBy(obj)):
00341                     obj.initializeInstance(instance, item, container)
00342                     initializedLayers.append((layer, obj))
00343 
00344 
00345     security.declareProtected(permissions.ModifyPortalContent,
00346                               'cleanupLayers')
00347     def cleanupLayers(self, instance, item=None, container=None):
00348         # scan each field looking for registered layers optionally
00349         # call its cleanupInstance method and then the cleanupField
00350         # method
00351         queuedLayers = []
00352         queued = lambda x: x in queuedLayers
00353 
00354         for field in self.fields():
00355             if ILayerContainer.isImplementedBy(field):
00356                 layers = field.registeredLayers()
00357                 for layer, obj in layers:
00358                     if not queued((layer, obj)):
00359                         queuedLayers.append((layer, obj))
00360                     if ILayer.isImplementedBy(obj):
00361                         obj.cleanupField(instance, field)
00362 
00363         for layer, obj in queuedLayers:
00364             if ILayer.isImplementedBy(obj):
00365                 obj.cleanupInstance(instance, item, container)
00366 
00367         # Now do the same for objects registered at this level
00368 
00369         if ILayerContainer.isImplementedBy(self):
00370             for layer, obj in self.registeredLayers():
00371                 if (not queued((layer, obj)) and
00372                     ILayer.isImplementedBy(obj)):
00373                     obj.cleanupInstance(instance, item, container)
00374                     queuedLayers.append((layer, obj))
00375 
00376     def __add__(self, other):
00377         c = SchemaLayerContainer()
00378         layers = {}
00379         for k, v in self.registeredLayers():
00380             layers[k] = v
00381         for k, v in other.registeredLayers():
00382             layers[k] = v
00383         for k, v in layers.items():
00384             c.registerLayer(k, v)
00385         return c
00386 
00387     security.declareProtected(permissions.View, 'copy')
00388     def copy(self):
00389         c = SchemaLayerContainer()
00390         layers = {}
00391         for k, v in self.registeredLayers():
00392             c.registerLayer(k, v)
00393         return c
00394 
00395 InitializeClass(SchemaLayerContainer)
00396 
00397 
00398 class BasicSchema(Schemata):
00399     """Manage a list of fields and run methods over them."""
00400 
00401     __implements__ = ISchema
00402 
00403     security = ClassSecurityInfo()
00404     security.setDefaultAccess('allow')
00405 
00406     _properties = {}
00407 
00408     def __init__(self, *args, **kwargs):
00409         """
00410         Initialize a Schema.
00411 
00412         The first positional argument may be a sequence of
00413         Fields. (All further positional arguments are ignored.)
00414 
00415         Keyword arguments are added to my properties.
00416         """
00417         Schemata.__init__(self)
00418 
00419         self._props = self._properties.copy()
00420         self._props.update(kwargs)
00421 
00422         if len(args):
00423             if type(args[0]) in [ListType, TupleType]:
00424                 for field in args[0]:
00425                     self.addField(field)
00426             else:
00427                 msg = ('You are passing positional arguments '
00428                        'to the Schema constructor. '
00429                        'Please consult the docstring '
00430                        'for %s.BasicSchema.__init__' %
00431                        (self.__class__.__module__,))
00432                 level = 3
00433                 if self.__class__ is not BasicSchema:
00434                     level = 4
00435                 warn(msg, level=level)
00436                 for field in args:
00437                     self.addField(args[0])
00438 
00439     def __add__(self, other):
00440         c = BasicSchema()
00441         # We can't use update and keep the order so we do it manually
00442         for field in self.fields():
00443             c.addField(field)
00444         for field in other.fields():
00445             c.addField(field)
00446         # Need to be smarter when joining layers
00447         # and internal props
00448         c._props.update(self._props)
00449         return c
00450 
00451     security.declareProtected(permissions.View, 'copy')
00452     def copy(self):
00453         """Returns a deep copy of this Schema.
00454         """
00455         c = BasicSchema()
00456         for field in self.fields():
00457             c.addField(field.copy())
00458         # Need to be smarter when joining layers
00459         # and internal props
00460         c._props.update(self._props)
00461         return c
00462 
00463     security.declareProtected(permissions.ModifyPortalContent, 'edit')
00464     def edit(self, instance, name, value):
00465         if self.allow(name):
00466             instance[name] = value
00467 
00468     security.declareProtected(permissions.ModifyPortalContent,
00469                               'setDefaults')
00470     def setDefaults(self, instance):
00471         """Only call during object initialization. Sets fields to
00472         schema defaults
00473         """
00474         ## TODO think about layout/vs dyn defaults
00475         for field in self.values():
00476             if field.getName().lower() == 'id': continue
00477             if field.type == "reference": continue
00478 
00479             # always set defaults on writable fields
00480             mutator = field.getMutator(instance)
00481             if mutator is None:
00482                 continue
00483             default = field.getDefault(instance)
00484 
00485             args = (default,)
00486             kw = {'field': field.__name__,
00487                   '_initializing_': True}
00488             if shasattr(field, 'default_content_type'):
00489                 # specify a mimetype if the mutator takes a
00490                 # mimetype argument
00491                 # if the schema supplies a default, we honour that, 
00492                 # otherwise we use the site property
00493                 default_content_type = field.default_content_type
00494                 if default_content_type is None:
00495                     default_content_type = getDefaultContentType(instance)
00496                 kw['mimetype'] = default_content_type
00497             mapply(mutator, *args, **kw)
00498 
00499     security.declareProtected(permissions.ModifyPortalContent,
00500                               'updateAll')
00501     def updateAll(self, instance, **kwargs):
00502         """This method mutates fields in the given instance.
00503 
00504         For each keyword argument k, the key indicates the name of the
00505         field to mutate while the value is used to call the mutator.
00506 
00507         E.g. updateAll(instance, id='123', amount=500) will, depending on the
00508         actual mutators set, result in two calls: ``instance.setId('123')`` and
00509         ``instance.setAmount(500)``.
00510         """
00511 
00512         keys = kwargs.keys()
00513 
00514         for name in keys:
00515 
00516             field = self.get(name, None)
00517 
00518             if field is None:
00519                 continue
00520 
00521             if not field.writeable(instance):
00522                 continue
00523 
00524             # If passed the test above, mutator is guaranteed to
00525             # exist.
00526             method = field.getMutator(instance)
00527             method(kwargs[name])
00528 
00529     security.declareProtected(permissions.View, 'allow')
00530     def allow(self, name):
00531         return self.has_key(name)
00532 
00533     security.declareProtected(permissions.View, 'validate')
00534     def validate(self, instance=None, REQUEST=None,
00535                  errors=None, data=None, metadata=None):
00536         """Validate the state of the entire object.
00537 
00538         The passed dictionary ``errors`` will be filled with human readable
00539         error messages as values and the corresponding fields' names as
00540         keys.
00541 
00542         If a REQUEST object is present, validate the field values in the
00543         REQUEST.  Otherwise, validate the values currently in the object.
00544         """
00545         if REQUEST:
00546             fieldset = REQUEST.form.get('fieldset', None)
00547             fieldsets = REQUEST.form.get('fieldsets', None)
00548         else:
00549             fieldset = fieldsets = None
00550         fields = []
00551 
00552         if fieldsets is not None:
00553             schemata = instance.Schemata()
00554             for fieldset in fieldsets:
00555                 fields += [(field.getName(), field)
00556                            for field in schemata[fieldset].fields()]            
00557         elif fieldset is not None:
00558             schemata = instance.Schemata()
00559             fields = [(field.getName(), field)
00560                       for field in schemata[fieldset].fields()]            
00561         else:
00562             if data:
00563                 fields.extend([(field.getName(), field)
00564                                for field in self.filterFields(isMetadata=0)])
00565             if metadata:
00566                 fields.extend([(field.getName(), field)
00567                                for field in self.filterFields(isMetadata=1)])
00568 
00569         if REQUEST:
00570             form = REQUEST.form
00571         else:
00572             form = None
00573             
00574         for name, field in fields:
00575             
00576             # Should not validate something we can't write to anyway
00577             if not field.writeable(instance):
00578                 continue
00579             
00580             error = 0
00581             value = None
00582             widget = field.widget
00583             
00584             if form:
00585                 result = widget.process_form(instance, field, form,
00586                                              empty_marker=_marker)
00587             else:
00588                 result = None
00589             if result is None or result is _marker:
00590                 accessor = field.getEditAccessor(instance) or field.getAccessor(instance)
00591                 if accessor is not None:
00592                     value = accessor()
00593                 else:
00594                     # can't get value to validate -- bail
00595                     continue
00596             else:
00597                 value = result[0]
00598 
00599             res = field.validate(instance=instance,
00600                                  value=value,
00601                                  errors=errors,
00602                                  REQUEST=REQUEST)
00603             if res:
00604                 errors[field.getName()] = res
00605         return errors
00606 
00607 
00608     # Utility method for converting a Schema to a string for the
00609     # purpose of comparing schema.  This comparison is used for
00610     # determining whether a schema has changed in the auto update
00611     # function.  Right now it's pretty crude.
00612     # TODO FIXME!
00613     security.declareProtected(permissions.View,
00614                               'toString')
00615     def toString(self):
00616         s = '%s: {' % self.__class__.__name__
00617         for f in self.fields():
00618             s = s + '%s,' % (f.toString())
00619         s = s + '}'
00620         return s
00621 
00622     security.declareProtected(permissions.View,
00623                               'signature')
00624     def signature(self):
00625         from md5 import md5
00626         return md5(self.toString()).digest()
00627 
00628     security.declareProtected(permissions.ModifyPortalContent,
00629                               'changeSchemataForField')
00630     def changeSchemataForField(self, fieldname, schemataname):
00631         """ change the schemata for a field """
00632         field = self[fieldname]
00633         self.delField(fieldname)
00634         field.schemata = schemataname
00635         self.addField(field)
00636 
00637     security.declareProtected(permissions.View, 'getSchemataNames')
00638     def getSchemataNames(self):
00639         """Return list of schemata names in order of appearing"""
00640         lst = []
00641         for f in self.fields():
00642             if not f.schemata in lst:
00643                 lst.append(f.schemata)
00644         return lst
00645 
00646     security.declareProtected(permissions.View, 'getSchemataFields')
00647     def getSchemataFields(self, name):
00648         """Return list of fields belong to schema 'name'
00649         in order of appearing
00650         """
00651         return [f for f in self.fields() if f.schemata == name]
00652 
00653     security.declareProtected(permissions.ModifyPortalContent,
00654                               'replaceField')
00655     def replaceField(self, name, field):
00656         if IField.isImplementedBy(field):
00657             oidx = self._names.index(name)
00658             new_name = field.getName()
00659             self._names[oidx] = new_name
00660             del self._fields[name]
00661             self._fields[new_name] = field
00662         else:
00663             raise ValueError, "Object doesn't implement IField: %r" % field
00664 
00665 InitializeClass(BasicSchema)
00666 
00667 
00668 class Schema(BasicSchema, SchemaLayerContainer):
00669     """
00670     Schema
00671     """
00672 
00673     __implements__ = ILayerRuntime, ILayerContainer, ISchema
00674 
00675     security = ClassSecurityInfo()
00676     security.setDefaultAccess('allow')
00677 
00678     def __init__(self, *args, **kwargs):
00679         BasicSchema.__init__(self, *args, **kwargs)
00680         SchemaLayerContainer.__init__(self)
00681 
00682     def __add__(self, other):
00683         c = Schema()
00684         # We can't use update and keep the order so we do it manually
00685         for field in self.fields():
00686             c.addField(field)
00687         for field in other.fields():
00688             c.addField(field)
00689         # Need to be smarter when joining layers
00690         # and internal props
00691         c._props.update(self._props)
00692         layers = {}
00693         for k, v in self.registeredLayers():
00694             layers[k] = v
00695         for k, v in other.registeredLayers():
00696             layers[k] = v
00697         for k, v in layers.items():
00698             c.registerLayer(k, v)
00699         return c
00700 
00701     security.declareProtected(permissions.View, 'copy')
00702     def copy(self, factory=None):
00703         """Returns a deep copy of this Schema.
00704         """
00705         if factory is None:
00706             factory = self.__class__
00707         c = factory()
00708         for field in self.fields():
00709             c.addField(field.copy())
00710         # Need to be smarter when joining layers
00711         # and internal props
00712         c._props.update(self._props)
00713         layers = {}
00714         for k, v in self.registeredLayers():
00715             c.registerLayer(k, v)
00716         return c
00717 
00718     security.declareProtected(permissions.View, 'wrapped')
00719     def wrapped(self, parent):
00720         schema = self.copy(factory=WrappedSchema)
00721         return schema.__of__(parent)
00722 
00723     security.declareProtected(permissions.ModifyPortalContent,
00724                               'moveField')
00725     def moveField(self, name, direction=None, pos=None, after=None, before=None):
00726         """Move a field
00727 
00728         >>> from Products.Archetypes.atapi import StringField as SF
00729         >>> schema = Schema((SF('a'), SF('b'), SF('c'),))
00730 
00731         >>> schema.keys()
00732         ['a', 'b', 'c']
00733 
00734         >>> sbefore = schema.copy()
00735         >>> sbefore.moveField('c', before='a')
00736         >>> sbefore.keys()
00737         ['c', 'a', 'b']
00738 
00739         >>> safter = schema.copy()
00740         >>> safter.moveField('a', after='b')
00741         >>> safter.keys()
00742         ['b', 'a', 'c']
00743 
00744         >>> spos = schema.copy()
00745         >>> spos.moveField('b', pos='top')
00746         >>> spos.keys()
00747         ['b', 'a', 'c']
00748 
00749         >>> spos = schema.copy()
00750         >>> spos.moveField('b', pos='bottom')
00751         >>> spos.keys()
00752         ['a', 'c', 'b']
00753 
00754         >>> spos = schema.copy()
00755         >>> spos.moveField('c', pos=0)
00756         >>> spos.keys()
00757         ['c', 'a', 'b']
00758 
00759         maxint can be used to move the field to the last position possible
00760         >>> from sys import maxint
00761         >>> spos = schema.copy()
00762         >>> spos.moveField('a', pos=maxint)
00763         >>> spos.keys()
00764         ['b', 'c', 'a']
00765 
00766         Errors
00767         ======
00768 
00769         >>> schema.moveField('d', pos=0)
00770         Traceback (most recent call last):
00771         ...
00772         KeyError: 'd'
00773 
00774         >>> schema.moveField('a', pos=0, before='b')
00775         Traceback (most recent call last):
00776         ...
00777         ValueError: You must apply exactly one argument.
00778 
00779         >>> schema.moveField('a')
00780         Traceback (most recent call last):
00781         ...
00782         ValueError: You must apply exactly one argument.
00783 
00784         >>> schema.moveField('a', before='a')
00785         Traceback (most recent call last):
00786         ...
00787         ValueError: name and before can't be the same
00788 
00789         >>> schema.moveField('a', after='a')
00790         Traceback (most recent call last):
00791         ...
00792         ValueError: name and after can't be the same
00793 
00794         >>> schema.moveField('a', pos='foo')
00795         Traceback (most recent call last):
00796         ...
00797         ValueError: pos must be a number or top/bottom
00798 
00799         """
00800         if bool(direction) + bool(after) + bool(before) + bool(pos is not None) != 1:
00801             raise ValueError, "You must apply exactly one argument."
00802         keys = self.keys()
00803 
00804         if name not in keys:
00805             raise KeyError, name
00806 
00807         if direction is not None:
00808             return self._moveFieldInSchemata(name, direction)
00809 
00810         if pos is not None:
00811             if not (isinstance(pos, int) or pos in ('top', 'bottom',)):
00812                 raise ValueError, "pos must be a number or top/bottom"
00813             if pos == 'top':
00814                 return self._moveFieldToPosition(name, 0)
00815             elif pos == 'bottom':
00816                 return self._moveFieldToPosition(name, len(keys))
00817             else:
00818                 return self._moveFieldToPosition(name, pos)
00819 
00820         if after is not None:
00821             if after == name:
00822                 raise ValueError, "name and after can't be the same"
00823             idx = keys.index(after)
00824             return self._moveFieldToPosition(name, idx+1)
00825 
00826         if before is not None:
00827             if before == name:
00828                 raise ValueError, "name and before can't be the same"
00829             idx = keys.index(before)
00830             return self._moveFieldToPosition(name, idx)
00831 
00832     def _moveFieldToPosition(self, name, pos):
00833         """Moves a field with the name 'name' to the position 'pos'
00834 
00835         This method doesn't obey the assignement of fields to a schemata
00836         """
00837         keys = self._names
00838         oldpos = keys.index(name)
00839         keys.remove(name)
00840         if oldpos >= pos:
00841            keys.insert(pos, name)
00842         else:
00843            keys.insert(pos - 1, name)
00844         self._names = keys
00845 
00846     def _moveFieldInSchemata(self, name, direction):
00847         """Moves a field with the name 'name' inside its schemata
00848         """
00849         if not direction in (-1, 1):
00850             raise ValueError, "Direction must be either -1 or 1"
00851 
00852         fields = self.fields()
00853         fieldnames = [f.getName() for f in fields]
00854         schemata_names = self.getSchemataNames()
00855 
00856         field = self[name]
00857         field_schemata_name = self[name].schemata
00858 
00859         d = {}
00860         for s_name in self.getSchemataNames():
00861             d[s_name] = self.getSchemataFields(s_name)
00862 
00863         lst = d[field_schemata_name]  # list of fields of schemata
00864         pos = [f.getName() for f in lst].index(field.getName())
00865 
00866         if direction == -1:
00867             if pos > 0:
00868                 del lst[pos]
00869                 lst.insert(pos-1, field)
00870         if direction == 1:
00871             if pos < len(lst):
00872                 del lst[pos]
00873                 lst.insert(pos+1, field)
00874 
00875         d[field_schemata_name] = lst
00876 
00877         # remove and re-add
00878         self.__init__()
00879         for s_name in schemata_names:
00880             for f in d[s_name]:
00881                 self.addField(f)
00882 
00883 
00884 InitializeClass(Schema)
00885 
00886 
00887 class WrappedSchema(Schema, Explicit):
00888     """
00889     Wrapped Schema
00890     """
00891 
00892     security = ClassSecurityInfo()
00893     security.setDefaultAccess('allow')
00894 
00895 InitializeClass(WrappedSchema)
00896 
00897 
00898 class ManagedSchema(Schema):
00899     """
00900     Managed Schema
00901     """
00902 
00903     security = ClassSecurityInfo()
00904     security.setDefaultAccess('allow')
00905 
00906     __implements__ = IManagedSchema, Schema.__implements__
00907 
00908     security.declareProtected(permissions.ModifyPortalContent,
00909                               'delSchemata')
00910     def delSchemata(self, name):
00911         """Remove all fields belonging to schemata 'name'"""
00912         for f in self.fields():
00913             if f.schemata == name:
00914                 self.delField(f.getName())
00915 
00916     security.declareProtected(permissions.ModifyPortalContent,
00917                               'addSchemata')
00918     def addSchemata(self, name):
00919         """Create a new schema by adding a new field with schemata 'name' """
00920         from Products.Archetypes.Field import StringField
00921 
00922         if name in self.getSchemataNames():
00923             raise ValueError, "Schemata '%s' already exists" % name
00924         self.addField(StringField('%s_default' % name, schemata=name))
00925 
00926     security.declareProtected(permissions.ModifyPortalContent,
00927                               'moveSchemata')
00928     def moveSchemata(self, name, direction):
00929         """Move a schemata to left (direction=-1) or to right
00930         (direction=1)
00931         """
00932         if not direction in (-1, 1):
00933             raise ValueError, 'Direction must be either -1 or 1'
00934 
00935         fields = self.fields()
00936         fieldnames = [f.getName() for f in fields]
00937         schemata_names = self.getSchemataNames()
00938 
00939         d = {}
00940         for s_name in self.getSchemataNames():
00941             d[s_name] = self.getSchemataFields(s_name)
00942 
00943         pos = schemata_names.index(name)
00944         if direction == -1:
00945             if pos > 0:
00946                 schemata_names.remove(name)
00947                 schemata_names.insert(pos-1, name)
00948         if direction == 1:
00949             if pos < len(schemata_names):
00950                 schemata_names.remove(name)
00951                 schemata_names.insert(pos+1, name)
00952 
00953         # remove and re-add
00954         self.__init__()
00955 
00956         for s_name in schemata_names:
00957             for f in fields:
00958                 if f.schemata == s_name:
00959                     self.addField(f)
00960 
00961 InitializeClass(ManagedSchema)
00962 
00963 
00964 # Reusable instance for MetadataFieldList
00965 MDS = MetadataStorage()
00966 
00967 class MetadataSchema(Schema):
00968     """Schema that enforces MetadataStorage."""
00969 
00970     security = ClassSecurityInfo()
00971     security.setDefaultAccess('allow')
00972 
00973     security.declareProtected(permissions.ModifyPortalContent,
00974                               'addField')
00975     def addField(self, field):
00976         """Strictly enforce the contract that metadata is stored w/o
00977         markup and make sure each field is marked as such for
00978         generation and introspcection purposes.
00979         """
00980         _properties = {'isMetadata': 1,
00981                        'storage': MetadataStorage(),
00982                        'schemata': 'metadata',
00983                        'generateMode': 'mVc'}
00984 
00985         field.__dict__.update(_properties)
00986         field.registerLayer('storage', field.storage)
00987 
00988         Schema.addField(self, field)
00989 
00990 InitializeClass(MetadataSchema)
00991 
00992 
00993 FieldList = Schema
00994 MetadataFieldList = MetadataSchema