Back to index

plone3  3.1.7
ClassGen.py
Go to the documentation of this file.
00001 from __future__ import nested_scopes
00002 import re
00003 from types import FunctionType as function
00004 
00005 from Products.Archetypes.utils import capitalize
00006 from Products.Archetypes.utils import _getSecurity
00007 from Products.Archetypes.debug import warn
00008 from Products.Archetypes.debug import deprecated
00009 from Globals import InitializeClass
00010 
00011 # marker that AT should generate a method -- used to discard unwanted
00012 #  inherited methods
00013 AT_GENERATE_METHOD = []
00014 
00015 
00016 _modes = {
00017     'r' : { 'prefix'   : 'get',
00018             'attr'     : 'accessor',
00019             'security' : 'read_permission',
00020             },
00021     'm' : { 'prefix'   : 'getRaw',
00022             'attr'     : 'edit_accessor',
00023             'security' : 'write_permission',
00024             },
00025     'w' : { 'prefix'   : 'set',
00026             'attr'     : 'mutator',
00027             'security' : 'write_permission',
00028             },
00029 
00030     }
00031 
00032 class GeneratorError(Exception):
00033     pass
00034 
00035 class Generator:
00036     def computeMethodName(self, field, mode):
00037         if mode not in _modes.keys():
00038             raise TypeError("Unsupported Mode %s in field: %s (%s)" % \
00039                             (field.getName(), mode))
00040 
00041         prefix = _modes[mode]['prefix']
00042         name   = capitalize(field.getName())
00043         return "%s%s" % (prefix, name)
00044 
00045     def makeMethod(self, klass, field, mode, methodName):
00046         name = field.getName()
00047         method = None
00048         if mode == "r":
00049             def generatedAccessor(self, **kw):
00050                 """Default Accessor."""
00051                 if kw.has_key('schema'):
00052                     schema = kw['schema']
00053                 else:
00054                     schema = self.Schema()
00055                     kw['schema'] = schema
00056                 return schema[name].get(self, **kw)
00057             method = generatedAccessor
00058         elif mode == "m":
00059             def generatedEditAccessor(self, **kw):
00060                 """Default Edit Accessor."""
00061                 if kw.has_key('schema'):
00062                     schema = kw['schema']
00063                 else:
00064                     schema = self.Schema()
00065                     kw['schema'] = schema
00066                 return schema[name].getRaw(self, **kw)
00067             method = generatedEditAccessor
00068         elif mode == "w":
00069             def generatedMutator(self, value, **kw):
00070                 """Default Mutator."""
00071                 if kw.has_key('schema'):
00072                     schema = kw['schema']
00073                 else:
00074                     schema = self.Schema()
00075                     kw['schema'] = schema
00076                 return schema[name].set(self, value, **kw)
00077             method = generatedMutator
00078         else:
00079             raise GeneratorError("""Unhandled mode for method creation:
00080             %s:%s -> %s:%s""" %(klass.__name__,
00081                                 name,
00082                                 methodName,
00083                                 mode))
00084 
00085         # Zope security requires all security protected methods to have a
00086         # function name. It uses this name to determine which roles are allowed
00087         # to access the method.
00088         # This code is renaming the internal name from e.g. generatedAccessor to
00089         # methodName.
00090         method = function(method.func_code,
00091                           method.func_globals,
00092                           methodName,
00093                           method.func_defaults,
00094                           method.func_closure,
00095                          )
00096         setattr(klass, methodName, method)
00097 
00098 class ClassGenerator:
00099     def updateSecurity(self, klass, field, mode, methodName):
00100         security = _getSecurity(klass)
00101 
00102         perm = _modes[mode]['security']
00103         perm = getattr(field, perm, None)
00104         method__roles__ = getattr(klass, '%s__roles__' % methodName, 0)
00105 
00106         # Check copied from SecurityInfo to avoid stomping over
00107         # existing permissions.
00108         if security.names.get(methodName, perm) != perm:
00109             warn('The method \'%s\' was already protected by a '
00110                  'different permission than the one declared '
00111                  'on the field. Assuming that the explicit '
00112                  'permission declared is the correct one and '
00113                  'has preference over the permission declared '
00114                  'on the field.' % methodName)
00115         elif method__roles__ is None:
00116             security.declarePublic(methodName)
00117         elif method__roles__ == ():
00118             security.declarePrivate(methodName)
00119         else:
00120             security.declareProtected(perm, methodName)
00121 
00122     def generateName(self, klass):
00123         return re.sub('([a-z])([A-Z])', '\g<1> \g<2>', klass.__name__)
00124 
00125     def checkSchema(self, klass):
00126         # backward compatibility, should be removed later on
00127         if klass.__dict__.has_key('type') and \
00128            not klass.__dict__.has_key('schema'):
00129             deprecated('Class %s has type attribute, should be schema' % \
00130                        klass.__name__, level = 4)
00131             klass.schema = klass.type
00132 
00133     def generateClass(self, klass):
00134         # We are going to assert a few things about the class here
00135         # before we start, set meta_type, portal_type based on class
00136         # name, but only if they are not set yet
00137         if (not getattr(klass, 'meta_type', None) or
00138             'meta_type' not in klass.__dict__.keys()):
00139             klass.meta_type = klass.__name__
00140         if (not getattr(klass, 'portal_type', None) or
00141             'portal_type' not in klass.__dict__.keys()):
00142             klass.portal_type = klass.__name__
00143         klass.archetype_name = getattr(klass, 'archetype_name',
00144                                        self.generateName(klass))
00145 
00146         self.checkSchema(klass)
00147         fields = klass.schema.fields()
00148         self.generateMethods(klass, fields)
00149 
00150     def generateMethods(self, klass, fields):
00151         generator = Generator()
00152         for field in fields:
00153             assert not 'm' in field.mode, 'm is an implicit mode'
00154 
00155             # Make sure we want to muck with the class for this field
00156             if "c" not in field.generateMode: continue
00157             type = getattr(klass, 'type')
00158             for mode in field.mode: #(r, w)
00159                 self.handle_mode(klass, generator, type, field, mode)
00160                 if mode == 'w':
00161                     self.handle_mode(klass, generator, type, field, 'm')
00162 
00163         InitializeClass(klass)
00164 
00165     def handle_mode(self, klass, generator, type, field, mode):
00166         attr = _modes[mode]['attr']
00167         # Did the field request a specific method name?
00168         methodName = getattr(field, attr, None)
00169         if not methodName:
00170             methodName = generator.computeMethodName(field, mode)
00171 
00172         # Avoid name space conflicts
00173         if not hasattr(klass, methodName) \
00174                or getattr(klass, methodName) is AT_GENERATE_METHOD:
00175             if type.has_key(methodName):
00176                 raise GeneratorError("There is a conflict"
00177                                      "between the Field(%s) and the attempt"
00178                                      "to generate a method of the same name on"
00179                                      "class %s" % (
00180                     methodName,
00181                     klass.__name__))
00182 
00183 
00184             # Make a method for this klass/field/mode
00185             generator.makeMethod(klass, field, mode, methodName)
00186 
00187         # Update security regardless of the method being generated or
00188         # not. Not protecting the method by the permission defined on
00189         # the field may leave security open and lead to misleading
00190         # bugs.
00191         self.updateSecurity(klass, field, mode, methodName)
00192 
00193         # Note on the class what we did (even if the method existed)
00194         attr = _modes[mode]['attr']
00195         setattr(field, attr, methodName)
00196 
00197 def generateCtor(name, module):
00198     # self is a App.FactoryDispater, Destination() is the real folder
00199     ctor = """
00200 def add%(name)s(self, id, **kwargs):
00201     from zope.event import notify
00202     from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
00203     obj = %(name)s(id)
00204     notify(ObjectCreatedEvent(obj))
00205     self._setObject(id, obj)
00206     obj = self._getOb(id)
00207     obj.initializeArchetype(**kwargs)
00208     notify(ObjectModifiedEvent(obj))
00209     return obj.getId()
00210 """ % {'name' : name}
00211     exec ctor in module.__dict__
00212     return getattr(module, "add%s" % name)
00213 
00214 def generateZMICtor(name, module):
00215     zmi_ctor = """
00216 def manage_add%(name)s(self, id, REQUEST=None):
00217     ''' Constructor for %(name)s '''
00218     kwargs = {}
00219     if REQUEST is not None:
00220         kwargs = REQUEST.form.copy()
00221         del kwargs['id']
00222     id = add%(name)s(self, id, **kwargs)
00223     obj = self._getOb(id)
00224     manage_tabs_message = 'Successfully added %(name)s'
00225     if REQUEST is not None:
00226         url = obj.absolute_url()
00227         REQUEST.RESPONSE.redirect(url + '/manage_edit%(name)sForm?manage_tabs_message=' + manage_tabs_message)
00228     return id
00229 """ % {'name':name}
00230     exec zmi_ctor in module.__dict__
00231     return getattr(module, "manage_add%s" % name)
00232 
00233 
00234 _cg = ClassGenerator()
00235 generateClass = _cg.generateClass
00236 generateMethods = _cg.generateMethods