ACTUALIZADO:
Sobre error en la llamada a self.leer_plantilla()

txt = self.leer_plantilla(self.txt)
html = self.leer_plantilla(self.html)

¡Muy buenas Pythoneros! estamos aquí con la tercera entrega de Aprender Python Programando.
El desafío de hoy, no es apto para personas impresionables, pues ¡Bienvenid@s a un nuevo reto para corajud@s!

¿Qué haremos? Hoy agregaremos más funcionalidad a nuestro módulo Presupuesto.

  1. Diseñaremos una plantilla HTML y otra plantilla TXT para nuestro presupuesto, así, no solo se verá más agradable, sino que además, será más fácil modificarlo
  2. Comenzaremos a colocar una numeración incremental automática, a cada nuevo presupuesto que generemos
  3. Podremos elegir entre guardar el archivo en formato HTML o mostrarlo en pantalla
  4. Crearemos una lista de precios ¡Basta de agregar tantos datos manualmente!

Finalmente, nuestro programa se verá así:

Produciendo presupuestos en formato HTML, como el siguiente:

¿Cómo funcionará ahora?

Ahora, solo nos pedirá:

  1. Los datos de la empresa y la fecha del presupuesto (igual que antes)
  2. En vez de detallar el servicio y el importe, los obtendrá de una lista de precios, por lo cual, solo nos pedirá ingresar el código correspondiente al plan:
    0: Plan corporativo
    1: Plan personal
    2: Plan de mantenimiento
  3. Calculará automáticamente el número de presupuesto, y
  4. Finalmente nos preguntará si deseamos guardar el archivo:
    1. Si elegimos “S” (si), guardará un HTML y nos informará el nombre del archivo (que será el número de presupuesto con extensión .html)
    2. Si elegimos “N” (no), nos mostrará el presupuesto en pantalla como lo hacía hasta ahora
    3. Si nos equivocamos, y no elegimos ni “S” (si) ni “N” (no), nos avisará que ingresamos una opción incorrecta, y nos dará la oportunidad, de volver a elegir.

¡Comencemos!

Archivos necesarios

Para hacerte de los archivos necesarios, sigue los ítemes de esta lista, paso por paso:

  1. Crea una carpeta llamada capitulo3
    Aquí almacenaremos todos los archivos del programa
  2. Dentro de la carpeta “capitulo3”, crea una subcarpeta llamada presupuestos
    Aquí se guardarán todos los presupuestos generamos en formato HTML (los que decidamos guardar)
  3. Dentro de la carpeta “capitulo3”, crea otra subcarpeta llamada templates
    Aquí guardaremos la plantilla HTML y otra con formato solo texto (TXT)
  4. Dentro de la carpeta templates guarda los archivos template.html y template.txt
    Pulsa en cada archivo para descargarlos desde Launchpad. Estas serán, nuestras dos plantillas
  5. Dentro de la carpeta capitulo3, crea un archivo llamado contador.txt
    Será la guía que utilizaremos para calcular incrementalmente nuestros números de presupuesto.
  6. Abre el archivo contador.txt, edita el contenido (está vacío), escribe 100 y guarda los cambios
    Inicializamos el contador en 100. Nuestro primer presupuesto, obtendrá el número 101 y así sucesivamente de forma incremental.
  7. Dentro de la carpeta capitulo3, guarda el módulo presupuesto.py (modificado)
    Explicaremos todos los cambios nuestro módulo, a lo largo del capítulo.

Finalmente, la estructura de archivos y directorios, deberá verse así:

[-]capitulo3
|_ [+]presupuestos
|_ [-]templates
   |_ template.html
   |_ template.txt
|_ contador.txt
|_ presupuesto.py

La lista de precios: Listas, tuplas y diccionarios

Te voy a pedir que abras el módulo Presupuesto (presupuesto.py) y vayas a la línea 22:

    # Planes y servicios - Capítulo 3
    planes = ('corporativo', 'personal', 'mantenimiento')       # Tupla
    corporativo = ['Diseño Sitio Web corporativo', 7200]        # Lista
    personal = ['Diseño Sitio Web básico', 4500]                # Lista
    mantenimiento = ['Mantenimiento sitio Web (mensual)', 500]  # Lista
    lista_precios = {'corporativo':corporativo,
                     'personal':personal,
                     'mantenimiento':mantenimiento}             # Diccionario

Como verás, hemos agregado cuatro nuevas propiedades de clase a nuestro módulo. Pero, el tipo de datos, no es ninguno de los que ya hemos visto! Pertenecen a tres nuevos tipos de datos: tuplas, listas y diccionarios.

Tanto las tuplas, como listas y diccionarios, son una forma de almacenar varios datos diferentes, de diversos tipos (cadenas de texto, enteros, flotantes, booleanos…) en una misma variable.

