[gen] Added field.persist to avoid storing values for fields that do not require it (like master fields only used to determine selectable values among slave fields).

This commit is contained in:
Gaetan Delannay 2014-03-04 15:03:37 +01:00
parent b8ceb66a49
commit ea08d7981f
19 changed files with 106 additions and 75 deletions

View file

@ -61,8 +61,7 @@ class Field:
value=zobj.getFormattedFieldValue(name, rawValue, showChanges);
requestValue=zobj.getRequestFieldValue(name);
inRequest=req.has_key(name);
errors=errors|();
inError=name in errors;
error=req.get('%s_error' % name);
isMultiple=(field.multiplicity[1] == None) or \
(field.multiplicity[1] > 1);
masterCss=field.slaves and ('master_%s' % name) or '';
@ -86,8 +85,8 @@ class Field:
src=":url('help')"/></acronym>''')
# Displays validation-error-related info about a field.
pxValidation = Px('''<x><acronym if="inError" title=":errors[name]"><img
src=":url('warning')"/></acronym><img if="not inError"
pxValidation = Px('''<x><acronym if="error" title=":error"><img
src=":url('warning')"/></acronym><img if="not error"
src=":url('warning_no.gif')"/></x>''')
# Displays the fact that a field is required.
@ -107,7 +106,7 @@ class Field:
layouts, move, indexed, searchable, specificReadPermission,
specificWritePermission, width, height, maxChars, colspan,
master, masterValue, focus, historized, sync, mapping, label,
sdefault, scolspan, swidth, sheight):
sdefault, scolspan, swidth, sheight, persist):
# The validator restricts which values may be defined. It can be an
# interval (1,None), a list of string values ['choice1', 'choice2'],
# a regular expression, a custom function, a Selection instance, etc.
@ -215,6 +214,10 @@ class Field:
# Width and height for the search widget
self.swidth = swidth or width
self.sheight = sheight or height
# "persist" indicates if field content must be stored in the database.
# For some fields it is not wanted (ie, fields used only as masters to
# update slave's selectable values).
self.persist = persist
def init(self, name, klass, appName):
'''When the application server starts, this secondary constructor is
@ -584,26 +587,14 @@ class Field:
res += '+'
return res
def getOnChange(self, name, zobj, layoutType):
def getOnChange(self, zobj, layoutType):
'''When this field is a master, this method computes the call to the
Javascript function that will be called when its value changes (in
order to update slaves).'''
if not self.slaves: return ''
q = zobj.getTool().quote
# Create the dict of request values for slave fields.
rvs = {}
req = zobj.REQUEST
for slave in self.slaves:
name = slave.name
if not req.has_key(name): continue
if not req[name]: continue
rvs[name] = req[name]
if rvs:
rvs = ',%s' % sutils.getStringDict(rvs)
else:
rvs = ''
return 'updateSlaves(this,null,%s,%s%s)' % \
(q(zobj.absolute_url()), q(layoutType), rvs)
return 'updateSlaves(this,null,%s,%s)' % \
(q(zobj.absolute_url()), q(layoutType))
def isEmptyValue(self, value, obj=None):
'''Returns True if the p_value must be considered as an empty value.'''
@ -680,7 +671,7 @@ class Field:
def store(self, obj, value):
'''Stores the p_value (produced by m_getStorableValue) that complies to
p_self type definition on p_obj.'''
setattr(obj, self.name, value)
if self.persist: setattr(obj, self.name, value)
def callMethod(self, obj, method, cache=True):
'''This method is used to call a p_method on p_obj. p_method is part of

View file

@ -74,7 +74,7 @@ class Action(Field):
move, indexed, False, specificReadPermission,
specificWritePermission, width, height, None, colspan,
master, masterValue, focus, historized, False, mapping,
label, None, None, None, None)
label, None, None, None, None, False)
self.validable = False
def getDefaultLayouts(self): return {'view': 'l-f', 'edit': 'lrv-f'}

View file

