Various improvements in both pod and gen.
This commit is contained in:
parent
1c0744da85
commit
37cf9e7a4f
|
@ -1,3 +1,7 @@
|
||||||
|
0.4.1 (2009-11-03)
|
||||||
|
- Ajax framework within appy.gen
|
||||||
|
- More improvements in XHTML->ODT conversion within appy.pod
|
||||||
|
|
||||||
0.4.0 (2009-08-12)
|
0.4.0 (2009-08-12)
|
||||||
- Alpha version.
|
- Alpha version.
|
||||||
|
|
||||||
|
|
|
@ -30,8 +30,9 @@ class Import:
|
||||||
|
|
||||||
class Search:
|
class Search:
|
||||||
'''Used for specifying a search for a given type.'''
|
'''Used for specifying a search for a given type.'''
|
||||||
def __init__(self, name, sortBy='title', limit=None, **fields):
|
def __init__(self, name, group=None, sortBy='', limit=None, **fields):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
self.group = group # Searches may be visually grouped in the portlet
|
||||||
self.sortBy = sortBy
|
self.sortBy = sortBy
|
||||||
self.limit = limit
|
self.limit = limit
|
||||||
self.fields = fields # This is a dict whose keys are indexed field
|
self.fields = fields # This is a dict whose keys are indexed field
|
||||||
|
@ -420,7 +421,8 @@ class State:
|
||||||
return res
|
return res
|
||||||
|
|
||||||
class Transition:
|
class Transition:
|
||||||
def __init__(self, states, condition=True, action=None, notify=None):
|
def __init__(self, states, condition=True, action=None, notify=None,
|
||||||
|
show=True):
|
||||||
self.states = states # In its simpler form, it is a tuple with 2
|
self.states = states # In its simpler form, it is a tuple with 2
|
||||||
# states: (fromState, toState). But it can also be a tuple of several
|
# states: (fromState, toState). But it can also be a tuple of several
|
||||||
# (fromState, toState) sub-tuples. This way, you may define only 1
|
# (fromState, toState) sub-tuples. This way, you may define only 1
|
||||||
|
@ -430,6 +432,8 @@ class Transition:
|
||||||
self.action = action
|
self.action = action
|
||||||
self.notify = notify # If not None, it is a method telling who must be
|
self.notify = notify # If not None, it is a method telling who must be
|
||||||
# notified by email after the transition has been executed.
|
# notified by email after the transition has been executed.
|
||||||
|
self.show = show # If False, the end user will not be able to trigger
|
||||||
|
# the transition. It will only be possible by code.
|
||||||
|
|
||||||
def getUsedRoles(self):
|
def getUsedRoles(self):
|
||||||
'''If self.condition is specifies a role.'''
|
'''If self.condition is specifies a role.'''
|
||||||
|
|
|
@ -145,8 +145,9 @@ class Generator(AbstractGenerator):
|
||||||
self.copyFile('Portlet.pt', self.repls,
|
self.copyFile('Portlet.pt', self.repls,
|
||||||
destName='%s.pt' % self.portletName, destFolder=self.skinsFolder)
|
destName='%s.pt' % self.portletName, destFolder=self.skinsFolder)
|
||||||
self.copyFile('tool.gif', {})
|
self.copyFile('tool.gif', {})
|
||||||
self.copyFile('Styles.css.dtml', self.repls, destFolder=self.skinsFolder,
|
self.copyFile('Styles.css.dtml',self.repls, destFolder=self.skinsFolder,
|
||||||
destName = '%s.css.dtml' % self.applicationName)
|
destName = '%s.css.dtml' % self.applicationName)
|
||||||
|
self.copyFile('IEFixes.css.dtml',self.repls,destFolder=self.skinsFolder)
|
||||||
if self.config.minimalistPlone:
|
if self.config.minimalistPlone:
|
||||||
self.copyFile('colophon.pt', self.repls,destFolder=self.skinsFolder)
|
self.copyFile('colophon.pt', self.repls,destFolder=self.skinsFolder)
|
||||||
self.copyFile('footer.pt', self.repls, destFolder=self.skinsFolder)
|
self.copyFile('footer.pt', self.repls, destFolder=self.skinsFolder)
|
||||||
|
@ -685,13 +686,17 @@ class Generator(AbstractGenerator):
|
||||||
self.labels.append(poMsgPl)
|
self.labels.append(poMsgPl)
|
||||||
# Create i18n labels for searches
|
# Create i18n labels for searches
|
||||||
for search in classDescr.getSearches(classDescr.klass):
|
for search in classDescr.getSearches(classDescr.klass):
|
||||||
searchLabelId = '%s_search_%s' % (classDescr.name, search.name)
|
searchLabel = '%s_search_%s' % (classDescr.name, search.name)
|
||||||
searchDescrId = '%s_descr' % searchLabelId
|
labels = [searchLabel, '%s_descr' % searchLabel]
|
||||||
for label in (searchLabelId, searchDescrId):
|
if search.group:
|
||||||
|
grpLabel = '%s_searchgroup_%s' % (classDescr.name, search.group)
|
||||||
|
labels += [grpLabel, '%s_descr' % grpLabel]
|
||||||
|
for label in labels:
|
||||||
default = ' '
|
default = ' '
|
||||||
if label == searchLabelId: default = search.name
|
if label == searchLabel: default = search.name
|
||||||
poMsg = PoMessage(label, '', default)
|
poMsg = PoMessage(label, '', default)
|
||||||
poMsg.produceNiceDefault()
|
poMsg.produceNiceDefault()
|
||||||
|
if poMsg not in self.labels:
|
||||||
self.labels.append(poMsg)
|
self.labels.append(poMsg)
|
||||||
# Generate the resulting Archetypes class and schema.
|
# Generate the resulting Archetypes class and schema.
|
||||||
self.copyFile('ArchetypesTemplate.py', repls, destName=fileName)
|
self.copyFile('ArchetypesTemplate.py', repls, destName=fileName)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import re, os, os.path
|
import re, os, os.path, Cookie
|
||||||
from appy.gen.utils import FieldDescr, SomeObjects
|
from appy.gen.utils import FieldDescr, SomeObjects
|
||||||
from appy.gen.plone25.mixins import AbstractMixin
|
from appy.gen.plone25.mixins import AbstractMixin
|
||||||
from appy.gen.plone25.mixins.FlavourMixin import FlavourMixin
|
from appy.gen.plone25.mixins.FlavourMixin import FlavourMixin
|
||||||
|
@ -98,14 +98,18 @@ class ToolMixin(AbstractMixin):
|
||||||
def showPortlet(self):
|
def showPortlet(self):
|
||||||
return not self.portal_membership.isAnonymousUser()
|
return not self.portal_membership.isAnonymousUser()
|
||||||
|
|
||||||
|
_sortFields = {'title': 'sortable_title'}
|
||||||
def executeQuery(self, contentType, flavourNumber=1, searchName=None,
|
def executeQuery(self, contentType, flavourNumber=1, searchName=None,
|
||||||
startNumber=0):
|
startNumber=0, search=None):
|
||||||
'''Executes a query on a given p_contentType (or several, separated
|
'''Executes a query on a given p_contentType (or several, separated
|
||||||
with commas) in Plone's portal_catalog. Portal types are from the
|
with commas) in Plone's portal_catalog. Portal types are from the
|
||||||
flavour numbered p_flavourNumber. If p_searchName is specified, it
|
flavour numbered p_flavourNumber. If p_searchName is specified, it
|
||||||
corresponds to a search defined on p_contentType: additional search
|
corresponds to a search defined on p_contentType: additional search
|
||||||
criteria will be added to the query. We will retrieve objects from
|
criteria will be added to the query. We will retrieve objects from
|
||||||
p_startNumber.'''
|
p_startNumber. If p_search is defined, it corresponds to a custom
|
||||||
|
Search instance (instead of a predefined named search like in
|
||||||
|
p_searchName). If both p_searchName and p_search are given, p_search
|
||||||
|
is ignored.'''
|
||||||
# Is there one or several content types ?
|
# Is there one or several content types ?
|
||||||
if contentType.find(',') != -1:
|
if contentType.find(',') != -1:
|
||||||
# Several content types are specified
|
# Several content types are specified
|
||||||
|
@ -117,23 +121,32 @@ class ToolMixin(AbstractMixin):
|
||||||
portalTypes = contentType
|
portalTypes = contentType
|
||||||
params = {'portal_type': portalTypes, 'batch': True}
|
params = {'portal_type': portalTypes, 'batch': True}
|
||||||
# Manage additional criteria from a search when relevant
|
# Manage additional criteria from a search when relevant
|
||||||
if searchName:
|
if searchName or search:
|
||||||
# In this case, contentType must contain a single content type.
|
# In this case, contentType must contain a single content type.
|
||||||
appyClass = self.getAppyClass(contentType)
|
appyClass = self.getAppyClass(contentType)
|
||||||
# Find the search
|
if searchName:
|
||||||
search = ArchetypesClassDescriptor.getSearch(appyClass, searchName)
|
search = ArchetypesClassDescriptor.getSearch(
|
||||||
|
appyClass, searchName)
|
||||||
|
if search:
|
||||||
|
# Add additional search criteria
|
||||||
for fieldName, fieldValue in search.fields.iteritems():
|
for fieldName, fieldValue in search.fields.iteritems():
|
||||||
appyType = getattr(appyClass, fieldName)
|
|
||||||
attrName = fieldName
|
attrName = fieldName
|
||||||
if (appyType.type == 'String') and appyType.isMultiValued():
|
if attrName == 'title': attrName = 'Title'
|
||||||
attrName = 'get%s%s' % (fieldName[0].upper(), fieldName[1:])
|
elif attrName == 'description': attrName = 'Description'
|
||||||
|
elif attrName == 'state': attrName = 'review_state'
|
||||||
|
else: attrName = 'get%s%s'% (fieldName[0].upper(),fieldName[1:])
|
||||||
params[attrName] = fieldValue
|
params[attrName] = fieldValue
|
||||||
|
# Add a sort order if specified
|
||||||
|
sb = search.sortBy
|
||||||
|
if sb:
|
||||||
|
# For field 'title', Plone has created a specific index
|
||||||
|
# 'sortable_title', because index 'Title' is a ZCTextIndex
|
||||||
|
# (for searchability) and can't be used for sorting.
|
||||||
|
if self._sortFields.has_key(sb): sb = self._sortFields[sb]
|
||||||
|
params['sort_on'] = sb
|
||||||
brains = self.portal_catalog.searchResults(**params)
|
brains = self.portal_catalog.searchResults(**params)
|
||||||
print 'Number of results per page is', self.getNumberOfResultsPerPage()
|
|
||||||
print 'StartNumber is', startNumber
|
|
||||||
res = SomeObjects(brains, self.getNumberOfResultsPerPage(), startNumber)
|
res = SomeObjects(brains, self.getNumberOfResultsPerPage(), startNumber)
|
||||||
res.brainsToObjects()
|
res.brainsToObjects()
|
||||||
print 'Res?', res.totalNumber, res.batchSize, res.startNumber
|
|
||||||
return res.__dict__
|
return res.__dict__
|
||||||
|
|
||||||
def getResultColumnsNames(self, contentType):
|
def getResultColumnsNames(self, contentType):
|
||||||
|
@ -322,10 +335,43 @@ class ToolMixin(AbstractMixin):
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getSearches(self, contentType):
|
def getSearches(self, contentType):
|
||||||
'''Returns the searches that are defined for p_contentType.'''
|
'''Returns the list of searches that are defined for p_contentType.
|
||||||
|
Every list item is a dict that contains info about a search or about
|
||||||
|
a group of searches.'''
|
||||||
appyClass = self.getAppyClass(contentType)
|
appyClass = self.getAppyClass(contentType)
|
||||||
return [s.__dict__ for s in \
|
res = []
|
||||||
ArchetypesClassDescriptor.getSearches(appyClass)]
|
visitedGroups = {} # Names of already visited search groups
|
||||||
|
for search in ArchetypesClassDescriptor.getSearches(appyClass):
|
||||||
|
# Determine first group label, we will need it.
|
||||||
|
groupLabel = ''
|
||||||
|
if search.group:
|
||||||
|
groupLabel = '%s_searchgroup_%s' % (contentType, search.group)
|
||||||
|
# Add an item representing the search group if relevant
|
||||||
|
if search.group and (search.group not in visitedGroups):
|
||||||
|
group = {'name': search.group, 'isGroup': True,
|
||||||
|
'labelId': groupLabel, 'searches': [],
|
||||||
|
'label': self.translate(groupLabel),
|
||||||
|
'descr': self.translate('%s_descr' % groupLabel),
|
||||||
|
}
|
||||||
|
res.append(group)
|
||||||
|
visitedGroups[search.group] = group
|
||||||
|
# Add the search itself
|
||||||
|
searchLabel = '%s_search_%s' % (contentType, search.name)
|
||||||
|
dSearch = {'name': search.name, 'isGroup': False,
|
||||||
|
'label': self.translate(searchLabel),
|
||||||
|
'descr': self.translate('%s_descr' % searchLabel)}
|
||||||
|
if search.group:
|
||||||
|
visitedGroups[search.group]['searches'].append(dSearch)
|
||||||
|
else:
|
||||||
|
res.append(dSearch)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def getCookieValue(self, cookieId, default=''):
|
||||||
|
'''Server-side code for getting the value of a cookie entry.'''
|
||||||
|
cookie = Cookie.SimpleCookie(self.REQUEST['HTTP_COOKIE'])
|
||||||
|
cookieValue = cookie.get(cookieId)
|
||||||
|
if cookieValue: return cookieValue.value
|
||||||
|
return default
|
||||||
|
|
||||||
def getQueryUrl(self, contentType, flavourNumber, searchName):
|
def getQueryUrl(self, contentType, flavourNumber, searchName):
|
||||||
'''This method creates the URL that allows to perform an ajax GET
|
'''This method creates the URL that allows to perform an ajax GET
|
||||||
|
@ -335,5 +381,4 @@ class ToolMixin(AbstractMixin):
|
||||||
'&page=macros¯o=queryResult&contentType=%s&flavourNumber=%s' \
|
'&page=macros¯o=queryResult&contentType=%s&flavourNumber=%s' \
|
||||||
'&searchName=%s&startNumber=' % (self.UID(), contentType,
|
'&searchName=%s&startNumber=' % (self.UID(), contentType,
|
||||||
flavourNumber, searchName)
|
flavourNumber, searchName)
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -320,7 +320,7 @@ class AbstractMixin:
|
||||||
fieldDescr = fieldDescr.__dict__
|
fieldDescr = fieldDescr.__dict__
|
||||||
appyType = fieldDescr['appyType']
|
appyType = fieldDescr['appyType']
|
||||||
if isEdit and (appyType['type']=='Ref') and appyType['add']:return False
|
if isEdit and (appyType['type']=='Ref') and appyType['add']:return False
|
||||||
if isEdit and appyType['type']=='Action': return False
|
if isEdit and (appyType['type'] in ('Action', 'Computed')): return False
|
||||||
if (fieldDescr['widgetType'] == 'backField') and \
|
if (fieldDescr['widgetType'] == 'backField') and \
|
||||||
not self.getBRefs(fieldDescr['fieldRel']): return False
|
not self.getBRefs(fieldDescr['fieldRel']): return False
|
||||||
# Do not show field if it is optional and not selected in flavour
|
# Do not show field if it is optional and not selected in flavour
|
||||||
|
@ -405,6 +405,22 @@ class AbstractMixin:
|
||||||
res.append(StateDescr(stateName, stateStatus).get())
|
res.append(StateDescr(stateName, stateStatus).get())
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def getAppyTransitions(self):
|
||||||
|
'''Returns the transitions that the user can trigger on p_self.'''
|
||||||
|
transitions = self.portal_workflow.getTransitionsFor(self)
|
||||||
|
res = []
|
||||||
|
if transitions:
|
||||||
|
# Retrieve the corresponding Appy transition, to check if the user
|
||||||
|
# may view it.
|
||||||
|
workflow = self.getWorkflow(appy=True)
|
||||||
|
if not workflow: return transitions
|
||||||
|
for transition in transitions:
|
||||||
|
# Get the corresponding Appy transition
|
||||||
|
appyTr = workflow._transitionsMapping[transition['id']]
|
||||||
|
if self._appy_showTransition(workflow, appyTr.show):
|
||||||
|
res.append(transition)
|
||||||
|
return res
|
||||||
|
|
||||||
def getAppyPage(self, isEdit, phaseInfo, appyName=True):
|
def getAppyPage(self, isEdit, phaseInfo, appyName=True):
|
||||||
'''On which page am I? p_isEdit indicates if the current page is an
|
'''On which page am I? p_isEdit indicates if the current page is an
|
||||||
edit or consult view. p_phaseInfo indicates the current phase.'''
|
edit or consult view. p_phaseInfo indicates the current phase.'''
|
||||||
|
@ -809,6 +825,12 @@ class AbstractMixin:
|
||||||
return stateShow(workflow, self._appy_getWrapper())
|
return stateShow(workflow, self._appy_getWrapper())
|
||||||
else: return stateShow
|
else: return stateShow
|
||||||
|
|
||||||
|
def _appy_showTransition(self, workflow, transitionShow):
|
||||||
|
'''Must I show a transition whose "show value" is p_transitionShow?'''
|
||||||
|
if callable(transitionShow):
|
||||||
|
return transitionShow(workflow, self._appy_getWrapper())
|
||||||
|
else: return transitionShow
|
||||||
|
|
||||||
def _appy_managePermissions(self):
|
def _appy_managePermissions(self):
|
||||||
'''When an object is created or updated, we must update "add"
|
'''When an object is created or updated, we must update "add"
|
||||||
permissions accordingly: if the object is a folder, we must set on
|
permissions accordingly: if the object is a folder, we must set on
|
||||||
|
|
|
@ -197,7 +197,7 @@
|
||||||
<div metal:use-macro="here/skyn/ref/macros/showReference" />
|
<div metal:use-macro="here/skyn/ref/macros/showReference" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span metal:define-macro="showGroup">
|
<metal:group define-macro="showGroup">
|
||||||
<fieldset class="appyGroup">
|
<fieldset class="appyGroup">
|
||||||
<legend><i tal:define="groupDescription python:contextObj.translate('%s_group_%s' % (contextObj.meta_type, widgetDescr['name']))"
|
<legend><i tal:define="groupDescription python:contextObj.translate('%s_group_%s' % (contextObj.meta_type, widgetDescr['name']))"
|
||||||
tal:content="groupDescription"></i></legend>
|
tal:content="groupDescription"></i></legend>
|
||||||
|
@ -222,9 +222,9 @@
|
||||||
</table>
|
</table>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<br/>
|
<br/>
|
||||||
</span>
|
</metal:group>
|
||||||
|
|
||||||
<div metal:define-macro="listFields"
|
<metal:fields define-macro="listFields"
|
||||||
tal:repeat="widgetDescr python: contextObj.getAppyFields(isEdit, pageName)">
|
tal:repeat="widgetDescr python: contextObj.getAppyFields(isEdit, pageName)">
|
||||||
|
|
||||||
<tal:displayArchetypesField condition="python: widgetDescr['widgetType'] == 'field'">
|
<tal:displayArchetypesField condition="python: widgetDescr['widgetType'] == 'field'">
|
||||||
|
@ -232,19 +232,17 @@
|
||||||
<metal:field use-macro="here/skyn/macros/macros/showArchetypesField" />
|
<metal:field use-macro="here/skyn/macros/macros/showArchetypesField" />
|
||||||
</tal:atField>
|
</tal:atField>
|
||||||
</tal:displayArchetypesField>
|
</tal:displayArchetypesField>
|
||||||
|
|
||||||
<tal:displayBackwardRef condition="python: (not isEdit) and (widgetDescr['widgetType'] == 'backField')">
|
<tal:displayBackwardRef condition="python: (not isEdit) and (widgetDescr['widgetType'] == 'backField')">
|
||||||
<tal:backRef condition="python: widgetDescr['appyType']['backd']['page'] == pageName">
|
<tal:backRef condition="python: widgetDescr['appyType']['backd']['page'] == pageName">
|
||||||
<metal:field metal:use-macro="here/skyn/macros/macros/showBackwardField" />
|
<metal:field metal:use-macro="here/skyn/macros/macros/showBackwardField" />
|
||||||
</tal:backRef>
|
</tal:backRef>
|
||||||
</tal:displayBackwardRef>
|
</tal:displayBackwardRef>
|
||||||
|
|
||||||
<tal:displayGroup condition="python: widgetDescr['widgetType'] == 'group'">
|
<tal:displayGroup condition="python: widgetDescr['widgetType'] == 'group'">
|
||||||
<tal:displayG condition="python: widgetDescr['page'] == pageName">
|
<tal:displayG condition="python: widgetDescr['page'] == pageName">
|
||||||
<metal:group metal:use-macro="here/skyn/macros/macros/showGroup" />
|
<metal:group metal:use-macro="here/skyn/macros/macros/showGroup" />
|
||||||
</tal:displayG>
|
</tal:displayG>
|
||||||
</tal:displayGroup>
|
</tal:displayGroup>
|
||||||
</div>
|
</metal:fields>
|
||||||
|
|
||||||
<span metal:define-macro="byline"
|
<span metal:define-macro="byline"
|
||||||
tal:condition="python: site_properties.allowAnonymousViewAbout or not isAnon"
|
tal:condition="python: site_properties.allowAnonymousViewAbout or not isAnon"
|
||||||
|
@ -574,7 +572,10 @@
|
||||||
onClick python:'javascript:onSort(\'title\')';"
|
onClick python:'javascript:onSort(\'title\')';"
|
||||||
id="arrow_title" style="cursor:pointer"/>
|
id="arrow_title" style="cursor:pointer"/>
|
||||||
<span tal:content="python: tool.translate('ref_name')"/>
|
<span tal:content="python: tool.translate('ref_name')"/>
|
||||||
<input id="filter_title" type="text" size="5" onkeyup="javascript:onTextEntered('title')"/>
|
<!--input id="filter_title" type="text" size="5" onkeyup="javascript:onTextEntered('title')"/-->
|
||||||
|
<tal:comment replace="nothing">Input fields like this have been commented out because they will
|
||||||
|
be replaced by Ajax server- searches that will be more relevant (the current Javascript search
|
||||||
|
is limited to the batch, which has little interest).</tal:comment>
|
||||||
</th>
|
</th>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Columns corresponding to other fields</tal:comment>
|
<tal:comment replace="nothing">Columns corresponding to other fields</tal:comment>
|
||||||
|
@ -592,9 +593,9 @@
|
||||||
<tal:workflowState condition="python: fieldName == 'workflow_state'">
|
<tal:workflowState condition="python: fieldName == 'workflow_state'">
|
||||||
<span tal:replace="python: tool.translate('workflow_state')"/>
|
<span tal:replace="python: tool.translate('workflow_state')"/>
|
||||||
</tal:workflowState>
|
</tal:workflowState>
|
||||||
<input type="text" size="5"
|
<!--input type="text" size="5"
|
||||||
tal:attributes="id python: 'filter_%s' % fieldName;
|
tal:attributes="id python: 'filter_%s' % fieldName;
|
||||||
onkeyup python:'javascript:onTextEntered(\'%s\')' % fieldName"/>
|
onkeyup python:'javascript:onTextEntered(\'%s\')' % fieldName"/-->
|
||||||
</th>
|
</th>
|
||||||
</tal:columnHeader>
|
</tal:columnHeader>
|
||||||
|
|
||||||
|
@ -604,8 +605,8 @@
|
||||||
onClick python:'javascript:onSort(\'root_type\')';"
|
onClick python:'javascript:onSort(\'root_type\')';"
|
||||||
id = "arrow_root_type" style="cursor:pointer"/>
|
id = "arrow_root_type" style="cursor:pointer"/>
|
||||||
<span tal:replace="python: tool.translate('root_type')"/>
|
<span tal:replace="python: tool.translate('root_type')"/>
|
||||||
<input type="text" size="5" id="filter_root_type"
|
<!--input type="text" size="5" id="filter_root_type"
|
||||||
tal:attributes="onkeyup python:'javascript:onTextEntered(\'root_type\')'"/>
|
tal:attributes="onkeyup python:'javascript:onTextEntered(\'root_type\')'"/-->
|
||||||
</th>
|
</th>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Column "Actions"</tal:comment>
|
<tal:comment replace="nothing">Column "Actions"</tal:comment>
|
||||||
|
@ -716,7 +717,7 @@
|
||||||
</metal:states>
|
</metal:states>
|
||||||
|
|
||||||
<metal:transitions define-macro="transitions"
|
<metal:transitions define-macro="transitions"
|
||||||
tal:define="transitions python: contextObj.portal_workflow.getTransitionsFor(contextObj);"
|
tal:define="transitions contextObj/getAppyTransitions"
|
||||||
tal:condition="transitions">
|
tal:condition="transitions">
|
||||||
<form id="triggerTransitionForm" method="post"
|
<form id="triggerTransitionForm" method="post"
|
||||||
tal:attributes="action python: contextObj.absolute_url() + '/skyn/do'">
|
tal:attributes="action python: contextObj.absolute_url() + '/skyn/do'">
|
||||||
|
@ -746,6 +747,35 @@
|
||||||
tal:define="queryUrl python: '%s/skyn/query' % appFolder.absolute_url();
|
tal:define="queryUrl python: '%s/skyn/query' % appFolder.absolute_url();
|
||||||
currentSearch request/search|nothing;
|
currentSearch request/search|nothing;
|
||||||
currentType request/type_name|nothing;">
|
currentType request/type_name|nothing;">
|
||||||
|
<script language="javascript">
|
||||||
|
<!--
|
||||||
|
function toggleSearchGroup(groupId) {
|
||||||
|
// What is the state of this toggle?
|
||||||
|
var state = readCookie(groupId);
|
||||||
|
if ((state != 'collapsed') && (state != 'expanded')) {
|
||||||
|
// No cookie yet, create it.
|
||||||
|
createCookie(groupId, 'expanded');
|
||||||
|
state = 'expanded';
|
||||||
|
}
|
||||||
|
var group = document.getElementById(groupId);
|
||||||
|
var displayValue = 'none';
|
||||||
|
var newState = 'collapsed';
|
||||||
|
var imgSrc = 'skyn/expand.gif';
|
||||||
|
if (state == 'collapsed') {
|
||||||
|
// Expand the group
|
||||||
|
displayValue = 'block';
|
||||||
|
imgSrc = 'skyn/collapse.gif';
|
||||||
|
newState = 'expanded';
|
||||||
|
}
|
||||||
|
// Update group visibility and img
|
||||||
|
group.style.display = displayValue;
|
||||||
|
var img = document.getElementById(groupId + '_img');
|
||||||
|
img.src = imgSrc;
|
||||||
|
// Inverse the cookie value
|
||||||
|
createCookie(groupId, newState);
|
||||||
|
}
|
||||||
|
-->
|
||||||
|
</script>
|
||||||
<tal:comment replace="nothing">Portlet title, with link to tool.</tal:comment>
|
<tal:comment replace="nothing">Portlet title, with link to tool.</tal:comment>
|
||||||
<dt class="portletHeader">
|
<dt class="portletHeader">
|
||||||
<tal:comment replace="nothing">If there is only one flavour, clicking on the portlet
|
<tal:comment replace="nothing">If there is only one flavour, clicking on the portlet
|
||||||
|
@ -796,14 +826,40 @@
|
||||||
</table>
|
</table>
|
||||||
</dt>
|
</dt>
|
||||||
<tal:comment replace="nothing">Searches for this content type.</tal:comment>
|
<tal:comment replace="nothing">Searches for this content type.</tal:comment>
|
||||||
<dt class="portletAppyItem portletSearch" tal:repeat="search python: tool.getSearches(rootClass)">
|
<tal:searchOrGroup repeat="searchOrGroup python: tool.getSearches(rootClass)">
|
||||||
<a tal:define="searchLabel python: '%s_search_%s' % (rootClass, search['name']);
|
<tal:group condition="searchOrGroup/isGroup">
|
||||||
searchDescr python: '%s_descr' % searchLabel"
|
<tal:expanded define="group searchOrGroup;
|
||||||
tal:attributes="href python: '%s?type_name=%s&flavourNumber=%s&search=%s' % (queryUrl, rootClass, flavourNumber, search['name']);
|
expanded python: tool.getCookieValue(group['labelId']) == 'expanded'">
|
||||||
title python: tool.translate(searchDescr);
|
<tal:comment replace="nothing">Group name</tal:comment>
|
||||||
class python: test(search['name'] == currentSearch, 'portletCurrent', '')"
|
<dt class="portletAppyItem portletGroup">
|
||||||
tal:content="structure python: tool.translate(searchLabel)"></a>
|
<img align="left" style="cursor:pointer"
|
||||||
|
tal:attributes="id python: '%s_img' % group['labelId'];
|
||||||
|
src python:test(expanded, 'skyn/collapse.gif', 'skyn/expand.gif');
|
||||||
|
onClick python:'javascript:toggleSearchGroup(\'%s\')' % group['labelId']"/>
|
||||||
|
<span tal:replace="group/label"/>
|
||||||
</dt>
|
</dt>
|
||||||
|
<tal:comment replace="nothing">Group searches</tal:comment>
|
||||||
|
<div tal:attributes="id group/labelId;
|
||||||
|
style python:test(expanded, 'display:block', 'display:none')">
|
||||||
|
<dt class="portletAppyItem portletSearch" tal:repeat="search group/searches">
|
||||||
|
<a tal:attributes="href python: '%s?type_name=%s&flavourNumber=%s&search=%s' % (queryUrl, rootClass, flavourNumber, search['name']);
|
||||||
|
title search/descr;
|
||||||
|
class python: test(search['name'] == currentSearch, 'portletCurrent', '');"
|
||||||
|
tal:content="structure search/label"></a>
|
||||||
|
</dt>
|
||||||
|
</div>
|
||||||
|
</tal:expanded>
|
||||||
|
</tal:group>
|
||||||
|
<dt tal:define="search searchOrGroup" tal:condition="not: searchOrGroup/isGroup"
|
||||||
|
class="portletAppyItem portletSearch">
|
||||||
|
|
||||||
|
<a tal:attributes="href python: '%s?type_name=%s&flavourNumber=%s&search=%s' % (queryUrl, rootClass, flavourNumber, search['name']);
|
||||||
|
title search/descr;
|
||||||
|
class python: test(search['name'] == currentSearch, 'portletCurrent', '');
|
||||||
|
id search/group"
|
||||||
|
tal:content="structure search/label"></a>
|
||||||
|
</dt>
|
||||||
|
</tal:searchOrGroup>
|
||||||
</tal:section>
|
</tal:section>
|
||||||
|
|
||||||
<tal:comment replace="nothing">All objects in flavour</tal:comment>
|
<tal:comment replace="nothing">All objects in flavour</tal:comment>
|
||||||
|
|
0
gen/plone25/templates/ArchetypesTemplate.py
Executable file → Normal file
0
gen/plone25/templates/ArchetypesTemplate.py
Executable file → Normal file
0
gen/plone25/templates/FlavourTemplate.py
Executable file → Normal file
0
gen/plone25/templates/FlavourTemplate.py
Executable file → Normal file
0
gen/plone25/templates/Install.py
Executable file → Normal file
0
gen/plone25/templates/Install.py
Executable file → Normal file
0
gen/plone25/templates/PodTemplate.py
Executable file → Normal file
0
gen/plone25/templates/PodTemplate.py
Executable file → Normal file
0
gen/plone25/templates/Portlet.pt
Executable file → Normal file
0
gen/plone25/templates/Portlet.pt
Executable file → Normal file
0
gen/plone25/templates/ProfileInit.py
Executable file → Normal file
0
gen/plone25/templates/ProfileInit.py
Executable file → Normal file
6
gen/plone25/templates/Styles.css.dtml
Executable file → Normal file
6
gen/plone25/templates/Styles.css.dtml
Executable file → Normal file
|
@ -230,6 +230,12 @@ fieldset {
|
||||||
.portletSearch {
|
.portletSearch {
|
||||||
padding: 0 0 0 0.6em;
|
padding: 0 0 0 0.6em;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
|
font-size: 95%;
|
||||||
|
}
|
||||||
|
.portletGroup {
|
||||||
|
font-variant: small-caps;
|
||||||
|
font-weight: bold;
|
||||||
|
font-style: normal;
|
||||||
}
|
}
|
||||||
.portletCurrent {
|
.portletCurrent {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
0
gen/plone25/templates/ToolTemplate.py
Executable file → Normal file
0
gen/plone25/templates/ToolTemplate.py
Executable file → Normal file
0
gen/plone25/templates/__init__.py
Executable file → Normal file
0
gen/plone25/templates/__init__.py
Executable file → Normal file
0
gen/plone25/templates/appyWrappers.py
Executable file → Normal file
0
gen/plone25/templates/appyWrappers.py
Executable file → Normal file
0
gen/plone25/templates/colophon.pt
Executable file → Normal file
0
gen/plone25/templates/colophon.pt
Executable file → Normal file
0
gen/plone25/templates/config.py
Executable file → Normal file
0
gen/plone25/templates/config.py
Executable file → Normal file
0
gen/plone25/templates/configure.zcml
Executable file → Normal file
0
gen/plone25/templates/configure.zcml
Executable file → Normal file
0
gen/plone25/templates/footer.pt
Executable file → Normal file
0
gen/plone25/templates/footer.pt
Executable file → Normal file
0
gen/plone25/templates/frontPage.pt
Executable file → Normal file
0
gen/plone25/templates/frontPage.pt
Executable file → Normal file
0
gen/plone25/templates/import_steps.xml
Executable file → Normal file
0
gen/plone25/templates/import_steps.xml
Executable file → Normal file
0
gen/plone25/templates/tool.gif
Executable file → Normal file
0
gen/plone25/templates/tool.gif
Executable file → Normal file
Before Width: | Height: | Size: 339 B After Width: | Height: | Size: 339 B |
|
@ -7,7 +7,6 @@ class ToolWrapper:
|
||||||
res = None
|
res = None
|
||||||
initiatorUid = self.session['initiator']
|
initiatorUid = self.session['initiator']
|
||||||
if initiatorUid:
|
if initiatorUid:
|
||||||
res = self.o.uid_catalog(UID=initiatorUid)[0].getObject().\
|
res = self.o.uid_catalog(UID=initiatorUid)[0].getObject().appy()
|
||||||
_appy_getWrapper(force=True)
|
|
||||||
return res
|
return res
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
developer the real classes used by the underlying web framework.'''
|
developer the real classes used by the underlying web framework.'''
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import time, os.path, mimetypes
|
import time, os.path, mimetypes, unicodedata
|
||||||
|
from appy.gen import Search
|
||||||
from appy.gen.utils import sequenceTypes
|
from appy.gen.utils import sequenceTypes
|
||||||
from appy.shared.utils import getOsTempFolder
|
from appy.shared.utils import getOsTempFolder
|
||||||
|
|
||||||
|
@ -74,6 +75,8 @@ class AbstractWrapper:
|
||||||
tool = property(get_tool)
|
tool = property(get_tool)
|
||||||
def get_flavour(self): return self.o.getTool().getFlavour(self.o, appy=True)
|
def get_flavour(self): return self.o.getTool().getFlavour(self.o, appy=True)
|
||||||
flavour = property(get_flavour)
|
flavour = property(get_flavour)
|
||||||
|
def get_request(self): return self.o.REQUEST
|
||||||
|
request = property(get_request)
|
||||||
def get_session(self): return self.o.REQUEST.SESSION
|
def get_session(self): return self.o.REQUEST.SESSION
|
||||||
session = property(get_session)
|
session = property(get_session)
|
||||||
def get_typeName(self): return self.__class__.__bases__[-1].__name__
|
def get_typeName(self): return self.__class__.__bases__[-1].__name__
|
||||||
|
@ -89,6 +92,8 @@ class AbstractWrapper:
|
||||||
stateLabel = property(get_stateLabel)
|
stateLabel = property(get_stateLabel)
|
||||||
def get_klass(self): return self.__class__.__bases__[1]
|
def get_klass(self): return self.__class__.__bases__[1]
|
||||||
klass = property(get_klass)
|
klass = property(get_klass)
|
||||||
|
def get_url(self): return self.o.absolute_url()+'/skyn/view'
|
||||||
|
url = property(get_url)
|
||||||
|
|
||||||
def link(self, fieldName, obj):
|
def link(self, fieldName, obj):
|
||||||
'''This method links p_obj to this one through reference field
|
'''This method links p_obj to this one through reference field
|
||||||
|
@ -226,6 +231,33 @@ class AbstractWrapper:
|
||||||
else: logMethod = logger.info
|
else: logMethod = logger.info
|
||||||
logMethod(message)
|
logMethod(message)
|
||||||
|
|
||||||
|
def normalize(self, s):
|
||||||
|
'''Returns a version of string p_s whose special chars have been
|
||||||
|
replaced with normal chars.'''
|
||||||
|
return unicodedata.normalize('NFKD', s).encode("ascii","ignore")
|
||||||
|
|
||||||
|
def search(self, klass, sortBy='', **fields):
|
||||||
|
'''Searches objects of p_klass. p_sortBy must be the name of an indexed
|
||||||
|
field (declared with indexed=True); every param in p_fields must
|
||||||
|
take the name of an indexed field and take a possible value of this
|
||||||
|
field.'''
|
||||||
|
# Find the content type corresponding to p_klass
|
||||||
|
flavour = self.flavour
|
||||||
|
contentType = flavour.o.getPortalType(klass)
|
||||||
|
# Create the Search object
|
||||||
|
search = Search('customSearch', sortBy=sortBy, **fields)
|
||||||
|
res = self.tool.o.executeQuery(contentType,flavour.number,search=search)
|
||||||
|
return [o.appy() for o in res['objects']]
|
||||||
|
|
||||||
|
def reindex(self):
|
||||||
|
'''Asks a direct object reindexing. In most cases you don't have to
|
||||||
|
reindex objects "manually" with this method. When an object is
|
||||||
|
modified after some user action has been performed, Appy reindexes
|
||||||
|
this object automatically. But if your code modifies other objects,
|
||||||
|
Appy may not know that they must be reindexed, too. So use this
|
||||||
|
method in those cases.'''
|
||||||
|
self.o.reindexObject()
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class FileWrapper:
|
class FileWrapper:
|
||||||
'''When you get, from an appy object, the value of a File attribute, you
|
'''When you get, from an appy object, the value of a File attribute, you
|
||||||
|
|
|
@ -29,6 +29,7 @@ XHTML_INNER_TAGS = ('b', 'i', 'u', 'em')
|
||||||
XHTML_UNSTYLABLE_TAGS = XHTML_LISTS + ('li', 'a')
|
XHTML_UNSTYLABLE_TAGS = XHTML_LISTS + ('li', 'a')
|
||||||
XML_SPECIAL_CHARS = {'<': '<', '>': '>', '&': '&', '"': '"',
|
XML_SPECIAL_CHARS = {'<': '<', '>': '>', '&': '&', '"': '"',
|
||||||
"'": '''}
|
"'": '''}
|
||||||
|
XML_ENTITIES = {'lt': '<', 'gt': '>', 'amp': '&', 'quot': '"', 'apos': "'"}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class PodError(Exception):
|
class PodError(Exception):
|
||||||
|
|
|
@ -59,6 +59,10 @@ HTML_ENTITIES = {
|
||||||
'ucirc':'û', 'uuml':'ü', 'yacute':'ý', 'thorn':'þ', 'yuml':'ÿ',
|
'ucirc':'û', 'uuml':'ü', 'yacute':'ý', 'thorn':'þ', 'yuml':'ÿ',
|
||||||
'euro':'€', 'nbsp':' ', "rsquo":"'", "lsquo":"'", "ldquo":"'",
|
'euro':'€', 'nbsp':' ', "rsquo":"'", "lsquo":"'", "ldquo":"'",
|
||||||
"rdquo":"'", 'ndash': ' ', 'oelig':'oe', 'quot': "'", 'mu': 'µ'}
|
"rdquo":"'", 'ndash': ' ', 'oelig':'oe', 'quot': "'", 'mu': 'µ'}
|
||||||
|
import htmlentitydefs
|
||||||
|
for k, v in htmlentitydefs.entitydefs.iteritems():
|
||||||
|
if not HTML_ENTITIES.has_key(k) and not XML_ENTITIES.has_key(k):
|
||||||
|
HTML_ENTITIES[k] = ''
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class Entity:
|
class Entity:
|
||||||
|
|
Loading…
Reference in a new issue