En la primer vista de ejemplo, el contenido de la página
— la fecha/hora actual — eran dinámicas, pero la URL (/time
) era
estática. En la mayoría de las aplicaciones Web, sin embargo, la URL contiene
parámetros que influyen en la salida de la página.
Vamos a crear una segunda vista que nos muestre la fecha y hora actual con un
adelanto de ciertas horas. El objetivo es montar un sitio en la que la página
/time/plus/1/
muestre la fecha/hora una hora más adelantada, la página
/time/plus/2/
muestre la fecha/hora dos horas más adelantada, la página
/time/plus/3/
muestre la fecha/hora tres horas más adelantada, y así.
A un novato se le ocurriría escribir una función de vista distinta para cada adelanto de horas, lo que resultaría una URLconf como esta:
urlpatterns = patterns('',
(r'^time/$', current_datetime),
(r'^time/plus/1/$', one_hour_ahead),
(r'^time/plus/2/$', two_hours_ahead),
(r'^time/plus/3/$', three_hours_ahead),
(r'^time/plus/4/$', four_hours_ahead),
)
Claramente, esta línea de pensamiento es incorrecta. No sólo porque producirá redundancia entre las funciones de vista, sino también la aplicación estará limitada a admitir sólo el rango horario definido — uno, dos, tres o cuatro horas. Si, de repente, quisiéramos crear una página que mostrara la hora cinco horas adelantada, tendríamos que crear una vista distinta y una línea URLconf, perpetuando la duplicación y la demencia. Aquí necesitamos algo de abstracción.
3.6.1. Algunas palabras acerca de las URLs bonitas
Si tienes experiencia en otra plataforma de diseño Web, como PHP o Java, es
posible que estés pensado, "¡Oye, usemos un parámetro cadena de consulta!",
algo como /time/plus?hours=3
, en la cual la hora será designada por el
parámetro hours
de la cadena de consulta de la URL (la parte a continuación
de ?
).
Con Django puedes hacer eso (pero te diremos cómo más adelante, si es que
realmente quieres saberlo), pero una de las filosofías del núcleo de Django es
que las URLs deben ser bonitas. La URL /time/plus/3
es mucho más limpia, más
simple, más legible, más fácil de dictarse a alguien y ... justamente más
bonita que su homóloga forma de cadena de consulta. Las URLs bonitas son un
signo de calidad en las aplicaciones Web.
El sistema de URLconf que usa Django estimula a generar URLs bonitas, haciendo más fácil el usarlas que el no usarlas.
3.6.2. Comodines en los patrones URL
Continuando con nuestro ejemplo hours_ahead
, pongámosle un comodín al
patrón URL. Como ya se mencionó antes, un patrón URL es una expresión regular;
de aquí, es que usamos el patrón de expresión regular \d+
para que
coincida con uno o más dígitos:
from django.conf.urls.defaults import *
from mysite.views import current_datetime, hours_ahead
urlpatterns = patterns('',
(r'^time/$', current_datetime),
(r'^time/plus/\d+/$', hours_ahead),
)
Este patrón coincidirá con cualquier URL que sea como /time/plus/2/
,
/time/plus/25/
, o también /time/plus/100000000000/
. Ahora que lo
pienso, podemos limitar el lapso máximo de horas en 99. Eso significa que
queremos tener números de uno o dos dígitos en la sintaxis de las expresiones
regulares, con lo que nos quedaría así \d{1,2}
:
(r'^time/plus/\d{1,2}/$', hours_ahead),
Nota Cuando construimos aplicaciones Web, siempre es importante considerar el caso más descabellado posible de entrada, y decidir si la aplicación admitirá o no esa entrada. Aquí hemos limitado a los exagerados reconociendo lapsos de hasta 99 horas. Y, por cierto, Los Limitadores exagerados, aunque largo, sería un nombre fantástico para una banda musical.
Ahora designaremos el comodín para la URL, necesitamos una forma de pasar esa
información a la función de vista, así podremos usar una sola función de vista
para cualquier adelanto de hora. Lo haremos colocando paréntesis alrededor de
los datos en el patrón URL que querramos guardar. En el caso del ejemplo,
queremos guardar cualquier número que se anotará en la URL, entonces pongamos
paréntesis alrededor de \d{1,2}
:
(r'^time/plus/(\d{1,2})/$', hours_ahead),
Si estás familiarizado con las expresiones regulares, te sentirás como en casa aquí; estamos usando paréntesis para capturar los datos del texto que coincide.
La URLconf final, incluyendo la vista anterior current_datetime
, nos
quedará algo así:
from django.conf.urls.defaults import *
from mysite.views import current_datetime, hours_ahead
urlpatterns = patterns('',
(r'^time/$', current_datetime),
(r'^time/plus/(\d{1,2})/$', hours_ahead),
)
Con cuidado, vamos a escribir la vista hours_ahead
.
hours_ahead
es muy similar a current_datetime
, vista que escribimos
antes, sólo que con una diferencia: tomará un argumento extra, el número de
horas que debemos adelantar. Agrega al archivo views.py
lo siguiente:
import django.http.HttpResponse
import datetime
def hours_ahead(request, offset):
offset = int(offset)
dt = datetime.datetime.now() + datetime.timedelta(hours=offset)
html = "<html><body>In %s hour(s), it will be %s.</body></html>" % (offset, dt)
return HttpResponse(html)
Repasemos el código anterior línea a línea:
- Tal como hicimos en la vista
current_datetime
, importamos la clasedjango.http.HttpResponse
y el módulodatetime
. - La función de vista
hours_ahead
, toma dos parámetros:request
yoffset
. request
es un objetoHttpRequest
, al igual que encurrent_datetime
. Lo diremos nuevamente: cada vista siempre toma un objetoHttpRequest
como primer parámetro.offset
es la cadena de caracteres capturada por los paréntesis en el patrón URL. Por ejemplo, si la petición URL fuera/time/plus/3/
, entonces eloffset
debería ser la cadena de caracteres'3'
. Si la petición URL fuera/time/plus/21/
, entonces eloffset
debería ser la cadena de caracteres'21'
. Notar que la cadena de caracteres capturada siempre es una cadena de caracteres, no un entero, incluso si se compone sólo de dígitos, como en el caso'21'
.
Decidimos llamar a la variable `offset`, pero puedes asignarle el nombre que quieras, siempre que sea un identificador válido para Python. El nombre de la variable no importa; todo lo que importa es lo que contiene el segundo parámetro de la función (luego de `request`). Es posible también usar untienes que hacer esto. No es una buena idea poner cualquier código Python en la carpeta raíz del servia palabra clave, en lugar de posición, como argumentos en la URLconf. Eso lo veremos en detalle en el Capítulo 8.
- Lo primero que hacemos en la función es llamar a
int()
sobreoffset
. Esto convierte el valor de la cadena de caracteres a entero. Tener en cuenta que Python lanzará una excepciónValueError
si se llama a la funciónint()
con un valor que no puede convertirse a un entero, como lo sería la cadena de caracteres'foo'
. Sin embargo, en este ejemplo no debemos preocuparnos de atrapar la excepción, porque tenemos la certeza que la variableoffset
será una cadena de caracteres conformada sólo por dígitos. Sabemos esto, por el patrón URL de la expresión regular en el URLconf —(\d{1,2})
— captura sólo dígitos. Esto ilustra otra ventaja de tener un URLconf: nos provee un primer nivel de validación de entrada. - La siguiente línea de la función muestra la razón por la que se llamó a
la función
int()
conoffset
. En esta línea, calculamos la hora actual más las hora que tieneoffset
, almacenando el resultado en la variabledt
. La funcióndatetime.timedelta
requiere que el parámetrohours
sea un entero. - A continuación, construimos la salida HTML de esta función de vista, tal
como lo hicimos en la vista
current_datetime
. Una pequeña diferencia en esta línea, es que usamos el formato de cadenas de Python con dos valores, no sólo uno. Por lo tanto, hay dos símbolos%s
en la cadena de caracteres y la tupla de valores a insertar sería:(offset, dt)
. - Finalmente, retornamos el
HttpResponse
del HTML — de nuevo, tal como hicimos en la vistacurrent_datetime
.
Con esta función de vista y la URLconf escrita, ejecuta el servidor de
desarrollo de Django (si no está corriendo), y visita
http://127.0.0.1:8000/time/plus/3/
para verificar que lo que hicimos
funciona. Luego prueba http://127.0.0.1:8000/time/plus/5/
. Para terminar
visita http://127.0.0.1:8000/time/plus/100/
para verificar que el patrón en
la URLconf sólo acepta número de uno o dos dígitos, Django debería mostrar un
error en este caso como "Page not found", tal como vimos anteriorment en la
sección "Errores 404". La URL http://127.0.0.1:8000/time/plus/
(sin horas
designadas) debería también mostrar un error 404.
Si estás siguiendo el libro y programando al mismo tiempo, notarás que el
archivo views.py
ahora contiene dos vistas. (Omitimos la vista
current_datetime
del ejemplo anterior sólo por claridad). Poniéndolas
juntas, veríamos algo similar a esto:
from django.http import HttpResponse
import datetime
def current_datetime(request):
now = datetime.datetime.now()
html = "<html><body>It is now %s.</body></html>" % now
return HttpResponse(html)
def hours_ahead(request, offset):
offset = int(offset)
dt = datetime.datetime.now() + datetime.timedelta(hours=offset)
html = "<html><body>In %s hour(s), it will be %s.</body></html>" % (offset, dt)
return HttpResponse(html)