El orden en el cual estos datos se especifican dentro de la variable, se denomina índice, teniendo el primer dato un índice 0 (cero), el siguiente 1, y así incrementalmente.

Veamos estos tres nuevos tipos de datos en detalle:

Tuplas

planes = ('corporativo', 'personal', 'mantenimiento')
  • El contenido de una tupla se escribe siempre entre paréntesis ( )

  • Admite cualquier tipo de dato:
    mi_tupla = (‘texto’, 100, 7.25, False, True)

  • Para acceder a uno de esos datos, se realiza por su número de índice: texto es índice 0; 100 es índice 1; 7.25 es índice 2; False es índice 3 y,True es índice 4

  • Para acceder a una variable por su número de índice, éste se escribe entre corchetes: print mi_tupla[2] imprimirá 7.25 mientras que print mi_tupla[4] imprimirá True

  • Los datos contenidos en una tupla no pueden modificarse.

En nuestro código, la tupla planes lo que está haciendo es almacenar el nombre de los tres tipos de planes que ofreceremos a nuestros clientes.

Listas

corporativo = ['Diseño Sitio Web corporativo', 7200]
personal = ['Diseño Sitio Web básico', 4500]
mantenimiento = ['Mantenimiento sitio Web (mensual)', 500]
  • El contenido de una lista se escribe siempre entre corchetes [ ]

  • Admite cualquier tipo de dato:
    mi_lista = [‘texto’, 100, 7.25, False, True]

  • Para acceder a uno de esos datos, se realiza por su número de índice al igual que con las tuplas: print mi_lista[2] imprimirá 7.25 mientras que print mi_lista[4] imprimirá True

  • A diferencia de las tuplas, los datos contenidos en una lista PUEDEN modificarse, accediendo a ellos por su número de índice:
    mi_lista[0] = 'otro contenido'

En nuestro código, hemos creado una lista para cada tipo de plan, donde el índice 0 (cero) será la descripción del servicio y el índice 1, el precio de ese servicio.

Diccionarios

lista_precios = {'corporativo':corporativo,
                 'personal':personal,
                 'mantenimiento':mantenimiento}
  • El contenido de un diccionario se escribe siempre entre llaves { }

  • Admite cualquier tipo de dato

  • Cada dato almacenado en un diccionario, debe llevar un nombre de clave antecediendo al dato:
    diccionario = {'nombre_de_clave':'texto', 
                   'numero_entero':100, 
                   'numero_flotante':7.25, 
                   'falso':False, 
                   'verdadero':True}

  • Para acceder a uno de esos datos, se realiza por su nombre de clave:
    print diccionario['numero_entero'] imprimirá 100 y si deseo modificar 100 por 125, escribo: diccionario[‘numero_entero’] = 125. Es decir que al igual que las listas, se pueden modificar los datos.

En nuestro código, hemos creado un diccionario para englobar el nombre de nuestros planes (que actuará como nombre de clave) y el valor de cada clave, será cada una de nuestras listas.

Para saber

Las tuplas, listas y diccionarios, admiten también como valores, otras tuplas, listas y/o diccionarios!

tupla1 = ('rosa', 'verde', 'rojo')
tupla2 = (tupla1, 'celeste')
tupla3 = ('hortensia', 'neomarica', 'rosa', 'jazmin')

lista1 = [tupla1, tupla2, 'negro', 'amarillo']
lista2 = [lista1, 'naranja']

diccionario1 = {'colores':lista2, 'plantas':tupla3}

gran_tupla = (diccionario1, 'y algo más!')

Hasta aquí vemos como acceder uno a uno a los datos de una tupla, lista o diccionario. Pero ¿qué sucede si queremos recorrerlos todos y no sabemos cuantos índices tiene? Para ello, utilizaremos una estructura de control llamada bucle for.

Una estructura de control es un bloque de código que permite tomar decisiones de manera dinámica, sobre código existente.

El bucle for

En nuestro código, la estructura de control que estamos implementado se denomina bucle for y es la que se encuentra representada por el siguiente bloque de código (líneas 44, 45 y 46):

for plan in self.planes: 
    texto_a_mostrar += '(%d)%s  ' % (codigo_plan, plan) 
    codigo_plan = codigo_plan+1

¿Por qué bucle? Porque es una acción que no se ejecuta solo una vez, sino que lo hará hasta que una condición deje de cumplirse.

¿Qué condición? la que restringe la ejecución de ese bucle. En nuestro caso, la condición estará delimitada por la cantidad de planes en nuestra tupla planes: for plan in self.planes:

Lo anterior puede leerse así:

[for] por
[plan] cada plan
[in] en self.planes
[:] hacer lo que sigue

Es decir que el condicionamiento (limitación) está dado por la cantidad de planes contenidos en la propiedad de clase, planes.