@ -23,19 +23,17 @@ from appy.gen.layout import Table
class Boolean(Field):
'''Field for storing boolean values.'''
pxView = pxCell = Px('''
<x><x>:value</x>
<input type="hidden" if="masterCss"
class=":masterCss" value=":rawValue" name=":name" id=":name"/>
</x>''')
pxView = pxCell = Px('''<x>:value</x>
<input type="hidden" if="masterCss"
class=":masterCss" value=":rawValue" name=":name" id=":name"/>''')
pxEdit = Px('''
<x var="isChecked=field.isChecked(zobj, rawValue)">
<input type="checkbox" name=":name + '_visible'" id=":name"
class=":masterCss" checked=":isChecked"
onclick=":'toggleCheckbox(%s, %s); %s' % (q(name), \
q('%s_hidden' % name), field.getOnChange(name, zobj, \
layoutType))"/>
q('%s_hidden' % name), \
field.getOnChange(zobj, layoutType))"/>
<input type="hidden" name=":name" id=":'%s_hidden' % name"
value=":isChecked and 'True' or 'False'"/>
</x>''')
@ -64,13 +62,14 @@ class Boolean(Field):
specificWritePermission=False, width=None, height=None,
maxChars=None, colspan=1, master=None, masterValue=None,
focus=False, historized=False, mapping=None, label=None,
sdefault=False, scolspan=1, swidth=None, sheight=None):
sdefault=False, scolspan=1, swidth=None, sheight=None,
persist=True):
Field.__init__(self, validator, multiplicity, default, show, page,
group, layouts, move, indexed, searchable,
specificReadPermission, specificWritePermission, width,
height, None, colspan, master, masterValue, focus,
historized, True, mapping, label, sdefault, scolspan,
swidth, sheight)
swidth, sheight, persist)
self.pythonType = bool
# Layout including a description

View file

@ -222,7 +222,7 @@ class Calendar(Field):
layouts, move, False, False, specificReadPermission,
specificWritePermission, width, height, None, colspan,
master, masterValue, focus, False, True, mapping, label,
None, None, None, None)
None, None, None, None, True)
# eventTypes can be a "static" list or tuple of strings that identify
# the types of events that are supported by this calendar. It can also
# be a method that computes such a "dynamic" list or tuple. When

View file

@ -70,7 +70,7 @@ class Computed(Field):
specificReadPermission, specificWritePermission, width,
height, None, colspan, master, masterValue, focus,
historized, sync, mapping, label, sdefault, scolspan,
swidth, sheight)
swidth, sheight, False)
self.validable = False
def getValue(self, obj):

View file

@ -173,7 +173,8 @@ class Date(Field):
specificWritePermission=False, width=None, height=None,
maxChars=None, colspan=1, master=None, masterValue=None,
focus=False, historized=False, mapping=None, label=None,
sdefault=None, scolspan=1, swidth=None, sheight=None):
sdefault=None, scolspan=1, swidth=None, sheight=None,
persist=True):
self.format = format
self.calendar = calendar
self.startYear = startYear
@ -186,7 +187,7 @@ class Date(Field):
specificReadPermission, specificWritePermission, width,
height, None, colspan, master, masterValue, focus,
historized, True, mapping, label, sdefault, scolspan,
swidth, sheight)
swidth, sheight, persist)
def getCss(self, layoutType, res):
# CSS files are only required if the calendar must be shown.

View file

@ -279,7 +279,7 @@ class File(Field):
specificReadPermission, specificWritePermission, width,
height, None, colspan, master, masterValue, focus,
historized, True, mapping, label, sdefault, scolspan,
swidth, sheight)
swidth, sheight, True)
@staticmethod
def getFileObject(filePath, fileName=None, zope=False):

View file

@ -58,7 +58,7 @@ class Float(Field):
maxChars=13, colspan=1, master=None, masterValue=None,
focus=False, historized=False, mapping=None, label=None,
sdefault=('',''), scolspan=1, swidth=None, sheight=None,
precision=None, sep=(',', '.'), tsep=' '):
persist=True, precision=None, sep=(',', '.'), tsep=' '):
# The precision is the number of decimal digits. This number is used
# for rendering the float, but the internal float representation is not
# rounded.
@ -80,7 +80,7 @@ class Float(Field):
specificReadPermission, specificWritePermission, width,
height, maxChars, colspan, master, masterValue, focus,
historized, True, mapping, label, sdefault, scolspan,
swidth, sheight)
swidth, sheight, persist)
self.pythonType = float
def getFormattedValue(self, obj, value, showChanges=False):

View file

