Web Scrapping con Python y Selenium

El día de hoy tuvimos el agrado de ser invitados por el Círculo UEL a dar una charla en el marco del FLISOL 2016 en la Universidad Los Ángeles de Chimbote Sede Piura y el tema fue Web Scrapping con Python y Selenium, hicimos algunos ejemplos muy interesantes y nos encantó la acogida del público y el interés suscitado, vamos a compartir los artículos que usamos:

Selenium con Python

Romper Captchas con Pytesseract y Selenium

Las diapositivas:

Descargar

El script que fue la estrella de la mañana:

# -*- coding: utf-8 -*-
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import os

try:
        import Image
except ImportError:
        from PIL import Image
import pytesseract

def decodifica_campo(campo):
        return u"%s" % campo

def ir_sunat_web(ruta):
        fp = webdriver.FirefoxProfile()
        fp.set_preference("browser.download.manager.showWhenStarting",False)
        fp.set_preference("browser.download.manager.closeWhenDone", True);
        fp.set_preference("browser.download.manager.showAlertOnComplete", False);
        fp.set_preference("browser.helperApps.neverAsk.saveToDisk", "application/zip");
        driver = webdriver.Firefox(fp)
        driver.get("http://www.sunat.gob.pe/cl-ti-itmrconsmulruc/jrmS00Alias")
        try:
                WebDriverWait(driver,5).until(EC.presence_of_element_located((By.NAME, "imagen")))
        except:
                print "No carga imagen"
        driver.save_screenshot("screenshot.png")
        img=Image.open('screenshot.png')
        img_recortada = img.crop((700,309,800,361))
        img_recortada.save("recorte.png")
        try:
                captcha = pytesseract.image_to_string(img_recortada)
                codigo = driver.find_element_by_name("codigoA")
                codigo.send_keys(captcha)
        except:
                pass
        archivo = driver.find_element_by_name("archivo")
        archivo.send_keys(ruta)
        form = driver.find_element_by_name("frmConsMulRucArchivo")
        botones=form.find_elements_by_class_name("form-button")
        botones[1].click()
        try:
                WebDriverWait(driver,5).until(EC.presence_of_all_elements_located((By.CLASS_NAME, "form-table")))
                enlace=driver.find_element(By.TAG_NAME,"a")
                enlace.click()
        except:
                print "Element is not present"
                driver.close()

def main():
        ruta = "/home/usuario/carpeta"
        ruta_archivo=os.path.join(ruta,"rucs.zip")
        ir_sunat_web(ruta_archivo)

main()

Así como también un vídeo de su funcionamiento.

Saludos y muchas gracias.

Personalizando Permisos

Hasta ahora hemos trabajado con los permisos básicos que nos proporciona Django de manera automática, pero que pasa si nosotros quisieramos restringir cosas mas al detalle, por ejemplo que un usuario determinado no vea el detalle de la persona, o la tabla que lista a todas las personas, o que no pueda exportar a una hoja de cálculo. Empecemos por lo mas sencillo, creemos un permiso para restringir el acceso al detalle de la persona, para ello personalizaremos los permisos en el modelo, de la siguiente manera:

models.py

from django.db import models

# Create your models here.
class Persona(models.Model):
        dni= models.CharField(primary_key=True,max_length=8)
        nombre = models.CharField(max_length=100)
        apellido_paterno = models.CharField(max_length=100)
        apellido_materno = models.CharField(max_length=100)

        class Meta:
                permissions = (('ver_detalle_persona', 'Puede ver detalle Persona'),)

Veamos que hay de nuevo en nuestro modelo y lo primero que salta a la vista es el uso de una metaclase aquí definiremos el atributo permissions que es una tupla donde se definiran los múltiples permisos personalizados que podemos crear en este caso tenemos el par ('ver_detalle_persona', 'Puede ver detalle Persona') donde 'ver_detalle_persona' es el nombre del permiso y 'Puede ver detalle Persona' es una especie de descripción de lo que significa ese permiso, ahora ya lo tenemos listo, pero no podemos aplicarlo directamente sino que primero tenemos que hacer la migración correspondiente a la base de datos, ya que ha ocurrido una modificación del modelo:

python manage.py makemigrations personas
python manage.py migrate

Ya con esto podemos usar el permiso, para ello modificaremos el archivo personas.html:

{% if perms.personas.ver_detalle_persona %}
        <a class="btn btn-small" href="{% url 'personas:detalle_persona' persona.pk %}">
        <span class="glyphicon glyphicon-folder-open"></span>
        </a>
{% endif %}

Ahora nos logueamos con cualquiera de los usuarios nuevos que creamos en la sesión pasada, en este caso lo haremos con usuario1:

/images/blog/sin_permiso_ver_persona.jpg

Y vemos que no aparece el ícono del libro por lo tanto aparentemente no podemos acceder al detalle de la persona, pero igual que la vez pasada si es posible acceder a través de la url:

http://localhost:8000/personas/detalle_persona/44626821/

Nos falta hacer lo mismo de antes en nuestra vista DetallePersona:

views.py

class DetallePersona(DetailView):
        model = Persona
        template_name = 'detalle_persona.html'

        @method_decorator(permission_required('personas.ver_detalle_persona',reverse_lazy('personas:personas')))
        def dispatch(self, *args, **kwargs):
                return super(DetallePersona, self).dispatch(*args, **kwargs)

Listo ya con esto el usuario no puede ingresar al detalle de la persona y es inmediatamente redireccionado a la tabla de personas, ahora otorguemosle permisos al usuario1 a través de la interfaz de administración:

/images/blog/permiso_ver_detalle_persona.jpg

Damos click en la flecha y guardamos la modificación, ahora ingresemos a la aplicación personas con el usuario1 y nos debe salir una pantalla como la siguiente:

/images/blog/con_permiso_ver_persona.jpg

Eso es todo por hoy, en un proximo post terminaremos de personalizar el resto de permisos y hacerle algunas modificaciones estéticas a nuestro proyecto. Saludos.

Aplicar Permisos por Usuario en Django

