This is an old revision of the document!
Table of Contents
Armar la Aplicación
Objetivo
La idea de la aplicación es tener una interfaz HTML donde el usuario puede ingresar algo (como en un “textbox” por ejemplo), y dada esta entrada, la aplicación crea una consulta SQL, ejecuta la consulta sobre la base de datos, y despliega los resultados en HTML para el usuario.
- Como fue mencionado antes, hay que tener al menos tres consultas demostrando una mezcla de rasgos de SQL, es decir, joins, consultas anidadas, agregación, etc.
- No es necesario tener todos los rasgos en todas las consultas. La idea es que se demuestren los rasgos en alguna consulta. Se puede empezar con una consulta simple.
- Es importante usar indices, vistas, etc., para optimizar las consultas.
- Si uno quiere usar una vista, y si la complejidad está en la vista (es decir, si la vista maneja la agregación o los joins, etc.), está bien tener una consulta más simple. O sea, si hay una vista con los rasgos mencionados, eso también cuenta.
- Se pueden armar varias interfaces (una página diferente para cada consulta) o una interfaz con varios formularios (una página para todas las consultas).
- Es importante usar métodos seguros, especialmente contra inyección.
Este paso es más complejo que los otros, por lo que les aconsejamos que dejen bastante tiempo para completarlo en vez de hacerlo al último minuto. No importa qué software se use para armar la aplicación pero aquí daremos algunas opciones. Puede ser que haya mejores opciones y si ustedes quieren usar otra opción no hay problema (pero puede ser que no podamos ofrecerles apoyo … dependerá del software).
Seguridad
Independientemente de la opción que se elija, la seguridad de la aplicación será parte de la evaluación del proyecto, así que hay que:
- Usar sentencias precompiladas.
- Crear un usuario para la aplicación que tenga solo el acceso mínimo requerido (si es que no se usa Django).
Con respecto al último punto, la idea es no usar un superuser cc3201
en la aplicación con acceso a todo, sino crear un usuario nuevo (p.ej. webuser
) con una contraseña (ojo: será más fácil si se evitan espacios y “caracteres especiales” y si se usan sólo caracteres alfanuméricos) y permitirle solo acceso de lectura a las tablas pertinentes con GRANT
, p.ej.:
CREATE USER webuser WITH PASSWORD 'contrasena'; GRANT CONNECT ON DATABASE cc3201 TO webuser; GRANT USAGE ON SCHEMA proyecto TO webuser; ... GRANT SELECT ON proyecto.tabla TO webuser;
… y después usar webuser
y su contraseña en la aplicación. Hay más información sobre GRANT aquí.
Hay que tener cuidado con consultas como:
SELECT * FROM tabla WHERE col = 'val';
En particular, esta consulta no ha especificado el esquema, así que Postgres usará el SEARCH_PATH
del usuario para identificar el primer esquema con la relación tabla
. Pero webuser
puede tener un SEARCH_PATH
diferente al SEARCH_PATH
de cc3201
. Una solución sería definir bien el SEARCH_PATH
de webuser
, pero ojo que si cambia el SEARCH_PATH
en el futuro, la aplicación puede dejar de funcionar. Si una consulta va a ser usada durante mucho tiempo (por ejemplo, está codificada en una aplicación), la mejor solución es siempre especificar los esquemas de las tablas (o de las vistas, etc.) referenciadas:
SELECT * FROM esquema.tabla WHERE col = 'val';
Así funcionará ante cambios al SEARCH_PATH
del usuario de la aplicación.
Servidor Web
Hay que eligir una sola opción aquí.
La primera opción (PHP) es de un nivel más bajo, y uno puede empezar rápidamente con ejemplos simples usando PHP.
La segunda opción (Django+Python) es un ejemplo de framework Model-View-Controller (MVC), el uso del cual es popular hoy en día. Ofrece una abstracción de nivel más alto, pero hay que aprender y configurar mucho.
La tercera opción (Java Servlets) queda un poco desactualizada, pero todavía hay varios sistemas legados que las usan. Se recomienda en el caso de realmente querer programar en Java.
Se puede eligir otra opción también. El curso no se trata del desarrollo de aplicaciones web, así que no importa qué opción se prefiere con respecto a la evaluación final (solo importan el resultado final y la seguridad).
Nota 1: Durante el tutorial se pedirá usar:
sudo apt-get update
Lo que podría arrojar el siguiente error:
E: Repository '...' changed its 'Suite' value from 'testing' to 'stable' N: This must be accepted explicitly before updates for this repository can be applied. See apt-secure(8) manpage for details.
De ser el caso, ejecutar en cambio:
sudo apt update
… aceptando cada vez que pida confirmación. Esto es necesario hacerlo solo una vez. Fuente
Nota 2: Si al intentar actualizar o ejecutar algo sale este error:
E: You don't have enough free space in /var/cache/apt/archives/.
… hay que liberar el cache de los paquetes en el servidor:
ls -lh /var/cache/apt/archives/ sudo apt-get clean ls -lh /var/cache/apt/archives/
… donde las ejecuciones de ls
son para confirmar que se desocupo espacio. Apt Docs
Opción 1: Apache2 + PHP
- PRO: más fácil de instalar y se puede empezar rápidamente con algunos ejemplos.
- CON: hay que programar (un poco) en PHP.
Apache2 es un servidor web. PHP es un scripting language que podemos mezclar con HTML. Armaremos Apache2 y crearemos la aplicación después con PHP.
Instalar apache2
Para instalar Apache2:
sudo apt-get update sudo apt-get install apache2
Para verificar que funcione, se puede ir a (siendo XX
el número de la máquina):
http://grupoXX.cc3201.dcc.uchile.cl/
Apache Web Server (apache2) interpretará las peticiones de HTTP y las traducirá a operaciones locales en el servidor. Con apache2 se pueden servir páginas estáticas en HTML, pero para crear la aplicación, claramente necesitaremos algo dinámico. Hay varias opciones pero se describirá una opción usando PHP.
Instalar PHP
PHP (PHP: Hypertext Preprocessor) es un lenguaje para crear scripts que se ejecutan en el servidor (al contrario de Javascript, por ejemplo, que se ejecuta normalmente en el lado del cliente).
Para instalar PHP con soporte para apache2 y postgres:
sudo apt-get install php7.4 libapache2-mod-php7.4 php7.4-pgsql
Ahora, tenemos que reiniciar Apache:
sudo /etc/init.d/apache2 restart
Ejemplos
Ejemplo estático
Podemos probar la funcionalidad de PHP con algo simple:
sudo nano /var/www/html/info.php
… y poner:
<?php phpinfo(); ?>
… y guardar.
Ir aquí para verificar que funcione:
http://grupoXX.cc3201.dcc.uchile.cl/info.php
Con un formulario HTML
Crearemos una prueba que acepte un string de entrada del usuario.
Empezaremos con una página HTML estática (p.ej., hello.html
):
sudo nano /var/www/html/hello.html
<html> <body> <form action="print.php" method="get"> Ingrese mensaje:<br> <input type="text" name="msg"></br> <input type="submit" value="Submit"> </form> </body> </html>
Este formulario le mandará a print.php
un atributo con el nombre msg
y un valor que sea la entrada del usuario; el formulario usará HTTP get
para mandar la información. Entonces tenemos que dar el código en print.php
:
sudo nano /var/www/html/print.php
<?php $name = $_GET['msg']; echo "El mensaje es: $name"; ?>
Con conexión a la base de datos
Esta vez, un ejemplo que haga una consulta a la base de datos.
sudo nano /var/www/html/pg.php
<?php try { $pdo = new PDO('pgsql:host=localhost;port=5432;dbname=cc3201;user=webuser;password=holaprueba'); echo "PDO connection object created"; $stmt = $pdo->query('SELECT * FROM test.hello'); while ($row = $stmt->fetch()) { print_r($row); } } catch(PDOException $e) { echo $e->getMessage(); } ?>
… hay que cambiar el user y el password en $pdo = new PDO( … )
y usar una consulta que funcione (o crear una tabla test.hello
).
El ejemplo estará aquí:
http://grupoXX.cc3201.dcc.uchile.cl/pg.php
Si funciona, podrán adaptar el ejemplo para crear su aplicación. Hay un tutorial aquí que puede ser útil.
OJO: ¡Es importante usar métodos seguros contra inyección! El ejemplo acá es solo una prueba, no un ejemplo a seguir. HINT: Hay un ejemplo seguro indicado en el lab 5.
Debugear / Logs
Si tu aplicación PHP no está funcionado, y en particular si ves “HTTP ERROR 500
” no entres en pánico. Es que hay un bug en tu código. Primero deberías revisar el siguiente log de apache2 que lista los errores que ha encontrado:
sudo more /var/log/apache2/error.log
Al final del archivo habrá los errores más recientes. Puedes usar la barra espaciadora para avanzar en el archivo. El error debería proveer más información para resolver el problema.
Si el error no es de Apache2 + PHP, puede tratarse de Postgres. Para ver los logs hay que abrir:
sudo more /var/log/postgresql/postgresql-11-main.log
Y ver al final del archivo el posible error.
Opción 2: Django (Python)
- PRO: se puede programar en Python
- PRO: Django es un framework más moderno que se usa bastante en la práctica actual
- PRO: da una abstracción de todo en Python
- CON: hay que programar en Python
- CON: difícil de configurar inicialmente
- CON: mucho más “pesado” e “indirecto” que PHP en particular
La idea de Django es proveer una abstracción Modelo/Vista/Controlador de una aplicación Web en Python, donde el modelo captura los datos (p.ej. SQL) y la lógica (p.ej. python), la vista representa la información de salida que hay que desplegar para el usuario (p.ej. HTML), y el controlador acepta la entrada del usuario y la convierte en modificaciones al modelo (p.ej. para actualizar la base de datos desde una petición en HTML).
Django tiene un costo inicial muy alto, pero después uno tendrá una abstracción más limpia, la cual es útil, en particular, para aplicaciones complejas. Si ustedes quieren hacer algo más simple y rápido, PHP sería la mejora opción. Si ustedes quieren aprender algo de un framework usado bastante en la práctica, Django es una buena opción PERO aquí sólo se dará la información suficiente para instalar y configurar una aplicación básica. No será un tutorial completo (el enfoque del curso es bases de datos, no aplicaciones web). Aquí puede encontrar más información útil: desde tutoriales hasta documentación: Django docs.
Instalar Python
Instalaremos Django con Python3. Para empezar, instalaremos Python3:
sudo apt-get update sudo apt-get install python3
Podemos ver la versión:
python3 -V
Considerar que la versión de Python 3 instalada puede variar, en este caso se trabajó con la versión 3.7
.
(Por defecto, python
usa python2
porque algunas dependencias del sistema operativo asumen eso y no son compatibles con python3. Para ejecutar python3
, hay que usar el comando python3
.)
Instalar Django
Usaremos PIP (un instalador para packages de Python) para instalar la versión más reciente de Django para python3.
sudo apt-get install python3-pip
Ahora, instalamos Django usando pip3
:
sudo pip3 install django
También tenemos que instalar soporte para Postgres:
sudo apt-get install libpq-dev python-dev sudo pip3 install psycopg2
Para probar la instalación, podemos ver la versión:
django-admin --version
En este tutorial se instalo la versión 3.0.7
. Si sale el error:
django-admin.py Command no found
Tenemos que darle el camino correcto al administrador de django
:
sudo vi /usr/bin/django-admin
… y cambiar el camino aquí:
if command -v python3 > /dev/null && test -e /usr/lib/python3/dist-packages/django/bin/django-admin.py
… por:
if command -v python3 > /dev/null && test -e /usr/local/lib/python3.7/dist-packages/django/bin/django-admin.py
(agregando local
y .7
o la versión de python que corresponda).
Crear un proyecto
Ahora que tenemos todo instalado, podemos crear un proyecto inicial en Django. Crearemos una carpeta para el proyecto:
mkdir /home/cc3201/django cd /home/cc3201/django
Y usaremos el administrador para crear un proyecto nuevo en la carpeta:
django-admin startproject cc3201
… donde cc3201
es algún nombre para el proyecto. El proyecto está en la carpeta del proyecto:
cd /home/cc3201/django/cc3201/ ls
Tenemos que hacer una migración:
sudo python3 /home/cc3201/django/cc3201/manage.py migrate
Iniciar el servidor de desarrollo
(Dos cosas que haremos aquí que no se deberían hacer en un sistema de producción: (1) Django tiene un servidor web integrado para probar las aplicaciones. Usaremos eso porque es más conveniente pero en general, Django no es un servidor web maduro, y en la producción, sería mejor usar un servidor web como apache2 o nginx. (2) Corremos Django en el puerto 80 y por eso, tendremos que usar sudo
; en general, no es una buena idea correr un servidor web con sudo
pero está bien aquí.)
Ahora, podemos intentar iniciar un servidor de desarrollo en el puerto 80:
sudo python3 /home/cc3201/django/cc3201/manage.py runserver 0.0.0.0:80
(Si hay un error “That port is already in use
”, hay que deshabiliar apache2 o tomcat7 …)
sudo service apache2 stop sudo service tomcat7 stop
(… otra posibilidad es que haya un proceso previo de Django activo, en cuyo caso, hay que ver las instrucciones de abajo para matar Django.)
Allowed Hosts
Para verificar que funcione, en su navegador web favorito vaya a:
http://grupoXX.cc3201.dcc.uchile.cl/
(donde hay que reemplazar XX
por el número de la máquina)
Pero dará una excepción sobre:
DisallowedHost at / Invalid HTTP_HOST header: 'grupoXX.cc3201.dcc.uchile.cl'. You may need to add 'grupoXX.cc3201.dcc.uchile.cl' to ALLOWED_HOSTS.
Para resolver eso, tenemos que modificar la configuración:
vi /home/cc3201/django/cc3201/cc3201/settings.py
(Muchos cc3201, ya sé.)
Tenemos que reemplazar:
ALLOWED_HOSTS = []
por
ALLOWED_HOSTS = ['grupoXX.cc3201.dcc.uchile.cl','localhost', '127.0.0.1']
… y guardar el archivo.
Ahora, la página debería mostrar algo más interesante:
http://grupoXX.cc3201.dcc.uchile.cl/
Cuando esté funcionando, se puede matar el servidor por el momento con Ctrl
+ c
.
Mantener el servidor activo de fondo
El problema que tenemos ahora es que hay que mantener el servidor activo, entonces para ejecutarlo de fondo (y no tener que mantener la sesión abierta todo el tiempo), vamos a instalar la herramienta screen
(muy muy útil y mucho mejor que usar &
) con la cual podemos crear una sesión de SSH “virtual” de fondo:
sudo apt-get install screen
Cuando esté listo, podemos poner:
screen
Y con “Enter”, podemos ver una sesión virtual nueva. Aquí podemos ejecutar cualquiera cosa y después regresar a la sesión normal. Ahora, en la screen, vamos a iniciar el servidor:
sudo python3 /home/cc3201/django/cc3201/manage.py runserver 0.0.0.0:80
Ahora podemos salir de la screen con Ctrl
+ a
, y después d
. Estaremos en la sesión primaria de nuevo. La screen todavía está corriendo de fondo. Si queremos regresar a la screen, tenemos que poner:
screen -d -r
Aquí, podemos matar el proceso de Django si queremos.
Matar el servidor de Django
Una opción es ir al screen
y matar el proceso con Ctrl
+ c
.
Otra opción es encontrar el identificador del proceso (PID) con:
sudo ps ax | grep -e runserver
Para terminar el proceso, hay que poner:
sudo kill -9 24923
Donde 24923 es un ejemplo de PID. Hay que matar todos los procesos que dicen runserver
(aparte de ps
).
Interfaz de administrador
Si quieren usar la interfaz de administrador de Django, hay que poner:
sudo python3 /home/cc3201/django/cc3201/manage.py createsuperuser
… e ingresar la información pedida. Después aquí se encontrará la interfaz:
http://grupoXX.cc3201.dcc.uchile.cl/admin/
Configurar archivos estáticos y templates
Cuando se desarrolla una aplicación web se manejan recursos estáticos como logos, videos o archivos CSS los cuales se agregan directamente a los templates (o plantillas) HTML generados. En el caso de Django hay que señalar donde estarán dichos recursos. Vamos a configurar una carpeta para los archivos estáticos y otra para los templates.
Para empezar, hay que configurar el settings.py
para encontrar archivos estáticos:
vi /home/cc3201/django/cc3201/cc3201/settings.py
Vamos a agregar a esta parte:
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], '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', ], }, }, ]
… la ruta de la carpeta templates
…
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', ], }, }, ]
… en el caso de la carpeta static
, vamos a agregar lo siguiente al final del archivo:
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') STATICFILES_DIRS = [ os.path.join(BASE_DIR, "static/"), ]
Pueden guardar el archivo ahora. Ahora cambiaremos la parte urls.py
para servir archivos estáticos y usar una página de entrada personalizada.
vi /home/cc3201/django/cc3201/cc3201/urls.py
… y poner el código aquí (reemplazando lo que está)
"""cc3201 URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/1.11/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.conf.urls import url, include 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) """ import os from django.conf import settings from django.conf.urls.static import static from django.contrib import admin from django.urls import path from django.views.generic.base import TemplateView urlpatterns = [ path('admin/', admin.site.urls), path('', TemplateView.as_view(template_name='index.html'), name='home'), ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
Bueno. Ahora tenemos que crear dos carpetas locales en el servidor donde guardaremos todos los archivos estáticos (css, imágenes, etc.) y templates:
mkdir /home/cc3201/django/cc3201/static/ mkdir /home/cc3201/django/cc3201/templates/
(Son la mismas ubicaciones que las agregadas en settings.py
.)
python3 /home/cc3201/django/cc3201/manage.py collectstatic
Ahora crearemos una página de entrada simple:
vi /home/cc3201/django/cc3201/templates/index.html
… y poner:
<!DOCTYPE html> <html lang="en"> <head> <title>CC3201 Project Homepage</title> </head> <body> <h1> Static homepage </h1> <p>Eso nomas.</p> </body> </html>
Junto con un archivo de estilos simple:
vi /home/cc3201/django/cc3201/static/style.css
… y poner:
body { background-color: gray; } h1 { color: blue; } p { color: green; }
… y guardar ambos archivos.
Ahora, se debería poder ver la página de entrada:
http://grupoXX.cc3201.dcc.uchile.cl/
Sin embargo, no se ven los colores puestos en el archivo de estilo. Necesitamos cargar el archivo style.css
:
{% load static %} <!DOCTYPE html> <html lang="en"> <head> <title>CC3201 Project Homepage</title> <link rel="stylesheet" type="text/css" href="{% static 'style.css' %}"> </head> <body> <h1> Static homepage </h1> <p>Eso nomas.</p> </body> </html>
Ahora, se debería poder ver la página de entrada con estilo (H):
http://grupoXX.cc3201.dcc.uchile.cl/
Crear una aplicación nueva
Ahora hemos terminado con la configuración de Django y el proyecto. Podemos crear una aplicación Web nueva. (El proyecto es como una configuración que puede servir varias aplicaciones mientras que una aplicación es una aplicación Web específica.) Aquí crearemos una aplicación llamada prueba
pero se puede cambiar el nombre para algo relacionado con su proyecto.
cd /home/cc3201/django/cc3201/ python3 manage.py startapp prueba cd
… se creará una carpeta aquí con varios archivos:
ls /home/cc3201/django/cc3201/prueba/
… y podemos “instalar” la aplicación en el proyecto:
vi /home/cc3201/django/cc3201/cc3201/settings.py
… y en INSTALLED_APPS
poner prueba
(o el nombre de su aplicación):
INSTALLED_APPS = [ 'prueba.apps.PruebaConfig', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ]
Crear la primera vista
En la carpeta /home/cc3201/django/cc3201/prueba/
, crearemos una vista básica. Hay que editar views.py
vi /home/cc3201/django/cc3201/prueba/views.py
… y pondremos:
from django.shortcuts import render from django.http import HttpResponse # Create your views here. def index(request): return HttpResponse("Hola, soy una prueba.")
Ahora, tenemos una vista pero tenemos que mapear un URL a la vista. Para eso, crearemos un archivo nuevo urls.py
en la misma carpeta (/home/cc3201/django/cc3201/prueba
en mi caso):
vi /home/cc3201/django/cc3201/prueba/urls.py
… y pondremos el código:
from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), ]
La idea es que la aplicación tenga el camino http://grupoXX.cc3201.dcc.uchile.cl/prueba/[patron]
, y cuando el [patron]
esté vacío (eso es lo que significa el primer argumento en la función path
) usemos la vista index
que definimos antes.
Bueno, ahora, tenemos que especificar que http://grupoXX.cc3201.dcc.uchile.cl/prueba/
será el hogar de la aplicación prueba (ojo que estaremos ahora en la configuración del proyecto, no de la aplicación):
vi /home/cc3201/django/cc3201/cc3201/urls.py
Agreguemos el import include
:
from django.conf.urls import include, url
… y el patrón prueba
aquí:
urlpatterns = [ path('admin/', admin.site.urls), path('prueba/', include('prueba.urls')), path('', TemplateView.as_view(template_name='index.html'), name='home'), ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
Ahora en:
http://grupoXX.cc3201.dcc.uchile.cl/prueba/
… se debería ver la página de prueba.
Migrar a Postgres
Por defecto, Django usa SQLite para manejar sus datos internos. Vamos a cambiar eso y usar Postgres. Pero Django va a necesitar crear tablas, etc., entonces, por el momento, tendremos que usar un usuario superuser
como cc3201
, no un usuario como webuser
. :(
Tenemos que configurar el proyecto:
vi /home/cc3201/django/cc3201/cc3201/settings.py
… y reemplazar la configuración antigua:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } }
… por (reemplazando password_de_cc3201
por el password del usuario cc3201
en postgres):
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': 'cc3201', 'USER': 'cc3201', 'PASSWORD': 'password_de_cc3201', 'HOST': 'localhost', 'PORT': '', } }
… y guardar el archivo.
Pero ahora si intentamos usar una aplicación que depende de la base de datos, tendremos problemas:
http://grupoXX.cc3201.dcc.uchile.cl/admin/
Tenemos que migrar a la base de datos nueva:
python3 /home/cc3201/django/cc3201/manage.py migrate
Y crear el superuser de nuevo (además de nombre y contraseña, les pedirá un correo; pueden poner a@a.cl o cualquier cosa con ese formato):
python3 /home/cc3201/django/cc3201/manage.py createsuperuser
Con esto, deberíamos estar bien:
http://grupoXX.cc3201.dcc.uchile.cl/admin/
Si vamos a Postgres, deberíamos ver algunas tablas nuevas:
psql
\dt *
Estas corresponden a tablas que usa Django para diversas funcionalidades como manejo de usuarios o permisos.
Crear un modelo para la base de datos
Crearemos el esquema test
más una tabla para probar la funcionalidad:
CREATE SCHEMA test; ALTER USER cc3201 SET search_path TO test, public;
Notar que es importante poner el path público para que Django puede reconocer este nuevo esquema. Ahora creamos la tabla de prueba:
CREATE TABLE test.mes( nombre varchar(255) PRIMARY KEY, numero int ); INSERT INTO test.mes VALUES ('Mayo',5); INSERT INTO test.mes VALUES ('Junio',6);
Ahora, crearemos un modelo que da una abstracción de nuestra base de datos en Python:
python3 /home/cc3201/django/cc3201/manage.py inspectdb
Deberíamos ver algunas clases en python que representan tablas internas que usa Django; además veremos nuestras tablas. Por ejemplo, deberíamos poder ver:
class Mes(models.Model): nombre = models.CharField(primary_key=True, max_length=255) numero = models.IntegerField(blank=True, null=True) class Meta: managed = False db_table = 'mes'
Cada clase refleja una relación en la base de datos con sus atributos y algunas restricciones básicas. Una instancia de esa clase representará una tupla en la relación.
Escribiremos el modelo a un archivo en la carpeta de la aplicación (en este caso prueba
):
python3 /home/cc3201/django/cc3201/manage.py inspectdb > /home/cc3201/django/cc3201/prueba/models.py
Una complicación aquí es que hay que poner un límite al número de caracteres en cada campo del tipo CharField
. Entonces hay que revisar models.py
y si hay una columna de una tabla así:
models.CharField(max_length=-1, blank=True, null=True)
… cambiarla por:
models.TextField(max_length=255, blank=True, null=True)
… con las opciones que aplican.
:(
Consultar el modelo con Python
Dado un modelo, Django tiene varias funciones para interactuar con la base de datos en Python. Probaremos la conexión a la base de datos con algo simple en la aplicación prueba
. Empezaremos con una vista nueva mayo
:
vi /home/cc3201/django/cc3201/prueba/views.py
… y agregar la importación Mes
(el modelo que refleja la tabla en la base de datos) y la vista mayo
abajo:
from django.shortcuts import render from django.http import HttpResponse from prueba.models import Mes # Create your views here. def index(request): return HttpResponse("Hola, soy una prueba.") def mayo(request): return HttpResponse("El número del mes 'Mayo' es "+str(Mes.objects.filter(nombre='Mayo')[:1].get().numero))
Tenemos que definir el URL:
vi /home/cc3201/django/cc3201/prueba/urls.py
from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), path('mayo/', views.mayo, name='mayo') ]
Después en:
http://grupoXX.cc3201.dcc.uchile.cl/prueba/mayo/
Deberíamos ver:
El número del mes 'Mayo' es 5
No vamos a hablar más de la abstracción de Python para consultar o actualizar las tablas. Usaremos SQL directamente.
Consultar a la base de datos usando SQL
En el proyecto, pueden hacer consultas por el modelo de Django o pueden usar consultas de SQL directas. (En el caso de usar el modelo, ojo que deberían incluir las consultas equivalentes de SQL en las entregas del proyecto.)
Crearemos una vista nueva para ejemplificar la funcionalidad:
from django.shortcuts import render from django.http import HttpResponse from prueba.models import Mes from django.db import connection from collections import namedtuple # Create your views here. def index(request): return HttpResponse("Hola, soy una prueba.") def mayo(request): return HttpResponse("El número del mes 'Mayo' es "+str(Mes.objects.filter(nombre='Mayo')[:1].get().numero)) def mayosql(request): return HttpResponse("El número del mes 'Mayo' es "+ str(consultar_mes('Mayo')[0].numero)) def consultar_mes(mes): with connection.cursor() as cursor: cursor.execute("SELECT numero FROM test.mes WHERE nombre = %s", [mes]) results = namedtuplefetchall(cursor) return results def namedtuplefetchall(cursor): "Return all rows from a cursor as a namedtuple" desc = cursor.description nt_result = namedtuple('Result', [col[0] for col in desc]) return [nt_result(*row) for row in cursor.fetchall()]
Tenemos que definir el URL:
vi /home/cc3201/django/cc3201/prueba/urls.py
from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), path('mayosql/', views.mayosql, name='mayosql'), path('mayo/', views.mayo, name='mayo') ]
Después en:
http://grupoXX.cc3201.dcc.uchile.cl/prueba/mayosql/
Podemos ver el resultado.
Crear una plantilla de HTML (HTML template)
Para crear la aplicación, necesitaremos algo en HTML. Podríamos ponerlo en el código de la vista, pero sería feo poner el HTML en el código. En lugar de esto, crearemos una plantilla de HTML en una sub-carpeta de la aplicación prueba
:
mkdir /home/cc3201/django/cc3201/prueba/templates/
Crearemos un formulario para pedirle al usuario el nombre de un mes:
vi /home/cc3201/django/cc3201/prueba/templates/mes_form.html
Pondremos:
<form action="" method="post"> {% csrf_token %} {{ mes_form }} <input type="submit" value="Submit" /> </form> {{ resultados }}
La acción dice que vamos a mandar la información entregada al camino “/mes/” por HTTP post
.
Ahora, podemos usar Django para definir los detalles del formulario (para reemplazar mes_form
arriba):
vi /home/cc3201/django/cc3201/prueba/forms.py
Pondremos:
from django import forms class NombreMesForm(forms.Form): nombre_mes = forms.CharField(label='Nombre del mes', max_length=100)
Esta clase representa un formulario HTML con un solo atributo.
Ahora, tenemos que agregar el código de la vista:
vi /home/cc3201/django/cc3201/prueba/views.py
from django.shortcuts import render from django.http import HttpResponse from prueba.models import Mes from django.db import connection from collections import namedtuple from django.shortcuts import render from .forms import NombreMesForm # Create your views here. def index(request): return HttpResponse("Hola, soy una prueba.") def mayo(request): return HttpResponse("El número del mes 'Mayo' es "+str(Mes.objects.filter(nombre='Mayo')[:1].get().numero)) def mayosql(request): return HttpResponse("El número del mes 'Mayo' es "+ str(consultar_mes('Mayo')[0].numero)) def mes(request): # si es POST, tenemos una petición del usuario if request.method == 'POST': # create a form instance and populate it with data from the request: form = NombreMesForm(request.POST) # verifica que sea valido: if form.is_valid(): nombre_mes = form.cleaned_data['nombre_mes'] sql_res = consultar_mes(nombre_mes) if sql_res: num_mes = consultar_mes(nombre_mes)[0].numero res = 'El número del mes '+nombre_mes+' es '+str(num_mes) else: res = 'El mes '+nombre_mes+' no está en la tabla' return render(request, 'mes_form.html', {'mes_form': form, 'resultados': res}) # si es GET (o algo diferente) crearemos un formulario en blanco else: form = NombreMesForm() return render(request, 'mes_form.html', {'mes_form': form}) def consultar_mes(mes): with connection.cursor() as cursor: cursor.execute("SELECT numero FROM test.mes WHERE nombre = %s", [mes]) results = namedtuplefetchall(cursor) return results def namedtuplefetchall(cursor): "Return all rows from a cursor as a namedtuple" desc = cursor.description nt_result = namedtuple('Result', [col[0] for col in desc]) return [nt_result(*row) for row in cursor.fetchall()]
Y tenemos que crear el URL:
vi /home/cc3201/django/cc3201/prueba/urls.py
from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), path('mayo/', views.mayo, name='mayo'), path('mayosql/', views.mayosql, name='mayosql'), path('mes/', views.mes, name='mes'), ]
Ahora desde el URL de la vista, se puede consultar por un mes en particular
http://grupoXX.cc3201.dcc.uchile.cl/prueba/mes/
Eso
Con el ejemplo previo, y por fin, tienen todos los ingredientes para armar su aplicación usando Django. Un recordatorio: hay que verificar que la aplicación esté segura contra inyección. Puede ser que los ejemplos que doy aquí no sean seguros, entonces hay que investigar en la Web si son seguros o no.
Opción 3: Java Servlets
- PRO: se puede programar en Java
- CON: difícil de instalar y configurar
- CON: una metodología desactualizada (pero con muchos sistemas legados todavía)
- CON: hay que programar en Java
La opción de Java Servlets es una opción legada que tenía relevancia cuando el lenguaje usado en el curso Introducción a Programación fue Java. En vez de borrar la opción, la hemos mantenido acá, pero en general, no es una opción recomendada, salvo en el caso de que un grupo realmente quiera programar en Java.
Instalar tomcat9
Hay que instalar tomcat9
, un servidor web para Java Servlets:
sudo apt-get update sudo apt-get install tomcat9 tomcat9-admin tomcat9-examples tomcat9-docs authbind
Configurar tomcat9
Tenemos que poner un usuario “admin” y una contraseña:
sudo nano /etc/tomcat9/tomcat-users.xml
Aquí:
<tomcat-users> </tomcat-users>
Hay que poner (ojo: la página es publica así que ¡es importante elegir una contraseña segura! es decir, ¡no password
ni holaprueba
!):
<tomcat-users> <user username="admin" password="holaprueba" roles="manager-gui,admin-gui"/> </tomcat-users>
… y guardar el archivo.
Por defecto, tomcat9 será instalado con el puerto 8080, el cual no es accesible desde afuera. Tenemos que cambiarlo a 80 (el puerto por defecto en HTTP).
sudo nano /etc/tomcat9/server.xml
Hay que cambiar (ojo que no es 8005
)
<Connector port="8080" protocol="HTTP/1.1"
… por …
<Connector port="80" protocol="HTTP/1.1"
… y guardar el archivo.
Ahora …
sudo nano /etc/default/tomcat9
Hay que cambiar:
#AUTHBIND=no
por (ojo: hay que borrar el gato):
AUTHBIND=yes
… y guardar el archivo.
El próximo paso es deshabilitar IPv6 porque AUTHBIND
solo funciona con IPv4.
sudo nano /usr/share/tomcat9/bin/setenv.sh
Hay que poner:
export CATALINA_OPTS="$CATALINA_OPTS -Djava.net.preferIPv4Stack=true"
Después, en:
sudo nano /usr/share/tomcat9/bin/startup.sh
Hay que comentar la primera línea aquí y agregar la segunda:
# exec "$PRGDIR"/"$EXECUTABLE" start "$@" exec authbind --deep "$PRGDIR"/"$EXECUTABLE" start "$@"
Por fin, está lista la configuración del puerto 80.
Finalmente, hay que darle a Tomcat acceso al controlador de Postgres:
sudo apt-get install wget cd /usr/share/tomcat9/lib/ sudo wget https://jdbc.postgresql.org/download/postgresql-9.4.1212.jre6.jar
Ahora se puede usar JDBC para consultar a Postgres desde Tomcat.
Correr el servidor de tomcat9
En el caso de que Apache esté instalado y corriendo, hay que deshabilitarlo (si no está instalado, se puede saltar este paso):
sudo /etc/init.d/apache2 stop
Está lista la configuración así que tenemos que reiniciar tomcat9:
sudo service tomcat9 restart
Ahora, se puede ir aquí para verificar que funcione (ojo: XX
es el número del grupo):
http://cc3201.dcc.uchile.cl/grupoXX/
Hemos instalado Tomcat con el cual podemos instalar Java Servlets. :)
Un ejemplo de un Java Servlet
(Una confesión: en el ejemplo que doy, pongo HTML en el código de Java. Eso es muy feo porque mezcla la presentación y la lógica de la aplicación. Sería muchísimo mejor usar JSP con los servlets, pero también más complicado.)
Efectivamente, un servlet es como un paquete de código en Java en un formato “WAR”. Es como un JAR pero con algunas diferencias: tiene una página web y algunas configuraciones más.
Descargar servlet-test.zip y abrirlo en Eclipse o IntelliJ, etc.
En src/
está el código.
En web/resources/
se pueden poner archivos estáticos como imágenes, etc.
En web/resources/index.html
está la página por defecto.
En web/WEB-INF/web.xml
está la configuración del servlet.
En web/WEB-INF/context.xml
hay que cambiar el username
y el password
. Se debería usar el usuario de la aplicación (p.ej. webuser
), no un super-usuario como cc3201
o postgres
. No hay que cambiar el IP ni el puerto (la aplicación será instalada directamente en la máquina virtual así que la conexión será por localhost) ni la base de datos.
Tenemos que crear un paquete .war
. Sobre build.xml
hacer clic derecho, Run As > Ant Build
.
Ahora hay que refrescar el proyecto: hacer clic en la carpeta del proyecto y presionar F5
. En dist/
, debería haber un archivo bddservlet.war
.
Tenemos que subir el archivo bddservlet.war
a la carpeta /var/lib/tomcat9/webapps/
pero será más fácil si cc3201
tiene permisos para estas carpetas:
sudo chown -R tomcat9:tomcat9 /var/lib/tomcat9 sudo chmod -R g+r /var/lib/tomcat9/conf sudo chmod -R g+w /var/lib/tomcat9/conf sudo chmod -R g+w /var/lib/tomcat9/logs sudo chmod -R g+w /var/lib/tomcat9/webapps sudo chmod -R g+w /var/lib/tomcat9/work sudo chmod -R g+s /var/lib/tomcat9/conf sudo chmod -R g+s /var/lib/tomcat9/logs sudo chmod -R g+s /var/lib/tomcat9/webapps sudo chmod -R g+s /var/lib/tomcat9/work sudo usermod -a -G tomcat9 cc3201
Después, hay que usar scp
o sftp
o wget
para poner bddservlet.war
en la carpeta /var/lib/tomcat9/webapps/
del servidor. Se instalará la aplicación automáticamente en la carpeta bddservlet/
en /var/lib/tomcat9/webapps/bddservlet
. (Si uno hace la transferencia de nuevo, actualizará la carpeta también.)
Ir a:
http://grupoXX.cc3201.dcc.uchile.cl/bddservlet/
Aquí se pueden ver tres ejemplos simples de aplicaciones que pueden adaptar para su aplicación.
OJO: ¡es importante usar métodos seguros contra inyección! Mi ejemplo es solo una prueba, no un ejemplo a seguir.
Si hay un problema con el servlet, el log está aquí:
sudo more /var/lib/tomcat9/logs/catalina.out
… el error más común es un problema con la contraseña/usuario en web/WEB-INF/context.xml
. Por eso, hay que revisar los detalles en psql
para verificar que estén correctos.
Opción 4: Algo diferente
Tienen sudo, así que pueden usar otra opción para armar la aplicación:
- Flask, React, Angular, Rails, Spring, etc.
… pero no podremos ofrecerles soporte si hay problemas.