appy.gen: Ploneless version.
This commit is contained in:
		
							parent
							
								
									d0cbe7e573
								
							
						
					
					
						commit
						a321257e55
					
				
					 13 changed files with 393 additions and 159 deletions
				
			
		|  | @ -515,6 +515,33 @@ class UserClassDescriptor(ClassDescriptor): | |||
|     def generateSchema(self): | ||||
|         ClassDescriptor.generateSchema(self, configClass=True) | ||||
| 
 | ||||
| class GroupClassDescriptor(ClassDescriptor): | ||||
|     '''Represents the class that corresponds to the Group for the generated | ||||
|        application.''' | ||||
|     def __init__(self, klass, generator): | ||||
|         ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator) | ||||
|         self.modelClass = self.klass | ||||
|         self.predefined = True | ||||
|         self.customized = False | ||||
|     def getParents(self, allClasses=()): | ||||
|         res = ['Group'] | ||||
|         if self.customized: | ||||
|             res.append('%s.%s' % (self.klass.__module__, self.klass.__name__)) | ||||
|         return res | ||||
|     def update(self, klass, attributes): | ||||
|         '''This method is called by the generator when he finds a custom group | ||||
|            definition. We must then add the custom group elements in this | ||||
|            default Group descriptor. | ||||
| 
 | ||||
|            NOTE: currently, it is not possible to define a custom Group | ||||
|            class.''' | ||||
|         self.orderedAttributes += attributes | ||||
|         self.klass = klass | ||||
|         self.customized = True | ||||
|     def isFolder(self, klass=None): return False | ||||
|     def generateSchema(self): | ||||
|         ClassDescriptor.generateSchema(self, configClass=True) | ||||
| 
 | ||||
| class TranslationClassDescriptor(ClassDescriptor): | ||||
|     '''Represents the set of translation ids for a gen-application.''' | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,9 +9,8 @@ from appy.gen.po import PoMessage, PoFile, PoParser | |||
| from appy.gen.generator import Generator as AbstractGenerator | ||||
| from appy.gen.utils import getClassName | ||||
| from appy.gen.descriptors import WorkflowDescriptor | ||||
| from descriptors import ClassDescriptor, ToolClassDescriptor, \ | ||||
|                         UserClassDescriptor, TranslationClassDescriptor | ||||
| from model import ModelClass, User, Tool, Translation | ||||
| from descriptors import * | ||||
| from model import ModelClass, User, Group, Tool, Translation | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| class Generator(AbstractGenerator): | ||||
|  | @ -24,9 +23,10 @@ class Generator(AbstractGenerator): | |||
|         AbstractGenerator.__init__(self, *args, **kwargs) | ||||
|         # Set our own Descriptor classes | ||||
|         self.descriptorClasses['class'] = ClassDescriptor | ||||
|         # Create our own Tool, User and Translation instances | ||||
|         # Create our own Tool, User, Group and Translation instances | ||||
|         self.tool = ToolClassDescriptor(Tool, self) | ||||
|         self.user = UserClassDescriptor(User, self) | ||||
|         self.group = GroupClassDescriptor(Group, self) | ||||
|         self.translation = TranslationClassDescriptor(Translation, self) | ||||
|         # i18n labels to generate | ||||
|         self.labels = [] # i18n labels | ||||
|  | @ -132,6 +132,7 @@ class Generator(AbstractGenerator): | |||
|             msg('pdf',                  '', msg.FORMAT_PDF), | ||||
|             msg('doc',                  '', msg.FORMAT_DOC), | ||||
|             msg('rtf',                  '', msg.FORMAT_RTF), | ||||
|             msg('front_page_text',      '', msg.FRONT_PAGE_TEXT), | ||||
|         ] | ||||
|         # Create a label for every role added by this application | ||||
|         for role in self.getAllUsedRoles(): | ||||
|  | @ -283,6 +284,27 @@ class Generator(AbstractGenerator): | |||
|         if isBack: res += '.back' | ||||
|         return res | ||||
| 
 | ||||
|     def getClasses(self, include=None): | ||||
|         '''Returns the descriptors for all the classes in the generated | ||||
|            gen-application. If p_include is: | ||||
|            * "all"        it includes the descriptors for the config-related | ||||
|                           classes (tool, user, group, translation) | ||||
|            * "allButTool" it includes the same descriptors, the tool excepted | ||||
|            * "custom"     it includes descriptors for the config-related classes | ||||
|                           for which the user has created a sub-class.''' | ||||
|         if not include: return self.classes | ||||
|         res = self.classes[:] | ||||
|         configClasses = [self.tool, self.user, self.group, self.translation] | ||||
|         if include == 'all': | ||||
|             res += configClasses | ||||
|         elif include == 'allButTool': | ||||
|             res += configClasses[1:] | ||||
|         elif include == 'custom': | ||||
|             res += [c for c in configClasses if c.customized] | ||||
|         elif include == 'predefined': | ||||
|             res = configClasses | ||||
|         return res | ||||
| 
 | ||||
|     def generateConfigureZcml(self): | ||||
|         '''Generates file configure.zcml.''' | ||||
|         repls = self.repls.copy() | ||||
|  | @ -375,27 +397,6 @@ class Generator(AbstractGenerator): | |||
|         repls['totalNumberOfTests'] = self.totalNumberOfTests | ||||
|         self.copyFile('__init__.py', repls) | ||||
| 
 | ||||
