#-*- encoding: iso-8859-1 -*-
#-*- coding: iso-8859-1 -*-
import re
import sqlalchemy
import PyQt4
from pyHed.common import *
from pyHed.components import *
import frameCustomBtn
"""
TODO:
ok - usar a gerência de condições parecida com a que existe no php2go:
ok - Usar o listBox ou um grid para listar os filtros informados pelo usuário. Caso ele só use um filtro, quando ele clicar em pesquisar executar o filtro
informado.
ok - Somente adicionar um filtro na lista quando ele clicar no botão adicionar filtro.
- Adicionar capacidade de comboBox nos campos para filtros.
"""
class FrameCustomSearcher(frameCustomBtn.FrameCustomBtn):
"""
Classe pai para gerências no sistema. Também é usada para fazer gerências automáticas no sistema, a partir de telas que derivam do FrmCustomBtnDb
Edgar, 02/set/2008
"""
def __init__(self, parent, params, hidePnlSubMenu=True, showAllRecords = True):
# verifica se o programador informou o sql da maneira certa (para o imbecil não perder 1 hora procurando o que está acontecendo...)
if params['SearchSQL'].find('%WHERECLAUSE%') == -1:
raise Exception(u'The %WHERECLAUSE% constant was not informed on the SQL!')
# mostra todos os registros da pesquisa ou não
self.__showAllRecords = showAllRecords
# armazena a sql de pesquisa do parent
self.sql = params['SearchSQL']
# campos para pesquisa
self.searchFields = params['SearchFields']
# títulos das colunas que devem aparecer
self.columnsTitle = params['SearchColumnsTitle']
# armazena o nome da primary key para filtro posterior na tela de edição
self.__primaryKeyFieldName = params['PrimaryKey']
# tipos de pesquisa, captions e seus respectivos representantes na SQL. NÃO SE ESQUECA: caso você adicione um operador novo, não se esqueça de adicionar
# nas três listas
equal = pyHedConsts.translation.getItem('framecustomsearcher', 'equal_condition')
different = pyHedConsts.translation.getItem('framecustomsearcher', 'different_condition')
lessThan = pyHedConsts.translation.getItem('framecustomsearcher', 'lessthan_condition')
biggerThan = pyHedConsts.translation.getItem('framecustomsearcher', 'biggerthan_condition')
empty = pyHedConsts.translation.getItem('framecustomsearcher', 'empty_condition')
notEmpty = pyHedConsts.translation.getItem('framecustomsearcher', 'notempty_condition')
having = pyHedConsts.translation.getItem('framecustomsearcher', 'having_condition')
begging = pyHedConsts.translation.getItem('framecustomsearcher', 'begging_condition')
self.__searchOperatorsTexts = {
'integer': '%s,%s,%s,%s,%s,%s' % (equal, different, lessThan, biggerThan, empty, notEmpty),
'string': '%s,%s,%s,%s' % (having, begging, equal, different),
'date': '%s,%s,%s,%s' % (equal, different, biggerThan, lessThan)
}
self.__searchOperatorsValues = {
'integer': '=,<>,<=, >=, is null, is not null',
'string': ' like , like ,=,!=',
'date': '=, !=, >=, <='
}
self.__searchOperatorsSimbols = {
'integer': 'equal,diferent,<=,>=,null,not null',
'string': 'having,begin,=,!=',
'date': '=,!=,>=,<='
}
# oculta o pnlSubMenu
# TODO: criar uma propriedade para ocultar/desocultar o pnlSubMenu
self.__hidePnlSubMenu = hidePnlSubMenu
super(FrameCustomSearcher, self).__init__(parent)
self.title = params['Caption']
# armazena os filtros e as condições
self.filterList = []
def onPaint(self):
super(FrameCustomSearcher, self).onPaint()
# oculta o panel lateral
if self.__hidePnlSubMenu:
self.hidePnlSubMenu()
# botão para adicionar condição
self.btnNewCondition = components.Button(parent=self.PnlButtons, text=pyHedConsts.translation.getItem('framecustomsearcher', 'add_condition_button'), icon='%s/icon_adicionar_cond_pesquisa.gif' % pyHedConsts.pyHedImagePath, x=10, y=4, width=140, hint=pyHedConsts.translation.getItem('framecustomsearcher', 'add_condition_button_hint'))
self.connect(self.btnNewCondition, PyQt4.QtCore.SIGNAL('clicked()'), self.evtNewCondition)
# Separador de Botões.
self.separator = components.Panel(self.PnlButtons, width=1, height=self.heightPnlButtons - 9, x=self.btnNewCondition.x() + self.btnNewCondition.width() + 10, y=4, bgColor='#000000')
# botao para remover condição de pesquisa
self.btnDelCondition = components.Button(parent=self.PnlButtons, text=pyHedConsts.translation.getItem('framecustomsearcher', 'remove_condition_button'), icon='%s/icon_remover_cond_pesquisa.gif' % pyHedConsts.pyHedImagePath, x=self.btnNewCondition.x() + self.btnNewCondition.width() + 20, y=4, width=140, hint=pyHedConsts.translation.getItem('framecustomsearcher', 'remove_condition_button_hint'))
self.connect(self.btnDelCondition, PyQt4.QtCore.SIGNAL('clicked()'), self.evtDelCondition)
# Separador de Botões.
self.separator2 = components.Panel(self.PnlButtons, width=1, height=self.heightPnlButtons - 9, x=self.btnDelCondition.x() + self.btnDelCondition.width() + 10, y=4, bgColor='#000000')
# botão para executar pesquisa
self.btnExecuteSearch = components.Button(parent=self.PnlButtons, text=pyHedConsts.translation.getItem('framecustomsearcher', 'search_button'), icon='%s/icon_executar_pesquisa.gif' % pyHedConsts.pyHedImagePath, x=self.btnDelCondition.x() + self.btnDelCondition.width() + 20, y=4, width=140, hint=pyHedConsts.translation.getItem('framecustomsearcher', 'search_button_hint'))
self.connect(self.btnExecuteSearch, PyQt4.QtCore.SIGNAL('clicked()'), self.evtExecuteSearch)
# panel que armazena combo com a lista de campos para pesquisa, condição e valor à pesquisar
self.pnlSearch = components.Panel(self.PnlMain, width=self.pnlButtons.width(), height=130, x=0, y=0, bgColor='#FFFFFF')
# ComboBox de campos para pesquisa
self.cmbxSearchFields = components.ComboBox(self.pnlSearch, text=pyHedConsts.translation.getItem('framecustomsearcher', 'label_field_combo_box'), x=10, y=5, width=150)
# adiciona os campos para pesquisa
for fieldName in self.searchFields:
self.cmbxSearchFields.addItem(fieldName.split(',')[0])
# ComboBox de tipos de pesquisa: iniciando, contendo, etc...
self.cmbxOperation = components.ComboBox(self.pnlSearch, text=pyHedConsts.translation.getItem('framecustomsearcher', 'label_operation_combo_box'), x=self.cmbxSearchFields.x() + self.cmbxSearchFields.width() + 20, y=5, width=150)
self.connect(self.cmbxOperation, PyQt4.QtCore.SIGNAL('currentIndexChanged(int)'), self.evtCmbxOperationChanged)
# edit para preenchimento do valor pelo usuário
self.edtValue = components.Edit(self.pnlSearch, text=pyHedConsts.translation.getItem('framecustomsearcher', 'label_condition_edit'), x=self.cmbxOperation.x() + self.cmbxOperation.width() + 20, y=5, width=250)
self.connect(self.edtValue, PyQt4.QtCore.SIGNAL('returnPressed()'), self.evtEdtValueKeyPressed)
# evento change do combo de fields. TEM QUE SER DECLARADO AQUI PORQUE O cmbxOperation deve ser declarado antes
self.connect(self.cmbxSearchFields, PyQt4.QtCore.SIGNAL('currentIndexChanged(int)'), self.evtCmbxSearchFieldsChanged)
# já inicia o combo
self.evtCmbxSearchFieldsChanged(0)
# cria o listBox que lista os filtros
self.listFilters = components.ListBox(self.pnlSearch, text=pyHedConsts.translation.getItem('framecustomsearcher', 'search_filter_list_box'), x=10, y=58, height=50, width=600)
# texto de Limite do resultado
if self.__showAllRecords:
lblSqlLimitText = pyHedConsts.translation.getItem('framecustomsearcher', 'show_all_records_text')
else:
lblSqlLimitText = pyHedConsts.translation.getItem('framecustomsearcher', 'show_50_records_text')
self.lblSqlLimit = components.Label(self.PnlMain, text=lblSqlLimitText, x=self.pnlSearch.width() - 280, y=self.pnlSearch.height() + self.pnlSearch.x() - 20, width=260, useDot=False, hint=pyHedConsts.translation.getItem('framecustomsearcher', 'lbl_limit_hint'), onClickEvent=self.evtLblSqlLimitClicked)
self.lblSqlLimit.setAlignment(PyQt4.QtCore.Qt.AlignRight)
self.lblSqlLimit.setStyleSheet("QLabel { text-decoration: underline; font-weight: bold;}")
self.lblSqlLimit.setCursor(PyQt4.QtCore.Qt.PointingHandCursor)
# cria o grid para listar os dados.
self.gridResult = dbComponents.DbGrid(self.PnlMain, top=self.pnlSearch.height(), height=self.PnlMain.height()-(self.pnlSearch.height()+72), width=self.PnlMain.width()-12)
self.connect(self.gridResult, PyQt4.QtCore.SIGNAL('cellDoubleClicked(int,int)'), self.evtGridResultDoubleClicked)
self.gridResult.setAutoSize(True)
def evtLblSqlLimitClicked(self):
self.__showAllRecords = not self.__showAllRecords
if not self.__showAllRecords:
self.lblSqlLimit.setText(pyHedConsts.translation.getItem('framecustomsearcher', 'show_50_records_text'))
else:
self.lblSqlLimit.setText(pyHedConsts.translation.getItem('framecustomsearcher', 'show_all_records_text'))
def __showResult(self, sql):
"""
Procedure responsável por exibir o resultado da pesquisa no grid e habilitar os componentes necessários
Edgar, 04/set/2008
"""
self.setCursor(PyQt4.QtCore.Qt.WaitCursor)
try:
# Seta a Ordenação do Grid Como Default.
self.gridResult.sortItems(PyQt4.QtCore.Qt.AscendingOrder)
# limpa o grid
self.gridResult.clearGrid()
# re-nomeia os títulos do Grid
self.gridResult.setColumnsHeaders(self.columnsTitle)
# exibe o resultado no grid
self.gridResult.addRecordSet(pyHedConsts.dbInst.getConnection().execute(sql).fetchall())
# oculta determinada coluna que representa a pk do select
self.gridResult.hideIndexColumn('*')
# TODO: melhorar esta funcionalidade. O ideal seria ocultar o grid e no lugar dele mostrar uma msg com este aviso.
if self.gridResult.rowCount() == 0:
components.messageBox(pyHedConsts.translation.getItem('framecustomsearcher', 'lbl_no_records_returned'))
finally:
self.setCursor(PyQt4.QtCore.Qt.ArrowCursor)
def evtGridResultDoubleClicked(self, aRow, aCol):
"""
Evento de duplo click do gridResult.
Edgar, 05/set/2008
"""
# bah!!! Que horror
self.parent.filterRegister(str(self.__primaryKeyFieldName + '=' + str(self.gridResult.item(aRow, self.gridResult.getIndexColumn()).text())))
self.closeFrame()
def evtSetWhereClause(self, fieldName):
"""
ATENÇÃO: caso você queira fazer algum select especial a partir de um campo ou algo parecido, ESTE É O SEU EVENTO!!! :-)
Esta é uma função que retorna o select de determinado field. Caso ela retorne vazio, então o filtro seguirá o padrão da gerência, caso contrário,
o sql que retorne desta procedure será usado como sql para o filtro determinado e o campo será "pulado" da lista.
Edgar, 04/set/2008
"""
pass
def __getWhereClause(self):
"""
Retorna a cláusula where para pesquisa
"""
# TODO: usar o evtSetWhereClause no momento do filtro
# armazena o valor normalizado
searchValue = re.compile('''("|'|<|>|=)''').sub('', unicode(self.edtValue.displayText()))
if searchValue == '' and self.cmbxOperation.currentText() != pyHedConsts.translation.getItem('framecustomsearcher', 'notempty_condition'):
raise pyHedExceptions.UserException(pyHedConsts.translation.getItem('framecustomsearcher', 'search_value_required'))
# verifica qual o tipo de campo selecionado
if (self.searchFields[self.cmbxSearchFields.currentIndex()].split(',')[2]).strip(' ').upper() == 'INTEGER':
filteredSQL = self.searchFields[self.cmbxSearchFields.currentIndex()].split(',')[1] + self.__searchOperatorsValues['integer'].split(',')[self.cmbxOperation.currentIndex()] + searchValue
elif (self.searchFields[self.cmbxSearchFields.currentIndex()].split(',')[2]).strip(' ').upper() == 'STRING':
# like '%<VALOR>%'
if self.__searchOperatorsSimbols['string'].split(',')[self.cmbxOperation.currentIndex()].upper() == 'HAVING':
filteredSQL = pyHedConsts.dbInst.normalizeField(self.searchFields[self.cmbxSearchFields.currentIndex()].split(',')[1]) + self.__searchOperatorsValues['string'].split(',')[self.cmbxOperation.currentIndex()] + pyHedConsts.dbInst.normalizeValue("'%" + searchValue + "%'")
# like '<VALOR>%'
elif self.__searchOperatorsSimbols['string'].split(',')[self.cmbxOperation.currentIndex()].upper() == 'BEGIN':
filteredSQL = pyHedConsts.dbInst.normalizeField(self.searchFields[self.cmbxSearchFields.currentIndex()].split(',')[1]) + self.__searchOperatorsValues['string'].split(',')[self.cmbxOperation.currentIndex()] + pyHedConsts.dbInst.normalizeValue("'" + searchValue + "%'")
# = '<valor>' ou != '<valor>'
else:
filteredSQL = self.searchFields[self.cmbxSearchFields.currentIndex()].split(',')[1] + self.__searchOperatorsValues['string'].split(',')[self.cmbxOperation.currentIndex()] + "'" + searchValue + "'"
elif(self.searchFields[self.cmbxSearchFields.currentIndex()].split(',')[2]).strip(' ').upper() == 'DATE':
filteredSQL = self.searchFields[self.cmbxSearchFields.currentIndex()].split(',')[1] + self.__searchOperatorsValues['date'].split(',')[self.cmbxOperation.currentIndex()] + pyHedConsts.dbInst.normalizeDateValue(searchValue)
else:
raise Exception("There is not such operation")
return filteredSQL
def evtExecuteSearch(self):
"""
Executa a pesquisa a partir dos filtros informados pelo usuário. É chamado pelo botão btnExecuteSearch
Edgar, 04/set/2008
"""
filterList = ''
# se possui uma lista de filtros, utiliza a mesma no where
if len(self.filterList) > 0:
for afilter in self.filterList:
filterList = filterList + afilter + ' and '
filterList = filterList[:len(filterList)-4]
else:
filterList = self.__getWhereClause()
# se tem limitador de registros, então usa o rowNum
# TODO: usar o próprio Alchemy para isso
if not self.__showAllRecords:
filterList = filterList + ' and rownum <= 50'
sql = self.sql.replace('%WHERECLAUSE%', filterList)
# executa o sql
self.__showResult(sql)
def evtDelCondition(self):
"""
Remove o último o filtro da lista de filtros da gerência. Se existir somente um filtro, não faz nada.
Edgar, 04/set/2008
"""
# verifica se existem itens para pesquisa
if len(self.filterList) == 0:
raise pyHedExceptions.UserException(pyHedConsts.translation.getItem('framecustomsearcher', 'no_filters_message'))
# verifica se o usuário selecionou um item para exclusão. Se não houver nada selecionado, seleciona o último item da lista
if self.listFilters.selectedItems() == []:
self.listFilters.setCurrentRow(self.listFilters.count() - 1)
# remove o item da lista de filtros SQL
self.filterList.pop(self.listFilters.currentRow())
# remove a linha o ListBox
item = self.listFilters.takeItem(self.listFilters.currentRow())
del item
def evtNewCondition(self):
"""
Adiciona um novo filtro para pesquisa. É adicionado logo abaixo do filtro atual.
Edgar, 04/set/2008
"""
# adiciona o filtro na lista
self.filterList.append(self.__getWhereClause())
# adiciona o filtro no listBox
self.listFilters.addItem("campo '%s' %s o valor '%s'" % (self.cmbxSearchFields.itemText(self.cmbxSearchFields.currentIndex()), self.cmbxOperation.itemText(self.cmbxOperation.currentIndex()), self.edtValue.text()))
def evtCmbxOperationChanged(self, index):
if self.cmbxOperation.currentText() == pyHedConsts.translation.getItem('framecustomsearcher', 'notempty_condition'):
self.edtValue.visible(False)
else:
self.edtValue.visible(True)
def evtCmbxSearchFieldsChanged(self, index):
"""
Preenche os tipos de condições que o campo (de acordo com o tipo: string, integer, date, etc) selecionado pelo usuário pode ter.
Edgar, 04/set/2008
"""
# TODO: ESTUDAR itemData no ComboBox. Verificar a possibilidade de eliminar alguns dos arrays que temos nesta classe
# limpa os valores de pesquisa e as operações do campo anterior, após preenche as corretas
self.edtValue.clear()
self.cmbxOperation.clear()
for op in self.__searchOperatorsTexts[(self.searchFields[index].split(',')[2]).strip(' ').lower()].split(','):
self.cmbxOperation.addItem(op)
if (self.searchFields[index].split(',')[2]).strip(' ').lower() == 'date':
# userDateFormat is a constant set on the config.ini of the application. If not set, the default is 00/00/0000
# used to set the format that the user will see the date
if pyHedConsts.config.has_key('userdateformat'):
self.edtValue.setInputMask(pyHedConsts.config['userdateformat'])
else:
self.edtValue.setInputMask('00/00/0000')
elif (self.searchFields[index].split(',')[2]).strip(' ').lower() == 'integer':
self.edtValue.setValidator(PyQt4.QtGui.QIntValidator(self))
self.edtValue.setInputMask('')
else:
self.edtValue.setValidator(None)
self.edtValue.setInputMask('')
def evtEdtValueKeyPressed(self):
self.evtExecuteSearch()
def paintEvent(self, ev):
"""
Evento para update da tela.
"""
pass
def closeFrame(self):
# BAH, QUE HORROR!!!! SUPER ULTRA BACALHAU!!!
# desbloqueia o botão de fechar enquanto a pesquisa não retornar...
# TODO: péssima solução! O ideal aqui seria usar os QDialog... Avaliar... - Edgar, 06/out/08 PARABÉNS PARA MIM!!! :-)
if (issubclass(self.parent.__class__, frameCustomBtn.FrameCustomBtn)):
self.parent.btnClose.setEnabled(True)
super(FrameCustomSearcher, self).closeFrame()
# re-opens the tab that actived the search frame
pyHedConsts.frmMain.tabs.setCurrentIndex(pyHedConsts.frmMain.tabs.indexOf(self.parent))