Various improvements in both pod and gen.

This commit is contained in:
Gaetan Delannay 2009-11-06 11:33:56 +01:00
parent 1c0744da85
commit 37cf9e7a4f
27 changed files with 227 additions and 49 deletions

View file

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

View file

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

View file

@ -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,14 +686,18 @@ 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()
self.labels.append(poMsg) if poMsg not in self.labels:
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)

View file

@ -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&macro=queryResult&contentType=%s&flavourNumber=%s' \ '&page=macros&macro=queryResult&contentType=%s&flavourNumber=%s' \
'&searchName=%s&startNumber=' % (self.UID(), contentType, '&searchName=%s&startNumber=' % (self.UID(), contentType,
flavourNumber, searchName) flavourNumber, searchName)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

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

View file

@ -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"
</dt> 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']"/>&nbsp;
<span tal:replace="group/label"/>
</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
View file

0
gen/plone25/templates/FlavourTemplate.py Executable file → Normal file
View file

0
gen/plone25/templates/Install.py Executable file → Normal file
View file

0
gen/plone25/templates/PodTemplate.py Executable file → Normal file
View file

0
gen/plone25/templates/Portlet.pt Executable file → Normal file
View file

0
gen/plone25/templates/ProfileInit.py Executable file → Normal file
View file

6
gen/plone25/templates/Styles.css.dtml Executable file → Normal file
View 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
View file

0
gen/plone25/templates/__init__.py Executable file → Normal file
View file

0
gen/plone25/templates/appyWrappers.py Executable file → Normal file
View file

0
gen/plone25/templates/colophon.pt Executable file → Normal file
View file

0
gen/plone25/templates/config.py Executable file → Normal file
View file

0
gen/plone25/templates/configure.zcml Executable file → Normal file
View file

0
gen/plone25/templates/footer.pt Executable file → Normal file
View file

0
gen/plone25/templates/frontPage.pt Executable file → Normal file
View file

0
gen/plone25/templates/import_steps.xml Executable file → Normal file
View file

0
gen/plone25/templates/tool.gif Executable file → Normal file
View file

Before

Width:  |  Height:  |  Size: 339 B

After

Width:  |  Height:  |  Size: 339 B

View file

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

View file

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

View file

@ -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 = {'<': '&lt;', '>': '&gt;', '&': '&amp;', '"': '&quot;', XML_SPECIAL_CHARS = {'<': '&lt;', '>': '&gt;', '&': '&amp;', '"': '&quot;',
"'": '&apos;'} "'": '&apos;'}
XML_ENTITIES = {'lt': '<', 'gt': '>', 'amp': '&', 'quot': '"', 'apos': "'"}
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class PodError(Exception): class PodError(Exception):

View file

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