@ -34,6 +34,6 @@ class Info(Field):
move, indexed, False, specificReadPermission,
specificWritePermission, width, height, None, colspan,
master, masterValue, focus, historized, False, mapping,
label, None, None, None, None)
label, None, None, None, None, False)
self.validable = False
# ------------------------------------------------------------------------------

View file

@ -54,13 +54,14 @@ class Integer(Field):
specificWritePermission=False, width=5, height=None,
maxChars=13, colspan=1, master=None, masterValue=None,
focus=False, historized=False, mapping=None, label=None,
sdefault=('',''), scolspan=1, swidth=None, sheight=None):
sdefault=('',''), scolspan=1, swidth=None, sheight=None,
persist=True):
Field.__init__(self, validator, multiplicity, default, show, page,
group, layouts, move, indexed, searchable,
specificReadPermission, specificWritePermission, width,
height, maxChars, colspan, master, masterValue, focus,
historized, True, mapping, label, sdefault, scolspan,
swidth, sheight)
swidth, sheight, persist)
self.pythonType = long
def validateValue(self, obj, value):

View file

@ -83,7 +83,8 @@ class List(Field):
group, layouts, move, indexed, False,
specificReadPermission, specificWritePermission, width,
height, None, colspan, master, masterValue, focus,
historized, True, mapping, label, None, None, None, None)
historized, True, mapping, label, None, None, None, None,
True)
self.validable = True
# Tuples of (names, Field instances) determining the format of every
# element in the list.

View file

@ -54,7 +54,7 @@ class Ogone(Field):
move, False, False,specificReadPermission,
specificWritePermission, width, height, None, colspan,
master, masterValue, focus, False, True, mapping, label,
None, None, None, None)
None, None, None, None, False)
# orderMethod must contain a method returning a dict containing info
# about the order. Following keys are mandatory:
# * orderID An identifier for the order. Don't use the object UID

View file

@ -84,7 +84,9 @@ class Pod(Field):
move, indexed, searchable, specificReadPermission,
specificWritePermission, width, height, None, colspan,
master, masterValue, focus, historized, False, mapping,
label, None, None, None, None)
label, None, None, None, None, True)
# Param "persist" is set to True but actually, persistence for a pod
# field is determined by freezing.
self.validable = False
def isFrozen(self, obj):

View file

