Instalar Docker en Debian Jessie



Hace un tiempo me toco dictar un curso de empaquetado para Debian y un compañero preparo una máquina virtual de Virtualbox con vagrant (un tutorial lo pueden revisar en el siguiente enlace).

Hubo un momento que dañamos algunos paquetes y recuperamos la imagen de forma rápida para continuar con el curso.

En mi caso como mi máquina personal no tengo como manejar varias máquinas virtuales con virtualbox preferí irme hacía la tecnología de contenedores con Docker. Pueden revisar wikipedia para saber más de docker.

La guía de instalación para Debian Jessie en inglés lo pueden encontrar en el siguiente enlace; y la guía de usuario en el siguiente enlace. El repositorio donde se alojan imágenes lo pueden revisar en este enlace.

0. Instalar soporte de https para los repositorios:
#apt-get install apt-transport-https

1. Borrar paquetes viejos o de lxc:
#apt-get purge lxc-docker 
#apt-get purge docker.io

2. Agregar la llave gpg de docker.io:
#apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys  58118E89F3A912897C070ADBF76221572C52609D 

3. Agregar el repositorio al sources.list.d:
vim /etc/apt/sources.list.d/docker.list

deb https://apt.dockerproject.org/repo debian-jessie main


4. Actualizar la lista de repositorios: 
#apt-get update

5. Verificar que se baja el paquete del repositorio correcto: 
#apt-cache policy docker-engine

6. Instalar docker:
#apt-get install docker-engine

7. Iniciar el demonio docker:
#service docker start

8. Verificar que se instaló correctamente:
# docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
b901d36b6f2f: Pull complete 
0a6ba66e537a: Pull complete 
Digest: sha256:8be990ef2aeb16dbcb9271ddfe2610fa6658d13f6dfb8bc72074cc1ca36966a7
Status: Downloaded newer image for hello-world:latest

Hello from Docker.
This message shows that your installation appears to be working correctly.

9. Dar acceso de docker a usuarios no root:

9.1 Agregar el grupo docker si no existe:
$sudo groupadd docker

9.2 Agregar al usuario al grupo docker:
$ sudo gpasswd -a ${USER} docker

9.3 Reiniciar el demonio docker:
$sudo service docker restart

En siguientes artículos se mostrará el uso de docker, como crear imágenes y por último se creará un entorno para empaquetar para Debian.
Dejaré por acá las referencias para usarlas en un futuro:
Referencias:
10. Algunos artículos del blog de Javier Garzas: 


Volviendo a lo básico, POO en Python (parte 2)


Continuando con la serie de artículos sobre programación orientada objetos con python, les dejo el enlace del primer artículo.


El siguiente artículo se basa en un artículo de un blog publicado en www.toptal.com.

En el artículo anterior se muestra las variables id_ciudad y cont_ciudad como atributos de la clase. Se mostrará con el siguiente ejemplo que no siempre es bueno usar atributos de una clase, que se rompe la norma de encapsulación de la POO. 



#!/usr/bin/env python3


# -- coding: utf-8 --








class Servicio(object):


dato = []





def init(self,otro_dato):


self.otro_dato = otro_dato





if name == "main":


s1 = Servicio(["a","b"])


s2 = Servicio(["c","d"])


s1.dato.append(1)


print "dato de S1: ",s1.dato


print "dato de S2: ",s2.dato


s2.dato.append(2)


print "dato de s1: ",s1.dato


print "dato de s2: ",s2.dato


print "otro dato de s1: ", s1.otro_dato


print "otro dato de s2: ", s2.otro_dato




Al ejecutar el código se tiene:


dato de S1:  [1]


dato de S2:  [1]


dato de s1:  [1, 2]


dato de s2:  [1, 2]


otro dato de s1:  ['a', 'b']


otro dato de s2:  ['c', 'd']



El las 4 primeras líneas de la ejecución muestrán que la instancia s1 y la instancia s2 practicamente tienen el mismo valor a pesar de que se agrega el dato en s1 y se refleja en s2, luego se agrega el dato en s2 y se refleja en s1.

En el artículo en inglés mencionan que la solución sería no tener una lista vacía como instancia de la clase si no colocar None. 

A continuación el código:


#!/usr/bin/env python3


# -- coding: utf-8 --








class Servicio(object):


dato = None





def init(self,otro_dato):


self.otro_dato = otro_dato





if name == "main":


s1 = Servicio(["a","b"])


s2 = Servicio(["c","d"])


s1.dato = 1


print "dato de S1: ",s1.dato


print "dato de S2: ",s2.dato


s2.dato = 2


print "dato de s1: ",s1.dato


print "dato de s2: ",s2.dato




El resultado de la ejecución es el siguiente: 


dato de S1:  1


dato de S2:  None


dato de s1:  1


dato de s2:  2



Como se ve ya no muestra valores iguales entre las dos instancias de la clase.

Ahora muestro el código sin la instancia de la clase, creando dato como una variable privada y unos métodos que acceden a dato:



#!/usr/bin/env python3


# -- coding: utf-8 --








class Servicio(object):








def init(self,otro_dato):


self.dato = []


self.otro_dato = otro_dato





def mostrar_dato(self):


return self.__dato





def agregar_dato(self,dato):


self.__dato.append(dato)





def inicializar_dato(self,):


self.__dato = []





if __name == "main":


s1 = Servicio(["a","b"])


s2 = Servicio(["c","d"])


try:


s1.dato.append(1)


except AttributeError:


print "No se pudo agregar a dato de s1"


finally:


print s1.mostrar_dato()


print s2.mostrar_dato()


s1.agregar_dato(1)


s1.agregar_dato(3)


s2.agregar_dato(2)


s2.agregar_dato(4)


print s1.mostrar_dato()


print s2.mostrar_dato()






Al ejecutar el script se tiene lo siguiente:


No se pudo agregar a dato de s1


[]


[]


[1, 3]


[2, 4]




No es un error crear atributos de una instancia lo que se tiene que saber es exactamente que lo que se necesita en la clase para evitar estos errores, y lo mejor es seguir el paradigma de programación orientada a objetos.

Volviendo a lo básico, POO en Python (parte 1)


Luego de un tiempo escribiendo artículos de temas muy diversos sobre python, ahora iniciaré una serie sobre programación orientada a objetos desde lo básico hasta las nuevas características incorporadas en el lenguaje.

