• Facebook
  • Twitter
  • Reddit
  • StumbleUpon
  • Digg
  • email

#-*- 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)