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)
|
||||
- Alpha version.
|
||||
|
||||
|
|
|
@ -30,8 +30,9 @@ class Import:
|
|||
|
||||
class Search:
|
||||
'''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.group = group # Searches may be visually grouped in the portlet
|
||||
self.sortBy = sortBy
|
||||
self.limit = limit
|
||||
self.fields = fields # This is a dict whose keys are indexed field
|
||||
|
@ -420,7 +421,8 @@ class State:
|
|||
return res
|
||||
|
||||
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
|
||||
# states: (fromState, toState). But it can also be a tuple of several
|
||||
# (fromState, toState) sub-tuples. This way, you may define only 1
|
||||
|
@ -430,6 +432,8 @@ class Transition:
|
|||
self.action = action
|
||||
self.notify = notify # If not None, it is a method telling who must be
|
||||
# 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):
|
||||
'''If self.condition is specifies a role.'''
|
||||
|
|
|
@ -145,8 +145,9 @@ class Generator(AbstractGenerator):
|
|||
self.copyFile('Portlet.pt', self.repls,
|
||||
destName='%s.pt' % self.portletName, destFolder=self.skinsFolder)
|
||||
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)
|
||||
self.copyFile('IEFixes.css.dtml',self.repls,destFolder=self.skinsFolder)
|
||||
if self.config.minimalistPlone:
|
||||
self.copyFile('colophon.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)
|
||||
# Create i18n labels for searches
|
||||
for search in classDescr.getSearches(classDescr.klass):
|
||||
searchLabelId = '%s_search_%s' % (classDescr.name, search.name)
|
||||
searchDescrId = '%s_descr' % searchLabelId
|
||||
for label in (searchLabelId, searchDescrId):
|
||||
searchLabel = '%s_search_%s' % (classDescr.name, search.name)
|
||||
labels = [searchLabel, '%s_descr' % searchLabel]
|
||||
if search.group:
|
||||
grpLabel = '%s_searchgroup_%s' % (classDescr.name, search.group)
|
||||
labels += [grpLabel, '%s_descr' % grpLabel]
|
||||
for label in labels:
|
||||
default = ' '
|
||||
if label == searchLabelId: default = search.name
|
||||
if label == searchLabel: default = search.name
|
||||
poMsg = PoMessage(label, '', default)
|
||||
poMsg.produceNiceDefault()
|
||||
self.labels.append(poMsg)
|
||||
if poMsg not in self.labels:
|
||||
self.labels.append(poMsg)
|
||||
# Generate the resulting Archetypes class and schema.
|
||||
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.plone25.mixins import AbstractMixin
|
||||
from appy.gen.plone25.mixins.FlavourMixin import FlavourMixin
|
||||
|
@ -98,14 +98,18 @@ class ToolMixin(AbstractMixin):
|
|||
def showPortlet(self):
|
||||
return not self.portal_membership.isAnonymousUser()
|
||||
|
||||
_sortFields = {'title': 'sortable_title'}
|
||||
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
|
||||
with commas) in Plone's portal_catalog. Portal types are from the
|
||||
flavour numbered p_flavourNumber. If p_searchName is specified, it
|
||||
corresponds to a search defined on p_contentType: additional search
|
||||
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 ?
|
||||
if contentType.find(',') != -1:
|
||||
# Several content types are specified
|
||||
|
@ -117,23 +121,32 @@ class ToolMixin(AbstractMixin):
|
|||
portalTypes = contentType
|
||||
params = {'portal_type': portalTypes, 'batch': True}
|
||||
# Manage additional criteria from a search when relevant
|
||||
if searchName:
|
||||
if searchName or search:
|
||||
# In this case, contentType must contain a single content type.
|
||||
appyClass = self.getAppyClass(contentType)
|
||||
# Find the search
|
||||
search = ArchetypesClassDescriptor.getSearch(appyClass, searchName)
|
||||
appyClass = self.getAppyClass(contentType)
|
||||
if searchName:
|
||||
search = ArchetypesClassDescriptor.getSearch(
|
||||
appyClass, searchName)
|
||||
if search:
|
||||
# Add additional search criteria
|
||||
for fieldName, fieldValue in search.fields.iteritems():
|
||||
appyType = getattr(appyClass, fieldName)
|
||||
attrName = fieldName
|
||||
if (appyType.type == 'String') and appyType.isMultiValued():
|
||||
attrName = 'get%s%s' % (fieldName[0].upper(), fieldName[1:])
|
||||
if attrName == 'title': attrName = 'Title'
|
||||
elif attrName == 'description': attrName = 'Description'
|
||||
elif attrName == 'state': attrName = 'review_state'
|
||||
else: attrName = 'get%s%s'% (fieldName[0].upper(),fieldName[1:])
|
||||
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)
|
||||
print 'Number of results per page is', self.getNumberOfResultsPerPage()
|
||||
print 'StartNumber is', startNumber
|
||||
res = SomeObjects(brains, self.getNumberOfResultsPerPage(), startNumber)
|
||||
res.brainsToObjects()
|
||||
print 'Res?', res.totalNumber, res.batchSize, res.startNumber
|
||||
return res.__dict__
|
||||
|
||||
def getResultColumnsNames(self, contentType):
|
||||
|
@ -322,10 +335,43 @@ class ToolMixin(AbstractMixin):
|
|||
return res
|
||||
|
||||
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)
|
||||
return [s.__dict__ for s in \
|
||||
ArchetypesClassDescriptor.getSearches(appyClass)]
|
||||
res = []
|
||||
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):
|
||||
'''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' \
|
||||
'&searchName=%s&startNumber=' % (self.UID(), contentType,
|
||||
flavourNumber, searchName)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -320,7 +320,7 @@ class AbstractMixin:
|
|||
fieldDescr = fieldDescr.__dict__
|
||||
appyType = fieldDescr['appyType']
|
||||
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 \
|
||||
not self.getBRefs(fieldDescr['fieldRel']): return False
|
||||
# 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())
|
||||
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):
|
||||
'''On which page am I? p_isEdit indicates if the current page is an
|
||||
edit or consult view. p_phaseInfo indicates the current phase.'''
|
||||
|
@ -809,6 +825,12 @@ class AbstractMixin:
|
|||
return stateShow(workflow, self._appy_getWrapper())
|
||||
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):
|
||||
'''When an object is created or updated, we must update "add"
|
||||
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>
|
||||
|
||||
<span metal:define-macro="showGroup">
|
||||
<metal:group define-macro="showGroup">
|
||||
<fieldset class="appyGroup">
|
||||
<legend><i tal:define="groupDescription python:contextObj.translate('%s_group_%s' % (contextObj.meta_type, widgetDescr['name']))"
|
||||
tal:content="groupDescription"></i></legend>
|
||||
|
@ -222,9 +222,9 @@
|
|||
</table>
|
||||
</fieldset>
|
||||
<br/>
|
||||
</span>
|
||||
</metal:group>
|
||||
|
||||
<div metal:define-macro="listFields"
|
||||
<metal:fields define-macro="listFields"
|
||||
tal:repeat="widgetDescr python: contextObj.getAppyFields(isEdit, pageName)">
|
||||
|
||||
<tal:displayArchetypesField condition="python: widgetDescr['widgetType'] == 'field'">
|
||||
|
@ -232,19 +232,17 @@
|
|||
<metal:field use-macro="here/skyn/macros/macros/showArchetypesField" />
|
||||
</tal:atField>
|
||||
</tal:displayArchetypesField>
|
||||
|
||||
<tal:displayBackwardRef condition="python: (not isEdit) and (widgetDescr['widgetType'] == 'backField')">
|
||||
<tal:backRef condition="python: widgetDescr['appyType']['backd']['page'] == pageName">
|
||||
<metal:field metal:use-macro="here/skyn/macros/macros/showBackwardField" />
|
||||
</tal:backRef>
|
||||
</tal:displayBackwardRef>
|
||||
|
||||
<tal:displayGroup condition="python: widgetDescr['widgetType'] == 'group'">
|
||||
<tal:displayG condition="python: widgetDescr['page'] == pageName">
|
||||
<metal:group metal:use-macro="here/skyn/macros/macros/showGroup" />
|
||||
</tal:displayG>
|
||||
</tal:displayGroup>
|
||||
</div>
|
||||
</metal:fields>
|
||||
|
||||
<span metal:define-macro="byline"
|
||||
tal:condition="python: site_properties.allowAnonymousViewAbout or not isAnon"
|
||||
|
@ -574,7 +572,10 @@
|
|||
onClick python:'javascript:onSort(\'title\')';"
|
||||
id="arrow_title" style="cursor:pointer"/>
|
||||
<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>
|
||||
|
||||
<tal:comment replace="nothing">Columns corresponding to other fields</tal:comment>
|
||||
|
@ -592,9 +593,9 @@
|
|||
<tal:workflowState condition="python: fieldName == 'workflow_state'">
|
||||
<span tal:replace="python: tool.translate('workflow_state')"/>
|
||||
</tal:workflowState>
|
||||
<input type="text" size="5"
|
||||
<!--input type="text" size="5"
|
||||
tal:attributes="id python: 'filter_%s' % fieldName;
|
||||
onkeyup python:'javascript:onTextEntered(\'%s\')' % fieldName"/>
|
||||
onkeyup python:'javascript:onTextEntered(\'%s\')' % fieldName"/-->
|
||||
</th>
|
||||
</tal:columnHeader>
|
||||
|
||||
|
@ -604,8 +605,8 @@
|
|||
onClick python:'javascript:onSort(\'root_type\')';"
|
||||
id = "arrow_root_type" style="cursor:pointer"/>
|
||||
<span tal:replace="python: tool.translate('root_type')"/>
|
||||
<input type="text" size="5" id="filter_root_type"
|
||||
tal:attributes="onkeyup python:'javascript:onTextEntered(\'root_type\')'"/>
|
||||
<!--input type="text" size="5" id="filter_root_type"
|
||||
tal:attributes="onkeyup python:'javascript:onTextEntered(\'root_type\')'"/-->
|
||||
</th>
|
||||
|
||||
<tal:comment replace="nothing">Column "Actions"</tal:comment>
|
||||
|
@ -716,7 +717,7 @@
|
|||
</metal:states>
|
||||
|
||||
<metal:transitions define-macro="transitions"
|
||||
tal:define="transitions python: contextObj.portal_workflow.getTransitionsFor(contextObj);"
|
||||
tal:define="transitions contextObj/getAppyTransitions"
|
||||
tal:condition="transitions">
|
||||
<form id="triggerTransitionForm" method="post"
|
||||
tal:attributes="action python: contextObj.absolute_url() + '/skyn/do'">
|
||||
|
@ -746,6 +747,35 @@
|
|||
tal:define="queryUrl python: '%s/skyn/query' % appFolder.absolute_url();
|
||||
currentSearch request/search|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>
|
||||
<dt class="portletHeader">
|
||||
<tal:comment replace="nothing">If there is only one flavour, clicking on the portlet
|
||||
|
@ -796,14 +826,40 @@
|
|||
</table>
|
||||
</dt>
|
||||
<tal:comment replace="nothing">Searches for this content type.</tal:comment>
|
||||
<dt class="portletAppyItem portletSearch" tal:repeat="search python: tool.getSearches(rootClass)">
|
||||
<a tal:define="searchLabel python: '%s_search_%s' % (rootClass, search['name']);
|
||||
searchDescr python: '%s_descr' % searchLabel"
|
||||
tal:attributes="href python: '%s?type_name=%s&flavourNumber=%s&search=%s' % (queryUrl, rootClass, flavourNumber, search['name']);
|
||||
title python: tool.translate(searchDescr);
|
||||
class python: test(search['name'] == currentSearch, 'portletCurrent', '')"
|
||||
tal:content="structure python: tool.translate(searchLabel)"></a>
|
||||
</dt>
|
||||
<tal:searchOrGroup repeat="searchOrGroup python: tool.getSearches(rootClass)">
|
||||
<tal:group condition="searchOrGroup/isGroup">
|
||||
<tal:expanded define="group searchOrGroup;
|
||||
expanded python: tool.getCookieValue(group['labelId']) == 'expanded'">
|
||||
<tal:comment replace="nothing">Group name</tal:comment>
|
||||
<dt class="portletAppyItem portletGroup">
|
||||
<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>
|
||||
<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: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 {
|
||||
padding: 0 0 0 0.6em;
|
||||
font-style: italic;
|
||||
font-size: 95%;
|
||||
}
|
||||
.portletGroup {
|
||||
font-variant: small-caps;
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
.portletCurrent {
|
||||
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
|
||||
initiatorUid = self.session['initiator']
|
||||
if initiatorUid:
|
||||
res = self.o.uid_catalog(UID=initiatorUid)[0].getObject().\
|
||||
_appy_getWrapper(force=True)
|
||||
res = self.o.uid_catalog(UID=initiatorUid)[0].getObject().appy()
|
||||
return res
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
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.shared.utils import getOsTempFolder
|
||||
|
||||
|
@ -74,6 +75,8 @@ class AbstractWrapper:
|
|||
tool = property(get_tool)
|
||||
def get_flavour(self): return self.o.getTool().getFlavour(self.o, appy=True)
|
||||
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
|
||||
session = property(get_session)
|
||||
def get_typeName(self): return self.__class__.__bases__[-1].__name__
|
||||
|
@ -89,6 +92,8 @@ class AbstractWrapper:
|
|||
stateLabel = property(get_stateLabel)
|
||||
def get_klass(self): return self.__class__.__bases__[1]
|
||||
klass = property(get_klass)
|
||||
def get_url(self): return self.o.absolute_url()+'/skyn/view'
|
||||
url = property(get_url)
|
||||
|
||||
def link(self, fieldName, obj):
|
||||
'''This method links p_obj to this one through reference field
|
||||
|
@ -226,6 +231,33 @@ class AbstractWrapper:
|
|||
else: logMethod = logger.info
|
||||
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:
|
||||
'''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')
|
||||
XML_SPECIAL_CHARS = {'<': '<', '>': '>', '&': '&', '"': '"',
|
||||
"'": '''}
|
||||
XML_ENTITIES = {'lt': '<', 'gt': '>', 'amp': '&', 'quot': '"', 'apos': "'"}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class PodError(Exception):
|
||||
|
|
|
@ -59,6 +59,10 @@ HTML_ENTITIES = {
|
|||
'ucirc':'û', 'uuml':'ü', 'yacute':'ý', 'thorn':'þ', 'yuml':'ÿ',
|
||||
'euro':'€', 'nbsp':' ', "rsquo":"'", "lsquo":"'", "ldquo":"'",
|
||||
"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:
|
||||
|
|
Loading…
Reference in a new issue