Esto me ayudará también a mí a corregir unas práctias en cuanto a la programación orientada a objetos y aprender nuevas prácticas.

Actualmente estoy usando una herramienta de una página web que permite evaluar la calidad de un código, este sitio se llama www.quantifiedcode.com.

Me he encontrado con errores en un desarrollo que por eso inicio esta serie de artículos.

En la siguiente figura verán como muestra los errores la herramienta:


Lo bueno es que al hacer un commit a github automáticamente quantifiedcode envía un correo con las incidencias.


Bueno, ahora toca ir al artículo en sí.

Se tiene una clase ciudad con varios métodos.

A continuación se muestra el código:

#!/usr/bin/env python3



from math import *



#Se crea la clase ciudad que hereda de object.

#Esto se declara asi actualmente por convencion.

class Ciudad(object):

#Datos

cont_ciudad = 0 

id_ciudad = 0





def init(self,nombre='',x=0,y=0):

"""Constructor que recibe el nombre de la ciudad,x y y"""

self.nombre = nombre 

self.x = x 

self.y = y

Ciudad.cont_ciudad += 1

self.id_ciudad = Ciudad.cont_ciudad



def str(self):

"""Metodo que retorna un string con la info de la ciudad"""

return 'Ciudad: ' + self.nombre + ',id= ' + str(self.id_ciudad) + ',x= %s,y=%s' %(self.x,self.y)

    

def set( self, nombre):

"""Metodo que asigna un valor"""

self.nombre= nombre



def get( self):

"""Metodo que obtiene un valor"""

return  self.nombre



def mover_a(self,x=0,y=0):

"""Metodo que cambia de valor a x y y"""

self.x += x

self.y += y



def distancia(self, otra_ciudad):

"""Metodo que calcula la distancia con respecto a otra ciudad"""

xi = pow(otra_ciudad.x-self.x,2)

yi = pow(otra_ciudad.y-self.y,2)

return sqrt(xi+ yi)



def del(self):

"""Elimina la clase"""

#obtener el nombre de una clase

class_name = self.class.name

print('class ', class_name, 'destroyed')





#Inicializando un metodo estatico

#Es el caso que no se referencia al objeto en si mismo con

#self

@staticmethod

def info():

"""Metodo que devuelve el nombre del desarrollador"""

return  "Desarrollado por Seraph"







if name == "main":

a = Ciudad('Valencia',5,5)

b = Ciudad('Maracay',5,15)

print(a)

print(b)

print(Ciudad.cont_ciudad)

a.mover_a(4,3)

b.mover_a(7,12)

print(a.info())

print(a)

print(b)




El método info maneja un decorador llamado staticmethod que lo define como un método estático, ya que no se le pasa self para trabajar con métodos o variables del mismo objeto.

El resultado de ejecutar el script es el siguiente:

Ciudad: Valencia,id= 1,x= 5,y=5
Ciudad: Maracay,id= 2,x= 5,y=15
2
Desarrollado por Seraph
Ciudad: Valencia,id= 1,x= 9,y=8
Ciudad: Maracay,id= 2,x= 12,y=27
('class\t', 'Ciudad', 'destroyed')
('class\t', 'Ciudad', 'destroyed')



Escritura y lectura de un archivo con formato bson usando Python


En artículos anteriores se ha tocado el tema de una base de datos mongodb con python, ahora en esté artículo se tocará el tema de un archivo binario que almacene documentos JSON en vez de usar un servidor mongodb.

Lo primero que se hará es crear un archivo con extensión bson donde se guardará un diccionario (json) con datos aleatorios y luego se busca la información del archivo.


Se tiene que tener instalado la libería bson para python:
apt-get install python-bson python3-bson


Escribir en un archivo bson:

#!/usr/bin/env python3

#Se importa el módulo bson

import bson

#Se abre el archivo prueba.bson para escritura y binario

f = open("prueba.bson", 'wb')

#Se intenta guardando un rango de valores del 1 al 99 decodificandolo en formato bson

try:

    for i in range(1,100):

        f.write(bson.BSON.encode({"dato":i}))

finally:

    f.close()



Al ejecutar el script se puede ver con un ls que el archivo pruebas.bson se ha creado y tiene datos dentro del mismo:
ls -l prueba.bson 
-rw-r--r-- 1 ernesto ernesto 1485 dic 13 12:16 prueba.bson


El código de lectura se muestra a continuación:
#!/usr/bin/env python3

#Se importa la libería bson

import bson

#Se abre el archivo prueba.bson en modo lectura y binario

f = open("prueba.bson", 'rb')

#Se extrae la información almacenada y se decodifica el bson

resultado = bson.decode_all(f.read())

#Se imprime en pantalla la lista

print (resultado)



El resultado de ejecutar el script se muestra a continuación:
python lectura.py 

[{u'dato': 1}, {u'dato': 2}, {u'dato': 3}, {u'dato': 4}, {u'dato': 5}, {u'dato': 6}, {u'dato': 7}, {u'dato': 8}, {u'dato': 9}, {u'dato': 10}, {u'dato': 11}, {u'dato': 12}, {u'dato': 13}, {u'dato': 14}, {u'dato': 15}, {u'dato': 16}, {u'dato': 17}, {u'dato': 18}, {u'dato': 19}, {u'dato': 20}, {u'dato': 21}, {u'dato': 22}, {u'dato': 23}, {u'dato': 24}, {u'dato': 25}, {u'dato': 26}, {u'dato': 27}, {u'dato': 28}, {u'dato': 29}, {u'dato': 30}, {u'dato': 31}, {u'dato': 32}, {u'dato': 33}, {u'dato': 34}, {u'dato': 35}, {u'dato': 36}, {u'dato': 37}, {u'dato': 38}, {u'dato': 39}, {u'dato': 40}, {u'dato': 41}, {u'dato': 42}, {u'dato': 43}, {u'dato': 44}, {u'dato': 45}, {u'dato': 46}, {u'dato': 47}, {u'dato': 48}, {u'dato': 49}, {u'dato': 50}, {u'dato': 51}, {u'dato': 52}, {u'dato': 53}, {u'dato': 54}, {u'dato': 55}, {u'dato': 56}, {u'dato': 57}, {u'dato': 58}, {u'dato': 59}, {u'dato': 60}, {u'dato': 61}, {u'dato': 62}, {u'dato': 63}, {u'dato': 64}, {u'dato': 65}, {u'dato': 66}, {u'dato': 67}, {u'dato': 68}, {u'dato': 69}, {u'dato': 70}, {u'dato': 71}, {u'dato': 72}, {u'dato': 73}, {u'dato': 74}, {u'dato': 75}, {u'dato': 76}, {u'dato': 77}, {u'dato': 78}, {u'dato': 79}, {u'dato': 80}, {u'dato': 81}, {u'dato': 82}, {u'dato': 83}, {u'dato': 84}, {u'dato': 85}, {u'dato': 86}, {u'dato': 87}, {u'dato': 88}, {u'dato': 89}, {u'dato': 90}, {u'dato': 91}, {u'dato': 92}, {u'dato': 93}, {u'dato': 94}, {u'dato': 95}, {u'dato': 96}, {u'dato': 97}, {u'dato': 98}, {u'dato': 99}]



