appy.gen: use new index 'getState' for indexing object states; reduced size of generated file config.py; optimized debug mode: class reload is not done automatically: a 'refresh' icon is available on view and edit views.

This commit is contained in:
Gaetan Delannay 2011-09-14 21:01:58 +02:00
parent 9258b76bdf
commit b6dcc42038
10 changed files with 106 additions and 121 deletions

View file

@ -326,7 +326,6 @@ class Search:
elif fieldName == 'description':
if usage == 'search': return 'Description'
else: return None
elif fieldName == 'state': return 'review_state'
else:
return 'get%s%s'% (fieldName[0].upper(),fieldName[1:])
@staticmethod
@ -487,24 +486,27 @@ class Type:
called for storing the name of the Appy field (p_name) and other
attributes that are based on the name of the Appy p_klass, and the
application name (p_appName).'''
if hasattr(self, 'name'): return # Already initialized
self.name = name
# Determine prefix for this class
if not klass: prefix = appName
else: prefix = getClassName(klass, appName)
# Recompute the ID (and derived attributes) that may have changed if
# we are in debug mode (because we recreate new Type instances).
self.id = id(self)
if self.slaves: self.master_css = 'appyMaster master_%s' % self.id
# Determine ids of i18n labels for this field
labelName = name
prefix = None
trPrefix = None
if self.label:
if isinstance(self.label, basestring): prefix = self.label
else: # It is a tuple (prefix, name)
if isinstance(self.label, basestring): trPrefix = self.label
else: # It is a tuple (trPrefix, name)
if self.label[1]: labelName = self.label[1]
if self.label[0]: prefix = self.label[0]
if not prefix:
if not klass: prefix = appName
else: prefix = getClassName(klass, appName)
if self.label[0]: trPrefix = self.label[0]
if not trPrefix:
trPrefix = prefix
# Determine name to use for i18n
self.labelId = '%s_%s' % (prefix, labelName)
self.labelId = '%s_%s' % (trPrefix, labelName)
self.descrId = self.labelId + '_descr'
self.helpId = self.labelId + '_help'
# Determine read and write permissions for this field
@ -522,9 +524,10 @@ class Type:
self.writePermission = wp
else:
self.writePermission = 'Modify portal content'
if isinstance(self, Ref):
self.backd = self.back.__dict__
if isinstance(self, Ref) and not self.isBack:
# We must initialise the corresponding back reference
self.back.klass = klass
self.back.init(self.back.attribute, self.klass, appName)
self.back.relationship = '%s_%s_rel' % (prefix, name)
def reload(self, klass, obj):
@ -534,6 +537,7 @@ class Type:
module has been performed.'''
res = getattr(klass, self.name, None)
if not res: return self
if isinstance(self, Ref) and self.isBack: return self
res.init(self.name, klass, obj.getProductConfig().PROJECTNAME)
return res
@ -1693,6 +1697,7 @@ class Ref(Type):
back.isBack = True
back.back = self
back.backd = self.__dict__
setattr(klass, back.attribute, back)
# When displaying a tabular list of referenced objects, must we show
# the table headers?
self.showHeaders = showHeaders
@ -2487,7 +2492,7 @@ class Transition:
targetState.updatePermissions(wf, obj)
# Refresh catalog-related security if required
if not obj.isTemporary():
obj.reindexObject(idxs=('allowedRolesAndUsers','review_state'))
obj.reindexObject(idxs=('allowedRolesAndUsers', 'getState'))
# Execute the related action if needed
msg = ''
if doAction and self.action: msg = self.executeAction(obj, wf)
@ -2654,12 +2659,4 @@ class Config:
# Language that will be used as a basis for translating to other
# languages.
self.sourceLanguage = 'en'
# ------------------------------------------------------------------------------
# Special field "type" is mandatory for every class. If one class does not
# define it, we will add a copy of the instance defined below.
title = String(multiplicity=(1,1), show='edit')
title.init('title', None, 'appy')
state = String()
state.init('state', None, 'appy')
# ------------------------------------------------------------------------------

View file

