appy.gen: Ploneless version.
This commit is contained in:
parent
d0cbe7e573
commit
a321257e55
|
@ -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"
|
||||
|
|
|
@ -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…
Reference in a new issue