Devuelve una lista de diccionarios(json) con los datos almacenados en el archivo.

El archivo binario contiene algo como lo siguiente:

Hacer traducciones con google translate desde python


Google Translate tiene un API que permite usar el traductor desde un programa, en el caso de Python se tiene goslate.



Para instalarlo se usa pip:
pip install goslate


Se ejecuta python. Y se prueba Hola Mundo en Inglés a otros idiomas:

Se importa el módulo:
>>> import goslate
Se crea la instancia del objeto:
>>> gs = goslate.Goslate()
Se traduce hello world a alemán, español e italiano:

>>> print gs.translate('hello world', 'de')
Hallo Welt
>>> print gs.translate('hello world', 'es')
Hola mundo
>>> print gs.translate('hello world', 'it')
Ciao mondo

En el sitio de pypi de la aplicación muestra otras funcionalidades de la aplicación como detectar el idioma, busquedas, entre otras.

Extracción de información de PDFs con python (parte 3)


Continuando con los artículos de extracción de información de PDF con Python, en este caso como en los artículos anteriores el pdf ha utilizar es un reporte que tiene Cencoex en su página sobre la asignación de dolares para las empresas del sector salud (extracción de información de PDFs parte 1 y parte 2).

En la parte 2 se logró extraer la información pero sin un patrón bien definido, en este caso ya se logra ordenar la información y guardar en una base de datos NoSQL como lo es MongoDB.

La primera parte del script es la parte de extraer la información del PDF como se explicó en la parte 2, la parte de ordenar se dedica a definir patrones de busqueda por medio de expresiones regulares en una lista que contiene las líneas de texto extraídas.


A continuación el código del script:

#!/usr/bin/env python

# -- coding: utf-8 --



import string

#Se importa los modulos necesarios de pdfminer



from pdfminer.pdfparser import PDFParser

from pdfminer.pdfdocument import PDFDocument

from cStringIO import StringIO

from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter

from pdfminer.converter import TextConverter

from pdfminer.layout import LAParams

from pdfminer.pdfpage import PDFPage



import re



def convertir(archivo, paginas=None):

    #Si no se le pasa el numero de paginas se inicializa pagenums si no se le pasa 

    #a pagenums el numero de paginas.

    if not paginas:

        pagenums = set()

    else:

        pagenums = set(paginas)



    #Se define la salida

    output = StringIO()

    #Se crea la instancia del manejador

    manager = PDFResourceManager()

    #se instancia el conversor de texto donde se le pasa el manejador y la salida

    converter = TextConverter(manager, output, laparams=LAParams())

    #Se instancia el interprete pasando el manejador y el conversor

    interpreter = PDFPageInterpreter(manager, converter)



    #Se abre el archivo de entrada

    infile = file(archivo, 'rb')

    #Se crea un ciclo pasando el archivo de entrada y el numero de paginas.

    for page in PDFPage.get_pages(infile, pagenums):

        #Se procesa cada pagina

        interpreter.process_page(page)

    #Se cierra el archivo de entrada

    infile.close()

    #Se cierra el conversor

    converter.close()

    #Se obtiene los valores de la salida en texto

    texto = output.getvalue()

    #Se cierra la salida

    output.close

    #Se devuelve el texto

    resultado = string.split(texto,"\n")



    return resultado



#Se le pasa el archivo pdf de cencoex del sector salud

def extraerDatos(archivo):

    #Se pasa el archivo a la funcion convertir que convierte la informacion

    #en una lista

    listado = convertir(archivo)

    

    #Se define el patron de la expresion regular del RIF y de numero.

    patron_rif = re.compile(r"(J-\d+)")

    patron_numero = re.compile(r"(\d+)")

    pattern = re.compile(r"(\d+)")



    #Se crea una lista vacia.

    lista = []

    #Se recorre la lista

    for i in listado: 

        #Se recorre cada elemento de la lista buscando los numeros.

        resultado_numero=  patron_numero.findall(i)

        #Si la cantidad de elementos devuelto es 1 y es una lista que contiene 3 elementos o menos

        if (len(resultado_numero) == 1) and (len(resultado_numero[0]) <= 3):

            #Se hace un split eliminando los espacios en blanco, agregando a nombre desde el 2do elemento de la lista 

            #hasta el final

            nombre = i.split(" ")[1:]

            #Luego se vuelve a agregar esos espacios

            nombre = string.join(nombre," ")

            #Se agrega a la lista un diccionario con el numero y el nombre de la empresa

            lista.append({'numero': int(resultado_numero[0]),"empresa": nombre})

    

    #Se crea un diccionario de contadores, en este caso 2.

    #el del ciclo del rif y el del monto

    contador = { "rif": 1, "monto":1}



    #Se vuelve a recorrer el listado



    for i in listado:

        #Se busca en cada elemento el rif por una expresion regular

        resultado_rif = patron_rif.findall(i)

        #Si el resultado es diferente de cero

        if len(resultado_rif) 0:

            #Se recorre la lista generada anteriormente (la lista de diccionarios)

            for num in range(len(lista)):

                #Si el elemento del diccionario de esa lista es igual al contador de rif

                #Se agrega el rif al diccionario actual de la lista y se finaliza el recorrido

                if  (lista[num]["numero"] == contador["rif"]):

                    lista[num]["rif"] = resultado_rif[0]

                    break

            #Se incrementa el contador de rif.

            contador["rif"] = contador["rif"] + 1

        #Si se encuentra una coma en la lista y al eliminar el espacio en blanco la longitud

        #de la lista es 1, se recorre la lista de diccionario para buscar el monto, en este caso se

        #consulta si el numero del diccionario es igual al contador de monto, si es así se agrega

        #el monto al diccionario y se finaliza el recorrido del ciclo

        if (string.find(i,",") -1 and len(i.split(" ")) == 1):

            for num in range(len(lista)):

                if  (lista[num]["numero"] == contador["monto"]):

                    lista[num]["monto"] = i

                    break

            #Se incrementa el contador monto

            contador["monto"] = contador["monto"] +1

    #Se retorna la lista de diccionarios con los elementos necesarios

    return lista