|     def getClasses(self, include=None): | ||||
|         '''Returns the descriptors for all the classes in the generated | ||||
|            gen-application. If p_include is: | ||||
|            * "all"        it includes the descriptors for the config-related | ||||
|                           classes (tool, user, translation) | ||||
|            * "allButTool" it includes the same descriptors, the tool excepted | ||||
|            * "custom"     it includes descriptors for the config-related classes | ||||
|                           for which the user has created a sub-class.''' | ||||
|         if not include: return self.classes | ||||
|         res = self.classes[:] | ||||
|         configClasses = [self.tool, self.user, self.translation] | ||||
|         if include == 'all': | ||||
|             res += configClasses | ||||
|         elif include == 'allButTool': | ||||
|             res += configClasses[1:] | ||||
|         elif include == 'custom': | ||||
|             res += [c for c in configClasses if c.customized] | ||||
|         elif include == 'predefined': | ||||
|             res = configClasses | ||||
|         return res | ||||
| 
 | ||||
|     def getClassesInOrder(self, allClasses): | ||||
|         '''When generating wrappers, classes mut be dumped in order (else, it | ||||
|            generates forward references in the Python file, that does not | ||||
|  | @ -478,13 +479,14 @@ class Generator(AbstractGenerator): | |||
|         msg = Msg(self.tool.name, '', Msg.CONFIG % self.applicationName) | ||||
|         self.labels.append(msg) | ||||
| 
 | ||||
|         # Tune the Ref field between Tool and User | ||||
|         # Tune the Ref field between Tool->User and Group->User | ||||
|         Tool.users.klass = User | ||||
|         if self.user.customized: | ||||
|             Tool.users.klass = self.user.klass | ||||
|             Group.users.klass = self.user.klass | ||||
| 
 | ||||
|         # Generate the Tool-related classes (User, Translation) | ||||
|         for klass in (self.user, self.translation): | ||||
|         # Generate the Tool-related classes (User, Group, Translation) | ||||
|         for klass in (self.user, self.group, self.translation): | ||||
|             klassType = klass.name[len(self.applicationName):] | ||||
|             klass.generateSchema() | ||||
|             self.labels += [ Msg(klass.name, '', klassType), | ||||
|  | @ -492,8 +494,7 @@ class Generator(AbstractGenerator): | |||
|             repls = self.repls.copy() | ||||
|             repls.update({'methods': klass.methods, 'genClassName': klass.name, | ||||
|               'baseMixin':'BaseMixin', 'parents': 'BaseMixin, SimpleItem', | ||||
|               'classDoc': 'User class for %s' % self.applicationName, | ||||
|               'icon':'object.gif'}) | ||||
|               'classDoc': 'Standard Appy class', 'icon':'object.gif'}) | ||||
|             self.copyFile('Class.py', repls, destName='%s.py' % klass.name) | ||||
| 
 | ||||
|         # Before generating the Tool class, finalize it with query result | ||||
|  |  | |||
|  | @ -85,28 +85,10 @@ class PloneInstaller: | |||
|             site.invokeFactory(self.appyFolderType, self.productName, | ||||
|                                title=self.productName) | ||||
|             getattr(site.portal_types, self.appyFolderType).global_allow = 0 | ||||
|             # Manager has been granted Add permissions for all root classes. | ||||
|             # This may not be desired, so remove this. | ||||
|             appFolder = getattr(site, self.productName) | ||||
|             for className in self.config.rootClasses: | ||||
|                 permission = self.getAddPermission(className) | ||||
|                 appFolder.manage_permission(permission, (), acquire=0) | ||||
|              | ||||
|         else: | ||||
|             appFolder = getattr(site, self.productName) | ||||
|         # All roles defined as creators should be able to create the | ||||
|         # corresponding root content types in this folder. | ||||
|         i = -1 | ||||
|         allCreators = set() | ||||
|         for klass in self.appClasses: | ||||
|             i += 1 | ||||
|             if not klass.__dict__.has_key('root') or not klass.__dict__['root']: | ||||
|                 continue # It is not a root class | ||||
|             creators = getattr(klass, 'creators', None) | ||||
|             if not creators: creators = self.defaultAddRoles | ||||
|             allCreators = allCreators.union(creators) | ||||
|             className = self.appClassNames[i] | ||||
|             permission = self.getAddPermission(className) | ||||
|             updateRolesForPermission(permission, tuple(creators), appFolder) | ||||
| 
 | ||||
|         # Beyond content-type-specific "add" permissions, creators must also | ||||
|         # have the main permission "Add portal content". | ||||
|         permission = 'Add portal content' | ||||
|  | @ -273,7 +255,8 @@ class ZopeInstaller: | |||
|         indexInfo = {'State': 'FieldIndex', 'UID': 'FieldIndex', | ||||
|                      'Title': 'TextIndex', 'SortableTitle': 'FieldIndex', | ||||
|                      'SearchableText': 'FieldIndex', 'Creator': 'FieldIndex', | ||||
|                      'Created': 'DateIndex', 'ClassName': 'FieldIndex'} | ||||
|                      'Created': 'DateIndex', 'ClassName': 'FieldIndex', | ||||
|                      'Allowed': 'KeywordIndex'} | ||||
|         tool = self.app.config | ||||
|         for className in self.config.attributes.iterkeys(): | ||||
|             wrapperClass = tool.getAppyClass(className, wrapper=True) | ||||
|  | @ -284,22 +267,49 @@ class ZopeInstaller: | |||
|                 indexInfo[indexName] = appyType.getIndexType() | ||||
|         self.installIndexes(indexInfo) | ||||
| 
 | ||||
|     def getAddPermission(self, className): | ||||
|         '''What is the name of the permission allowing to create instances of | ||||
|            class whose name is p_className?''' | ||||
|         return self.productName + ': Add ' + className | ||||
| 
 | ||||
|     def installBaseObjects(self): | ||||
|         '''Creates the tool and the root data folder if they do not exist.''' | ||||
|         # Create or update the base folder for storing data | ||||
|         zopeContent = self.app.objectIds() | ||||
|         if 'data' not in zopeContent: self.app.manage_addFolder('data') | ||||
| 
 | ||||
|         if 'data' not in zopeContent: | ||||
|             self.app.manage_addFolder('data') | ||||
|             data = self.app.data | ||||
|             # Manager has been granted Add permissions for all root classes. | ||||
|             # This may not be desired, so remove this. | ||||
|             for className in self.config.rootClasses: | ||||
|                 permission = self.getAddPermission(className) | ||||
|                 data.manage_permission(permission, (), acquire=0) | ||||
|             # All roles defined as creators should be able to create the | ||||
|             # corresponding root classes in this folder. | ||||
|             i = -1 | ||||
|             for klass in self.config.appClasses: | ||||
|                 i += 1 | ||||
|                 if not klass.__dict__.has_key('root') or \ | ||||
|                    not klass.__dict__['root']: | ||||
|                     continue # It is not a root class | ||||
|                 creators = getattr(klass, 'creators', None) | ||||
|                 if not creators: creators = self.config.defaultAddRoles | ||||
|                 className = self.config.appClassNames[i] | ||||
|                 permission = self.getAddPermission(className) | ||||
|                 updateRolesForPermission(permission, tuple(creators), data) | ||||
| 
 | ||||
|         if 'config' not in zopeContent: | ||||
|             toolName = '%sTool' % self.productName | ||||
|             createObject(self.app, 'config', toolName,self.productName,wf=False) | ||||
|         # Remove some default objects created by Zope but not useful | ||||
|         # Remove some default objects created by Zope but not useful to Appy | ||||
|         for name in ('standard_html_footer', 'standard_html_header',\ | ||||
|                      'standard_template.pt'): | ||||
|             if name in zopeContent: self.app.manage_delObjects([name]) | ||||
| 
 | ||||
|     def installTool(self): | ||||
|         '''Updates the tool (now that the catalog is created) and updates its | ||||
|            inner objects (translations, documents).''' | ||||
|            inner objects (users, groups, translations, documents).''' | ||||
|         tool = self.app.config | ||||
|         tool.createOrUpdate(True, None) | ||||
|         tool.refreshSecurity() | ||||
|  | @ -307,10 +317,24 @@ class ZopeInstaller: | |||
| 
 | ||||
