#-*- encoding: iso-8859-1 -*-
#-*- coding: iso-8859-1 -*-
import re
import socket
import datetime
import PyQt4
import sqlalchemy
from pyHed.common import *
from pyHed.components import *
from pyHed.db import *
import frameCustomBtn
import frameCustomSearcher
class FrameCustomBtnDb(frameCustomBtn.FrameCustomBtn):
def __init__(self, parent, params=None, allowedOperations=[pyHedConsts.opInsert, pyHedConsts.opUpdate, pyHedConsts.opDelete]):
"""
FrameCustomBtnDb is used to create data forms (input data forms/frames). It uses SQLAlchemy to do the default db operations (insert/update/delete)
automaticly. The developer only need to id the fields (against the ORM class) and let pyHed (using SQLAlchemy) do the rest...
- params: any param. Can be used to transfer data between classes...
- allowedOperations: the allowed operations in this frame. By default, all operations are allowed and the AccessControl will set wich one are
or are not allowed.
Edgar, 19/dez/2008
"""
# Allowed operations. Fist, loads the AccessControl permissions. After, if this frame have full access operation (defined by the accessControl control),
# set the allowed operations defined by the developer
# 2 = visible and editable
# 1 = visible
# 0 = Invisible
self.validateAccessPermition(allowedOperations)
super(FrameCustomBtnDb, self).__init__(parent, params)
# just a "shortcut" to the dbSession
self.session = pyHedConsts.dbInst.getSession()
# dbClass ORM. This is your data table db class
self.dbClass = None
# Operation executed in the frame
self.__operation = None
# current ORM register
self.__currentRegister = None
# SQL search param. Used to create a default search screen
self.__searchSQL = None
# search grid columns
self.__searchColumnsTitle = None
# primary key of the table/view
self.__primaryKey = None
# Search fields: String array: <field caption>,<field name>,<field type>. Ex: Name, NAME, String
self.__searchFields = []
def validateAccessPermition(self, operations):
"""
Allowed operations (Insert, Update and Delete or Nothing) on screen. It load's from the AccessControl class. If you don't you the default AccessControl
of pyHed, you can override this method and write your own validation.
- 2 = Visible and Editable (accessControl.AcVisibleEditable)
- 1 = Visible (accessControl.AcVisible)
- 0 = Invisible (accessControl.AcInvisible)
Edgar, 13/07/2009
"""
if pyHedConsts.AccessControlInst.getAcessoAcao(self.__module__) == accessControl.AcVisibleEditable:
self.__allowedOperations = operations
else:
self.__allowedOperations = []
def onPaint(self):
super(FrameCustomBtnDb, self).onPaint()
# New register button
self.btnNew = components.Button(parent=self.PnlButtons, text=pyHedConsts.translation.getItem('framecustombtndb', 'new_button'), icon='%s/icon_novo.gif' % pyHedConsts.pyHedImagePath, x=10, y=4, width=80, hint=pyHedConsts.translation.getItem('framecustombtndb', 'new_button_hint'))
self.connect(self.btnNew, PyQt4.QtCore.SIGNAL('clicked()'), self.newRecord)
self.btnNew.setEnabled(self.__allowedOperations.count(pyHedConsts.opInsert) > 0)
# Edit current register button
self.btnEdit = components.Button(parent=self.PnlButtons, text=pyHedConsts.translation.getItem('framecustombtndb', 'edit_button'), icon='%s/icon_editar.gif' % pyHedConsts.pyHedImagePath, x=self.btnNew.width() + 20, y=4, width=80, hint=pyHedConsts.translation.getItem('framecustombtndb', 'edit_button_hint'))
self.connect(self.btnEdit, PyQt4.QtCore.SIGNAL('clicked()'), self.edit)
self.btnEdit.setEnabled(False)
# delete register button
self.btnDelete = components.Button(parent=self.PnlButtons, text=pyHedConsts.translation.getItem('framecustombtndb', 'del_button'), icon='%s/icon_excluir.gif' % pyHedConsts.pyHedImagePath, x=self.btnEdit.x() + self.btnEdit.width() + 20, y=4, width=80, hint=pyHedConsts.translation.getItem('framecustombtndb', 'del_button_hint'))
self.connect(self.btnDelete, PyQt4.QtCore.SIGNAL('clicked()'), self.delete)
self.btnDelete.setEnabled(False)
# Button separator. BACALHAU!!! :-)
self.separator = components.Panel(self.PnlButtons, width=1, height=self.heightPnlButtons - 9, x=self.btnDelete.x() + self.btnDelete.width() + 10, y=4, bgColor='#000000')
# Save data button
self.btnSave = components.Button(parent=self.PnlButtons, text=pyHedConsts.translation.getItem('framecustombtndb', 'save_button'), icon='%s/icon_salvar.gif' % pyHedConsts.pyHedImagePath, x=self.btnDelete.x() + self.btnDelete.width() + 20, y=4, width=80, hint=pyHedConsts.translation.getItem('framecustombtndb', 'save_button_hint'))
self.btnSave.setEnabled(False)
self.connect(self.btnSave, PyQt4.QtCore.SIGNAL('clicked()'), self.post)
# Cancel button
self.btnCancel = components.Button(parent=self.PnlButtons, text=pyHedConsts.translation.getItem('framecustombtndb', 'cancel_button'), icon='%s/icon_cancelar.gif' % pyHedConsts.pyHedImagePath, x=self.btnSave.x() + self.btnSave.width() + 20, y=4, width=80, hint=pyHedConsts.translation.getItem('framecustombtndb', 'cancel_button_hint'))
self.btnCancel.setEnabled(False)
self.connect(self.btnCancel, PyQt4.QtCore.SIGNAL('clicked()'), self.cancelEdit)
# Button separator. BACALHAU!!! :-)
self.separator2 = components.Panel(self.PnlButtons, width=1, height=self.heightPnlButtons - 9, x=self.btnCancel.x() + self.btnCancel.width() + 10, y=4, bgColor='#000000')
# Search button
self.btnSearch = components.Button(parent=self.PnlButtons, text=pyHedConsts.translation.getItem('framecustombtndb', 'search_button'), icon='%s/icon_pesquisar.gif' % pyHedConsts.pyHedImagePath, x=self.btnCancel.x() + self.btnCancel.width() + 20, y=4, width=85, hint=pyHedConsts.translation.getItem('framecustombtndb', 'search_button_hint'))
self.btnSearch.setVisible(False)
self.connect(self.btnSearch, PyQt4.QtCore.SIGNAL('clicked()'), self.evtPesquisar)
def closeFrame(self):
if self.Operation in (pyHedConsts.opInsert, pyHedConsts.opUpdate):
if components.messageDlg(pyHedConsts.translation.getItem('framecustombtndb', 'save_question')) == PyQt4.QtGui.QMessageBox.Yes:
super(FrameCustomBtnDb, self).closeFrame()
else:
super(FrameCustomBtnDb, self).closeFrame()
def setFrameOperation(self, operation):
"""
Sets the current operation on the frame: Insert, Update, browser, None.
You can see that there is a if condition for each operation. We decide to do that for a better understand.
Responsável por setar o status da tela: Insert, Update, browser, None.
Caso você precise fazer insert, update e/ou delete na mão, você terá OBRIGATORIAMENTE de usar este método.
Para exemplo, veja os eventos de Insert, Update ou Delete do frmCustomBtnDb
Edgar, 01/set/2008
"""
if operation == pyHedConsts.opInsert:
# disable the buttons
self.btnNew.setEnabled(False)
self.btnEdit.setEnabled(False)
self.btnDelete.setEnabled(False)
self.btnCancel.setEnabled(True)
self.btnSave.setEnabled(True)
# allows edit in all dbCompoonents
self.setDbComponentsReadOnly(False, self.PnlMain)
# clean the dbComponents values
self.clearDbComponents(self.PnlMain)
elif operation == pyHedConsts.opBrowse:
# enabled the correct buttons
self.btnNew.setEnabled(self.__allowedOperations.count(pyHedConsts.opInsert) > 0)
self.btnEdit.setEnabled(self.__allowedOperations.count(pyHedConsts.opUpdate) > 0)
self.btnDelete.setEnabled(self.__allowedOperations.count(pyHedConsts.opDelete) > 0)
self.btnCancel.setEnabled(False)
self.btnSave.setEnabled(False)
# fill the dbComponents values
self.fillDbComponents(self.PnlMain)
# make the dbComponents not editable
self.setDbComponentsReadOnly(True, self.PnlMain)
elif operation == pyHedConsts.opUpdate:
# disable the correct buttons
self.btnNew.setEnabled(False)
self.btnEdit.setEnabled(False)
self.btnDelete.setEnabled(False)
self.btnCancel.setEnabled(True)
self.btnSave.setEnabled(True)
# allows edit in all dbCompoonents
self.setDbComponentsReadOnly(False, self.PnlMain)
elif operation == pyHedConsts.opNone:
# disable the correct buttons
self.btnNew.setEnabled(self.__allowedOperations.count(pyHedConsts.opInsert) > 0)
self.btnEdit.setEnabled(False)
self.btnDelete.setEnabled(False)
self.btnCancel.setEnabled(False)
self.btnSave.setEnabled(False)
# clean the dbComponents values
self.clearDbComponents(self.PnlMain)
# make the dbComponents not editable
self.setDbComponentsReadOnly(True, self.PnlMain)
def setDbComponentsReadOnly(self, readOnly, container):
"""
make all the dbComponents that have the container as your parent editable or not
- readOnly: true/false editabled or not
- container: search for all dbComponents "inside" this container
Edgar, 01/set/2008
"""
for comp in container.children():
# if comp is a container component, than search for dbComponents "inside" of it.
if comp.children() != [] and not issubclass(comp.__class__, dbComponents.DbCustom):
self.setDbComponentsReadOnly(readOnly, comp)
else:
if (issubclass(comp.__class__, dbComponents.DbCustom)):
comp.setreadOnly(readOnly)
def fillDbComponents(self, container):
"""
Fill the values of the currentRegister in all dbComponents
Edgar, 05/set/2008
"""
for comp in container.children():
# if comp is a container component, than search for dbComponents "inside" of it.
if comp.children() != [] and not issubclass(comp.__class__, dbComponents.DbCustom):
self.fillDbComponents(comp)
else:
if issubclass(comp.__class__, dbComponents.DbCustom):
comp.setValue(self.CurrentRegister.__getattribute__(comp.getDataField()))
def clearDbComponents(self, container):
"""
Clean all values in dbComponents
Edgar, 01/set/2008
"""
for comp in container.children():
# if comp is a container component, than search for dbComponents "inside" of it.
if comp.children() != [] and not issubclass(comp.__class__, dbComponents.DbCustom):
self.clearDbComponents(comp)
elif (issubclass(comp.__class__, dbComponents.DbCustom)):
comp.setValue(None)
def evtPesquisar(self):
"""
Def called when the user click in btnSearch. Attention: this button will be disabled if the developer don't fill the search SQL (self.searchSQL)
Edgar, 02/set/2008
"""
params = {}
params['SearchSQL'] = self.__searchSQL
params['SearchColumnsTitle'] = self.__searchColumnsTitle
params['SearchFields'] = self.__searchFields
params['PrimaryKey'] = self.__primaryKey
params['Caption'] = "Pesquisar: %s" % self.title
pyHedConsts.frmMain.showFrame(frameCustomSearcher.FrameCustomSearcher, self, params)
# lock the close button. BACALHAU!!!
# TODO: péssima solução! O ideal aqui seria usar os QDialog... Avaliar... - Edgar, 06/out/08 PARABÉNS PARA MIM!!! :-)
self.btnClose.setEnabled(False)
def evtOnNewRecord(self):
"""
Def called when the user click in new Register button. Your can used this "event" to put your own validations, etc...
Edgar, 09/mar/2009
"""
pass
def newRecord(self):
"""
Def called when the new register button is clicked.
Edgar, 29/ago/2008
"""
# frame operation
self.__setOperation(pyHedConsts.opInsert)
self.evtOnNewRecord()
def evtBeforePost(self, register):
"""
Event that you can used to validate data, check data, etc...
- register: current Register: ORM class
Quando você precisar acessar valores antes do post (envio dos dados para o banco) ou modificar algum valor, fazer alguma validação específica,
etc... Usar este método.
- register = registro atual do SQLAlchemy. A classe com os dados recém setados.
Edgar, 15/out/2008
"""
pass
def evtAfterPost(self, register):
"""
Quando você precisar acessar valores depois do post (envio dos dados para o banco) ou modificar algum valor, fazer alguma validação específica,
etc... Usar este método.
- register = registro atual do SQLAlchemy. A classe com os dados recém setados.
Edgar, 08/dez/2008
"""
pass
def __createAudit(self, dbTable):
"""
Executa a auditoria das operações básicas do framework: Insert, Update e Delete
- data = classe que contém os dados que serão alterados no sistema
Edgar, 05/maio/2009
"""
# title locale
if pyHedConsts.config.has_key('pyhedencoding'):
title = unicode(self.title).encode(pyHedConsts.config['pyhedencoding'])
else:
title = self.title
# on local machines, the gethostbyaddr returns a error
try:
ip = socket.gethostbyaddr(socket.gethostname())[2][0]
except socket.herror, e:
ip = 'local'
return pyHedDbTables.AUDITORIA(pyHedConsts.dbInst.getEngine().execute(pyHedDbTables.AUDITORIA_SEQ),
None,
int(pyHedConsts.perfil),
pyHedConsts.idUsuarioLogado,
pyHedConsts.idUsuarioLogado,
self.Operation,
datetime.datetime.now(),
ip[0:14],# fixed for Windows 7 and your ip address on wireless
'%s %s' % (pyHedConsts.translation.getItem('audit', 'operation'), title),
'%s %s' % (pyHedConsts.translation.getItem('audit', 'operation'), title),
self.__module__.upper(),
'%s=%s' % (self.PrimaryKey, dbTable.__dict__[self.PrimaryKey]),
re.compile('''("|')''').sub('', str(dbTable.__dict__))
)
def post(self):
# TODO: percorrer TODO o self. E não somente o PnlMain e seus child's
def setValues(register, container):
# procura todos os componentes DB do container para setar os valores na classe que será postada no banco
for comp in container.children():
# se o componente não for um container, busca os dados dele. Senão, percorre os itens dentro dele
if comp.children() != [] and not issubclass(comp.__class__, dbComponents.DbCustom):
setValues(register, comp)
else:
if issubclass(comp.__class__, dbComponents.DbCustom) and not isinstance(comp, dbComponents.DbLabel) and not isinstance(comp, subFrameCustomDetail.SubFrameCustomDetail):
if comp.Required and comp.getValue() == None:
raise pyHedExceptions.UserException('%s %s' % (pyHedConsts.translation.getItem('framecustombtndb', 'required_field'), re.compile('''(&)''').sub('', unicode(comp.label.text()))))
newReg.__dict__[comp.getDataField()] = comp.getValue()
"""
Evento que é chamado quando o usuário clicar no botao salvar. Todo o código e regras de negócios para a operação
salvar deve ficar no override desta função. Você também pode usar o evtBeforePost para fazer validações (recomendado).
Edgar, 29/ago/2008
"""
# se a operacao for update, entao seta a classe de registro atual para a edicao
# TODO: refazer todo o processo de insert/update e auditoria para fazer melhor integração com o SQLAlchemy.
# Revalidar o sessionMaker do dbMain e usar o sessionMaker padrão do ORM
# http://www.sqlalchemy.org/docs/05/session.html
if self.Operation == pyHedConsts.opUpdate:
newReg = self.CurrentRegister
else:
newReg = self.dbClass()
# preenche os valores dos componentes na nova classe
setValues(newReg, self.PnlMain)
self.session.expunge_all()
self.session.begin()
try:
# evento para controle de beforePost
self.evtBeforePost(newReg)
# gera a auditoria
audit = self.__createAudit(newReg)
# nao e uma boa solucao! ESTUDAR MELHOR O SQLAlchemy
if self.Operation == pyHedConsts.opUpdate:
self.session.merge(newReg)
else:
self.session.add(newReg)
# gera a auditoria
self.session.add(audit)
# salva os dados
self.session.commit()
# seta o registro atual
self.CurrentRegister = newReg
# evento para controle de beforePost
self.evtAfterPost(newReg)
# seta a operacao
self.__setOperation(pyHedConsts.opBrowse)
except Exception, e:
self.session.rollback()
raise e
def evtOnEdit(self):
"""
Evento que é chamado quando o usuário clicar no botao editar. Na classes que derivarem desta, validações e outras coisas devem ficar aqui.
Edgar, 09/mar/2009
"""
pass
def edit(self):
"""
Método é chamado quando o usuário clicar no botao editar. Na classes que derivarem desta, a operação de edição deve
ficar aqui.
Edgar, 29/ago/2008
"""
self.__setOperation(pyHedConsts.opUpdate)
self.evtOnEdit()
def evtBeforeDelete(self, register):
"""
Quando você precisar acessar valores antes do delete (envio dos dados para o banco) ou modificar algum valor, fazer alguma validação específica,
etc... Usar este método.
- register = registro atual do SQLAlchemy.
Edgar, 15/out/2008
"""
pass
def evtAfterDelete(self, register):
"""
Quando você precisar acessar valores depois do post (envio dos dados para o banco) ou modificar algum valor, fazer alguma validação específica,
etc... Usar este método.
- register = registro excluído do SQLAlchemy.
Edgar, 08/dez/2008
"""
pass
def delete(self):
"""
Evento que é chamado quando o usuário clicar no botao excluir. Na classes que derivarem desta, a operação de exclusão deve
ficar aqui.
Edgar, 29/ago/2008
"""
if self.CurrentRegister is None:
raise pyHedExceptions.UserException(pyHedConsts.translation.getItem('framecustombtndb', 'no_record_selected'))
if components.messageDlg(pyHedConsts.translation.getItem('framecustombtndb', 'delete_ask_msg')) == PyQt4.QtGui.QMessageBox.Yes:
# seta a operacao
self.__setOperation(pyHedConsts.opDelete)
# evento de before delete deve ser chamado
self.evtBeforeDelete(self.CurrentRegister)
# apaga o registro do banco
self.session.begin()
try:
# gera a auditoria
audit = self.__createAudit(self.CurrentRegister)
# exclui o registro
self.session.delete(self.CurrentRegister)
# gera a auditoria
self.session.add(audit)
self.session.commit()
# evento de before delete deve ser chamado
self.evtAfterDelete(self.CurrentRegister)
except Exception, e:
self.__setOperation(pyHedConsts.opBrowse)
self.session.rollback()
raise e
# seta a operacao
self.__setOperation(pyHedConsts.opNone)
# aviso de operação finalizada
components.messageBox(pyHedConsts.translation.getItem('framecustombtndb', 'record_deleted_success'))
def cancelEdit(self):
"""
Evento que é chamado quando o usuário clicar no botão de cancelamento de edição. Na classes que derivarem desta, a operação de ficar aqui.
Edgar, 29/ago/2008
"""
# seta a operacao
if self.CurrentRegister is not None:
self.__setOperation(pyHedConsts.opBrowse)
else:
self.__setOperation(pyHedConsts.opNone)
def filterRegister(self, condicao):
"""
Procedure responsável por, a partir de uma condicao WHERE (sql) montar o dbClass e setar o registro corrente
Edgar, 08/set/2008
"""
if condicao is None:
raise Exception(pyHedConsts.translation.getItem('framecustombtndb', 'empty_where_clause'))
self.__setCurrentRegister(self.session.query(self.dbClass).filter(condicao).one())
def __getCurrentRegister(self):
return self.__currentRegister
def __setCurrentRegister(self, value):
self.__currentRegister = value
# se não é vazio, seta os valores
if value is not None:
self.setFrameOperation(pyHedConsts.opBrowse)
def __getOperation(self):
return self.__operation
def __setOperation(self, value):
self.__operation = value
self.setFrameOperation(value)
def __getSearchSQL(self):
return self.__searchSQL
def __setSearchSQL(self, value):
self.btnSearch.setVisible(value is not None)
self.__searchSQL = value
def __getSearchFields(self):
return self.__searchFields
def __getSearchColumnsTitle(self):
return self.__searchColumnsTitle
def __setSearchColumnsTitle(self, value):
self.__searchColumnsTitle = value
def __getPrimaryKey(self):
return self.__primaryKey
def __setPrimaryKey(self, value):
self.__primaryKey = value
def __getAllowedOperations(self):
return self.__allowedOperations
# colunas que devem aparecer no grid de pesquisa
SearchColumnsTitle = property(__getSearchColumnsTitle, __setSearchColumnsTitle)
# SQL de pesquisa
SearchSQL = property(__getSearchSQL, __setSearchSQL)
# primary key da Tela
PrimaryKey = property(__getPrimaryKey, __setPrimaryKey)
# Campos para pesquisa
SearchFields = property (__getSearchFields)
# Retorna a operação que o usuário está executando: Insert, Update, Delete
Operation = property(__getOperation, __setOperation)
# Retorna a classe do SQLAlchemy que representa o registro atual que está sendo exibido na tela
CurrentRegister = property(__getCurrentRegister, __setCurrentRegister)
# Operaçoes permitidas para o frame
AllowedOperations = property(__getAllowedOperations)