if name == "main":

    #Se importa el modulo pymongo

    import pymongo

    #Se conecta a la base de datos mongodb local

    connection = pymongo.MongoClient("mongodb://localhost")

    #Se usa la base de datos cencoex y la coleccion salud

    db=connection.cencoex

    salud = db.salud

    #Se extraen los datos del archivo salud.pdf y se guarda en datos

    datos = extraerDatos("salud.pdf")

    #Se recorre datos, si la longitud de los elementos de los diccionarios es igual a 4 

    #Se inserta en la base de datos

    for num in range(len(datos)):

        if len(datos[num].keys()) == 4:

            try:

                salud.insert_one(datos[num])

            except Exception as e:

                print "Unexpected error:", type(e), e







Al ejecutar el script no devuelve nada por pantalla, al ejecutar mongodb y hacer una consulta se encuentra que se insertaron los datos.

> db.salud.findOne()
{
"_id" : ObjectId("561bda362b405e26d2174c84"),
"rif" : "J-000836493",
"monto" : "136.455.250,07",
"empresa" : "ABBOTT LABORATORIES C.A.",
"numero" : 1
}

Ahora se hace una consulta al número 195:
> db.salud.findOne({"numero": 195})
{
"_id" : ObjectId("561bda382b405e26d2174d3e"),
"rif" : "J-002594160",
"monto" : "2.011.181,88",
"empresa" : "ZUOZ PHARMA S.A.",
"numero" : 195
}

Al hacer la consulta al 196 devuelve null, ya que son 195 elementos.
> db.salud.findOne({"numero": 196})
null

Al consultar la cantidad de elementos insertados en la base de datos se nota que hay una inconsistencia entre los números de los elementos y la cantidad, este tema se tratará en un sisguiente artículo.

> db.salud.count()
187

Es claro que al usar archivos pdf no se tiene un patrón bien definido para tener toda la información de los mismos por medio de scripts, siendo este la peor manera de suministrar información que pueda ser manejada por programas de computadora.

El script lo pueden encontrar en el repositorio de github.

En próximo artículo tocará arreglar la información que se tiene en la base de datos con respecto al pdf (visualmente) y se creará la forma de como visualizar la información por un API REST Ful.


Extracción de información de PDFs con python (parte 2).


En el primer artículo de la serie se explico el uso de peepdf, en este caso se utilizará pdfminer.

En el siguiente enlace consiguen un tutorial de como usarlo, u otro, y otro más.

El archivo a analizar será el mismo del artículo anterior el cual es un archivo de la página de CENCOEX del área de salud.

En la siguiente figura se muestra el archivo salud.pdf donde el archivo tiene 4 páginas de una tabla con 4 columnas, No., Empresa, RIF y total asignado en Bsf.



Para instalar pypdf2 para Debian se usará apt-get:
apt-get install pdfminer-data python-pdfminer

Para pip de la siguiente forma:
pip install pdfminer 


A continuación se muestra el código del script que se utilizará para extraer los datos:

#!/usr/bin/env python

# -- coding: utf-8 --



#Se importa los modulos necesarios de pdfminer



from pdfminer.pdfparser import PDFParser

from pdfminer.pdfdocument import PDFDocument

from cStringIO import StringIO

from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter

from pdfminer.converter import TextConverter

from pdfminer.layout import LAParams

from pdfminer.pdfpage import PDFPage







def convertir(archivo, paginas=None):

    #Si no se le pasa el numero de paginas se inicializa pagenums si no se le pasa 

    #a pagenums el numero de paginas.

    if not paginas:

        pagenums = set()

    else:

        pagenums = set(paginas)



    #Se define la salida

    output = StringIO()

    #Se crea la instancia del manejador

    manager = PDFResourceManager()

    #se instancia el conversor de texto donde se le pasa el manejador y la salida

    converter = TextConverter(manager, output, laparams=LAParams())

    #Se instancia el interprete pasando el manejador y el conversor

    interpreter = PDFPageInterpreter(manager, converter)



    #Se abre el archivo de entrada

    infile = file(archivo, 'rb')

    #Se crea un ciclo pasando el archivo de entrada y el numero de paginas.

    for page in PDFPage.get_pages(infile, pagenums):

        #Se procesa cada pagina

        interpreter.process_page(page)

    #Se cierra el archivo de entrada

    infile.close()

    #Se cierra el conversor

    converter.close()

    #Se obtiene los valores de la salida en texto

    texto = output.getvalue()

    #Se cierra la salida

    output.close

    #Se devuelve el texto

    return texto





if name == "main":



    print convertir("salud.pdf")



Al ejecutar el script se obtiene lo siguiente ( a continuación se muestra la salida final):


...
...
J-314931326
J-306044434
J-306652809
J-000273170
J-294885756
J-315368676
J-000276099
J-309260006
J-000068607
J-307695226
J-000886440
J-075558855
J-000298327
J-000440581
J-075265092
J-308718840

TOTAL

924.611,13
4.551.099,39
6.210.501,67
2.727.618,90
15.237.285,96
24.726.553,87
8.012.224,42
12.415.583,66
1.242.072,56
843.053,00
422.464,13
827.852,61
2.120.640,06
39.935.357,41
64.414.559,56
1.585.616,34
1.926.786,95
1.171.948,02
15.803.447,35
3.876.885,14
3.876.885,14
14.222.393,78
13.859.953,87
1.856.410,99
5.572.194,16
2.858.560,62
1.226.368,54
426.087,02
8.577.657,04
3.244.765,25
7.291.129,73
7.514.239,65
7.146.119,95
105.741.558,37
1.607.871,72
160.091.223,81
78.578.774,12
796.193,32
1.450.824,00
1.643.440,45
765.726,00
900.903,00
26.500,00
880.332,50
2.140.900,16
6.768.878,02
105.909.664,86
3.227.153,04
22.673.390,14
112.625,66
1.288.555,83
115.943.301,02
17.886.755,85
1.508.591,35