@ -286,7 +286,7 @@ class Ref(Field):
uids=[o.UID() for o in \
field.getLinkedObjects(zobj).objects]"
name=":name" id=":name" size=":isMultiple and field.height or ''"
onchange=":field.getOnChange(name, zobj, layoutType)"
onchange=":field.getOnChange(zobj, layoutType)"
multiple=":isMultiple">
<option value="" if="not isMultiple">:_('choose_a_value')</option>
<option for="ztied in zobjects" var2="uid=ztied.o.UID()"
@ -328,8 +328,8 @@ class Ref(Field):
label=None, queryable=False, queryFields=None, queryNbCols=1,
navigable=False, searchSelect=None, changeOrder=True,
sdefault='', scolspan=1, swidth=None, sheight=None,
render='list', menuIdMethod=None, menuInfoMethod=None,
menuUrlMethod=None):
persist=True, render='list', menuIdMethod=None,
menuInfoMethod=None, menuUrlMethod=None):
self.klass = klass
self.attribute = attribute
# May the user add new objects through this ref ?
@ -431,7 +431,7 @@ class Ref(Field):
specificReadPermission, specificWritePermission, width,
height, None, colspan, master, masterValue, focus,
historized, sync, mapping, label, sdefault, scolspan,
swidth, sheight)
swidth, sheight, persist)
self.validable = self.link
def getDefaultLayouts(self):
@ -662,6 +662,7 @@ class Ref(Field):
* a Zope object;
* a Appy object;
* a list of Appy or Zope objects.'''
if not self.persist: return
# Standardize p_value into a list of Zope objects
objects = value
if not objects: objects = []
@ -759,6 +760,11 @@ class Ref(Field):
res = self.masterValue(obj, masterValues)
return res
else:
# If this field is a ajax-updatable slave, no need to compute
# selectable objects: it will be overridden by method
# self.masterValue by a subsequent ajax request (=the "if" statement
# above).
if self.masterValue and callable(self.masterValue): return []
if not self.select:
# No select method has been defined: we must retrieve all
# objects of the referred type that the user is allowed to

View file

@ -110,7 +110,7 @@ class String(Field):
withTranslations=True, withBlankValue=True)"
name=":name" id=":name" class=":masterCss"
multiple=":isMultiple and 'multiple' or ''"
onchange=":field.getOnChange(name, zobj, layoutType)"
onchange=":field.getOnChange(zobj, layoutType)"
size=":isMultiple and field.height or 1">
<option for="val in possibleValues" value=":val[0]"
selected=":field.isSelected(zobj, name, val[0], rawValue)"
@ -292,8 +292,9 @@ class String(Field):
width=None, height=None, maxChars=None, colspan=1, master=None,
masterValue=None, focus=False, historized=False, mapping=None,
label=None, sdefault='', scolspan=1, swidth=None, sheight=None,
transform='none', styles=('p','h1','h2','h3','h4'),
allowImageUpload=True, inlineEdit=False):
persist=True, transform='none',
styles=('p','h1','h2','h3','h4'), allowImageUpload=True,
inlineEdit=False):
# According to format, the widget will be different: input field,
# textarea, inline editor... Note that there can be only one String
# field of format CAPTCHA by page, because the captcha challenge is
@ -318,7 +319,7 @@ class String(Field):
specificReadPermission, specificWritePermission, width,
height, maxChars, colspan, master, masterValue, focus,
historized, True, mapping, label, sdefault, scolspan,
swidth, sheight)
swidth, sheight, persist)
self.isSelect = self.isSelection()
# If self.isSelect, self.sdefault must be a list of value(s).
if self.isSelect and not sdefault:
@ -390,6 +391,7 @@ class String(Field):
def store(self, obj, value):
'''When the value is XHTML, we perform some cleanup.'''
if not self.persist: return
if (self.format == String.XHTML) and value:
# When image upload is allowed, ckeditor inserts some "style" attrs
# (ie for image size when images are resized). So in this case we

View file

@ -417,7 +417,7 @@ class BaseMixin:
# Trigger field-specific validation
self.intraFieldValidation(errors, values)
if errors.__dict__:
rq.set('errors', errors.__dict__)
for k,v in errors.__dict__.iteritems(): rq.set('%s_error' % k, v)
self.say(errorMessage)
return self.gotoEdit()
@ -425,7 +425,7 @@ class BaseMixin:
msg = self.interFieldValidation(errors, values)
if not msg: msg = errorMessage
if errors.__dict__:
rq.set('errors', errors.__dict__)
for k,v in errors.__dict__.iteritems(): rq.set('%s_error' % k, v)
self.say(msg)
return self.gotoEdit()
@ -814,19 +814,27 @@ class BaseMixin:
res.append(field)
return res
def getSlaveFieldsRequestValues(self, pageName):
'''Returns the list of slave fields having a masterValue being a
method.'''
res = {}
def getSlavesRequestInfo(self, pageName):
'''When slave fields must be updated via Ajax requests, we must carry
some information from the global request object to the ajax requests:
- the selected values in slave fields;
- validation errors.'''
requestValues = {}
errors = {}
req = self.REQUEST
for field in self.getAllAppyTypes():
if field.page.name != pageName: continue
if field.masterValue and callable(field.masterValue):
# We have such a field
# We have a slave field that is updated via ajax requests.
name = field.name
# Remember the request value for this field if present.
if req.has_key(name) and req[name]:
res[name] = req[name]
return sutils.getStringDict(res)
requestValues[name] = req[name]
# Remember the validation error for this field if present.
errorKey = '%s_error' % name
if req.has_key(errorKey):
errors[name] = req[errorKey]
return sutils.getStringDict(requestValues), sutils.getStringDict(errors)
def getCssJs(self, fields, layoutType, res):
'''Gets, in p_res ~{'css':[s_css], 'js':[s_js]}~ the lists of

View file

