From 6d6c842f128b17089cba00fa0038fedb3cd0af4c Mon Sep 17 00:00:00 2001 From: Gaetan Delannay Date: Wed, 5 Mar 2014 13:25:36 +0100 Subject: [PATCH] [gen] Allow for ajax-based master-slave relationships within the search screen for Ref fields. --- fields/__init__.py | 31 ++++++++++++++++++---------- fields/boolean.py | 7 ++----- fields/computed.py | 6 ++---- fields/date.py | 8 ++------ fields/float.py | 6 ++---- fields/integer.py | 6 ++---- fields/ref.py | 41 ++++++++++++++++++++----------------- fields/string.py | 6 ++---- gen/layout.py | 3 ++- gen/mixins/ToolMixin.py | 14 ++----------- gen/mixins/__init__.py | 6 +----- gen/templates/wrappers.pyt | 3 ++- gen/ui/appy.css | 1 + gen/ui/appy.js | 15 +++++++++----- gen/wrappers/ToolWrapper.py | 8 +++----- gen/wrappers/__init__.py | 6 ++++-- 16 files changed, 79 insertions(+), 88 deletions(-) diff --git a/fields/__init__.py b/fields/__init__.py index 2d6d386..cb4dbd2 100644 --- a/fields/__init__.py +++ b/fields/__init__.py @@ -51,15 +51,17 @@ class Field: pxRender = Px(''' :tool.pxLayoutedObject''') @@ -345,8 +348,8 @@ class Field: '''Creates a dictionary indicating, for every layout type, if the field value must be retrieved synchronously or not.''' if isinstance(sync, bool): - sync = {'edit': sync, 'view': sync, 'cell': sync} - for layoutType in ('edit', 'view', 'cell'): + sync = {'edit': sync, 'view': sync, 'cell': sync, 'search': sync} + for layoutType in ('edit', 'view', 'search', 'cell'): if layoutType not in sync: sync[layoutType] = False return sync @@ -405,9 +408,13 @@ class Field: for layoutType in layouts.iterkeys(): if isinstance(layouts[layoutType], basestring): layouts[layoutType] = Table(layouts[layoutType]) - # Derive "view" and "cell" layouts from the "edit" layout when relevant + # Derive "view", "search" and "cell" layouts from the "edit" layout + # when relevant. if 'view' not in layouts: layouts['view'] = Table(other=layouts['edit'], derivedType='view') + if 'search' not in layouts: + layouts['search'] = Table(other=layouts['view'], + derivedType='search') # Create the "cell" layout from the 'view' layout if not specified. if 'cell' not in layouts: layouts['cell'] = Table(other=layouts['view'], derivedType='cell') @@ -587,14 +594,16 @@ class Field: res += '+' return res - def getOnChange(self, zobj, layoutType): + def getOnChange(self, zobj, layoutType, className=None): '''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 - return 'updateSlaves(this,null,%s,%s)' % \ - (q(zobj.absolute_url()), q(layoutType)) + # When the field is on a search screen, we need p_className. + cName = className and (',%s' % q(className)) or '' + return 'updateSlaves(this,null,%s,%s,null,null%s)' % \ + (q(zobj.absolute_url()), q(layoutType), cName) def isEmptyValue(self, value, obj=None): '''Returns True if the p_value must be considered as an empty value.''' diff --git a/fields/boolean.py b/fields/boolean.py index e283d96..e116b84 100644 --- a/fields/boolean.py +++ b/fields/boolean.py @@ -38,9 +38,7 @@ class Boolean(Field): value=":isChecked and 'True' or 'False'"/> ''') - pxSearch = Px(''' - -
   + pxSearch = Px(''' @@ -53,8 +51,7 @@ class Boolean(Field): -
-
''') +