IMPORTACIONES TOTALES (ORDINARIAS + ALADI + SUCRE)

DISTRIBUCIÓN POR EMPRESAS AGREMIADAS DEL SECTOR SALUD

PERÍODO DE REFERENCIA: 01/01/2014 AL 05/09/2014

EMPRESA


163 RENTA MEDICA PC CA
164 REPRECLIN  LAB  C.A.
165 REPRESENTACIONES AITOR C.A.
166 REPRESENTACIONES DE LABORATORIO WEISS C.A.
167 REPRESENTACIONES LABIN-VE, S.A.
168 REPRESENTACIONES LUFRAN C.A.
169 REPRESENTACIONES MEDICAS YOMA CA
170 REPRESENTACIONES NOLVER C.A.
171 REPRESENTACIONES VARGAS C.A.
172 RESPITEC EQUIPOS MEDICOS C.A.
173 SANIFARMA PAÑALEX C.A
174 SANOFI-AVENTIS DE VENEZUELA S.A.
175 SEIJIRO YAZAWA IWAI C.A.
176 SERVIMEDIC SOLUCIONES CARDIOVASCULARES S.A.
177 SICA FARM DE VENEZUELA C.A
178 SINO PRODUCTS PAN AMERICAN SUPPLYC.A
179 SM PHARMA C.A.
180 SOLUCIONES ELECTRONICAS Y MEDICAS C.A.
181 SUMINISTROS BAIKOR CA
182 SUMINISTROS FOTOGRAFICOS Y MEDICOS SUFOMECA C.A. SUFOMECA
182 SUMINISTROS FOTOGRAFICOS Y MEDICOS SUFOMECA C.A. SUFOMECA
183 SUMINISTROS MEDICOS JAYOR C.A
184 SUMINISTROS MÉDICOS MULTIMÉDICA DMR C.A
185 SUMINISTROS RADI SURADI C.A.
186 SUPLIDORA HOSPIMED 2004 C.A
187 SURGILAP EQUIPOS C.A.
188 SYSMED EQUIPOS MEDICOS C.A.
189 TECNOLOGIA CLINICA CARDIOPULSO C.A.
190 TECNOLOGIA DOS C.A.
191 TECNOLOGIA MEDICA DEL CARIBE C.A.
192 TECNOMED J TRAPP C.A.
193 ULTRA CARE MEDICAL C.A.
194 WEST PHARMACEUTICAL SERVICES VENEZUELA C.A.
195 ZUOZ PHARMA S.A.

TOTAL GENERAL

RIF

J-306068848
J-002678143
J-308916064
J-000790078
J-003285536
J-001582738
J-001905545
J-003243906
J-002285540
J-300855775
J-002185449
J-303088481
J-000717184
J-306080740
J-306338438
J-303391923
J-070131390
J-303539661
J-312802316
J-001457810
J-001457810
J-003530255
J-303120784
J-003143324
J-300511529
J-302742463
J-308785539
J-306449191
J-304998074
J-001832726
J-000036071
J-310620440
J-002594160
J-002015080

TOTAL

4.310,96
5.676.457,12
205.152,00
6.221.734,67
4.270.788,35
1.954.333,42
1.996.335,62
51.816.733,75
5.653.885,98
58.431,62
410.002,29
103.980.350,21
3.252.208,70
2.382.957,73
212.924,97
171.935,94
27.281.496,00
572.896,00
492.918,00
3.033.050,09
3.033.050,09
12.005.888,05
1.313.993,48
9.298.817,31
4.714.939,88
1.085.603,86
1.190.975,38
831.945,94
271.105,08
2.048.621,93
1.900.646,66
1.151.336,80
2.011.181,88
22.706.399,72
2.237.473.782,31

NOTA: Las cifras de las modalidades de importacion ALADI y SUCRE se encuentran hasta el 31-08-2014


Como se nota, la información viene de manera inconsistente, tocaría trabajar el texto para ordenarlo (para próximo artículo). 

Tomemos la empresa con el número 163. Es la empresa: Renta Médica PC, C.A., RIF: J-306068848 y le asignaron 4.310,96 (como lo muestra los campos del resultado subrayados y en italica y la figura a continuación).


Ya se va obteniendo información útil. Sólo queda ordenarla un poco (siguiente artículo), aparte revisar otras herramientas. Los datos en un futuro se almacenarán en una base de datos sqlite o mongodb para crear un API Rest ful con Python (próximos artículos) de manera de mostrar la forma como debería visualizarse información de las instituciones para Datos Abiertos (OpenData).


Como ven extraer los datos no es difícil, lo complicado es que el archivo no tiene consistencia para manejarlo de manera fácil. 








Extracción de información de PDFs con python (parte 1).


Existen varias herramientas en python para obtener información de PDF.

En este caso visite la página de Cencoex, en la sección de Cencoex en cifras hay un enlace a un pdf  a Liquidaciones a Empresas Agremiadas del Sector Salud (Ordinarias + ALADI + SUCRE)

Para este primer artículo se usará pycurl para bajar el pdf y peepdf para analizarlo. Para descargar peepdf se puede hacer desde el repositorio del proyecto en github.

En el caso de pycurl se instala por pip o por apt-get en Debian.

Se baja peepdf:
git clone https://github.com/jesparza/peepdf.git

Para bajar el archivo se ejecuta el código a continuación, el cual baja el archivo pdf y lo salva como salud.pdf:
import pycurl

with open('salud.pdf', 'wb') as f:
    c = pycurl.Curl()
    c.setopt(c.URL, 'http://www.cencoex.gob.ve/images/stories/pdfs/estadisticas/Salud.pdf')
    c.setopt(c.WRITEDATA, f)
    c.perform()
    c.close()


