Back to index

plone3  3.1.7
property.py
Go to the documentation of this file.
00001 ##############################################################################
00002 #
00003 # PlonePAS - Adapt PluggableAuthService for use in Plone
00004 # Copyright (C) 2005 Enfold Systems, Kapil Thangavelu, et al
00005 #
00006 # This software is subject to the provisions of the Zope Public License,
00007 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this
00008 # distribution.
00009 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
00010 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00011 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
00012 # FOR A PARTICULAR PURPOSE.
00013 #
00014 ##############################################################################
00015 """
00016 Mutable Property Provider
00017 """
00018 import copy
00019 from sets import Set
00020 
00021 from ZODB.PersistentMapping import PersistentMapping
00022 from BTrees.OOBTree import OOBTree
00023 from Globals import DTMLFile, InitializeClass
00024 
00025 from Products.CMFCore.utils import getToolByName
00026 
00027 from Products.PluggableAuthService.utils import classImplements
00028 from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin
00029 from Products.PluggableAuthService.interfaces.plugins import IPropertiesPlugin
00030 from Products.PluggableAuthService.interfaces.plugins import IUserEnumerationPlugin
00031 from Products.PluggableAuthService.UserPropertySheet import _guessSchema
00032 from Products.PlonePAS.sheet import MutablePropertySheet, validateValue
00033 from Products.PlonePAS.interfaces.plugins import IMutablePropertiesPlugin
00034 
00035 
00036 def manage_addZODBMutablePropertyProvider(self, id, title='',
00037                                           RESPONSE=None, schema=None, **kw):
00038     """
00039     Create an instance of a mutable property manager.
00040     """
00041     o = ZODBMutablePropertyProvider(id, title, schema, **kw)
00042     self._setObject(o.getId(), o)
00043 
00044     if RESPONSE is not None:
00045         RESPONSE.redirect('manage_workspace')
00046 
00047 manage_addZODBMutablePropertyProviderForm = DTMLFile(
00048     "../zmi/MutablePropertyProviderForm", globals())
00049 
00050 
00051 def isStringType(data):
00052     return isinstance(data, str) or isinstance(data, unicode)
00053 
00054 
00055 class ZODBMutablePropertyProvider(BasePlugin):
00056     """Storage for mutable properties in the ZODB for users/groups.
00057 
00058     API sounds like it's only for users, but groups work as well.
00059     """
00060 
00061     meta_type = 'ZODB Mutable Property Provider'
00062 
00063     def __init__(self, id, title='', schema=None, **kw):
00064         """Create in-ZODB mutable property provider.
00065 
00066         Provide a schema either as a list of (name,type,value) tuples
00067         in the 'schema' parameter or as a series of keyword parameters
00068         'name=value'. Types will be guessed in this case.
00069 
00070         The 'value' is meant as the default value, and will be used
00071         unless the user provides data.
00072 
00073         If no schema is provided by constructor, the properties of the
00074         portal_memberdata object will be used.
00075 
00076         Types available: string, text, boolean, int, long, float, lines, date
00077         """
00078         self.id = id
00079         self.title = title
00080         self._storage = OOBTree()
00081 
00082         # calculate schema and default values
00083         defaultvalues = {}
00084         if not schema and not kw:
00085             schema = ()
00086         elif not schema and kw:
00087             schema = _guessSchema(kw)
00088             defaultvalues = kw
00089         else:
00090             valuetuples = [(name, value) for name, type, value in schema]
00091             schema = [(name, type) for name, type, value in schema]
00092             for name, value in valuetuples: defaultvalues[name] = value
00093         self._schema = tuple(schema)
00094         self._defaultvalues = defaultvalues
00095 
00096         # don't use _schema directly or you'll lose the fallback! use
00097         # _getSchema instead same for default values
00098 
00099 
00100     def _getSchema(self, isgroup=None):
00101         # this could probably stand to be cached
00102         datatool = isgroup and "portal_groupdata" or "portal_memberdata"
00103 
00104         schema = self._schema
00105         if not schema:
00106             # if no schema is provided, use portal_memberdata properties
00107             schema = ()
00108             mdtool = getToolByName(self, datatool, None)
00109             # Don't fail badly if tool is not available.
00110             if mdtool is not None:
00111                 mdschema = mdtool.propertyMap()
00112                 schema = [(elt['id'], elt['type']) for elt in mdschema]
00113         return schema
00114 
00115 
00116     def _getDefaultValues(self, isgroup=None):
00117         """Returns a dictionary mapping of property names to default values.
00118         Defaults to portal_*data tool if necessary.
00119         """
00120         datatool = isgroup and "portal_groupdata" or "portal_memberdata"
00121 
00122         defaultvalues = self._defaultvalues
00123         if not self._schema:
00124             # if no schema is provided, use portal_*data properties
00125             defaultvalues = {}
00126             mdtool = getToolByName(self, datatool, None)
00127             # Don't fail badly if tool is not available.
00128             if mdtool is not None:
00129                 # we rely on propertyMap and propertyItems mapping
00130                 mdvalues = mdtool.propertyItems()
00131                 for name, value in mdvalues:
00132                     # For selection types the default value is the name of a
00133                     # method which returns the possible values. There is no way
00134                     # to set a default value for those types.
00135                     ptype = mdtool.getPropertyType(name)
00136                     if ptype == "selection":
00137                         defaultvalues[name] = ""
00138                     elif ptype == "multiple selection":
00139                         defaultvalues[name] = []
00140                     else:
00141                         defaultvalues[name] = value
00142 
00143             # ALERT! if someone gives their *_data tool a title, and want a title
00144             #        as a property of the user/group (and groups do by default)
00145             #        we don't want them all to have this title, since a title is
00146             #        used in the UI if it exists
00147             if defaultvalues.get("title"): defaultvalues["title"] = ""
00148         return defaultvalues
00149 
00150 
00151     def getPropertiesForUser(self, user, request=None):
00152         """Get property values for a user or group.
00153         Returns a dictionary of values or a PropertySheet.
00154 
00155         This implementation will always return a MutablePropertySheet.
00156 
00157         NOTE: Must always return something, or else the property sheet
00158         won't get created and this will screw up portal_memberdata.
00159         """
00160         isGroup = getattr(user, 'isGroup', lambda: None)()
00161 
00162         data = self._storage.get(user.getId())
00163         defaults = self._getDefaultValues(isGroup)
00164 
00165         # provide default values where missing
00166         if not data: data = {}
00167         for key, val in defaults.items():
00168             if not data.has_key(key):
00169                 data[key] = val
00170 
00171         return MutablePropertySheet(self.id,
00172                                     schema=self._getSchema(isGroup), **data)
00173 
00174 
00175     def setPropertiesForUser(self, user, propertysheet):
00176         """Set the properties of a user or group based on the contents of a
00177         property sheet.
00178         """
00179         isGroup = getattr(user, 'isGroup', lambda: None)()
00180 
00181         properties = dict(propertysheet.propertyItems())
00182 
00183         for name, property_type in self._getSchema(isGroup) or ():
00184             if (name in properties and not
00185                 validateValue(property_type, properties[name])):
00186                 raise ValueError, ('Invalid value: %s does not conform '
00187                                    'to %s' % (name, property_type))
00188 
00189         allowed_prop_keys = [pn for pn, pt in self._getSchema(isGroup) or ()]
00190         if allowed_prop_keys:
00191             prop_names = Set(properties.keys()) - Set(allowed_prop_keys)
00192             if prop_names:
00193                 raise ValueError, 'Unknown Properties: %r' % prop_names
00194 
00195         userid = user.getId()
00196         userprops = self._storage.get(userid)
00197         if userprops is not None:
00198             userprops.update(properties)
00199             self._storage[userid] = self._storage[userid]   # notify persistence machinery of change
00200         else:
00201             self._storage.insert(user.getId(), properties)
00202 
00203 
00204     def deleteUser(self, user_id):
00205         """Delete all user properties
00206         """
00207         # Do nothing if an unknown user_id is given
00208         try:
00209             del self._storage[user_id]
00210         except KeyError:
00211             pass
00212 
00213 
00214 
00215     def testMemberData(self, memberdata, criteria, exact_match=False):
00216         """Test if a memberdata matches the search criteria.
00217         """
00218         for (key, value) in criteria.items():
00219             testvalue=memberdata.get(key, None)
00220             if testvalue is None:
00221                 return False
00222 
00223             if isStringType(testvalue):
00224                 testvalue=testvalue.lower()
00225             if isStringType(value):
00226                 value=value.lower()
00227                 
00228             if exact_match:
00229                 if value!=testvalue:
00230                     return False
00231             else:
00232                 try:
00233                     if value not in testvalue:
00234                         return False
00235                 except TypeError:
00236                     # Fall back to exact match if we can check for sub-component
00237                     if value!=testvalue:
00238                         return False
00239 
00240 
00241         return True
00242 
00243 
00244     def enumerateUsers( self
00245                       , id=None
00246                       , login=None
00247                       , exact_match=False
00248                       , **kw
00249                       ):
00250 
00251         """ See IUserEnumerationPlugin.
00252         """
00253         plugin_id = self.getId()
00254 
00255         criteria=copy.copy(kw)
00256         if id is not None:
00257             criteria["id"]=id
00258         if login is not None:
00259             criteria["login"]=login
00260 
00261         users=[ (user,data) for (user,data) in self._storage.items()
00262                     if self.testMemberData(data, criteria, exact_match)]
00263 
00264         user_info=[ { 'id' : self.prefix + user_id,
00265                      'login' : user_id,
00266                      'title' : data.get('fullname', user_id),
00267                      'description' : data.get('fullname', user_id),
00268                      'email' : data.get('email', ''),
00269                      'pluginid' : plugin_id } for (user_id, data) in users ]
00270 
00271         return tuple(user_info)
00272 
00273 
00274 
00275 classImplements(ZODBMutablePropertyProvider,
00276                 IPropertiesPlugin,
00277                 IUserEnumerationPlugin,
00278                 IMutablePropertiesPlugin)
00279 
00280 InitializeClass(ZODBMutablePropertyProvider)
00281 
00282 class PersistentProperties(PersistentMapping): pass