3ENGINE

Programación y otros cachivaches

Etiqueta: Python

Página 2/6

Tecnologia

Método rápido para servir archivos a través de HTTP (Servidor HTTP) con SimpleHTTPServer


Python viene con un módulo llamado SimpleHTTPServer que permite montar un sencillo Servidor HTTP al que acceder través de localhost. Esto es útil a la hora de desarrollar porque hay cosas que no funcionan con ficheros. Por ejemplo las URLs en los navegadores web.

En la documentación de Python explica que también es posible «lanzar» directamente el servidor HTTP utilizando el modificador -m del intérprete y como argumento opcional el número de puerto.

Ejemplo:

python -m SimpleHTTPServer 8000

La clase encarga de hacer todo esto es SimpleHTTPRequestHandler que implementa la interface BaseHTTPRequestHandler. Esta clase es capaz de servir archivos del directorio actual y de cualquier archivo que esté por debajo, asi como la cartografía de la estructura de directorios en una petición HTTP:

Servidor HTTP

Un problemilla: SimpleHTTPRequestHandler no admite HTTP POST

Tengo form.html que contiene un formulario con un submit action de tipo POST:

<!DOCTYPE html>
<html>
<head>
</head>
<body>
  <img src="python.png"/>
  <form action="action_page.php" method="post">
    First name:<br>
    <input type="text" name="firstname" value="Mickey"><br>
    Last name:<br>
    <input type="text" name="lastname" value="Mouse"><br><br>
    <input type="submit" value="Submit">
  </form>
</body>
</html>

Con el siguiente resultado:

form

Ahora bien si hago submit me encuentro con una desagradable sorpresa:

error

El problema es que SimpleHTTPRequestHandler implementa las funciones do_GET() y do_HEAD(), pero NO implementa do_POST() aunque si lo pensamos tiene cierta lógica.

Implementación de do_POST() con el objetivo de testear una aplicación

Si el objetivo es comprobar que información POST esta llegando al servidor esta implementación puede servirnos:

from SimpleHTTPServer import SimpleHTTPRequestHandler
import SocketServer
import time
import urlparse
from cgi import parse_header, parse_multipart