En el directorio de la aplicación se ejecuta (opción -f para el archivo y -i en modo interactivo:
./peepdf.py  -f ../salud.pdf -i 

Y devuelve:
File: salud.pdf
MD5: e639fca504182dc5cb383cc455d30c21
SHA1: 2c29f4a20e4e0b6a3c10decbfacced991e59ad79
SHA256: 9a4d3d48f060d1fa29fe9cb5e7a435643586e9871e62254839daf3fd87e45506
Size: 57611 bytes
Version: 1.3
Binary: True
Linearized: False
Encrypted: False
Updates: 0
Objects: 34
Streams: 7
Comments: 0
Errors: 0

Version 0:
Catalog: 1
Info: 2
Objects (34): [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34]
Streams (7): [10, 33, 32, 5, 15, 21, 27]
Encoded (7): [10, 33, 32, 5, 15, 21, 27]

El comando tree devuelve la estructura lógica del archivo:

PPDF> tree

/Catalog (1)
/Pages (3)
/Page (4)
/Pages (3)
stream (5)
integer (6)
Unknown (0)
/Catalog (1)
/R10 (12)
stream (10)
/R7 (11)
/ExtGState (7)
/R8 (13)
/Font (8)
/Encoding (34)
/FontDescriptor (9)
stream (32)
stream (33)
/Page (14)
/Pages (3)
stream (15)
integer (16)
Unknown (0)
/Catalog (1)
/R10 (18)
stream (10)
/R7 (17)
/ExtGState (7)
/R8 (19)
/Font (8)
/Page (20)
/Pages (3)
stream (21)
integer (22)
Unknown (0)
/Catalog (1)
/R10 (24)
stream (10)
/R7 (23)
/ExtGState (7)
/R8 (25)
/Font (8)
/Page (26)
/Pages (3)
stream (27)
integer (28)
Unknown (0)
/Catalog (1)
/R10 (30)
stream (10)
/R7 (29)
/ExtGState (7)
/R8 (31)
/Font (8)
/Info (2)


Para ver la estructura física se usa el comando offsets:
PPDF> offsets 

       0 Header
      15
        Object  5 (8865)
    8879
    8881
        Object  6 (19)
    8899
    8901
        Object  15 (9005)
   17905
   17907
        Object  16 (20)
   17926
   17928
        Object  21 (8618)
   26545
   26547
        Object  22 (20)
   26566
   26568
        Object  27 (6554)
   33121
   33123
        Object  28 (20)
   33142
   33144
        Object  4 (189)
   33332
   33334
        Object  14 (191)
   33524
   33526
        Object  20 (191)
   33716
   33718
        Object  26 (191)
   33908
   33910
        Object  3 (88)
   33997
   33999
        Object  1 (47)
   34045
   34047
        Object  7 (40)
   34086
   34088
        Object  11 (29)
   34116
   34118
        Object  12 (31)
   34148
   34150
        Object  10 (4140)
   38289
   38291
        Object  13 (29)
   38319
   38321
        Object  17 (29)
   38349
   38351
        Object  18 (31)
   38381
   38383
        Object  19 (29)
   38411
   38413
        Object  23 (29)
   38441
   38443
        Object  24 (31)
   38473
   38475
        Object  25 (29)
   38503
   38505
        Object  29 (29)
   38533
   38535
        Object  30 (31)
   38565
   38567
        Object  31 (29)
   38595
   38597
        Object  33 (614)
   39210
   39212
        Object  8 (436)
   39647
   39649
        Object  34 (408)
   40056
   40058
        Object  9 (204)
   40261
   40263
        Object  32 (16249)
   56511
   56513
        Object  2 (243)
   56755
   56757
        Xref Section (709)
   57465
   57467
        Trailer (138)
   57604
   57605 EOF

Para ver el metadato del pdf se ejecuta metadata:
PPDF> metadata

Info Object in version 0:

<< /ModDate D:20140908210848+04'-30'
/CreationDate D:20140908210848+04'-30'
/Producer Nitro PDF PrimoPDF
/Title LIQUIDACIONES DEL 1 ENERO AL 5 DE SEPTIEMBRE 2014.xls
/Creator PrimoPDF http://www.primopdf.com
/Author lrojas >>

La fecha de creación del archivo es 09-08-2014 y la hora es 21:08:48  hora de Venezuela. El título del archivo Liquidaciones del 1 enero al 5 de septiembre de 2014, ah, es un archivo xls de excel. Y usaron primopdf para generar el pdf, por último el autor del pdf es lrojas.


Si se quiere revisar el objeto 2:
PPDF> object 2

<< /ModDate D:20140908210848+04'-30'
/CreationDate D:20140908210848+04'-30'
/Producer Nitro PDF PrimoPDF
/Title LIQUIDACIONES DEL 1 ENERO AL 5 DE SEPTIEMBRE 2014.xls
/Creator PrimoPDF http://www.primopdf.com
/Author lrojas >>

Es el que contiene la información de los metadatos.

Revisando el objeto 15 se obtiene:
432 1739.33 3991 6 re
f
432 1642.33 3991 6 re
f
432 1545.33 3991 6 re
f
432 1448.33 3991 6 re
f
432 1351.33 3991 6 re
f
432 1254.33 3991 6 re
f
432 1157.33 3991 6 re
f
432 1060.33 3991 6 re
f
432 963.332 3991 6 re
f
432 866.332 3991 6 re
f
432 769.332 3991 6 re
f
432 672.332 3991 6 re

O usando rawstream:
rawstream 15
50 9f fa 71 0f 29 76 8a bc 44 3e dd c6 15 84 9e   öP..q.)v..D>.....ö
4c 89 76 f6 75 8c 3d 23 65 11 fb 65 48 d1 0e a1   öL.v.u.=#e..eH...ö
37 f6 08 d3 b6 f8 d2 17 15 4a 76 29 b0 04 86 e1   ö7........Jv)....ö
b4 9e 9b 3a 34 e7 d1 ce 91 9a c9 c0 91 9e cc 6c   ö...:4..........lö
4a 5d 24 67 c7 aa 21 fe 2c ea e6 ea 9e 8d 95 33   öJÅ$g..!.,......3ö
61 e5 55 2c 13 3c cd 61 21 d1 26 03 47 ba 32 b3   öa.U,.<.a!.&.G.2.ö
09 da cc ce 8e da e4 61 a1 85 34 d6 ba 00 63 5c   ö.......a..4...cÖö
08 f1 63 34 7a 90 44 cb 79 4d 95 50 58 6c e7 55   ö..c4z.D.yM.PXl.Uö
74 d4 da fb 44 4c 8a 48 a8 ae 41 42 b1 e5 44 a8   öt...DL.H..AB..D.ö
e9 bb 42 bb 47 28 3b cd 5e 22 0f 5d 3b 42 e8 d9   ö..B.G(;.Ü".Å;B..ö
e4 68 93 af 5f ec 99 50 8b d8 7f 03 08 05 8b 04   ö.h.._..P......ö
f5 a3 48 d5 bc 7b 7d 97 33 80 d8 c6 f6 85 57 90   ö..H..äå.3.....W.ö
4b 6a 8c 50 15 17 e4 e2 3a ca ce 7e 55 46 e4 5a   öKj.P....:..üUF.Zö
d4 d9 d5 eb 7d 70 a0 fb d6 a0 98 7d 3e 8f d9 72   ö....åp.....å>..rö
12 6d 08 55 71 41 2e 79 12 93 b3 9f 36 d4 72 5a   ö.m.UqA.y....6.rZö
68 a3 40 78 6b 9a 72 79 8c e9 6e 67 6c b9 cb ce   öh.@xk.ry..ngl...ö
9a 4b 4d ec f8 02 e7 f3 3e fa f8 f8 cd 2f 6e 7e   ö.KM.....>..../nüö
79 d3 c5 a0 96 4f 37 9d f1 46 3b 8e ea 1b 4d 13   öy....O7..F;...M.ö
7a fd 8d c9 ae fb 3e 4d 16 4c 7d e1 e9 63 a1 bc   öz.....>M.Lå..c..ö
eb 47 b5 bc 1d a2 5a de 0c ad 5a 5e 0f 83 5a 1e   ö.G....Z...ZÜ..Z.ö
c7 a0 96 d3 21 d8 ab 72 3a 16 77 5d 9e 4f a7 3c   ö....!..r:.wÅ.O.<ö
2f 2f 07 c1 ad ca e9 64 a7 55 39 9d eb b2 2a a7   ö//.....d.U9...*.ö
d3 1c 56 e5 b4 01 fb aa 9c 36 37 5e 95 d3 fe 9c   ö..V......67Ü....ö
ab 72 da b9 6e 55 4e fb 48 ad ca 69 57 96 f3 f2   ö.r..nUN.H..iW...ö
b2 af c0 aa 9c d6 e9 ae ca 69 85 dd aa 9c 96 b5   ö.........i......ö
ac ca 29 b9 7c 55 4e f9 9b ab 72 ca c2 5a 95 53   ö..).öUN...r..Z.Sö
8e c2 aa 9c a6 b4 56 e5 34 5a 7b 5e 5e 06 75 ce   ö......V.4ZäÜÜ.u.ö
cb 4b 1f ec bc bc b4 8d ce cb 0b 79 b0 fc 47 37   ö.K.........y..G7ö
ff 07 29 f1 43 c1                                 ö..).C.ö


