Added an AJAX framework within appy.gen, and its first use: a pagination mechanism for producing paginated references in the reference widget.
This commit is contained in:
parent
4c4b2d0f87
commit
605c42d94e
|
@ -1,5 +1,5 @@
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import re
|
import re, time
|
||||||
from appy.gen.utils import sequenceTypes, PageDescr
|
from appy.gen.utils import sequenceTypes, PageDescr
|
||||||
|
|
||||||
# Default Appy permissions -----------------------------------------------------
|
# Default Appy permissions -----------------------------------------------------
|
||||||
|
@ -33,7 +33,7 @@ class Type:
|
||||||
def __init__(self, validator, multiplicity, index, default, optional,
|
def __init__(self, validator, multiplicity, index, default, optional,
|
||||||
editDefault, show, page, group, move, searchable,
|
editDefault, show, page, group, move, searchable,
|
||||||
specificReadPermission, specificWritePermission, width,
|
specificReadPermission, specificWritePermission, width,
|
||||||
height, master, masterValue):
|
height, master, masterValue, focus):
|
||||||
# The validator restricts which values may be defined. It can be an
|
# The validator restricts which values may be defined. It can be an
|
||||||
# interval (1,None), a list of string values ['choice1', 'choice2'],
|
# interval (1,None), a list of string values ['choice1', 'choice2'],
|
||||||
# a regular expression, a custom function, a Selection instance, etc.
|
# a regular expression, a custom function, a Selection instance, etc.
|
||||||
|
@ -86,6 +86,9 @@ class Type:
|
||||||
self.master.slaves.append(self)
|
self.master.slaves.append(self)
|
||||||
# When master has some value(s), there is impact on this field.
|
# When master has some value(s), there is impact on this field.
|
||||||
self.masterValue = masterValue
|
self.masterValue = masterValue
|
||||||
|
# If a field must retain attention in a particular way, set focus=True.
|
||||||
|
# It will be rendered in a special way.
|
||||||
|
self.focus = focus
|
||||||
self.id = id(self)
|
self.id = id(self)
|
||||||
self.type = self.__class__.__name__
|
self.type = self.__class__.__name__
|
||||||
self.pythonType = None # The True corresponding Python type
|
self.pythonType = None # The True corresponding Python type
|
||||||
|
@ -106,11 +109,12 @@ class Integer(Type):
|
||||||
default=None, optional=False, editDefault=False, show=True,
|
default=None, optional=False, editDefault=False, show=True,
|
||||||
page='main', group=None, move=0, searchable=False,
|
page='main', group=None, move=0, searchable=False,
|
||||||
specificReadPermission=False, specificWritePermission=False,
|
specificReadPermission=False, specificWritePermission=False,
|
||||||
width=None, height=None, master=None, masterValue=None):
|
width=None, height=None, master=None, masterValue=None,
|
||||||
|
focus=False):
|
||||||
Type.__init__(self, validator, multiplicity, index, default, optional,
|
Type.__init__(self, validator, multiplicity, index, default, optional,
|
||||||
editDefault, show, page, group, move, False,
|
editDefault, show, page, group, move, False,
|
||||||
specificReadPermission, specificWritePermission, width,
|
specificReadPermission, specificWritePermission, width,
|
||||||
height, master, masterValue)
|
height, master, masterValue, focus)
|
||||||
self.pythonType = long
|
self.pythonType = long
|
||||||
|
|
||||||
class Float(Type):
|
class Float(Type):
|
||||||
|
@ -118,11 +122,12 @@ class Float(Type):
|
||||||
default=None, optional=False, editDefault=False, show=True,
|
default=None, optional=False, editDefault=False, show=True,
|
||||||
page='main', group=None, move=0, searchable=False,
|
page='main', group=None, move=0, searchable=False,
|
||||||
specificReadPermission=False, specificWritePermission=False,
|
specificReadPermission=False, specificWritePermission=False,
|
||||||
width=None, height=None, master=None, masterValue=None):
|
width=None, height=None, master=None, masterValue=None,
|
||||||
|
focus=False):
|
||||||
Type.__init__(self, validator, multiplicity, index, default, optional,
|
Type.__init__(self, validator, multiplicity, index, default, optional,
|
||||||
editDefault, show, page, group, move, False,
|
editDefault, show, page, group, move, False,
|
||||||
specificReadPermission, specificWritePermission, width,
|
specificReadPermission, specificWritePermission, width,
|
||||||
height, master, masterValue)
|
height, master, masterValue, focus)
|
||||||
self.pythonType = float
|
self.pythonType = float
|
||||||
|
|
||||||
class String(Type):
|
class String(Type):
|
||||||
|
@ -141,11 +146,12 @@ class String(Type):
|
||||||
default=None, optional=False, editDefault=False, format=LINE,
|
default=None, optional=False, editDefault=False, format=LINE,
|
||||||
show=True, page='main', group=None, move=0, searchable=False,
|
show=True, page='main', group=None, move=0, searchable=False,
|
||||||
specificReadPermission=False, specificWritePermission=False,
|
specificReadPermission=False, specificWritePermission=False,
|
||||||
width=None, height=None, master=None, masterValue=None):
|
width=None, height=None, master=None, masterValue=None,
|
||||||
|
focus=False):
|
||||||
Type.__init__(self, validator, multiplicity, index, default, optional,
|
Type.__init__(self, validator, multiplicity, index, default, optional,
|
||||||
editDefault, show, page, group, move, searchable,
|
editDefault, show, page, group, move, searchable,
|
||||||
specificReadPermission, specificWritePermission, width,
|
specificReadPermission, specificWritePermission, width,
|
||||||
height, master, masterValue)
|
height, master, masterValue, focus)
|
||||||
self.format = format
|
self.format = format
|
||||||
def isSelection(self):
|
def isSelection(self):
|
||||||
'''Does the validator of this type definition define a list of values
|
'''Does the validator of this type definition define a list of values
|
||||||
|
@ -166,11 +172,12 @@ class Boolean(Type):
|
||||||
default=None, optional=False, editDefault=False, show=True,
|
default=None, optional=False, editDefault=False, show=True,
|
||||||
page='main', group=None, move=0, searchable=False,
|
page='main', group=None, move=0, searchable=False,
|
||||||
specificReadPermission=False, specificWritePermission=False,
|
specificReadPermission=False, specificWritePermission=False,
|
||||||
width=None, height=None, master=None, masterValue=None):
|
width=None, height=None, master=None, masterValue=None,
|
||||||
|
focus=False):
|
||||||
Type.__init__(self, validator, multiplicity, index, default, optional,
|
Type.__init__(self, validator, multiplicity, index, default, optional,
|
||||||
editDefault, show, page, group, move, searchable,
|
editDefault, show, page, group, move, searchable,
|
||||||
specificReadPermission, specificWritePermission, width,
|
specificReadPermission, specificWritePermission, width,
|
||||||
height, master, masterValue)
|
height, master, masterValue, focus)
|
||||||
self.pythonType = bool
|
self.pythonType = bool
|
||||||
|
|
||||||
class Date(Type):
|
class Date(Type):
|
||||||
|
@ -179,15 +186,19 @@ class Date(Type):
|
||||||
WITHOUT_HOUR = 1
|
WITHOUT_HOUR = 1
|
||||||
def __init__(self, validator=None, multiplicity=(0,1), index=None,
|
def __init__(self, validator=None, multiplicity=(0,1), index=None,
|
||||||
default=None, optional=False, editDefault=False,
|
default=None, optional=False, editDefault=False,
|
||||||
format=WITH_HOUR, show=True, page='main', group=None, move=0,
|
format=WITH_HOUR, startYear=time.localtime()[0]-10,
|
||||||
searchable=False,
|
endYear=time.localtime()[0]+10,
|
||||||
|
show=True, page='main', group=None, move=0, searchable=False,
|
||||||
specificReadPermission=False, specificWritePermission=False,
|
specificReadPermission=False, specificWritePermission=False,
|
||||||
width=None, height=None, master=None, masterValue=None):
|
width=None, height=None, master=None, masterValue=None,
|
||||||
|
focus=False):
|
||||||
Type.__init__(self, validator, multiplicity, index, default, optional,
|
Type.__init__(self, validator, multiplicity, index, default, optional,
|
||||||
editDefault, show, page, group, move, searchable,
|
editDefault, show, page, group, move, searchable,
|
||||||
specificReadPermission, specificWritePermission, width,
|
specificReadPermission, specificWritePermission, width,
|
||||||
height, master, masterValue)
|
height, master, masterValue, focus)
|
||||||
self.format = format
|
self.format = format
|
||||||
|
self.startYear = startYear
|
||||||
|
self.endYear = endYear
|
||||||
|
|
||||||
class File(Type):
|
class File(Type):
|
||||||
def __init__(self, validator=None, multiplicity=(0,1), index=None,
|
def __init__(self, validator=None, multiplicity=(0,1), index=None,
|
||||||
|
@ -195,11 +206,11 @@ class File(Type):
|
||||||
page='main', group=None, move=0, searchable=False,
|
page='main', group=None, move=0, searchable=False,
|
||||||
specificReadPermission=False, specificWritePermission=False,
|
specificReadPermission=False, specificWritePermission=False,
|
||||||
width=None, height=None, master=None, masterValue=None,
|
width=None, height=None, master=None, masterValue=None,
|
||||||
isImage=False):
|
focus=False, isImage=False):
|
||||||
Type.__init__(self, validator, multiplicity, index, default, optional,
|
Type.__init__(self, validator, multiplicity, index, default, optional,
|
||||||
editDefault, show, page, group, move, False,
|
editDefault, show, page, group, move, False,
|
||||||
specificReadPermission, specificWritePermission, width,
|
specificReadPermission, specificWritePermission, width,
|
||||||
height, master, masterValue)
|
height, master, masterValue, focus)
|
||||||
self.isImage = isImage
|
self.isImage = isImage
|
||||||
|
|
||||||
class Ref(Type):
|
class Ref(Type):
|
||||||
|
@ -208,13 +219,14 @@ class Ref(Type):
|
||||||
editDefault=False, add=False, link=True, unlink=False,
|
editDefault=False, add=False, link=True, unlink=False,
|
||||||
back=None, isBack=False, show=True, page='main', group=None,
|
back=None, isBack=False, show=True, page='main', group=None,
|
||||||
showHeaders=False, shownInfo=(), wide=False, select=None,
|
showHeaders=False, shownInfo=(), wide=False, select=None,
|
||||||
move=0, searchable=False,
|
maxPerPage=30, move=0, searchable=False,
|
||||||
specificReadPermission=False, specificWritePermission=False,
|
specificReadPermission=False, specificWritePermission=False,
|
||||||
width=None, height=None, master=None, masterValue=None):
|
width=None, height=None, master=None, masterValue=None,
|
||||||
|
focus=False):
|
||||||
Type.__init__(self, validator, multiplicity, index, default, optional,
|
Type.__init__(self, validator, multiplicity, index, default, optional,
|
||||||
editDefault, show, page, group, move, False,
|
editDefault, show, page, group, move, False,
|
||||||
specificReadPermission, specificWritePermission, width,
|
specificReadPermission, specificWritePermission, width,
|
||||||
height, master, masterValue)
|
height, master, masterValue, focus)
|
||||||
self.klass = klass
|
self.klass = klass
|
||||||
self.attribute = attribute
|
self.attribute = attribute
|
||||||
self.add = add # May the user add new objects through this ref ?
|
self.add = add # May the user add new objects through this ref ?
|
||||||
|
@ -231,6 +243,8 @@ class Ref(Type):
|
||||||
# as possible
|
# as possible
|
||||||
self.select = select # If a method is defined here, it will be used to
|
self.select = select # If a method is defined here, it will be used to
|
||||||
# filter the list of available tied objects.
|
# filter the list of available tied objects.
|
||||||
|
self.maxPerPage = maxPerPage # Maximum number of referenced objects
|
||||||
|
# shown at once.
|
||||||
|
|
||||||
class Computed(Type):
|
class Computed(Type):
|
||||||
def __init__(self, validator=None, multiplicity=(0,1), index=None,
|
def __init__(self, validator=None, multiplicity=(0,1), index=None,
|
||||||
|
@ -238,11 +252,11 @@ class Computed(Type):
|
||||||
page='main', group=None, move=0, searchable=False,
|
page='main', group=None, move=0, searchable=False,
|
||||||
specificReadPermission=False, specificWritePermission=False,
|
specificReadPermission=False, specificWritePermission=False,
|
||||||
width=None, height=None, method=None, plainText=True,
|
width=None, height=None, method=None, plainText=True,
|
||||||
master=None, masterValue=None):
|
master=None, masterValue=None, focus=False):
|
||||||
Type.__init__(self, None, multiplicity, index, default, optional,
|
Type.__init__(self, None, multiplicity, index, default, optional,
|
||||||
False, show, page, group, move, False,
|
False, show, page, group, move, False,
|
||||||
specificReadPermission, specificWritePermission, width,
|
specificReadPermission, specificWritePermission, width,
|
||||||
height, master, masterValue)
|
height, master, masterValue, focus)
|
||||||
self.method = method # The method used for computing the field value
|
self.method = method # The method used for computing the field value
|
||||||
self.plainText = plainText # Does field computation produce pain text
|
self.plainText = plainText # Does field computation produce pain text
|
||||||
# or XHTML?
|
# or XHTML?
|
||||||
|
@ -256,13 +270,17 @@ class Action(Type):
|
||||||
default=None, optional=False, editDefault=False, show=True,
|
default=None, optional=False, editDefault=False, show=True,
|
||||||
page='main', group=None, move=0, searchable=False,
|
page='main', group=None, move=0, searchable=False,
|
||||||
specificReadPermission=False, specificWritePermission=False,
|
specificReadPermission=False, specificWritePermission=False,
|
||||||
width=None, height=None, action=None, master=None,
|
width=None, height=None, action=None, result='computation',
|
||||||
masterValue=None):
|
master=None, masterValue=None, focus=False):
|
||||||
Type.__init__(self, None, (0,1), index, default, optional,
|
Type.__init__(self, None, (0,1), index, default, optional,
|
||||||
False, show, page, group, move, False,
|
False, show, page, group, move, False,
|
||||||
specificReadPermission, specificWritePermission, width,
|
specificReadPermission, specificWritePermission, width,
|
||||||
height, master, masterValue)
|
height, master, masterValue, focus)
|
||||||
self.action = action # Can be a single method or a list/tuple of methods
|
self.action = action # Can be a single method or a list/tuple of methods
|
||||||
|
self.result = result # 'computation' means that the action will simply
|
||||||
|
# compute things and redirect the user to the same page, with some
|
||||||
|
# status message about execution of the action. 'file' means that the
|
||||||
|
# result is the binary content of a file that the user will download.
|
||||||
|
|
||||||
def __call__(self, obj):
|
def __call__(self, obj):
|
||||||
'''Calls the action on p_obj.'''
|
'''Calls the action on p_obj.'''
|
||||||
|
@ -274,7 +292,10 @@ class Action(Type):
|
||||||
actRes = act(obj)
|
actRes = act(obj)
|
||||||
if type(actRes) in sequenceTypes:
|
if type(actRes) in sequenceTypes:
|
||||||
res[0] = res[0] and actRes[0]
|
res[0] = res[0] and actRes[0]
|
||||||
res[1] = res[1] + '\n' + actRes[1]
|
if self.result == 'file':
|
||||||
|
res[1] = res[1] + actRes[1]
|
||||||
|
else:
|
||||||
|
res[1] = res[1] + '\n' + actRes[1]
|
||||||
else:
|
else:
|
||||||
res[0] = res[0] and actRes
|
res[0] = res[0] and actRes
|
||||||
else:
|
else:
|
||||||
|
@ -284,8 +305,8 @@ class Action(Type):
|
||||||
res = list(actRes)
|
res = list(actRes)
|
||||||
else:
|
else:
|
||||||
res = [actRes, '']
|
res = [actRes, '']
|
||||||
# If res is None (ie the user-defined action did not return anything)
|
# If res is None (ie the user-defined action did not return
|
||||||
# we consider the action as successfull.
|
# anything), we consider the action as successfull.
|
||||||
if res[0] == None: res[0] = True
|
if res[0] == None: res[0] = True
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
res = (False, str(e))
|
res = (False, str(e))
|
||||||
|
@ -298,11 +319,12 @@ class Info(Type):
|
||||||
default=None, optional=False, editDefault=False, show=True,
|
default=None, optional=False, editDefault=False, show=True,
|
||||||
page='main', group=None, move=0, searchable=False,
|
page='main', group=None, move=0, searchable=False,
|
||||||
specificReadPermission=False, specificWritePermission=False,
|
specificReadPermission=False, specificWritePermission=False,
|
||||||
width=None, height=None, master=None, masterValue=None):
|
width=None, height=None, master=None, masterValue=None,
|
||||||
|
focus=False):
|
||||||
Type.__init__(self, None, (0,1), index, default, optional,
|
Type.__init__(self, None, (0,1), index, default, optional,
|
||||||
False, show, page, group, move, False,
|
False, show, page, group, move, False,
|
||||||
specificReadPermission, specificWritePermission, width,
|
specificReadPermission, specificWritePermission, width,
|
||||||
height, master, masterValue)
|
height, master, masterValue, focus)
|
||||||
|
|
||||||
# Workflow-specific types ------------------------------------------------------
|
# Workflow-specific types ------------------------------------------------------
|
||||||
class State:
|
class State:
|
||||||
|
|
|
@ -82,6 +82,8 @@ class ArchetypeFieldDescriptor:
|
||||||
self.widgetType = 'CalendarWidget'
|
self.widgetType = 'CalendarWidget'
|
||||||
if self.appyType.format == Date.WITHOUT_HOUR:
|
if self.appyType.format == Date.WITHOUT_HOUR:
|
||||||
self.widgetParams['show_hm'] = False
|
self.widgetParams['show_hm'] = False
|
||||||
|
self.widgetParams['starting_year'] = self.appyType.startYear
|
||||||
|
self.widgetParams['ending_year'] = self.appyType.endYear
|
||||||
elif self.appyType.type == 'Float':
|
elif self.appyType.type == 'Float':
|
||||||
self.widgetType = 'DecimalWidget'
|
self.widgetType = 'DecimalWidget'
|
||||||
elif self.appyType.type == 'File':
|
elif self.appyType.type == 'File':
|
||||||
|
|
|
@ -120,6 +120,10 @@ class Generator(AbstractGenerator):
|
||||||
msg('no_elem_selected', '', msg.NO_SELECTION),
|
msg('no_elem_selected', '', msg.NO_SELECTION),
|
||||||
msg('delete_confirm', '', msg.DELETE_CONFIRM),
|
msg('delete_confirm', '', msg.DELETE_CONFIRM),
|
||||||
msg('delete_done', '', msg.DELETE_DONE),
|
msg('delete_done', '', msg.DELETE_DONE),
|
||||||
|
msg('goto_first', '', msg.GOTO_FIRST),
|
||||||
|
msg('goto_previous', '', msg.GOTO_PREVIOUS),
|
||||||
|
msg('goto_next', '', msg.GOTO_NEXT),
|
||||||
|
msg('goto_last', '', msg.GOTO_LAST),
|
||||||
]
|
]
|
||||||
# Create basic files (config.py, Install.py, etc)
|
# Create basic files (config.py, Install.py, etc)
|
||||||
self.generateTool()
|
self.generateTool()
|
||||||
|
@ -408,7 +412,7 @@ class Generator(AbstractGenerator):
|
||||||
getterName = 'get%s%s' % (attrName[0].upper(), attrName[1:])
|
getterName = 'get%s%s' % (attrName[0].upper(), attrName[1:])
|
||||||
if isinstance(appyType, Ref):
|
if isinstance(appyType, Ref):
|
||||||
res += blanks + 'return self.o._appy_getRefs("%s", ' \
|
res += blanks + 'return self.o._appy_getRefs("%s", ' \
|
||||||
'noListIfSingleObj=True)\n' % attrName
|
'noListIfSingleObj=True).objects\n' % attrName
|
||||||
elif isinstance(appyType, Computed):
|
elif isinstance(appyType, Computed):
|
||||||
res += blanks + 'appyType = getattr(self.klass, "%s")\n' % attrName
|
res += blanks + 'appyType = getattr(self.klass, "%s")\n' % attrName
|
||||||
res += blanks + 'return self.o.getComputedValue(' \
|
res += blanks + 'return self.o.getComputedValue(' \
|
||||||
|
@ -417,6 +421,8 @@ class Generator(AbstractGenerator):
|
||||||
res += blanks + 'v = self.o.%s()\n' % getterName
|
res += blanks + 'v = self.o.%s()\n' % getterName
|
||||||
res += blanks + 'if not v: return None\n'
|
res += blanks + 'if not v: return None\n'
|
||||||
res += blanks + 'else: return FileWrapper(v)\n'
|
res += blanks + 'else: return FileWrapper(v)\n'
|
||||||
|
elif isinstance(appyType, String) and appyType.isMultiValued():
|
||||||
|
res += blanks + 'return list(self.o.%s())\n' % getterName
|
||||||
else:
|
else:
|
||||||
if attrName in ArchetypeFieldDescriptor.specialParams:
|
if attrName in ArchetypeFieldDescriptor.specialParams:
|
||||||
getterName = attrName.capitalize()
|
getterName = attrName.capitalize()
|
||||||
|
|
|
@ -79,13 +79,16 @@ class ToolMixin(AbstractMixin):
|
||||||
res.append({'title': flavour.title, 'number':flavour.number})
|
res.append({'title': flavour.title, 'number':flavour.number})
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def getAppName(self):
|
||||||
|
'''Returns the name of this application.'''
|
||||||
|
return self.getProductConfig().PROJECTNAME
|
||||||
|
|
||||||
def getAppFolder(self):
|
def getAppFolder(self):
|
||||||
'''Returns the folder at the root of the Plone site that is dedicated
|
'''Returns the folder at the root of the Plone site that is dedicated
|
||||||
to this application.'''
|
to this application.'''
|
||||||
portal = self.getProductConfig().getToolByName(
|
cfg = self.getProductConfig()
|
||||||
self, 'portal_url').getPortalObject()
|
portal = cfg.getToolByName(self, 'portal_url').getPortalObject()
|
||||||
appName = self.getProductConfig().PROJECTNAME
|
return getattr(portal, self.getAppName())
|
||||||
return getattr(portal, appName)
|
|
||||||
|
|
||||||
def getRootClasses(self):
|
def getRootClasses(self):
|
||||||
'''Returns the list of root classes for this application.'''
|
'''Returns the list of root classes for this application.'''
|
||||||
|
@ -275,9 +278,9 @@ class ToolMixin(AbstractMixin):
|
||||||
for importPath in importPaths:
|
for importPath in importPaths:
|
||||||
if not importPath: continue
|
if not importPath: continue
|
||||||
objectId = os.path.basename(importPath)
|
objectId = os.path.basename(importPath)
|
||||||
self.appy().create(appyClass, id=objectId)
|
self.appy().create(appyClass, id=objectId, _data=importPath)
|
||||||
self.plone_utils.addPortalMessage(self.translate('import_done'))
|
self.plone_utils.addPortalMessage(self.translate('import_done'))
|
||||||
return rq.RESPONSE.redirect(rq['HTTP_REFERER'])
|
return self.goto(rq['HTTP_REFERER'])
|
||||||
|
|
||||||
def isAlreadyImported(self, contentType, importPath):
|
def isAlreadyImported(self, contentType, importPath):
|
||||||
appFolder = self.getAppFolder()
|
appFolder = self.getAppFolder()
|
||||||
|
|
|
@ -6,11 +6,11 @@
|
||||||
The AbstractMixin defined hereafter is the base class of any mixin.'''
|
The AbstractMixin defined hereafter is the base class of any mixin.'''
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import os, os.path, sys, types
|
import os, os.path, sys, types, mimetypes
|
||||||
import appy.gen
|
import appy.gen
|
||||||
from appy.gen import String
|
from appy.gen import String
|
||||||
from appy.gen.utils import FieldDescr, GroupDescr, PhaseDescr, StateDescr, \
|
from appy.gen.utils import FieldDescr, GroupDescr, PhaseDescr, StateDescr, \
|
||||||
ValidationErrors, sequenceTypes
|
ValidationErrors, sequenceTypes, RefObjects
|
||||||
from appy.gen.plone25.descriptors import ArchetypesClassDescriptor
|
from appy.gen.plone25.descriptors import ArchetypesClassDescriptor
|
||||||
from appy.gen.plone25.utils import updateRolesForPermission, getAppyRequest
|
from appy.gen.plone25.utils import updateRolesForPermission, getAppyRequest
|
||||||
|
|
||||||
|
@ -20,10 +20,6 @@ class AbstractMixin:
|
||||||
inherits from this class. It contains basic functions allowing to
|
inherits from this class. It contains basic functions allowing to
|
||||||
minimize the amount of generated code.'''
|
minimize the amount of generated code.'''
|
||||||
|
|
||||||
def getAppyAttribute(self, name):
|
|
||||||
'''Returns method or attribute value corresponding to p_name.'''
|
|
||||||
return eval('self.%s' % name)
|
|
||||||
|
|
||||||
def createOrUpdate(self, created):
|
def createOrUpdate(self, created):
|
||||||
'''This method creates (if p_created is True) or updates an object.
|
'''This method creates (if p_created is True) or updates an object.
|
||||||
In the case of an object creation, p_self is a temporary object
|
In the case of an object creation, p_self is a temporary object
|
||||||
|
@ -35,11 +31,16 @@ class AbstractMixin:
|
||||||
if created:
|
if created:
|
||||||
obj = self.portal_factory.doCreate(self, self.id) # portal_factory
|
obj = self.portal_factory.doCreate(self, self.id) # portal_factory
|
||||||
# creates the final object from the temp object.
|
# creates the final object from the temp object.
|
||||||
obj.processForm()
|
if created and (obj._appy_meta_type == 'tool'):
|
||||||
|
# We are in the special case where the tool itself is being created.
|
||||||
|
# In this case, we do not process form data.
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
obj.processForm()
|
||||||
|
|
||||||
# Get the current language and put it in the request
|
# Get the current language and put it in the request
|
||||||
if rq.form.has_key('current_lang'):
|
#if rq.form.has_key('current_lang'):
|
||||||
rq.form['language'] = rq.form.get('current_lang')
|
# rq.form['language'] = rq.form.get('current_lang')
|
||||||
|
|
||||||
# Manage references
|
# Manage references
|
||||||
obj._appy_manageRefs(created)
|
obj._appy_manageRefs(created)
|
||||||
|
@ -80,7 +81,7 @@ class AbstractMixin:
|
||||||
objId = self.generateUniqueId(rq.get('type_name'))
|
objId = self.generateUniqueId(rq.get('type_name'))
|
||||||
urlBack = '%s/portal_factory/%s/%s/skyn/edit' % \
|
urlBack = '%s/portal_factory/%s/%s/skyn/edit' % \
|
||||||
(baseUrl, rq.get('type_name'), objId)
|
(baseUrl, rq.get('type_name'), objId)
|
||||||
return rq.RESPONSE.redirect(urlBack)
|
return self.goto(urlBack)
|
||||||
|
|
||||||
def onUpdate(self):
|
def onUpdate(self):
|
||||||
'''This method is executed when a user wants to update an object.
|
'''This method is executed when a user wants to update an object.
|
||||||
|
@ -95,11 +96,15 @@ class AbstractMixin:
|
||||||
|
|
||||||
# Go back to the consult view if the user clicked on 'Cancel'
|
# Go back to the consult view if the user clicked on 'Cancel'
|
||||||
if rq.get('buttonCancel', None):
|
if rq.get('buttonCancel', None):
|
||||||
urlBack = '%s/skyn/view?phase=%s&pageName=%s' % (
|
if '/portal_factory/' in self.absolute_url():
|
||||||
self.absolute_url(), rq.get('phase'), rq.get('pageName'))
|
# Go back to the Plone site (no better solution at present).
|
||||||
|
urlBack = self.portal_url.getPortalObject().absolute_url()
|
||||||
|
else:
|
||||||
|
urlBack = '%s/skyn/view?phase=%s&pageName=%s' % (
|
||||||
|
self.absolute_url(), rq.get('phase'), rq.get('pageName'))
|
||||||
self.plone_utils.addPortalMessage(
|
self.plone_utils.addPortalMessage(
|
||||||
self.translate('Changes canceled.', domain='plone'))
|
self.translate('Changes canceled.', domain='plone'))
|
||||||
return rq.RESPONSE.redirect(urlBack)
|
return self.goto(urlBack)
|
||||||
|
|
||||||
# Trigger field-specific validation
|
# Trigger field-specific validation
|
||||||
self.validate(REQUEST=rq, errors=errors, data=1, metadata=0)
|
self.validate(REQUEST=rq, errors=errors, data=1, metadata=0)
|
||||||
|
@ -124,7 +129,7 @@ class AbstractMixin:
|
||||||
obj.translate('Changes saved.', domain='plone'))
|
obj.translate('Changes saved.', domain='plone'))
|
||||||
urlBack = '%s/skyn/view?phase=%s&pageName=%s' % (
|
urlBack = '%s/skyn/view?phase=%s&pageName=%s' % (
|
||||||
obj.absolute_url(), rq.get('phase'), rq.get('pageName'))
|
obj.absolute_url(), rq.get('phase'), rq.get('pageName'))
|
||||||
return rq.RESPONSE.redirect(urlBack)
|
return self.goto(urlBack)
|
||||||
elif rq.get('buttonPrevious', None):
|
elif rq.get('buttonPrevious', None):
|
||||||
# Go to the edit view (previous page) for this object
|
# Go to the edit view (previous page) for this object
|
||||||
rq.set('fieldset', rq.get('previousPage'))
|
rq.set('fieldset', rq.get('previousPage'))
|
||||||
|
@ -139,69 +144,108 @@ class AbstractMixin:
|
||||||
msg = self.translate('delete_done')
|
msg = self.translate('delete_done')
|
||||||
self.delete()
|
self.delete()
|
||||||
self.plone_utils.addPortalMessage(msg)
|
self.plone_utils.addPortalMessage(msg)
|
||||||
rq.RESPONSE.redirect(rq['HTTP_REFERER'])
|
self.goto(rq['HTTP_REFERER'])
|
||||||
|
|
||||||
def getAppyType(self, fieldName):
|
def goto(self, url):
|
||||||
'''Returns the Appy type corresponding to p_fieldName.'''
|
'''Brings the user to some p_url after an action has been executed.'''
|
||||||
|
return self.REQUEST.RESPONSE.redirect(url)
|
||||||
|
|
||||||
|
def getAppyAttribute(self, name):
|
||||||
|
'''Returns method or attribute value corresponding to p_name.'''
|
||||||
|
return eval('self.%s' % name)
|
||||||
|
|
||||||
|
def getAppyType(self, fieldName, forward=True):
|
||||||
|
'''Returns the Appy type corresponding to p_fieldName. If you want to
|
||||||
|
get the Appy type corresponding to a backward field, set p_forward
|
||||||
|
to False and specify the corresponding Archetypes relationship in
|
||||||
|
p_fieldName.'''
|
||||||
res = None
|
res = None
|
||||||
if fieldName == 'id': return res
|
if forward:
|
||||||
if self.wrapperClass:
|
if fieldName == 'id': return res
|
||||||
baseClass = self.wrapperClass.__bases__[-1]
|
if self.wrapperClass:
|
||||||
try:
|
baseClass = self.wrapperClass.__bases__[-1]
|
||||||
# If I get the attr on self instead of baseClass, I get the
|
try:
|
||||||
# property field that is redefined at the wrapper level.
|
# If I get the attr on self instead of baseClass, I get the
|
||||||
appyType = getattr(baseClass, fieldName)
|
# property field that is redefined at the wrapper level.
|
||||||
res = self._appy_getTypeAsDict(fieldName, appyType, baseClass)
|
appyType = getattr(baseClass, fieldName)
|
||||||
except AttributeError:
|
res = self._appy_getTypeAsDict(fieldName, appyType, baseClass)
|
||||||
# Check for another parent
|
except AttributeError:
|
||||||
if self.wrapperClass.__bases__[0].__bases__:
|
# Check for another parent
|
||||||
baseClass = self.wrapperClass.__bases__[0].__bases__[-1]
|
if self.wrapperClass.__bases__[0].__bases__:
|
||||||
try:
|
baseClass = self.wrapperClass.__bases__[0].__bases__[-1]
|
||||||
appyType = getattr(baseClass, fieldName)
|
try:
|
||||||
res = self._appy_getTypeAsDict(fieldName, appyType,
|
appyType = getattr(baseClass, fieldName)
|
||||||
baseClass)
|
res = self._appy_getTypeAsDict(fieldName, appyType,
|
||||||
except AttributeError:
|
baseClass)
|
||||||
pass
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
referers = self.getProductConfig().referers
|
||||||
|
for appyType, rel in referers[self.__class__.__name__]:
|
||||||
|
if rel == fieldName:
|
||||||
|
res = appyType.__dict__
|
||||||
|
res['backd'] = appyType.back.__dict__
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _appy_getRefs(self, fieldName, ploneObjects=False,
|
def _appy_getRefs(self, fieldName, ploneObjects=False,
|
||||||
noListIfSingleObj=False):
|
noListIfSingleObj=False, startNumber=None):
|
||||||
'''p_fieldName is the name of a Ref field. This method returns an
|
'''p_fieldName is the name of a Ref field. This method returns an
|
||||||
ordered list containing the objects linked to p_self through this
|
ordered list containing the objects linked to p_self through this
|
||||||
field. If p_ploneObjects is True, the method returns the "true"
|
field. If p_ploneObjects is True, the method returns the "true"
|
||||||
Plone objects instead of the Appy wrappers.'''
|
Plone objects instead of the Appy wrappers.
|
||||||
res = []
|
If p_startNumber is None, this method returns all referred objects.
|
||||||
sortedFieldName = '_appy_%s' % fieldName
|
If p_startNumber is a number, this method will return x objects,
|
||||||
exec 'objs = self.get%s%s()' % (fieldName[0].upper(), fieldName[1:])
|
starting at p_startNumber, x being appyType.maxPerPage.'''
|
||||||
if objs:
|
appyType = self.getAppyType(fieldName)
|
||||||
if type(objs) != list:
|
sortedUids = getattr(self, '_appy_%s' % fieldName)
|
||||||
objs = [objs]
|
batchNeeded = startNumber != None
|
||||||
objectsUids = [o.UID() for o in objs]
|
exec 'refUids= self.getRaw%s%s()' % (fieldName[0].upper(),fieldName[1:])
|
||||||
sortedObjectsUids = getattr(self, sortedFieldName)
|
# There may be too much UIDs in sortedUids because these fields
|
||||||
# The list of UIDs may contain too much UIDs; indeed, when deleting
|
# are not updated when objects are deleted. So we do it now. TODO: do
|
||||||
# objects, the list of UIDs are not updated.
|
# such cleaning on object deletion?
|
||||||
uidsToDelete = []
|
toDelete = []
|
||||||
for uid in sortedObjectsUids:
|
for uid in sortedUids:
|
||||||
try:
|
if uid not in refUids:
|
||||||
uidIndex = objectsUids.index(uid)
|
toDelete.append(uid)
|
||||||
obj = objs[uidIndex]
|
for uid in toDelete:
|
||||||
if not ploneObjects:
|
sortedUids.remove(uid)
|
||||||
obj = obj._appy_getWrapper(force=True)
|
# Prepare the result
|
||||||
res.append(obj)
|
res = RefObjects()
|
||||||
except ValueError:
|
res.totalNumber = res.batchSize = len(sortedUids)
|
||||||
uidsToDelete.append(uid)
|
if batchNeeded:
|
||||||
# Delete unused UIDs
|
res.batchSize = appyType['maxPerPage']
|
||||||
for uid in uidsToDelete:
|
if startNumber != None:
|
||||||
sortedObjectsUids.remove(uid)
|
res.startNumber = startNumber
|
||||||
if res and noListIfSingleObj:
|
# Get the needed referred objects
|
||||||
appyType = self.getAppyType(fieldName)
|
i = res.startNumber
|
||||||
|
# Is is possible and more efficient to perform a single query in
|
||||||
|
# uid_catalog and get the result in the order of specified uids?
|
||||||
|
while i < (res.startNumber + res.batchSize):
|
||||||
|
if i >= res.totalNumber: break
|
||||||
|
refUid = sortedUids[i]
|
||||||
|
refObject = self.uid_catalog(UID=refUid)[0].getObject()
|
||||||
|
if not ploneObjects:
|
||||||
|
refObject = refObject.appy()
|
||||||
|
res.objects.append(refObject)
|
||||||
|
i += 1
|
||||||
|
if res.objects and noListIfSingleObj:
|
||||||
if appyType['multiplicity'][1] == 1:
|
if appyType['multiplicity'][1] == 1:
|
||||||
res = res[0]
|
res.objects = res.objects[0]
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getAppyRefs(self, fieldName):
|
def getAppyRefs(self, fieldName, forward=True, startNumber=None):
|
||||||
'''Gets the objects linked to me through p_fieldName.'''
|
'''Gets the objects linked to me through p_fieldName. If you need to
|
||||||
return self._appy_getRefs(fieldName, ploneObjects=True)
|
get a backward reference, set p_forward to False and specify the
|
||||||
|
corresponding Archetypes relationship in p_fieldName.
|
||||||
|
If p_startNumber is None, this method returns all referred objects.
|
||||||
|
If p_startNumber is a number, this method will return x objects,
|
||||||
|
starting at p_startNumber, x being appyType.maxPerPage.'''
|
||||||
|
if forward:
|
||||||
|
return self._appy_getRefs(fieldName, ploneObjects=True,
|
||||||
|
startNumber=startNumber).__dict__
|
||||||
|
else:
|
||||||
|
# Note Pagination is not yet implemented for backward ref.
|
||||||
|
return RefObjects(self.getBRefs(fieldName)).__dict__
|
||||||
|
|
||||||
def getAppyRefIndex(self, fieldName, obj):
|
def getAppyRefIndex(self, fieldName, obj):
|
||||||
'''Gets the position of p_obj within Ref field named p_fieldName.'''
|
'''Gets the position of p_obj within Ref field named p_fieldName.'''
|
||||||
|
@ -211,8 +255,8 @@ class AbstractMixin:
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getAppyBackRefs(self):
|
def getAppyBackRefs(self):
|
||||||
'''Returns the list of back references (=types) that are defined for
|
'''Returns the list of back references (=types, not objects) that are
|
||||||
this class.'''
|
defined for this class.'''
|
||||||
className = self.__class__.__name__
|
className = self.__class__.__name__
|
||||||
referers = self.getProductConfig().referers
|
referers = self.getProductConfig().referers
|
||||||
res = []
|
res = []
|
||||||
|
@ -303,6 +347,11 @@ class AbstractMixin:
|
||||||
res = fieldDescr['show'](obj)
|
res = fieldDescr['show'](obj)
|
||||||
else:
|
else:
|
||||||
res = fieldDescr['show']
|
res = fieldDescr['show']
|
||||||
|
# Take into account possible values 'view' and 'edit' for 'show' param.
|
||||||
|
if (res == 'view' and isEdit) or (res == 'edit' and not isEdit):
|
||||||
|
res = False
|
||||||
|
else:
|
||||||
|
res = True
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getAppyFields(self, isEdit, page):
|
def getAppyFields(self, isEdit, page):
|
||||||
|
@ -460,22 +509,12 @@ class AbstractMixin:
|
||||||
'''This method is called when the user wants to change order of an
|
'''This method is called when the user wants to change order of an
|
||||||
item in a reference field.'''
|
item in a reference field.'''
|
||||||
rq = self.REQUEST
|
rq = self.REQUEST
|
||||||
# Move the item up (-1), down (+1) or at a given position ?
|
# Move the item up (-1), down (+1) ?
|
||||||
move = -1 # Move up
|
move = -1 # Move up
|
||||||
|
if rq['move'] == 'down':
|
||||||
|
move = 1 # Down
|
||||||
isDelta = True
|
isDelta = True
|
||||||
if rq.get('moveDown.x', None) != None:
|
|
||||||
move = 1 # Move down
|
|
||||||
elif rq.get('moveSeveral.x', None) != None:
|
|
||||||
try:
|
|
||||||
move = int(rq.get('moveValue'))
|
|
||||||
# In this case, it is not a delta value; it is the new position
|
|
||||||
# where the item must be moved.
|
|
||||||
isDelta = False
|
|
||||||
except ValueError:
|
|
||||||
self.plone_utils.addPortalMessage(
|
|
||||||
self.translate('ref_invalid_index'))
|
|
||||||
self.changeRefOrder(rq['fieldName'], rq['refObjectUid'], move, isDelta)
|
self.changeRefOrder(rq['fieldName'], rq['refObjectUid'], move, isDelta)
|
||||||
return rq.RESPONSE.redirect(rq['HTTP_REFERER'])
|
|
||||||
|
|
||||||
def getWorkflow(self, appy=True):
|
def getWorkflow(self, appy=True):
|
||||||
'''Returns the Appy workflow instance that is relevant for this
|
'''Returns the Appy workflow instance that is relevant for this
|
||||||
|
@ -563,24 +602,34 @@ class AbstractMixin:
|
||||||
def executeAppyAction(self, actionName, reindex=True):
|
def executeAppyAction(self, actionName, reindex=True):
|
||||||
'''Executes action with p_fieldName on this object.'''
|
'''Executes action with p_fieldName on this object.'''
|
||||||
appyClass = self.wrapperClass.__bases__[1]
|
appyClass = self.wrapperClass.__bases__[1]
|
||||||
res = getattr(appyClass, actionName)(self._appy_getWrapper(force=True))
|
appyType = getattr(appyClass, actionName)
|
||||||
|
actionRes = appyType(self._appy_getWrapper(force=True))
|
||||||
self.reindexObject()
|
self.reindexObject()
|
||||||
return res
|
return appyType.result, actionRes
|
||||||
|
|
||||||
def onExecuteAppyAction(self):
|
def onExecuteAppyAction(self):
|
||||||
'''This method is called every time a user wants to execute an Appy
|
'''This method is called every time a user wants to execute an Appy
|
||||||
action on an object.'''
|
action on an object.'''
|
||||||
rq = self.REQUEST
|
rq = self.REQUEST
|
||||||
res, msg = self.executeAppyAction(rq['fieldName'])
|
resultType, actionResult = self.executeAppyAction(rq['fieldName'])
|
||||||
|
successfull, msg = actionResult
|
||||||
if not msg:
|
if not msg:
|
||||||
# Use the default i18n messages
|
# Use the default i18n messages
|
||||||
suffix = 'ko'
|
suffix = 'ko'
|
||||||
if res:
|
if successfull:
|
||||||
suffix = 'ok'
|
suffix = 'ok'
|
||||||
label='%s_action_%s' % (self.getLabelPrefix(rq['fieldName']),suffix)
|
label='%s_action_%s' % (self.getLabelPrefix(rq['fieldName']),suffix)
|
||||||
msg = self.translate(label)
|
msg = self.translate(label)
|
||||||
self.plone_utils.addPortalMessage(msg)
|
if (resultType == 'computation') or not successfull:
|
||||||
return rq.RESPONSE.redirect(rq['HTTP_REFERER'])
|
self.plone_utils.addPortalMessage(msg)
|
||||||
|
return self.goto(rq['HTTP_REFERER'])
|
||||||
|
else:
|
||||||
|
# msg does not contain a message, but a complete file to show as is.
|
||||||
|
# (or, if your prefer, the message must be shown directly to the
|
||||||
|
# user, not encapsulated in a Plone page).
|
||||||
|
res = self.getProductConfig().File(msg.name, msg.name, msg,
|
||||||
|
content_type=mimetypes.guess_type(msg.name)[0])
|
||||||
|
return res.index_html(rq, rq.RESPONSE)
|
||||||
|
|
||||||
def onTriggerTransition(self):
|
def onTriggerTransition(self):
|
||||||
'''This method is called whenever a user wants to trigger a workflow
|
'''This method is called whenever a user wants to trigger a workflow
|
||||||
|
@ -597,7 +646,7 @@ class AbstractMixin:
|
||||||
msg = self.translate(u'Your content\'s status has been modified.',
|
msg = self.translate(u'Your content\'s status has been modified.',
|
||||||
domain='plone')
|
domain='plone')
|
||||||
self.plone_utils.addPortalMessage(msg)
|
self.plone_utils.addPortalMessage(msg)
|
||||||
return rq.RESPONSE.redirect(urlBack)
|
return self.goto(urlBack)
|
||||||
|
|
||||||
def callAppySelect(self, selectMethod, brains):
|
def callAppySelect(self, selectMethod, brains):
|
||||||
'''Selects objects from a Reference field.'''
|
'''Selects objects from a Reference field.'''
|
||||||
|
@ -616,11 +665,14 @@ class AbstractMixin:
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getCssClasses(self, appyType, asSlave=True):
|
def getCssClasses(self, appyType, asSlave=True):
|
||||||
'''Gets the CSS classes (used for master/slave relationships) for this
|
'''Gets the CSS classes (used for master/slave relationships, or if the
|
||||||
object, either as slave (p_asSlave=True) either as master. The HTML
|
field corresponding to p_appyType is focus) for this object,
|
||||||
element on which to define the CSS class for a slave or a master is
|
either as slave (p_asSlave=True) or as master. The HTML element on
|
||||||
|
which to define the CSS class for a slave or a master is
|
||||||
different. So this method is called either for getting CSS classes
|
different. So this method is called either for getting CSS classes
|
||||||
as slave or as master.'''
|
as slave or as master. We set the focus-specific CSS class only when
|
||||||
|
p_asSlave is True, because we this place as being the "standard" one
|
||||||
|
for specifying CSS classes for a field.'''
|
||||||
res = ''
|
res = ''
|
||||||
if not asSlave and appyType['slaves']:
|
if not asSlave and appyType['slaves']:
|
||||||
res = 'appyMaster master_%s' % appyType['id']
|
res = 'appyMaster master_%s' % appyType['id']
|
||||||
|
@ -628,6 +680,11 @@ class AbstractMixin:
|
||||||
res = 'slave_%s' % appyType['master'].id
|
res = 'slave_%s' % appyType['master'].id
|
||||||
res += ' slaveValue_%s_%s' % (appyType['master'].id,
|
res += ' slaveValue_%s_%s' % (appyType['master'].id,
|
||||||
appyType['masterValue'])
|
appyType['masterValue'])
|
||||||
|
# Add the focus-specific class if needed
|
||||||
|
if appyType['focus']:
|
||||||
|
prefix = ''
|
||||||
|
if res: prefix = ' '
|
||||||
|
res += prefix + 'appyFocus'
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def fieldValueSelected(self, fieldName, value, vocabValue):
|
def fieldValueSelected(self, fieldName, value, vocabValue):
|
||||||
|
@ -999,9 +1056,18 @@ class AbstractMixin:
|
||||||
exec 'self.set%s%s([])' % (fieldName[0].upper(),
|
exec 'self.set%s%s([])' % (fieldName[0].upper(),
|
||||||
fieldName[1:])
|
fieldName[1:])
|
||||||
|
|
||||||
def getUrl(self):
|
def getUrl(self, t='view', **kwargs):
|
||||||
'''This method returns the URL of the consult view for this object.'''
|
'''This method returns various URLs about this object.'''
|
||||||
return self.absolute_url() + '/skyn/view'
|
baseUrl = self.absolute_url()
|
||||||
|
params = ''
|
||||||
|
for k, v in kwargs.iteritems(): params += '&%s=%s' % (k, v)
|
||||||
|
params = params[1:]
|
||||||
|
if t == 'showRef':
|
||||||
|
chunk = '/skyn/ajax?objectUid=%s&page=ref&' \
|
||||||
|
'macro=showReferenceContent&' % self.UID()
|
||||||
|
return baseUrl + chunk + params
|
||||||
|
else: # We consider t=='view'
|
||||||
|
return baseUrl + '/skyn/view' + params
|
||||||
|
|
||||||
def translate(self, label, mapping={}, domain=None, default=None):
|
def translate(self, label, mapping={}, domain=None, default=None):
|
||||||
'''Translates a given p_label into p_domain with p_mapping.'''
|
'''Translates a given p_label into p_domain with p_mapping.'''
|
||||||
|
|
25
gen/plone25/skin/ajax.pt
Normal file
25
gen/plone25/skin/ajax.pt
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<tal:comment replace="nothing">
|
||||||
|
This page is called by a XmlHttpRequest object. It requires parameters "page" and "macro":
|
||||||
|
they are used to call the macro that will render the HTML chunk to be returned to the browser.
|
||||||
|
It also requires parameters "objectUid", which is the UID of the related object. The object will
|
||||||
|
be available to the macro as "contextObj".
|
||||||
|
It can also have a parameter "action", that refers to a method that will be triggered on
|
||||||
|
contextObj before returning the result of the macro to the browser.
|
||||||
|
</tal:comment>
|
||||||
|
<tal:ajax define="page request/page;
|
||||||
|
macro request/macro;
|
||||||
|
macroPath python: 'here/%s/macros/%s' % (page, macro);
|
||||||
|
contextObj python: context.uid_catalog(UID=request['objectUid'])[0].getObject();
|
||||||
|
action request/action|nothing;
|
||||||
|
response request/RESPONSE;
|
||||||
|
member context/portal_membership/getAuthenticatedMember;
|
||||||
|
portal context/portal_url/getPortalObject;
|
||||||
|
portal_url context/portal_url/getPortalPath;
|
||||||
|
dummy python:response.setHeader('Content-Type','text/html;;charset=utf-8');
|
||||||
|
dummy2 python:response.setHeader('Expires', 'Mon, 11 Dec 1975 12:05:05 GMT');
|
||||||
|
dummy3 python:response.setHeader('CacheControl', 'no-cache')">
|
||||||
|
<tal:executeAction condition="action">
|
||||||
|
<tal:do define="dummy python: contextObj.getAppyAttribute('on'+action)()" omit-tag=""/>
|
||||||
|
</tal:executeAction>
|
||||||
|
<metal:callMacro use-macro="python: context.get(page).macros.get(macro)"/>
|
||||||
|
</tal:ajax>
|
BIN
gen/plone25/skin/arrowLeftDouble.png
Normal file
BIN
gen/plone25/skin/arrowLeftDouble.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 212 B |
BIN
gen/plone25/skin/arrowLeftSimple.png
Normal file
BIN
gen/plone25/skin/arrowLeftSimple.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 218 B |
BIN
gen/plone25/skin/arrowRightDouble.png
Normal file
BIN
gen/plone25/skin/arrowRightDouble.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 212 B |
BIN
gen/plone25/skin/arrowRightSimple.png
Normal file
BIN
gen/plone25/skin/arrowRightSimple.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 216 B |
|
@ -74,7 +74,7 @@
|
||||||
<form name="edit_form" method="post" enctype="multipart/form-data"
|
<form name="edit_form" method="post" enctype="multipart/form-data"
|
||||||
class="enableUnloadProtection atBaseEditForm"
|
class="enableUnloadProtection atBaseEditForm"
|
||||||
tal:attributes="action python: contextObj.absolute_url()+'/skyn/do'">
|
tal:attributes="action python: contextObj.absolute_url()+'/skyn/do'">
|
||||||
<div metal:use-macro="here/skyn/macros/macros/listFields" />
|
<div metal:use-macro="here/skyn/macros/macros/listFields" /><br/>
|
||||||
<input type="hidden" name="action" value="Update"/>
|
<input type="hidden" name="action" value="Update"/>
|
||||||
<input type="hidden" name="fieldset" tal:attributes="value fieldset"/>
|
<input type="hidden" name="fieldset" tal:attributes="value fieldset"/>
|
||||||
<input type="hidden" name="pageName" tal:attributes="value pageName"/>
|
<input type="hidden" name="pageName" tal:attributes="value pageName"/>
|
||||||
|
|
|
@ -107,7 +107,10 @@
|
||||||
<input type="hidden" name="action" value="ExecuteAppyAction"/>
|
<input type="hidden" name="action" value="ExecuteAppyAction"/>
|
||||||
<input type="hidden" name="objectUid" tal:attributes="value contextObj/UID"/>
|
<input type="hidden" name="objectUid" tal:attributes="value contextObj/UID"/>
|
||||||
<input type="hidden" name="fieldName" tal:attributes="value field/getName"/>
|
<input type="hidden" name="fieldName" tal:attributes="value field/getName"/>
|
||||||
<input type="submit" name="do" tal:attributes="value label"/>
|
<input type="submit" name="do" tal:attributes="value label" onClick="javascript:;"/>
|
||||||
|
<tal:comment replace="nothing">The previous onClick is simply used to prevent Plone
|
||||||
|
from adding a CSS class that displays a popup when the user triggers the form multiple
|
||||||
|
times.</tal:comment>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -115,7 +118,8 @@
|
||||||
tal:define="field fieldDescr/atField|widgetDescr/atField;
|
tal:define="field fieldDescr/atField|widgetDescr/atField;
|
||||||
appyType fieldDescr/appyType|widgetDescr/appyType;
|
appyType fieldDescr/appyType|widgetDescr/appyType;
|
||||||
showLabel showLabel|python:True;
|
showLabel showLabel|python:True;
|
||||||
label python: tool.translate(field.widget.label_msgid);
|
labelId field/widget/label_msgid;
|
||||||
|
label python: tool.translate(labelId);
|
||||||
descrId field/widget/description_msgid|python:'';
|
descrId field/widget/description_msgid|python:'';
|
||||||
description python: tool.translate(descrId)"
|
description python: tool.translate(descrId)"
|
||||||
tal:attributes="class python: contextObj.getCssClasses(appyType, asSlave=True)">
|
tal:attributes="class python: contextObj.getCssClasses(appyType, asSlave=True)">
|
||||||
|
@ -157,8 +161,7 @@
|
||||||
<tal:comment replace="nothing">For other fields like Refs we use specific view/edit macros.</tal:comment>
|
<tal:comment replace="nothing">For other fields like Refs we use specific view/edit macros.</tal:comment>
|
||||||
<tal:viewRef condition="python: (not isEdit) and (appyType['type'] == 'Ref')">
|
<tal:viewRef condition="python: (not isEdit) and (appyType['type'] == 'Ref')">
|
||||||
<tal:ref define="isBack python:False;
|
<tal:ref define="isBack python:False;
|
||||||
fieldRel python:field.relationship;
|
fieldName field/getName;
|
||||||
objs python:contextObj.getAppyRefs(field.getName());
|
|
||||||
innerRef innerRef|python:False">
|
innerRef innerRef|python:False">
|
||||||
<metal:viewRef use-macro="here/skyn/ref/macros/showReference" />
|
<metal:viewRef use-macro="here/skyn/ref/macros/showReference" />
|
||||||
</tal:ref>
|
</tal:ref>
|
||||||
|
@ -186,10 +189,9 @@
|
||||||
<div metal:define-macro="showBackwardField"
|
<div metal:define-macro="showBackwardField"
|
||||||
tal:define="isBack python:True;
|
tal:define="isBack python:True;
|
||||||
appyType widgetDescr/appyType;
|
appyType widgetDescr/appyType;
|
||||||
fieldRel widgetDescr/fieldRel;
|
fieldName widgetDescr/fieldRel;
|
||||||
objs python:contextObj.getBRefs(fieldRel);
|
labelId python: '%s_%s_back' % (contextObj.meta_type, appyType['backd']['attribute']);
|
||||||
label python:contextObj.translate('%s_%s_back' % (contextObj.meta_type, appyType['backd']['attribute']));
|
descrId python: '';
|
||||||
description python:'';
|
|
||||||
innerRef innerRef|python:False">
|
innerRef innerRef|python:False">
|
||||||
<div metal:use-macro="here/skyn/ref/macros/showReference" />
|
<div metal:use-macro="here/skyn/ref/macros/showReference" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -309,6 +311,63 @@
|
||||||
<tal:comment replace="nothing">"Static" javascripts</tal:comment>
|
<tal:comment replace="nothing">"Static" javascripts</tal:comment>
|
||||||
<script language="javascript">
|
<script language="javascript">
|
||||||
<!--
|
<!--
|
||||||
|
// AJAX machinery
|
||||||
|
var xhrObjects = new Array(); // An array of XMLHttpRequest objects
|
||||||
|
function XhrObject() { // Wraps a XmlHttpRequest object
|
||||||
|
this.freed = 1; // Is this xhr object already dealing with a request or not?
|
||||||
|
this.xhr = false;
|
||||||
|
if (window.XMLHttpRequest) this.xhr = new XMLHttpRequest();
|
||||||
|
else this.xhr = new ActiveXObject("Microsoft.XMLHTTP");
|
||||||
|
this.hook = ''; // The ID of the HTML element in the page that will be
|
||||||
|
// replaced by result of executing the Ajax request.
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAjaxChunk(pos) {
|
||||||
|
// This function is the callback called by the AJAX machinery (see function
|
||||||
|
// askAjaxChunk below) when an Ajax response is available.
|
||||||
|
// First, find back the correct XMLHttpRequest object
|
||||||
|
if ( (typeof(xhrObjects[pos]) != 'undefined') &&
|
||||||
|
(xhrObjects[pos].freed == 0)) {
|
||||||
|
var hook = xhrObjects[pos].hook;
|
||||||
|
if (xhrObjects[pos].xhr.readyState == 1) {
|
||||||
|
// The request has been initialized: display the waiting radar
|
||||||
|
var hookElem = document.getElementById(hook);
|
||||||
|
if (hookElem) hookElem.innerHTML = "<div align=\"center\"><img src=\"skyn/waiting.gif\"/><\/div>";
|
||||||
|
}
|
||||||
|
if (xhrObjects[pos].xhr.readyState == 4) {
|
||||||
|
// We have received the HTML chunk
|
||||||
|
var hookElem = document.getElementById(hook);
|
||||||
|
if (hookElem && (xhrObjects[pos].xhr.status == 200)) {
|
||||||
|
hookElem.innerHTML = xhrObjects[pos].xhr.responseText;
|
||||||
|
}
|
||||||
|
xhrObjects[pos].freed = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function askAjaxChunk(hook, url) {
|
||||||
|
// This function will ask to get a chunk of HTML on the server by
|
||||||
|
// triggering a XMLHttpRequest.
|
||||||
|
// First, get a non-busy XMLHttpRequest object.
|
||||||
|
var pos = -1;
|
||||||
|
for (var i=0; i < xhrObjects.length; i++) {
|
||||||
|
if (xhrObjects[i].freed == 1) { pos = i; break; }
|
||||||
|
}
|
||||||
|
if (pos == -1) {
|
||||||
|
pos = xhrObjects.length;
|
||||||
|
xhrObjects[pos] = new XhrObject();
|
||||||
|
}
|
||||||
|
xhrObjects[pos].hook = hook;
|
||||||
|
if (xhrObjects[pos].xhr) {
|
||||||
|
xhrObjects[pos].freed = 0;
|
||||||
|
// Perform the asynchronous HTTP GET
|
||||||
|
xhrObjects[pos].xhr.open('GET', url, true);
|
||||||
|
xhrObjects[pos].xhr.onreadystatechange = function() { getAjaxChunk(pos); }
|
||||||
|
if (window.XMLHttpRequest) { xhrObjects[pos].xhr.send(null); }
|
||||||
|
else if (window.ActiveXObject) { xhrObjects[pos].xhr.send(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Function used by checkbox widgets for having radio-button-like behaviour
|
// Function used by checkbox widgets for having radio-button-like behaviour
|
||||||
function toggleCheckbox(visibleCheckbox, hiddenBoolean) {
|
function toggleCheckbox(visibleCheckbox, hiddenBoolean) {
|
||||||
vis = document.getElementById(visibleCheckbox);
|
vis = document.getElementById(visibleCheckbox);
|
||||||
|
@ -805,3 +864,47 @@
|
||||||
<metal:phases use-macro="here/skyn/macros/macros/phases"/>
|
<metal:phases use-macro="here/skyn/macros/macros/phases"/>
|
||||||
</dt>
|
</dt>
|
||||||
</metal:portletContent>
|
</metal:portletContent>
|
||||||
|
|
||||||
|
<tal:comment replace="nothing">
|
||||||
|
Buttons for navigating among a list of elements (next, back, first, last, etc).
|
||||||
|
</tal:comment>
|
||||||
|
<metal:appyNavigate define-macro="appyNavigate" tal:condition="python: totalNumber > batchSize">
|
||||||
|
<table cellpadding="0" cellspacing="0" align="right" class="appyNav"
|
||||||
|
tal:define="baseUrl python: contextObj.getUrl('showRef', **{'fieldName': fieldName, 'isBack': isBack, 'innerRef': innerRef, 'labelId': labelId, 'descrId': descrId}) + '&%s_startNumber=' % ajaxHookId">
|
||||||
|
<tr>
|
||||||
|
<tal:comment replace="nothing">Go to the first page</tal:comment>
|
||||||
|
<td><img style="cursor:pointer" tal:condition="python: (startNumber != 0) and (startNumber != batchSize)"
|
||||||
|
tal:attributes="src string: $portal_url/skyn/arrowLeftDouble.png;
|
||||||
|
title python: tool.translate('goto_first');
|
||||||
|
onClick python: 'askAjaxChunk(\'%s\', \'%s\')' % (ajaxHookId, baseUrl+'0')"/></td>
|
||||||
|
<tal:comment replace="nothing">Go to the previous page</tal:comment>
|
||||||
|
<td><img style="cursor:pointer" tal:condition="python: startNumber != 0"
|
||||||
|
tal:define="sNumber python: startNumber - batchSize"
|
||||||
|
tal:attributes="src string: $portal_url/skyn/arrowLeftSimple.png;
|
||||||
|
title python: tool.translate('goto_previous');
|
||||||
|
onClick python: 'askAjaxChunk(\'%s\', \'%s\')' % (ajaxHookId, baseUrl+str(sNumber))"/></td>
|
||||||
|
<tal:comment replace="nothing">Explain which elements are currently shown</tal:comment>
|
||||||
|
<td class="discreet">
|
||||||
|
<span tal:replace="python: startNumber+1"/>
|
||||||
|
<img tal:attributes="src string: $portal_url/skyn/to.png"/>
|
||||||
|
<span tal:replace="python: startNumber+len(objs)"/>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<tal:comment replace="nothing">Go to the next page</tal:comment>
|
||||||
|
<td><img style="cursor:pointer" tal:condition="python: sNumber < totalNumber"
|
||||||
|
tal:define="sNumber python: startNumber + batchSize"
|
||||||
|
tal:attributes="src string: $portal_url/skyn/arrowRightSimple.png;
|
||||||
|
title python: tool.translate('goto_next');
|
||||||
|
onClick python: 'askAjaxChunk(\'%s\', \'%s\')' % (ajaxHookId, baseUrl+str(sNumber))"/></td>
|
||||||
|
<tal:comment replace="nothing">Go to the last page</tal:comment>
|
||||||
|
<td><img style="cursor:pointer" tal:condition="python: (startNumber != sNumber) and (startNumber != sNumber-batchSize)"
|
||||||
|
tal:define="lastPageIsIncomplete python: totalNumber % batchSize;
|
||||||
|
nbOfCompletePages python: totalNumber/batchSize;
|
||||||
|
nbOfCountedPages python: test(lastPageIsIncomplete, nbOfCompletePages, nbOfCompletePages-1);
|
||||||
|
sNumber python: (nbOfCountedPages*batchSize)"
|
||||||
|
tal:attributes="src string: $portal_url/skyn/arrowRightDouble.png;
|
||||||
|
title python: tool.translate('goto_last');
|
||||||
|
onClick python: 'askAjaxChunk(\'%s\', \'%s\')' % (ajaxHookId, baseUrl+str(sNumber))"/></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</metal:appyNavigate>
|
||||||
|
|
|
@ -21,31 +21,28 @@
|
||||||
<img src="edit.gif" title="label_edit" i18n:domain="plone" i18n:attributes="title" />
|
<img src="edit.gif" title="label_edit" i18n:domain="plone" i18n:attributes="title" />
|
||||||
</a></td>
|
</a></td>
|
||||||
<tal:comment replace="nothing">Delete the element</tal:comment>
|
<tal:comment replace="nothing">Delete the element</tal:comment>
|
||||||
<td class="noPadding"><a tal:attributes="href python: obj.absolute_url() + '/delete_confirmation'"
|
<td class="noPadding">
|
||||||
tal:condition="python: member.has_permission('Delete objects', obj)">
|
<img tal:condition="python: member.has_permission('Delete objects', obj)"
|
||||||
<img src="delete_icon.gif" title="label_remove" i18n:domain="plone" i18n:attributes="title" />
|
src="delete_icon.gif" title="Delete" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
|
||||||
</a></td>
|
tal:attributes="onClick python:'javascript:onDeleteObject(\'%s\')' % obj.UID()"/>
|
||||||
|
</td>
|
||||||
<tal:comment replace="nothing">Arrows for moving objects up or down</tal:comment>
|
<tal:comment replace="nothing">Arrows for moving objects up or down</tal:comment>
|
||||||
<td class="noPadding" tal:condition="python: len(objs)>1">
|
<td class="noPadding" tal:condition="python: (len(objs)>1) and member.has_permission('Modify portal content', obj)">
|
||||||
<form tal:condition="python: member.has_permission('Modify portal content', obj)"
|
<tal:moveRef define="objectIndex python:contextObj.getAppyRefIndex(fieldName, obj);
|
||||||
tal:attributes="action python: contextObj.absolute_url() + '/skyn/do'"
|
baseUrl python: contextObj.getUrl('showRef', **{'fieldName': fieldName, 'isBack': isBack, 'innerRef': innerRef, 'labelId': labelId, 'descrId': descrId, '%s_startNumber' % ajaxHookId: startNumber, 'action':'ChangeRefOrder', 'refObjectUid': obj.UID()})">
|
||||||
tal:define="objectIndex python:contextObj.getAppyRefIndex(field.getName(), obj)">
|
<tal:comment replace="nothing">Move up</tal:comment>
|
||||||
<input type="hidden" name="action" value="ChangeRefOrder"/>
|
<img tal:define="ajaxUrl python: baseUrl + '&move=up'" tal:condition="python: objectIndex > 0"
|
||||||
<input type="hidden" name="fieldName" tal:attributes="value field/getName"/>
|
tal:attributes="src string: $portal_url/skyn/arrowUp.png;
|
||||||
<input type="hidden" name="refObjectUid" tal:attributes="value obj/UID"/>
|
title python: tool.translate('move_up');
|
||||||
<tal:comment replace="nothing">Arrow up</tal:comment>
|
onClick python: 'askAjaxChunk(\'%s\', \'%s\')' % (ajaxHookId, ajaxUrl)"
|
||||||
<span tal:condition="python: objectIndex > 0">
|
style="cursor:pointer"/>
|
||||||
<input type="image" name="moveUp" class="imageInput"
|
<tal:comment replace="nothing">Move down</tal:comment>
|
||||||
tal:attributes="src string: $portal_url/skyn/arrowUp.png;
|
<img tal:define="ajaxUrl python: baseUrl + '&move=down'" tal:condition="python: objectIndex < (totalNumber-1)"
|
||||||
title python: tool.translate('move_up')"/>
|
tal:attributes="src string: $portal_url/skyn/arrowDown.png;
|
||||||
</span>
|
title python: tool.translate('move_down');
|
||||||
<tal:comment replace="nothing">Arrow down</tal:comment>
|
onClick python: 'askAjaxChunk(\'%s\', \'%s\')' % (ajaxHookId, ajaxUrl)"
|
||||||
<span tal:condition="python: objectIndex < (len(objs)-1)">
|
style="cursor:pointer"/>
|
||||||
<input type="image" name="moveDown" class="imageInput"
|
</tal:moveRef>
|
||||||
tal:attributes="src string: $portal_url/skyn/arrowDown.png;
|
|
||||||
title python: tool.translate('move_down')"/>
|
|
||||||
</span>
|
|
||||||
</form>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -58,18 +55,59 @@
|
||||||
<img style="cursor:pointer" tal:condition="showPlusIcon"
|
<img style="cursor:pointer" tal:condition="showPlusIcon"
|
||||||
tal:attributes="src string:$portal_url/skyn/plus.png;
|
tal:attributes="src string:$portal_url/skyn/plus.png;
|
||||||
title python: tool.translate('add_ref');
|
title python: tool.translate('add_ref');
|
||||||
onClick python: 'href: window.location=\'%s/skyn/do?action=Create&initiator=%s&field=%s&type_name=%s\'' % (folder.absolute_url(), contextObj.UID(), field.getName(), linkedPortalType)"/>
|
onClick python: 'href: window.location=\'%s/skyn/do?action=Create&initiator=%s&field=%s&type_name=%s\'' % (folder.absolute_url(), contextObj.UID(), fieldName, linkedPortalType)"/>
|
||||||
</metal:plusIcon>
|
</metal:plusIcon>
|
||||||
|
|
||||||
|
<tal:comment replace="nothing">
|
||||||
|
This macro shows a reference field. More precisely, it shows nothing, but calls
|
||||||
|
a Javascript function that will asynchonously call (via a XmlHttpRequest object) the
|
||||||
|
macro 'showReferenceContent' defined below, that will really show content.
|
||||||
|
It requires:
|
||||||
|
- isBack (bool) Is the reference a backward or forward reference?
|
||||||
|
- fieldName (string) The name of the reference field (if it is a forward reference)
|
||||||
|
or the name of the Archetypes relationship (if it is a backward reference)
|
||||||
|
- innerRef (bool) Are we rendering a reference within a reference or not?
|
||||||
|
- contextObj (object) the object from which the reference starts
|
||||||
|
- labelId (string) the i18n id of the reference field label
|
||||||
|
- descrId (string) the i18n id of the reference field description
|
||||||
|
</tal:comment>
|
||||||
<div metal:define-macro="showReference"
|
<div metal:define-macro="showReference"
|
||||||
tal:define="folder python: test(contextObj.isPrincipiaFolderish, contextObj, contextObj.getParentNode());
|
tal:define="ajaxHookId python: contextObj.UID()+fieldName;
|
||||||
|
ajaxUrl python: contextObj.getUrl('showRef', **{'fieldName': fieldName, 'isBack': isBack, 'innerRef': innerRef, 'labelId': labelId, 'descrId': descrId})"
|
||||||
|
tal:attributes="id ajaxHookId">
|
||||||
|
<script language="javascript"
|
||||||
|
tal:content="python: 'askAjaxChunk(\'%s\',\'%s\')' % (ajaxHookId, ajaxUrl)">
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<tal:comment replace="nothing">
|
||||||
|
This macro is called by a XmlHttpRequest for displaying the paginated referred objects
|
||||||
|
of a reference field.
|
||||||
|
</tal:comment>
|
||||||
|
<div metal:define-macro="showReferenceContent"
|
||||||
|
tal:define="fieldName request/fieldName;
|
||||||
|
isBack python: test(request['isBack']=='True', True, False);
|
||||||
|
innerRef python: test(request['innerRef']=='True', True, False);
|
||||||
|
labelId request/labelId;
|
||||||
|
descrId request/descrId;
|
||||||
|
ajaxHookId python: contextObj.UID()+fieldName;
|
||||||
|
startNumber python: int(request.get('%s_startNumber' % ajaxHookId, 0));
|
||||||
|
appyType python: contextObj.getAppyType(fieldName, not isBack);
|
||||||
|
tool contextObj/getTool;
|
||||||
|
refObjects python:contextObj.getAppyRefs(fieldName, not isBack, startNumber);
|
||||||
|
objs refObjects/objects;
|
||||||
|
totalNumber refObjects/totalNumber;
|
||||||
|
batchSize refObjects/batchSize;
|
||||||
|
folder python: test(contextObj.isPrincipiaFolderish, contextObj, contextObj.getParentNode());
|
||||||
flavour python:tool.getFlavour(contextObj);
|
flavour python:tool.getFlavour(contextObj);
|
||||||
linkedPortalType python:flavour.getPortalType(appyType['klass']);
|
linkedPortalType python:flavour.getPortalType(appyType['klass']);
|
||||||
addPermission python: '%s: Add %s' % (appName, linkedPortalType);
|
addPermission python: '%s: Add %s' % (tool.getAppName(), linkedPortalType);
|
||||||
multiplicity python:test(isBack, appyType['backd']['multiplicity'], appyType['multiplicity']);
|
multiplicity python:test(isBack, appyType['backd']['multiplicity'], appyType['multiplicity']);
|
||||||
maxReached python:(multiplicity[1] != None) and (len(objs) >= multiplicity[1]);
|
maxReached python:(multiplicity[1] != None) and (len(objs) >= multiplicity[1]);
|
||||||
showPlusIcon python:not isBack and appyType['add'] and not maxReached and member.has_permission(addPermission, folder);
|
showPlusIcon python:not isBack and appyType['add'] and not maxReached and member.has_permission(addPermission, folder);
|
||||||
atMostOneRef python: (multiplicity[1] == 1) and (len(objs)<=1)">
|
atMostOneRef python: (multiplicity[1] == 1) and (len(objs)<=1);
|
||||||
|
label python: tool.translate(labelId);
|
||||||
|
description python: tool.translate(descrId)">
|
||||||
|
|
||||||
<tal:comment replace="nothing">This macro displays the Reference widget on a "consult" page.
|
<tal:comment replace="nothing">This macro displays the Reference widget on a "consult" page.
|
||||||
|
|
||||||
|
@ -106,6 +144,7 @@
|
||||||
<fieldset tal:attributes="class python:test(innerRef, 'innerAppyFieldset', '')">
|
<fieldset tal:attributes="class python:test(innerRef, 'innerAppyFieldset', '')">
|
||||||
<legend tal:condition="python: not innerRef or showPlusIcon">
|
<legend tal:condition="python: not innerRef or showPlusIcon">
|
||||||
<span tal:condition="not: innerRef" tal:content="label"/>
|
<span tal:condition="not: innerRef" tal:content="label"/>
|
||||||
|
<tal:numberOfRefs>(<span tal:replace="totalNumber"/>)</tal:numberOfRefs>
|
||||||
<metal:plusIcon use-macro="here/skyn/ref/macros/plusIcon"/>
|
<metal:plusIcon use-macro="here/skyn/ref/macros/plusIcon"/>
|
||||||
</legend>
|
</legend>
|
||||||
|
|
||||||
|
@ -113,6 +152,9 @@
|
||||||
<p tal:condition="python: not innerRef and description"
|
<p tal:condition="python: not innerRef and description"
|
||||||
tal:content="description" class="discreet" ></p>
|
tal:content="description" class="discreet" ></p>
|
||||||
|
|
||||||
|
<tal:comment replace="nothing">Appy (top) navigation</tal:comment>
|
||||||
|
<metal:nav use-macro="here/skyn/macros/macros/appyNavigate"/>
|
||||||
|
|
||||||
<tal:comment replace="nothing">No object is present</tal:comment>
|
<tal:comment replace="nothing">No object is present</tal:comment>
|
||||||
<p tal:condition="not:objs" tal:content="python: tool.translate('no_ref')"></p>
|
<p tal:condition="not:objs" tal:content="python: tool.translate('no_ref')"></p>
|
||||||
|
|
||||||
|
@ -167,9 +209,8 @@
|
||||||
</tal:showNormalField>
|
</tal:showNormalField>
|
||||||
<tal:showRef condition="python: appyType['type'] == 'Ref'">
|
<tal:showRef condition="python: appyType['type'] == 'Ref'">
|
||||||
<tal:ref tal:define="isBack python:appyType['isBack'];
|
<tal:ref tal:define="isBack python:appyType['isBack'];
|
||||||
fieldRel python:field.relationship;
|
fieldName python: test(isBack, field.relationship, field.getName());
|
||||||
objs python:contextObj.getAppyRefs(field.getName());
|
innerRef python:True">
|
||||||
innerRef python:True">
|
|
||||||
<metal:showField use-macro="here/skyn/ref/macros/showReference" />
|
<metal:showField use-macro="here/skyn/ref/macros/showReference" />
|
||||||
</tal:ref>
|
</tal:ref>
|
||||||
</tal:showRef>
|
</tal:showRef>
|
||||||
|
@ -190,6 +231,10 @@
|
||||||
|
|
||||||
</td></tr>
|
</td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<tal:comment replace="nothing">Appy (bottom) navigation</tal:comment>
|
||||||
|
<metal:nav use-macro="here/skyn/macros/macros/appyNavigate"/>
|
||||||
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<tal:comment replace="nothing">A carriage return needed in some cases.</tal:comment>
|
<tal:comment replace="nothing">A carriage return needed in some cases.</tal:comment>
|
||||||
<br tal:define="widgetDescr widgetDescr|nothing"
|
<br tal:define="widgetDescr widgetDescr|nothing"
|
||||||
|
@ -202,7 +247,7 @@
|
||||||
appyType python:here.getAppyType(field.getName());
|
appyType python:here.getAppyType(field.getName());
|
||||||
allBrains python:here.uid_catalog(portal_type=refPortalType);
|
allBrains python:here.uid_catalog(portal_type=refPortalType);
|
||||||
brains python:here.callAppySelect(appyType['select'], allBrains);
|
brains python:here.callAppySelect(appyType['select'], allBrains);
|
||||||
refUids python: [o.UID() for o in here.getAppyRefs(field.getName())];
|
refUids python: [o.UID() for o in here.getAppyRefs(field.getName())['objects']];
|
||||||
isMultiple python:test(appyType['multiplicity'][1]!=1, 'multiple', '');
|
isMultiple python:test(appyType['multiplicity'][1]!=1, 'multiple', '');
|
||||||
appyFieldName python: 'appy_ref_%s' % field.getName();
|
appyFieldName python: 'appy_ref_%s' % field.getName();
|
||||||
inError python:test(errors.has_key(field.getName()), True, False);
|
inError python:test(errors.has_key(field.getName()), True, False);
|
||||||
|
|
BIN
gen/plone25/skin/to.png
Normal file
BIN
gen/plone25/skin/to.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 214 B |
BIN
gen/plone25/skin/waiting.gif
Executable file
BIN
gen/plone25/skin/waiting.gif
Executable file
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
|
@ -10,6 +10,14 @@
|
||||||
float:right;
|
float:right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.appyNav {
|
||||||
|
padding: 0.4em 0 0.4em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appyFocus {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
#importedElem {
|
#importedElem {
|
||||||
color: grey;
|
color: grey;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
|
|
|
@ -142,6 +142,11 @@ class AbstractWrapper:
|
||||||
del kwargs['id']
|
del kwargs['id']
|
||||||
else:
|
else:
|
||||||
objId = '%s.%f' % (idPrefix, time.time())
|
objId = '%s.%f' % (idPrefix, time.time())
|
||||||
|
# Determine if object must be created from external data
|
||||||
|
externalData = None
|
||||||
|
if kwargs.has_key('_data'):
|
||||||
|
externalData = kwargs['_data']
|
||||||
|
del kwargs['_data']
|
||||||
# Where must I create the object?
|
# Where must I create the object?
|
||||||
if not isField:
|
if not isField:
|
||||||
folder = self.o.getTool().getAppFolder()
|
folder = self.o.getTool().getAppFolder()
|
||||||
|
@ -173,7 +178,9 @@ class AbstractWrapper:
|
||||||
self.o.reindexObject()
|
self.o.reindexObject()
|
||||||
# Call custom initialization
|
# Call custom initialization
|
||||||
try:
|
try:
|
||||||
appyObj.onEdit(True)
|
if externalData: param = externalData
|
||||||
|
else: param = True
|
||||||
|
appyObj.onEdit(param)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
ploneObj.reindexObject()
|
ploneObj.reindexObject()
|
||||||
|
@ -210,6 +217,15 @@ class AbstractWrapper:
|
||||||
wfTool.doActionFor(self.o, transitionName, comment=comment)
|
wfTool.doActionFor(self.o, transitionName, comment=comment)
|
||||||
del self.o._v_appy_do
|
del self.o._v_appy_do
|
||||||
|
|
||||||
|
def log(self, message, logLevel='info'):
|
||||||
|
'''Logs a message in the log file. p_logLevel may be "info", "warning"
|
||||||
|
or "error".'''
|
||||||
|
logger = self.o.getProductConfig().logger
|
||||||
|
if logLevel == 'warning': logMethod = logger.warn
|
||||||
|
elif logLevel == 'error': logMethod = logger.error
|
||||||
|
else: logMethod = logger.info
|
||||||
|
logMethod(message)
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
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
|
||||||
|
@ -246,12 +262,21 @@ class FileWrapper:
|
||||||
'''Writes the file on disk. If p_filePath is specified, it is the
|
'''Writes the file on disk. If p_filePath is specified, it is the
|
||||||
path name where the file will be dumped; folders mentioned in it
|
path name where the file will be dumped; folders mentioned in it
|
||||||
must exist. If not, the file will be dumped in the OS temp folder.
|
must exist. If not, the file will be dumped in the OS temp folder.
|
||||||
The absoulte path name of the dumped file is returned.'''
|
The absolute path name of the dumped file is returned.'''
|
||||||
if not filePath:
|
if not filePath:
|
||||||
filePath = '%s/file%f.%s' % (getOsTempFolder(), time.time(),
|
filePath = '%s/file%f.%s' % (getOsTempFolder(), time.time(),
|
||||||
self.name)
|
self.name)
|
||||||
f = file(filePath, 'w')
|
f = file(filePath, 'w')
|
||||||
f.write(self.content)
|
if self.content.__class__.__name__ == 'Pdata':
|
||||||
|
# The file content is splitted in several chunks.
|
||||||
|
f.write(self.content.data)
|
||||||
|
nextPart = self.content.next
|
||||||
|
while nextPart:
|
||||||
|
f.write(nextPart.data)
|
||||||
|
nextPart = nextPart.next
|
||||||
|
else:
|
||||||
|
# Only one chunk
|
||||||
|
f.write(self.content)
|
||||||
f.close()
|
f.close()
|
||||||
return filePath
|
return filePath
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -81,6 +81,10 @@ class PoMessage:
|
||||||
NO_SELECTION = 'You must select at least one element.'
|
NO_SELECTION = 'You must select at least one element.'
|
||||||
DELETE_CONFIRM = 'Are you sure you want to delete this element?'
|
DELETE_CONFIRM = 'Are you sure you want to delete this element?'
|
||||||
DELETE_DONE = 'The element has been deleted.'
|
DELETE_DONE = 'The element has been deleted.'
|
||||||
|
GOTO_FIRST = 'Go to top'
|
||||||
|
GOTO_PREVIOUS = 'Go to previous'
|
||||||
|
GOTO_NEXT = 'Go to next'
|
||||||
|
GOTO_LAST = 'Go to end'
|
||||||
|
|
||||||
def __init__(self, id, msg, default, fuzzy=False, comments=[]):
|
def __init__(self, id, msg, default, fuzzy=False, comments=[]):
|
||||||
self.id = id
|
self.id = id
|
||||||
|
|
11
gen/utils.py
11
gen/utils.py
|
@ -170,4 +170,15 @@ class AppyRequest:
|
||||||
else:
|
else:
|
||||||
res = self.zopeRequest.get(attr, None)
|
res = self.zopeRequest.get(attr, None)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
class RefObjects:
|
||||||
|
'''Represents a bunch of objects retrieved from a reference.'''
|
||||||
|
def __init__(self, objects=None):
|
||||||
|
self.objects = objects or [] # The objects
|
||||||
|
self.totalNumber = len(self.objects) # self.objects may only represent a
|
||||||
|
# part of all available objects.
|
||||||
|
self.batchSize = self.totalNumber # The max length of self.objects.
|
||||||
|
self.startNumber = 0 # The index of first object in self.objects in
|
||||||
|
# the whole list.
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -21,6 +21,13 @@ import xml.sax
|
||||||
from xml.sax.handler import ContentHandler, ErrorHandler
|
from xml.sax.handler import ContentHandler, ErrorHandler
|
||||||
from xml.sax.xmlreader import InputSource
|
from xml.sax.xmlreader import InputSource
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
|
from appy.shared.errors import AppyError
|
||||||
|
|
||||||
|
# Error-related constants ------------------------------------------------------
|
||||||
|
CONVERSION_ERROR = '"%s" value "%s" could not be converted by the XML ' \
|
||||||
|
'unmarshaller.'
|
||||||
|
CUSTOM_CONVERSION_ERROR = 'Custom converter for "%s" values produced an ' \
|
||||||
|
'error while converting value "%s". %s'
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class XmlElement:
|
class XmlElement:
|
||||||
|
@ -150,7 +157,7 @@ class XmlUnmarshaller(XmlParser):
|
||||||
If "object" is specified, it means that the tag contains sub-tags, each
|
If "object" is specified, it means that the tag contains sub-tags, each
|
||||||
one corresponding to the value of an attribute for this object.
|
one corresponding to the value of an attribute for this object.
|
||||||
if "tuple" is specified, it will be converted to a list.'''
|
if "tuple" is specified, it will be converted to a list.'''
|
||||||
def __init__(self, klass=None, tagTypes={}):
|
def __init__(self, klass=None, tagTypes={}, conversionFunctions={}):
|
||||||
XmlParser.__init__(self)
|
XmlParser.__init__(self)
|
||||||
self.klass = klass # If a klass is given here, instead of creating
|
self.klass = klass # If a klass is given here, instead of creating
|
||||||
# a root UnmarshalledObject instance, we will create an instance of this
|
# a root UnmarshalledObject instance, we will create an instance of this
|
||||||
|
@ -167,6 +174,19 @@ class XmlUnmarshaller(XmlParser):
|
||||||
# it is not the case of p_xmlContent, you can provide the missing type
|
# it is not the case of p_xmlContent, you can provide the missing type
|
||||||
# information in p_tagTypes. Here is an example of p_tagTypes:
|
# information in p_tagTypes. Here is an example of p_tagTypes:
|
||||||
# {"information": "list", "days": "list", "person": "object"}.
|
# {"information": "list", "days": "list", "person": "object"}.
|
||||||
|
self.conversionFunctions = conversionFunctions
|
||||||
|
# The parser assumes that data is represented in some standard way. If
|
||||||
|
# it is not the case, you may provide, in this dict, custom functions
|
||||||
|
# allowing to convert values of basic types (long, float, DateTime...).
|
||||||
|
# Every such function must take a single arg which is the value to
|
||||||
|
# convert and return the converted value. Dict keys are strings
|
||||||
|
# representing types ('bool', 'int', 'unicode', etc) and dict values are
|
||||||
|
# conversion functions. Here is an example:
|
||||||
|
# {'int': convertInteger, 'DateTime': convertDate}
|
||||||
|
# NOTE: you can even invent a new basic type, put it in self.tagTypes,
|
||||||
|
# and create a specific conversionFunction for it. This way, you can
|
||||||
|
# for example convert strings that have specific values (in this case,
|
||||||
|
# knowing that the value is a 'string' is not sufficient).
|
||||||
|
|
||||||
def startDocument(self):
|
def startDocument(self):
|
||||||
self.res = None # The resulting web of Python objects
|
self.res = None # The resulting web of Python objects
|
||||||
|
@ -246,18 +266,37 @@ class XmlUnmarshaller(XmlParser):
|
||||||
def endElement(self, elem):
|
def endElement(self, elem):
|
||||||
e = XmlParser.endElement(self, elem)
|
e = XmlParser.endElement(self, elem)
|
||||||
if e.currentBasicType:
|
if e.currentBasicType:
|
||||||
# Get and convert the value of this field
|
value = e.currentContent.strip()
|
||||||
if e.currentBasicType in self.numericTypes:
|
if not value: value = None
|
||||||
try:
|
|
||||||
exec 'value = %s' % e.currentContent.strip()
|
|
||||||
except SyntaxError:
|
|
||||||
value = None
|
|
||||||
elif e.currentBasicType == 'DateTime':
|
|
||||||
value = DateTime(e.currentContent.strip())
|
|
||||||
elif e.currentBasicType == 'base64':
|
|
||||||
value = e.currentContent.decode('base64')
|
|
||||||
else:
|
else:
|
||||||
value = e.currentContent.strip()
|
# If we have a custom converter for values of this type, use it.
|
||||||
|
if self.conversionFunctions.has_key(e.currentBasicType):
|
||||||
|
try:
|
||||||
|
value = self.conversionFunctions[e.currentBasicType](
|
||||||
|
value)
|
||||||
|
except Exception, err:
|
||||||
|
raise AppyError(CUSTOM_CONVERSION_ERROR % (
|
||||||
|
e.currentBasicType, value, str(err)))
|
||||||
|
# If not, try a standard conversion
|
||||||
|
elif e.currentBasicType in self.numericTypes:
|
||||||
|
try:
|
||||||
|
exec 'value = %s' % value
|
||||||
|
except SyntaxError:
|
||||||
|
raise AppyError(CONVERSION_ERROR % (
|
||||||
|
e.currentBasicType, value))
|
||||||
|
except NameError:
|
||||||
|
raise AppyError(CONVERSION_ERROR % (
|
||||||
|
e.currentBasicType, value))
|
||||||
|
# Check that the value is of the correct type. For instance,
|
||||||
|
# a float value with a comma in it could have been converted
|
||||||
|
# to a tuple instead of a float.
|
||||||
|
if not isinstance(value, eval(e.currentBasicType)):
|
||||||
|
raise AppyError(CONVERSION_ERROR % (
|
||||||
|
e.currentBasicType, value))
|
||||||
|
elif e.currentBasicType == 'DateTime':
|
||||||
|
value = DateTime(value)
|
||||||
|
elif e.currentBasicType == 'base64':
|
||||||
|
value = e.currentContent.decode('base64')
|
||||||
# Store the value on the last container
|
# Store the value on the last container
|
||||||
self.storeValue(elem, value)
|
self.storeValue(elem, value)
|
||||||
# Clean the environment
|
# Clean the environment
|
||||||
|
|
Loading…
Reference in a new issue