''') def __init__(self, validator=None, multiplicity=(0,1), default=None, show=True, page='main', group=None, layouts = None, move=0, diff --git a/fields/computed.py b/fields/computed.py index ea1a6b3..ec89d06 100644 --- a/fields/computed.py +++ b/fields/computed.py @@ -38,11 +38,9 @@ class Computed(Field): ''') - pxSearch = Px(''' -
   + pxSearch = Px(''' -
''') + size=":field.width" value=":field.sdefault"/>''') def __init__(self, validator=None, multiplicity=(0,1), default=None, show=('view', 'result'), page='main', group=None, diff --git a/fields/date.py b/fields/date.py index c266e93..92a8806 100644 --- a/fields/date.py +++ b/fields/date.py @@ -81,10 +81,7 @@ class Date(Field): ''') - pxSearch = Px(''' - - - + pxSearch = Px('''
''') - pxSearch = Px(''' -
   + pxSearch = Px(''' @@ -48,8 +47,7 @@ class Float(Field): -
-
''') +
''') def __init__(self, validator=None, multiplicity=(0,1), default=None, show=True, page='main', group=None, layouts=None, move=0, diff --git a/fields/integer.py b/fields/integer.py index 76e8823..88d0450 100644 --- a/fields/integer.py +++ b/fields/integer.py @@ -32,8 +32,7 @@ class Integer(Field): maxlength=":field.maxChars" value=":inRequest and requestValue or value" type="text"/>''') - pxSearch = Px(''' -
   + pxSearch = Px(''' @@ -45,8 +44,7 @@ class Integer(Field): -
-
''') +
''') def __init__(self, validator=None, multiplicity=(0,1), default=None, show=True, page='main', group=None, layouts=None, move=0, diff --git a/fields/ref.py b/fields/ref.py index 50d1858..34dc2b6 100644 --- a/fields/ref.py +++ b/fields/ref.py @@ -282,21 +282,20 @@ class Ref(Field): pxEdit = Px(''' ''') - pxSearch = Px(''' -
   + pxSearch = Px(''' :_('search_and')
- -
''') + ''') def __init__(self, klass=None, attribute=None, validator=None, multiplicity=(0,1), default=None, add=False, addConfirm=False, @@ -740,11 +741,12 @@ class Ref(Field): newIndex = oldIndex + move uids.insert(newIndex, uid) - def getSelectableObjects(self, obj): - '''This method returns the list of all objects that can be selected to - be linked as references to p_obj via p_self. If master values are - present in the request, we use field.masterValues method instead of - self.select.''' + def getPossibleValues(self, obj): + '''This method returns the list of all objects that can be selected + to be linked as references to p_obj via p_self. It is applicable only + for ref fields with link=True. If master values are present in the + request, we use field.masterValues method instead of self.select. + ''' req = obj.request if 'masterValues' in req: # Convert masterValue(s) from UID(s) to real object(s). @@ -762,8 +764,8 @@ class Ref(Field): 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). + # 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 @@ -774,7 +776,7 @@ class Ref(Field): return self.select(obj) xhtmlToText = re.compile('<.*?>', re.S) - def getReferenceLabel(self, refObject): + def getReferenceLabel(self, refObject, unlimited=False): '''p_self must have link=True. I need to display, on an edit view, the p_refObject in the listbox that will allow the user to choose which object(s) to link through the Ref. The information to display may @@ -793,6 +795,7 @@ class Ref(Field): if res: prefix = ' | ' res += prefix + value + if unlimited: return res maxWidth = self.width or 30 if len(res) > maxWidth: res = res[:maxWidth-2] + '...' diff --git a/fields/string.py b/fields/string.py index 3ff6d25..bbbe936 100644 --- a/fields/string.py +++ b/fields/string.py @@ -144,8 +144,7 @@ class String(Field): :field.pxView''') - pxSearch = Px(''' -
   + pxSearch = Px(''' :ztool.truncateValue(v[1], field.swidth) -

- ''') +
''') # Some predefined functions that may also be used as validators @staticmethod diff --git a/gen/layout.py b/gen/layout.py index 0a3a0b6..140b625 100644 --- a/gen/layout.py +++ b/gen/layout.py @@ -7,6 +7,7 @@ # "edit" represents a given page for a given Appy class, in edit mode. # "cell" represents a cell in a table, like when we need to render a field # value in a query result or in a reference table. +# "search" represents an advanced search screen. # Layout elements for a class or page ------------------------------------------ # s - The page summary, containing summarized information about the page or @@ -113,7 +114,7 @@ class Table: '''Represents a table where to dispose graphical elements.''' simpleParams = ('style', 'css_class', 'cellpadding', 'cellspacing', 'width', 'align') - derivedRepls = {'view': 'hrvd', 'cell': 'ldc'} + derivedRepls = {'view': 'hrvd', 'search': '', 'cell': 'ldc'} def __init__(self, layoutString=None, style=None, css_class='', cellpadding=0, cellspacing=0, width='100%', align='left', other=None, derivedType=None): diff --git a/gen/mixins/ToolMixin.py b/gen/mixins/ToolMixin.py index 8bfa751..9e1dc62 100644 --- a/gen/mixins/ToolMixin.py +++ b/gen/mixins/ToolMixin.py @@ -648,6 +648,8 @@ class ToolMixin(BaseMixin): for attrName in rq.form.keys(): if attrName.startswith('w_') and \ not self._searchValueIsEmpty(attrName): + field = self.getAppyType(attrName[2:], rq.form['className']) + if not field.persist: continue # We have a(n interval of) value(s) that is not empty for a # given field. attrValue = rq.form[attrName] @@ -1384,18 +1386,6 @@ class ToolMixin(BaseMixin): res = self.goto(siteUrl, self.translate('wrong_password_reinit')) return res - def getSearchValues(self, name, className): - '''Gets the possible values for selecting a value for searching field - p_name belonging to class p_className.''' - klass = self.getAppyClass(className, wrapper=True) - method = getattr(klass, name).searchSelect - tool = self.appy() - if method.__class__.__name__ == 'function': - objects = method(tool) - else: - objects = method.__get__(tool)(tool) - return [(o.uid, o) for o in objects] - def getGoogleAnalyticsCode(self): '''If the config defined a Google Analytics ID, this method returns the Javascript code to be included in every page, allowing Google diff --git a/gen/mixins/__init__.py b/gen/mixins/__init__.py index 43d93b3..19b0e33 100644 --- a/gen/mixins/__init__.py +++ b/gen/mixins/__init__.py @@ -690,6 +690,7 @@ class BaseMixin: '''Returns the database value of field named p_name for p_self. If p_onlyIfSync is True, it returns the value only if appyType can be retrieved in synchronous mode.''' + if layoutType == 'search': return # No object in search screens. field = self.getAppyType(name) if not onlyIfSync or (onlyIfSync and field.sync[layoutType]): # We must really get the field value. @@ -699,11 +700,6 @@ class BaseMixin: listType = self.getAppyType(listName) return listType.getInnerValue(self, outerValue, name, int(i)) - def getFormattedFieldValue(self, name, value, showChanges=False): - '''Gets a nice, string representation of p_value which is a value from - field named p_name.''' - return self.getAppyType(name).getFormattedValue(self,value,showChanges) - def getRequestFieldValue(self, name): '''Gets the value of field p_name as may be present in the request.''' # Return the request value for standard fields. diff --git a/gen/templates/wrappers.pyt b/gen/templates/wrappers.pyt index 057e549..54a4e24 100644 --- a/gen/templates/wrappers.pyt +++ b/gen/templates/wrappers.pyt @@ -10,7 +10,8 @@ from appy.gen.wrappers.TranslationWrapper import TranslationWrapper as WT from appy.gen.wrappers.PageWrapper import PageWrapper as WPage from Globals import InitializeClass from AccessControl import ClassSecurityInfo -tfw = {"edit":"f","cell":"f","view":"f"} # Layout for Translation fields +# Layouts for Translation fields +tfw = {"edit":"f","cell":"f","view":"f","search":"f"} diff --git a/gen/ui/appy.css b/gen/ui/appy.css index 9dbd3e7..b576811 100644 --- a/gen/ui/appy.css +++ b/gen/ui/appy.css @@ -93,6 +93,7 @@ input.button { border-width: 0 !important; color: #666666; height: 23px; margin: 0.1em 0 0.3em ; border-bottom: 1px dashed grey } .portletSearch { font-size: 90%; font-style: italic } .inputSearch { height: 15px; width: 132px; margin: 3px 3px 2px 3px !important } +td.search { padding-top: 8px } .content { padding: 14px 14px 9px 15px; background-color: #f1f1f1 } .grey { display: none; position: absolute; left: 0px; top: 0px; z-index:2; background:grey; opacity:0.5; -moz-opacity:0.5; -khtml-opacity:0.5; diff --git a/gen/ui/appy.js b/gen/ui/appy.js index 1b9071e..2101644 100644 --- a/gen/ui/appy.js +++ b/gen/ui/appy.js @@ -244,15 +244,16 @@ function askComputedField(hookId, objectUrl, fieldName) { } function askField(hookId, objectUrl, layoutType, showChanges, masterValues, - requestValue, error){ + requestValue, error, className){ // 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); + var px = fieldName + ':pxRender'; + if (className) px = className + ':' + px; + askAjaxChunk(hookId, 'GET', objectUrl, px, params, null, evalInnerScripts); } // Function used by checkbox widgets for having radio-button-like behaviour @@ -354,6 +355,8 @@ function getSlaves(master) { allSlaves = getElementsHavingName('table', 'slave'); res = []; masterName = master.attributes['name'].value; + // Remove leading 'w_' if the master is in a search screen. + if (masterName.indexOf('w_') == 0) masterName = masterName.slice(2); if (master.type == 'checkbox') { masterName = masterName.substr(0, masterName.length-8); } @@ -370,7 +373,8 @@ function getSlaves(master) { return res; } -function updateSlaves(master,slave,objectUrl,layoutType,requestValues,errors){ +function updateSlaves(master, slave, objectUrl, layoutType, requestValues, + errors, className){ /* 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. */ @@ -401,7 +405,8 @@ function updateSlaves(master,slave,objectUrl,layoutType,requestValues,errors){ var err = null; if (errors && (slaveName in errors)) err = errors[slaveName]; - askField(slaveId,objectUrl,layoutType,false,masterValues,reqValue,err); + askField(slaveId, objectUrl, layoutType, false, masterValues, reqValue, + err, className); } } } diff --git a/gen/wrappers/ToolWrapper.py b/gen/wrappers/ToolWrapper.py index c5b1a4b..b303330 100644 --- a/gen/wrappers/ToolWrapper.py +++ b/gen/wrappers/ToolWrapper.py @@ -495,6 +495,7 @@ class ToolWrapper(AbstractWrapper): refInfo=req.get('ref', None); searchInfo=ztool.getSearchInfo(className, refInfo); cssJs={}; + layoutType='search'; x=ztool.getCssJs(searchInfo.fields, 'edit', cssJs)"> @@ -506,7 +507,6 @@ class ToolWrapper(AbstractWrapper):

:_('%s_plural'%className):_('search_title')

-
@@ -516,13 +516,11 @@ class ToolWrapper(AbstractWrapper):
- diff --git a/gen/wrappers/__init__.py b/gen/wrappers/__init__.py index 79e8f07..93a88bc 100644 --- a/gen/wrappers/__init__.py +++ b/gen/wrappers/__init__.py @@ -575,7 +575,9 @@ class AbstractWrapper(object): lang=ztool.getUserLanguage(); q=ztool.quote; action=req.get('action', None); px=req['px'].split(':'); - field=(len(px) == 2) and zobj.getAppyType(px[0]) or None; + className=(len(px) == 3) and px[0] or None; + field=className and zobj.getAppyType(px[1], className) or None; + field=(len(px) == 2) and zobj.getAppyType(px[0]) or field; dir=ztool.getLanguageDirection(lang); dleft=(dir == 'ltr') and 'left' or 'right'; dright=(dir == 'ltr') and 'right' or 'left'; @@ -590,7 +592,7 @@ class AbstractWrapper(object): :getattr(obj, px[0]) - :getattr(field, px[1]) + :getattr(field, px[-1])''') # --------------------------------------------------------------------------