Uno de los problemas de analizar pdf generados en la APN en Venezuela es que no lo generan a partir de la información que manejan directamente de las bases de datos, si no que usan una hoja de cálculo en este caso en windows y la de Microsoft Office, y generan el pdf con herramientas para windows. 

¿Será autentica la información que tiene el pdf luego de manipulación con varias herramientas?

En siguientes post seguiré probando distintas herramientas en Python para ver si logro hacer un pdfscraping decente del pdf de Cencoex.

Obtener la resolución de la pantalla desde Python.


Este artículo se basa en un artículo en inglés sobre el tema.

Desde la línea de comandos en Linux se puede ejecutar el comando xrandr como se muestra a continuación:

ernesto@grievous:~$ xrandr | grep ''
   1366x768      60.00+

La resolución es de 1366x768.

El script se muestra a continuación:

#!/usr/bin/env python

#Se importa el modulo subprocess
import subprocess

#Se define un par de variables con los comandos a pasar:
cmd = ['xrandr']
cmd2 = ['grep', '*']

#Se ejecuta el comando xrandr y luego se abre una tuberia.
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)

#Se ejecuta el segundo comando
p2 = subprocess.Popen(cmd2, stdin=p.stdout, stdout=subprocess.PIPE)

#Se cierra la salida estandar.
p.stdout.close()


#Obteccion de la resolucion
resolution_string, junk = p2.communicate()
resolution = resolution_string.split()[0]
width, height = resolution.split('x')
print width,height



Al ejecutar el script se obtiene:
./screencatch.py 
1366 768

Obtener resolución con gtk con el siguiente script:

#!/usr/bin/env python

#Se importa gtk
import gtk

#Se captura el ancho y alto 
width = gtk.gdk.screen_width()
height = gtk.gdk.screen_height()

#Se muestra en la consola el resultado
print width,height

Al ejecutar el script se obtiene:
 ./screencatch2.py 
1366 768

Obtener la resolución con PySide/PyQT con el siguiente script:

#!/usr/bin/env python
#Importar QtGui de PySide
#para solo PyQt se cambia a
#from PyQt4 import QtGui

from PySide import QtGui

#Se crea la instancia de la aplicacion
app = QtGui.QApplication([])
#Se captura la resolucion y se muestra  en la consola
screen_resolution = app.desktop().screenGeometry()
width, height = screen_resolution.width(), screen_resolution.height()

print width,height

El resultado de ejecutar el script  es:

./screencatch3.py 
1366 768


En el artículo mencionado se muestra como obtener la información con wxPython y en un enlace en el artículo como obtener la información desde windows y desde MacOS.


Usar mongodb en Django


En artículos anteriores se ha tocado el tema de  mongodb y el tema de Django.

En este caso se tocará el tema de crear un blog con Django usando mongodb, este artículo se basa en un artículo en Inglés del sistio Developers Work de IBM.

En este caso se tendrá un formulario para crear post, actualizarlos y borrarlos. Se usará la plantilla de material design para Django explicado en el artículo anterior.

Para poder desarrollar la aplicación en Django y con mongodb se tiene que instalar Django, mongodb y mongoengine a continuación se muestra la instalación a lo Debian:

apt-get install python-mongoengine mongodb-server mongodb-clients mongodb python3-pymongo python3-pymongo-ext python-django python3-django

Lo primero que se tiene que hacer es crear el proyecto blog.

django-admin startproject blog

Esto crea directorios y archivos como se muestra a continuación:

blog
├── blog
│   ├── init.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── manage.py


Ahora se crea la aplicación posts:
django-admin startapp posts 