Hasta ahora solo hemos trabajado con un usuario(el superusuario que creamos al principio) en nuestro proyecto, pero en la vida real son muchos los usuarios que interactuan con el software y todos no cuentan con los mismos permisos, hay algunos que pueden crear, ver, editar o eliminar elementos y otros no. Para empezar a trabajar con permisos primero debemos crear algunos usuarios adicionales, para ello vamos a ingresar a la consola de administración de Django, poniendo lo siguiente en nuestra barra de direcciones:

http://localhost:8000/admin

A continuación veremos una pantalla como esta:

/images/blog/login_administracion.jpg

Aquí nos logueamos usando el superusuario con el que hemos venido trabajando y tendremos una pantalla como esta:

/images/blog/admin_django.jpg

En el enlace donde dice Users vamos a dar click en Add para crear nuestro nuevo usuario, al que llamaremos usuario1:

/images/blog/crear_usuario.jpg

Completamos los campos y le damos click a save:

/images/blog/usuario_creado.jpg

Por ahora con esto es suficiente y ya tenemos nuestro usuario creado y listo para usarlo, salimos dando click en logout y nos vamos al login del proyecto para ingresar con nuestro nuevo usuario y la contraseña que le hemos asignado:

/images/blog/nuevo_logueo.jpg

En nuestra aplicación personas, con este usuario podemos hacer lo mismo que con el usuario anterior, pero que pasa si nosotros queremos restringir algunas cosas a este nuevo usuario, por ejemplo, no dejar que este usuario cree ni modifique personas, pero que si pueda ver su detalle y exportar el reporte en una hoja de calculo ¿Cómo hacemos esto? Con Django otra vez esto es muy sencillo y podemos definir diferentes niveles de acceso, restringiendo las opciones en la plantilla html y negando el acceso a la vista en cuestión, hagamos lo primero:

Si queremos restringir el acceso a las opciones desde la plantilla html, lo mas rápido que se nos ocurre es eliminar los íconos que nos brindan esas opciones, esto lo podemos hacer usando los permisos proporcionados por django de la siguiente manera: Primero para restringir la visualización del ícono de crear personas: personas.html

{% if perms.personas.add_persona %}

        <div class="col-lg-1">
                <a id="crear_detalle" href="reporte_personas_excel' %}" class="btn btn-info btn-block">
                <span class="glyphicon glyphicon-list-alt"></span>
                </a>
        </div>

{% endif %}

Si observamos detenidamente hemos agregado en la parte superior del div del enlace un condicional que utilizar una variable llamada perms, en esta variable se almacenan los permisos de los que dispone el usuario que ha iniciado sesión, luego de perms podemos observar el nombre de la aplicación, en nuestro caso personas, y finalmente tenemos el permiso llamado add y el modelo persona que configuran el permiso add_persona, esta condificional nos dice, si el usuario tiene el permiso add_persona, entonces se debe renderizar el div, sino no hace nada. Si ejecutamos nuestra aplicación observamos lo siguiente:

/images/blog/no_crear1.jpg

El ícono de agregar ya no aparece, pero todavía no podemos cantar victoria, un usuario avispado puede recordar la url de creación de personas y tipearla directamente en la barra de direcciones:

/images/blog/crear_persona.jpg

Mmmm, vemos que si puede ingresar ¿Cómo hacemos para restringir eso? Nuestros viejos amigos los decoradores vuelven nuevamente a nuestro auxilio, para ello debemos editar el archivo views.py:

from django.contrib.auth.decorators import permission_required
from django.utils.decorators import method_decorator
class CrearPersona(CreateView):
        model = Persona
        fields =['dni','nombre','apellido_paterno','apellido_materno']
        template_name = 'crear_persona.html'
        success_url = reverse_lazy('personas:personas')

        @method_decorator(permission_required('personas.add_persona',reverse_lazy('personas:personas')))
        def dispatch(self, *args, **kwargs):
                return super(CrearPersona, self).dispatch(*args, **kwargs)

Expliquemos esto que está un poco endiablado: Un método sobre una clase no equivale realmente a una función independiente, por lo que se debe transformar en un decorador primero, el decorador @method_decorator transforma un decorador de una función en un decorador de un método a fin de que puede ser usado sobre una instancia de un método, en el caso de las vistas basadas en clase a quien debemos aplicar el decorador es al método dispatch, en este ejemplo, cada instancia de CrearPersona tendrá protección de permission_required. El punto de entrada as_view() crea una instancia de la clase y llama al método dispatch(), (el despachador o resolvedor de URL) que busca la petición para determinar si es un GET, POST, etc, y releva la petición a un método que coincida con uno definido, o levante una excepción HttpResponseNotAllowed si no encuentra coincidencias. A la función permission_required se le deben pasar dos argumentos: El primero es el permiso a verificar que en este caso es add_persona y tiene la misma notación, primero la aplicación y luego el permiso. El segundo es la url a donde debe ser direccionado el usuario en caso de no tener el permiso necesario, en este caso a la url personas.

Ahora pongamos nuevamente la url anterior y veamos que nos sale:

/images/blog/permiso_denegado.jpg

Bueno con esto ya nos aseguramos que el usuario “usuario1″ no va a poder entrar de ninguna manera a la funcionalidad de creación de personas, ahora lo que falta es aplicar lo mismo a la vista ModificarPersona las demás vistas tendrá un tratamiento especial en un artículo posterior:

personas.html

<tbody>
{% for persona in personas %}
        <tr>
                <td>{{ persona.dni }}</td>
                <td>{{ persona.nombre }}</td>
                <td>{{ persona.apellido_paterno }}</td>
                <td>{{ persona.apellido_materno }}</td>
                <td class="text-center">
                <a class="btn btn-small" href="{% url personas:detalle_persona' persona.pk %}">
                        <span class="glyphicon glyphicon-folder-open"></span>
                </a>
                {% if perms.personas.change_persona %}
                        <a class="btn btn-small" href="{% url 'personas:modificar_persona' persona.pk %}">
                        <span class="glyphicon glyphicon-pencil"></span>
                        </a>
                {% endif %}
                </td>
        </tr>
{% endfor %}
</tbody>

views.py

