En este capítulo del tutorial de Python 3 más PyQt5 crearemos una sencilla aplicación que nos permita enviar correos con servidores que dispongan del protocolo SMTP (protocolo para transferencia simple de correo) tales como Hotmail, Gmail, Yahoo, ...
El programa permitirá al usuario configurar sus preferencias tales como el servidor STMP, puerto, credenciales, uso opcional del protocolo starttls, adjuntar multiples archivos (QFileDialog), agregar múltiples destinatarios, etc.
El programa mostrará en una etiqueta QLabel el proceso de envío del correo así como cualquier excepción que pudiera ocurrir.
import sys, smtplib, email
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
from PyQt5.QtWidgets import QApplication, QDialog, QGridLayout, QMessageBox, QPushButton, QLabel, QLineEdit, QFileDialog, QTextEdit, QCheckBox
class FileDialog(QFileDialog):
def __init__(self):
QFileDialog.__init__(self)
self.setFileMode(QFileDialog.ExistingFiles) #Selección múltiple
self.files = [] #Lista que guardará las rutas de los archivos
self.filesSelected.connect(self.getFiles) #Activar la señal al seleccionar archivos
def getFiles(self):
self.files = self.selectedFiles() #Agregar las rutas de archivos a la lista
class Dialogo(QDialog):
def __init__(self):
QDialog.__init__(self)
self.setWindowTitle("Enviar email SMTP")
self.resize(450, 600)
#Crear objetos en el cuadro de diálogo
self.layout = QGridLayout()
self.setLayout(self.layout)
self.label_host = QLabel("Host SMTP:")
self.txt_host = QLineEdit()
self.chk_tls = QCheckBox("STARTTLS")
self.chk_tls.setChecked(True)
self.label_port = QLabel("Puerto:")
self.txt_port = QLineEdit()
self.label_user = QLabel("Usuario:")
self.txt_user = QLineEdit()
self.label_pass = QLabel("Password:")
self.txt_pass = QLineEdit()
self.txt_pass.setEchoMode(QLineEdit.Password)
self.label_from = QLabel("Remitente:")
self.txt_from = QLineEdit()
self.label_to = QLabel("Destinatario/s:")
self.txt_to = QLineEdit()
self.label_subject = QLabel("Asunto:")
self.txt_subject = QLineEdit()
self.label_dialog = QLabel("Adjunto:")
self.btn_dialog = QPushButton("Seleccionar ...")
self.label_body = QLabel("Mensaje: ")
self.txt_body = QTextEdit()
self.label_state = QLabel("")
self.btn_send = QPushButton("Enviar")
#Crear objeto de la clase FileDialog()
self.fileDialog = FileDialog()
#Agregar objetos al layout grid
self.layout.addWidget(self.label_host)
self.layout.addWidget(self.txt_host)
self.layout.addWidget(self.chk_tls)
self.layout.addWidget(self.label_port)
self.layout.addWidget(self.txt_port)
self.layout.addWidget(self.label_user)
self.layout.addWidget(self.txt_user)
self.layout.addWidget(self.label_pass)
self.layout.addWidget(self.txt_pass)
self.layout.addWidget(self.label_from)
self.layout.addWidget(self.txt_from)
self.layout.addWidget(self.label_to)
self.layout.addWidget(self.txt_to)
self.layout.addWidget(self.label_subject)
self.layout.addWidget(self.txt_subject)
self.layout.addWidget(self.label_dialog)
self.layout.addWidget(self.btn_dialog)
self.layout.addWidget(self.label_body)
self.layout.addWidget(self.txt_body)
self.layout.addWidget(self.label_state)
self.layout.addWidget(self.btn_send)
#Señal para abrir el cuadro de diálogo de selección de archivos
self.btn_dialog.clicked.connect(self.openDialog)
#Señal para proceder a enviar los correos
self.btn_send.clicked.connect(self.sendMail)
#Método encargado de abrir el cuadro de diálogo de selección de archivos
def openDialog(self):
self.fileDialog.open()
#Método encargado de procesar el envío de correos
def sendMail(self):
To = self.txt_to.text().split(", ") #Lista con los destinatarios
#Capturar cualquier posible excepción y mostrarlo en el label label_state
try:
#Recorrer los destinatarios
for destinatario in To:
#Guardar los valores del formulario
Host = self.txt_host.text()
Port = self.txt_port.text()
User = self.txt_user.text()
Pass = self.txt_pass.text()
From = self.txt_from.text()
Subject = self.txt_subject.text()
Body = self.txt_body.toPlainText()
#Objeto para procesar el envío de correo
smtp = smtplib.SMTP(Host, Port)
#Si el servidor utilizar el protocolo starttls activarlo
if self.chk_tls.isChecked(): smtp.starttls()
#Credenciales
smtp.login(User, Pass)
#Iniciar la cabecera
header = MIMEMultipart()
header['From'] = From
header['To'] = destinatario
header["Subject"] = Subject
msg = MIMEText(Body, 'html') #Content-type:text/html
header.attach(msg)
#Lista para agregrar en la cabecera los archivos a enviar
parts = []
for file in self.fileDialog.files:
parts.append(MIMEBase('application', "octet-stream"))
#Nos permite indexar la ruta de los archivos que se encuentran
#en el atributo files del objeto fileDialog
x = 0
#Recorrer archivo a archivo
for part in parts:
#Lectura del archivo
part.set_payload(open(self.fileDialog.files[x], "rb").read())
#Codificar el archivo en base64
encoders.encode_base64(part)
#Extracción del nombre del archivo
explode = self.fileDialog.files[x].split("/")
filename = explode[len(explode)-1]
#Añadir archivo a la cabecera
part.add_header('Content-Disposition', 'attachment; filename="'+filename+'"')
header.attach(part)
x = x + 1
#Enviar el correo al destinatario
smtp.sendmail(From, destinatario, header.as_string())
#Terminar la conexión con el servidor
smtp.quit()
#Mostrar el proceso
self.label_state.setText("Enviando a " + destinatario + " ... ")
except Exception as e:
#Capturar cualquier posible excepción y mostrarla
self.label_state.setText(str(e))
parts[:] = [] #Limpiar la lista de archivos
self.fileDialog.files[:] = [] #Limpiar la lista de archivos
self.label_state.setText("Envío finalizado")
app = QApplication(sys.argv)
dialogo = Dialogo()
dialogo.show()
app.exec_()
En este capítulo del tutorial de Python 3 más PyQt5 seguimos con la continuación de la aplicación que estamos desarrollando desde los dos capítulos anteriores:
En esta ocasión veremos como eliminar fila/s, para esta tarea incluiremos un botón al cuadro de diálogo que al hacer click sobre él, entrará en acción un método llamado Eliminar el cual contendrá el proceso de búsqueda del index de la/s fila/s seleccionada/s por el usuario y su posterior eliminación tanto del objeto table como de la tabla usuarios.
administrar.pyw
import sys
from PyQt5.QtWidgets import QApplication, QDialog, QGridLayout, QMessageBox, QTableWidget, QTableWidgetItem, QPushButton
from PyQt5 import uic
from PyQt5.QtSql import QSqlDatabase, QSqlQuery
class Dialogo(QDialog):
def __init__(self):
QDialog.__init__(self)
self.setWindowTitle("Administrar usuarios") #Título
self.resize(800, 600) #Tamaño inicial
self.layout = QGridLayout() #Crear un layout grid
self.setLayout(self.layout) #Agregar el layout al cuadro de diálogo
self.table = QTableWidget() #Crear la tabla
self.btn_eliminar = QPushButton("Eliminar fila/s")
self.layout.addWidget(self.btn_eliminar)
self.layout.addWidget(self.table) #Agregar la tabla al layout
#Establecer conexión a la base de datos MySql
self.db = QSqlDatabase.addDatabase('QMYSQL')
self.db.setHostName("localhost")
self.db.setDatabaseName("usuarios")
self.db.setUserName("root")
self.db.setPassword("password")
self.Seleccionar()
self.table.itemChanged.connect(self.Actualizar)
self.btn_eliminar.clicked.connect(self.Eliminar)
def Seleccionar(self):
estado = self.db.open()
if estado == False:
QMessageBox.warning(self, "Error", self.db.lastError().text(), QMessageBox.Discard)
else:
self.table.setColumnCount(3)
self.table.setHorizontalHeaderLabels(['id', 'nombre', 'edad'])
row = 0
sql = "SELECT * FROM usuarios"
query = QSqlQuery(sql)
while query.next():
self.table.insertRow(row)
id = QTableWidgetItem(str(query.value(0)))
nombre = QTableWidgetItem(str(query.value(1)))
edad = QTableWidgetItem(str(query.value(2)))
self.table.setItem(row, 0, id)
self.table.setItem(row, 1, nombre)
self.table.setItem(row, 2, edad)
row = row + 1
self.db.close()
def Actualizar(self):
estado = self.db.open()
if estado == False:
QMessageBox.warning(self, "Error", self.db.lastError().text(), QMessageBox.Discard)
else:
column = self.table.currentColumn()
row = self.table.currentRow()
id = self.table.item(row, 0).text()
value = self.table.currentItem().text()
columns = ['id', 'nombre', 'edad']
query = QSqlQuery()
sql = "UPDATE usuarios SET " + columns[column] + "=" + ":value WHERE id=:id"
query.prepare(sql)
query.bindValue(":id", id)
query.bindValue(":value", value)
estado = query.exec_()
if estado == False:
QMessageBox.warning(self, "Error", self.db.lastError().text(), QMessageBox.Discard)
self.db.close()
def Eliminar(self):
estado = self.db.open()
if estado == False:
QMessageBox.warning(self, "Error", self.db.lastError().text(), QMessageBox.Discard)
else:
rows = self.table.selectionModel().selectedRows()
index = []
for i in rows:
index.append(i.row())
index.sort(reverse=True)
for i in index:
id = self.table.item(i, 0).text()
self.table.removeRow(i)
sql = "DELETE FROM usuarios WHERE id=:id"
query = QSqlQuery()
query.prepare(sql)
query.bindValue(":id", id)
estado = query.exec_()
if estado == False:
QMessageBox.warning(self, "Error", self.db.lastError().text(), QMessageBox.Discard)
self.db.close()
app = QApplication(sys.argv)
dialogo = Dialogo()
dialogo.show()
app.exec_()
En este capítulo del tutorial de Python 3 más PyQt5 continuamos realizando consultas a bases de datos MySQL, basándonos en la aplicación del capítulo anterior: http://jquery-manual.blogspot.com.es/2015/07/21-python-pyqt-interfaz-grafica-qtsql.html donde pudimos ver como seleccionar los registros de la tabla usuarios para incluirlos en un objeto de la clase QTableWidget, este objeto es de gran utilidad para mantener un enlace directo con la base de datos, ya que permite la edición de campos (items). El objetivo es actualizar un determinado campo cada vez que el usuario ha realizado una modificación en el mismo, y para ello haremos uso del evento itemChanged de la clase QTableWidget.
administrar.pyw
import sys
from PyQt5.QtWidgets import QApplication, QDialog, QGridLayout, QMessageBox, QTableWidget, QTableWidgetItem
from PyQt5 import uic
from PyQt5.QtSql import QSqlDatabase, QSqlQuery
class Dialogo(QDialog):
def __init__(self):
QDialog.__init__(self)
self.setWindowTitle("Administrar usuarios") #Título
self.resize(800, 600) #Tamaño inicial
self.layout = QGridLayout() #Crear un layout grid
self.setLayout(self.layout) #Agregar el layout al cuadro de diálogo
self.table = QTableWidget() #Crear la tabla
self.layout.addWidget(self.table) #Agregar la tabla al layout
#Establecer conexión a la base de datos MySql
self.db = QSqlDatabase.addDatabase('QMYSQL')
self.db.setHostName("localhost")
self.db.setDatabaseName("usuarios")
self.db.setUserName("root")
self.db.setPassword("password")
self.Seleccionar()
self.table.itemChanged.connect(self.Actualizar)
def Seleccionar(self):
estado = self.db.open()
if estado == False:
QMessageBox.warning(self, "Error", self.db.lastError().text(), QMessageBox.Discard)
else:
self.table.setColumnCount(3)
self.table.setHorizontalHeaderLabels(['id', 'nombre', 'edad'])
row = 0
sql = "SELECT * FROM usuarios"
query = QSqlQuery(sql)
while query.next():
self.table.insertRow(row)
id = QTableWidgetItem(str(query.value(0)))
nombre = QTableWidgetItem(str(query.value(1)))
edad = QTableWidgetItem(str(query.value(2)))
self.table.setItem(row, 0, id)
self.table.setItem(row, 1, nombre)
self.table.setItem(row, 2, edad)
row = row + 1
self.db.close()
def Actualizar(self):
estado = self.db.open()
if estado == False:
QMessageBox.warning(self, "Error", self.db.lastError().text(), QMessageBox.Discard)
else:
column = self.table.currentColumn()
row = self.table.currentRow()
id = self.table.item(row, 0).text()
value = self.table.currentItem().text()
columns = ['id', 'nombre', 'edad']
query = QSqlQuery()
sql = "UPDATE usuarios SET " + columns[column] + "=" + ":value WHERE id=:id"
query.prepare(sql)
query.bindValue(":id", id)
query.bindValue(":value", value)
estado = query.exec_()
if estado == False:
QMessageBox.warning(self, "Error", self.db.lastError().text(), QMessageBox.Discard)
self.db.close()
app = QApplication(sys.argv)
dialogo = Dialogo()
dialogo.show()
app.exec_()
En este capítulo del tutorial de Python 3 más PyQt5 veremos como conectar a bases de datos MySQL y generaremos una consulta para insertar registros en una determinada tabla, para ello utilizaremos las clases que proporciona el módulo QtSql.
Para el ejemplo crearemos una base de datos Mysql llamada usuarios, a la cuál agregaremos una tabla también llamada usuarios con las siguientes columnas:
id : del tipo INT, Autoincrementable y clave primaria
nombre : del tipo VARCHAR y con una longitud de 50
edad : del tipo INT
Del módulo QtSql utilizaremos las dos siguiente clases:
QSqlDatabase: para establecer la conexión a la base de datos QSqlQuery: para generar la consulta
insertar.pyw
import sys
from PyQt5.QtWidgets import QApplication, QDialog, QGridLayout, QMessageBox, QLabel, QPushButton, QLineEdit, QSpinBox
from PyQt5 import uic
from PyQt5.QtSql import QSqlDatabase, QSqlQuery
class Dialogo(QDialog):
def __init__(self):
QDialog.__init__(self)
self.setWindowTitle("Insertar datos") #Título
self.resize(300, 300) #Tamaño inicial
self.setMinimumSize(300, 300) #Tamaño mínimo
self.setMaximumSize(300, 300) #Tamaño máximo
self.layout = QGridLayout() #Crear un layout grid
self.setLayout(self.layout) #Agregar el layout al cuadro de diálogo
self.label_nombre = QLabel("Nombre:") #Etiqueta nombre
self.txt_nombre = QLineEdit() #Campo para ingresar el nombre
self.label_edad = QLabel("Edad:") #Etiqueta edad
self.txt_edad = QSpinBox() #Campo para ingresar la edad
#Botones
self.btn_insertar = QPushButton("Insertar")
self.btn_cancelar = QPushButton("Cancelar")
#Agregar elementos al layout divido en dos columnas
self.layout.addWidget(self.label_nombre, 1, 1)
self.layout.addWidget(self.txt_nombre, 1, 2)
self.layout.addWidget(self.label_edad, 2, 1)
self.layout.addWidget(self.txt_edad, 2, 2)
self.layoutButton = QGridLayout() #Layout para agrupar los botones
#Agregar los botones al layoutButton
self.layoutButton.addWidget(self.btn_insertar, 1, 1)
self.layoutButton.addWidget(self.btn_cancelar, 1, 2)
#Agregar el layoutButton en la fila 3 columna 2
self.layout.addLayout(self.layoutButton, 3, 2)
#Establecer conexión a la base de datos MySql
self.db = QSqlDatabase.addDatabase('QMYSQL')
self.db.setHostName("localhost")
self.db.setDatabaseName("usuarios")
self.db.setUserName("root")
self.db.setPassword("password")
self.btn_insertar.clicked.connect(self.Insertar)
self.btn_cancelar.clicked.connect(self.Cancelar)
def Insertar(self):
estado = self.db.open()
if estado == False:
QMessageBox.warning(self, "Error", self.db.lastError().text(), QMessageBox.Discard)
else:
nombre = self.txt_nombre.text()
edad = self.txt_edad.text()
sql = "INSERT INTO usuarios(nombre, edad) VALUES (:nombre, :edad)"
consulta = QSqlQuery()
consulta.prepare(sql)
consulta.bindValue(":nombre", nombre)
consulta.bindValue(":edad", edad)
estado = consulta.exec_()
if estado == True:
QMessageBox.information(self, "Correcto", "Datos guardados", QMessageBox.Discard)
else:
QMessageBox.warning(self, "Error", self.db.lastError().text(), QMessageBox.Discard)
self.db.close()
def Cancelar(self):
self.close()
app = QApplication(sys.argv)
dialogo = Dialogo()
dialogo.show()
app.exec_()
En este capítulo del tutorial de Python 3 más PyQt5 nos basaremos en el sencillo programa de los dos capítulos anteriores el cual nos permite crear gráficos con diferentes herramientas. Concretamente veremos como dibujar polígonos o también conocidos como polilíneas.
El procedimiento será el siguiente: al hacer click y soltar el mouse en un lugar del plano de dibujo almacenaremos ese punto QPointF(x, y) en una lista, el usuario podrá agregar tantos puntos como necesite, cada punto señalado en el plano marcará el punto de unión de cada línea del polígono.
Para crear el polígono utilizaremos el siguiente método: addPolygon(QPolygonF(), QPen(), QBrush())
En este capítulo del tutorial Python 3 más PyQt5 veremos como agregar objetos como pueden ser líneas, objetos rectángulares y elipses al plano de dibujo (QGraphicsView), este capítulo básicamente es la continuación del capítulo anterior: http://jquery-manual.blogspot.com/2015/07/16-python-pyqt-interfaz-grafica.html.
Para poder agregar dichos objetos a la escena utilizaremos los siguientes métodos de la clase QGraphicsScene:
Agrega una línea: addLine(x1, y1, x2, y2, QPen)
Agrega un objeto rectángular: addRect(x, y, width, height, QPen, QBrush)
Agrega un objeto elíptico: addEllipse(x, y, width, height, QPen, QBrush)
La posición inicial x e y será obtenida por el evento MousePressEvent, es decir, cuando el usuario haga click en el plano de dibujo.
La posición final x e y será obtenida por el evento MouseReleaseEvent, es decir, cuando el usuario suelte el mouse en el plano de dibujo.
En este capítulo del tutorial de Python 3 más PyQt5 veremos lo útil que es trabajar con objetos de la clase QTabWidget, para ello nos basaremos en crear un navegador con las herramientas de WebKit y apoyándonos en un Tab Widget para ir agregando o eliminando páginas de navegación mediante los conocidos tabs o pestañas.
Crearemos un archivo ui con Designer en una ventana principal (Main Window) al cual agregaremos:
Un objeto Tab Widget con nombre tabWidget que es el que viene por defecto.
Un objeto Push Button con nombre btn_mas y como texto el signo "+" para agregar páginas.
Un objeto Push Button con nombre btn_menos y como texto el signo "-" para eliminar páginas.
Un objeto Line Edit con nombre url para la navegación.
Agrupar los elementos en un Lay Out in a Grid.
Guardar el archivo como webkit-tab.ui
webkit-tab.pyw
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtCore import QUrl
from PyQt5.QtWebKitWidgets import QWebView
from PyQt5 import uic
class Navegador(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
uic.loadUi("webkit-tab.ui", self)
self.pages = [] #array donde se guardará cada página objeto
self.index = 0 #Al crear o eliminar tabs incrementar o disminuir el index
self.add_tab() #Crear un tab por defecto
#Agregar un nuevo tab con página
self.btn_mas.clicked.connect(self.add_tab)
#Eliminar un tab
self.btn_menos.clicked.connect(self.remove_tab)
#Cargar información de la página activa al terminar de cargarla
self.pages[self.tabWidget.currentIndex()].loadFinished.connect(self.getInfo)
#Actualizar información de la página del tab activo
self.tabWidget.currentChanged.connect(self.current_changed)
#Buscar la url indicada por el usuario
self.url.returnPressed.connect(self.search)
#Agregar un nuevo tab con página
def add_tab(self):
#Comprobar el número de páginas/tabs e incrementar en 1 para el index del nuevo elemento
if len(self.pages) > 0:
for i in range(len(self.pages)):
self.index = i + 1
#Crear objeto página
pagina = QWebView()
#Agregar objeto página al array pages
self.pages.append(pagina)
#Cargar la url por defecto
self.pages[self.index].load(QUrl("http://www.google.com"))
#Agregar el objeto página a un nuevo tab
self.tabWidget.addTab(self.pages[self.index], "")
#Poner el foco en el nuevo tab
self.tabWidget.setCurrentIndex(self.index)
def getInfo(self):
#Index del tab que tiene el foco
index = self.tabWidget.currentIndex()
#Título de la página web
title = self.pages[index].title()
#Agregar título al tab
self.tabWidget.setTabText(index, title)
#Agregar título a la ventana
self.setWindowTitle(title)
#Actualizar la url del buscador
url = self.pages[index].url()
self.url.setText(url.toString())
def current_changed(self):
#Index del tab que tiene el foco
index = self.tabWidget.currentIndex()
#Actualizar la información de la página si un tab obtiene el foco
self.pages[index].loadFinished.connect(self.getInfo)
#Título de la página web
title = self.pages[index].title()
#Agregar título al tab
self.tabWidget.setTabText(index, title)
#Agregar título a la ventana
self.setWindowTitle(title)
#Actualizar la url del buscador
url = self.pages[index].url()
self.url.setText(url.toString())
def search(self):
#Url del campo de búsqueda
url = QUrl(self.url.text())
#Obtener el index del tab que tiene el foco
index = self.tabWidget.currentIndex()
#Navegar a la url
self.pages[index].setUrl(url)
def remove_tab(self):
#Obtener el index del tab que tiene el foco
index = self.tabWidget.currentIndex()
#Eliminar el tab
self.tabWidget.removeTab(index)
#Eliminar la página objeto del array pages
self.pages.pop(index)
#Actualizar el valor del atributo index
if self.index > 0: self.index = self.index - 1
#Actualizar la información del tab que obtendrá el foco tras la eliminación del seleccionado
self.current_changed()
app = QApplication(sys.argv)
navegador = Navegador()
navegador.show()
app.exec_()