Luego, en cada iteración (es decir, cada repetición dentro del bucle) voy agregando texto a la variable texto_a_mostrar utilizando una cadena con comodines ¿los recuerdas? Utilizo el patrón d que indica que allí irá un dígito y el patrón s indicando que lo reemplazaré por una string, tal cual lo vimos en capítulos anteriores:

texto_a_mostrar += '(%d)%s  ' % (codigo_plan, plan)

Ese texto_a_mostrar es el que se utiliza luego en el raw_input() para preguntar qué tipo de plan ofrecer.

Pero el código de plan lo genero dinámicamente ¿Cómo? Antes del bucle for, inicializo la variable codigo_plan en cero:
codigo_plan = 0

En el bucle for, la voy incrementando en 1, con cada iteración:
codigo_plan = codigo_plan+1

¿Qué obtengo? El índice de cada plan dentro de la tupla:
Índice 0: corporativo
Índice 1: personal
Índice 2: mantenimiento

Y ¿de dónde surge la variable plan? se declara automáticamente en el bucle for:
for plan in self.planes:

Convirtiendo a enteros con int()

Siguiendo con el método anterior, destinado a la selección de planes, pido ingresar el código correspondiente al plan, que a la vez será el número de índice del plan, dentro de la tupla planes:

elegir_plan = raw_input(texto_a_mostrar)

raw_input() retorna el valor ingresado, como cadena de texto. Pero necesito utilizarlo como número de índice! entonces, convierto ese valor a entero, con otra función nativa de Python: int()

elegir_plan = int(elegir_plan) 

Accediendo a datos por su número de índice

Cuando ingresamos el código de plan (0, 1 ó 2) estamos ingresando un número de índice. Mediante:

self.plan = self.planes[elegir_plan]

Lo que estoy haciendo, es traer el “nombre” (valor) del tipo de plan almacenado en la tupla planes.

Los datos del servicio a cotizar como “descripción” y precio, los he guardado en el diccionario lista_precios. Recordemos que al diccionario se accede por nombre de clave. Éste, lo obtuve antes haciendo self.planes[elegir_plan].

Entonces, accedo al diccionario para traerme la lista, que contiene descripción del servicio e importe, utilizando como clave, el nombre del plan:

datos_servicio = self.lista_precios[self.planes[elegir_plan]]

Almaceno la lista correspondiente al plan en una ueva variable llamada datos_Servicios.Pero esta variable, es una lista. Entonces, para obtener el servicio, debo recurrir al índice 0:

self.servicio = datos_servicio[0]

Y para obtener el importe, al índice 1:

importe = datos_servicio[1]

Finalmente, formateo el importe con float():

self.importe = float(importe)

Chuleta #1

Con nuestra lista de precios hemos aprendido sobre:

Tuplas

Crear mi_tupla = ('texto', 100, 25.83, False)
Acceder print mi_tupla[1] # Imprime: 100
Modificar No se puede!

Listas

Crear mi_lista = ['texto', 100, 25.83, False]
Acceder print mi_lista[2] # Imprime: 25.83
Modificar mi_lista[0] = 'Otro valor'

Diccionarios:

Crear dict = {'clave':'dato', 'otra_clave':155}
Acceder print dict['otra_clave'] # Imprime: 155
Modificar dict['clave'] = 'Texto'

Recorrer listas, tuplas y diccionarios con bucle for
flores = ['rosa', 'jazmín', 'crisantemo']
for flor in flores:
    print 'Flor de ' + flor

Imprimirá:
Flor de rosa
Flor de jazmín
Flor de crisantemo

Convertir un literal a entero:
literal = '234'
int(literal)

En el siguiente capítulo veremos como logramos utilizar plantillas HTML para nuestros presupuesto y como fue el proceso para almacenarlos. Ahora, un desafío extra…

Nuevo reto

Mirando el método que se encuentra en la línea 72 de nuestro módulo Presupuesto:

    # Armar numero de presupuesto
    def armar_numero_presupuesto(self):
        contador = open('contador.txt', 'r+')   # Abro contador
        ultimo_num = int(contador.read())       # obtengo ultimo numero
        nuevo = ultimo_num+1                    # genero nuevo número
        contador.seek(0)                        # muevo cursor a byte 0
        nuevo = str(nuevo)                      # convierto numero a string
        contador.write(nuevo)                   # sobreescribo el número
        contador.close()                        # cierro contador
        self.numero_presupuesto = nuevo         # seteo el nuevo número

Leyendo los comentarios a la derecha de cada línea ¿Alguien se anima a explicar por qué luego de abrir un archivo, leerlo y antes de escribirlo se utiliza contador.seek(0) para “mover el cursor al byte 0”?

Vamos a ver esto de forma detallada en el siguiente capítulo, pero ¿quién se anima a tratar de deducirlo y explircarlo?