[gen] More work ZPT->PX.

This commit is contained in:
Gaetan Delannay 2013-08-21 12:35:30 +02:00
parent 2e9a832463
commit 34e3a3083e
31 changed files with 3287 additions and 3067 deletions

View file

@ -1,7 +1,6 @@
# ------------------------------------------------------------------------------
import re, os, os.path
import re, os, os.path, base64, urllib
from appy.shared.utils import normalizeText
from appy.px import Px
# Function for creating a Zope object ------------------------------------------
def createObject(folder, id, className, appName, wf=True, noSecurity=False):
@ -10,15 +9,14 @@ def createObject(folder, id, className, appName, wf=True, noSecurity=False):
creation of the config object), computing workflow-related info is not
possible at this time. This is why this function can be called with
p_wf=False.'''
exec 'from Products.%s.%s import %s as ZopeClass' % (appName, className,
className)
exec 'from Products.%s.%s import %s as ZopeClass' % \
(appName, className, className)
# Get the tool. Depends on whether p_folder is a Zope (temp) folder or not.
isFolder = folder.meta_type.endswith('Folder')
tool = isFolder and folder.config or folder.getTool()
user = tool.getUser()
if not noSecurity:
# Check that the user can create objects of className
if folder.meta_type.endswith('Folder'): # Folder or temp folder.
tool = folder.config
else:
tool = folder.getTool()
user = tool.getUser()
# Check that the user can create objects of className.
userRoles = user.getRoles()
allowedRoles=ZopeClass.wrapperClass.getCreators(tool.getProductConfig())
allowed = False
@ -31,267 +29,23 @@ def createObject(folder, id, className, appName, wf=True, noSecurity=False):
raise Unauthorized("User can't create instances of %s" % \
ZopeClass.__name__)
obj = ZopeClass(id)
folder._objects = folder._objects + \
({'id':id, 'meta_type':className},)
folder._objects = folder._objects + ({'id':id, 'meta_type':className},)
folder._setOb(id, obj)
obj = folder._getOb(id) # Important. Else, obj is not really in the folder.
obj.portal_type = className
obj.id = id
obj._at_uid = id
user = obj.getUser()
if not user.getId():
if user.name == 'System Processes':
userId = 'admin' # This is what happens when Zope is starting.
else:
userId = None # Anonymous.
else:
userId = user.getId()
obj.creator = userId or 'Anonymous User'
# If no user object is there, we are at startup, before default User
# instances are created.
userId = user and user.login or 'system'
obj.creator = userId
from DateTime import DateTime
obj.created = DateTime()
obj.modified = obj.created
obj.__ac_local_roles__ = { userId: ['Owner'] } # userId can be None (anon).
obj.__ac_local_roles__ = { userId: ['Owner'] }
if wf: obj.notifyWorkflowCreated()
return obj
# Classes used by edit/view PXs for accessing information ----------------------
class Descr:
'''Abstract class for description classes.'''
def get(self): return self.__dict__
class GroupDescr(Descr):
'''Intermediary, on-the-fly-generated data structure that groups all fields
sharing the same appy.gen.Group instance, that some logged user can
see.'''
# PX that renders a group of fields
pxView = Px('''<p>pxGroupedFields</p>''')
# PX that renders a group of searches
pxViewSearches = Px('''
<x var="expanded=req.get(field.labelId, 'collapsed') == 'expanded'">
<!-- Group name, prefixed by the expand/collapse icon -->
<div class="portletGroup">
<img class="clickable" style="margin-right: 3px" align=":dleft"
id=":'%s_img' % field.labelId"
src=":expanded and url('collapse.gif') or url('expand.gif')"
onclick=":'toggleCookie(%s)' % q(field.labelId)"/>
<x if="not field.translated">:_(field.labelId)</x>
<x if="field.translated">:field.translated</x>
</div>
<!-- Group content -->
<div var="display=expanded and 'display:block' or 'display:none'"
id=":field.labelId" style=":'padding-left: 10px; %s' % display">
<x for="searches in field.widgets">
<x for="elem in searches">
<!-- An inner group within this group -->
<x if="elem['type'] == 'group'"
var2="field=elem">:field.pxViewSearches</x>
<!-- A search -->
<x if="elem['type'] != 'group'" var2="search=elem">:search.pxView</x>
</x>
</x>
</div>
</x>''')
def __init__(self, group, page, metaType, forSearch=False):
self.type = 'group'
# All p_group attributes become self attributes.
for name, value in group.__dict__.iteritems():
if not name.startswith('_'):
setattr(self, name, value)
self.columnsWidths = [col.width for col in group.columns]
self.columnsAligns = [col.align for col in group.columns]
# Names of i18n labels
labelName = self.name
prefix = metaType
if group.label:
if isinstance(group.label, basestring): prefix = group.label
else: # It is a tuple (metaType, name)
if group.label[1]: labelName = group.label[1]
if group.label[0]: prefix = group.label[0]
if forSearch: gp = 'searchgroup'
else: gp = 'group'
self.labelId = '%s_%s_%s' % (prefix, gp, labelName)
self.descrId = self.labelId + '_descr'
self.helpId = self.labelId + '_help'
# The name of the page where the group lies
self.page = page.name
# The widgets belonging to the group that the current user may see.
# They will be stored by m_addWidget below as a list of lists because
# they will be rendered as a table.
self.widgets = [[]]
# PX to user for rendering this group.
self.px = forSearch and self.pxViewSearches or self.pxView
@staticmethod
def addWidget(groupDict, newWidget):
'''Adds p_newWidget into p_groupDict['widgets']. We try first to add
p_newWidget into the last widget row. If it is not possible, we
create a new row.
This method is a static method taking p_groupDict as first param
instead of being an instance method because at this time the object
has already been converted to a dict (for being maniputated within
ZPTs).'''
# Get the last row
widgetRow = groupDict['widgets'][-1]
numberOfColumns = len(groupDict['columnsWidths'])
# Computes the number of columns already filled by widgetRow
rowColumns = 0
for widget in widgetRow: rowColumns += widget['colspan']
freeColumns = numberOfColumns - rowColumns
if freeColumns >= newWidget['colspan']:
# We can add the widget in the last row.
widgetRow.append(newWidget)
else:
if freeColumns:
# Terminate the current row by appending empty cells
for i in range(freeColumns): widgetRow.append('')
# Create a new row
newRow = [newWidget]
groupDict['widgets'].append(newRow)
class PhaseDescr(Descr):
'''Describes a phase.'''
pxPhase = Px('''
<tr var="singlePage=len(phase['pages']) == 1">
<td var="label='%s_phase_%s' % (zobj.meta_type, phase['name'])">
<!-- The title of the phase -->
<div class="portletGroup"
if="not singlePhase and not singlePage">::_(label)</div>
<!-- The page(s) within the phase -->
<x for="aPage in phase['pages']">
<!-- First line: page name and icons -->
<div if="not (singlePhase and singlePage)"
class=":aPage==page and 'portletCurrent portletPage' or \
'portletPage'">
<a href=":zobj.getUrl(page=aPage)">::_('%s_page_%s' % \
(zobj.meta_type, aPage))</a>
<x var="locked=zobj.isLocked(user, aPage);
editable=mayEdit and phase['pagesInfo'][aPage]['showOnEdit']">
<a if="editable and not locked"
href="zobj.getUrl(mode='edit', page=aPage)">
<img src=":url('edit')" title=":_('object_edit')"/></a>
<a if="editable and locked">
<img style="cursor: help"
var="lockDate=tool.formatDate(locked[1]);
lockMap={'user':ztool.getUserName(locked[0]), \
'date':lockDate};
lockMsg=_('page_locked', mapping=lockMap)"
src=":url('locked')" title=":lockMsg"/></a>
<a if="editable and locked and user.has_role('Manager')">
<img class="clickable" title=":_('page_unlock')" src=":url('unlock')"
onclick=":'onUnlockPage(%s,%s)' % \
(q(zobj.UID()), q(aPage))"/></a>
</x>
</div>
<!-- Next lines: links -->
<x var="links=phase['pagesInfo'][aPage].get('links')" if="links">
<div for="link in links">
<a href=":link['url']">:link['title']</a>
</div>
</x>
</x>
</td>
</tr>''')
def __init__(self, name, obj):
self.name = name
self.obj = obj
# The list of names of pages in this phase
self.pages = []
# The list of hidden pages in this phase
self.hiddenPages = []
# The dict below stores infor about every page listed in self.pages.
self.pagesInfo = {}
self.totalNbOfPhases = None
# The following attributes allows to browse, from a given page, to the
# last page of the previous phase and to the first page of the following
# phase if allowed by phase state.
self.previousPhase = None
self.nextPhase = None
self.px = self.pxPhase
def addPageLinks(self, appyType, obj):
'''If p_appyType is a navigable Ref, we must add, within self.pagesInfo,
those links.'''
if appyType.page.name in self.hiddenPages: return
infos = []
for obj in appyType.getValue(obj, type="zobjects"):
infos.append({'title': obj.title, 'url':obj.absolute_url()})
self.pagesInfo[appyType.page.name]['links'] = infos
def addPage(self, appyType, obj, layoutType):
'''Adds page-related information in the phase.'''
# If the page is already there, we have nothing more to do.
if (appyType.page.name in self.pages) or \
(appyType.page.name in self.hiddenPages): return
# Add the page only if it must be shown.
isShowableOnView = appyType.page.isShowable(obj, 'view')
isShowableOnEdit = appyType.page.isShowable(obj, 'edit')
if isShowableOnView or isShowableOnEdit:
# The page must be added.
self.pages.append(appyType.page.name)
# Create the dict about page information and add it in self.pageInfo
pageInfo = {'page': appyType.page,
'showOnView': isShowableOnView,
'showOnEdit': isShowableOnEdit}
pageInfo.update(appyType.page.getInfo(obj, layoutType))
self.pagesInfo[appyType.page.name] = pageInfo
else:
self.hiddenPages.append(appyType.page.name)
def computeNextPrevious(self, allPhases):
'''This method also fills fields "previousPhase" and "nextPhase"
if relevant, based on list of p_allPhases.'''
# Identify previous and next phases
for phaseInfo in allPhases:
if phaseInfo['name'] == self.name:
i = allPhases.index(phaseInfo)
if i > 0:
self.previousPhase = allPhases[i-1]
if i < (len(allPhases)-1):
self.nextPhase = allPhases[i+1]
class SearchDescr(Descr):
'''Describes a Search.'''
# PX for rendering a search.
pxView = Px('''
<div class="portletSearch">
<a href=":'%s?className=%s&amp;search=%s' % \
(queryUrl, rootClass, search['name'])"
class=":search['name'] == currentSearch and 'portletCurrent' or ''"
title=":search['translatedDescr']">:search['translated']</a>
</div>''')
def __init__(self, search, className, tool):
self.search = search
self.name = search.name
self.type = 'search'
self.colspan = search.colspan
if search.translated:
self.translated = search.translated
self.translatedDescr = search.translatedDescr
else:
# The label may be specific in some special cases.
labelDescr = ''
if search.name == 'allSearch':
label = '%s_plural' % className
elif search.name == 'customSearch':
label = 'search_results'
else:
label = '%s_search_%s' % (className, search.name)
labelDescr = label + '_descr'
self.translated = tool.translate(label)
if labelDescr:
self.translatedDescr = tool.translate(labelDescr)
else:
self.translatedDescr = ''
self.px = self.pxView
# ------------------------------------------------------------------------------
upperLetter = re.compile('[A-Z]')
def produceNiceMessage(msg):
@ -448,4 +202,22 @@ def callMethod(obj, method, klass=None, cache=True):
res = method(obj)
rq.methodCache[key] = res
return res
# Functions for manipulating the authentication cookie -------------------------
def readCookie(request):
'''Returns the tuple (login, password) read from the authentication
cookie received in p_request. If no user is logged, its returns
(None, None).'''
cookie = request.get('_appy_', None)
if not cookie: return None, None
cookieValue = base64.decodestring(urllib.unquote(cookie))
if ':' in cookieValue: return cookieValue.split(':')
return None, None
def writeCookie(login, password, request):
'''Encode p_login and p_password into the cookie set in the p_request.'''
cookieValue = base64.encodestring('%s:%s' % (login, password)).rstrip()
cookieValue = urllib.quote(cookieValue)
request.RESPONSE.setCookie('_appy_', cookieValue, path='/')
# ------------------------------------------------------------------------------