59 KiB
weight | title | date | draft | summary | resources | categories | tags | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
4 | Apuntes de py4web | 2022-03-30T18:41:45+0200 | false | Apuntes de py4web, creando webapps con Python |
|
|
|
{{< admonition type=warning title="Work in progress" open=true >}}
Apuntes de py4web, muy incompletos.
De momento son una copia descarada del curso de Luca de Alfaro (que está genial para aprender)
{{< /admonition >}}
{{< admonition type=info title="Referencias" open=true >}}
- El tutorial de Luca de Alfaro Pero mejor usa los videos de la siguiente referencia.
- El video-tutorial de Luca de Alfaro (muy bueno)
- El video-tutorial en Invidious
- Bulma CSS crash course by Traversy Media
{{< /admonition >}}
¿Que aplicación queremos crear?
Una aplicación de inventario que llamaremos Cornucopia.
Instalación de py4web
Clonando py4web desde github
-
Como de costumbre creamos un entorno virtual para trabajar. Con un entorno virtual independiente podremos instalar todas las bibliotecas python que queramos sin interferir con el python del sistema. Personalmente uso
pyenv
para gestionar los entornos virtuales en mi sistema por que me permite usar cualquier versión de Python. (Echa un ojo a los apuntes de python en este blog si quieres saber mas depyenv
)pyenv virtualenv 3.9.7 ve_py4web
-
Creamos un directorio de trabajo y asignamos el entorno virtual. Es decir que usando otra vez
pyenv
vamos a asociar el entorno virtual que creamos en el paso anterior a un directorio de nuestro disco (como entorno virtual local). Nuestro sistema, (gracias apyenv
sabrá que hay que activar el virtualenv local si estamos trabajando en este directorio o en cualquiera de sus descendientes. En otras palabras estamos activan el virtualenv automáticamente si estamos en algún directorio del proyecto.mkdir webdev cd webdev pyenv local ve_py4web myve # Esta macro se encarga de instalar lsp en mi entorno python
El alias
myve
en mi ordenador equivale a ejecutar las lineas de abajo. Es decir instala en nuestro nuevo entorno virtual algunas bibliotecas básicas para gestionar entornos virtuales e instalar otras bibliotecas. Además también instala el Language Server Protocol server, que nos valdrá para tener facilidades adicionales al editar ficheros de Python:pip install --upgrade pip setuptools wheel pipx virtualenv virtualenvwrapper pip install 'python-lsp-server[all]'
-
Clonamos el
py4web
:git clone https://github.com/web2py/py4web.git
-
Instalamos las dependencias de py4web, es decir las bibliotecas de python que el programa py4web necesita para funcionar.
cd py4web pip install -r ./requirements.txt
-
Ya estamos listos para arrancar nuestra nueva instancia de py4web, podemos comprobar que funciona con
./py4web.py version
. Para arrancar la aplicación, establecemos la password de administración y lanzamos la aplicación especificando en que directorio residen las aplicaciones.
./py4web.py set_password
./py4web.py run apps
Instalando py4web con pip
Seguimos las instrucciones del github de py4web, coincide con lo que comenta Luca de Alfaro en este segundo video.
pyenv virtualenv 3.11.4 ve_p4w_311
amd p4w_311
pyenv local ve_p4w_311
myve # esta macro se encarga de instalar LSP en mis entornos python
pip install --upgrade --no-cache-dir py4web
py4web setup apps # Instalar todas contestando a todo que si
py4web set_password # Configurar password de administración
py4web run apps # Arrancar el sistema
py4web -h # resumen de comandos
Siempre podemos añadir el scaffold_bulma como comentamos en el apartado anterior.
Preparando la primera app
Pasos a seguir:
-
Crear la app dentro de nuestro py4web
-
Decidir la base de datos
-
Decidir el tipo de session (fichero
settings.py
) -
Cambiar el
SESSION_SECRET_KEY
en el ficherosettings.py
{{< admonition type=tip title="login con username" open=true >}}Si queremos activar el uso de usernames (un nombre de usuario abreviado para el login) tenemos que cambiar la opción
auth.use_username
en el ficherocommon.py
.Si hacemos este cambio y usamos sqlite3 como base de datos py4web no será capaz de cambiar la estructura de la tabla "al vuelo". Tendremos que cambiarla a mano en la base de datos si ya está creada, o directamente borrar la base de datos y permitir que py4web la genere de nuevo. {{< /admonition >}}
-
Configurar el modelo de nuestra app (la estructura de la base de datos)
Crear la app dentro de nuestro py4web
En la instalación que hemos propuesto, tendremos py4web instalado en el directorio ..../webdev/py4web
, podemos crear una nueva aplicación desde el dashboard del propio py4web o crearla nosotros a mano en el directorio ..../webdev/py4web/apps
Tenemos varios templates para escoger a la hora de crear una nueva aplicación:
- minimal
-
es un template que contiene lo mínimo imprescindible para empezar
- scaffold
-
Un template creado por Maximo Di Pietro, mucho más completa que minimal. Basado en no.css un framework [CSS]^(Cascade Style Sheets) extremadamente simple.
- scaffold_bulma
-
Un template creado por Luca de Alfaro, basado en Bulma otro framework CSS (con la particularidad de que no necesita JavaScript). Este template no venía por defecto con py4web lo he descargado del Bitbucket de Luca de Alfaro.
Vamos a usar como punto de partida la propuesta de Luca De Alfaro, un Template basado en Bulma para la parte CSS. Podemos pasarle la dirección del github al Dashboard y crear una nueva aplicación o clonarlo con git con:
cd py4web/apps
git clone git@bitbucket.org:luca_de_alfaro/scaffold_bulma.git cornucopia
Con esto tendremos un nuevo directorio ..../webdev/py4web/apps/cornucopia
que contendrá nuestra nueva aplicación. Como queremos tener nuestra aplicación en nuestro propio git tenemos que cambiar la URL asociada a nuestra propia dirección (p.ej. <git@git.comacero.com:py4web/cornucopia.git>) para ello:
cd cornucopia
git remote -v
git remote set-url origin git@git.comacero.com:py4web/cornucopia.git
git remote -v
git push --set-upstream --all
Así conseguimos que el directorio ..../webdev/py4web
esté apuntando al git original de py4web (para poder actualizar fácilmente), y el directorio de nuestra app apuntando a nuestro propio git para tener controlada el desarrollo del software.
Estructura de nuestra aplicación
En el nuevo directorio de nuestra aplicación tenemos los siguientes subdirectorios:
tree -d cornucopia
cornucopia
├── databases
├── __pycache__
├── static
│ ├── css
│ ├── font-awesome-4.7.0
│ │ ├── css
│ │ ├── fonts
│ │ ├── less
│ │ └── scss
│ └── js
├── templates
├── translations
└── uploads
static
-
Almacena el contenido estático, aquí nos encontraremos imágenes (como logos por ejemplo), ficheros CSS y JavaScript.
databases
-
En este directorio se almacena la base de datos, se usa principalmente en el desarrollo con motores de base de datos como SQLite, en producción lo normal es usar motores de base de datos más potentes; como MariaDB o Postgresql que no almacenarán sus datos en este directorio.
templates
-
Aquí residen las plantillas de las páginas web de nuestra aplicación
translations
-
En este directorio tenemos los ficheros de I18n para nuestra aplicación.
uploads
-
Aquí se almacenan los contenidos de tipo
upload
que usemos en nuestra applicación (si es que los usamos, claro)
py4web va a cargar nuestra aplicación como un módulo Python. Por eso en el directorio cornucopia
tenemos un fichero __init__.py
Si vemos el contenido del fichero veremos que hace tres cosas:
- Carga el módulo general
py4web
- Importa la definición de
db
desde el ficheromodels.py
- Importa los controladores desde el fichero
controllers.py
En el fichero controllers.py
es donde definimos todas las rutas que tendrá nuestra aplicación. Un ejemplo chorras de ruta sería:
@action('sample-page')
@action.uses('spage.html')
def serve_sample_page():
return dict()
- El decorador
@action
define la ruta, es decir la URL asociada que en nuestro caso con el servidor local sería https://localhost:8000/cornucopia/sample-page - El decorador
@action.uses
define los recursos necesarios para esta nueva ruta, en este caso solo especificamos un html template que tiene que existir en el directoriocornucopia/templates
- Por último definimos la función que implementa el controlador de la ruta, es imprescindible que esta función devuelva un diccionario. El diccionario sirve para pasar valores al html template, pero en nuestro caso no son necesarios así que devolvemos un diccionario vacío.
Otros ficheros de la aplicación:
settings.py
-
contiene declaraciones de valores usados por la aplicación. Por ejemplo que base de datos usamos, etc. etc.
models.py
-
contiene las definiciones de las tablas de la base de datos
common.py
-
Define varias valores fundamentales para el funcionamiento de la aplicación. Entre otros:
- La conexión a la base de datos
- El tipo de sesión que usaremos
- El mecanismo de autenticación que usará la aplicación
El flujo de trabajo durante el desarrollo
Nos vamos a pasar la mayor parte del tiempo escribiendo controllers y templates para cada ruta que necesitemos en nuestra aplicación vamos a tener que codificar un controller y casi con toda seguridad su correspondiente template html
Para la parte de los templates vamos a usar un lenguage de templates: [YATL]^(Yet Another Template Language) (ver documentación de YATL)aunque py4web soporta también Renoir (aparentemente usa los dos tras las bambalinas y de forma transparente para nosotros)
{{< admonition type=tip title="Lenguajes de template" open=true >}}
Los lenguajes de template permiten definir plantillas de documentos (de cualquier tipo), donde se hacen operaciones de sustitución de parámetros o incluso secciones de código de programa, para generar el documento final.
El concepto ha demostrado ser tan potente que hay implementaciones en practicamente todos los lenguajes de programación (cuando no se implementa directamente en el propio lenguaje)
Hay muchos lenguajes de template para Python, probablemente Jinja sea el más conocido. En py4web se usa YATL.
{{< /admonition >}}
Decidir que motor de base de datos vamos a usar.
Por defecto py4web va a usar SQLite como motor de base de datos, pero soporta muchos más.
Podemos seguir con nuestro desarrollo en SQLite sin más. En ese caso puedes saltarte la instalación de MariaDB.
Para el caso de que alguien quiera hacer el desarrollo con un motor de base de datos más potente que SQLite vamos a describir como usar MariaDB (instrucciones también válidas para MySQL) para tener al menos dos opciones y por si alguien quiere experimentar con un motor de base de datos más potente que SQLite.
Yo voy a instalar MariaDB como un contenedor Docker para el desarrollo va perfecto.
Cuando se pase la aplicación a producción habrá que ver cual es la mejor opción. Se podría seguir con el servidor "dockerizado" o decidir si se quiere una instalación de MariaDB en el servidor real (el host si hablamos la jerga de contenedores) o incluso podría ser que tuviéramos un servidor de base de datos en nuestra red y quisieramos usarlo para nuestra aplicación. Hay muchas posibilidades.
Usando MariaDB como base de datos
{{< admonition type=tip title="MySQL" open=true >}}
Si queremos usar MySQL en lugar de MariaDB el procedimiento sería exactamente el mismo que el descrito.
{{< /admonition >}}
Como ya comenté, me voy a instalar la base de datos en Docker, prefiero tenerla lo más aislada posible en el portatil.
# Bajamos la imagen del hub de Docker
docker pull mariadb:10.7.3
# Lanzamos el servidor de base de datos:
docker run --detach --name my-mariadb --publish 3306:3306 \
--env MARIADB_USER=pyuser --env MARIADB_PASSWORD=secreto \
--env MARIADB_ROOT_PASSWORD=secreto mariadb:10.7.3
# Instalamos el cliente de mariadb en nuestro linux
sudo apt install mariadb-client
# Nos conectamos a la base de datos (para probar el acceso)
mariadb -h 127.0.0.1 -u pyuser -p
Ya tenemos un servidor de bases de datos MariaDB accesible desde nuestro PC. Al estar dockerizada tenemos que hacer todas las conexiones con la dirección IP, como si fuera un servidor independiente en nuestra red, aunque usamos la IP local: 127.0.0.1
como dirección del servidor MariaDB.
También tenemos que crear manualmente la base de datos que vamos a usar. Crearemos la base de datos cornucopiadb
y daremos todos los privilegios de acceso sobre esta base de datos al usuario pyuser
. El comando de conexion a la base de datos sería:
mariadb -h 127.0.0.1 -u root -p
Y para crear la base de datos y dar privilegios:
create database cornucopiadb;
grant all privileges on cornucopiadb.* to pyuser@'%';
flush privileges;
quit
Es decir:
- Creamos la base de datos
cornucopiadb
- Concedemos todos los privilegios al usario
pyuser
conectado desde cualquier IP a todas las tablas decornucopiadb
- Hacemos un
flush
para que los privilegios se activen de inmediato
El siguiente paso es instalar alguna biblioteca Python de conexión a la base de datos. Cualquiera de los conectores disponibles para MySQL debería funcionar correctamente con MariaDB. En nuestro caso vamos a instalar pymysql
. Nos aseguramos de tener activado el entorno virtual del proyecto e instalamos con pip
:
pyenv which pip
pip install pymysql
Sabiendo que biblioteca de conexión a MariaDB tenemos instalada ya podemos definir la conexión a la base de datos en nuestra aplicación py4web. Tenemos que editar el parámetro DB_URI
en el fichero settings.py
de nuestra aplicación.
El DB_URI
que viene configurado por defecto es el de sqlite3, en la documentación oficial podemos comprobar que necesitamos algo como: mysql://pyuser:secreto@127.0.0.1/cornucopiadb?set_encoding =utf8mb4
.
Una vez editado el fichero settings.py
y cambiado el DB_URI
podemos arrancar nuestra aplicación con ./py4web run apps
{{< admonition type=tip title="Otros motores de base de datos" open=true >}}
Los pasos descritos deberían ser muy parecidos al margen del motor de base de datos que usemos. Solo tendremos que instalar la biblioteca python adecuada a la base de datos elegida y ajustar el DB_URI
a ese motor de base de datos.
{{< /admonition >}}
Decidir el tipo de sesión y cambio del SESSION_SECRET_KEY
En el video-curso de Luca de Alfaro, nos explican cuales son las funciones de una session
, y por qué es imprescindible tener un mecanismo para gestionar sesiones de usuario en nuestra aplicación. Si no lo tienes claro mira los videos:
Editamos el fichero settings.py
de nuestra nueva aplicación y fijamos el tipo de sesión (recomendable usar database
)
Cambiamos el parámetro SESSION_SECRET_KEY
que viene por defecto en el template por uno nuevo. Podemos usar un generador de _uuid: como este.
Definiendo nuestro modelo
Una vez arrancado py4web podemos conectarnos a la base de datos (da igual que sea sqlite3 o MariaDB) y comprobar que py4web ha creado las tablas necesarias para la gestión de usuarios y sesiones. Podemos comprobarlo desde nuestro cliente de base de datos con el comando .tables
si usamos sqlite3 o con el comando show tables
si estamos usando MariaDB. También podemos comprobarlo desde el propio cuadro de mando del py4web.
Veremos que py4web ha creado las tablas:
auth_user
auth_user_tag_groups
py4web_session
Todas las tablas estarán vacías, puesto que aun no hemos creado usuarios de la aplicación (lo haremos más adelante). Pero ya vemos que py4web es capaz de ir creando nuevas tablas en la base de datos que hayamos definido. Así, a medida que vayamos definiendo el modelo de nuestra aplicación, veremos que automáticamente se crean (y/o modifican) las tablas correspondientes en nuestra base de datos.
Definiendo tablas
El objetivo de nuestra aplicación es mantener un inventario de "cosas". Parece lógico que nuestra primera tabla valga para almacenar "cosas". Así que en el fichero cornucopia/models.py
añadimos las siguientes lineas (en la sección indicada) y salvamos el fichero:
### Define your table below
#
# db.define_table('thing', Field('name'))
#
## always commit your models to avoid problems later
db.define_table('thing',
Field('id', 'integer'),
Field('name', 'string')
Field('description', 'string'),
migrate = True)
Ya tenemos creada nuestra nueva tabla thing
. La hemos definido de forma explícita con dos campos: id
y description
y un parámetro adicional: migrate = True
Lo cierto es que se recomienda no crear explicitamente el campo id
. py4web se va a encargar de crear siempre este campo para todas las tablas. Es un campo entero autoincremental (generalmente empezando por 1); esto quiere decir que cada vez que se inserta un nuevo registro en la tabla, la base de datos va a asignar un entero en el campo id
incrementando un contador interno de la tabla.
El parámetro migrate = True
hace que py4web intente mantener la definición de la base de datos real (en el motor de base de datos que estemos usando) alineada con el modelo que definamos en el fichero models.py
. Si cambiamos la definición, se ejecutarán los comandos de base de datos necesarios para cambiar la definición de la tabla en la base de datos.
En realidad se ha definido migrate = True
por defecto para todas las tablas en el fichero settings.py
así que la definición de nuestra tabla podria quedar commo:
Table('thing',
Field('name', 'string')
Field('descriptor', 'string'))
Si recargamos el fichero models.py
(desde el Dashboard), podremos comprobar que se ha creado la correspondiente tabla en la base de datos y podríamos crear algunos registros (lineas de la tabla) en ella a través del propio Dashboard de py4web.
Laa tabla es demasiado simple, evidentemente tenemos que mejorar nuestro modelo. Pero antes de profundizar vamos a echar un vistazo a los formularios.
El primer formulario
Un formulario simple
Los formularios (html forms) son una parte importantísima de nuestra aplicación. Gran parte de las interacciones con los usuarios se harán via formularios html.
Ya tenemos definida nuestra tabla thing
, vamos a ver como crear un formulario (html form) para añadir nuevos objetos thing
a nuestra base de datos.
En la parte del template definimos:
[[extend 'layout.html']]
<div class="section">
Add a new Thing
<div>
<form action="[[=URL('add') ]]" method="POST">
<div class="field">
<input class="input" name="thing_name" type="text" placeholder="Thing name here" />
<input class="button" name="" type="submit" value="Submit"/>
</div>
</form>
</div>
</div>
y en la parte del controller vamos a añadir una ruta add
:
@action('add', method=['GET', 'POST'])
@action.uses('add.html', db, auth.user)
def add():
if request.method == 'GET':
return dict()
else:
print(request.params.get("thing_name"))
db.thing.insert(name=request.params.get("thing_name"))
redirect(URL('add'))
El primer decorador de nuestro controller (en la primera línea) define la ruta asociada, que será <server_url>/<app_name>/add
, en nuestro caso podría quedar como http://127.0.0.1:8000/cornucopia/add
. Además especificamos que el controlador atiende peticiones (html requests) de tipo GET
y POST
.
El segundo decorador del controller (@action.uses...
en la segunda línea) define los fixtures asociados al controller:
- En primer lugar asocia el template a la ruta, en nuestro caso
add.html
. Es importante que sea el primero por que los que vengan a continuación a menudo inyectarán funciones en el template para que las podamos usar al generar el código html - Después un objeto
db
que encapsula la conexión a la base de datos. Esta conexión está definida en el ficherocommon.py
y en nuestro caso nos permite acceder a una base de datos en el servidor MariaDB. - Aunque no está declarado especificamente también se asigna el session fixture.
@ El auth fixture nos permite gestionar el login del usuario, los permisos del usuario, etc. Hay varias opciones para configurar el comportamiento de
auth
en el ficherocommon.py
. Al especificarauth.user
será necesario ser un usuario autenticado para acceder a esta ruta.
{{< admonition type=tip title="Fixtures" open=true >}}
Un objeto de clase Fixture
en py4web implementa los siguientes métodos:
on_request
que se invoca al recibir un html requeston_error
que se invoca si hay un error al procesar la requeston_success
que se invoca una vez procesada con éxito la requesttransform
que se invoca tras procesar la request para transformar la salida de la misma
Por ejemplo el DAL fixture:
on_request
: hace una reconexión con la base de datos (es configurable)on_error
: hace un rollback de la transacción pendiente en la base de datoson_success
: hace un commit de la transacción pendiente en la base de datos {{< /admonition >}}
Cuando visitamos la URL <server_url>/cornucopia/add
estamos haciendo un GET
así que nuestro controller devuelve un diccionario vacío, py4web renderiza el template y lo envía como respuesta al navegador del usuario que verá en su pantalla el formulario html.
Cuando el usuario hace un Submit con el botón correspondiente, lo que enviamos al servidor web es una POST request
, así que nuestro controller va a imprimir en la consola el nombre de nuestra thing y la va a insertar en la base de datos.
En el código podemos ver como consultar los parámetros del payload de una POST request
, py4web está basado en Bottle.py así que podemos consultar la documentación de Bottle para cualquier duda.
También podemos ver como se hace una inserción en la base de datos con ayuda del [DAL]^(Database Abstraction Layer) de py4web. Vamos a hacer un uso intensivo del DAL si quieres puedes echar un ojo a la documentación
De todas formas esto no es más que un ejemplo para ver un formulario básico, en realidad jamás los vamos a implementar así por que por un lado es inseguro, y por otro lado py4web nos ofrece facilidades mucho más potentes para hacerlos.
Un formulario usando las "facilidades" de py4web
El formulario simple que hemos definido en el punto anterior, además de dar bastante trabajo es inseguro cualquiera podría crear "cosas" en nuestra base de datos enviando POST Requests a nuestro servidor.
@action('add', method=['GET', 'POST'])
@action.uses('add.html', db, auth.user)
def add():
form = Form(db.thing, csrf_session=session, formstyle=FormStyleBulma)
if form.accepted:
# We simply redirect, the insertion already happened
redirect(URL('index'))
# This is a GET or a POST with errors
return(dict(form=form))
Este es nuestro formulario re-escrito. La parte importante es que ahora usamos Form()
(es imprescindible hacer un from py4web.utils.form import Form, FormStyleBulma
al principio de nuestro módulo controllers.py
)
Form()
genera el formulario html a partir de la definición de la tabla en la base de datos. A mayores le pasamos dos parámetros: uno para que genere un formulario con estilo "Bulma" y otro para que el formulario vaya protegido con una clave basada en la sesión del usuario.
Además Form()
nos ofrece un método para saber si el formulario ha sido recibido con datos válidos y aceptado. En ese caso simplemente dirigimos al usuario a la página principal (de momento).
El fichero de template quedaría tan simple como:
[[extend 'layout.html']]
<div class="section">
Add a new Thing
<div>
[[=form]]
</div>
</div>
Refinar la base de datos
Para comprobar como nuestro formulario se adapta automáticamente a la definición de la tabla en la base de datos vamos a añadir un par de campos a la definición. En el fichero models.py
vamos a añadir también una helper function:
def get_user_username():
"""Return user username if we have an auth_user."""
return auth.current_user.get('username') if auth.current_user else None
Esta función nos devuelve el username del usuario logueado en el servidor (si es que se ha logueado claro) En general todo el template Bulma que nos propone Luca De Alfaro se orienta a usar el correo del usuario como identidad del mismo (y tiene sus ventajas), pero a mi me gusta usar el login.
La nueva definición de la tabla queda:
db.define_table('thing',
Field('id', 'integer'),
Field('name', 'string', requires=IS_NOT_EMPTY()),
Field('description', 'string'),
Field('created_by', default=get_user_username),
Field('creation_date', 'datetime', default=get_time),
migrate=True)
Hemos añadido dos campos de tal manera que el propio py4web se va a encargar de poner el valor cuando creemos una nueva "cosa" usando las helper functions get_time
y get_username
que tenemos en el fichero models.py
.
También hemos especificado el parámetro migrate=True
, este es el valor por defecto así que no vamos a cambiar nada por especificarlo. El parámetro a True
hace que py4web cree o modifique las tablas en la base de datos cuando modifiquemos el fichero models.py
(ver documentación
Si ahora comprobamos nuestra aplicación en el py4web veremos que:
a) La estructura de la tabla en la base de datos se ha actualizado y ahora tenemos los dos campos nuevos
b) El formulario de la página add
también se ha actualizado para mostrar los nuevos campos a la hora de crear una nueva "cosa"
De todas formas los dos nuevos campos no deberían ser actualizables o iniciados por el usuario de la aplicación, es mejor reservarlos para que se inicien con los valores por defecto, para eso nos basta con cambiar el modelo y dejarlo así:
db.define_table('thing',
Field('id', 'integer'),
Field('name', 'string', requires=IS_NOT_EMPTY()),
Field('description', 'string'),
Field('created_by', default=get_user_username),
Field('creation_date', 'datetime', default=get_time),
migrate=True)
db.thing.id.readable = db.thing.id.writable = False
db.thing.created_by.readable = db.thing.created_by.writable = False
db.thing.creation_date.readable = db.thing.creation_date.writable = False
Con esto comprobaremos que al añadir una "cosa" ya no nos aparecen los campos en el formulario.
Listado de "cosas"
Vamos a añadir un listado de "cosas" a nuestra aplicación, de momento lo añadimos en la página principal (una chapuza, pero lo corregiremos)
Para empezar cambiamos el controller que se ocupa de la página principal:
@action('index')
@action.uses('index.html', db, auth)
def index():
rows = db(db.thing).select()
return dict(rows=rows)
Con esto hemos usado por primera vez el [DAL]^(Database Abstraction Layer) de py4web. En la línea 4 estamos haciendo un select de todas las "cosas"" que hay en la tabla things
. Y lo pasamos al template a través de la variable rows
.
En el template tenemos el siguiente código:
[[extend 'layout.html']]
<div class="section">
<div class="container">
<table class="table is-stripped is-fullwidth">
<tr>
<th>Id</th>
<th>Name</th>
<th>Description</th>
<th>Created by</th>
<th>Created on</th>
</tr>
[[for row in rows:]]
<tr>
<td>[[=row.id]]</td>
<td>[[=row.name]]</td>
<td>[[=row.description]]</td>
<td>[[=row.created_by]]</td>
<td>[[=row.creation_date]]</td>
</tr>
[[pass]]
</table>
</div>
</div>
Las líneas 1 ~ 12 definen una página web con una tabla (vamos a presentar una "cosa" por cada linea de la tabla)
En las lineas 13 ~ 21 tenemos un bucle for
que itera sobre todas las rows que hemos pasado al template generando una linea de la tabla para cada row
, es decir para cada "cosa" almacenada en la base de datos.
Esta tabla es bastante chorras y no nos vale de mucho. Vamos a darle un poco más de funcionalidad
Listado de "cosas" interactivo
Vamos a cambiar el código html para que en cada linea de la tabla (cada "cosa") tengamos un botón de edición y un botón de borrado.
[[extend 'layout.html']]
<div class="section">
<div class="container">
<table class="table is-stripped is-fullwidth">
<tr>
<th>Id</th>
<th>Name</th>
<th>Description</th>
<th>Created by</th>
<th>Created on</th>
<th></th>
<th></th>
</tr>
[[for row in rows:]]
<tr>
<td>[[=row.id]]</td>
<td>[[=row.name]]</td>
<td>[[=row.description]]</td>
<td>[[=row.created_by]]</td>
<td>[[=row.creation_date]]</td>
<td>
<a class="button" href="[[=URL('edit', row.id)]]">
<span class="icon"><i class="fa fa-fw fa-pencil"></i></span> <span>Edit</span>
</a>
</td>
<td>
<a class="button" href="[[=URL('delete', row.id, signer=url_signer)]]">
<span class="icon"><i class="fa fa-fw fa-trash"></i></span>
</a>
</td>
</tr>
[[pass]]
</table>
</div>
</div>
Hemos añadido un par de títulos de columna vacíos para que la tabla quede bonita, y en cada linea definimos los botones (lineas 22 ~ 32)
Para el botón Edit definimos un botón con un icono y un texto.
Para el botón Delete solo ponemos el icono de la papelera.
Evidentemente tendremos que definir los correspondientes controllers para las acciones de Edición y Borrado de los objetos "cosa" de nuestra base de datos. Pero hay que fijarse también en como generamos las URL asociadas a los botones:
Edición
La url la generamos con URL('edit', row.id)
eso nos va a generar una url de la forma: http://<host>:<port>/<appName>/edit/<id>
en nuestro caso sería algo como http://127.0.0.1:8000/cornucopia/edit/83
. Y la tradución sería: quiero editar la "cosa" con id=83
Para que el controller lea correctamente este URL tenemos que informar de las estructura del path que añadimos a la url. En nuestro caso el path es sencillamente /<thing_id>
pero podría ser mucho más largo y con más parámetros.
Nuestro controller sería:
@action('edit/<thing_id:int>', method=['GET', 'POST'])
@action.uses('edit.html', url_signer, db, session, auth.user)
def edit(thing_id=None):
assert(thing_id is not None)
# my_thing = db(db.thing.id == thing.id).select().first()
my_thing = db.thing[thing_id]
if my_thing is None:
# Nothing found to edit
redirect(URL('index'))
form = Form(db.thing, record=my_thing,
csrf_session=session, formstyle=FormStyleBulma)
if form.accepted:
# The update has been done
redirect(URL('index'))
return dict(form=form)
- En la línea 1 el decorador
@action
define la interpretación del path, comothing_id
que será un entero - En la línea 3, el controller toma el
thing_id
como parámetro, además si no nos lo pasan (hay que ser siempre paranoico) lo ponemos aNone
y provocamos un fallo en la línea 4 - Podríamos seleccionar nuestra "cosa" en la base de datos explicitamente como en la linea 5, pero esto se hace con tanta frecuencia que py4web nos ofrece una forma abreviada, que usamos en la linea 6
- Por fin, si no encontramos la "cosa" en la base de datos (siempre paranoicos), nos vamos a la página principal sin hacer nada. Si la encontramos generamos un formulario para editarla especificando el parámetro
record
en la llamada aForm()
- Las lineas 13 ~ 15 se encargan de redirigir a la página principal si tenemos un POST request y el formulario ha completado una edición
- En caso contrario, aun tenemos que preguntar al usuario que quiere editar así que pasamos el formulario al template y generamos la página de eición para el usuario.
Borrado
Para el botón de borrado la url generada es ligeramente diferente, usamos el comando: URL('delete', row.id, signer=url_signer)
, donde especificamos que la html request debe ir firmada.
La acción de borrado se va a ejecutar directamente en el controller en cuando el botón lance la request así que necesitamos asegurarnos de la autenticidad. La firma va a depender tanto de la sesión como del contenido de la propia request para que un atacante no pueda falsificar peticiones de borrado.
El controller nos quedaría como especificamos a continuación:
@action('delete/<thing_id:int>')
@action.uses(db, session, auth.user, url_signer.verify())
def delete(product_id=None):
assert product_id is not None
db(db.product.id == thing_id).delete()
redirect(URL('index'))
Vemos que simplemente procede al borrado de nuestra "cosa" en la base de datos y redirige de nuevo a la página principal. Ni siquiera necesitamos un template ya que no tenemos una página web dedicada a la acción de Borrar.
{{< admonition type=warning title="Apuntes de web2py" open=true >}}
Lo que sigue a continuación de este aviso son mis antiguos apuntes de web2py
{{< /admonition >}}
web2py
web2py es un framework para facilitar el desarrollo de aplicaciones web escrito en Python.
web2py funciona correctamente en Python 3. Su curva de aprendizaje no es tan empinada como la de Django (que es el framework de aplicaciones web de referencia en Python) y en muchos sentidos es más moderno que Django.
web2py tiene una documentación muy completa y actualizada (disponible también en castellano) y sobre todo una comunidad de usuarios y desarrolladores muy activa y que responden con rapidez a las dudas que puedas plantear.
web2py está basado en el modelo MVC
web2py incorpora Bootstrap 4
{{< admonition type=info title="Referencias web2py" open=false >}}
- Página de documentación y recursos de web2py, con enlaces a grupos de usuarios y tutoriales
- Evolución del modelo MVC
- Fat models and thin controllers
- Crítica del mantra
- Video tutorial en Python Weekly
- Web2py Tutorial by Terry Toy
{{< /admonition >}}
Empezar rápido
Instalación
Vamos a ver el proceso de instalación de una instancia de web2py en modo standalone. web2py instalado de esta forma es ideal para entornos de desarrollo. Para un entorno de producción puede ser más conveniente instalar web2py tras un servidor web como Apache o Nginx, pero dependiendo de la carga de trabajo y de como administres tus sistemas puede ser mejor opción usarlo standalone también en producción.
-
Creamos un entorno virtual
Como ya hemos comentado web2py funciona ya en Python 3. Y en cualquier caso, con Python nunca está de mas encapsular nuestras pruebas y desarrollos en un entorno virtual.^[Los siguientes comandos asumen que tienes instalado virtualenvwrapper como recomendamos en la guía de postinstalación de Linux Mint, si no lo tienes te recomendamos crear un virtualenv con los comandos tradicionales] Así que creamos el virtualenv que llamaremos web2py:
mkvirtualenv -p `which python3` web2py
-
Bajamos el programa de la web de Web2py y descomprimimos el framework:
# creamos un directorio (cambia el path a tu gusto) mkdir web2py_test cd web2py_test # bajamos el programa de la web y descomprimimos wget https://mdipierro.pythonanywhere.com/examples/static/web2py_src.zip # opcionalmente borramos el zip, aunque sería mejor guardarlo # por si queremos hacer nuevas instalaciones rm web2py_src.zip
-
Generamos certificados para el protocolo ssl:
Para usar con comodidad web2py conviene que nos generemos unos certificados para gestionar el ssl:
# nos movemos al directorio de web2py cd web2py openssl genrsa -out server.key 2048 openssl req -new -key server.key -out server.csr Country Name (2 letter code) [AU]:ES State or Province Name (full name) [Some-State]:A Coruna Locality Name (eg, city) []:A Coruna Organization Name (eg, company) [Internet Widgits Pty Ltd]:BricoLabs Organizational Unit Name (eg, section) []:Division de Hackeo Common Name (e.g. server FQDN or YOUR name) []:testServer@bricolabs.cc Email Address []:contacto@bricolabs.cc Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []:secret1t05 An optional company name[]:Asociacion BricoLabs
Y ahora ejecutamos:
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
-
Servidor de base de datos.
Para usar web2py es imprescindible tener acceso a un servidor de base de datos. Podemos usar MySQL o MariaDB por ejemplo. Pero para empezar rápidamente vamos a tirar de SQLite, un servidor fácil de instalar potente y versátil. Es importante usar la versión 3 que introduce grandes mejoras sobre el antiguo SQLite
sudo apt install sqlite3
-
Arrancamos el servidor:
Deberíamos tener los ficheros generados en el paso anterior:
server.key
,server.csr
yserver.crt
, en el directorio raiz de web2py. Podemos arrancar el servidor con los siguientes parámetros (recuerda activar el entorno virtual si no lo tienes activo):python web2py.py -a 'admin_password' -c server.crt -k server.key -i 0.0.0.0 -p 8000
Y ya podemos acceder nuestro server web2py, con nuestro navegador favorito, visitando la dirección https://localhost:8000
Y ahora si que ya tenemos todo listo para empezar a usar web2py. Ya podemos crear nuestra primera aplicación.
Los detalles tenebrosos (del arranque)
Si tienes mucha prisa por aprender web2py puedes saltarte esta sección e ir directamente a la sección siguiente
Si por el contrario quieres entender exactamente que hemos hecho para poder arrancar el web2py continuar leyendo puede ser el primer paso.
- ¿Qué es un virtualenv?
-
Python nos permite definir virtualenv. Un virtualenv es un entorno python aislado. Todos los virtualenvs están aislados entre si y mejor todavía son independientes del python del sistema. Esto te permite tener multiples entornos de desarrollo (o producción) cada uno con distintas versiones de python y diferentes librerias python instaladas en cada uno de ellos, o quizás diferentes versiones de las mismas librerias.
- ¿Que es virtualenvwrapper?
-
Es un frontend para usar virtualenv, la herramienta nativa de python para gestionar virtualenvs. Es completamente opcional, aunque a mi me parece muy cómoda.
- ¿Qué es todo eso de los certificados?
-
web2py viene preparado para usar https (estas siglas tienen varias interpretaciones: HTTP over TLS, HTTP over SSL o HTTP Secure). https usa comunicaciones cifradas entre tu navegador y el servidor web para garantizar dos cosas: que estás accediendo al auténtico servidor y que nadie este interceptando la comunicación entre navegador y servidor. En particular web2py exige que se use https para conectarse a las páginas de administración. Así que si no generas los certificados podrás arrancar y conectar con web2py pero no podrás hacer demasiadas cosas.
-
Para usar https hay que hacer varias cosas:
- Generar un CSR (Certificate Signing Request)
- Obtener con ese CSR un certificado SSL de una autoridad certificadora (CA)
- O alternativamente generar nosotros un certificado a partir del CSR
-
Lo que hemos hecho con los comandos openssl ha sido:
- Generar un par de claves (privada y pública) para nuestro servidor (
server.key
) - Generar con esa clave un CSR (el CSR lleva la información que le hemos metido de nuestro servidor y la clave pública)
- Generar un certificado firmándolo nosotros mismos con esa misma clave como si fueramos la autoridad certificadora.
- Generar un par de claves (privada y pública) para nuestro servidor (
-
Esto nos vale para arrancar web2py aunque nuestro navegador nos dará una alerta de riesgo de seguridad por que no reconoce a la CA.
- ¿Qué es un motor de base de datos?
-
web2py usa un motor (o gestor) de base de datos relacional. Puede usar muchos, incluyendo los más populares como por ejemplo MySQL, Postgres o MariaDB.
-
Las bases de datos relacionales se basan en relaciones. Las relaciones primarias son tablas que almacenan registros (filas) con atributos comunes (columnas). Las relaciones derivadas se establecen entre distintas tablas mediante consultas (queries) o vistas (views)
-
web2py te permite gestionar y utilizar las bases de datos a muy alto nivel, así que podras usarlo sin saber practicamente de bases de datos; pero no es demasiado difícil aprender los conceptos básicos y compensa ;-) Todo lo que puedas aprender de bases de datos te ayudará a hacer mejores aplicaciones web.
Nuestra primera aplicación
Vamos a crear nuestra primera aplicación en web2py.
Si has seguido los pasos de la sección anterior ya tienes el web2py funcionando y puedes seguir cualquiera de los tutoriales que hay en la red para aprender. El capítulo 3 del libro de web2py es muy recomendable, y está disponible en castellano, puedes ventilarte los ejemplos que trae explicados en una tarde y son muy ilustrativos.
En esta guía vamos a ver la creación de una aplicación paso a paso.
Crearemos una aplicación de inventario para el material de la Asociación BricoLabs, pero lo haremos de manera que también nos valga para uso particular y tener controladas todas nuestras cacharradas.
Este no es un tutorial de diseño profesional de aplicaciones, sólo pretendemos demostrar lo fácil que es iniciarse con web2py.
De hecho, no seguiremos un orden lógico en el diseño de la aplicación, si no que intentaremos seguir un orden que facilite conocer el framework.
Sin más rollo, vamos a comenzar con nuestra aplicación:
Crea una aplicación desde el interfaz de administración, en nuestro caso la llamaremos cornucopia.
Nuestro web2py "viene de serie" con algunas aplicaciones de ejemplo. La propia pantalla inicial es una de ellas la aplicación "Welcome" o "Bienvenido" (dependerá del lenguaje por defecto de tu navegador).
Para crear nuestra aplicación cornucopia:
- Vamos al botón admin en la pantalla principal.
- Metemos la password de administración (con la que hemos arrancado el web2py en la linea de comandos).
- Desde la ventana de administración creamos nuestra nueva aplicación
Inmediatamente nos encontraremos en la ventana de diseño de nuestra nueva aplicación. web2py nos permite diseñar completamente nuestra aplicación desde aquí, ni siquiera necesitaremos un editor de texto (aunque nada impide usar uno, desde luego).
private/appconfig.ini
El primer fichero que vamos a examinar es private/appconfig.ini
La sección private
debería estar abajo de todo en la ventana de diseño.
En la sección [app]
del fichero podemos configurar el nombre de la aplicación y los datos del desarrollador.
En la sección [db]
fichero configuramos el motor de base de datos que vamos a usar en nuestra aplicación. Por defecto viene configurado sqlite así que no vamos a tener que cambiar nada en este sentido.
En la seccion [smtp]
podemos configurar el gateway de correo que usará la aplicación para enviar correos a los usuarios. Por defecto viene viene la configuración para usar una cuenta de gmail como gateway, solo tenemos que cubrir los valores de usuario y password y la dirección de correo.^[Es aconsejable crear una cuenta de gmail, o cualquier otro servicio de correo que nos guste, para pruebas. Usar tu cuenta de correo personal podría ser muy mala idea]
El Modelo
En la parte superior de la ventana de diseño (o edición) de nuestra aplicación tenemos la sección Models
web2py se encarga de crear las tablas necesarias en la base de datos que le hayamos indicado que use.
Al crear la aplicación _web2py ha creado en la base de datos todas las tablas relacionadas con la gestión de usuarios y sus privilegios.
Si echamos un ojo al modelo gráfico (Graphs Models) veremos las tablas que web2py ha creado por defecto y las relaciones entre ellas. Estas tablas que ha creado el framework son las que se encargan de la gestión de usuarios, sus privilegios y el acceso de los mismos al sistema, es decir la capa de seguridad.
Si vemos el log de comandos de sql (sql.log) veremos los comandos que web2py ha ejecutado en el motor de base de datos.
Y por último si vemos database administration podremos ver las tablas creadas en la base de datos, e incluso crear nuevos registros en esas tablas (de momento no lo hagas)
También podemos echar un ojo al contenido del fichero db.py
o menu.py
pero por el momento no vamos a modificar nada en esos ficheros.
Ahora tenemos que ampliar el modelo y añadir todo lo que consideremos necesario para nuestra aplicación.
Diseñando el modelo
Build fat models and thin controllers es uno de los lemas del modelo MVC, no vamos a entrar en detalles de momento pero un modelo bien diseñado nos va a ahorrar muchísimo trabajo al construir la aplicación.
El diseño de bases de datos es una rama de la ingeniería en si mismo, hay camiones de libros escritos sobre el tema y todo tipo de herramientas para ayudar al diseñador. Pero nosotros nos vamos a centrar en usar sólo lo que nos ofrece web2py.
Además como estamos aprendiendo vamos a ver algunas facilidades que nos da web2py sin proponer ningún proceso de diseño del modelo (recuerda, esto no es un curso de diseño de aplicaciones)
Vamos a definir el modelo (concretamente las tablas) de nuestra aplicación en un nuevo fichero de la sección Models, que llamaremos db_custom
así que pulsamos en el botón Create, y creamos el fichero db_custom
.
web2py parsea todos los ficheros de la sección Models por
orden alfabético. Esto nos permite separar nuestro código del que
viene originalmente con la aplicación. Pero es importante que db.py
sea siempre el primero alfabeticamente para que se ejecute antes que
el resto.
web2py se encarga también de añadir la extensión .py
al nuevo
fichero que estamos creando así que teclea sólo el nombre db_custom
.
El objetivo de nuestra aplicación es mantener un inventario de
"cosas". Parece lógico que nuestra primera tabla valga para almacenar
"cosas". Así que en el fichero db_custom.py
añadimos las siguientes
lineas y salvamos el fichero:
db.define_table('thing',
Field('id', 'integer'),
Field('desc', 'string'),
migrate = True);
Tickets de error
Ya hemos salvado nuestro fichero, vamos a echar un ojo a nuestra base de datos con el botón Graph Model.
¡Tenemos un horror! ¿Qué ha pasado?. Si pinchamos en el link del Ticket se abrirá una nueva pestaña en nuestro navegador:
En el ticket tenemos mucha información acerca del error,
afortunadamente en este caso es facilito. El nombre del campo desc
que hemos añadido a nuestra tabla thing
es una palabra reservada en
todas las variedades de SQL (es el comando para ver la
definición de una tabla: desc tablename)
Editamos de nuevo nuestro fichero db_custom.py
y corregimos el
contenido:
db.define_table('thing',
Field('id', 'integer'),
Field('description', 'string'),
migrate = True);
¡Ahora si! Si pulsamos en el botón de Graph Model (después de salvar el nuevo contenido) veremos que web2py ha creado la nueva tabla en la base de datos. Incluso podríamos empezar a añadir filas (cosas) a nuestra tabla desde el database administration
El campo id
es casi obligatorio en todas las tablas que definamos
en web2py, siempre será un valor único para cada fila en una
tabla y se usará internamente como clave primaria. Podemos usar otros
campos como clave primaria pero de momento mantendremos las cosas
símples.
Además el campo id
se añade por defecto a la definición de una
tabla, aunque no lo especifiques en la definición. Si además tenemos
en cuenta que string
es el tipo por defecto si no especificas un
tipo, podríamos haber escrito nuestra tabla como:
db.define_table('thing',
Field('description'),
migrate = True);
Pero de momento vamos a dejar todas las definiciones explícitas para no liarnos.
Si visitas ahora la sección de administración de la base de datos puedes añadir algunas "cosas" a la nueva tabla.
Mejorando la tabla
Evidentemente nuestro modelo de "cosa" es demasiado simple, tenemos que añadirle nuevos atributos de distintos tipos para que sea funcional. Pero antes de ir a por todas vamos a ver algunas funciones que nos ofrece web2py para construir los Modelos.
Vamos a añadir algunos campos más de distintos tipos a nuestro modelo y verlos con un poco de calma.
db.define_table('thing',
Field('id', 'integer'),
Field('name', 'string'),
Field('description', 'string'),
Field('picture', 'upload'),
Field('created_on, 'datetime'),
migrate = True);
Hemos añadido a nuestra "cosa" un nombre (name), una foto (_picture), que seguro que nos será muy útil, y una fecha de creación (created_on) que será la fecha en que añadimos esta "cosa" concreta a nuestro inventario.
Si ahora volvemos al administrador de base de datos podemos comprobar que:
-
No hemos perdido las "cosas" que añadimos antes, web2py ha añadido las nuevas columnas pero ha conservado los valores de las antiguas.
-
Podemos editar las "cosas" que habíamos añadido sin mas que hacer click en el
id
-
Si queremos editar (o añadir) una "cosa", web2py nos ofrece un diálogo para subir la foto de nuestro objeto. Sabe que los atributos de tipo upload son fichero que subiremos al servidor.
De la misma forma nos ofrece un menú inteligente para añadir el campo
datetime
Este es el tipo de facilidades que ofrecen los frameworks para acelerar el trabajo de crear una aplicación.
Sigamos refinando nuestra definición de "cosa" añadiendo
características más sofisticadas nuestra tabla thing
:
db.define_table('thing',
Field('id', 'integer'),
Field('name', 'string',
required = True,
requires = IS_NOT_EMPTY(error_message='cannot be empty')),
Field('description', 'string'),
Field('qty', 'integer',
default=1,
label=T('Quantity')),
Field('picture', 'upload'),
Field('created_on', 'datetime'),
format='%(name)s',
migrate = True);
En la linea del name
hemos añadido un VALIDATOR
. Se trata de
funciones
auxiliares
que nos permiten comprobar multitud de condiciones y que son
extremadamente útiles (iremos viendo casos de uso). En este caso
exigimos que el campo name
no puede estar vacío y además
especificamos el mensaje de error que debe aparecer si sucede.
Hemos añadido un atributo qty
(cantidad), hemos especificado que
tenga un valor por defecto de una unidad, y además hemos especificado
el label
.
El label
se usará en los formularios en lugar del nombre del campo
en la base de datos. Si vamos a añadir una nueva "cosa" veremos que en
el formulario no aparece qty sino que nos pregunta Quantity.
Además, y esto es muy importante, hemos asignado el valor de la
etiqueta con la función T()
.
web2py incorpora un sistema completo de internacionalización. Al
usar la función T()
la cadena Quantity se ha añadido a todos los
diccionarios de traducción (si es que no estaba ya) y solo tenemos que
añadir la traducción en el diccionario correspondiente (p.ej. a
es.py
) para que funcione la i18n. Una vez añadida si el idioma por
defecto de nuestro navegador es el castellano, en el formulario
aparecerá "Cantidad" en lugar de Quantity.
Por último hemos añadido el format
a la definición de la tabla,
format
especifica que cuando nos refiramos a un objeto "cosa" se
represente por defecto con su atributo name
.
Relaciones entre tablas
Supongamos ahora que queremos tener registrado en nuestro inventario al proveedor de cada una de nuestras cosas. ¿cómo se hace eso?
Vamos a añadir los proveedores a nuestro modelo:
db.define_table('provider',
Field('id', 'integer'),
Field('name, 'string'),
Field('CIF', 'string'),
Field('email', 'string'),
Field('phone', 'string'),
migrate = True);
Y ahora, a la tabla thing
, le añadimos la referencia a proveedores:
db.define_table('thing',
Field('id', 'integer'),
Field('name', 'string',
required = True,
requires = IS_NOT_EMPTY(error_message='cannot be empty')),
Field('description', 'string'),
Field('qty', 'integer', default=1, label=T('Quantity')),
Field('picture', 'upload'),
Field('created_on', 'datetime'),
Field('provider_id', 'reference provider',
requires=IS_EMPTY_OR(IS_IN_DB(db, 'provider.id', '%(name)s'))),
format='%(name)s',
migrate = True);
Si ahora creamos un proveedor (o varios):
A la hora de crear una nueva "cosa" web2py se encargará de ofrecernos un desplegable para escoger el proveedor.
Detalles tenebrosos (del modelo inicial)
Si ya has leido el capítulo 3 del libro de web2py, los detalles tenebrosos serán menos tenebrosos para ti.
Lo primero que tenemos que comentar es que mientras hemos estado definiendo el modelo nos hemos pasado casi todo el tiempo usando el DAL que viene empaquetado con web2py.
El DAL (Database Abstraction Layer) es una biblioteca de alto nivel para tratar con bases de datos relacionales. Y podemos usarla por separado en cualquier proyecto que queramos sin tener que usar web2py.
En segundo lugar, merece la pena subrayar que hay dos tipos de restricciones que podemos aplicar a los atributos (o campos) de una tabla.
Cuando usamos default=1
, o required=True
, este tipo de directivas
se traducen directamente en la definición de la tabla en el motor de
bases de datos.
Si marcásemos el campo name
como required=True
e intentásemos
crear un registro con el campo vacío, python nos daría una excepción
de base de datos. El motor de base de datos sería el que diera el
error, por que se violaría la estrutura de tabla que tiene definida.
En cambio los VALIDATORS funcionan de una forma totalmente distinta. Se aplican a nivel de formulario, las comprobaciones y el error (si procede) se harían en web2py, no sería la base de datos la que daría el error.
Por último, quizás hayais notado que hay un fallo grave (deliberado)
en el modelo propuesto como ejemplo. Está claro que una cosa puede ser
comprada a varios proveedores. Y un proveedor puede vendernos muchas
cosas. Eso significa que entre las tablas thing
y provider
tenemos
una relación n:n (many to many relationship), varias cosas pueden
estar relacionadas con varios proveedores. Este tipo de relaciones
siempre son un problema en las bases de datos relacionales y más
adelante veremos como implementarlas correctamente. De momento nos
quedamos con el modelo simplificado.
Checklist
Hay que editar private/appconfig.ini
:
[app]
name = cornucopia
author = salvari <salvari@protonmail.com>
description = una aplicación de inventario
keywords = web2py, python, framework, bricolabs
generator = Web2py Web Framework
production = false
toolbar = false
Secciones en el futuro
web2py y git
Instalación con nginx
Certificados let's encrypt
Notas sueltas de Bases de Datos
Truncar tablas en MariaDB
Los comandos de TRUNCATE
se tratan internamente como un borrado y creado de la tabla.
Cuando tenemos tablas con relaciones tenemos dos caminos posibles:
Borrar toda la tabla y resetear el contador:
DELETE FROM COUNTRY;
ALTER TABLE COUNTRY AUTO_INCREMENT = 1;
Desactivar los chequeos y volverlos a activar al final (ojito que no estoy seguro de que la desactivación sea por sesión)
SET FOREIGN_KEY_CHECKS = 0;
TRUNCATE TABLE COUNTRY;
TRUNCATE TABLE STATE;
SET FOREIGN_KEY_CHECKS = 1;