@ -370,40 +370,21 @@ class Generator(AbstractGenerator):
# Compute the list of ordered attributes (forward and backward,
# inherited included) for every Appy class.
attributes = []
attributesDict = []
for classDescr in classesAll:
titleFound = False
attrs = []
attrNames = []
names = []
for name, appyType, klass in classDescr.getOrderedAppyAttributes():
attrs.append(self.getAppyTypePath(name, appyType, klass))
attrNames.append(name)
names.append(name)
if name == 'title': titleFound = True
# Add the "title" mandatory field if not found
if not titleFound:
attrs.insert(0, 'copy.deepcopy(appy.gen.title)')
attrNames.insert(0, 'title')
if not titleFound: names.insert(0, 'title')
# Any backward attributes to append?
if classDescr.name in self.referers:
for field, rel in self.referers[classDescr.name]:
try:
getattr(field.classDescr.klass, field.fieldName)
klass = field.classDescr.klass
except AttributeError:
klass = field.classDescr.modelClass
attrs.append(self.getAppyTypePath(field.fieldName,
field.appyType, klass, isBack=True))
attrNames.append(field.appyType.back.attribute)
attributes.append('"%s":[%s]' % (classDescr.name,','.join(attrs)))
aDict = ''
i = -1
for attr in attrs:
i += 1
aDict += '"%s":attributes["%s"][%d],' % \
(attrNames[i], classDescr.name, i)
attributesDict.append('"%s":{%s}' % (classDescr.name, aDict))
names.append(field.appyType.back.attribute)
qNames = ['"%s"' % name for name in names]
attributes.append('"%s":[%s]' % (classDescr.name, ','.join(qNames)))
repls['attributes'] = ',\n '.join(attributes)
repls['attributesDict'] = ',\n '.join(attributesDict)
# Compute list of used roles for registering them if needed
specificRoles = self.getAllUsedRoles(plone=False)
repls['roles'] = ','.join(['"%s"' % r.name for r in specificRoles])

View file

@ -6,7 +6,7 @@ import os, os.path, time
from StringIO import StringIO
from sets import Set
import appy
from appy.gen import Type, Ref
from appy.gen import Type, Ref, String
from appy.gen.po import PoParser
from appy.gen.utils import produceNiceMessage
from appy.gen.plone25.utils import updateRolesForPermission
@ -231,10 +231,11 @@ class PloneInstaller:
'''Creates or updates the POD templates in the tool according to pod
declarations in the application classes.'''
# Creates the templates for Pod fields if they do not exist.
for contentType, appyTypes in self.attributes.iteritems():
for contentType in self.attributes.iterkeys():
appyClass = self.tool.getAppyClass(contentType)
if not appyClass: continue # May be an abstract class
for appyType in appyTypes:
wrapperClass = self.tool.getAppyClass(contentType, wrapper=True)
for appyType in wrapperClass.__fields__:
if appyType.type != 'Pod': continue
# Find the attribute that stores the template, and store on
# it the default one specified in the appyType if no
@ -290,6 +291,7 @@ class PloneInstaller:
nvProps.manage_changeProperties(**{'idsNotToList': current})
self.tool = getattr(self.ploneSite, self.toolInstanceName)
self.tool.refreshSecurity()
self.appyTool = self.tool.appy()
if self.reinstall:
self.tool.createOrUpdate(False, None)
@ -413,9 +415,12 @@ class PloneInstaller:
def manageIndexes(self):
'''For every indexed field, this method installs and updates the
corresponding index if it does not exist yet.'''
indexInfo = {}
for className, appyTypes in self.attributes.iteritems():
for appyType in appyTypes:
# Create a special index for object state, that does not correspond to
# a field.
indexInfo = {'getState': 'FieldIndex'}
for className in self.attributes.iterkeys():
wrapperClass = self.tool.getAppyClass(className, wrapper=True)
for appyType in wrapperClass.__fields__:
if not appyType.indexed or (appyType.name == 'title'): continue
n = appyType.name
indexName = 'get%s%s' % (n[0].upper(), n[1:])
@ -553,17 +558,25 @@ class ZopeInstaller:
def completeAppyTypes(self):
'''We complete here the initialisation process of every Appy type of
every gen-class of the application.'''
appName = self.productName
for klass in self.classes:
# Store on wrapper class the ordered list of Appy types
wrapperClass = klass.wrapperClass
if not hasattr(wrapperClass, 'title'):
# Special field "type" is mandatory for every class.
title = String(multiplicity=(1,1), show='edit', indexed=True)
title.init('title', None, 'appy')
setattr(wrapperClass, 'title', title)
names = self.config.attributes[wrapperClass.__name__[:-8]]
wrapperClass.__fields__ = [getattr(wrapperClass, n) for n in names]
# Post-initialise every Appy type
for baseClass in klass.wrapperClass.__bases__:
if baseClass.__name__ == 'AbstractWrapper': continue
for name, appyType in baseClass.__dict__.iteritems():
if isinstance(appyType, Type):
appyType.init(name, baseClass, self.productName)
# Do not forget back references
if isinstance(appyType, Ref):
bAppyType = appyType.back
bAppyType.init(bAppyType.attribute, appyType.klass,
self.productName)
bAppyType.klass = baseClass
if not isinstance(appyType, Type) or \
(isinstance(appyType, Ref) and appyType.isBack):
continue # Back refs are initialised within fw refs
appyType.init(name, baseClass, appName)
def installApplication(self):
'''Performs some application-wide installation steps.'''