|         # Create the admin user if no user exists. | ||||
|         if not self.app.acl_users.getUsers(): | ||||
|             appyTool.create('users', name='min', firstName='ad', | ||||
|                             login='admin', password1='admin', | ||||
|                             password2='admin', roles=['Manager']) | ||||
|             self.app.acl_users._doAddUser('admin', 'admin', ['Manager'], ()) | ||||
|             appyTool.log('Admin user "admin" created.') | ||||
| 
 | ||||
|         # Create group "admins" if it does not exist | ||||
|         if not appyTool.count('Group', login='admins'): | ||||
|             appyTool.create('groups', login='admins', title='Administrators', | ||||
|                             roles=['Manager']) | ||||
|             appyTool.log('Group "admins" created.') | ||||
| 
 | ||||
|         # Create a group for every global role defined in the application | ||||
|         for role in self.config.applicationGlobalRoles: | ||||
|             relatedGroup = '%s_group' % role | ||||
|             if appyTool.count('Group', login=relatedGroup): continue | ||||
|             appyTool.create('groups', login=relatedGroup, title=relatedGroup, | ||||
|                             roles=[role]) | ||||
|             appyTool.log('Group "%s", related to global role "%s", was ' \ | ||||
|                          'created.' % (relatedGroup, role)) | ||||
| 
 | ||||
|         # Create POD templates within the tool if required | ||||
|         for contentType in self.config.attributes.iterkeys(): | ||||
|             appyClass = tool.getAppyClass(contentType) | ||||
|  | @ -426,9 +450,16 @@ class ZopeInstaller: | |||
|                         continue # Back refs are initialised within fw refs | ||||
|                     appyType.init(name, baseClass, appName) | ||||
| 
 | ||||
|     def installRoles(self): | ||||
|         '''Installs the application-specific roles if not already done.''' | ||||
|         roles = list(self.app.__ac_roles__) | ||||
|         for role in self.config.applicationRoles: | ||||
|             if role not in roles: roles.append(role) | ||||
|         self.app.__ac_roles__ = tuple(roles) | ||||
| 
 | ||||
|     def install(self): | ||||
|         self.logger.info('is being installed...') | ||||
|         # Create the "admin" user if no user is present in the database | ||||
|         self.installRoles() | ||||
|         self.installAppyTypes() | ||||
|         self.installZopeClasses() | ||||
|         self.enableUserTracking() | ||||
|  |  | |||
|  | @ -30,6 +30,7 @@ class ToolMixin(BaseMixin): | |||
|         if res.find('Extensions_appyWrappers') != -1: | ||||
|             elems = res.split('_') | ||||
|             res = '%s%s' % (elems[1], elems[4]) | ||||
|         if res in ('User', 'Group', 'Translation'): res = appName + res | ||||
|         return res | ||||
| 
 | ||||
|     def getCatalog(self): | ||||
|  | @ -212,6 +213,17 @@ class ToolMixin(BaseMixin): | |||
|         if not appy: return res | ||||
|         return res.appy() | ||||
| 
 | ||||
|     def getAllowedValue(self): | ||||
|         '''Gets, for the currently logged user, the value for index | ||||
|            "Allowed".''' | ||||
|         user = self.getUser() | ||||
|         res = ['user:%s' % user.getId(), 'Anonymous'] + user.getRoles() | ||||
|         try: | ||||
|             res += ['user:%s' % g for g in user.groups.keys()] | ||||
|         except AttributeError, ae: | ||||
|             pass # The Zope admin does not have this attribute. | ||||
|         return res | ||||
| 
 | ||||
