00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029 from xml.parsers import expat
00030
00031 import sys
00032 import gettext
00033
00034 from SearchWidgetFactory import *
00035 from AbstractSearchWidget import *
00036 from Koo.Common import Common
00037 from Koo.Common import Shortcuts
00038 from Koo.Common import Calendar
00039 from Koo.Common import Numeric
00040 from Koo import Rpc
00041
00042 from PyQt4.QtGui import *
00043 from PyQt4.QtCore import *
00044 from PyQt4.uic import *
00045
00046 class SearchFormContainer( QWidget ):
00047 def __init__(self, parent):
00048 QWidget.__init__( self, parent )
00049 layout = QGridLayout( self )
00050 layout.setSpacing( 0 )
00051 layout.setContentsMargins( 0, 0, 0, 0 )
00052
00053 self.col = 4
00054 self.x = 0
00055 self.y = 0
00056
00057 def addWidget(self, widget, name=None):
00058 if self.x + 1 > self.col:
00059 self.x = 0
00060 self.y = self.y + 1
00061
00062
00063
00064 widget.gridLine = self.y
00065 if name:
00066 label = QLabel( name )
00067 label.gridLine = self.y
00068 vbox = QVBoxLayout()
00069 vbox.setSpacing( 0 )
00070 vbox.setContentsMargins( 0, 0, 0, 0 )
00071 vbox.addWidget( label, 0 )
00072 vbox.addWidget( widget )
00073 self.layout().addLayout( vbox, self.y, self.x )
00074 else:
00075 self.layout().addWidget( widget, self.y, self.x )
00076 self.x = self.x + 1
00077
00078 (CustomSearchItemWidgetUi, CustomSearchItemWidgetBase) = loadUiType( Common.uiPath('customsearchitem.ui') )
00079
00080 class CustomSearchItemWidget(AbstractSearchWidget, CustomSearchItemWidgetUi):
00081
00082 operators = (
00083 ('is empty', _('is empty'), ('char', 'text', 'many2one', 'date', 'time', 'datetime', 'float_time'), False),
00084 ('is not empty', _('is not empty'), ('char', 'text', 'many2one', 'date', 'time', 'datetime', 'float_time'), False),
00085 ('ilike', _('contains'), ('char','text','many2one','many2many','one2many'), True),
00086 ('not ilike', _('does not contain'), ('char','text','many2one'), True),
00087 ('=', _('is equal to'), ('boolean','char','text','integer','float','date','time','datetime','float_time'), True),
00088 ('<>', _('is not equal to'), ('boolean','char','text','integer','float','date','time','datetime','float_time'), True),
00089 ('>', _('greater than'), ('char','text','integer','float','date','time','datetime','float_time'), True),
00090 ('<', _('less than'), ('char','text','integer','float','date','time','datetime','float_time'), True),
00091 ('in', _('in'), ('selection','char','text','integer','float','date','time','datetime','float_time'), True),
00092 ('not in', _('not in'), ('selection','char','text','integer','float','date','time','datetime','float_time'), True),
00093 )
00094
00095 typeOperators = {
00096 'char': ('ilike', 'not ilike', '=', '<', '>', '<>'),
00097 'integer': ('=', '<>', '>', '<')
00098 }
00099
00100 typeRelated = ('many2one','many2many','one2many')
00101
00102 def __init__(self, parent=None):
00103 AbstractSearchWidget.__init__(self, '', parent)
00104 CustomSearchItemWidgetUi.__init__(self)
00105 self.setupUi(self)
00106
00107 self.uiRelatedField.setVisible( False )
00108 self.connect(self.uiField, SIGNAL('currentIndexChanged(int)'), self.updateRelatedAndOperators)
00109 self.connect(self.uiRelatedField, SIGNAL('currentIndexChanged(int)'), self.updateOperators)
00110 self.connect(self.uiOperator, SIGNAL('currentIndexChanged(int)'), self.updateValue)
00111
00112 self.fields = None
00113
00114 def setup(self, fields):
00115 self.uiField.addItem( '' )
00116
00117 self.fields = fields
00118
00119 fields = [(x, fields[x].get('string', x)) for x in fields]
00120 fields.sort( key=lambda x: x[1] )
00121 for field in fields:
00122 self.uiField.addItem( field[1], QVariant( field[0] ) )
00123
00124
00125 self.uiOperator.addItem( '' )
00126 for operator in self.operators:
00127 self.uiOperator.addItem( operator[1], QVariant( operator[0] ) )
00128
00129 self.scNew = QShortcut( self )
00130 self.scNew.setKey( Shortcuts.CreateInField )
00131 self.scNew.setContext( Qt.WidgetWithChildrenShortcut )
00132
00133 self.setAndSelected()
00134
00135 self.connect( self.scNew, SIGNAL('activated()'), self, SIGNAL('newItem()') )
00136 self.connect( self.pushNew, SIGNAL('clicked()'), self, SIGNAL('newItem()') )
00137 self.connect( self.pushAnd, SIGNAL('clicked()'), self.andSelected )
00138 self.connect( self.pushOr, SIGNAL('clicked()'), self.orSelected )
00139 self.connect( self.pushRemove, SIGNAL('clicked()'), self, SIGNAL('removeItem()') )
00140
00141 def setValid(self, valid):
00142 if valid:
00143 color = 'white'
00144 else:
00145 color = 'red'
00146 color = QColor( color )
00147 palette = QPalette()
00148 palette.setColor(QPalette.Active, QPalette.Base, color)
00149 self.setPalette(palette);
00150
00151 def setAndSelected(self):
00152 self.pushOr.setChecked( False )
00153 self.pushOr.setEnabled( True )
00154 self.pushAnd.setEnabled( False )
00155 self.pushAnd.setChecked( True )
00156
00157 def setOrSelected(self):
00158 self.pushAnd.setEnabled( True )
00159 self.pushAnd.setChecked( False )
00160 self.pushOr.setChecked( True )
00161 self.pushOr.setEnabled( False )
00162
00163 def isAndSelected(self):
00164 return self.pushAnd.isChecked()
00165
00166 def isOrSelected(self):
00167 return self.pushOr.isChecked()
00168
00169 def copyState(self, widget):
00170 if widget.isAndSelected():
00171 self.setAndSelected()
00172 else:
00173 self.setOrSelected()
00174
00175 def andSelected(self):
00176 self.setAndSelected()
00177
00178 def orSelected(self):
00179 self.setOrSelected()
00180
00181 def updateRelatedAndOperators(self, index):
00182 fieldName = unicode( self.uiField.itemData( self.uiField.currentIndex() ).toString() )
00183 if not fieldName:
00184 return
00185
00186 fieldType = self.fields[fieldName].get('type')
00187
00188 if fieldType in self.typeRelated:
00189 self.uiRelatedField.setVisible( True )
00190 self.uiRelatedField.setCurrentIndex( 0 )
00191 model = self.fields[fieldName].get('relation')
00192 if model:
00193 view = Rpc.session.execute('/object', 'execute', model, 'fields_view_get', False, 'form', Rpc.session.context)
00194 self.relatedFields = view['fields']
00195 else:
00196 self.relatedFields = {}
00197
00198 self.uiRelatedField.clear()
00199 self.uiRelatedField.addItem( '', QVariant('') )
00200 fields = [(x, self.relatedFields[x].get('string', x)) for x in self.relatedFields]
00201 fields.sort( key=lambda x: x[1] )
00202 for field in fields:
00203 self.uiRelatedField.addItem( field[1], QVariant( field[0] ) )
00204 else:
00205 self.uiRelatedField.clear()
00206 self.uiRelatedField.setVisible( False )
00207
00208 self.updateOperators()
00209
00210 def updateOperators(self, index=None):
00211 self.uiOperator.clear()
00212 fieldName = unicode( self.uiField.itemData( self.uiField.currentIndex() ).toString() )
00213 relatedFieldName = unicode( self.uiRelatedField.itemData( self.uiRelatedField.currentIndex() ).toString() )
00214 if not fieldName:
00215 return
00216
00217 if relatedFieldName:
00218 fieldType = self.relatedFields[relatedFieldName].get('type')
00219 else:
00220 fieldType = self.fields[fieldName].get('type')
00221
00222 self.uiOperator.addItem( '' )
00223 for operator in self.operators:
00224 if fieldType in operator[2]:
00225 self.uiOperator.addItem( operator[1], QVariant( operator[0] ) )
00226
00227
00228 def updateValue(self, index):
00229 operator = unicode( self.uiOperator.itemData( self.uiOperator.currentIndex() ).toString() )
00230 for op in self.operators:
00231 if operator == op[0]:
00232 self.uiValue.setVisible( op[3] )
00233 break
00234 self.setValid( True )
00235
00236 def clear(self):
00237 self.uiField.setCurrentIndex( 0 )
00238 self.uiRelatedField.clear()
00239 self.uiOperator.setCurrentIndex( 0 )
00240 self.uiValue.clear()
00241
00242 def correctValue(self, value, fieldName, relatedFieldName):
00243 text = value
00244
00245 if relatedFieldName:
00246 fieldType = self.relatedFields[relatedFieldName].get('type')
00247 else:
00248 fieldType = self.fields[fieldName].get('type')
00249 if fieldType == 'date':
00250 value = Calendar.textToDate( value )
00251 text = Calendar.dateToText( value )
00252 value = Calendar.dateToStorage( value )
00253 elif fieldType == 'datetime':
00254 value = Calendar.textToDateTime( value )
00255 text = Calendar.dateTimeToText( value )
00256 value = Calendar.dateTimeToStorage( value )
00257 elif fieldType == 'float_time':
00258 value = Calendar.textToFloatTime( value )
00259 text = Calendar.floatTimeToText( value )
00260 value = Calendar.floatTimeToStorage( value )
00261 elif fieldType == 'time':
00262 value = Calendar.textToTime( value )
00263 text = Calendar.timeToText( value )
00264 value = Calendar.timeToStorage( value )
00265 elif fieldType == 'float':
00266 value = Numeric.textToFloat( value ) or 0.0
00267 text = Numeric.floatToText( value )
00268 elif fieldType == 'integer':
00269 value = Numeric.textToInteger( value ) or 0.0
00270 text = Numeric.integerToText( value )
00271 elif fieldType == 'selection':
00272
00273 if relatedFieldName:
00274 options = self.relatedFields[relatedFieldName]['selection']
00275 else:
00276 options = self.fields[fieldName]['selection']
00277
00278 text = []
00279 keys = []
00280 for selection in options:
00281
00282
00283
00284 if value.lower().strip() == selection[1].lower().strip():
00285 keys = [ selection[0] ]
00286 text = [ selection[1] ]
00287 break
00288 if value.lower() in selection[1].lower():
00289 keys.append( selection[0] )
00290 text.append( selection[1] )
00291 value = keys
00292 text = ', '.join( text )
00293 elif fieldType == 'boolean':
00294 options = [
00295 (True, _('True')),
00296 (False, _('False')),
00297 ]
00298 text = ''
00299 for selection in options:
00300 if value.lower() in selection[1].lower():
00301 value = selection[0]
00302 text = selection[1]
00303 break
00304 if not text:
00305 value = options[1][0]
00306 text = options[1][1]
00307 return (value, text)
00308
00309 def value(self):
00310 if not self.uiField.currentIndex():
00311 return []
00312 if not self.uiOperator.currentIndex():
00313 self.setValid( False )
00314 return []
00315 self.setValid( True )
00316 fieldName = unicode( self.uiField.itemData( self.uiField.currentIndex() ).toString() )
00317 relatedFieldName = unicode( self.uiRelatedField.itemData( self.uiRelatedField.currentIndex() ).toString() )
00318 operator = unicode( self.uiOperator.itemData( self.uiOperator.currentIndex() ).toString() )
00319 value = unicode( self.uiValue.text() )
00320 if operator in ('in', 'not in'):
00321 text = []
00322 newValue = []
00323 for item in value.split(','):
00324 data = self.correctValue( item.strip(), fieldName, relatedFieldName )
00325 newValue.append( data[0] )
00326 text.append( data[1] )
00327 value = newValue
00328 text = ', '.join( text )
00329
00330 newValue = []
00331 for item in value:
00332 if isinstance(item, list):
00333 newValue += [x for x in item]
00334 else:
00335 newValue.append( item )
00336 value = newValue
00337 elif operator == 'is empty':
00338 operator = '='
00339 value = False
00340 text = ''
00341 elif operator == 'is not empty':
00342 operator = '!='
00343 value = False
00344 text = ''
00345 else:
00346 (value, text) = self.correctValue( value, fieldName, relatedFieldName )
00347
00348 self.uiValue.setText( text )
00349
00350 if self.pushOr.isChecked():
00351 condition = '|'
00352 else:
00353 condition = '&'
00354
00355 if relatedFieldName:
00356 queryFieldName = '%s.%s' % (fieldName, relatedFieldName)
00357 else:
00358 queryFieldName = fieldName
00359 return [condition,(queryFieldName, operator, value)]
00360
00361 class CustomSearchFormWidget(AbstractSearchWidget):
00362 def __init__(self, parent=None):
00363 AbstractSearchWidget.__init__(self, '', parent)
00364
00365 self.layout = QVBoxLayout( self )
00366
00367 self.model = None
00368 self.name = ''
00369 self.focusable = True
00370 self.expanded = True
00371 self._loaded = False
00372 self.fields = None
00373 self.widgets = []
00374
00375 self.widgets = []
00376
00377
00378 def isLoaded(self):
00379 return self._loaded
00380
00381
00382 def isEmpty(self):
00383 if len(self.widgets):
00384 return False
00385 else:
00386 return True
00387
00388
00389
00390
00391
00392
00393 def setItemValid(self, number, valid):
00394 self.widgets[number].setValid( valid )
00395
00396 def setAllItemsValid(self, valid):
00397 for number in xrange(self.itemCount()):
00398 self.setItemValid(number, valid)
00399
00400 def insertItem(self, previousItem=None):
00401 filterItem = CustomSearchItemWidget( self )
00402 filterItem.setup( self.fields )
00403 if previousItem:
00404 index = self.layout.indexOf(previousItem)+1
00405 filterItem.copyState( previousItem )
00406 self.layout.insertWidget( index, filterItem )
00407 self.widgets.insert(index, filterItem)
00408 else:
00409 self.layout.addWidget( filterItem )
00410 self.widgets.append( filterItem )
00411 self.connect( filterItem, SIGNAL('newItem()'), self.newItem )
00412 self.connect( filterItem, SIGNAL('removeItem()'), self.removeItem )
00413
00414 def dropItem(self, item):
00415 if len(self.widgets) > 1:
00416 self.layout.removeWidget( item )
00417 item.hide()
00418 self.widgets.remove( item )
00419 else:
00420 item.clear()
00421
00422 def newItem(self):
00423 self.insertItem( self.sender() )
00424
00425 def removeItem(self):
00426 self.dropItem( self.sender() )
00427
00428 def itemCount(self):
00429 return len(self.widgets)
00430
00431
00432
00433
00434
00435 def setup(self, fields, domain):
00436
00437 if self._loaded:
00438 return
00439 self._loaded = True
00440
00441 self.fields = fields
00442 self.insertItem()
00443
00444
00445
00446
00447
00448
00449
00450
00451
00452
00453
00454 def setFocus(self):
00455 pass
00456
00457
00458
00459
00460
00461
00462
00463
00464 def clear(self):
00465 for item in self.widgets[:]:
00466 self.dropItem( item )
00467 self.setAllItemsValid(True)
00468
00469
00470
00471
00472
00473 def value(self, domain=[]):
00474 res = []
00475 for x in self.widgets:
00476 res += x.value()
00477
00478 if res:
00479
00480 res = res[:-2] + res[-1:]
00481
00482 v_keys = [x[0] for x in res]
00483 for f in domain:
00484 if f[0] not in v_keys:
00485 res.append(f)
00486 return res
00487
00488
00489
00490
00491
00492
00493
00494
00495
00496
00497 def setValue(self, val):
00498 pass
00499
00500
00501
00502