View file

@ -79,10 +79,9 @@ class ToolMixin(BaseMixin):
def _appy_getAllFields(self, contentType):
'''Returns the (translated) names of fields of p_contentType.'''
res = []
for appyType in self.getProductConfig().attributes[contentType]:
if appyType.name != 'title': # Will be included by default.
label = '%s_%s' % (contentType, appyType.name)
res.append((appyType.name, self.translate(label)))
for appyType in self.getAllAppyTypes(className=contentType):
if appyType.name == 'title': continue # Will be included by default.
res.append((appyType.name, self.translate(appyType.labelId)))
# Add object state
res.append(('workflowState', self.translate('workflow_state')))
return res
@ -91,7 +90,7 @@ class ToolMixin(BaseMixin):
'''Returns the (translated) names of fields that may be searched on
objects of type p_contentType (=indexed fields).'''
res = []
for appyType in self.getProductConfig().attributes[contentType]:
for appyType in self.getAllAppyTypes(className=contentType):
if appyType.indexed:
res.append((appyType.name, self.translate(appyType.labelId)))
return res
@ -343,26 +342,23 @@ class ToolMixin(BaseMixin):
def getPublishedObject(self):
'''Gets the currently published object, if its meta_class is among
application classes.'''
rq = self.REQUEST
obj = rq['PUBLISHED']
obj = self.REQUEST['PUBLISHED']
parent = obj.getParentNode()
if parent.id == 'skyn':
obj = parent.getParentNode()
if obj.meta_type in self.getProductConfig().attributes:
return obj
return None
if parent.id == 'skyn': obj = parent.getParentNode()
if obj.meta_type in self.getProductConfig().attributes: return obj
def getAppyClass(self, contentType):
def getAppyClass(self, contentType, wrapper=False):
'''Gets the Appy Python class that is related to p_contentType.'''
# Retrieve first the Archetypes class corresponding to p_ContentType
portalType = self.portal_types.get(contentType)
if not portalType: return None
if not portalType: return
atClassName = portalType.getProperty('content_meta_type')
appName = self.getProductConfig().PROJECTNAME
exec 'from Products.%s.%s import %s as atClass' % \
(appName, atClassName, atClassName)
# Get then the Appy Python class
return atClass.wrapperClass.__bases__[-1]
if wrapper: return atClass.wrapperClass
else: return atClass.wrapperClass.__bases__[-1]
def getCreateMeans(self, contentTypeOrAppyClass):
'''Gets the different ways objects of p_contentTypeOrAppyClass (which
@ -395,6 +391,8 @@ class ToolMixin(BaseMixin):
'''This method checks if the currently logged user can trigger searches
on a given p_rootClass. This is done by calling method "maySearch"
on the class. If no such method exists, we return True.'''
# When editign a form, one should avoid annoying the user with this.
if self.REQUEST['ACTUAL_URL'].endswith('/edit'): return
pythonClass = self.getAppyClass(rootClass)
if 'maySearch' in pythonClass.__dict__:
return pythonClass.maySearch(self.appy())

View file

@ -51,10 +51,7 @@ class BaseMixin:
for appyType in self.getAppyTypes('edit', rq.get('page')):
value = getattr(values, appyType.name, None)
appyType.store(obj, value)
if created:
# Now we have a title for the object, so we derive a nice id
obj.unmarkCreationFlag()
obj._renameAfterCreation(check_auto_id=True)
if created: obj.unmarkCreationFlag()
if previousData:
# Keep in history potential changes on historized fields
self.historizeData(previousData)
@ -499,7 +496,6 @@ class BaseMixin:
'''Are we in debug mode ?'''
for arg in sys.argv:
if arg == 'debug-mode=on': return True
return False
def getClass(self, reloaded=False):
'''Returns the Appy class that dictates self's behaviour.'''
@ -524,30 +520,34 @@ class BaseMixin:
def getAppyType(self, name, asDict=False, className=None):
'''Returns the Appy type named p_name. If no p_className is defined, the
field is supposed to belong to self's class.'''
className = className or self.__class__.__name__
attrs = self.getProductConfig().attributesDict[className]
appyType = attrs.get(name, None)
if appyType and asDict: return appyType.__dict__
return appyType
if not className:
klass = self.__class__.wrapperClass
else:
klass = self.getTool().getAppyClass(className, wrapper=True)
res = getattr(klass, name, None)
if res and asDict: return res.__dict__
return res
def getAllAppyTypes(self, className=None):
'''Returns the ordered list of all Appy types for self's class if
p_className is not specified, or for p_className else.'''
className = className or self.__class__.__name__
return self.getProductConfig().attributes[className]
if not className:
klass = self.__class__.wrapperClass
else:
klass = self.getTool().getAppyClass(className, wrapper=True)
return klass.__fields__
def getGroupedAppyTypes(self, layoutType, pageName):
'''Returns the fields sorted by group. For every field, the appyType
(dict version) is given.'''
res = []
groups = {} # The already encountered groups
# In debug mode, reload the module containing self's class.
debug = self.isDebug()
if debug:
# If param "refresh" is there, we must reload the Python class
refresh = ('refresh' in self.REQUEST)
if refresh:
klass = self.getClass(reloaded=True)
for appyType in self.getAllAppyTypes():
if debug:
appyType = appyType.reload(klass, self)
if refresh: appyType = appyType.reload(klass, self)
if appyType.page.name != pageName: continue
if not appyType.isShowable(self, layoutType): continue
if not appyType.group:
@ -594,16 +594,17 @@ class BaseMixin:
the result.'''
res = []
for name in fieldNames:
appyType = self.getAppyType(name, asDict)
if appyType: res.append(appyType)
elif name == 'state':
if name == 'state':
# We do not return a appyType if the attribute is not a *real*
# attribute, but the workfow state.
res.append({'name': name, 'labelId': 'workflow_state',
'filterable': False})
else:
self.appy().log('Field "%s", used as shownInfo in a Ref, ' \
'was not found.' % name, type='warning')
appyType = self.getAppyType(name, asDict)
if appyType: res.append(appyType)
else:
self.appy().log('Field "%s", used as shownInfo in a Ref, ' \
'was not found.' % name, type='warning')
if addTitle and ('title' not in fieldNames):
res.insert(0, self.getAppyType('title', asDict))
return res

View file

@ -183,10 +183,8 @@ class Tool(ModelClass):
listBoxesMaximumWidth = Integer(default=100)
def refreshSecurity(self): pass # Real method in the wrapper
refreshSecurity = Action(action=refreshSecurity, confirm=True)
# First arg of Ref field below is None because we don't know yet if it will
# link to the predefined User class or a custom class defined in the
# application.
users = Ref(None, multiplicity=(0,None), add=True, link=False,
# Ref(User) will maybe be transformed into Ref(CustomUserClass).
users = Ref(User, multiplicity=(0,None), add=True, link=False,
back=Ref(attribute='toTool'), page='users', queryable=True,
queryFields=('login',), showHeaders=True,
shownInfo=('login', 'title', 'roles'))

View file

@ -670,7 +670,7 @@
isEdit python: layoutType == 'edit';
pageInfo python: phaseInfo['pagesInfo'][page]">
<br/>
<tal:previousButton condition="python: previousPage and pageInfo['showPrevious']">
<tal:previous condition="python: previousPage and pageInfo['showPrevious']">
<tal:button condition="isEdit">
<input type="image" class="imageInput" style="cursor:pointer" name="buttonPrevious"
title="label_previous" i18n:attributes="title" i18n:domain="plone"
@ -683,28 +683,34 @@
title="label_previous" i18n:attributes="title" i18n:domain="plone"/>
</a>
</tal:link>
</tal:previousButton>
</tal:previous>
<tal:saveButton condition="python: isEdit and pageInfo['showSave']">
<tal:save condition="python: isEdit and pageInfo['showSave']">
<input type="image" class="imageInput" style="cursor:pointer" name="buttonOk"
title="label_save" i18n:attributes="title" i18n:domain="plone"
tal:attributes="src string:$portal_url/skyn/save.png"/>
</tal:saveButton>
</tal:save>
<tal:cancelButton condition="python: isEdit and pageInfo['showCancel']">
<tal:cancel condition="python: isEdit and pageInfo['showCancel']">
<input type="image" class="imageInput" style="cursor:pointer" name="buttonCancel"
title="label_cancel" i18n:attributes="title" i18n:domain="plone"
tal:attributes="src string:$portal_url/skyn/cancel.png"/>
</tal:cancelButton>
</tal:cancel>
<tal:editLink condition="python: not isEdit and pageInfo['showOnEdit']">
<tal:edit condition="python: not isEdit and pageInfo['showOnEdit']">
<img title="Edit" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
tal:attributes="onClick python: 'href: window.location=\'%s\'' % contextObj.getUrl(mode='edit', page=page);
src string: $portal_url/skyn/editBig.png"
tal:condition="python: member.has_permission('Modify portal content', contextObj)"/>
</tal:editLink>
</tal:edit>
<tal:nextButton condition="python: nextPage and pageInfo['showNext']">
<tal:refresh condition="contextObj/isDebug">
<img title="Refresh" style="cursor:pointer"
tal:attributes="onClick python: 'href: window.location=\'%s\'' % contextObj.getUrl(mode=layoutType, page=page, refresh='yes');
src string: $portal_url/skyn/refresh.png"/>
</tal:refresh>
<tal:next condition="python: nextPage and pageInfo['showNext']">
<tal:button condition="isEdit">
<input type="image" class="imageInput" style="cursor:pointer" name="buttonNext"
title="label_next" i18n:attributes="title" i18n:domain="plone"
@ -717,7 +723,7 @@
title="label_next" i18n:attributes="title" i18n:domain="plone"/>
</a>
</tal:link>
</tal:nextButton>
</tal:next>
</div>
<tal:comment replace="nothing">

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -59,9 +59,6 @@ catalogMap = {}
# In the following dict, we store, for every Appy class, the ordered list of
# appy types (included inherited ones).
attributes = {<!attributes!>}
# In the following dict, we store, for every Appy class, a dict of appy types
# keyed by their names.
attributesDict = {<!attributesDict!>}
# Application roles
applicationRoles = [<!roles!>]

View file

@ -4,7 +4,7 @@
# ------------------------------------------------------------------------------
import os, os.path, time, mimetypes, random
import appy.pod
from appy.gen import Type, Search, Ref
from appy.gen import Type, Search, Ref, String
from appy.gen.utils import sequenceTypes
from appy.shared.utils import getOsTempFolder, executeCommand, normalizeString
from appy.shared.xml_parser import XmlMarshaller
@ -41,7 +41,6 @@ class AbstractWrapper(object):
elif name == 'typeName': return self.__class__.__bases__[-1].__name__
elif name == 'id': return self.o.id
elif name == 'uid': return self.o.UID()
elif name == 'title': return self.o.Title()
elif name == 'klass': return self.__class__.__bases__[-1]
elif name == 'url': return self.o.absolute_url()
elif name == 'state': return self.o.getState()
@ -57,12 +56,7 @@ class AbstractWrapper(object):
return self.o.portal_membership.getAuthenticatedMember()
elif name == 'fields': return self.o.getAllAppyTypes()
# Now, let's try to return a real attribute.
try:
res = object.__getattribute__(self, name)
except AttributeError, ae:
# Maybe a back reference?
res = self.o.getAppyType(name)
if not res: raise ae
res = object.__getattribute__(self, name)
# If we got an Appy type, return the value of this type for this object
if isinstance(res, Type):
o = self.o