appy.gen: Ploneless version.

This commit is contained in:
Gaetan Delannay 2011-11-28 22:50:01 +01:00
parent d0cbe7e573
commit a321257e55
13 changed files with 393 additions and 159 deletions

View file

@ -2566,9 +2566,9 @@ class Transition:
comments=comment)
# Update permissions-to-roles attributes
targetState.updatePermissions(wf, obj)
# Refresh catalog-related security if required
if not obj.isTemporary():
obj.reindex(indexes=('allowedRolesAndUsers', 'State'))
# Reindex the object if required. Not only security-related indexes
# (Allowed, State) need to be updated here.
if not obj.isTemporary(): obj.reindex()
# Execute the related action if needed
msg = ''
if doAction and self.action: msg = self.executeAction(obj, wf)
@ -2718,7 +2718,7 @@ class Config:
# If you want to replace the default front page with a page coming from
# your application, use the following parameter. Setting
# frontPage = True will replace the Plone front page with a page
# whose content will come fron i18n label "front_page_text".
# whose content will come from i18n label "front_page_text".
self.frontPage = False
# You can choose the Plone or Appy main template
self.frontPageTemplate = 'plone' # or "appy"

View file

@ -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.'''

View file

@ -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

View file

@ -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()

View file

@ -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

View file

@ -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)
catalog.catalog_object(self, url, idxs=indexes)
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)
catalog.catalog_object(self, url)
def say(self, msg, type='info'):
'''Prints a p_msg in the user interface. p_logLevel may be "info",
@ -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

View file

@ -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):

View file

@ -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!>

View file

@ -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!>

View file

@ -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

View 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)
# ------------------------------------------------------------------------------

View file

@ -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
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)
# This object must be owned by its Plone user
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
# ------------------------------------------------------------------------------

View file

@ -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)