@ -83,6 +83,20 @@ function evalInnerScripts(xhrObject, hookElem) {
for (var i=0; i<scripts.length; i++) { eval(scripts[i].innerHTML) }
}
function injectChunk(elem, content){
if (!isIe) elem.innerHTML = content;
else {
if (elem.tagName != 'TABLE') elem.innerHTML = content;
else {
/* IE doesn't want to replace content of a table. Force it to do so via
a temporary DOM element. */
var temp = document.createElement('div');
temp.innerHTML = content;
elem.replaceChild(temp.firstChild, elem.firstChild);
}
}
}
function getAjaxChunk(pos) {
// This function is the callback called by the AJAX machinery (see function
// askAjaxChunk below) when an Ajax response is available.
@ -93,13 +107,14 @@ function getAjaxChunk(pos) {
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=\"ui/waiting.gif\"/><\/div>";
if (hookElem)
injectChunk(hookElem, "<div align=\"center\"><img src=\"ui/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;
injectChunk(hookElem, xhrObjects[pos].xhr.responseText);
// Call a custom Javascript function if required
if (xhrObjects[pos].onGet) {
xhrObjects[pos].onGet(xhrObjects[pos], hookElem);
@ -229,12 +244,13 @@ function askComputedField(hookId, objectUrl, fieldName) {
}
function askField(hookId, objectUrl, layoutType, showChanges, masterValues,
requestValue){
requestValue, error){
// Sends an Ajax request for getting the content of any field.
var fieldName = hookId.split('_')[1];
var params = {'layoutType': layoutType, 'showChanges': showChanges};
if (masterValues) params['masterValues'] = masterValues.join('*');
if (requestValue) params[fieldName] = requestValue;
if (error) params[fieldName + '_error'] = error;
askAjaxChunk(hookId, 'GET', objectUrl, fieldName+':pxRender', params, null,
evalInnerScripts);
}
@ -354,7 +370,7 @@ function getSlaves(master) {
return res;
}
function updateSlaves(master, slave, objectUrl, layoutType, requestValues){
function updateSlaves(master,slave,objectUrl,layoutType,requestValues,errors){
/* Given the value(s) in a master field, we must update slave's visibility or
value(s). If p_slave is given, it updates only this slave. Else, it updates
all slaves of p_master. */
@ -382,15 +398,19 @@ function updateSlaves(master, slave, objectUrl, layoutType, requestValues){
var reqValue = null;
if (requestValues && (slaveName in requestValues))
reqValue = requestValues[slaveName];
askField(slaveId, objectUrl, layoutType, false, masterValues, reqValue);
var err = null;
if (errors && (slaveName in errors))
err = errors[slaveName];
askField(slaveId,objectUrl,layoutType,false,masterValues,reqValue,err);
}
}
}
function initSlaves(objectUrl, layoutType, requestValues) {
function initSlaves(objectUrl, layoutType, requestValues, errors) {
/* When the current page is loaded, we must set the correct state for all
slave fields. p_requestValues are those from the slave fields that must
be ajax-updated. */
slave fields. For those that are updated via Ajax requests, their
p_requestValues and validation p_errors must be carried to those
requests. */
slaves = getElementsHavingName('table', 'slave');
i = slaves.length -1;
while (i >= 0) {
@ -398,8 +418,7 @@ function initSlaves(objectUrl, layoutType, requestValues) {
master = document.getElementById(masterName);
// If master is not here, we can't hide its slaves when appropriate.
if (master) {
updateSlaves(master, slaves[i], objectUrl, layoutType, requestValues);
}
updateSlaves(master,slaves[i],objectUrl,layoutType,requestValues,errors);}
i -= 1;
}
}

View file

@ -141,9 +141,10 @@ class ToolWrapper(AbstractWrapper):
</x>''')
pxPageBottom = Px('''
<script type="text/javascript">:'initSlaves(%s,%s,%s)' % \
(q(zobj.absolute_url()), q(layoutType), \
zobj.getSlaveFieldsRequestValues(page))</script>''')
<script var="info=zobj.getSlavesRequestInfo(page)"
type="text/javascript">:'initSlaves(%s,%s,%s,%s)' % \
(q(zobj.absolute_url()), q(layoutType), info[0], info[1])
</script>''')
pxPortlet = Px('''
<x var="toolUrl=tool.url;

View file

@ -533,7 +533,7 @@ class AbstractWrapper(object):
pxEdit = Px('''
<x var="x=zobj.allows('write', raiseError=True);
errors=req.get('errors', None) or {};
errors=req.get('errors', {});
layout=zobj.getPageLayout(layoutType);
cssJs={};
phaseObj=zobj.getAppyPhases(currentOnly=True, \