appy.bin: backup.py: added field 'To' to mails sent by the backup procedure (so it not less directly considered as junk mail); bugfix in job.py used with Appy > 0.8; appy.gen: optimized performance (methods defined in 'show' attrs were called twice on edit.pt and view.pt); appy.gen: added String.richText allowing to have ckeditor with more text-formatting icons; added ckeditor 'show source' button by default (impossible to live without that); appy.gen: solved security-related problems; appy.gen.mail: allowto send mail as authenticated user; appy.gen: bugfixes in pages when rendered by IE.
This commit is contained in:
parent
459a714b76
commit
6245023365
|
@ -134,8 +134,8 @@ class ZodbBackuper:
|
||||||
'''Send content of self.logMem to self.emails.'''
|
'''Send content of self.logMem to self.emails.'''
|
||||||
w = self.log
|
w = self.log
|
||||||
subject = 'Backup notification.'
|
subject = 'Backup notification.'
|
||||||
msg = 'From: %s\nSubject: %s\n\n%s' % (self.options.fromAddress,
|
msg = 'From: %s\nTo: %s\nSubject: %s\n\n%s' % (self.options.fromAddress,
|
||||||
subject, self.logMem.getvalue())
|
self.emails, subject, self.logMem.getvalue())
|
||||||
try:
|
try:
|
||||||
w('> Sending mail notifications to %s...' % self.emails)
|
w('> Sending mail notifications to %s...' % self.emails)
|
||||||
smtpInfo = self.options.smtpServer.split(':', 3)
|
smtpInfo = self.options.smtpServer.split(':', 3)
|
||||||
|
@ -151,6 +151,7 @@ class ZodbBackuper:
|
||||||
smtpServer.login(login, password)
|
smtpServer.login(login, password)
|
||||||
res = smtpServer.sendmail(self.options.fromAddress,
|
res = smtpServer.sendmail(self.options.fromAddress,
|
||||||
self.emails.split(','), msg)
|
self.emails.split(','), msg)
|
||||||
|
smtpServer.quit()
|
||||||
if res:
|
if res:
|
||||||
w('Could not send mail to some recipients. %s' % str(res))
|
w('Could not send mail to some recipients. %s' % str(res))
|
||||||
w('Done.')
|
w('Done.')
|
||||||
|
|
|
@ -67,7 +67,7 @@ else:
|
||||||
# If we are in a Appy application, the object on which we will call the
|
# If we are in a Appy application, the object on which we will call the
|
||||||
# method is the config object on this root object.
|
# method is the config object on this root object.
|
||||||
if not appName:
|
if not appName:
|
||||||
targetObject = rootObject.data.appy()
|
targetObject = rootObject.config.appy()
|
||||||
elif not appName.startswith('path='):
|
elif not appName.startswith('path='):
|
||||||
objectName = 'portal_%s' % appName.lower()
|
objectName = 'portal_%s' % appName.lower()
|
||||||
targetObject = getattr(rootObject, objectName).appy()
|
targetObject = getattr(rootObject, objectName).appy()
|
||||||
|
|
|
@ -356,6 +356,12 @@ class Search:
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class Type:
|
class Type:
|
||||||
'''Basic abstract class for defining any appy type.'''
|
'''Basic abstract class for defining any appy type.'''
|
||||||
|
# Those attributes can be overridden by subclasses for defining,
|
||||||
|
# respectively, names of CSS and Javascript files that are required by this
|
||||||
|
# field, keyed by layoutType.
|
||||||
|
cssFiles = {}
|
||||||
|
jsFiles = {}
|
||||||
|
|
||||||
def __init__(self, validator, multiplicity, index, default, optional,
|
def __init__(self, validator, multiplicity, index, default, optional,
|
||||||
editDefault, show, page, group, layouts, move, indexed,
|
editDefault, show, page, group, layouts, move, indexed,
|
||||||
searchable, specificReadPermission, specificWritePermission,
|
searchable, specificReadPermission, specificWritePermission,
|
||||||
|
@ -723,13 +729,25 @@ class Type:
|
||||||
res = copy.deepcopy(defaultFieldLayouts)
|
res = copy.deepcopy(defaultFieldLayouts)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getCss(self, layoutType):
|
def getCss(self, layoutType, res):
|
||||||
'''This method returns a list of CSS files that are required for
|
'''This method completes the list p_res with the names of CSS files
|
||||||
displaying widgets of self's type on a given p_layoutType.'''
|
that are required for displaying widgets of self's type on a given
|
||||||
|
p_layoutType. p_res is not a set because order of inclusion of CSS
|
||||||
|
files may be important and may be loosed by using sets.'''
|
||||||
|
if layoutType in self.cssFiles:
|
||||||
|
for fileName in self.cssFiles[layoutType]:
|
||||||
|
if fileName not in res:
|
||||||
|
res.append(fileName)
|
||||||
|
|
||||||
def getJs(self, layoutType):
|
def getJs(self, layoutType, res):
|
||||||
'''This method returns a list of Javascript files that are required for
|
'''This method completes the list p_res with the names of Javascript
|
||||||
displaying widgets of self's type on a given p_layoutType.'''
|
files that are required for displaying widgets of self's type on a
|
||||||
|
given p_layoutType. p_res is not a set because order of inclusion of
|
||||||
|
CSS files may be important and may be loosed by using sets.'''
|
||||||
|
if layoutType in self.jsFiles:
|
||||||
|
for fileName in self.jsFiles[layoutType]:
|
||||||
|
if fileName not in res:
|
||||||
|
res.append(fileName)
|
||||||
|
|
||||||
def getValue(self, obj):
|
def getValue(self, obj):
|
||||||
'''Gets, on_obj, the value conforming to self's type definition.'''
|
'''Gets, on_obj, the value conforming to self's type definition.'''
|
||||||
|
@ -1025,6 +1043,9 @@ class Float(Type):
|
||||||
return self.pythonType(value)
|
return self.pythonType(value)
|
||||||
|
|
||||||
class String(Type):
|
class String(Type):
|
||||||
|
# Javascript files sometimes required by this type
|
||||||
|
jsFiles = {'edit': ('ckeditor/ckeditor.js',)}
|
||||||
|
|
||||||
# Some predefined regular expressions that may be used as validators
|
# Some predefined regular expressions that may be used as validators
|
||||||
c = re.compile
|
c = re.compile
|
||||||
EMAIL = c('[a-zA-Z][\w\.-]*[a-zA-Z0-9]@[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.' \
|
EMAIL = c('[a-zA-Z][\w\.-]*[a-zA-Z0-9]@[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.' \
|
||||||
|
@ -1132,7 +1153,7 @@ class String(Type):
|
||||||
maxChars=None, colspan=1, master=None, masterValue=None,
|
maxChars=None, colspan=1, master=None, masterValue=None,
|
||||||
focus=False, historized=False, mapping=None, label=None,
|
focus=False, historized=False, mapping=None, label=None,
|
||||||
transform='none', styles=('p','h1','h2','h3','h4'),
|
transform='none', styles=('p','h1','h2','h3','h4'),
|
||||||
allowImageUpload=True):
|
allowImageUpload=True, richText=False):
|
||||||
# According to format, the widget will be different: input field,
|
# According to format, the widget will be different: input field,
|
||||||
# textarea, inline editor... Note that there can be only one String
|
# textarea, inline editor... Note that there can be only one String
|
||||||
# field of format CAPTCHA by page, because the captcha challenge is
|
# field of format CAPTCHA by page, because the captcha challenge is
|
||||||
|
@ -1141,6 +1162,11 @@ class String(Type):
|
||||||
# When format is XHTML, the list of styles that the user will be able to
|
# When format is XHTML, the list of styles that the user will be able to
|
||||||
# select in the styles dropdown is defined hereafter.
|
# select in the styles dropdown is defined hereafter.
|
||||||
self.styles = styles
|
self.styles = styles
|
||||||
|
# When richText is True, we show to the user icons in ckeditor allowing
|
||||||
|
# him to tailor text appearance, color, size, etc. While this may be an
|
||||||
|
# option if the objective is to edit web pages, this may not be desired
|
||||||
|
# for producing standardized, pod-print-ready documents.
|
||||||
|
self.richText = richText
|
||||||
# When format is XHTML, do we allow the user to upload images in it ?
|
# When format is XHTML, do we allow the user to upload images in it ?
|
||||||
self.allowImageUpload = allowImageUpload
|
self.allowImageUpload = allowImageUpload
|
||||||
# The following field has a direct impact on the text entered by the
|
# The following field has a direct impact on the text entered by the
|
||||||
|
@ -1212,7 +1238,8 @@ class String(Type):
|
||||||
# When image upload is allowed, ckeditor inserts some "style" attrs
|
# When image upload is allowed, ckeditor inserts some "style" attrs
|
||||||
# (ie for image size when images are resized). So in this case we
|
# (ie for image size when images are resized). So in this case we
|
||||||
# can't remove style-related information.
|
# can't remove style-related information.
|
||||||
value = cleanXhtml(value, keepStyles=self.allowImageUpload)
|
keepStyles = self.allowImageUpload or self.richText
|
||||||
|
value = cleanXhtml(value, keepStyles=keepStyles)
|
||||||
Type.store(self, obj, value)
|
Type.store(self, obj, value)
|
||||||
|
|
||||||
def getFormattedValue(self, obj, value):
|
def getFormattedValue(self, obj, value):
|
||||||
|
@ -1379,9 +1406,8 @@ class String(Type):
|
||||||
return 'ZCTextIndex'
|
return 'ZCTextIndex'
|
||||||
return Type.getIndexType(self)
|
return Type.getIndexType(self)
|
||||||
|
|
||||||
def getJs(self, layoutType):
|
def getJs(self, layoutType, res):
|
||||||
if (layoutType == 'edit') and (self.format == String.XHTML):
|
if self.format == String.XHTML: Type.getJs(self, layoutType, res)
|
||||||
return ('ckeditor/ckeditor.js',)
|
|
||||||
|
|
||||||
def getCaptchaChallenge(self, session):
|
def getCaptchaChallenge(self, session):
|
||||||
'''Returns a Captcha challenge in the form of a dict. At key "text",
|
'''Returns a Captcha challenge in the form of a dict. At key "text",
|
||||||
|
@ -1445,6 +1471,11 @@ class Boolean(Type):
|
||||||
return res
|
return res
|
||||||
|
|
||||||
class Date(Type):
|
class Date(Type):
|
||||||
|
# Required CSS and Javascript files for this type.
|
||||||
|
cssFiles = {'edit': ('jscalendar/calendar-blue.css',)}
|
||||||
|
jsFiles = {'edit': ('jscalendar/calendar.js',
|
||||||
|
'jscalendar/lang/calendar-en.js',
|
||||||
|
'jscalendar/calendar-setup.js')}
|
||||||
# Possible values for "format"
|
# Possible values for "format"
|
||||||
WITH_HOUR = 0
|
WITH_HOUR = 0
|
||||||
WITHOUT_HOUR = 1
|
WITHOUT_HOUR = 1
|
||||||
|
@ -1474,14 +1505,13 @@ class Date(Type):
|
||||||
master, masterValue, focus, historized, True, mapping,
|
master, masterValue, focus, historized, True, mapping,
|
||||||
label)
|
label)
|
||||||
|
|
||||||
def getCss(self, layoutType):
|
def getCss(self, layoutType, res):
|
||||||
if (layoutType == 'edit') and self.calendar:
|
# CSS files are only required if the calendar must be shown.
|
||||||
return ('jscalendar/calendar-blue.css',)
|
if self.calendar: Type.getCss(self, layoutType, res)
|
||||||
|
|
||||||
def getJs(self, layoutType):
|
def getJs(self, layoutType, res):
|
||||||
if (layoutType == 'edit') and self.calendar:
|
# Javascript files are only required if the calendar must be shown.
|
||||||
return ('jscalendar/calendar.js', 'jscalendar/lang/calendar-en.js',
|
if self.calendar: Type.getJs(self, layoutType, res)
|
||||||
'jscalendar/calendar-setup.js')
|
|
||||||
|
|
||||||
def getSelectableYears(self):
|
def getSelectableYears(self):
|
||||||
'''Gets the list of years one may select for this field.'''
|
'''Gets the list of years one may select for this field.'''
|
||||||
|
@ -2372,21 +2402,15 @@ class List(Type):
|
||||||
if i >= len(outerValue): return ''
|
if i >= len(outerValue): return ''
|
||||||
return getattr(outerValue[i], name, '')
|
return getattr(outerValue[i], name, '')
|
||||||
|
|
||||||
def getCss(self, layoutType):
|
def getCss(self, layoutType, res):
|
||||||
'''Gets the CSS required by sub-fields if any.'''
|
'''Gets the CSS required by sub-fields if any.'''
|
||||||
res = ()
|
|
||||||
for name, field in self.fields:
|
for name, field in self.fields:
|
||||||
css = field.getCss(layoutType)
|
field.getCss(layoutType, res)
|
||||||
if css: res += css
|
|
||||||
return res
|
|
||||||
|
|
||||||
def getJs(self, layoutType):
|
def getJs(self, layoutType, res):
|
||||||
'''Gets the JS required by sub-fields if any.'''
|
'''Gets the JS required by sub-fields if any.'''
|
||||||
res = ()
|
|
||||||
for name, field in self.fields:
|
for name, field in self.fields:
|
||||||
js = field.getJs(layoutType)
|
field.getJs(layoutType, res)
|
||||||
if js: res += js
|
|
||||||
return res
|
|
||||||
|
|
||||||
# Workflow-specific types and default workflows --------------------------------
|
# Workflow-specific types and default workflows --------------------------------
|
||||||
appyToZopePermissions = {
|
appyToZopePermissions = {
|
||||||
|
@ -2768,6 +2792,14 @@ class WorkflowAuthenticated:
|
||||||
initial=True)
|
initial=True)
|
||||||
WorkflowAuthenticated.__instance__ = WorkflowAuthenticated()
|
WorkflowAuthenticated.__instance__ = WorkflowAuthenticated()
|
||||||
|
|
||||||
|
class WorkflowOwner:
|
||||||
|
'''One-state workflow allowing only manager and owner to consult and
|
||||||
|
edit.'''
|
||||||
|
mgr = 'Manager'
|
||||||
|
o = 'Owner'
|
||||||
|
active = State({r:(mgr, o), w:(mgr, o), d:mgr}, initial=True)
|
||||||
|
WorkflowOwner.__instance__ = WorkflowOwner()
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class Selection:
|
class Selection:
|
||||||
'''Instances of this class may be given as validator of a String, in order
|
'''Instances of this class may be given as validator of a String, in order
|
||||||
|
|
|
@ -267,7 +267,7 @@ class ZopeInstaller:
|
||||||
appyTool.log('Admin user "admin" created.')
|
appyTool.log('Admin user "admin" created.')
|
||||||
|
|
||||||
# Create group "admins" if it does not exist
|
# Create group "admins" if it does not exist
|
||||||
if not appyTool.count('Group', login='admins'):
|
if not appyTool.count('Group', noSecurity=True, login='admins'):
|
||||||
appyTool.create('groups', login='admins', title='Administrators',
|
appyTool.create('groups', login='admins', title='Administrators',
|
||||||
roles=['Manager'])
|
roles=['Manager'])
|
||||||
appyTool.log('Group "admins" created.')
|
appyTool.log('Group "admins" created.')
|
||||||
|
@ -275,7 +275,8 @@ class ZopeInstaller:
|
||||||
# Create a group for every global role defined in the application
|
# Create a group for every global role defined in the application
|
||||||
for role in self.config.applicationGlobalRoles:
|
for role in self.config.applicationGlobalRoles:
|
||||||
relatedGroup = '%s_group' % role
|
relatedGroup = '%s_group' % role
|
||||||
if appyTool.count('Group', login=relatedGroup): continue
|
if appyTool.count('Group', noSecurity=True, login=relatedGroup):
|
||||||
|
continue
|
||||||
appyTool.create('groups', login=relatedGroup, title=relatedGroup,
|
appyTool.create('groups', login=relatedGroup, title=relatedGroup,
|
||||||
roles=[role])
|
roles=[role])
|
||||||
appyTool.log('Group "%s", related to global role "%s", was ' \
|
appyTool.log('Group "%s", related to global role "%s", was ' \
|
||||||
|
|
31
gen/mail.py
31
gen/mail.py
|
@ -13,9 +13,13 @@ def sendMail(tool, to, subject, body, attachments=None):
|
||||||
list of email addresses).'''
|
list of email addresses).'''
|
||||||
# Just log things if mail is disabled
|
# Just log things if mail is disabled
|
||||||
fromAddress = tool.mailFrom
|
fromAddress = tool.mailFrom
|
||||||
if not tool.mailEnabled:
|
if not tool.mailEnabled or not tool.mailHost:
|
||||||
tool.log('Mail disabled: should send mail from %s to %s.' % \
|
if not tool.mailHost:
|
||||||
(fromAddress, str(to)))
|
msg = ' (no mailhost defined)'
|
||||||
|
else:
|
||||||
|
msg = ''
|
||||||
|
tool.log('Mail disabled%s: should send mail from %s to %s.' % \
|
||||||
|
(msg, fromAddress, str(to)))
|
||||||
tool.log('Subject: %s' % subject)
|
tool.log('Subject: %s' % subject)
|
||||||
tool.log('Body: %s' % body)
|
tool.log('Body: %s' % body)
|
||||||
if attachments:
|
if attachments:
|
||||||
|
@ -65,11 +69,24 @@ def sendMail(tool, to, subject, body, attachments=None):
|
||||||
msg.attach(part)
|
msg.attach(part)
|
||||||
# Send the email
|
# Send the email
|
||||||
try:
|
try:
|
||||||
mh = smtplib.SMTP(tool.mailHost)
|
smtpInfo = tool.mailHost.split(':', 3)
|
||||||
mh.sendmail(fromAddress, [to], msg.as_string())
|
login = password = None
|
||||||
mh.quit()
|
if len(smtpInfo) == 2:
|
||||||
|
# We simply have server and port
|
||||||
|
server, port = smtpInfo
|
||||||
|
else:
|
||||||
|
# We also have login and password
|
||||||
|
server, port, login, password = smtpInfo
|
||||||
|
smtpServer = smtplib.SMTP(server, port=int(port))
|
||||||
|
if login:
|
||||||
|
smtpServer.login(login, password)
|
||||||
|
res = smtpServer.sendmail(fromAddress, [to], msg.as_string())
|
||||||
|
smtpServer.quit()
|
||||||
|
if res:
|
||||||
|
tool.log('Could not send mail to some recipients. %s' % str(res),
|
||||||
|
type='warning')
|
||||||
except smtplib.SMTPException, e:
|
except smtplib.SMTPException, e:
|
||||||
tool.log('Mail sending failed: %s' % str(e))
|
tool.log('Mail sending failed: %s' % str(e), type='error')
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
def sendNotification(obj, transition, transitionName, workflow):
|
def sendNotification(obj, transition, transitionName, workflow):
|
||||||
|
|
|
@ -43,9 +43,10 @@ class Migrator:
|
||||||
# Manage groups. Exclude not-used default Plone groups.
|
# Manage groups. Exclude not-used default Plone groups.
|
||||||
for groupId in user.getGroups():
|
for groupId in user.getGroups():
|
||||||
if groupId in self.bypassGroups: continue
|
if groupId in self.bypassGroups: continue
|
||||||
if tool.count('Group', login=groupId):
|
if tool.count('Group', noSecurity=True, login=groupId):
|
||||||
# The Appy group already exists, get it
|
# The Appy group already exists, get it
|
||||||
appyGroup = tool.search('Group', login=groupId)[0]
|
appyGroup = tool.search('Group', noSecurity=True,
|
||||||
|
login=groupId)[0]
|
||||||
else:
|
else:
|
||||||
# Create the group. Todo: get Plone group roles and title
|
# Create the group. Todo: get Plone group roles and title
|
||||||
appyGroup = tool.create('groups', login=groupId,
|
appyGroup = tool.create('groups', login=groupId,
|
||||||
|
|
|
@ -981,7 +981,8 @@ class ToolMixin(BaseMixin):
|
||||||
if rolesToShow:
|
if rolesToShow:
|
||||||
res.append(', '.join([self.translate('role_%s'%r) \
|
res.append(', '.join([self.translate('role_%s'%r) \
|
||||||
for r in rolesToShow]))
|
for r in rolesToShow]))
|
||||||
return (' | '.join(res), appyUser.o.getUrl(mode='edit'))
|
return (' | '.join(res), appyUser.o.getUrl(mode='edit', page='main',
|
||||||
|
nav=''))
|
||||||
|
|
||||||
def generateUid(self, className):
|
def generateUid(self, className):
|
||||||
'''Generates a UID for an instance of p_className.'''
|
'''Generates a UID for an instance of p_className.'''
|
||||||
|
|
|
@ -589,11 +589,15 @@ class BaseMixin:
|
||||||
klass = self.getTool().getAppyClass(className, wrapper=True)
|
klass = self.getTool().getAppyClass(className, wrapper=True)
|
||||||
return klass.__fields__
|
return klass.__fields__
|
||||||
|
|
||||||
def getGroupedAppyTypes(self, layoutType, pageName):
|
def getGroupedAppyTypes(self, layoutType, pageName, cssJs=None):
|
||||||
'''Returns the fields sorted by group. For every field, the appyType
|
'''Returns the fields sorted by group. For every field, the appyType
|
||||||
(dict version) is given.'''
|
(dict version) is given.'''
|
||||||
res = []
|
res = []
|
||||||
groups = {} # The already encountered groups
|
groups = {} # The already encountered groups
|
||||||
|
# If a dict is given in p_cssJs, we must fill it with the CSS and JS
|
||||||
|
# files required for every returned appyType.
|
||||||
|
collectCssJs = isinstance(cssJs, dict)
|
||||||
|
css = js = None
|
||||||
# If param "refresh" is there, we must reload the Python class
|
# If param "refresh" is there, we must reload the Python class
|
||||||
refresh = ('refresh' in self.REQUEST)
|
refresh = ('refresh' in self.REQUEST)
|
||||||
if refresh:
|
if refresh:
|
||||||
|
@ -602,6 +606,11 @@ class BaseMixin:
|
||||||
if refresh: appyType = appyType.reload(klass, self)
|
if refresh: appyType = appyType.reload(klass, self)
|
||||||
if appyType.page.name != pageName: continue
|
if appyType.page.name != pageName: continue
|
||||||
if not appyType.isShowable(self, layoutType): continue
|
if not appyType.isShowable(self, layoutType): continue
|
||||||
|
if collectCssJs:
|
||||||
|
if css == None: css = []
|
||||||
|
appyType.getCss(layoutType, css)
|
||||||
|
if js == None: js = []
|
||||||
|
appyType.getJs(layoutType, js)
|
||||||
if not appyType.group:
|
if not appyType.group:
|
||||||
res.append(appyType.__dict__)
|
res.append(appyType.__dict__)
|
||||||
else:
|
else:
|
||||||
|
@ -610,6 +619,9 @@ class BaseMixin:
|
||||||
groupDescr = appyType.group.insertInto(res, groups,
|
groupDescr = appyType.group.insertInto(res, groups,
|
||||||
appyType.page, self.meta_type)
|
appyType.page, self.meta_type)
|
||||||
GroupDescr.addWidget(groupDescr, appyType.__dict__)
|
GroupDescr.addWidget(groupDescr, appyType.__dict__)
|
||||||
|
if collectCssJs:
|
||||||
|
cssJs['css'] = css
|
||||||
|
cssJs['js'] = js
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getAppyTypes(self, layoutType, pageName):
|
def getAppyTypes(self, layoutType, pageName):
|
||||||
|
@ -622,23 +634,19 @@ class BaseMixin:
|
||||||
res.append(appyType)
|
res.append(appyType)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getCssJs(self, fields, layoutType):
|
def getCssJs(self, fields, layoutType, res):
|
||||||
'''Gets the list of Javascript and CSS files required by Appy types
|
'''Gets, in p_res ~{'css':[s_css], 'js':[s_js]}~ the lists of
|
||||||
p_fields when shown on p_layoutType.'''
|
Javascript and CSS files required by Appy types p_fields when shown
|
||||||
|
on p_layoutType.'''
|
||||||
# Lists css and js below are not sets, because order of Javascript
|
# Lists css and js below are not sets, because order of Javascript
|
||||||
# inclusion can be important, and this could be losed by using sets.
|
# inclusion can be important, and this could be losed by using sets.
|
||||||
css = []
|
css = []
|
||||||
js = []
|
js = []
|
||||||
for field in fields:
|
for field in fields:
|
||||||
fieldCss = field.getCss(layoutType)
|
field.getCss(layoutType, css)
|
||||||
if fieldCss:
|
field.getJs(layoutType, js)
|
||||||
for fcss in fieldCss:
|
res['css'] = css
|
||||||
if fcss not in css: css.append(fcss)
|
res['js'] = js
|
||||||
fieldJs = field.getJs(layoutType)
|
|
||||||
if fieldJs:
|
|
||||||
for fjs in fieldJs:
|
|
||||||
if fjs not in js: js.append(fjs)
|
|
||||||
return {'css':css, 'js':js}
|
|
||||||
|
|
||||||
def getAppyTypesFromNames(self, fieldNames, asDict=True):
|
def getAppyTypesFromNames(self, fieldNames, asDict=True):
|
||||||
'''Gets the Appy types named p_fieldNames.'''
|
'''Gets the Appy types named p_fieldNames.'''
|
||||||
|
@ -874,7 +882,8 @@ class BaseMixin:
|
||||||
else:
|
else:
|
||||||
appyClass = self.getTool().getAppyClass(className)
|
appyClass = self.getTool().getAppyClass(className)
|
||||||
if hasattr(appyClass, 'workflow'): wf = appyClass.workflow
|
if hasattr(appyClass, 'workflow'): wf = appyClass.workflow
|
||||||
else: wf = gen.WorkflowAnonymous
|
else:
|
||||||
|
wf = gen.WorkflowAnonymous
|
||||||
if not name: return wf
|
if not name: return wf
|
||||||
return WorkflowDescriptor.getWorkflowName(wf)
|
return WorkflowDescriptor.getWorkflowName(wf)
|
||||||
|
|
||||||
|
@ -1495,7 +1504,7 @@ class BaseMixin:
|
||||||
# Define the attributes that will initialize the ckeditor instance for
|
# Define the attributes that will initialize the ckeditor instance for
|
||||||
# this field.
|
# this field.
|
||||||
field = self.getAppyType(name)
|
field = self.getAppyType(name)
|
||||||
ckAttrs = {'toolbar': 'Appy',
|
ckAttrs = {'toolbar': field.richText and 'AppyRich' or 'Appy',
|
||||||
'format_tags': '%s' % ';'.join(field.styles)}
|
'format_tags': '%s' % ';'.join(field.styles)}
|
||||||
if field.allowImageUpload:
|
if field.allowImageUpload:
|
||||||
ckAttrs['filebrowserUploadUrl'] = '%s/upload' % self.absolute_url()
|
ckAttrs['filebrowserUploadUrl'] = '%s/upload' % self.absolute_url()
|
||||||
|
|
37
gen/model.py
37
gen/model.py
|
@ -125,6 +125,8 @@ class ModelClass:
|
||||||
# Determine page show
|
# Determine page show
|
||||||
pageShow = page.show
|
pageShow = page.show
|
||||||
if isinstance(pageShow, basestring): pageShow='"%s"' % pageShow
|
if isinstance(pageShow, basestring): pageShow='"%s"' % pageShow
|
||||||
|
elif callable(pageShow):
|
||||||
|
pageShow = '%s.%s' % (wrapperName, pageShow.__name__)
|
||||||
res += '"%s":Pge("%s", show=%s),'% (page.name, page.name, pageShow)
|
res += '"%s":Pge("%s", show=%s),'% (page.name, page.name, pageShow)
|
||||||
res += '}\n'
|
res += '}\n'
|
||||||
# Secondly, dump every attribute
|
# Secondly, dump every attribute
|
||||||
|
@ -194,7 +196,7 @@ class Page(ModelClass):
|
||||||
_appy_attributes = ['title', 'content', 'pages']
|
_appy_attributes = ['title', 'content', 'pages']
|
||||||
folder = True
|
folder = True
|
||||||
title = gen.String(show='edit', indexed=True)
|
title = gen.String(show='edit', indexed=True)
|
||||||
content = gen.String(format=gen.String.XHTML, layouts='f')
|
content = gen.String(format=gen.String.XHTML, layouts='f', richText=True)
|
||||||
# Pages can contain other pages.
|
# Pages can contain other pages.
|
||||||
def showSubPages(self): pass
|
def showSubPages(self): pass
|
||||||
pages = gen.Ref(None, multiplicity=(0,None), add=True, link=False,
|
pages = gen.Ref(None, multiplicity=(0,None), add=True, link=False,
|
||||||
|
@ -209,10 +211,10 @@ toolFieldPrefixes = ('defaultValue', 'podTemplate', 'formats', 'resultColumns',
|
||||||
'enableAdvancedSearch', 'numberOfSearchColumns',
|
'enableAdvancedSearch', 'numberOfSearchColumns',
|
||||||
'searchFields', 'optionalFields', 'showWorkflow',
|
'searchFields', 'optionalFields', 'showWorkflow',
|
||||||
'showAllStatesInPhase')
|
'showAllStatesInPhase')
|
||||||
defaultToolFields = ('title', 'unoEnabledPython','openOfficePort',
|
defaultToolFields = ('title', 'mailHost', 'mailEnabled', 'mailFrom',
|
||||||
'numberOfResultsPerPage', 'mailHost', 'mailEnabled',
|
'appyVersion', 'users', 'groups', 'translations', 'pages',
|
||||||
'mailFrom', 'appyVersion', 'users', 'groups',
|
'unoEnabledPython','openOfficePort',
|
||||||
'translations', 'pages')
|
'numberOfResultsPerPage')
|
||||||
|
|
||||||
class Tool(ModelClass):
|
class Tool(ModelClass):
|
||||||
# In a ModelClass we need to declare attributes in the following list.
|
# In a ModelClass we need to declare attributes in the following list.
|
||||||
|
@ -220,33 +222,40 @@ class Tool(ModelClass):
|
||||||
folder = True
|
folder = True
|
||||||
|
|
||||||
# Tool attributes
|
# Tool attributes
|
||||||
|
def isManager(self): pass
|
||||||
title = gen.String(show=False, page=gen.Page('main', show=False))
|
title = gen.String(show=False, page=gen.Page('main', show=False))
|
||||||
def validPythonWithUno(self, value): pass # Real method in the wrapper
|
|
||||||
unoEnabledPython = gen.String(validator=validPythonWithUno)
|
|
||||||
openOfficePort = gen.Integer(default=2002)
|
|
||||||
numberOfResultsPerPage = gen.Integer(default=30)
|
|
||||||
mailHost = gen.String(default='localhost:25')
|
mailHost = gen.String(default='localhost:25')
|
||||||
mailEnabled = gen.Boolean(default=False)
|
mailEnabled = gen.Boolean(default=False)
|
||||||
mailFrom = gen.String(default='info@appyframework.org')
|
mailFrom = gen.String(default='info@appyframework.org')
|
||||||
appyVersion = gen.String(show=False, layouts='f')
|
appyVersion = gen.String(layouts='f')
|
||||||
|
|
||||||
# Ref(User) will maybe be transformed into Ref(CustomUserClass).
|
# Ref(User) will maybe be transformed into Ref(CustomUserClass).
|
||||||
users = gen.Ref(User, multiplicity=(0,None), add=True, link=False,
|
users = gen.Ref(User, multiplicity=(0,None), add=True, link=False,
|
||||||
back=gen.Ref(attribute='toTool', show=False),
|
back=gen.Ref(attribute='toTool', show=False),
|
||||||
page=gen.Page('users', show='view'),
|
page=gen.Page('users', show=isManager),
|
||||||
queryable=True, queryFields=('title', 'login'),
|
queryable=True, queryFields=('title', 'login'),
|
||||||
showHeaders=True, shownInfo=('title', 'login', 'roles'))
|
showHeaders=True, shownInfo=('title', 'login', 'roles'))
|
||||||
groups = gen.Ref(Group, multiplicity=(0,None), add=True, link=False,
|
groups = gen.Ref(Group, multiplicity=(0,None), add=True, link=False,
|
||||||
back=gen.Ref(attribute='toTool2', show=False),
|
back=gen.Ref(attribute='toTool2', show=False),
|
||||||
page=gen.Page('groups', show='view'),
|
page=gen.Page('groups', show=isManager),
|
||||||
queryable=True, queryFields=('title', 'login'),
|
queryable=True, queryFields=('title', 'login'),
|
||||||
showHeaders=True, shownInfo=('title', 'login', 'roles'))
|
showHeaders=True, shownInfo=('title', 'login', 'roles'))
|
||||||
translations = gen.Ref(Translation, multiplicity=(0,None), add=False,
|
translations = gen.Ref(Translation, multiplicity=(0,None), add=False,
|
||||||
link=False, show='view',
|
link=False, show='view',
|
||||||
back=gen.Ref(attribute='trToTool', show=False),
|
back=gen.Ref(attribute='trToTool', show=False),
|
||||||
page=gen.Page('translations', show='view'))
|
page=gen.Page('translations', show=isManager))
|
||||||
pages = gen.Ref(Page, multiplicity=(0,None), add=True, link=False,
|
pages = gen.Ref(Page, multiplicity=(0,None), add=True, link=False,
|
||||||
show='view', back=gen.Ref(attribute='toTool3', show=False),
|
show='view', back=gen.Ref(attribute='toTool3', show=False),
|
||||||
page=gen.Page('pages', show='view'))
|
page=gen.Page('pages', show=isManager))
|
||||||
|
|
||||||
|
# Document generation page
|
||||||
|
dgp = {'page': gen.Page('documentGeneration', show=isManager)}
|
||||||
|
def validPythonWithUno(self, value): pass # Real method in the wrapper
|
||||||
|
unoEnabledPython = gen.String(show=False,validator=validPythonWithUno,**dgp)
|
||||||
|
openOfficePort = gen.Integer(default=2002, show=False, **dgp)
|
||||||
|
# User interface page
|
||||||
|
numberOfResultsPerPage = gen.Integer(default=30,
|
||||||
|
page=gen.Page('userInterface', show=False))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _appy_clean(klass):
|
def _appy_clean(klass):
|
||||||
|
|
|
@ -64,7 +64,7 @@ img {border: 0}
|
||||||
.portletSearch { font-size: 90%; font-style: italic; padding-left: 1em}
|
.portletSearch { font-size: 90%; font-style: italic; padding-left: 1em}
|
||||||
.phase { border-style: dashed; border-width: thin; padding: 4px 0.6em 5px 1em;}
|
.phase { border-style: dashed; border-width: thin; padding: 4px 0.6em 5px 1em;}
|
||||||
.phaseSelected { background-color: #F4F5F6; }
|
.phaseSelected { background-color: #F4F5F6; }
|
||||||
.content { padding: 14px 14px 9px 15px;}
|
.content { padding: 14px 14px 9px 15px; }
|
||||||
.grey { display: none; position: absolute; left: 0px; top: 0px;
|
.grey { display: none; position: absolute; left: 0px; top: 0px;
|
||||||
background:grey; opacity:0.5; -moz-opacity:0.5; -khtml-opacity:0.5;
|
background:grey; opacity:0.5; -moz-opacity:0.5; -khtml-opacity:0.5;
|
||||||
filter:alpha(Opacity=50);}
|
filter:alpha(Opacity=50);}
|
||||||
|
|
|
@ -9,20 +9,22 @@ CKEDITOR.editorConfig = function( config )
|
||||||
config.toolbar_Appy =
|
config.toolbar_Appy =
|
||||||
[
|
[
|
||||||
{ name: 'basicstyles', items : [ 'Format', 'Bold', 'Italic', 'Underline',
|
{ name: 'basicstyles', items : [ 'Format', 'Bold', 'Italic', 'Underline',
|
||||||
'Strike', 'Subscript', 'Superscript', '-',
|
'Strike', 'Subscript', 'Superscript'] },
|
||||||
'RemoveFormat' ] },
|
|
||||||
{ name: 'paragraph', items : [ 'NumberedList', 'BulletedList', '-',
|
{ name: 'paragraph', items : [ 'NumberedList', 'BulletedList', '-',
|
||||||
'Outdent', 'Indent', '-', 'JustifyLeft',
|
'Outdent', 'Indent', '-', 'JustifyLeft',
|
||||||
'JustifyCenter', 'JustifyRight',
|
'JustifyCenter', 'JustifyRight',
|
||||||
'JustifyBlock'] },
|
'JustifyBlock'] },
|
||||||
{ name: 'clipboard', items : [ 'Cut', 'Copy', 'Paste', 'PasteText',
|
{ name: 'clipboard', items : [ 'Cut', 'Copy', 'Paste', 'PasteText',
|
||||||
'PasteFromWord', '-', 'Undo', 'Redo' ] },
|
'PasteFromWord', 'Undo', 'Redo']},
|
||||||
{ name: 'editing', items : [ 'Find', 'Replace', '-', 'SelectAll', '-',
|
{ name: 'editing', items : [ 'Find', 'Replace', '-', 'SelectAll', '-',
|
||||||
'SpellChecker', 'Scayt']},
|
'SpellChecker', 'Scayt']},
|
||||||
{ name: 'insert', items : [ 'Image', 'Table', 'HorizontalRule',
|
{ name: 'insert', items : [ 'Image', 'Table', 'SpecialChar', 'Link',
|
||||||
'SpecialChar', 'PageBreak', 'Link', 'Unlink',
|
'Unlink', 'Source', 'Maximize']},
|
||||||
'-', 'Maximize']},
|
|
||||||
];
|
];
|
||||||
|
config.toolbar_AppyRich = config.toolbar_Appy.concat(
|
||||||
|
[{name: 'styles', items: [ 'Font', 'FontSize', 'TextColor', 'BGColor',
|
||||||
|
'RemoveFormat' ]},]
|
||||||
|
)
|
||||||
config.format_p = { element:'p', attributes:{'style':'margin:0;padding:0'}};
|
config.format_p = { element:'p', attributes:{'style':'margin:0;padding:0'}};
|
||||||
config.format_h1 = { element:'h1', attributes:{'style':'margin:0;padding:0'}};
|
config.format_h1 = { element:'h1', attributes:{'style':'margin:0;padding:0'}};
|
||||||
config.format_h2 = { element:'h2', attributes:{'style':'margin:0;padding:0'}};
|
config.format_h2 = { element:'h2', attributes:{'style':'margin:0;padding:0'}};
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
<tal:main define="tool context/getTool">
|
<tal:main define="tool context/getTool">
|
||||||
<html metal:use-macro="context/ui/template/macros/main">
|
<html metal:use-macro="context/ui/template/macros/main">
|
||||||
<metal:fill fill-slot="content"
|
<metal:fill fill-slot="content"
|
||||||
tal:define="contextObj context/getParentNode;
|
tal:define="contextObj context/getParentNode;
|
||||||
dummy python: contextObj.allows('Modify portal content', raiseError=True);
|
dummy python: contextObj.allows('Modify portal content', raiseError=True);
|
||||||
errors request/errors | python:{};
|
errors request/errors | python:{};
|
||||||
layoutType python:'edit';
|
layoutType python:'edit';
|
||||||
layout python: contextObj.getPageLayout(layoutType);
|
layout python: contextObj.getPageLayout(layoutType);
|
||||||
phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType=layoutType);
|
page request/page|python:'main';
|
||||||
phase phaseInfo/name;
|
cssJs python: {};
|
||||||
page request/page|python:'main';
|
groupedWidgets python: contextObj.getGroupedAppyTypes(layoutType, page, cssJs=cssJs);
|
||||||
cssJs python: contextObj.getCssJs(contextObj.getAppyTypes(layoutType, page), layoutType);
|
phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType=layoutType);
|
||||||
confirmMsg request/confirmMsg | nothing;"
|
phase phaseInfo/name;
|
||||||
|
confirmMsg request/confirmMsg | nothing;"
|
||||||
tal:on-error="structure python: tool.manageError(error)">
|
tal:on-error="structure python: tool.manageError(error)">
|
||||||
|
|
||||||
<tal:comment replace="nothing">Include type-specific CSS and JS.</tal:comment>
|
<tal:comment replace="nothing">Include type-specific CSS and JS.</tal:comment>
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
</tal:comment>
|
</tal:comment>
|
||||||
<table metal:define-macro="widgets"
|
<table metal:define-macro="widgets"
|
||||||
tal:attributes="width layout/width">
|
tal:attributes="width layout/width">
|
||||||
<tr tal:repeat="widget python: contextObj.getGroupedAppyTypes(layoutType, page)">
|
<tr tal:repeat="widget groupedWidgets">
|
||||||
<td tal:condition="python: widget['type'] == 'group'">
|
<td tal:condition="python: widget['type'] == 'group'">
|
||||||
<metal:call use-macro="app/ui/widgets/show/macros/group"/>
|
<metal:call use-macro="app/ui/widgets/show/macros/group"/>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -12,11 +12,9 @@
|
||||||
<div class="portletContent" tal:condition="python: contextObj and contextObj.mayNavigate()">
|
<div class="portletContent" tal:condition="python: contextObj and contextObj.mayNavigate()">
|
||||||
<div class="portletTitle" tal:define="parent contextObj/getParent">
|
<div class="portletTitle" tal:define="parent contextObj/getParent">
|
||||||
<span tal:replace="contextObj/Title"></span>
|
<span tal:replace="contextObj/Title"></span>
|
||||||
<div style="float:right" tal:condition="python: parent">
|
<a tal:condition="python: parent" tal:attributes="href parent/absolute_url">
|
||||||
<a tal:attributes="href parent/absolute_url">
|
|
||||||
<img tal:attributes="src string: $appUrl/ui/gotoSource.png"/>
|
<img tal:attributes="src string: $appUrl/ui/gotoSource.png"/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<metal:phases use-macro="here/ui/portlet/macros/phases"/>
|
<metal:phases use-macro="here/ui/portlet/macros/phases"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -34,10 +32,9 @@
|
||||||
class python:test(not currentSearch and (currentClass==rootClass), 'portletCurrent', '')"
|
class python:test(not currentSearch and (currentClass==rootClass), 'portletCurrent', '')"
|
||||||
tal:content="structure python: _(rootClass + '_plural')">
|
tal:content="structure python: _(rootClass + '_plural')">
|
||||||
</a>
|
</a>
|
||||||
<div style="float: right"
|
<span tal:define="addPermission python: '%s: Add %s' % (appName, rootClass);
|
||||||
tal:define="addPermission python: '%s: Add %s' % (appName, rootClass);
|
userMayAdd python: user.has_permission(addPermission, appFolder);
|
||||||
userMayAdd python: user.has_permission(addPermission, appFolder);
|
createMeans python: tool.getCreateMeans(rootClass)">
|
||||||
createMeans python: tool.getCreateMeans(rootClass)">
|
|
||||||
<tal:comment replace="nothing">Create a new object from a web form</tal:comment>
|
<tal:comment replace="nothing">Create a new object from a web form</tal:comment>
|
||||||
<a tal:condition="python: ('form' in createMeans) and userMayAdd"
|
<a tal:condition="python: ('form' in createMeans) and userMayAdd"
|
||||||
tal:attributes="href python: '%s/do?action=Create&className=%s' % (toolUrl, rootClass);
|
tal:attributes="href python: '%s/do?action=Create&className=%s' % (toolUrl, rootClass);
|
||||||
|
@ -57,30 +54,30 @@
|
||||||
title python: _('search_objects')">
|
title python: _('search_objects')">
|
||||||
<img tal:attributes="src string: $appUrl/ui/search.gif"/>
|
<img tal:attributes="src string: $appUrl/ui/search.gif"/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</span>
|
||||||
<tal:comment replace="nothing">Searches for this content type.</tal:comment>
|
<tal:comment replace="nothing">Searches for this content type.</tal:comment>
|
||||||
<tal:searchOrGroup repeat="searchOrGroup python: tool.getSearches(rootClass)">
|
<tal:searchOrGroup repeat="searchOrGroup python: tool.getSearches(rootClass)">
|
||||||
<tal:group condition="searchOrGroup/isGroup">
|
<tal:group condition="searchOrGroup/isGroup">
|
||||||
<tal:expanded define="group searchOrGroup;
|
<tal:expanded define="group searchOrGroup;
|
||||||
expanded python: request.get(group['labelId'], 'collapsed') == 'expanded'">
|
expanded python: request.get(group['labelId'], 'collapsed') == 'expanded'">
|
||||||
<tal:comment replace="nothing">Group name</tal:comment>
|
<tal:comment replace="nothing">Group name</tal:comment>
|
||||||
<dt class="portletGroup">
|
<div class="portletGroup">
|
||||||
<img align="left" style="cursor:pointer; margin-right: 3px"
|
<img align="left" style="cursor:pointer; margin-right: 3px"
|
||||||
tal:attributes="id python: '%s_img' % group['labelId'];
|
tal:attributes="id python: '%s_img' % group['labelId'];
|
||||||
src python:test(expanded, 'ui/collapse.gif', 'ui/expand.gif');
|
src python:test(expanded, 'ui/collapse.gif', 'ui/expand.gif');
|
||||||
onClick python:'toggleCookie(\'%s\')' % group['labelId']"/>
|
onClick python:'toggleCookie(\'%s\')' % group['labelId']"/>
|
||||||
<span tal:replace="group/label"/>
|
<span tal:replace="group/label"/>
|
||||||
</dt>
|
</div>
|
||||||
<tal:comment replace="nothing">Group searches</tal:comment>
|
<tal:comment replace="nothing">Group searches</tal:comment>
|
||||||
<span tal:attributes="id group/labelId;
|
<div tal:attributes="id group/labelId;
|
||||||
style python:test(expanded, 'display:block', 'display:none')">
|
style python:test(expanded, 'display:block', 'display:none')">
|
||||||
<dt class="portletSearch" tal:repeat="search group/searches">
|
<div class="portletSearch" tal:repeat="search group/searches">
|
||||||
<a tal:attributes="href python: '%s?className=%s&search=%s' % (queryUrl, rootClass, search['name']);
|
<a tal:attributes="href python: '%s?className=%s&search=%s' % (queryUrl, rootClass, search['name']);
|
||||||
title search/descr;
|
title search/descr;
|
||||||
class python: test(search['name'] == currentSearch, 'portletCurrent', '');"
|
class python: test(search['name'] == currentSearch, 'portletCurrent', '');"
|
||||||
tal:content="structure search/label"></a>
|
tal:content="structure search/label"></a>
|
||||||
</dt>
|
</div>
|
||||||
</span>
|
</div>
|
||||||
</tal:expanded>
|
</tal:expanded>
|
||||||
</tal:group>
|
</tal:group>
|
||||||
<dt tal:define="search searchOrGroup" tal:condition="not: searchOrGroup/isGroup"
|
<dt tal:define="search searchOrGroup" tal:condition="not: searchOrGroup/isGroup"
|
||||||
|
@ -100,10 +97,9 @@
|
||||||
currently shown object, made of phases and contained pages.
|
currently shown object, made of phases and contained pages.
|
||||||
</tal:comment>
|
</tal:comment>
|
||||||
<table metal:define-macro="phases"
|
<table metal:define-macro="phases"
|
||||||
tal:define="phases contextObj/getAppyPhases|nothing;
|
tal:define="phases contextObj/getAppyPhases;
|
||||||
singlePhase python: len(phases) == 1;
|
singlePhase python: len(phases) == 1;
|
||||||
page python: req.get('page', 'main')"
|
page python: req.get('page', 'main')">
|
||||||
tal:condition="python: phases" width="100%">
|
|
||||||
<tal:phase repeat="phase phases">
|
<tal:phase repeat="phase phases">
|
||||||
<tal:comment replace="nothing">The box containing phase-related information</tal:comment>
|
<tal:comment replace="nothing">The box containing phase-related information</tal:comment>
|
||||||
<tr tal:define="singlePage python: len(phase['pages']) == 1">
|
<tr tal:define="singlePage python: len(phase['pages']) == 1">
|
||||||
|
@ -116,30 +112,25 @@
|
||||||
tal:content="structure python: _(label)">
|
tal:content="structure python: _(label)">
|
||||||
</div>
|
</div>
|
||||||
<tal:comment replace="nothing">The page(s) within the phase</tal:comment>
|
<tal:comment replace="nothing">The page(s) within the phase</tal:comment>
|
||||||
<table width="100%" cellpadding="0">
|
<tal:page repeat="aPage phase/pages">
|
||||||
<tal:page repeat="aPage phase/pages">
|
<tal:comment replace="nothing">First line: page name and icons</tal:comment>
|
||||||
<tal:comment replace="nothing">1st line: page name and icons</tal:comment>
|
<div tal:condition="python: not (singlePhase and singlePage)"
|
||||||
<tr valign="top" tal:condition="python: not (singlePhase and singlePage)">
|
tal:attributes="class python: test(aPage == page, 'portletCurrent portletPage', 'portletPage')">
|
||||||
<td tal:attributes="class python: test(aPage == page, 'portletCurrent portletPage', 'portletPage')">
|
<a tal:attributes="href python: contextObj.getUrl(page=aPage)"
|
||||||
<a tal:attributes="href python: contextObj.getUrl(page=aPage)"
|
|
||||||
tal:content="structure python: _('%s_page_%s' % (contextObj.meta_type, aPage))">
|
tal:content="structure python: _('%s_page_%s' % (contextObj.meta_type, aPage))">
|
||||||
</a>
|
</a>
|
||||||
</td>
|
<a tal:condition="python: contextObj.allows('Modify portal content') and phase['pagesInfo'][aPage]['showOnEdit']"
|
||||||
<td align="right">
|
tal:attributes="href python: contextObj.getUrl(mode='edit', page=aPage)">
|
||||||
<img title="Edit" style="cursor:pointer"
|
<img title="Edit" tal:attributes="src string: $appUrl/ui/edit.gif"/>
|
||||||
tal:attributes="onClick python: 'href: window.location=\'%s\'' % contextObj.getUrl(mode='edit', page=aPage);
|
</a>
|
||||||
src string: $appUrl/ui/edit.gif"
|
</div>
|
||||||
tal:condition="python: contextObj.allows('Modify portal content') and phase['pagesInfo'][aPage]['showOnEdit']"/>
|
<tal:comment replace="nothing">Next lines: links</tal:comment>
|
||||||
</td>
|
<tal:links define="links python: phase['pagesInfo'][aPage].get('links')" tal:condition="links">
|
||||||
</tr>
|
<div tal:repeat="link links">
|
||||||
<tal:comment replace="nothing">2nd line: links</tal:comment>
|
<a tal:content="link/title" tal:attributes="href link/url"></a>
|
||||||
<tal:links define="links python: phase['pagesInfo'][aPage].get('links')" tal:condition="links">
|
</div>
|
||||||
<tr tal:repeat="link links">
|
</tal:links>
|
||||||
<td><a tal:content="link/title" tal:attributes="href link/url"></a></td>
|
</tal:page>
|
||||||
</tr>
|
|
||||||
</tal:links>
|
|
||||||
</tal:page>
|
|
||||||
</table>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tal:comment replace="nothing">The down arrow pointing to the next phase (if any)</tal:comment>
|
<tal:comment replace="nothing">The down arrow pointing to the next phase (if any)</tal:comment>
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
tal:define="className request/className;
|
tal:define="className request/className;
|
||||||
refInfo request/ref|nothing;
|
refInfo request/ref|nothing;
|
||||||
searchInfo python: tool.getSearchInfo(className, refInfo);
|
searchInfo python: tool.getSearchInfo(className, refInfo);
|
||||||
cssJs python: tool.getCssJs(searchInfo['fields'], 'edit')">
|
cssJs python: {};
|
||||||
|
dummy python: tool.getCssJs(searchInfo['fields'], 'edit', cssJs)">
|
||||||
|
|
||||||
<tal:comment replace="nothing">Include type-specific CSS and JS.</tal:comment>
|
<tal:comment replace="nothing">Include type-specific CSS and JS.</tal:comment>
|
||||||
<link tal:repeat="cssFile cssJs/css" rel="stylesheet" type="text/css"
|
<link tal:repeat="cssFile cssJs/css" rel="stylesheet" type="text/css"
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
<tal:main define="tool context/config">
|
<tal:main define="tool context/config">
|
||||||
<html metal:use-macro="context/ui/template/macros/main">
|
<html metal:use-macro="context/ui/template/macros/main">
|
||||||
<metal:fill fill-slot="content"
|
<metal:fill fill-slot="content"
|
||||||
tal:define="contextObj python: context.getParentNode();
|
tal:define="contextObj python: context.getParentNode();
|
||||||
dummy python: contextObj.allows('View', raiseError=True);
|
dummy python: contextObj.allows('View', raiseError=True);
|
||||||
portal_type python: context.portal_type.lower().replace(' ', '_');
|
portal_type python: context.portal_type.lower().replace(' ', '_');
|
||||||
errors python: req.get('errors', {});
|
errors python: req.get('errors', {});
|
||||||
layoutType python:'view';
|
layoutType python:'view';
|
||||||
layout python: contextObj.getPageLayout(layoutType);
|
layout python: contextObj.getPageLayout(layoutType);
|
||||||
phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType='view');
|
phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType='view');
|
||||||
page req/page|python:'main';
|
page req/page|python:'main';
|
||||||
phase phaseInfo/name;"
|
phase phaseInfo/name;
|
||||||
|
groupedWidgets python: contextObj.getGroupedAppyTypes(layoutType, page);"
|
||||||
tal:on-error="structure python: tool.manageError(error)">
|
tal:on-error="structure python: tool.manageError(error)">
|
||||||
<metal:prologue use-macro="context/ui/page/macros/prologue"/>
|
<metal:prologue use-macro="context/ui/page/macros/prologue"/>
|
||||||
<metal:show use-macro="context/ui/page/macros/show"/>
|
<metal:show use-macro="context/ui/page/macros/show"/>
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
from appy.gen import WorkflowOwner
|
||||||
from appy.gen.wrappers import AbstractWrapper
|
from appy.gen.wrappers import AbstractWrapper
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class GroupWrapper(AbstractWrapper):
|
class GroupWrapper(AbstractWrapper):
|
||||||
|
workflow = WorkflowOwner
|
||||||
|
|
||||||
def showLogin(self):
|
def showLogin(self):
|
||||||
'''When must we show the login field?'''
|
'''When must we show the login field?'''
|
||||||
|
|
|
@ -30,16 +30,23 @@ class ToolWrapper(AbstractWrapper):
|
||||||
return NOT_UNO_ENABLED_PYTHON % value
|
return NOT_UNO_ENABLED_PYTHON % value
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def isManager(self):
|
||||||
|
'''Some pages on the tool can only be accessed by God.'''
|
||||||
|
if self.user.has_role('Manager'): return 'view'
|
||||||
|
|
||||||
podOutputFormats = ('odt', 'pdf', 'doc', 'rtf')
|
podOutputFormats = ('odt', 'pdf', 'doc', 'rtf')
|
||||||
def getPodOutputFormats(self):
|
def getPodOutputFormats(self):
|
||||||
'''Gets the available output formats for POD documents.'''
|
'''Gets the available output formats for POD documents.'''
|
||||||
return [(of, self.translate(of)) for of in self.podOutputFormats]
|
return [(of, self.translate(of)) for of in self.podOutputFormats]
|
||||||
|
|
||||||
def getInitiator(self):
|
def getInitiator(self, field=False):
|
||||||
'''Retrieves the object that triggered the creation of the object
|
'''Retrieves the object that triggered the creation of the object
|
||||||
being currently created (if any).'''
|
being currently created (if any), or the name of the field in this
|
||||||
|
object if p_field is given.'''
|
||||||
nav = self.o.REQUEST.get('nav', '')
|
nav = self.o.REQUEST.get('nav', '')
|
||||||
if nav: return self.getObject(nav.split('.')[1])
|
if not nav or not nav.startswith('ref.'): return
|
||||||
|
if not field: return self.getObject(nav.split('.')[1])
|
||||||
|
return nav.split('.')[2].split(':')[0]
|
||||||
|
|
||||||
def getObject(self, uid):
|
def getObject(self, uid):
|
||||||
'''Allow to retrieve an object from its unique identifier p_uid.'''
|
'''Allow to retrieve an object from its unique identifier p_uid.'''
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
from appy.gen import WorkflowOwner
|
||||||
from appy.gen.wrappers import AbstractWrapper
|
from appy.gen.wrappers import AbstractWrapper
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class UserWrapper(AbstractWrapper):
|
class UserWrapper(AbstractWrapper):
|
||||||
|
workflow = WorkflowOwner
|
||||||
|
|
||||||
def showLogin(self):
|
def showLogin(self):
|
||||||
'''When must we show the login field?'''
|
'''When must we show the login field?'''
|
||||||
if self.o.isTemporary(): return 'edit'
|
if self.o.isTemporary(): return 'edit'
|
||||||
return 'view'
|
return ('view', 'result')
|
||||||
|
|
||||||
def showName(self):
|
def showName(self):
|
||||||
'''Name and first name, by default, are always shown.'''
|
'''Name and first name, by default, are always shown.'''
|
||||||
|
@ -29,7 +31,8 @@ class UserWrapper(AbstractWrapper):
|
||||||
if login == 'admin':
|
if login == 'admin':
|
||||||
return 'This username is reserved.' # XXX Translate
|
return 'This username is reserved.' # XXX Translate
|
||||||
# Check that no user or group already uses this login.
|
# Check that no user or group already uses this login.
|
||||||
if self.count('User', login=login) or self.count('Group', login=login):
|
if self.count('User', noSecurity=True, login=login) or \
|
||||||
|
self.count('Group', noSecurity=True, login=login):
|
||||||
return 'This login is already in use.' # XXX Translate
|
return 'This login is already in use.' # XXX Translate
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,8 @@ class AbstractWrapper(object):
|
||||||
elif name == 'user':
|
elif name == 'user':
|
||||||
return self.o.getUser()
|
return self.o.getUser()
|
||||||
elif name == 'appyUser':
|
elif name == 'appyUser':
|
||||||
return self.search1('User', login=self.o.getUser().getId())
|
return self.search1('User', noSecurity=True,
|
||||||
|
login=self.o.getUser().getId())
|
||||||
elif name == 'fields': return self.o.getAllAppyTypes()
|
elif name == 'fields': return self.o.getAllAppyTypes()
|
||||||
# Now, let's try to return a real attribute.
|
# Now, let's try to return a real attribute.
|
||||||
res = object.__getattribute__(self, name)
|
res = object.__getattribute__(self, name)
|
||||||
|
|
|
@ -106,8 +106,7 @@ class Debianizer:
|
||||||
|
|
||||||
def __init__(self, app, out, appVersion='0.1.0',
|
def __init__(self, app, out, appVersion='0.1.0',
|
||||||
pythonVersions=('2.6',), zopePort=8080,
|
pythonVersions=('2.6',), zopePort=8080,
|
||||||
depends=('zope2.12', 'openoffice.org', 'imagemagick'),
|
depends=('openoffice.org', 'imagemagick'), sign=False):
|
||||||
sign=False):
|
|
||||||
# app is the path to the Python package to Debianize.
|
# app is the path to the Python package to Debianize.
|
||||||
self.app = app
|
self.app = app
|
||||||
self.appName = os.path.basename(app)
|
self.appName = os.path.basename(app)
|
||||||
|
@ -262,6 +261,10 @@ class Debianizer:
|
||||||
# Create postinst, a script that will:
|
# Create postinst, a script that will:
|
||||||
# - bytecompile Python files after the Debian install
|
# - bytecompile Python files after the Debian install
|
||||||
# - change ownership of some files if required
|
# - change ownership of some files if required
|
||||||
|
# - [in the case of a app-package] execute:
|
||||||
|
# apt-get -t squeeze-backports install zope2.12
|
||||||
|
# (if zope2.12 is defined as a simple dependency in field "Depends:"
|
||||||
|
# it will fail because it will not be searched in squeeze-backports).
|
||||||
# - [in the case of an app-package] call update-rc.d for starting it at
|
# - [in the case of an app-package] call update-rc.d for starting it at
|
||||||
# boot time.
|
# boot time.
|
||||||
f = file('postinst', 'w')
|
f = file('postinst', 'w')
|
||||||
|
@ -273,6 +276,8 @@ class Debianizer:
|
||||||
self.appName)
|
self.appName)
|
||||||
content += 'if [ -e %s ]\nthen\n%sfi\n' % (bin, cmds)
|
content += 'if [ -e %s ]\nthen\n%sfi\n' % (bin, cmds)
|
||||||
if self.appName != 'appy':
|
if self.appName != 'appy':
|
||||||
|
# Install zope2.12 from squeeze-backports
|
||||||
|
content += 'apt-get -t squeeze-backports install zope2.12\n'
|
||||||
# Allow user "zope", that runs the Zope instance, to write the
|
# Allow user "zope", that runs the Zope instance, to write the
|
||||||
# database and log files.
|
# database and log files.
|
||||||
content += 'chown -R zope:root /var/lib/%s\n' % self.appNameLower
|
content += 'chown -R zope:root /var/lib/%s\n' % self.appNameLower
|
||||||
|
|
Loading…
Reference in a new issue