class MyRequestHandler(SimpleHTTPRequestHandler):

    def __init__(self, *args):
        SimpleHTTPRequestHandler.__init__(self, *args)

    def parse_POST(self):
        ctype, pdict = parse_header(self.headers['content-type'])
        if ctype == 'multipart/form-data':
            postvars = parse_multipart(self.rfile, pdict)
        elif ctype == 'application/x-www-form-urlencoded':
            length = int(self.headers['content-length'])
            postvars = urlparse.parse_qs(
                    self.rfile.read(length), 
                    keep_blank_values=1)
        else:
            postvars = {}
        return postvars
    
    def do_GET(self):
        print self.command
        print self.path
        return SimpleHTTPRequestHandler.do_GET(self)

    def do_POST(self):
        print self.command
        print self.path
        fields = self.parse_POST()
        self.send_response(200) 
        self.send_header('Content-type', 'text/html')
        self.end_headers()
        self.wfile.write('
    ') for key, value in fields.iteritems(): self.wfile.write('
  • %s: %s
  • ' % (key, value)) self.wfile.write('
') PORT = 8000 Handler = MyRequestHandler httpd = SocketServer.TCPServer(("", PORT), Handler) print "serving at port", PORT httpd.serve_forever()

Si lanzo el script, ahora al hacer el submit de form.htm este es el resultado:

resultpost

Espero que os pueda servir.
demo: server.zip




Tecnologia

Python y PyQt: cómo agrupar una lista de radio buttons (QRadioButtonGroupBox)


QT, al contrario de otras librerias centradas en el desarrollo de interfaces gráficas de usuario, no implementa un widget que permita agrupar una lista de radio buttons. Crear un nuevo Widget que permita esto, no es muy complicado. A continuación el código:

Agrupar una lista de radio buttons


from PyQt4.QtGui import *
from PyQt4.QtCore import *


class QRadioButtonGroupBox(QGroupBox):

    selectItemChanged = pyqtSignal(int)

    def __buttonGroupClickedSlot(self, index):
        self.selectItemChanged.emit(index)

    def __init__(self, *args):
        QGroupBox.__init__(self, *args)
        self.box = QFormLayout(parent=self)
        self.buttonGroup = QButtonGroup()
        QObject.connect(self.buttonGroup, SIGNAL("buttonClicked(int)"), 
                        self.__buttonGroupClickedSlot)

    def selectedItem(self):
        return self.buttonGroup.checkedId()

    def setChecked_(self, index, isChecked=True):
        self.buttonGroup.button(index).setChecked(isChecked)

    def addItem(self, text, isChecked=False):
        radioButton = QRadioButton(text)
        index = len(self.buttonGroup.buttons())
        self.buttonGroup.addButton(radioButton, index)
        self.buttonGroup.button(index).setChecked(isChecked)
        self.box.addRow(radioButton)
        return index

    def addItems(self, *items):
        for item in items:
            self.addItem(item)

    def removeItems(self):
        for button in self.buttonGroup.buttons():
            self.buttonGroup.removeButton(button)
            self.box.removeWidget(button)

selectedItem retorna el número de radiobutton seleccionada, setChecked_ permite seleccionar uno de los radiobuttons de la lista, addItem y addItems sirve para añadir radiobuttons y removeItems para eliminar la lista completa de radiobutton. También incluye el signal selectItemChanged que se dispara cuando el usuario selecciona un radiosbutton de la lista.

A continuación una demo:

def selectedItemChangedSlot(index):
    print 'ha seleccionado:', index

app = QApplication(sys.argv)
main = QRadioButtonGroupBox('Mi color favorito')
main.selectItemChanged.connect(selectedItemChangedSlot)

main.addItem('1')
main.addItem('2')
main.addItem('3')
main.removeItems()

main.addItem('Rojo')
main.addItem('Azul', isChecked=True)
main.addItem('Amarillo')
main.addItems('Marron', 'Naranja', 'Verde')

main.show()
sys.exit(app.exec_())

Resultado:

agrupar una lista de radio buttons

Punto Extra

A veces puede ser interesante tener la posibilidad de acompañar al radio button de un widget extra. El siguiente código permite esto:

from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys


class QRadioButtonGroupBox(QGroupBox):

    selectItemChanged = pyqtSignal(int, QWidget)

    def __setEnabledWidget(self, index):
        widget = None
        for button in self.buttonGroup.buttons():
            if button.buddie:
                if index == self.buttonGroup.id(button):
                    button.buddie.setEnabled(True)
                    widget = button.buddie
                else:
                    button.buddie.setEnabled(False)
        return widget

    def __buttonGroupClickedSlot(self, index):
        widget = self.__setEnabledWidget(index)
        self.selectItemChanged.emit(index, widget)

    def __init__(self, *args):
        QGroupBox.__init__(self, *args)
        self.box = QFormLayout(parent=self)
        self.buttonGroup = QButtonGroup()
        QObject.connect(self.buttonGroup, SIGNAL("buttonClicked(int)"),
                        self.__buttonGroupClickedSlot)

    def selectedItem(self):
        return self.buttonGroup.checkedId()

    def buddie(self, index):
        button = self.buttonGroup.button(index)
        return button.buddie

    def setChecked_(self, index):
        self.__setEnabledWidget(index)
        self.buttonGroup.button(index).setChecked(True)

    def addItem(self, text, widget=None, isChecked=False):
        radioButton = QRadioButton(text)
        index = len(self.buttonGroup.buttons())
        self.buttonGroup.addButton(radioButton, index)
        self.buttonGroup.button(index).setChecked(isChecked)
        radioButton.buddie = widget
        if widget:
            self.box.addRow(radioButton, widget)
            widget.setEnabled(isChecked)
        else:
            self.box.addRow(radioButton)
        return index

    def addItems(self, *items):
        for item in items:
            self.addItem(item)

    def removeItems(self):
        for button in self.buttonGroup.buttons():
            self.buttonGroup.removeButton(button)
            if button.buddie:
                self.box.removeWidget(button.buddie)
                button.buddie = None
            self.box.removeWidget(button)

Una demo:

def selectedItemChangedSlot(index, widget):
    print 'ha seleccionado:', index
    print 'widget asociado:', widget

app = QApplication(sys.argv)
main = QRadioButtonGroupBox('Mi color favorito')
main.selectItemChanged.connect(selectedItemChangedSlot)
main.addItem('Rojo')
main.addItem('Azul', isChecked=True)
main.addItem('Amarillo')
main.addItems('Marron', 'Naranja', 'Verde')
colores = QComboBox()
colores.addItems(['violeta', 'fucsia', 'lima'])
main.addItem('otro color', widget=colores)
main.addItem('mas colores:', QLineEdit())
main.show()
sys.exit(app.exec_())

Resultado:

agrupar una lista de radio buttons




Tecnologia

Cómo incrustar imágenes en una aplicación Qt en Python


En este post voy a explicar mediante una pequeña demo cómo incrustar imágenes dentro de una aplicación. El principal objetivo de esto es no tener que distribuir las imágenes junto con la aplicación. Pero antes, un poco de teoria:

El Sistema de Recursos de Qt

Se trata de un sistema independiente de la plataforma que permite almacenar archivos binarios en el ejecutable de la aplicación. Esto es útil si la aplicación necesita de un cierto conjunto de archivos (iconos, archivos de traducción, etc.) y no queremos correr el riesgo de perder los archivos.

Recursos Archivos Colección (.qrc)

Los recursos asociados con la aplicación se especifican en un archivo .qrc. Es un formato de archivo basado en XML que enumera los archivos y opcionalmente un nombre de recurso que la aplicación debe utilizar para acceder al recurso. Los archivos de recursos que figuran en el archivo .qrc son archivos que forman parte del árbol de código fuente de la aplicación. Las rutas especificadas son relativas al directorio que contiene el archivo .qrc.

Utilidad pyrcc4

pyrcc4 es equivalente a la utilidad rcc de Qt y se utiliza exactamente de la misma manera. Lee el archivo .qrc, y los archivos de recursos, y genera un módulo de Python que sólo necesita ser importado por la aplicación para que los recursos estén disponibles como si se tratara de los archivos originales.

Para mas información leer The Qt Resource System

Ejemplo

Paso 1: generar el fichero .qrc




    img/icon_ok.png
    img/icon_cancel.png


    img/image_qt.png


Por defecto, los recursos son accesibles en la aplicación bajo el mismo nombre de archivo. Opcionalmente el atributo alias permite cambiar el nombre. Al especificar un prefijo de ruta mediante la etiqueta qresource y el atributo prefix se han agrupado las imágenes en dos grupos. En este caso, las imagenes son accesibles con: :icons/ok.png , :icons/cancel.png y :img/image_qt.png respectivamente.

Paso 2: generar el módulo Python con los recursos

pyrcc4 -o images_rc.py images.qrc

Le indicamos a la herramienta que genere a partir de images.qrc un módulo Python images_rc.py con los recursos.

Paso 3: código fuente

# -*- coding: utf-8 -*-
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import images_rc
import sys


class MainWindow(QMainWindow):

    def accept(self):
        QMessageBox.information(self, 'Thanks!', 'Thanks for testing the demo', QMessageBox.AcceptRole)
        self.close()
        
    def __init__(self, *args):
        QMainWindow.__init__(self, *args)
       
        # añadir imagen 
        title = QLabel() 
        pixmap = QPixmap(':images/title.png')
        title.setPixmap(pixmap)

        # añadir texto              
        info = QLabel('visit my blog at:3engine.net') 

        # crear botones 
        acceptButton = QPushButton('Accept', self)
        acceptButton.setIcon(QIcon(':icons/ok.png'))

        cancelButton = QPushButton('Cancel', self)
        cancelButton.setIcon(QIcon(':icons/cancel.png'))

        # añadir botones al diálogo
        buttonBox = QDialogButtonBox()
        buttonBox.addButton(acceptButton, QDialogButtonBox.AcceptRole)
        buttonBox.addButton(cancelButton, QDialogButtonBox.RejectRole)
        buttonBox.accepted.connect(lambda: self.accept())
        buttonBox.rejected.connect(lambda: self.close())

        # crear layout principal           
        vbox = QVBoxLayout()
        vbox.addWidget(title)
        vbox.addSpacing(10)
        vbox.addWidget(info)
        vbox.addSpacing(10)
        vbox.addStretch()
        vbox.addWidget(buttonBox)

        centralWidget = QWidget()
        centralWidget.setLayout(vbox)
        self.setCentralWidget(centralWidget)
        self.resize(100, 100)
        self.setWindowTitle('demo')       


if __name__ == "__main__":
    app = QApplication(sys.argv)
    main = MainWindow()
    main.show()    
    sys.exit(app.exec_())

En el código vemos el uso de los recursos incrustados en los widgets de tipo QPixmap y QIcon

El resultado es el siguiente

cap_qt

Enlace a la demo




Tecnologia

pyqt: cómo hacer que QLabel sea clicable.


En Qt el widget QLabel no es clicable. Para emular el click del ratón basta añadir una propiedad que use la clase Signal y sobreescribir el evento mouseRelease de QLabel de modo que emita un señal clicked.

class QLabelClickable(QLabel):

    clicked = pyqtSignal()
    
    def __init__(self, *args):
        QLabel.__init__(self, *args)
   
    def mouseReleaseEvent(self, ev):
        self.clicked.emit()

Y este es el código para probarlo:

class MainWindow(QMainWindow):

    def label_clicked(self):
        print "click"
        
    def __init__(self, *args):
        QMainWindow.__init__(self, *args)
        self.mylabel = QLabelClickable('click me!')
        self.mylabel.clicked.connect(self.label_clicked)
        self.setCentralWidget(self.mylabel)

app = QApplication(sys.argv)
main = MainWindow()
main.show()     
sys.exit(app.exec_())

En este otro ejemplo ahora QLabel incorpora una imagen y además hago uso del antiguo sistema para conectar eventos:

class MainWindow(QMainWindow):

    def label_clicked(self):
        print "click"
        
    def __init__(self, *args):
        QMainWindow.__init__(self, *args)
        self.mylabel = QLabelClickable('')
        self.mylabel.setPixmap(QPixmap('myimage.png').scaled(10, 10, Qt.KeepAspectRatio))
        self.connect(self.mylabel, SIGNAL('clicked()'), self.label_clicked)
        self.setCentralWidget(self.mylabel)

app = QApplication(sys.argv)
main = MainWindow()
main.show()     
sys.exit(app.exec_())