1112 lines
41 KiB
Python
1112 lines
41 KiB
Python
# -*- coding: iso-8859-15 -*-
|
|
|
|
|
|
# Glasnost
|
|
# By: Odile Bénassy <obenassy@entrouvert.com>
|
|
# Romain Chantereau <rchantereau@entrouvert.com>
|
|
# Nicolas Clapiès <nclapies@easter-eggs.org>
|
|
# Pierre-Antoine Dejace <padejace@entrouvert.be>
|
|
# Thierry Dulieu <tdulieu@easter-eggs.com>
|
|
# Florent Monnier <monnier@codelutin.com>
|
|
# Cédric Musso <cmusso@easter-eggs.org>
|
|
# Frédéric Péters <fpeters@entrouvert.be>
|
|
# Benjamin Poussin <poussin@codelutin.com>
|
|
# Emmanuel Raviart <eraviart@entrouvert.com>
|
|
# Sébastien Régnier <regnier@codelutin.com>
|
|
# Emmanuel Saracco <esaracco@easter-eggs.com>
|
|
#
|
|
# Copyright (C) 2000, 2001 Easter-eggs & Emmanuel Raviart
|
|
# Copyright (C) 2002 Odile Bénassy, Code Lutin, Thierry Dulieu, Easter-eggs,
|
|
# Entr'ouvert, Frédéric Péters, Benjamin Poussin, Emmanuel Raviart,
|
|
# Emmanuel Saracco & Théridion
|
|
# Copyright (C) 2003 Odile Bénassy, Romain Chantereau, Nicolas Clapiès,
|
|
# Code Lutin, Pierre-Antoine Dejace, Thierry Dulieu, Easter-eggs,
|
|
# Entr'ouvert, Florent Monnier, Cédric Musso, Ouvaton, Frédéric Péters,
|
|
# Benjamin Poussin, Rodolphe Quiédeville, Emmanuel Raviart, Sébastien
|
|
# Régnier, Emmanuel Saracco, Théridion & Vecam
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
|
|
__doc__ = """Glasnost Objects Web"""
|
|
|
|
__version__ = '$Revision$'[11:-2]
|
|
|
|
|
|
import md5
|
|
import time
|
|
import types
|
|
|
|
import glasnost.common.context as context
|
|
import glasnost.common.faults as faults
|
|
import glasnost.common.slots as slots
|
|
import glasnost.common.system as system
|
|
import glasnost.common.translation as translation
|
|
import glasnost.common.tools_new as commonTools
|
|
import glasnost.common.xhtmlgenerator as X
|
|
|
|
from glasnost.proxy.DispatcherProxy import MultiCall
|
|
|
|
import kinds # Do not remove!
|
|
import things
|
|
from tools import *
|
|
import WebAPI
|
|
|
|
|
|
register = things.register
|
|
|
|
|
|
class BaseObjectWebMixin(things.ThingMixin):
|
|
id_kind_stateInEditMode = 'hidden'
|
|
id_kind_stateInViewMode = 'hidden'
|
|
id_kind_widget_fieldLabel = N_('ID')
|
|
id_kind_widgetName = 'InputText'
|
|
|
|
serverRole_kind_stateInEditMode = 'hidden'
|
|
serverRole_kind_stateInViewMode = 'hidden'
|
|
|
|
version_kind_defaultValue = 0
|
|
version_kind_stateInEditMode = 'hidden'
|
|
version_kind_stateInViewMode = 'hidden'
|
|
version_kind_widget_fieldLabel = N_('Version Number')
|
|
version_kind_widgetName = 'InputText'
|
|
|
|
def getEmbeddedViewLayout(self):
|
|
fields = {}
|
|
self.makeFieldsFromInstance(fields)
|
|
self.repairFields(fields)
|
|
return self.getCompactViewLayout(fields, parentSlot = None)
|
|
|
|
def getLabelTranslated(self, destinationLanguages = None, multiCall = None):
|
|
translationsProxy = getProxyForServerRole('translations')
|
|
if translationsProxy:
|
|
if not destinationLanguages:
|
|
destinationLanguages = context.getVar('destinationLanguages')
|
|
try:
|
|
return translationsProxy.getTranslation(
|
|
self.getLabel(), self.getId(), 'self.getLabel()',
|
|
self.getLabelLanguage(), destinationLanguages,
|
|
multiCall = multiCall)
|
|
except faults.UnknownStringDigest:
|
|
return self.getLabel()
|
|
else:
|
|
return self.getLabel()
|
|
|
|
def getSlotToModifyNames(self, parentSlot = None):
|
|
return None # Modify all slots.
|
|
|
|
def getViewLayoutSlotNames(self, fields, parentSlot = None):
|
|
slotNames = things.ThingMixin.getViewLayoutSlotNames(
|
|
self, fields, parentSlot = parentSlot)
|
|
userToken = context.getVar('userToken', default = '')
|
|
|
|
if context.getVar('useCompactLayout', default = 0) or (
|
|
self.id and not self.getWeb().canModifyObject(self.id)):
|
|
slotNames = slotNames[:]
|
|
for slotName in ('language',):
|
|
if slotName in slotNames:
|
|
slotNames.remove(slotName)
|
|
return slotNames
|
|
|
|
def getWeb(self):
|
|
return getWebForServerRole(self.serverRole)
|
|
|
|
def makeContentTitle(self, contentSlot, contentLabel):
|
|
return contentLabel
|
|
|
|
def newWidget(self, parentSlot = None):
|
|
return commonTools.newThing('widget', 'Thing')
|
|
|
|
|
|
class AdminWithoutWritersMixin(BaseObjectWebMixin):
|
|
adminsSet_kind_widget_fieldLabel = N_('Administrators')
|
|
|
|
def newWidget(self, parentSlot = None):
|
|
return commonTools.newThing('widget', 'Thing')
|
|
|
|
|
|
class AdminMixin(AdminWithoutWritersMixin):
|
|
readersSet_kind_itemKind_value_defaultValue = system.generalPublicId
|
|
|
|
writersSet_kind_itemKind_value_defaultValue = None
|
|
|
|
|
|
class ObjectWebMixin(BaseObjectWebMixin):
|
|
addToId = None
|
|
addToId_kind_stateInEditMode = 'read-only'
|
|
addToId_kind_widget_fieldLabel = N_('Added to')
|
|
addToId_kind_widgetName = 'SelectId'
|
|
addToId_kindName = 'Id'
|
|
|
|
addToSlot = None
|
|
addToSlot_kind_stateInEditMode = 'hidden'
|
|
addToSlot_kind_stateInViewMode = 'hidden'
|
|
addToSlot_kind_widget_fieldLabel = N_('Added to slot')
|
|
addToSlot_kindName = 'String'
|
|
|
|
def getViewLayoutSlotNames(self, fields, parentSlot = None):
|
|
slotNames = BaseObjectWebMixin.getViewLayoutSlotNames(
|
|
self, fields, parentSlot = parentSlot)
|
|
if 'addToId' in slotNames:
|
|
slotNames.remove('addToId')
|
|
if 'addToSlot' in slotNames:
|
|
slotNames.remove('addToSlot')
|
|
return slotNames
|
|
|
|
def getEditLayoutSlotNames(self, fields, parentSlot = None):
|
|
slotNames = BaseObjectWebMixin.getEditLayoutSlotNames(
|
|
self, fields, parentSlot = parentSlot)
|
|
if 'addToId' in slotNames:
|
|
slotNames.remove('addToId')
|
|
if 'addToSlot' in slotNames:
|
|
slotNames.remove('addToSlot')
|
|
if fields.has_key('addToId') and fields['addToId'] and \
|
|
not (fields.has_key('id') and fields['id']):
|
|
slotNames.insert(0, 'addToId')
|
|
slotNames.insert(0, 'addToSlot')
|
|
return slotNames
|
|
|
|
|
|
class WebMixin(things.ThingMixin):
|
|
thingCategory = 'web'
|
|
|
|
|
|
class ObjectsWebMixin(WebMixin):
|
|
def admin(self):
|
|
context.push(_level = 'admin',
|
|
defaultDispatcherId = context.getVar('dispatcherId'))
|
|
try:
|
|
if not self.canGetAdmin():
|
|
return accessForbidden()
|
|
admin = self.getAdmin()
|
|
|
|
keywords = {}
|
|
admin.makeFieldsFromInstance(keywords)
|
|
admin.repairFields(keywords)
|
|
|
|
layout = X.array()
|
|
layout += admin.getViewLayout(keywords)
|
|
|
|
buttonsBar = X.div(_class = 'buttons-bar')
|
|
layout += buttonsBar
|
|
navigationButtonsBar = X.span(_class = 'navigation-buttons-bar')
|
|
buttonsBar += navigationButtonsBar
|
|
navigationButtonsBar += X.buttonStandalone(
|
|
'view-list', X.actionUrl())
|
|
if self.canModifyAdmin():
|
|
actionButtonsBar = X.span(_class = 'action-buttons-bar')
|
|
buttonsBar += actionButtonsBar
|
|
actionButtonsBar += X.buttonStandalone(
|
|
'edit', X.actionUrl('adminEdit'))
|
|
finally:
|
|
context.pull(_level = 'admin')
|
|
return writePageLayout(
|
|
layout,_('%s Settings') % _(self.objectsNameCapitalized))
|
|
admin.isPublicForWeb = 1
|
|
|
|
def adminEdit(self, again = '', error = '', **keywords):
|
|
context.push(_level = 'adminEdit',
|
|
defaultDispatcherId = context.getVar('dispatcherId'),
|
|
layoutMode = 'edit')
|
|
try:
|
|
if keywords is None:
|
|
keywords = {}
|
|
if not self.isAdmin():
|
|
return accessForbidden()
|
|
admin = self.getAdmin()
|
|
|
|
if not again:
|
|
admin.makeFieldsFromInstance(keywords)
|
|
admin.repairFields(keywords)
|
|
|
|
layout = X.array()
|
|
layout += admin.getErrorLayout(error, keywords)
|
|
form = X.form(
|
|
action = X.actionUrl('adminSubmit'),
|
|
enctype= 'multipart/form-data', method = 'post')
|
|
layout += form
|
|
form += admin.getEditLayout(keywords)
|
|
|
|
buttonsBar = X.div(_class = 'buttons-bar')
|
|
form += buttonsBar
|
|
actionButtonsBar = X.span(_class = 'action-buttons-bar')
|
|
buttonsBar += actionButtonsBar
|
|
actionButtonsBar += X.buttonInForm('modify', 'modifyButton')
|
|
finally:
|
|
context.pull(_level = 'adminEdit')
|
|
return writePageLayout(layout, _('Editing %s Settings') \
|
|
% _(self.objectsNameCapitalized))
|
|
adminEdit.isPublicForWeb = 1
|
|
|
|
def adminSubmit(self, **keywords):
|
|
uri = None
|
|
context.push(_level = 'adminSubmit',
|
|
defaultDispatcherId = context.getVar('dispatcherId'))
|
|
try:
|
|
if keywords is None:
|
|
keywords = {}
|
|
if not self.isAdmin():
|
|
return accessForbidden()
|
|
admin = self.getAdmin()
|
|
|
|
if isButtonSelected('applyButton', keywords):
|
|
keywords['again'] = '1'
|
|
keywords['hideErrors'] = '1'
|
|
admin = self.newAdmin(keywords)
|
|
admin.submitFields(keywords)
|
|
if keywords.has_key('again') and keywords['again']:
|
|
uri = X.actionUrl('adminEdit')
|
|
uri.addKeywords(keywords)
|
|
return # The redirect(uri) will be returned by the finally
|
|
# instruction.
|
|
try:
|
|
self.modifyAdmin(admin)
|
|
except faults.WrongVersion:
|
|
keywords['again'] = '1'
|
|
keywords['error'] = '1'
|
|
keywords['versionError'] = '1'
|
|
uri = X.actionUrl('adminEdit')
|
|
uri.addKeywords(keywords)
|
|
return # The redirect(uri) will be returned by the finally
|
|
# instruction.
|
|
except:
|
|
if context.getVar('debug'):
|
|
raise
|
|
return accessForbidden()
|
|
uri = X.actionUrl('admin')
|
|
# The redirect(uri) will be returned by the finally instruction.
|
|
finally:
|
|
context.pull(_level = 'adminSubmit')
|
|
if uri:
|
|
return redirect(uri)
|
|
adminSubmit.isPublicForWeb = 1
|
|
|
|
def clone(self, id):
|
|
if not id or not self.hasObject(id):
|
|
return pageNotFound()
|
|
if not self.canCloneObject(id):
|
|
return accessForbidden()
|
|
object = self.getObject(id)
|
|
keywords = {}
|
|
object.makeFieldsFromInstance(keywords)
|
|
del keywords['id']
|
|
keywords['again'] = 1
|
|
keywords['cloned'] = 1
|
|
uri = X.actionUrl('edit')
|
|
uri.addKeywords(keywords)
|
|
return redirect(uri)
|
|
clone.isPublicForWeb = 1
|
|
|
|
def delete(self, id):
|
|
method = context.getVar('httpMethod')
|
|
if id and self.hasObject(id):
|
|
try:
|
|
self.deleteObject(id)
|
|
except faults.Fault:
|
|
if context.getVar('debug'):
|
|
raise
|
|
return accessForbidden()
|
|
if method == 'DELETE':
|
|
return HTTP_NO_CONTENT
|
|
uri = X.actionUrl()
|
|
return redirect(uri)
|
|
else:
|
|
# TODO: error page when user tries to delete an object he can't
|
|
return pageNotFound()
|
|
delete.isPublicForWeb = 1
|
|
|
|
def download(self, id, path):
|
|
localId = commonTools.extractLocalId(id)
|
|
if localId == '__admin__':
|
|
if not self.canGetAdmin():
|
|
return accessForbidden()
|
|
object = self.getAdmin()
|
|
else:
|
|
if not self.hasObject(id):
|
|
return pageNotFound()
|
|
if not self.canGetObject(id):
|
|
return accessForbidden()
|
|
object = self.getObject(id)
|
|
rememberObject(id)
|
|
|
|
req = context.getVar('req')
|
|
|
|
uploadSlot = object.getSlotByPath(path)
|
|
upload = uploadSlot.getValue()
|
|
data = upload.getSlot('data', parentSlot = uploadSlot).getValue()
|
|
dataFileName = upload.getSlot(
|
|
'dataFileName', parentSlot = uploadSlot).getValue()
|
|
if dataFileName:
|
|
req.headers_out['Content-Disposition'] = \
|
|
'inline; filename="%s"' % dataFileName
|
|
req.headers_out['Content-Length'] = str(len(data))
|
|
req.content_type = upload.getSlot(
|
|
'dataType', parentSlot = uploadSlot).getValue()
|
|
setHttpCookie()
|
|
req.send_http_header()
|
|
if req.method == 'HEAD':
|
|
return OK
|
|
req.write(data)
|
|
return OK
|
|
download.isPublicForWeb = 1
|
|
|
|
def edit(self, id = '', again = '', error = '', **keywords):
|
|
if keywords is None:
|
|
keywords = {}
|
|
keywords['id'] = id
|
|
if id and not self.hasObject(id):
|
|
return pageNotFound()
|
|
if id:
|
|
if not self.canModifyObject(id):
|
|
return accessForbidden()
|
|
object = self.getObject(id)
|
|
rememberObject(id)
|
|
else:
|
|
if not self.canAddObject():
|
|
return accessForbidden()
|
|
object = self.newObject(keywords)
|
|
|
|
if id and not again:
|
|
object.makeFieldsFromInstance(keywords)
|
|
if not id and not again:
|
|
object.initFields(keywords)
|
|
object.repairFields(keywords)
|
|
|
|
if not id:
|
|
headerTitle = _(self.newObjectNameCapitalized)
|
|
else:
|
|
headerTitle = _('Editing %s - %s') % (
|
|
_(self.objectNameCapitalized), object.getLabel())
|
|
|
|
if keywords.has_key('headerTitle'):
|
|
headerTitle = keywords['headerTitle']
|
|
|
|
context.push(_level = 'edit',
|
|
isCreateEditMode = not id,
|
|
layoutMode = 'edit')
|
|
try:
|
|
layout = X.array()
|
|
leadIn = self.getEditLeadIn(object, keywords)
|
|
if leadIn:
|
|
layout += X.enclose(leadIn, _class = 'lead-in')
|
|
layout += object.getErrorLayout(error, keywords)
|
|
form = X.form(action = X.actionUrl('submit'),
|
|
enctype = 'multipart/form-data', method = 'post')
|
|
layout += form
|
|
if keywords.has_key('cloned') and keywords['cloned']:
|
|
form += X.input(name = 'cloned', type = 'hidden', value = '1')
|
|
|
|
if keywords.has_key('nextUri') and keywords['nextUri']:
|
|
form += X.input(name = 'nextUri', type = 'hidden',
|
|
value = keywords['nextUri'])
|
|
|
|
slot = slots.Root(object)
|
|
widget = slot.getWidget()
|
|
form += widget.getModelPageBodyLayout(slot, keywords)
|
|
|
|
buttonsBar = X.div(_class = 'buttons-bar')
|
|
form += buttonsBar
|
|
actionButtonsBar = X.span(_class = 'action-buttons-bar')
|
|
buttonsBar += actionButtonsBar
|
|
if not id:
|
|
actionButtonsBar += X.buttonInForm('create', 'createButton')
|
|
else:
|
|
if self.canDeleteObject(id):
|
|
actionButtonsBar += X.buttonInForm(
|
|
'delete', 'deleteButton')
|
|
actionButtonsBar += X.buttonInForm('modify', 'modifyButton')
|
|
|
|
return writePageLayout(layout, headerTitle)
|
|
finally:
|
|
context.pull(_level = 'edit')
|
|
edit.isPublicForWeb = 1
|
|
|
|
def getEditLeadIn(self, object, fields):
|
|
return None
|
|
|
|
def getObjectsLayout(self, partialObjects, objectIds,
|
|
slotNames = None):
|
|
layout = None
|
|
translationsProxy = getProxyForServerRole('translations')
|
|
if len(partialObjects) > 0:
|
|
if translationsProxy:
|
|
labelsMultiCall = MultiCall()
|
|
for objectId in objectIds:
|
|
partialObjects[objectId].getLabelTranslated(
|
|
context.getVar('readLanguages'),
|
|
multiCall = labelsMultiCall)
|
|
|
|
layout = X.array()
|
|
if slotNames:
|
|
table = X.table(_class = 'objects-table')
|
|
layout += table
|
|
partialObject = partialObjects.values()[0]
|
|
thead = X.thead()
|
|
table += thead
|
|
tr = X.tr()
|
|
thead += tr
|
|
tr += X.th()(X.nbsp)
|
|
for slotName in slotNames:
|
|
slot = partialObject.getSlot(slotName)
|
|
tr += slot.getWidget().getHtmlColumnLabel(slot)
|
|
tbody = X.tbody()
|
|
table += tbody
|
|
even = 0
|
|
else:
|
|
ul = X.ul()
|
|
layout += ul
|
|
if translationsProxy:
|
|
lazyLabels = labelsMultiCall.call()
|
|
else:
|
|
lazyLabels = objectIds # fastest way to get a list of same len
|
|
for objectId, lazyLabel in zip(objectIds, lazyLabels):
|
|
if translationsProxy:
|
|
try:
|
|
label = lazyLabel()
|
|
except faults.UnknownStringDigest:
|
|
label = partialObjects[objectId].getLabel()
|
|
else:
|
|
label = partialObjects[objectId].getLabel()
|
|
partialObject = partialObjects[objectId]
|
|
fields = {}
|
|
if slotNames:
|
|
for slotName in slotNames:
|
|
slot = partialObject.getSlot(slotName)
|
|
kind = slot.getKind()
|
|
if kind.hasToMakeFieldFromValue:
|
|
kind.makeFieldFromValue(
|
|
slot, fields, slot.getValue())
|
|
for slotName in slotNames:
|
|
slot = partialObject.getSlot(slotName)
|
|
kind = slot.getKind()
|
|
if kind.hasToRepairField:
|
|
kind.repairField(slot, fields)
|
|
tr = X.tr(_class = ((even and 'even') or 'odd'))
|
|
even = not even
|
|
tbody += tr
|
|
tr += X.td()(
|
|
X.a(href = X.idUrl(partialObject.id))(label))
|
|
for slotName in slotNames:
|
|
slot = partialObject.getSlot(slotName)
|
|
widget = slot.getWidget()
|
|
widget.setInForm(0)
|
|
widget.setReadOnly(0)
|
|
tr += widget.getHtmlColumnValue(slot, fields)
|
|
else:
|
|
ul += X.li()(
|
|
X.a(href = X.idUrl(partialObject.id))(label))
|
|
|
|
#layout += X.br()
|
|
return layout
|
|
|
|
def getObjectsSectionLayout(self, partialObjects, intertitle,
|
|
slotNames = None):
|
|
layout = None
|
|
translationsProxy = getProxyForServerRole('translations')
|
|
if len(partialObjects) > 0:
|
|
if translationsProxy:
|
|
labelsMultiCall = MultiCall()
|
|
for partialObject in partialObjects:
|
|
partialObject.getLabelTranslated(
|
|
context.getVar('readLanguages'),
|
|
multiCall = labelsMultiCall)
|
|
|
|
layout = X.array(X.h2()(intertitle))
|
|
table = X.table(_class = 'objects-table')
|
|
layout += table
|
|
partialObject = partialObjects[0]
|
|
if slotNames:
|
|
thead = X.thead()
|
|
table += thead
|
|
tr = X.tr()
|
|
thead += tr
|
|
tr += X.th()(X.nbsp)
|
|
for slotName in slotNames:
|
|
slot = partialObject.getSlot(slotName)
|
|
tr += slot.getWidget().getHtmlColumnLabel(slot)
|
|
tbody = X.tbody()
|
|
table += tbody
|
|
else:
|
|
tbody = table
|
|
even = 0
|
|
if translationsProxy:
|
|
lazyLabels = labelsMultiCall.call()
|
|
else:
|
|
lazyLabels = partialObjects # fastest way to get a list of same len
|
|
for partialObject, lazyLabel in zip(partialObjects, lazyLabels):
|
|
if translationsProxy:
|
|
label = lazyLabel()
|
|
else:
|
|
label = partialObject.getLabel()
|
|
fields = {}
|
|
if slotNames:
|
|
for slotName in slotNames:
|
|
slot = partialObject.getSlot(slotName)
|
|
kind = slot.getKind()
|
|
if kind.hasToMakeFieldFromValue:
|
|
kind.makeFieldFromValue(
|
|
slot, fields, slot.getValue())
|
|
for slotName in slotNames:
|
|
slot = partialObject.getSlot(slotName)
|
|
kind = slot.getKind()
|
|
if kind.hasToRepairField:
|
|
kind.repairField(slot, fields)
|
|
tr = X.tr(_class = ((even and 'even') or 'odd'))
|
|
even = not even
|
|
tbody += tr
|
|
tr += X.td()(X.a(href = X.idUrl(partialObject.id))(label))
|
|
if slotNames:
|
|
for slotName in slotNames:
|
|
slot = partialObject.getSlot(slotName)
|
|
widget = slot.getWidget()
|
|
widget.setInForm(0)
|
|
widget.setReadOnly(0)
|
|
tr += widget.getHtmlColumnValue(slot, fields)
|
|
layout += X.br()
|
|
return layout
|
|
|
|
def getSortedIds(self, objects):
|
|
translationsProxy = getProxyForServerRole('translations')
|
|
labels = {}
|
|
ids = objects.keys()
|
|
if not translationsProxy:
|
|
for k, v in objects.items():
|
|
labels[k] = v.getLabel()
|
|
else:
|
|
objectsByDispatcherId = {}
|
|
for id, object in objects.items():
|
|
dispatcherId = commonTools.extractDispatcherId(id)
|
|
if not objectsByDispatcherId.has_key(dispatcherId):
|
|
objectsByDispatcherId[dispatcherId] = {}
|
|
objectsByDispatcherId[dispatcherId][id] = object
|
|
readLanguages = context.getVar('readLanguages')
|
|
for sameDispatcherObjects in objectsByDispatcherId.values():
|
|
labelsMultiCall = MultiCall()
|
|
for object in sameDispatcherObjects.values():
|
|
object.getLabelTranslated(
|
|
readLanguages, multiCall = labelsMultiCall)
|
|
for id, lazyLabel in zip(ids, labelsMultiCall.call()):
|
|
try:
|
|
labels[id] = lazyLabel()
|
|
except faults.UnknownStringDigest:
|
|
labels[id] = objects[id].getLabel()
|
|
ids.sort(lambda id1, id2, labels = labels:
|
|
locale.strcoll(labels[id1], labels[id2]))
|
|
return ids
|
|
|
|
def getViewAboveButtonsBarLayout(self, object, fields):
|
|
return None
|
|
|
|
def getViewActionButtonsBarLayout(self, object, fields):
|
|
layout = X.array()
|
|
if self.canDeleteObject(object.id):
|
|
layout += X.buttonStandalone(
|
|
'delete', X.idUrl(object.id, 'submit').add(
|
|
'deleteButton', _('Delete')))
|
|
if self.canCloneObject(object.id):
|
|
layout += X.buttonStandalone(
|
|
'clone', X.idUrl(object.id, 'clone'))
|
|
if self.canModifyObject(object.id):
|
|
layout += X.buttonStandalone('edit', X.idUrl(object.id, 'edit'))
|
|
return layout
|
|
|
|
def getViewAllActionButtonsBarLayout(self):
|
|
layout = X.array()
|
|
if self.canAddObject():
|
|
layout += X.buttonStandalone('new', X.actionUrl('edit'))
|
|
return layout
|
|
|
|
def getViewAllButtonsBarLayout(self):
|
|
layout = X.div(_class = 'buttons-bar')
|
|
layout += X.span(_class = 'navigation-buttons-bar')(
|
|
self.getViewAllNavigationButtonsBarLayout())
|
|
layout += X.span(_class = 'other-action-buttons-bar')(
|
|
self.getViewAllOtherActionButtonsBarLayout())
|
|
layout += X.span(_class = 'action-buttons-bar')(
|
|
self.getViewAllActionButtonsBarLayout())
|
|
return layout
|
|
|
|
def getViewAllLeadIn(self):
|
|
return None
|
|
|
|
def getViewAllNavigationButtonsBarLayout(self):
|
|
layout = X.array()
|
|
userToken = context.getVar('userToken')
|
|
if self.canModifyAdmin() and userToken:
|
|
layout += X.buttonStandalone('settings', X.actionUrl('admin'))
|
|
return layout
|
|
|
|
def getViewAllOtherActionButtonsBarLayout(self):
|
|
return None
|
|
|
|
def getViewBelowButtonsBarLayout(self, object, fields):
|
|
return None
|
|
|
|
def getViewButtonsBarLayout(self, object, fields):
|
|
layout = X.div(_class = 'buttons-bar')
|
|
layout += X.span(_class = 'navigation-buttons-bar')(
|
|
self.getViewNavigationButtonsBarLayout(object, fields))
|
|
layout += X.span(_class = 'other-action-buttons-bar')(
|
|
self.getViewOtherActionButtonsBarLayout(object, fields))
|
|
layout += X.span(_class = 'action-buttons-bar')(
|
|
self.getViewActionButtonsBarLayout(object, fields))
|
|
return layout
|
|
|
|
def getViewLeadIn(self, object, fields):
|
|
return None
|
|
|
|
def getViewNavigationButtonsBarLayout(self, object, fields):
|
|
layout = X.array()
|
|
userToken = context.getVar('userToken', default = '')
|
|
if userToken:
|
|
layout += X.buttonStandalone('view-list', X.actionUrl())
|
|
return layout
|
|
|
|
def getViewOtherActionButtonsBarLayout(self, object, fields):
|
|
return None
|
|
|
|
def id(self, id):
|
|
if not self.hasObject(id):
|
|
return pageNotFound()
|
|
if not self.canGetObject(id):
|
|
return accessForbidden()
|
|
object = self.getObject(id)
|
|
return writePageLayout(
|
|
X.p(id),
|
|
_('Id for "%s"') % object.getLabel())
|
|
id.isPublicForWeb = 1
|
|
|
|
image = download
|
|
image.isPublicForWeb = 1
|
|
|
|
def imageEdit(self, id = '', path = '', **keywords):
|
|
if keywords is None:
|
|
keywords = {}
|
|
keywords['id'] = id
|
|
if id:
|
|
localId = commonTools.extractLocalId(id)
|
|
if localId == '__admin__':
|
|
if not self.canModifyAdmin():
|
|
return accessForbidden()
|
|
object = self.getAdmin()
|
|
else:
|
|
if not self.hasObject(id):
|
|
return pageNotFound()
|
|
if not self.canModifyObject(id):
|
|
return accessForbidden()
|
|
object = self.getObject(id)
|
|
rememberObject(id)
|
|
else:
|
|
if not self.canAddObject():
|
|
return accessForbidden()
|
|
object = self.newObject(keywords)
|
|
|
|
uploadSlot = object.getSlotByPath(path)
|
|
upload = uploadSlot.getValue()
|
|
data = upload.getSlot('data', parentSlot = uploadSlot).getField(
|
|
keywords)
|
|
req = context.getVar('req')
|
|
dataFileName = upload.getSlot(
|
|
'dataFileName', parentSlot = uploadSlot).getField(keywords)
|
|
if dataFileName:
|
|
req.headers_out['Content-disposition'] = \
|
|
'attachment; filename="%s"' % dataFileName
|
|
req.headers_out['Content-length'] = str(len(data))
|
|
req.content_type = upload.getSlot(
|
|
'dataType', parentSlot = uploadSlot).getField(keywords)
|
|
setHttpCookie()
|
|
req.send_http_header()
|
|
if req.method == 'HEAD':
|
|
return OK
|
|
req.write(data)
|
|
return OK
|
|
imageEdit.isPublicForWeb = 1
|
|
|
|
def rss(self):
|
|
lastObjects = self.getLastObjects(20, None, None, None)
|
|
req = context.getVar('req')
|
|
req.content_type = 'application/rss+xml'
|
|
req.send_http_header()
|
|
|
|
if req.caching:
|
|
req.openCachePage()
|
|
|
|
req.write("""\
|
|
<?xml version="1.0" encoding="iso-8859-15"?>
|
|
<rss version="2.0"
|
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
xmlns:content="http://purl.org/rss/1.0/modules/content/">
|
|
<channel>
|
|
<title>%(websiteTitle)s (%(objectsName)s)</title>
|
|
<link>http://%(websiteUrl)s</link>
|
|
""" % {
|
|
'websiteTitle': context.getVar('virtualHost').getLabel(),
|
|
'websiteUrl': context.getVar('virtualHost').hostName,
|
|
'objectsName': _(self.objectsNameCapitalized),
|
|
})
|
|
# Workaround Python Emacs mode bug: """
|
|
|
|
for object in lastObjects:
|
|
req.write("""\
|
|
<item>
|
|
<title>%s</title>
|
|
<dc:language>%s</dc:language>
|
|
<link>%s</link>
|
|
</item>
|
|
""" % (
|
|
xmlEncode(object.getLabel()),
|
|
object.language,
|
|
X.idUrl(object.id).getAsAbsoluteUrl()))
|
|
|
|
req.write("""\
|
|
</channel>
|
|
</rss>
|
|
""")
|
|
|
|
if req.caching:
|
|
req.closeCachePage()
|
|
|
|
return OK
|
|
rss.isPublicForWeb = 1
|
|
|
|
def search(self, slotNames = None, **keywords):
|
|
if 'terms' in keywords.keys():
|
|
terms = keywords['terms']
|
|
del keywords['terms']
|
|
terms = terms.split(' ')
|
|
object = self.newObject(keywords)
|
|
allSlotNames = object.getSlotNames()
|
|
for slotName in ['addToId', 'addToSlot', 'serverRole',
|
|
'thingName', 'thingCategory']:
|
|
if slotName in allSlotNames:
|
|
allSlotNames.remove(slotName)
|
|
for slotName in allSlotNames:
|
|
kind = object.getSlot(slotName).getKind()
|
|
if kind.__class__.__name__ == 'String':
|
|
keywords['%s_PT' % slotName] = terms
|
|
for key in keywords.keys():
|
|
if not '_' in key:
|
|
del keywords[key]
|
|
if type(slotNames) is not types.ListType:
|
|
slotNames = None
|
|
if not self.canGetObjects():
|
|
return accessForbidden()
|
|
if slotNames:
|
|
objects = self.searchPartialObjects(requiredSlotNames = slotNames,
|
|
**keywords)
|
|
else:
|
|
objects = self.searchObjects(**keywords)
|
|
layout = X.array()
|
|
ids = self.getSortedIds(objects)
|
|
layout += self.getObjectsLayout(objects, ids, slotNames = slotNames)
|
|
layout += self.getViewAllButtonsBarLayout()
|
|
title = _(self.objectsNameCapitalized) + ' ' + _('Search Results')
|
|
return writePageLayout(layout, title)
|
|
search.isPublicForWeb = 1
|
|
|
|
def submit(self, id = '', **keywords):
|
|
if keywords is None:
|
|
keywords = {}
|
|
|
|
if id and not self.hasObject(id):
|
|
return pageNotFound()
|
|
|
|
if isButtonSelected('deleteButton', keywords):
|
|
return confirmDelete(
|
|
X.idUrl(id, 'delete'),
|
|
id = id,
|
|
objectName = self.objectName)
|
|
|
|
keywords['id'] = id
|
|
if isButtonSelected('applyButton', keywords):
|
|
keywords['again'] = '1'
|
|
keywords['hideErrors'] = '1'
|
|
|
|
error = 0
|
|
object = self.newObject(keywords)
|
|
if error:
|
|
keywords['again'] = '1'
|
|
keywords['error'] = '1'
|
|
else:
|
|
object.submitFields(keywords)
|
|
|
|
if keywords.has_key('again') and keywords['again']:
|
|
uri = X.idUrl(id, 'edit')
|
|
uri.addKeywords(keywords)
|
|
return redirect(uri)
|
|
|
|
if not id:
|
|
result = self.submitAddObject(object, **keywords)
|
|
else:
|
|
result = self.submitModifyObject(object, **keywords)
|
|
|
|
if result:
|
|
return result
|
|
|
|
if not id and keywords.has_key('addToId') and keywords['addToId']:
|
|
keywords['id'] = object.id
|
|
self.submitAddObjectToOther(**keywords)
|
|
|
|
if keywords.has_key('nextUri') and keywords['nextUri']:
|
|
return redirect(keywords['nextUri'])
|
|
return redirect(X.idUrl(object.id))
|
|
submit.isPublicForWeb = 1
|
|
|
|
def submitAddObject(self, object, **keywords):
|
|
try:
|
|
object.id = self.addObject(object)
|
|
except faults.UserAccessDenied:
|
|
if context.getVar('debug'):
|
|
raise
|
|
return accessForbidden()
|
|
|
|
def submitAddObjectToOther(self, **keywords):
|
|
# FIXME: proper error handling
|
|
addToId = keywords['addToId']
|
|
proxy = getProxy(addToId)
|
|
objectWeAddTo = proxy.getObject(addToId)
|
|
if keywords.has_key('addToSlot') and keywords['addToSlot']:
|
|
slotName = keywords['addToSlot']
|
|
slot = objectWeAddTo.getSlot(slotName)
|
|
else:
|
|
# if not slotname specified, we take the first sequence
|
|
for slotName in objectWeAddTo.getOrderedLayoutSlotNames():
|
|
slot = objectWeAddTo.getSlot(slotName)
|
|
kind = slot.getKind()
|
|
if kind.getThingName() != 'Sequence':
|
|
continue
|
|
if kind.itemKind.getThingName() != 'Id':
|
|
continue
|
|
break
|
|
else:
|
|
raise 'Unable to find a slot to add to'
|
|
kindName = slot.getKind().getThingName()
|
|
cV = slot.getValue()
|
|
if kindName == 'Sequence':
|
|
if not cV:
|
|
cV = []
|
|
cV.append(keywords['id'])
|
|
else:
|
|
cV = keywords['id']
|
|
setattr(objectWeAddTo, slotName, cV)
|
|
proxy.modifyObject(objectWeAddTo)
|
|
|
|
def submitModifyObject(self, object, **keywords):
|
|
try:
|
|
self.modifyPartialObject(object, object.getSlotToModifyNames())
|
|
except faults.WrongVersion:
|
|
keywords['again'] = '1'
|
|
keywords['error'] = '1'
|
|
keywords['versionError'] = '1'
|
|
uri = X.idUrl(object.id, 'edit')
|
|
uri.addKeywords(keywords)
|
|
return redirect(uri)
|
|
except faults.UserAccessDenied:
|
|
return accessForbidden()
|
|
|
|
def thumbnail(self, id, path, width = '', height = ''):
|
|
localId = commonTools.extractLocalId(id)
|
|
if localId == '__admin__':
|
|
if not self.canGetAdmin():
|
|
return accessForbidden()
|
|
object = self.getAdmin()
|
|
else:
|
|
if not self.hasObject(id):
|
|
return pageNotFound()
|
|
if not self.canGetObject(id):
|
|
return accessForbidden()
|
|
object = self.getObject(id)
|
|
rememberObject(id)
|
|
try:
|
|
width = int(width)
|
|
except ValueError:
|
|
width = 128
|
|
try:
|
|
height = int(height)
|
|
except ValueError:
|
|
height = 128
|
|
|
|
uploadSlot = object.getSlotByPath(path)
|
|
upload = uploadSlot.getValue()
|
|
data = upload.getSlot('data', parentSlot = uploadSlot).getValue()
|
|
|
|
if data and upload.isType('image'):
|
|
import cStringIO
|
|
# From Python Imaging Library.
|
|
try:
|
|
import Image as PILImage
|
|
except ImportError:
|
|
PILImage = None
|
|
|
|
if PILImage:
|
|
imageFile = cStringIO.StringIO(data)
|
|
imageObject = PILImage.open(imageFile)
|
|
imageObject.thumbnail((width, height))
|
|
thumbnailFile = cStringIO.StringIO()
|
|
imageObject.save(thumbnailFile, imageObject.format)
|
|
data = thumbnailFile.getvalue()
|
|
width, height = imageObject.size
|
|
|
|
req = context.getVar('req')
|
|
dataFileName = upload.getSlot(
|
|
'dataFileName', parentSlot = uploadSlot).getValue()
|
|
if dataFileName:
|
|
req.headers_out['Content-disposition'] = \
|
|
'attachment; filename="%s"' % dataFileName
|
|
req.headers_out['Content-length'] = str(len(data))
|
|
req.content_type = upload.getSlot(
|
|
'dataType', parentSlot = uploadSlot).getValue()
|
|
setHttpCookie()
|
|
req.send_http_header()
|
|
if req.method == 'HEAD':
|
|
return OK
|
|
req.write(data)
|
|
return OK
|
|
thumbnail.isPublicForWeb = 1
|
|
|
|
def use(self, id):
|
|
return self.view(id)
|
|
use.isPublicForWeb = 1
|
|
|
|
def view(self, id):
|
|
if not self.hasObject(id):
|
|
return pageNotFound()
|
|
if not self.canGetObject(id):
|
|
return accessForbidden()
|
|
object = self.getObject(id)
|
|
rememberObject(id)
|
|
|
|
keywords = {}
|
|
|
|
object.makeFieldsFromInstance(keywords)
|
|
object.repairFields(keywords)
|
|
|
|
label = object.getLabelTranslated(context.getVar('readLanguages'))
|
|
|
|
layout = X.array()
|
|
leadIn = self.getViewLeadIn(object, keywords)
|
|
if leadIn:
|
|
layout += X.enclose(leadIn, _class = 'lead-in')
|
|
slot = slots.Root(object)
|
|
widget = slot.getWidget()
|
|
layout += widget.getModelPageBodyLayout(slot, keywords)
|
|
pageTitle = context.getVar('pageTitle', default = None)
|
|
layout += self.getViewAboveButtonsBarLayout(object, keywords)
|
|
if context.getVar('userId'):
|
|
layout += self.getViewButtonsBarLayout(object, keywords)
|
|
layout += self.getViewBelowButtonsBarLayout(object, keywords)
|
|
|
|
context.push(
|
|
currentObject = WebAPI.GlasnostObject(object = object),
|
|
)
|
|
if not pageTitle:
|
|
pageTitle = '%s - %s' % (_(self.objectNameCapitalized), label)
|
|
layout = writePageLayout(layout, pageTitle)
|
|
context.pull()
|
|
return layout
|
|
view.isPublicForWeb = 1
|
|
|
|
def viewAll(self, slotNames = None):
|
|
if type(slotNames) is not types.ListType:
|
|
slotNames = None
|
|
|
|
context.push(_level = 'viewAll',
|
|
defaultDispatcherId = context.getVar('dispatcherId'))
|
|
try:
|
|
if not self.canGetObjects():
|
|
return accessForbidden()
|
|
objects = self.getObjects()
|
|
layout = X.array()
|
|
leadIn = self.getViewAllLeadIn()
|
|
if leadIn:
|
|
layout += X.enclose(leadIn, _class = 'lead-in')
|
|
ids = self.getSortedIds(objects)
|
|
layout += self.getObjectsLayout(
|
|
objects, ids, slotNames = slotNames)
|
|
layout += self.getViewAllButtonsBarLayout()
|
|
finally:
|
|
context.pull(_level = 'viewAll')
|
|
return writePageLayout(layout, _(self.objectsNameCapitalized))
|
|
viewAll.isPublicForWeb = 1
|
|
|
|
def viewAllPy(self, **keywords):
|
|
context.push(_level = 'viewAllPy',
|
|
defaultDispatcherId = context.getVar('dispatcherId'))
|
|
try:
|
|
keywords['objects'] = [WebAPI.GlasnostObject(id)
|
|
for id in self.getObjectIds()]
|
|
finally:
|
|
context.pull(_level = 'viewAllPy')
|
|
req = context.getVar('req')
|
|
keywords['req'] = req
|
|
stdO = sys.stdout
|
|
sys.stdout = req
|
|
try:
|
|
execfile(context.getVar('PyFile'), keywords)
|
|
sys.stdout = stdO
|
|
except:
|
|
sys.stdout = stdO
|
|
raise apache.SERVER_RETURN, HTTP_INTERNAL_SERVER_ERROR
|
|
return OK
|
|
|
|
def viewAllTal(self, **keywords):
|
|
context.push(_level = 'viewAllTal',
|
|
defaultDispatcherId = context.getVar('dispatcherId'))
|
|
try:
|
|
keywords['objects'] = [WebAPI.GlasnostObject(id)
|
|
for id in self.getObjectIds()]
|
|
finally:
|
|
context.pull(_level = 'viewAllTal')
|
|
file = context.getVar('TALFile')
|
|
return processTALFile(
|
|
'',
|
|
xtal = context.getVar('TALFileIsXTAL'),
|
|
file = file,
|
|
**keywords)
|
|
|
|
def viewTal(self, id, **keywords):
|
|
if not self.hasObject(id):
|
|
return pageNotFound()
|
|
if not self.canGetObject(id):
|
|
return accessForbidden()
|
|
file = context.getVar('TALFile')
|
|
keywords['currentObject'] = WebAPI.GlasnostObject(id)
|
|
rememberObject(id)
|
|
return processTALFile(
|
|
'',
|
|
xtal = context.getVar('TALFileIsXTAL'),
|
|
file = file,
|
|
**keywords)
|
|
|
|
def viewPy(self, id, **keywords):
|
|
req = context.getVar('req')
|
|
keywords['currentObject'] = WebAPI.GlasnostObject(id)
|
|
keywords['req'] = context.req
|
|
for k, v in getTemplateVars().items():
|
|
keywords[k] = v
|
|
|
|
rememberObject(id)
|
|
stdO = sys.stdout
|
|
sys.stdout = req
|
|
try:
|
|
execfile(context.getVar('PyFile'), keywords)
|
|
sys.stdout = stdO
|
|
except:
|
|
sys.stdout = stdO
|
|
raise apache.SERVER_RETURN, HTTP_INTERNAL_SERVER_ERROR
|
|
return OK
|
|
|