Ahora se tiene la siguiente estructura de directorios y archivos: 
blog
├── blog
│   ├── init.py
│   ├── init.pyc
│   ├── settings.py
│   ├── settings.pyc
│   ├── urls.py
│   └── wsgi.py
├── manage.py
└── posts
    ├── admin.py
    ├── init.py
    ├── migrations
    │   └── init.py
    ├── models.py
    ├── tests.py
    └── views.py


Modificar el archivo blog/blog/settings.py para que refleje el uso de mongodb y la aplicación.

Es necesario agregar en las primeras líneas del settings.py el nombre de la base de datos para mongodb:
DBNAME = 'blongo'

Agregar en las aplicaciones posts y el soporte a material-design que se explico en artículo anterior.

INSTALLED_APPS = (
    'material',
    'material.admin',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'posts',
)

Se modifica también el settings.py para que procese el directorio donde estarán las plantillas:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates' )],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

Se crea los directorios templates y media: 
mkdir -p {templates,static}

Y en media se crea los directorios css, images y js:
mkdir -p static/{css,images,js}

Ahora la estructura de directorios queda de la siguiente manera:
blog
├── blog
│   ├── init.py
│   ├── init.pyc
│   ├── settings.py
│   ├── settings.pyc
│   ├── urls.py
│   └── wsgi.py
├── manage.py
├── posts
│   ├── admin.py
│   ├── init.py
│   ├── migrations
│   │   └── init.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── static
│   ├── css
│   ├── images
│   └── js
└── templates




En el directorio posts se modifica el archivo models.py con el siguiente contenido:
#Se importa models
from django.db import models

#Se importa mongoengine quien es el que facilita la conexión a mongodb
from mongoengine import *

#Se trae el nombre de la base de datos de mongodb de settings.py
from blog.settings import DBNAME

#Se conecta a la base de datos
connect(DBNAME)


#Se crea la tabla Posts (documento), con title, content y la fecha de publicación y/o actualización
class Post(Document):
    title = StringField(max_length=120, required=True)
    content = StringField(max_length=500, required=True)
    last_update = DateTimeField(required=True)


Se modifica el archivo blog/posts/views.py con el siguiente contenido (ahí se publica el formulario, la lista de posts, se actualiza cada post y se puede borrar cada post):

from django.shortcuts import render, render_to_response
from django.template import RequestContext

from models import Post

import datetime

#Se define index donde recibe como argumento request
def index(request):
    #SI el metodo es un post, se toma los datos del formulario y se guarda en 
    #mongodb
    if request.method == 'POST':
       # nuevo post
       title = request.POST['title']
       content = request.POST['content']

       post = Post(title=title)
       post.last_update = datetime.datetime.now()
       post.content = content
       post.save()
   #Si el metodo es get entonces se está cargando la página inicialmente, así que se publican 
   #los posts
    # se obtiene todos los posts de la base de datos
    posts = Post.objects
    return render_to_response('index.html', {'Posts': posts},context_instance=RequestContext(request))


#Actualizar un post
def update(request):
    #Captura el id del post
    id = eval("request." + request.method + "['id']")
    post = Post.objects(id=id)[0]
    #Si el metodo es post, se está actualizando la información del formulario
    if request.method == 'POST':
       
        # se actualiza los valores y se salva en mongodb
        post.title = request.POST['title']
        post.last_update = datetime.datetime.now()
        post.content = request.POST['content']
        post.save()
        template = 'index.html'
        params = {'Posts': Post.objects}
    #Si el metodo es GET se muestra la página incial update.html
    elif request.method == 'GET':
        template = 'update.html'
        params = {'post':post}

    return render_to_response(template, params, context_instance=RequestContext(request))

#Para borrar un posts
def delete(request):
    #Se toma el id del post
    id = eval("request." + request.method + "['id']")
    #Se pregunta si es POST, se le pasa el id del posts y se borra, se va a la página index.html
    if request.method == 'POST':
        post = Post.objects(id=id)[0]
        post.delete()
        template = 'index.html'
        params = {'Posts': Post.objects}
    #Si es un metodo get entonces se muestra la página delete.html
    elif request.method == 'GET':
        template = 'delete.html'
        params = { 'id': id }

    return render_to_response(template, params, context_instance=RequestContext(request))


 Ahora se mostrará las imágenes de los archivos base.html, index.html, update.html y delete.html.

En el archivo base.html se agregará las siguientes dos líneas las cuales permiten que se use el estilo y javascript a lo material design:
{% include 'material/includes/material_css.html' %}
{% include 'material/includes/material_js.html' %}



Los archivos index.html, update.html y delete.html tendrán 2 líneas, en una heredan el contenido  del archivo base.html y en la otra línea se carga material_form (material design):
{% extends 'base.html' %}
{% load material_form %}  

En el caso que se tenga un formulario se debe colocar después del token csrf la siguiente línea:
{% form form=form %}{% endform %}

Ya con eso se maneja los formularios con el estilo material design. 

A continuación se muestra la imagen del archivo index.html:


A continuación se muestra la imagen del archivo update.html:


A continuación se muestra la imagen del archivo delete.html:


Para que esto funcione falta modificar el archivo blog/blog/urls.py con los urls del index, update y delete:
from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'^$', 'posts.views.index'),
    url(r'^update/', 'posts.views.update'),
    url(r'^delete/', 'posts.views.delete'),
]


Se crea la base de datos administrativa con el comando migrate:
./manage.py migrate 
Operations to perform:
  Synchronize unmigrated apps: staticfiles, posts, material, messages, material_admin
  Apply all migrations: admin, contenttypes, auth, sessions
Synchronizing apps without migrations:
  Creating tables...
    Running deferred SQL...
  Installing custom SQL...
Running migrations:
  Rendering model states... DONE
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying sessions.0001_initial... OK


Se crea la cuenta administrativa:
./manage.py createsuperuser
Username (leave blank to use 'ecrespo'): ernesto
Email address: ecrespo@xxxx.xxx
Password: 
Password (again): 
Superuser created successfully.

Se ejecuta el servidor de django:
./manage.py runserver 
Performing system checks...

System check identified no issues (0 silenced).
May 14, 2015 - 20:52:27
Django version 1.8, using settings 'blog.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

A continuación se muestra una imagen de la aplicación mientras se llena el formulario:


Por último se muestra el post que se acaba de publicar:




Para más información pueden revisar los siguientes enlaces: