[pod] When inserting an image via statement do... from document(...), parameter 'sizeUnit' can now be 'pc' (percentage): in this case, percentages are expressed as a tuple (widthPercentage, heightPercentage) in parameter 'size' and must be integers from 1 to 100. [bin] backup.py: better error handling when contacting SMTP server. [gen] Calendar widget for Date fields: bugfix (when the date range is in reverse chronological order). [gen] Ref field: added hook 'afterLink' allowing to execute a method just after an object has been linked. [gen] Ref field: added attribute 'unlinkElement' allowing to define a specific condition for unlinking a given object (before, it was only possible to define, in attribute 'unlink', a global condition allowing to unlink any object from the Ref. [gen] Bugfix: the link to the home page, when clicking on the logo, is fixed.

This commit is contained in:
Gaetan Delannay 2014-07-10 09:46:39 +02:00
parent 268309045a
commit 25f0e8184e
10 changed files with 58 additions and 19 deletions

View file

@ -172,6 +172,9 @@ class ZodbBackuper:
if res:
w('Could not send mail to some recipients. %s' % str(res))
w('Done.')
except smtplib.SMTPException, sme:
w('Error while contacting SMTP server %s (%s).' % \
(self.options.smtpServer, str(se)))
except socket.error, se:
w('Could not connect to SMTP server %s (%s).' % \
(self.options.smtpServer, str(se)))

View file

@ -1 +1 @@
0.9.0
0.9.1

View file

@ -267,7 +267,9 @@ class Date(Field):
'''Gets the Javascript init code for displaying a calendar popup for
this field, for an input named p_name (which can be different from
self.name if, ie, it is a search field).'''
# Always express the range of years in chronological order.
years = [years[0], years[-1]]
years.sort()
return 'Calendar.setup({inputField: "%s", button: "%s_img", ' \
'onSelect: onSelectDate, range:[%d,%d]});' % \
(name, name, years[0], years[-1])
'onSelect: onSelectDate, range:%s})' % (name, name, str(years))
# ------------------------------------------------------------------------------

View file

@ -123,7 +123,7 @@ class Ref(Field):
onclick=":'onDeleteObject(%s)' % q(tiedUid)"/>
</td>
<!-- Unlink -->
<td if="mayUnlink">
<td if="mayUnlink and field.mayUnlinkElement(obj, tied)">
<img var="imgName=linkList and 'unlinkUp' or 'unlink'; action='unlink'"
class="clickable" title=":_('object_unlink')" src=":url(imgName)"
onclick=":'onLink(%s,%s,%s,%s)' % (q(action), q(zobj.id), \
@ -471,8 +471,9 @@ class Ref(Field):
def __init__(self, klass=None, attribute=None, validator=None,
multiplicity=(0,1), default=None, add=False, addConfirm=False,
delete=None, noForm=False, link=True, unlink=None, insert=None,
beforeLink=None, afterUnlink=None, back=None, show=True,
delete=None, noForm=False, link=True, unlink=None,
unlinkElement=None, insert=None, beforeLink=None,
afterLink=None, afterUnlink=None, back=None, show=True,
page='main', group=None, layouts=None, showHeaders=False,
shownInfo=(), select=None, maxPerPage=30, move=0,
indexed=False, searchable=False, specificReadPermission=False,
@ -516,6 +517,11 @@ class Ref(Field):
# By default, one may unlink objects via a Ref for which one can
# link objects.
self.unlink = bool(self.link)
# "unlink" above is a global flag. If it is True, you can go further and
# determine, for every linked object, if it can be unlinked or not by
# defining a method in parameter "unlinkElement" below. This method
# accepts the linked object as unique arg.
self.unlinkElement = unlinkElement
# When an object is inserted through this Ref field, at what position is
# it inserted? If "insert" is:
# None, it will be inserted at the end;
@ -535,11 +541,15 @@ class Ref(Field):
# maintained in the order of their insertion.
self.insert = insert
# Immediately before an object is going to be linked via this Ref field,
# method specified in "beforeLink" wil be executed if specified and will
# method potentially specified in "beforeLink" will be executed and will
# take the object to link as single parameter.
self.beforeLink = beforeLink
# Immediately after an object has been linked via this Ref field, method
# potentially specified in "afterLink" will be executed and will take
# the linked object as single parameter.
self.afterLink = afterLink
# Immediately after an object as been unlinked from this Ref field,
# method specified in "afterUnlink" will be executed if specified and
# method potentially specified in "afterUnlink" will be executed and
# will take the unlinked object as single parameter.
self.afterUnlink = afterUnlink
self.back = None
@ -937,6 +947,8 @@ class Ref(Field):
refs.data.sort(key=lambda uid:self.insert[1](obj, \
tool.getObject(uid, appy=True)))
refs._p_changed = 1
# Execute self.afterLink if present
if self.afterLink: self.afterLink(obj, value)
# Update the back reference
if not back: self.back.linkObject(value, obj, back=True)
@ -945,7 +957,9 @@ class Ref(Field):
p_obj through this Ref field.'''
zobj = obj.o
# Security check
if not noSecurity: zobj.mayEdit(self.writePermission, raiseError=True)
if not noSecurity:
zobj.mayEdit(self.writePermission, raiseError=True)
self.mayUnlinkElement(obj, value, raiseError=True)
# p_value can be a list of objects
if type(value) in sutils.sequenceTypes:
for v in value: self.unlinkObject(obj, v, back=back)
@ -1053,6 +1067,17 @@ class Ref(Field):
q(addConfirmMsg))
return res
def mayUnlinkElement(self, obj, tied, raiseError=False):
'''May we unlink from this Ref field this specific p_tied object?'''
if not self.unlinkElement: return True
res = self.unlinkElement(obj, tied)
if res: return True
else:
if not raiseError: return
# Raise an exception.
obj.o.raiseUnauthorized('field.unlinkElement prevents you to ' \
'unlink this object.')
def getCbJsInit(self, obj):
'''When checkboxes are enabled, this method defines a JS associative
array (named "_appy_objs_cbs") that will store checkboxes' statuses.
@ -1215,16 +1240,21 @@ class Ref(Field):
# Perform the action on every target. Count the number of failed
# operations.
failed = 0
mustDelete = action == 'delete_many'
singleAction = action.split('_')[0]
mustDelete = singleAction == 'delete'
for target in targets:
if mustDelete:
# Delete
if target.o.mayDelete(): target.o.delete()
else: failed += 1
else:
# Link or unlink
exec 'self.%sObject(appyObj, target)' % \
action.split('_')[0]
# Link or unlink. For unlinking, we need to perform an
# additional check.
if (singleAction == 'unlink') and \
not self.mayUnlinkElement(appyObj, target):
failed += 1
else:
exec 'self.%sObject(appyObj, target)' % singleAction
if failed:
msg = obj.translate('action_partial', mapping={'nb':failed})
urlBack = obj.getUrl(rq['HTTP_REFERER'])

View file

@ -153,7 +153,7 @@ class User(ModelClass):
'toTool']
# All methods defined below are fake. Real versions are in the wrapper.
title = gen.String(show=False, indexed=True)
gm = {'group': 'main', 'width': 25}
gm = {'group': 'main', 'width': 34}
def showName(self): pass
name = gen.String(show=showName, **gm)
firstName = gen.String(show=showName, **gm)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 B

After

Width:  |  Height:  |  Size: 43 B

View file

@ -187,8 +187,8 @@ class AbstractWrapper(object):
style=":url(bannerName, bg=True) + '; background-repeat:no-repeat;\
position:relative'">
<!-- Logo (transparent clickable zone by default) -->
<div align=":dleft" style="position: absolute"><a href="/">
<img src=":url('logo')"/></a></div>
<div align=":dleft" style="position: absolute">
<a href=":ztool.getSiteUrl()"><img src=":url('logo')"/></a></div>
<!-- Top links -->
<div style="margin-top: 4px" align=":dright">

View file

@ -54,7 +54,6 @@ class BufferAction:
'''Gets the line describing exception p_e, containing the exception
class, message and line number.'''
return '%s: %s' % (e.__class__.__name__, str(e))
return '%s.%s: %s' % (e.__module__, e.__class__.__name__, str(e))
def manageError(self, result, context, errorMessage, dumpTb=True):
'''Manage the encountered error: dump it into the buffer or raise an

View file

@ -383,7 +383,7 @@ class ImageImporter(DocImporter):
self.format = 'png'
# Retrieve image size from self.size.
width = height = None
if self.size:
if self.size and (self.sizeUnit != 'pc'):
width, height = self.size
if self.sizeUnit == 'px':
# Convert it to cm
@ -397,6 +397,10 @@ class ImageImporter(DocImporter):
# If width and/or height is missing, compute it.
if not width or not height:
width, height = getSize(self.importPath, self.format)
if self.sizeUnit == 'pc':
# Apply the given percentage to the real width and height.
width = width * (float(self.size[0])/100)
height = height * (float(self.size[1])/100)
if width != None:
size = ' %s:width="%fcm" %s:height="%fcm"' % (s, width, s, height)
else:

View file

@ -314,7 +314,8 @@ class Renderer:
(width, height) expressing size in p_sizeUnit (see below).
If not specified, size will be computed from image info;
* p_sizeUnit is the unit for p_size elements, it can be "cm"
(centimeters) or "px" (pixels);
(centimeters), "px" (pixels) or "pc" (percentage). Percentages, in
p_size, must be expressed as integers from 1 to 100.
* if p_style is given, it is the content of a "style" attribute,
containing CSS attributes. If "width" and "heigth" attributes are
found there, they will override p_size and p_sizeUnit.