class ModificarPersona(UpdateView):
        model = Persona
        template_name = 'modificar_persona.html'
        fields = ['dni','nombre','apellido_paterno','apellido_materno']
        success_url = reverse_lazy('personas:personas')

        @method_decorator(permission_required('personas.change_persona',reverse_lazy('personas:personas')))
        def dispatch(self, *args, **kwargs):
                return super(ModificarPersona, self).dispatch(*args, **kwargs)

Notamos que el permiso ahora se llama change_persona, detengamonos un poco aquí y expliquemos esto, por defecto django aplica tres permisos a cada uno de nuestros modelos: add, change y delete, que especifican si un usuario puede crear, modificar o borrar un elemento de un modelo dado, en este caso el modelo es persona.

Si corremos nuestra proyecto tenemos lo siguiente:

/images/blog/sin_accesos.jpg

Nótese que ya no aparece el lápiz de edición y tampoco se puede acceder con el url de modificar directamente en la barra de direcciones.

Ahora vamos a crear un nuevo usuario llamado usuario2, al que si le vamos a dar los permisos de crear y modificar, repetimos los pasos para crear el usuario en la interfaz de administración y nos detenemos en la ventana posterior a la creación del usuario, en la opción de permisos:

/images/blog/permisos.jpg

En esta opción tenemos un sinnumero de permisos, busquemos los relacionados a la aplicación personas y al modelo persona y los seleccionamos:

/images/blog/seleccionar_permisos.jpg

Seleccionamos la flechita entre los dos cuadros y le damos al botón Save:

/images/blog/escoger.jpg

Con esto ya tenemos que el usuario “usuario2″ tiene los permisos asignados. Ahora salgamos de la interfaz de administración e ingresemos a nuestro proyecto con el “usuario2″:

/images/blog/con_permisos.jpg

Listo ahora si el usuario2, ya tendrá estos permisos básicos y el usuario1 no, el usuario mamaya es superusuario por lo tanto puede ingresar donde quiera.

Eso es todo. Saludos.

Mejorando Nuestro Login

Este es un post pequeñito donde vamos a demostrar como mejorar nuestro login que se ve un poco feo, para ello debemos crear un módulo de Python llamado forms.py en nuestra aplicación seguridad:

/images/blog/forms.jpg

Ahora vamos a crear una clase llamada FormularioLogin que va a heredar de AuthenticationForm, la herencia en Python se determina poniendo la clase de la que se va a heredar entre parentesis en la definición de la clase hija:

forms.py

from django.contrib.auth.forms import AuthenticationForm

class FormularioLogin(AuthenticationForm):

        def __init__(self, *args, **kwargs):
                super(FormularioLogin, self).__init__(*args, **kwargs)
                self.fields['username'].widget.attrs['class'] = 'form-control'
                self.fields['username'].widget.attrs['placeholder'] = 'Usuario'
                self.fields['password'].widget.attrs['class'] = 'form-control'
                self.fields['password'].widget.attrs['placeholder'] = 'Contraseña'

¿Que cosa hemos hecho aquí?, sencillo hemos sobreescrito el comportamiento de los campos del formulario AuthenticationForm, expliquemos esto: primero se necesita acceder a cada uno de los campos del formulario, estos son 'username' y 'password', y se accede a ellos a través de la lista “fields”, luego accederemos a los widgets de cada uno de estos campos, el widget es el elemento o etiqueta html que se dibuja en el navegador, en este caso los dos son “input”, al ser elementos html tienen atributos y cada atributo tiene un valor, en este caso vamos a modificar los atributos “class” y “placeholder”, el atributo class establece la clase CSS que se aplica a los estilos del elemento y el atributo placeholder provee una ayuda a los usuarios para indicar que cosa se debe escribir en las cajas de texto. Hemos aplicado la clase form-control de bootstrap, que sirve para mostrar los elementos de un formulario mejor presentados, a ambos elementos y hemos definido la ayuda que aparecerá en cada caja de texto.

Recordemos que en nuestra vista Login el atributo form_class es AuthenticationForm, ahora esto va a cambiar:

views.py

from seguridad.forms import FormularioLogin

# Create your views here.
class Login(FormView):
        template_name = 'login.html'
        form_class = FormularioLogin
        success_url = reverse_lazy("personas:bienvenida")

        def dispatch(self, request, *args, **kwargs):
                if request.user.is_authenticated():
                        return HttpResponseRedirect(self.get_success_url())
                else:
                        return super(Login, self).dispatch(request, *args, **kwargs)

        def form_valid(self, form):
                login(self.request, form.get_user())
                return super(Login, self).form_valid(form)

Finalmente tendremos la siguiente presentación:

/images/blog/login_bonito.jpg

¿Mejor, verdad?, eso es todo por hoy.

Saludos.

Logout y Protección de Vistas en Django

En el post anterior habiamos creado un login con Django ahora nos toca hacer lo mismo pero para salir de la sesión que habiamos iniciado, para ello vamos a modificar nuestro archivo urls.py para crear la url llamada salir que invoca al método logout definido en django:

from django.conf.urls import patterns, url
from seguridad.views import Login
from django.contrib.auth.views import logout

urlpatterns = patterns(",
        url(r'^$', Login.as_view(), name="login"),
        url(r'^salir$', logout, name="salir", kwargs={'next_page': '/'}),
)

Como podemos observar llamamos a la vista logout de django.contrib.auth.views, que nos permite terminar la sesión que hemos iniciado pasandole el argumento next_page que nos redirecciona a la url raiz de nuestro proyecto, osea al login.

Ahora vamos a utilizar la url creada, hacemos una modificación a nuestro archivo base.html para agregar un enlace de salida:

{% load staticfiles %}
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="description" content="">
        <meta name="author" content="">
        <title>Ejemplo Círculo de Programadores de Python Piura</title>
        <link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap.min.css' %}">
        <link rel="stylesheet" type="text/css" href="{% static 'css/query.dataTables.min.css' %}"/>
        <script src="{% static 'js/jquery.js' %}"></script>
        <script src="{% static 'js/bootstrap.min.js' %}"></script>
        <script src="{% static 'js/jquery.dataTables.min.js' %}"></script>
    </head>
    <body>
        <div id="wrapper">
            <nav class="navbar navbar-default navbar-static-top" role="navigation" style="margin-bottom: 0">
                <div class="navbar-header">
                    <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                        <span class="sr-only">Desplegar navegación</span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                    </button>
                    <a class="navbar-brand" href="{% url 'personas:bienvenida' %}">
                        <span class="glyphicon glyphicon-home"></span> Inicio
                    </a>
                    <a class="navbar-brand" href="{% url 'personas:personas' %}">
                        <span class="glyphicon glyphicon-user"></span> Personas
                    </a>
                </div>
                <div class="navbar-right">
                    <a class="navbar-brand" href="{% url 'seguridad:salir' %}">
                        <span class="glyphicon glyphicon-log-out"></span> Salir
                    </a>
                </div>
                {% block menu %}
                {% endblock menu %}
            </nav>
            <div id="page-wrapper">
                <div class="container-fluid">
                    {% block cuerpo %}
                    {% endblock cuerpo %}
                </div>
            </div>
        </div>
    </body>
    {% block js %}
    {% endblock js %}
</html>

Con lo que tenemos lo siguiente:

/images/blog/salir.jpg

Ahora ya podemos dar click en salir y terminar la sesión que estamos utilizando, pero si observamos bien esto nos nos sirve de nada para proteger nuestra aplicación de accesos no deseados, es decir que un usuario sin estar logueado puede ingresar facilmente a cualquiera de nuestras urls, con solo ponerla en el navegador, por ejemplo, pongamos en la barra de direcciones:

http://localhost:8000/personas/

Y observemos que podemos entrar sin ningún problema a pesar de no estar logueados.

¿Cómo arreglamos esto? Sencillo, viene a nuestra ayuda un decorador muy útil llamado login_required, este va a proteger nuestra vista de aquellos usuarios que pretenden ingresar sin tener una sesión iniciada, para utilizarlo simplemente debemos modificar nuestro archivo urls.py de la aplicación con las vistas que queremos proteger:

from django.conf.urls import patterns, url
from personas.views import Personas, CrearPersona,
ReportePersonasExcel,\
Bienvenida, DetallePersona, ModificarPersona
from django.contrib.auth.decorators import login_required

urlpatterns = patterns(",
        url(r'^$',login_required(Bienvenida.as_view()), name="bienvenida"),
        url(r'^personas/$',login_required(Personas.as_view()),
        name="personas"),
        url(r'^crear_persona/$',login_required(CrearPersona.as_view()),
        name="crear_persona"),
        url(r'^reporte_personas_excel/$',login_required(ReportePersonasExcel.a
        s_view()), name="reporte_personas_excel"),
        url(r'^detalle_persona/(?P<pk>\d+)/$',
        login_required(DetallePersona.as_view()), name="detalle_persona"),
        url(r'^modificar_persona/(?P<pk>\d+)/$',login_required(ModificarPerson
        a.as_view()), name="modificar_persona"),
)

Si probamos ahora poniendo nuevamente la dirección anterior en nuestra barra de direcciones sin habernos logueado, nos aparecerá la siguiente pantalla:

/images/blog/error.jpg

¿Muy feo, no? Lamentablemente no podemos presentar esto al usuario sino que quisieramos que cada vez que alguien intente acceder de esta forma, nos redireccione a la pantalla de login, esto lo hacemos con una linea agregada al archivo settings.py:

LOGIN_URL = '/'

Ahora probemos nuevamente:

/images/blog/correcto.jpg

Con esto ya tenemos protegidas nuestras vistas y garantizamos que no tendremos accesos no deseados. Saludos.

Login en Django

Hasta este momento hemos podido tener acceso sin ningún tipo de restricciones a nuestro proyecto tutorial, pero todos sabemos que en el mundo real, eso no funciona así, por lo tanto es necesario que creemos un login para que los usuarios puedan iniciar sesión, para ello vamos a rediseñar nuestro proyecto tutorial y vamos a agregar nuevas cosas, para hacer este pequeño post me he basado en estos artículos:

Formulario Login usando Class Based Views

El Libro de Django

Listo ahora si empecemos con todo: Primero crearemos una aplicación llamada seguridad, donde vamos a manejar todo lo referente a los usuarios:

/images/blog/seguridad.jpg

Ahora vamos a decirle a nuestro settings.py que vamos a utilizar la aplicación seguridad:

INSTALLED_APPS = (
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'seguridad',
        'personas',
)

Y redefinimos nuestro archivo urls.py principal de tal manera que al ingresar ya no apunte a la aplicación personas sino a seguridad:

from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
        url(r'^admin/', include(admin.site.urls)),
        url(r'^', include('seguridad.urls',namespace='seguridad')),
        url(r'^personas/', include('personas.urls',namespace='personas')),
]

Dentro de nuestra aplicación seguridad vamos a crear una vista llamada Login:

#Importamos la vista genérica FormView
from django.views.generic.edit import FormView
from django.http.response import HttpResponseRedirect
from django.core.urlresolvers import reverse_lazy
#Importamos el formulario de autenticación de django
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth import login

# Create your views here.
class Login(FormView):
        #Establecemos la plantilla a utilizar
        template_name = 'login.html'
        #Le indicamos que el formulario a utilizar es el formulario de autenticación de Django
        form_class = AuthenticationForm
        #Le decimos que cuando se haya completado exitosamente la operación nos redireccione a la url bienvenida de la aplicación personas
        success_url = reverse_lazy("personas:bienvenida")

def dispatch(self, request, *args, **kwargs):
        #Si el usuario está autenticado entonces nos direcciona a la url establecida en success_url
        if request.user.is_authenticated():
                return HttpResponseRedirect(self.get_success_url())
        #Sino lo está entonces nos muestra la plantilla del login simplemente
        else:
                return super(Login, self).dispatch(request, *args, **kwargs)

def form_valid(self, form):
        login(self.request, form.get_user())
        return super(Login, self).form_valid(form)

Expliquemos algunas cosas:

reverse_lazy, como su nombre lo indica, es una implementación perezosa de la URL de resolución inversa(reverse). A diferencia de la función inversa tradicional, reverse_lazy no se ejecutará hasta que se necesite el valor. Es útil para prevenir excepciones 'Reverse Not Found' cuando se trabaja con direcciones URL que no pueden ser conocidos inmediatamente. dispatch, en el archivo urls.py el punto de entrada as_view() crea una instancia de la clase y llama al método dispatch(), (el despachador o resolvedor de URL) que busca la petición para determinar si es un GET, POST, etc, y releva la petición a un método que coincida con uno definido, o levante una excepción HttpResponseNotAllowed si no encuentra coincidencias. form_valid, este método es llamado cuando el formulario valida los datos. login, la llamada a login() acepta un objeto de la clase HttpRequest y un objeto User y almacena el identificador del usuario en la sesión, usando el entorno de sesiones de Django. En el caso nuestro, al usar el formulario de autenticación de django, invocando a form.get_user() obtenemos el objeto User.

Ahora vamos a crear la plantilla login.html y la guardaremos dentro de la carpeta templates(que debemos crear) en nuestra aplicación seguridad:

{% load staticfiles %}
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8"/></meta>
        <title>CIPROPY</title>
        <meta content="width=device-width, initial-scale=1.0" name="viewport"></meta>
        <link rel="stylesheet" type="text/css" href='{% static "css/bootstrap.min.css" %}'></link>
    </head>
    <body>
        <div class="container">
            <br>
            <div class="row">
                <div class="col-md-4 col-md-offset-4">
                    <div class="login-panel panel panel-default">
                        <div class="panel-heading">
                            <h3 class="panel-title">CIPROPY</h3>
                        </div>
                        <div class="panel-body">
                            <form role="form" method="post" id='form_login'>
                                {% csrf_token %}
                                <div class="form-group">
                                    <label>Usuario:</label>
                                    {{ form.username }}
                                </div>
                                <div class="form-group">
                                    <label>Contraseña:</label>
                                    {{ form.password }}
                                </div>
                                <button class="btn btn-lg btn-success btn-block" type="submit" name="login"/>Login</button>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </body>
</html>

Nótese que esta plantilla no extiende de ninguna otra, ya que su comportamiento es especial, otra cosa peculiar aquí es el uso de las etiquetas {{ form.username }} y {{ form.password }} que al renderizarse se comportan como dos cajas de texto una normal y la otra tipo password, esto es posible gracias a que estamos usando el Formulario AuthenticationForm de Django y lo hemos definido previamente en nuestra vista Login, este formulario tiene los campos username y password y los podemos utilizar en la plantilla anteponiendo el objeto form, este comportamiento especial es definido por la clase FormView.

Ahora si debemos crear un archivo urls.py en nuestra aplicación seguridad de la siguiente manera:

from django.conf.urls import patterns, url
from seguridad.views import Login

urlpatterns = patterns(",
        url(r'^$', Login.as_view(), name="login"),
)

Y listo con eso ya tenemos que al momento de correr nuestro servidor de prueba nos va a mostrar lo siguiente:

/images/blog/login.jpg

Con esto ya tenemos el proyecto listo pero no tenemos ningún usuario, debemos crear el superusuario para probar, mas adelante en próximos tutoriales crearemos mas usuarios para hacer las pruebas correspondientes, nos movemos hasta la carpeta donde está ubicado el proyecto y desde una terminal escribimos lo siguiente:

python manage.py createsuperuser

Nos pedira algunos datos, que debemos completar o presionar enter para dejar el dato mostrado por defecto entre paréntesis:

Username (leave blank to use 'mamaya'):
Email address:
Password:
Password (again):
Superuser created successfully.

En mi caso he dejado por defecto el usuario mamaya, no le he puesto ninguna dirección de correo y he completado la password, esta no aparece al momento de ser digitada ya que es un mecanismo de protección de django, si todo se ha hecho correctamente nos aparecerá el mensaje “Superuser created successfully."

Ahora si ya podemos usar el usuario mamaya para loguearnos .. image:: /images/blog/login.jpg

Si hemos escrito correctamente la password nos redireccionará hasta nuestra vista principal de la aplicación personas y sino nos volverá a mostrar la ventana de login:

/images/blog/presonas.jpg

Listo eso es todo, ya estaremos mejorando el funcionamiento de nuestro login en próximos posts.

Saludos.

Uso de UpdateView

En este post vamos a terminar lo que dejamos inconcluso anteriormente, el uso de la clase UpdateView, como ya tenemos listo nuestro archivo personas.html, simplemente vamos a crear la clase y la url necesarias.

views.py

class ModificarPersona(UpdateView):
        #Especificamos que el modelo a utilizar va a ser Persona
        model = Persona
        #Establecemos que la plantilla se llamará modificar persona
        template_name = 'modificar_persona.html'
        #Determinamos los campos con los que se va a trabajar, esto es
        obligatorio sino nos saldrá un error
        fields = ['dni','nombre','apellido_paterno','apellido_materno']
        #Con esta linea establecemos que se hará despues que la operación de
        modificación se complete correctamente
        success_url = reverse_lazy('personas:personas')

Ahora creamos nuestra plantilla modificar_persona.html:

{% extends "base.html" %}
{% block cuerpo %}

<div class="row">
    <div class="col-lg-12">
        <h1 class="page-header">Modificar Persona</h1>
    </div>
</div>
<div class="row">
    <div class="col-lg-12">
        <div class="panel panel-default">
            <div class="panel-heading">
                Por favor ingrese todos los campos necesarios.
            </div>
            <div class="panel-body">
                <form role="form" method="post">
                    {% csrf_token %}
                    <div class="form-group">
                        {{ form.as_p }}
                    </div>
                    <div class='form-group'>
                        <input type="submit" class="btn btn-primary" name="submit" value="Modificar Persona">
                        <button type="reset" class="btn btn-primary" onclick="location.href='{% url 'personas:personas' %}'">
                            Cancelar
                        </button>
                    </div>
                </form>
            </div>
        </div>
    </div>
</div>
<div id="popup"></div>
{% endblock cuerpo %}

Ahora le toca el turno a urls.py:

from django.conf.urls import patterns, url
from personas.views import Personas, CrearPersona,
ReportePersonasExcel,\
Bienvenida, DetallePersona, ModificarPersona