|     def executeQuery(self, className, searchName=None, startNumber=0, | ||||
|                      search=None, remember=False, brainsOnly=False, | ||||
|                      maxResults=None, noSecurity=False, sortBy=None, | ||||
|  | @ -303,10 +315,9 @@ class ToolMixin(BaseMixin): | |||
|         if refObject: | ||||
|             refField = refObject.getAppyType(refField) | ||||
|             params['UID'] = getattr(refObject, refField.name).data | ||||
|         # Determine what method to call on the portal catalog | ||||
|         if noSecurity: catalogMethod = 'unrestrictedSearchResults' | ||||
|         else:          catalogMethod = 'searchResults' | ||||
|         exec 'brains = self.getPath("/catalog").%s(**params)' % catalogMethod | ||||
|         # Use index "Allowed" if noSecurity is False | ||||
|         if not noSecurity: params['Allowed'] = self.getAllowedValue() | ||||
|         brains = self.getPath("/catalog")(**params) | ||||
|         if brainsOnly: | ||||
|             # Return brains only. | ||||
|             if not maxResults: return brains | ||||
|  |  | |||
|  | @ -87,6 +87,8 @@ class BaseMixin: | |||
|             if field.type != 'Ref': continue | ||||
|             for obj in field.getValue(self): | ||||
|                 field.back.unlinkObject(obj, self, back=True) | ||||
|         # Uncatalog the object | ||||
|         self.reindex(unindex=True) | ||||
|         # Delete the object | ||||
|         self.getParentNode().manage_delObjects([self.id]) | ||||
| 
 | ||||
|  | @ -306,22 +308,12 @@ class BaseMixin: | |||
|         url = self.absolute_url_path() | ||||
|         catalog = self.getPhysicalRoot().catalog | ||||
|         if unindex: | ||||
|             method = catalog.uncatalog_object | ||||
|             catalog.uncatalog_object(url) | ||||
|         else: | ||||
|             method = catalog.catalog_object | ||||
|         if indexes: | ||||
|             return method(self, url) | ||||
|         else: | ||||
|             return method(self, url, idxs=indexes) | ||||
| 
 | ||||
|     def unindex(self, indexes=None): | ||||
|         '''Undatalog this object.''' | ||||
|         url = self.absolute_url_path() | ||||
|         catalog = self.getPhysicalRoot().catalog | ||||
|         if indexes: | ||||
|             return catalog.catalog_object(self, url) | ||||
|         else: | ||||
|             return catalog.catalog_object(self, url, idxs=indexes) | ||||
|             if indexes: | ||||
|                 catalog.catalog_object(self, url, idxs=indexes) | ||||
|             else: | ||||
|                 catalog.catalog_object(self, url) | ||||
| 
 | ||||
|     def say(self, msg, type='info'): | ||||
|         '''Prints a p_msg in the user interface. p_logLevel may be "info", | ||||
|  | @ -620,7 +612,7 @@ class BaseMixin: | |||
|                 # Insert the GroupDescr instance corresponding to | ||||
|                 # appyType.group at the right place | ||||
|                 groupDescr = appyType.group.insertInto(res, groups, | ||||
|                     appyType.page, self.meta_type) | ||||
|                                                   appyType.page, self.meta_type) | ||||
|                 GroupDescr.addWidget(groupDescr, appyType.__dict__) | ||||
|         return res | ||||
| 
 | ||||
|  | @ -1170,6 +1162,23 @@ class BaseMixin: | |||
|         '''Returns the name of the (Zope) class for self.''' | ||||
|         return self.portal_type | ||||
| 
 | ||||
|     def Allowed(self): | ||||
|         '''Returns the list of roles and users that are allowed to view this | ||||
|            object. This index value will be used within catalog queries for | ||||
|            filtering objects the user is allowed to see.''' | ||||
|         res = set() | ||||
|         # Get, from the workflow, roles having permission 'View'. | ||||
|         for role in self.getProductConfig().rolesForPermissionOn('View', self): | ||||
|             res.add(role) | ||||
|         # Add users having, locally, this role on this object. | ||||
|         localRoles = getattr(self, '__ac_local_roles__', None) | ||||
|         if not localRoles: return list(res) | ||||
|         for id, roles in localRoles.iteritems(): | ||||
|             for role in roles: | ||||
|                 if role in res: | ||||
|                     res.add('user:%s' % id) | ||||
|         return list(res) | ||||
| 
 | ||||
|     def _appy_showState(self, workflow, stateShow): | ||||
|         '''Must I show a state whose "show value" is p_stateShow?''' | ||||
|         if callable(stateShow): | ||||
|  | @ -1280,6 +1289,24 @@ class BaseMixin: | |||
|             return nobody | ||||
|         return user | ||||
| 
 | ||||
|     def getTool(self): | ||||
|         '''Returns the application tool.''' | ||||
|         return self.getPhysicalRoot().config | ||||
| 
 | ||||
|     def getProductConfig(self): | ||||
|         '''Returns a reference to the config module.''' | ||||
|         return self.__class__.config | ||||
| 
 | ||||
|     def index_html(self): | ||||
|        """Redirects to /ui. Transfers the status message if any.""" | ||||
|        rq = self.REQUEST | ||||
|        msg = rq.get('portal_status_message', '') | ||||
|        if msg: | ||||
|            url = self.getUrl(portal_status_message=msg) | ||||
|        else: | ||||
|            url = self.getUrl() | ||||
|        return rq.RESPONSE.redirect(url) | ||||
| 
 | ||||
|     def userIsAnon(self): | ||||
|         '''Is the currently logged user anonymous ?''' | ||||
|         return self.getUser().getUserName() == 'Anonymous User' | ||||
|  | @ -1406,22 +1433,7 @@ class BaseMixin: | |||
| 
 | ||||
|     def allows(self, permission): | ||||
|         '''Has the logged user p_permission on p_self ?''' | ||||
|         # Get first the roles that have this permission on p_self. | ||||
|         zopeAttr = Permission.getZopeAttrName(permission) | ||||
|         if not hasattr(self.aq_base, zopeAttr): return | ||||
|         allowedRoles = getattr(self.aq_base, zopeAttr) | ||||
|         # Has the user one of those roles? | ||||
|         user = self.getUser() | ||||
|         # XXX no groups at present | ||||
|         #ids = [user.getId()] + user.getGroups() | ||||
|         ids = [user.getId()] | ||||
|         userGlobalRoles = user.getRoles() | ||||
|         for role in allowedRoles: | ||||
|             # Has the user this role ? Check in the local roles first. | ||||
|             for id, roles in self.__ac_local_roles__.iteritems(): | ||||
|                 if (role in roles) and (id in ids): return True | ||||
|             # Check then in the global roles. | ||||
|             if role in userGlobalRoles: return True | ||||
|         return self.getUser().has_permission(permission, self) | ||||
| 
 | ||||
|     def getEditorInit(self, name): | ||||
|         '''Gets the Javascrit init code for displaying a rich editor for | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ | |||
| # ------------------------------------------------------------------------------ | ||||
| import types | ||||
| from appy.gen import * | ||||
| Grp=Group # Avoid name clash between appy.gen.Group and class Group below | ||||
| 
 | ||||
| # Prototypical instances of every type ----------------------------------------- | ||||
| class Protos: | ||||
|  | @ -83,8 +84,8 @@ class ModelClass: | |||
|                     value = '%s.%s' % (moduleName, value.__name__) | ||||
|             elif isinstance(value, Selection): | ||||
|                 value = 'Selection("%s")' % value.methodName | ||||
|             elif isinstance(value, Group): | ||||
|                 value = 'Group("%s")' % value.name | ||||
|             elif isinstance(value, Grp): | ||||
|                 value = 'Grp("%s")' % value.name | ||||
|             elif isinstance(value, Page): | ||||
|                 value = 'pages["%s"]' % value.name | ||||
|             elif callable(value): | ||||
|  | @ -149,6 +150,23 @@ class User(ModelClass): | |||
|     gm['multiplicity'] = (0, None) | ||||
|     roles = String(validator=Selection('getGrantableRoles'), indexed=True, **gm) | ||||
| 
 | ||||
| # The Group class -------------------------------------------------------------- | ||||
| class Group(ModelClass): | ||||
|     # In a ModelClass we need to declare attributes in the following list. | ||||
|     _appy_attributes = ['title', 'login', 'roles', 'users'] | ||||
|     # All methods defined below are fake. Real versions are in the wrapper. | ||||
|     m = {'group': 'main', 'width': 25, 'indexed': True} | ||||
|     title = String(multiplicity=(1,1), **m) | ||||
|     def showLogin(self): pass | ||||
|     def validateLogin(self): pass | ||||
|     login = String(show=showLogin, validator=validateLogin, | ||||
|                    multiplicity=(1,1), **m) | ||||
|     roles = String(validator=Selection('getGrantableRoles'), | ||||
|                    multiplicity=(0,None), **m) | ||||
|     users = Ref(User, multiplicity=(0,None), add=False, link=True, | ||||
|                 back=Ref(attribute='groups', show=True), | ||||
|                 showHeaders=True, shownInfo=('title', 'login')) | ||||
| 
 | ||||
| # The Translation class -------------------------------------------------------- | ||||
| class Translation(ModelClass): | ||||
|     _appy_attributes = ['po', 'title'] | ||||
|  | @ -166,7 +184,7 @@ toolFieldPrefixes = ('defaultValue', 'podTemplate', 'formats', 'resultColumns', | |||
|                      'enableAdvancedSearch', 'numberOfSearchColumns', | ||||
|                      'searchFields', 'optionalFields', 'showWorkflow', | ||||
|                      'showWorkflowCommentField', 'showAllStatesInPhase') | ||||
| defaultToolFields = ('users', 'translations', 'enableNotifications', | ||||
| defaultToolFields = ('users', 'groups', 'translations', 'enableNotifications', | ||||
|                      'unoEnabledPython', 'openOfficePort', | ||||
|                      'numberOfResultsPerPage', 'listBoxesMaximumWidth', | ||||
|                      'appyVersion', 'refreshSecurity') | ||||
|  | @ -187,13 +205,20 @@ class Tool(ModelClass): | |||
|     refreshSecurity = Action(action=refreshSecurity, confirm=True) | ||||
|     # Ref(User) will maybe be transformed into Ref(CustomUserClass). | ||||
|     users = Ref(User, multiplicity=(0,None), add=True, link=False, | ||||
|                 back=Ref(attribute='toTool', show=False), page='users', | ||||
|                 queryable=True, queryFields=('login',), showHeaders=True, | ||||
|                 shownInfo=('login', 'title', 'roles')) | ||||
|                 back=Ref(attribute='toTool', show=False), | ||||
|                 page=Page('users', show='view'), | ||||
|                 queryable=True, queryFields=('title', 'login'), | ||||
|                 showHeaders=True, shownInfo=('title', 'login', 'roles')) | ||||
|     groups = Ref(Group, multiplicity=(0,None), add=True, link=False, | ||||
|                  back=Ref(attribute='toTool2', show=False), | ||||
|                  page=Page('groups', show='view'), | ||||
|                  queryable=True, queryFields=('title', 'login'), | ||||
|                  showHeaders=True, shownInfo=('title', 'login', 'roles')) | ||||
|     translations = Ref(Translation, multiplicity=(0,None),add=False,link=False, | ||||
|                        back=Ref(attribute='trToTool', show=False), show='view', | ||||
|                        page=Page('translations', show='view')) | ||||
|     enableNotifications = Boolean(default=True, page='notifications') | ||||
|     enableNotifications = Boolean(default=True, | ||||
|                                   page=Page('notifications', show=False)) | ||||
| 
 | ||||
|     @classmethod | ||||
|     def _appy_clean(klass): | ||||
|  |  | |||
|  | @ -23,21 +23,11 @@ class <!genClassName!>(<!parents!>): | |||
|     global_allow = 1 | ||||
|     icon = "ui/<!icon!>" | ||||
|     wrapperClass = Wrapper | ||||
|     for elem in dir(<!baseMixin!>): | ||||
|         if not elem.startswith('__'): security.declarePublic(elem) | ||||
|     def getTool(self): return self.getPhysicalRoot().config | ||||
|     def getProductConfig(self): return cfg | ||||
|     def index_html(self): | ||||
|        """Redirects to /ui. Transfers the status message if any.""" | ||||
|        rq = self.REQUEST | ||||
|        msg = rq.get('portal_status_message', '') | ||||
|        if msg: | ||||
|            url = self.getUrl(portal_status_message=msg) | ||||
|        else: | ||||
|            url = self.getUrl() | ||||
|        return rq.RESPONSE.redirect(url) | ||||
|     config = cfg | ||||
|     def do(self): | ||||
|         '''BaseMixin.do can't be traversed by Zope if this class is the tool. | ||||
|            So here, we redefine this method.''' | ||||
|         return BaseMixin.do(self) | ||||
|     for elem in dir(<!baseMixin!>): | ||||
|         if not elem.startswith('__'): security.declarePublic(elem) | ||||
| <!methods!> | ||||
|  |  | |||
|  | @ -1,8 +1,10 @@ | |||
| # ------------------------------------------------------------------------------ | ||||
| from appy.gen import * | ||||
| Grp = Group # Avoid name clashes with the Group class below and appy.gen.Group | ||||
| from appy.gen.plone25.wrappers import AbstractWrapper | ||||
| from appy.gen.plone25.wrappers.ToolWrapper import ToolWrapper as WTool | ||||
| from appy.gen.plone25.wrappers.UserWrapper import UserWrapper as WUser | ||||
| from appy.gen.plone25.wrappers.GroupWrapper import GroupWrapper as WGroup | ||||
| from appy.gen.plone25.wrappers.TranslationWrapper import TranslationWrapper as WT | ||||
| from Globals import InitializeClass | ||||
| from AccessControl import ClassSecurityInfo | ||||
|  | @ -10,6 +12,7 @@ tfw = {"edit":"f","cell":"f","view":"f"} # Layout for Translation fields | |||
| <!imports!> | ||||
| 
 | ||||
| <!User!> | ||||
| <!Group!> | ||||
| <!Translation!> | ||||
| <!Tool!> | ||||
| <!wrappers!> | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ from ZPublisher.HTTPRequest import BaseRequest | |||
| from OFS.Image import File | ||||
| from ZPublisher.HTTPRequest import FileUpload | ||||
| from AccessControl import getSecurityManager | ||||
| from AccessControl.PermissionRole import rolesForPermissionOn | ||||
| from DateTime import DateTime | ||||
| from Products.ExternalMethod.ExternalMethod import ExternalMethod | ||||
| from Products.Transience.Transience import TransientObjectContainer | ||||
|  |  | |||
							
								
								
									
										74
									
								
								gen/plone25/wrappers/GroupWrapper.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								gen/plone25/wrappers/GroupWrapper.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,74 @@ | |||
| # ------------------------------------------------------------------------------ | ||||
| from appy.gen.plone25.wrappers import AbstractWrapper | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| class GroupWrapper(AbstractWrapper): | ||||
| 
 | ||||
|     def showLogin(self): | ||||
|         '''When must we show the login field?''' | ||||
|         if self.o.isTemporary(): return 'edit' | ||||
|         return 'view' | ||||
| 
 | ||||
|     def validateLogin(self, login): | ||||
|         '''Is this p_login valid?''' | ||||
|         return True | ||||
| 
 | ||||
|     def getGrantableRoles(self): | ||||
|         '''Returns the list of roles that the admin can grant to a user.''' | ||||
|         res = [] | ||||
|         for role in self.o.getProductConfig().grantableRoles: | ||||
|             res.append( (role, self.translate('role_%s' % role)) ) | ||||
|         return res | ||||
| 
 | ||||
|     def validate(self, new, errors): | ||||
|         '''Inter-field validation.''' | ||||
|         return self._callCustom('validate', new, errors) | ||||
| 
 | ||||
|     def confirm(self, new): | ||||
|         '''Use this method for remembering the previous list of users for this | ||||
|            group.''' | ||||
|         obj = self.o | ||||
|         if hasattr(obj.aq_base, '_oldUsers'): del obj.aq_base._oldUsers | ||||
|         obj._oldUsers = self.users | ||||
| 
 | ||||
|     def addUser(self, user): | ||||
|         '''Adds a p_user to this group.''' | ||||
|         # Update the Ref field. | ||||
|         self.link('users', user) | ||||
|         # Update the group-related info on the Zope user. | ||||
|         zopeUser = user.getZopeUser() | ||||
|         zopeUser.groups[self.login] = self.roles | ||||
| 
 | ||||
|     def removeUser(self, user): | ||||
|         '''Removes a p_user from this group.''' | ||||
|         self.unlink('users', user) | ||||
|         # Update the group-related info on the Zope user. | ||||
|         zopeUser = user.getZopeUser() | ||||
|         del zopeUser.groups[self.login] | ||||
| 
 | ||||
|     def onEdit(self, created): | ||||
|         # Create or update, on every Zope user of this group, group-related | ||||
|         # information. | ||||
|         # 1. Remove reference to this group for users that were removed from it | ||||
|         newUsers = self.users | ||||
|         # The list of previously existing users does not exist when editing a | ||||
|         # group from Python. For updating self.users, it is recommended to use | ||||
|         # methods m_addUser and m_removeUser above. | ||||
|         oldUsers = getattr(self.o.aq_base, '_oldUsers', ()) | ||||
|         for user in oldUsers: | ||||
|             if user not in newUsers: | ||||
|                 del user.getZopeUser().groups[self.login] | ||||
|                 self.log('User "%s" removed from group "%s".' % \ | ||||
|                          (user.login, self.login)) | ||||
|         # 2. Add reference to this group for users that were added to it | ||||
|         for user in newUsers: | ||||
|             zopeUser = user.getZopeUser() | ||||
|             # We refresh group-related info on the Zope user even if the user | ||||
|             # was already in the group. | ||||
|             zopeUser.groups[self.login] = self.roles | ||||
|             if user not in oldUsers: | ||||
|                 self.log('User "%s" added to group "%s".' % \ | ||||
|                          (user.login, self.login)) | ||||
|         if hasattr(self.o.aq_base, '_oldUsers'): del self.o._oldUsers | ||||
|         return self._callCustom('onEdit', created) | ||||
| # ------------------------------------------------------------------------------ | ||||
|  | @ -12,24 +12,18 @@ class UserWrapper(AbstractWrapper): | |||
|     def validateLogin(self, login): | ||||
|         '''Is this p_login valid?''' | ||||
|         # The login can't be the id of the whole site or "admin" | ||||
|         if (login == self.o.portal_url.getPortalObject().getId()) or \ | ||||
|            (login == 'admin'): | ||||
|             return self.translate(u'This username is reserved. Please choose ' \ | ||||
|                                    'a different name.', domain='plone') | ||||
|         # Check that the login does not already exist and check some | ||||
|         # Plone-specific rules. | ||||
|         pr = self.o.portal_registration | ||||
|         if not pr.isMemberIdAllowed(login): | ||||
|             return self.translate(u'The login name you selected is already ' \ | ||||
|                'in use or is not valid. Please choose another.', domain='plone') | ||||
|         if login == 'admin': | ||||
|             return self.translate('This username is reserved.') | ||||
|         # Check that no user or group already uses this login. | ||||
|         if self.count('User', login=login) or self.count('Group', login=login): | ||||
|             return self.translate('This login is already in use.') | ||||
|         return True | ||||
| 
 | ||||
|     def validatePassword(self, password): | ||||
|         '''Is this p_password valid?''' | ||||
|         # Password must be at least 5 chars length | ||||
|         if len(password) < 5: | ||||
|             return self.translate(u'Passwords must contain at least 5 letters.', | ||||
|                                   domain='plone') | ||||
|             return self.translate('Passwords must contain at least 5 letters.') | ||||
|         return True | ||||
| 
 | ||||
|     def showPassword(self): | ||||
|  | @ -49,7 +43,7 @@ class UserWrapper(AbstractWrapper): | |||
|         page = self.request.get('page', 'main') | ||||
|         if page == 'main': | ||||
|             if hasattr(new, 'password1') and (new.password1 != new.password2): | ||||
|                 msg = self.translate(u'Passwords do not match.', domain='plone') | ||||
|                 msg = self.translate('Passwords do not match.') | ||||
|                 errors.password1 = msg | ||||
|                 errors.password2 = msg | ||||
|         return self._callCustom('validate', new, errors) | ||||
|  | @ -61,41 +55,104 @@ class UserWrapper(AbstractWrapper): | |||
|         if created: | ||||
|             # Create the corresponding Zope user | ||||
|             aclUsers._doAddUser(login, self.password1, self.roles, ()) | ||||
|             zopeUser = aclUsers.getUser(login) | ||||
|             # Remove our own password copies | ||||
|             self.password1 = self.password2 = '' | ||||
|         # Perform updates on the corresponding Plone user | ||||
|         zopeUser = aclUsers.getUserById(login) | ||||
|         # This object must be owned by its Plone user | ||||
|             from persistent.mapping import PersistentMapping | ||||
|             # The following dict will store, for every group, global roles | ||||
|             # granted to it. | ||||
|             zopeUser.groups = PersistentMapping() | ||||
|         else: | ||||
|             # Updates roles at the Zope level. | ||||
|             zopeUser = aclUsers.getUserById(login) | ||||
|             zopeUser.roles = self.roles | ||||
|         # "self" must be owned by its Zope user | ||||
|         if 'Owner' not in self.o.get_local_roles_for_userid(login): | ||||
|             self.o.manage_addLocalRoles(login, ('Owner',)) | ||||
|         # Change group membership according to self.roles. Indeed, instead of | ||||
|         # granting roles directly to the user, we will add the user to a | ||||
|         # Appy-created group having this role. | ||||
|         userRoles = self.roles | ||||
|         #userGroups = zopeUser.getGroups() | ||||
|         # for role in self.o.getProductConfig().grantableRoles: | ||||
|         #    # Retrieve the group corresponding to this role | ||||
|         #    groupName = '%s_group' % role | ||||
|         #    if role == 'Manager':    groupName = 'Administrators' | ||||
|         #    elif role == 'Reviewer': groupName = 'Reviewers' | ||||
|         #    group = self.o.portal_groups.getGroupById(groupName) | ||||
|         #    # Add or remove the user from this group according to its role(s). | ||||
|         #    if role in userRoles: | ||||
|         #        # Add the user if not already present in the group | ||||
|         #        if groupName not in userGroups: | ||||
|         #            group.addMember(self.login) | ||||
|         #    else: | ||||
|         #        # Remove the user if it was in the corresponding group | ||||
|         #        if groupName in userGroups: | ||||
|         #            group.removeMember(self.login) | ||||
|         return self._callCustom('onEdit', created) | ||||
| 
 | ||||
|     def getZopeUser(self): | ||||
|         '''Gets the Zope user corresponding to this user.''' | ||||
|         return self.o.acl_users.getUser(self.login) | ||||
| 
 | ||||
|     def onDelete(self): | ||||
|         '''Before deleting myself, I must delete the corresponding Plone | ||||
|            user.''' | ||||
|         # Delete the corresponding Plone user | ||||
|         self.o.acl_users._doDelUser(self.login) | ||||
|         self.log('Plone user "%s" deleted.' % self.login) | ||||
|         '''Before deleting myself, I must delete the corresponding Zope user.''' | ||||
|         self.o.acl_users._doDelUsers([self.login]) | ||||
|         self.log('User "%s" deleted.' % self.login) | ||||
|         # Call a custom "onDelete" if any. | ||||
|         return self._callCustom('onDelete') | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| try: | ||||
|     from AccessControl.PermissionRole import _what_not_even_god_should_do, \ | ||||
|                                              rolesForPermissionOn | ||||
|     from Acquisition import aq_base | ||||
| except ImportError: | ||||
|     pass # For those using Appy without Zope | ||||
| 
 | ||||
| class ZopeUserPatches: | ||||
|     '''This class is a fake one that defines Appy variants of some of Zope's | ||||
|        AccessControl.User methods. The idea is to implement the notion of group | ||||
|        of users.''' | ||||
| 
 | ||||
|     def getRoles(self): | ||||
|         '''Returns the global roles that this user (or any of its groups) | ||||
|            possesses.''' | ||||
|         res = list(self.roles) | ||||
|         # Add group global roles | ||||
|         if not hasattr(aq_base(self), 'groups'): return res | ||||
|         for roles in self.groups.itervalues(): | ||||
|             for role in roles: | ||||
|                 if role not in res: res.append(role) | ||||
|         return res | ||||
| 
 | ||||
|     def getRolesInContext(self, object): | ||||
|         '''Return the list of global and local (to p_object) roles granted to | ||||
|            this user (or to any of its groups).''' | ||||
|         object = getattr(object, 'aq_inner', object) | ||||
|         # Start with user global roles | ||||
|         res = self.getRoles() | ||||
|         # Add local roles | ||||
|         localRoles = getattr(object, '__ac_local_roles__', None) | ||||
|         if not localRoles: return res | ||||
|         userId = self.getId() | ||||
|         groups = getattr(self, 'groups', ()) | ||||
|         for id, roles in localRoles.iteritems(): | ||||
|             if (id != userId) and (id not in groups): continue | ||||
|             for role in roles: res.add(role) | ||||
|         return res | ||||
| 
 | ||||
|     def allowed(self, object, object_roles=None): | ||||
|         '''Checks whether the user has access to p_object. The user (or one of | ||||
|            its groups) must have one of the roles in p_object_roles.''' | ||||
|         if object_roles is _what_not_even_god_should_do: return 0 | ||||
|         # If "Anonymous" is among p_object_roles, grant access. | ||||
|         if (object_roles is None) or ('Anonymous' in object_roles): return 1 | ||||
|         # If "Authenticated" is among p_object_roles, grant access if the user | ||||
|         # is not anonymous. | ||||
|         if 'Authenticated' in object_roles and \ | ||||
|            (self.getUserName() != 'Anonymous User'): | ||||
|             if self._check_context(object): return 1 | ||||
|         # Try first to grant access based on global user roles | ||||
|         for role in self.getRoles(): | ||||
|             if role not in object_roles: continue | ||||
|             if self._check_context(object): return 1 | ||||
|             return | ||||
|         # Try then to grant access based on local roles | ||||
|         innerObject = getattr(object, 'aq_inner', object) | ||||
|         localRoles = getattr(innerObject, '__ac_local_roles__', None) | ||||
|         if not localRoles: return | ||||
|         userId = self.getId() | ||||
|         groups = getattr(self, 'groups', ()) | ||||
|         for id, roles in localRoles.iteritems(): | ||||
|             if (id != userId) and (id not in groups): continue | ||||
|             for role in roles: | ||||
|                 if role not in object_roles: continue | ||||
|                 if self._check_context(object): return 1 | ||||
|                 return | ||||
| 
 | ||||
|     from AccessControl.User import SimpleUser | ||||
|     SimpleUser.getRoles = getRoles | ||||
|     SimpleUser.getRolesInContext = getRolesInContext | ||||
|     SimpleUser.allowed = allowed | ||||
| # ------------------------------------------------------------------------------ | ||||
|  |  | |||
|  | @ -54,6 +54,8 @@ class AbstractWrapper(object): | |||
|             return o.workflow_history[key] | ||||
|         elif name == 'user': | ||||
|             return self.o.getUser() | ||||
|         elif name == 'appyUser': | ||||
|             return self.search('User', login=self.o.getUser().getId())[0] | ||||
|         elif name == 'fields': return self.o.getAllAppyTypes() | ||||
|         # Now, let's try to return a real attribute. | ||||
|         res = object.__getattribute__(self, name) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Gaetan Delannay
						Gaetan Delannay