urlpatterns = patterns(",
        url(r'^$',Bienvenida.as_view(), name="bienvenida"),
        url(r'^personas/$',Personas.as_view(), name="personas"),
        url(r'^crear_persona/$',CrearPersona.as_view(), name="crear_persona"),
        url(r'^reporte_personas_excel/$',ReportePersonasExcel.as_view(),
        name="reporte_personas_excel"),
        url(r'^detalle_persona/(?P<pk>\d+)/$', DetallePersona.as_view(),
        name="detalle_persona"),
        url(r'^modificar_persona/(?P<pk>\d+)/$',ModificarPersona.as_view(),
        name="modificar_persona"),
)

Nótese que la última url añadida es la de modificar persona, y funciona de manera parecida al detalle.

Ahora si hacemos que el enlace con el ícono del lapiz apunte a la url de modificar_persona:

personas.html

<a class="btn btn-small" href="{% url 'personas:modificar_persona' persona.pk %}">
        <span class="glyphicon glyphicon-pencil"></span>
</a>

Con lo que tenemos lo siguiente:

/images/blog/modificar_persona.jpg

Saludos es todo por hoy.

Uso de DetailView

Luego de haber visto el uso de TemplateView, CreateView y ListView, estos dos últimos los vimos de pasada casi sin mencionarlos, ahora vamos a agregar un par de nuevas funcionalidades a nuestro proyecto tutorial, para ello empezaremos modificando el archivo personas.html para agregarle un par de enlaces que nos van a permitir editar y ver el detalle de una persona en cuestión, en este post solamente cubriremos la funcionalidad de ver el detalle, posteriormente en otro post nos encargaremos de la funcionalidad de editar a la persona:

{% extends "base.html" %}
{% block cuerpo %}
<div class="row">
    <div class="col-lg-12">
        <h1 class="page-header">Tablas</h1>
    </div>
</div>
<div class="row">
    <div class="col-lg-12">
        <div class="panel panel-info">
            <div class="panel-heading">
                Personas
            </div>
            <div class="panel-body">
                <div class='form-group'>
                    <div class="row">
                        <div class="col-lg-10">

                        </div>
                        <div class="col-lg-1">
                            <a id="crear_detalle" href="{% url 'personas:reporte_personas_excel' %}" class="btn btn-info btn-block">
                                <span class="glyphicon glyphicon-list-alt"></span>
                            </a>
                        </div>
                        <div class="col-lg-1">
                            <a id="crear_detalle" href="{% url 'personas:crear_persona' %}" class="btn btn-info btn-block">
                                <span class="glyphicon glyphicon-plus"></span>
                            </a>
                        </div>
                    </div>
                </div>
                <div class="row">
                    <div class="col-lg-12">
                        <table id="tabla" class="table table-striped table-bordered" cellspacing="0" width="100%">
                            <thead>
                                <tr>
                                    <th class="text-center">DNI</th>
                                    <th class="text-center">NOMBRE</th>
                                    <th class="text-center">APELLIDO PATERNO</th>
                                    <th class="text-center">APELLIDO MATERNO</th>
                                    <th class="text-center">ACCIONES</th>
                                </tr>
                            </thead>
                            <tbody>
                            {% for persona in personas %}
                                <tr>
                                    <td>{{ persona.dni }}</td>
                                    <td>{{ persona.nombre }}</td>
                                    <td>{{ persona.apellido_paterno }}</td>
                                    <td>{{ persona.apellido_materno }}</td>
                                    <td class="text-center">
                                        <a class="btn btn-small" href="#">
                                            <span class="glyphicon glyphicon-folder-open"></span>
                                        </a>
                                        <a class="btn btn-small" href="#">
                                            <span class="glyphicon glyphicon-pencil"></span>
                                        </a>
                                    </td>
                                </tr>
                            {% endfor %}
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
{% endblock cuerpo %}
{% block js %}
<script>
$(document).ready(function()
{
    var table = $('#tabla').DataTable( {
        "language": {
            url: "/static/localizacion/es_ES.json"
        }
    } );

    $('#tabla tbody').on( 'click', 'tr', function()
    {
        if ($(this).hasClass('selected') )
        {
            $(this).removeClass('selected');

        }
        else
        {
            table.$('tr.selected').removeClass('selected');
            $(this).addClass('selected');
        }
    });

});
</script>
{% endblock js %}

Nótese que en la cabecera de la tabla hemos puesto el encabezado acciones y que dentro del cuerpo de la tabla se ha agregado una columna que contiene dos enlaces: uno con el ícono de un libro y otro con el ícono de un lapiz, ambos son, para ver el detalle de la persona y el otro para editarlo.

/images/blog/tabla_nueva.jpg

Ahora vamos a implementar la funcionalidad de ver el detalle de la persona, para ello usamos la clase DetailView, esta clase nos simplifica la vida ya que nos permite mostrar el detalle de un modelo en particular, debiendo definir el modelo al que haremos referencia y la plantilla donde se renderizará el contenido:

from django.views.generic.detail import DetailView

class DetallePersona(DetailView):
        model = Persona
        template_name = 'detalle_persona.html'

Ahora debemos crear la plantilla detalle_persona.html:

{% extends "base.html" %}
{% block cuerpo %}
<div class="row">
    <div class="col-lg-12">
        <h1 class="page-header">Personas</h1>
    </div>
</div>
<div class="row">
    <div class="col-lg-12">
        <div class="panel panel-info">
            <div class="panel-heading">
                Detalle de Persona
            </div>
            <div class="panel-body">
                <div class='form-group'>
                    <div class="row">
                        <div class="col-md-12">
                            <label>DNI:</label>
                            <p>{{ object.dni }}</p>
                            <label>NOMBRE:</label>
                            <p>{{ object.nombre }}</p>
                            <label>APELLIDO PATERNO:</label>
                            <p>{{ object.apellido_paterno }}</p>
                            <label>APELLIDO MATERNO: </label>
                            <p>{{ object.apellido_materno }}</p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
{% endblock cuerpo %}

Modificamos el archivo urls.py para crear el url detalle_persona:

from django.conf.urls import patterns, url
from personas.views import Personas, CrearPersona,
ReportePersonasExcel,\
Bienvenida, DetallePersona

urlpatterns = patterns(",
        url(r'^$',Bienvenida.as_view(), name="bienvenida"),
        url(r'^personas/$',Personas.as_view(), name="personas"),
        url(r'^crear_persona/$',CrearPersona.as_view(), name="crear_persona"),
        url(r'^reporte_personas_excel/$',ReportePersonasExcel.as_view(),
        name="reporte_personas_excel"),
        url(r'^detalle_persona/(?P<pk>\d+)/$', DetallePersona.as_view(),
        name="detalle_persona"),
)

Ahora hacemos que el enlace con el ícono del libro abierto apunte a nuestra url detalle_persona, debemos tener en cuenta que el enlace debe estar de acuerdo a la definición de la expresión regular en la url, en este caso se le pasa como argumento la primary key que debe ser un conjunto de dígitos:

<a class="btn btn-small" href="{% url 'personas:detalle_persona' persona.pk %}">
        <span class="glyphicon glyphicon-folder-open"></span>
</a>

Ahora cuando hagamos click en el ícono del lápiz nos mostrará lo siguiente:

/images/blog/detalle_persona.jpg

Eso es todo.

Saludos.

Uso de TemplateView

Vamos a trabajar con TemplateView para hacer una vista basada en clases, algo que ya hemos utilizado antes y que es bastante fácil de programar, para ello seguiremos con nuestro proyecto tutorial, primero le vamos a hacer unas pequeñas modificaciones para que se adapte a lo que necesitamos. Editamos el archivo base.html para agregar un pequeño menú en la parte superior de la pantalla:

{% load staticfiles %}
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="description" content="">
        <meta name="author" content="">
        <title>Ejemplo Círculo de Programadores de Python Piura</title>
        <link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap.min.css' %}">
        <link rel="stylesheet" type="text/css" href="{% static 'css/jquery.dataTables.min.css'  %}"  />
        <script src="{% static 'js/jquery.js' %}"></script>
        <script src="{% static 'js/bootstrap.min.js' %}"></script>
        <script src="{% static 'js/jquery.dataTables.min.js' %}"></script>
    </head>
    <body>
        <div id="wrapper">
            <nav class="navbar navbar-default navbar-static-top" role="navigation" style="margin-bottom: 0">
                <div class="navbar-header">
                    <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                        <span class="sr-only">Desplegar navegación</span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                    </button>
                    <a class="navbar-brand" href="#">
                        <span class="glyphicon glyphicon-home"></span> Inicio
                    </a>
                    <a class="navbar-brand" href="#">
                        <span class="glyphicon glyphicon-user"></span> Personas
                    </a>
                </div>
                {% block menu %}
                {% endblock menu %}
            </nav>
            <div id="page-wrapper">
                <div class="container-fluid">
                    {% block cuerpo %}
                    {% endblock cuerpo %}
                </div>
            </div>
        </div>
    </body>
    {% block js %}
    {% endblock js %}
</html>

Tendremos algo como esto:

/images/blog/menu.jpg

Creamos un archivo llamado bienvenida.html para poner un aviso de bienvenida y de esta manera no ingresar directamente a nuestra tabla de personas, para ello debemos crear una vista usando TemplateView y agregar el url correspondiente:

bienvenida.html

{% extends "base.html" %}
{% block cuerpo %}
<div class="row">
    <div class="col-lg-12">
        <h1 class="page-header">Círculo de Programadores de Python Piura</h1>
    </div>
</div>
<div class="row">
    <div class="col-lg-12">
        <div class="panel panel-info">
            <div class="panel-heading">
                Bienvenido
            </div>
            <div class="panel-body">
                <div class='form-group'>
                    <div class="row">
                        <h3 class="text-center">Bienvenido.</h3>
                    </div>
                </div>
            </div>
    </div>
</div>
{% endblock cuerpo %}

Creamos una clase llamada Bienvenida, que hereda de TemplateView y le decimos que la plantilla a utilizar se llamará bienvenida.html.

views.py

from django.views.generic.base import TemplateView

class Bienvenida(TemplateView):
        template_name = "bienvenida.html"

En el archivo urls.py cambiamos la url raiz para que apunte a la clase Bienvenida y creamos una url llamada personas para mostrar la tabla personas.

urls.py

from django.conf.urls import patterns, url
from personas.views import Personas, CrearPersona,
ReportePersonasExcel,\
Bienvenida

urlpatterns = patterns(",
        url(r'^$',Bienvenida.as_view(), name="bienvenida"),
        url(r'^personas/$',Personas.as_view(), name="personas"),
        url(r'^crear_persona/$',CrearPersona.as_view(), name="crear_persona"),
        url(r'^reporte_personas_excel/$',ReportePersonasExcel.as_view(), name="reporte_personas_excel"),
)

Con lo que tendriamos algo como esto:

/images/blog/nueva.jpg

¿Y ahora como vemos nuestra tabla personas? Muy fácil haciendo que el enlace llamado “Personas" apunte a la url personas y de paso también hacemos que el enlace “Inicio" apunte a la url de bienvenida, para ello modificamos el archivo base.html:

<a class="navbar-brand" href="{% url 'personas:bienvenida' %}">
        <span class="glyphicon glyphicon-home"></span> Inicio
</a>
<a class="navbar-brand" href="{% url 'personas:personas' %}">
        <span class="glyphicon glyphicon-user"></span> Personas
</a>

Con esto hemos podido ver el uso sencillo de TemplateView mas adelante vamos a utilizar otras vistas basadas en clases, hasta la próxima. Saludos.

Generar Reportes en Excel desde Django

En ocasiones necesitamos tener nuestros reportes en alguna hoja de cálculo, o peor aún un cliente nos pide un reporte específico en excel ¿Cómo hacemos para generar reportes desde Django en excel? En nuestra ayuda viene una librería muy interesante que se llama openpyxl, esta libreria nos permite generar contenido en los diferentes formatos de hojas de cálculo de Microsoft Office y soporta desde versiones antiguas hasta la versión Office 2010, para instalarla lo hacemos con nuestro viejo conocido pip:

pip install openpyxl

Ahora vamos a trabajar usando el proyecto tutorial que creamos en el post pasado, aquí agregamos un botón adicional que nos va a permitir exportar el contenido de nuestra tabla de personas a excel, por lo tanto editamos el archivo personas.html y agregamos un botón adicional al que ya teniamos:

personas.html

<div class="col-lg-1">
        <a id="crear_detalle" href="#" class="btn btn-info btn-block">
                <span class="glyphicon glyphicon-list-alt"></span>
        </a>
</div>

Nótese que aún no ponemos el enlace en "#"" para no tener ningún problema, mas adelante modificaremos esto, ahora nuestra aplicación debe verse así:

/images/blog/tablas2.jpg

En el archivo views.py de la aplicación personas, vamos a crear una clase que nos permita exportar todas las personas que tenemos guardadas, a un archivo en excel.

views.py

#Vista genérica para mostrar resultados
from django.views.generic.base import TemplateView
#Workbook nos permite crear libros en excel
from openpyxl import Workbook
#Nos devuelve un objeto resultado, en este caso un archivo de excel
from django.http.response import HttpResponse

#Nuestra clase hereda de la vista genérica TemplateView
class ReportePersonasExcel(TemplateView):

        #Usamos el método get para generar el archivo excel
        def get(self, request, *args, **kwargs):
                #Obtenemos todas las personas de nuestra base de datos
                personas = Persona.objects.all()
                #Creamos el libro de trabajo
                wb = Workbook()
                #Definimos como nuestra hoja de trabajo, la hoja activa, por defecto la primera del libro
                ws = wb.active
                #En la celda B1 ponemos el texto 'REPORTE DE PERSONAS'
                ws['B1'] = 'REPORTE DE PERSONAS'
                #Juntamos las celdas desde la B1 hasta la E1, formando una sola celda
                ws.merge_cells('B1:E1′)
                #Creamos los encabezados desde la celda B3 hasta la E3
                ws['B3'] = 'DNI'
                ws['C3'] = 'NOMBRE'
                ws['D3'] = 'APELLIDO PATERNO'
                ws['E3'] = 'APELLIDO MATERNO'
                cont=4
                #Recorremos el conjunto de personas y vamos escribiendo cada uno de los datos en las celdas
                for persona in personas:
                        ws.cell(row=cont,column=2).value = persona.dni
                        ws.cell(row=cont,column=3).value = persona.nombre
                        ws.cell(row=cont,column=4).value = persona.apellido_paterno
                        ws.cell(row=cont,column=5).value = persona.apellido_materno
                        cont = cont + 1
                #Establecemos el nombre del archivo
                nombre_archivo ="ReportePersonasExcel.xlsx"
                #Definimos que el tipo de respuesta a devolver es un archivo de microsoft excel
                response = HttpResponse(content_type="application/ms-excel")
                contenido = "attachment; filename={0}".format(nombre_archivo)
                response["Content-Disposition"] = contenido
                wb.save(response)
                return response

Modificamos el archivo urls.py:

urls.py

from django.conf.urls import patterns, url
from personas.views import Personas, CrearPersona, ReportePersonasExcel

urlpatterns = patterns(",
        url(r'^$',Personas.as_view(), name="personas"),
        url(r'^crear_persona/$',CrearPersona.as_view(), name="crear_persona"),
        url(r'^reporte_personas_excel/$',ReportePersonasExcel.as_view(), name="reporte_personas_excel"),
)

Finalmente nuestro archivo personas.html va a quedar de la siguiente manera:

{% extends "base.html" %}
{% block cuerpo %}
<div class="row">
    <div class="col-lg-12">
        <h1 class="page-header">Tablas</h1>
    </div>
</div>
<div class="row">
    <div class="col-lg-12">
        <div class="panel panel-info">
            <div class="panel-heading">
                Personas
            </div>
            <div class="panel-body">
                <div class='form-group'>
                    <div class="row">
                        <div class="col-lg-10">

                        </div>
                        <div class="col-lg-1">
                            <a id="crear_detalle" href="{% url 'personas:reporte_personas_excel' %}" class="btn btn-info btn-block">
                                <span class="glyphicon glyphicon-list-alt"></span>
                            </a>
                        </div>
                        <div class="col-lg-1">
                            <a id="crear_detalle" href="{% url 'personas:crear_persona' %}" class="btn btn-info btn-block">
                                <span class="glyphicon glyphicon-plus"></span>
                            </a>
                        </div>
                    </div>
                </div>
                <div class="row">
                    <div class="col-lg-12">
                        <table id="tabla" class="table table-striped table-bordered" cellspacing="0" width="100%">
                            <thead>
                                <tr>
                                <th class="text-center">DNI</th>
                                <th class="text-center">NOMBRE</th>
                                <th class="text-center">APELLIDO PATERNO</th>
                                <th class="text-center">APELLIDO MATERNO</th>
                                </tr>
                            </thead>
                            <tbody>
                                {% for persona in personas %}
                                <tr>
                                <td>{{ persona.dni }}</td>
                                <td>{{ persona.nombre }}</td>
                                <td>{{ persona.apellido_paterno }}</td>
                                <td>{{ persona.apellido_materno }}</td>
                                </tr>
                                {% endfor %}
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
{% endblock cuerpo %}
{% block js %}
<script>
$(document).ready(function()
{
        var table = $('#tabla').DataTable({
                "language": {
                        url: "/static/localizacion/es_ES.json"
                }
        });

        $('#tabla tbody').on( 'click', 'tr', function()
        {
                if ($(this).hasClass('selected') )
                {
                        $(this).removeClass('selected');
                }
                else
                {
                        table.$('tr.selected').removeClass('selected');
                        $(this).addClass('selected');
                }
        });

});
</script>
{% endblock js %}

Y nuestro resultado al dar click al botón de exportar en excel será el siguiente:

/images/blog/exportar.jpg

Y el archivo se mostrará así:

/images/blog/reporte_excel.jpg

Hay muchísimas formas de sacarle el jugo a esta libreria, definir formatos, bordes, colores, etc. Esta ha sido una pequeña introducción, hasta la próxima.

Saludos.