You are on page 1of 18

Tutorial de Django

Introduccin o Requisitos El ecosistema de Django Estructura de (una aplicacin) Django Nuestra aplicacin web: Trivial trivial Creacin del proyecto y la aplicacin Definiendo los modelos Siguente: implementando la autentificacin Formulario de preguntas Respondiendo a la pregunta Conclusiones Notas

Introduccin
Django es un "framework" para el desarrollo de aplicaciones Web basado en el lenguaje de programacin Python y que sigue el patrn de diseo MVC. En los ltimos tiempos la palabra "framework" parece que se ha convertido en el "Santo Grial" del desarrollo de aplicaciones Web. Uno de los "framework" (1) ms famosos o populares que se ha dado a conocer ha sido Ruby On Rails. Qu aporta esta herramienta frente a otras alternativas ms conocidas y establecidas?. Lo ms novedoso era la prevalencia de la convencin frente a la configuracin. En vez de andar editando ficheros .xml (como en las herramientas J2EE) se estableca una estructura de directorios estndar para la aplicacin, no era necesario configurar mapeos, plantillas, etc. Rails tambin abstrae completamente la interaccin con bases de datos relacionales: el sistema es capaz de "adivinar" los modelos e inferir los objetos que intervienen en la aplicacin con slo examinar la definicin de tablas en el sistema relacional. Hay un objeto, "ActiveRecord" que es el que relaciona o hace de puente entre el sistema relacional y el sistema orientado a objetos. Basndose en algunas de las ideas de Rails y otras propias han surgido bastantes herramientas de desarrollo de aplicaciones web con una orientacin similar: poco cdigo, reusable, etc. En este artculo presentaremos Django, que recoge muchas de las ideas de Rails y aporta algunas soluciones propias. Desarrollaremos una aplicacin viendo los problemas que nos encontramos y cmo los solucionamos. En casi todos los tutoriales que hemos ledo, se

presenta slo lo ms bsico, pero a la hora de avanzar o tratar de hacer algo ms "real" nos encontramos con dificultades. Este artculo trata de paliar sto.

Requisitos
Para seguir este tutorial es necesario conocer el lenguaje Python, y haber ledo el tutorial de Django. Por supuesto, asumimos que el lector sabe HTML y tecnologas relacionadas (protocolo HTTP, CSS, etc.)

El ecosistema de Django
Es bastante habitual en este mundillo de los "framework MVC - que te cagas" (2) que haya una (sana) competencia entre las distintas herramientas y lenguajes. Podemos encontrar numerosas comparativas, tutoriales, guas, etc, pero todo este (til) material slo rasca la superficie de lo que supone trabajar con estas herramientas. A veces da la impresin de que hay un concurso del tipo Con XXX monto una aplicacin del tipo YYY en ZZZ minutos ! Mi framework lava ms blanco! No vamos a entrar en el juego. No hemos cronometrado lo que se ha tardado en terminar el ejemplo que desarrollaremos en este artculo. Si contamos lneas de cdigo del ejemplo, se podra "picar" todo en apenas diez minutos, pero no este tiempo es un dato real ni vlido. Tampoco vamos a decir que Django es mejor que otras herramientas (ni siquiera lo tenemos claro), aunque s que mencionaremos qu es lo que nos ha gustado y lo que no nos ha gustado (3, 4). Como bien es sabido, en el mundillo Python siempre hay mucho donde elegir: Interfaces grficas? Servidores web? Sistemas de plantillas? ...? Esto puede resultar confuso, puesto que antes de empezar a desarrollar una aplicacin del tipo que sea hay que documentarse bastante para elegir la herramienta adecuada. En cuanto a los " Web Frameworks" (otra vez la palabreja ;-) tambin hay bastantes alternativas. Las ms conocidas son Django, TurboGears y Pylons. Los programadores del lenguaje Ruby no tienen que estrujarse tanto la cabeza. La herramienta por antonomasia para desarrollo web de Ruby es Ruby On Rails. No hay mucho ms donde elegir. Esto tiene ventajas (centralizacin de esfuerzos, uniformidad, ...) e inconvenientes (menor flexibilidad, ...). Cada uno debe valorar qu es lo que prefiere. Por ltimo, un breve apunte sobre los lenguajes y sus modismos. Ruby es un lenguaje con una filosofa parecida a Perl: "hay ms de una forma de hacerlo", Python tiene una aproximacin diferente: "hacer las cosas de una sla manera, la ms sencilla". De nuevo, aqu intervienen las preferencias personales de cada uno a la hora de programar. Django sigue bastante la " filosofa pythonera" y tratan de seguir una serie de principios de diseo (mejor explcito que implcito, asumir que el desarrollador sabe lo que est haciendo, ...).

Estructura de (una aplicacin) Django


Django distingue entre proyectos y aplicaciones. Un proyecto es un sitio web completo que constar de una o varias aplicaciones. Estas aplicaciones las proporciona Django o las escribe el desarrollador. El comando que crea un proyecto es django-admin.py. No vamos a repetir lo que cuenta el tutorial de Django. Simplemente, con django-admin.py startproject miweb se crea un directorio miweb que contiene varios ficheros .py: __init__.py, manage.py, settings.py y urls.py.
__init__.py: Define nuestro directorio como un mdulo Python vlido. manage.py: Utilidad para gestionar nuestro proyecto: arrancar servidor de

pruebas, sincronizar modelos, etc. settings.py: Configuracin del proyecto. urls.py: Gestin de las urls. Este fichero sera el controlador de la aplicacin. Mapea las url entrantes a funciones Python definidas en mdulos.

Para crear una aplicacin nueva dentro del proyecto ejecutamos python manage.py startapp miaplicacion. Este comando crea el directorio miaplicacion y los ficheros __init__.py, views.py, y models.py.
__init__.py: Define nuestro directorio como un mdulo Python vlido. models.py: Aqu se definen los modelos u objetos que sern mapeados a una base

de datos relacional. views.py: Define las funciones que van a responder a las urls entrantes.

Esto es un diseo MVC: modelo (models.py), vista (views.py), controlador(urls.py). Aclaracin: los desarrolladores de Django llaman a su arquitectura MVT: Model - View Template, ya que consideran que el controlador es el propio framework.

Nuestra aplicacin web: Trivial trivial


Vamos a desarrollar una aplicacin web un poco ms completa que la que proponen en el tutorial de Django. Es un Trivial multiusuario. Especificaciones:

Las preguntas y los usuarios los crea un administrador. Existen diferentes categoras de preguntas. Cada usuario tiene su propio juego (esto es, responde a sus preguntas). Es obligatorio estar validado en el sistema para jugar.

Como es una aplicacin de prueba, usaremos el servidor de desarrollo que viene con Django. Los ficheros estticos (CSS, imgenes, etc) tambin los servir Django, aunque

esta prctica est totalmente desaconsejada en un entorno de produccin. Como sistema relacional usaremos sqlite (hay un "driver" para Python). Nos encontramos en este punto con un enfoque totalmente diferente al de Rails: Django mantiene los modelos relacionales (tablas, relaciones, ...) a partir del modelo que nosotros definimos (en una clase), mientras que Rails "adivina" el modelo a partir del esquema relacional. La ventaja del "enfoque Rails" es que en cuanto que el modelo cambia, la herramienta detecta estos cambios y se refleja automticmente. El inconveniente es que hay que seguir las convenciones Rails para que sto funcione. Personalmente, preferimos el planteamiento de Django: el desarrollador define modelos, la herramienta se encarga de traducir estos modelos a SQL, pero hay un inconveniente: si los modelos cambian, hay que hacer el cambio manualmente en el esquema del sistema relacional. Parece que en posteriores versiones de Django tratarn de implementar esta mejora, pero a da de hoy (Django versin 0.96), no es as. Tras esta breve disquisicin, pasemos a definir los modelos. Encontramos las siguientes entidades:

Usuario, caracterizado por un nombre, "login", contrasea, ... Categora Pregunta, con un ttulo, conjunto de respuestas posibles, respuesta correcta, un grfico asociado, ... (5) Respuesta, asociada a un usuario concreto y una pregunta concreta, guardando el resultado (acierto/fallo), etc.

Creacin del proyecto y la aplicacin


Lo primero es crear el proyecto: django-admin.py startproject Trivial

Ajustamos algunos parmetros en settings.py y urls.py. Habilitaremos la interfaz administrativa (6), el directorio desde el que se sirven los contenidos estticos y algunos ajustes ms.
settings.py

(extracto):

MEDIA_ROOT = '/home/david/desarrollo/Trivial/site_media/' MEDIA_URL = 'http://localhost:8000/site_media/' ADMIN_MEDIA_PREFIX = '/media/' INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.admin', 'Trivial.juego', ) urls.py

(extracto):

from settings import MEDIA_ROOT urlpatterns = patterns('', (r'^admin/', include('django.contrib.admin.urls')), ) urlpatterns += patterns('django.views', (r'^site_media/(.*)$', 'static.serve', {'document_root': MEDIA_ROOT}), )

Cuando queramos pasar a produccin, slo tendremos que eliminar la ltima entrada en urls.py y editar MEDIA_URL en settings.py. Tambin tenemos que crear la base de datos sqlite (comando sqlite data/datos.db). Ahora, desde el directorio Trivial (directorio raz) creamos la aplicacin propiamente dicha (juego): python manage.py startapp juego.

Definiendo los modelos


Editamos el fichero juego/models.py para definir nuestros objetos. Las clases que representan modelos deben heredar de la clase Model y siguen una sintaxis muy sencilla e intuitiva. Django incorpora en el paquete django.contrib.auth todo un sistema de autentificacin y gestin de usuarios, as que no vamos a reinventar la rueda y utilizaremos este sistema (7). Estos son nuestros modelos:
from django.db import models from django.contrib.auth.models import User

class Usuario(User): def __str__(self): return self.username class Admin: pass class Categoria(models.Model): nombre = models.CharField("Categora", maxlength=200) def __str__(self): return self.nombre class Admin: pass class Pregunta(models.Model): categoria = models.ForeignKey(Categoria, verbose_name="Categora la que pertenece") titulo = models.CharField("Ttulo", maxlength=200) texto = models.TextField("Texto de la pregunta") respuesta_1 = models.CharField(maxlength=200) respuesta_2 = models.CharField(maxlength=200) respuesta_3 = models.CharField(maxlength=200) respuesta_4 = models.CharField(maxlength=200) respuesta_correcta = models.CharField(maxlength=200) foto = models.CharField(maxlength=200) def __str__(self): return self.titulo class Admin: pass class Respuesta(models.Model): tiempo = models.IntegerField("Tiempo en segs.") resultado = models.IntegerField("0 -> incorrecto, 1 -> correcto") pregunta = models.ForeignKey(Pregunta, verbose_name="Pregunta que se responde") usuario = models.ForeignKey(User, verbose_name="Usuario que responde") def __str__(self): return str(self.pregunta) + " (Usuario: " + str(self.usuario) + ")" class Admin: pass

Como decamos antes, "mejor explcito que implcito". Definimos un mtodo __str__ en todas las clases para tener una descripcin "humana" de cada objeto, tanto a la hora de desarrollar como de gestionar en la interfaz administrativa. La clase anidada Admin sirve para que la clase madre aparezca en la interfaz administrativa.

La clase Usuario hereda directamente de la clase User de Django (django.contrib.auth.models.User). Por ltimo, haremos que Django sincronice la informacin que tiene de los modelos con el sistema relacional (vamos, que cree las tablas necesarias): python manage.py syncdb Este comando tambin crear las tablas necesarias para la aplicacin administrativa y el sistema de gestin de usuarios (de hecho nos pedir los datos necesarios para crear un "superusuario"). Si arrancamos el servidor y apuntamos nuestro navegador a http://localhost:8000/admin/ veremos en marcha la interfaz de administracin:

Lo primero que hemos hecho ha sido crear un grupo (Concursantes) y asignarle el permiso de "crear objetos del tipo Respuesta". Despus creamos unos cuantos usuarios y les hacemos miembros del grupo. Ya tenemos un sistema de control de acceso, permisos bastante granulares (estos usuarios slo podrn crear objetos del tipo Respuesta, pero no modificarlos o borrarlos) sin escribir nada de cdigo.

Siguente: implementando la autentificacin


El siguiente paso es relativamente sencillo si utilizamos las facilidades que Django nos proporciona. El decorador @login_required en el paquete django.contrib.auth.decorators funciona de la siguiente manera: si el usuario no est autentificado, redirige a una plantilla o template de validacin (registration/login.html por defecto):

Si est autentificado, la funcin "decorada" ( index en este caso) se ejecuta normalmente. La primera pantalla que queremos que el usuario autenfificado vea es un listado de preguntas clasificado por categoras. ste sera nuestro "index.html", pero, como hemos dicho, queremos que el usuario se valide antes. Veamos cmo hacerlo. En urls.py aadimos una entrada para "mapear" la direccin "/" (raz del sitio) a la funcin "index" en views.py:
urls.py

(extracto):

from settings import MEDIA_ROOT urlpatterns = patterns('', (r'^/?$', 'Trivial.juego.views.index'), (r'^admin/', include('django.contrib.admin.urls')), ) urlpatterns += patterns('django.views', (r'^site_media/(.*)$', 'static.serve', {'document_root': MEDIA_ROOT}), )

Y nuestra funcin (8) en views.py sera algo as:


@login_required def index(request): categorias = Categoria.objects.all() preguntas = Pregunta.objects.all() respuestas = Respuesta.objects.filter(usuario=request.user) return render_to_response('index.html', {'categorias': categorias, 'preguntas': preguntas, 'respuestas': respuestas, 'usuario': request.user,} )

Qu hace este index? Recoge todas las categoras, preguntas y respuestas del usuario validado y se las "pasa" a una plantilla o template llamada "index.html". Tambin le pasa los datos del usuario (request.user). Como hemos especificado que hay un login previo, podemos estar seguros de que esta variable "usuario" tiene datos correctos. Veamos ahora la plantilla o template index.html, no sin antes repasar cmo funcionan los templates en Django:
{% extends "base.html" %} {% block cuerpo %} <strong>Listado de preguntas</strong> {% if categorias %} {% regroup preguntas by categoria as agrupado %} <ul> {% for grupo in agrupado %} <li>{{ grupo.grouper }}</li> <ul> {% for item in grupo.list %} <li><a href="pregunta/{{item.id}}/">{{ item.titulo }}</a><br> {% for r in respuestas %} {% ifequal item r.pregunta %} &nbsp;&nbsp;La pregunta ya ha sido respondida. {% endifequal %} {% endfor %} </li> {% endfor %} </ul> {% endfor %} </ul> {% else %} <p>No hay categoras</p> {% endif %} <p><a href="/accounts/logout/">Desconectar</a></p> {% endblock %}

En este template comprobamos si hay categoras {% if categorias %} y mostramos en forma de listas anidadas todas las preguntas de cada categora con la etiqueta {% regroup preguntas by categoria as agrupado %} y lo que sigue. Para cada pregunta comprobamos si tiene una respuesta asociada:
{% for r in respuestas %} {% ifequal item r.pregunta %} &nbsp;&nbsp;La pregunta ya ha sido respondida. {% endifequal %} {% endfor %}

Esta comprobacin es algo ineficiente (en cada pregunta itera sobre todas las respuestas) pero no lo hemos refinado por mantener la simplicidad. Seguro que se puede hacer mejor ;) En este template tambin estamos utilizando una caracterstica muy til: la herencia de plantillas. En una plantilla aparte ( base.html) definimos un esqueleto con unos bloques de contenido que cada una de las plantillas "hijas" se encarga de completar con {% block
loquesea %}

As quedara nuestra pantalla inicial:

Formulario de preguntas
Cuando el usuario sigue el enlace (<a href="pregunta/{{item.id}}/">{{ item.titulo }}</a>) que presenta cada pregunta en la plantilla index.html se le dirige a la pgina que llamaremos "ficha de pregunta". Estas son las modificaciones que hemos introducido:
urls.py

(extracto):

urlpatterns = patterns('', (r'^/?$', 'Trivial.juego.views.index'), (r'^pregunta/(\d+)/$', 'Trivial.juego.views.pregunta'), (r'^admin/', include('django.contrib.admin.urls')),

En views.py definimos la funcin "pregunta":


from django.shortcuts import render_to_response from django.contrib.auth.decorators import login_required from Trivial.juego.models import Pregunta, Respuesta @login_required def pregunta(request, id): pregunta = Pregunta.objects.get(id=id) try: respuesta = Respuesta.objects.get(pregunta=id, usuario=request.user) except ObjectDoesNotExist: respuesta = None return render_to_response('pregunta.html', {'pregunta': pregunta, 'respuesta': respuesta, 'tiempo': str(int(time.time())), } )

A la funcin pregunta le llegan dos argumentos: request e id, tal y como se define en urls.py. Lo primero que hacemos es buscar la pregunta correspondiente (pregunta = Pregunta.objects.get(id=id)) y luego buscamos la posible respuesta que haya podido hacer el usuario en una anterior visita (respuesta = Respuesta.objects.get(pregunta=id, usuario=request.user)). Si no hay respuestas capturamos la excepcin, asignamos None a la respuesta y seguimos. Finalmente, sta es la plantilla que muestra los datos de una pregunta, pregunta.html:
{% extends "base.html" %} {% block cuerpo %} <h2>Categora: {{ pregunta.categoria }}</h2> <h3>{{ pregunta.titulo }}</h3> {% if texto_error %} <p class="error">{{ texto_error }}</p> {% endif %} <img class="foto" src="/site_media/{{ pregunta.foto }}"> <p>{{ pregunta.texto }}</p> {% if respuesta %} <p>Ya has respondido antes a la pregunta.</p> <p>Tiempo empleado: {{ respuesta.tiempo }} segundos.</p> <p>El resultado fue {% if respuesta.resultado %} CORRECTO {% else %} INCORRECTO

{% endif %} </p> {% else %} <form method="post" action="/responder/"> <input type="hidden" name="pregunta" value="{{ pregunta.id }}"> <input type="hidden" name="tiempo" value="{{ tiempo }}"> <input type="radio" value="{{ pregunta.respuesta_1 }}" name="respuesta">{{ pregunta.respuesta_1 }}<br> <input type="radio" value="{{ pregunta.respuesta_2 }}" name="respuesta">{{ pregunta.respuesta_2 }}<br> <input type="radio" value="{{ pregunta.respuesta_3 }}" name="respuesta">{{ pregunta.respuesta_3 }}<br> <input type="radio" value="{{ pregunta.respuesta_4 }}" name="respuesta">{{ pregunta.respuesta_4 }}<br> <br> <input type="submit" value="Responder"> </form> {% endif %} {% endblock %}

Nos encontramos en esta plantilla con una variable (texto_error) que no hemos asignado desde la funcin pregunta. Esta variable puede tener un valor cuando esta plantilla es invocada desde otra funcin definida en views.py (respuesta). Lo veremos un poco ms adelante. Lo primero que comprobamos es si esta pregunta ya ha sido respondida. Si es as, la variable respuesta tendr un valor distinto a None. En este caso informamos al usuario del resultado y el tiempo empleado en resolver la pregunta.

Si no hay respuesta, generamos un formulario con las posibles respuestas y dos campos ocultos con el id de la pregunta y una marca de tiempo. El formulario apunta a la url /responder/, ahora veremos cmo lo tratamos.

Respondiendo a la pregunta
De nuevo, aadimos una regla al fichero urls.py para procesar las respuestas de los usuarios. El fichero quedara as (versin final):
from django.contrib.auth.views import login, logout from django.conf.urls.defaults import * from settings import MEDIA_ROOT urlpatterns = patterns('', (r'^/?$', 'Trivial.juego.views.index'), (r'^pregunta/(\d+)/$', 'Trivial.juego.views.pregunta'), (r'^responder/$', 'Trivial.juego.views.respuesta'), (r'^accounts/login/$', login), (r'^accounts/logout/$', logout, {'template_name': 'registration/logout.html' }), (r'^admin/', include('django.contrib.admin.urls')), ) urlpatterns += patterns('django.views', (r'^site_media/(.*)$', 'static.serve', {'document_root': MEDIA_ROOT}), )

La funcin nueva (respuesta) se define en views.py y es as:

@login_required def respuesta(request): pregunta = Pregunta.objects.get(id=request.POST['pregunta']) if not request.POST.has_key('respuesta') or request.POST['respuesta'] == "": texto_error = "Debe elegir una opcin" return render_to_response('pregunta.html', {'pregunta': pregunta, 'texto_error': texto_error, 'tiempo': str(int(time.time())), } ) else: opcion = request.POST['respuesta']; respuesta = Respuesta() respuesta.pregunta = pregunta respuesta.usuario = request.user respuesta.tiempo = int(time.time()) - int(request.POST['tiempo']) if pregunta.respuesta_correcta == opcion: respuesta.resultado = 1 else: respuesta.resultado = 0 respuesta.save() return render_to_response('respuesta.html', {'pregunta': pregunta, 'respuesta': respuesta, 'opcion': opcion, } )

Primero localizamos la pregunta (nos llega el id en la variable POST 'pregunta'). Despus comprobamos que han pulsado uno de los "radiobutton" de respuesta (request.POST['respuesta']). Si no han respondido, redirigimos de nuevo a la pgina de pregunta pasando un mensaje de error.

Si han respondido, creamos un objeto Respuesta asociado a la pregunta y al usuario. Tambin asignamos a esta respuesta el tiempo que se ha tardado en resolver la pregunta y el resultado. Despus, redirigimos a la plantilla 'respuesta.html' pasando el objeto pregunta, el objeto respuesta recin creado y la opcin que haban seleccionado.
respuesta.html: {% extends "base.html" %} {% block cuerpo %} <h2>Resultado de la pregunta</h2> <h3>{{ pregunta.titulo }}</h3> <img class="foto" src="/site_media/{{ pregunta.foto }}"> <p>{{ pregunta.texto }}</p> <p>Respuesta seleccionada: {{ opcion }}</p> <p>Respuesta correcta: {{ pregunta.respuesta_correcta }}</p> {% if respuesta.resultado %} <p>Enhorabuena!</p> {% else %} <p>Has fallado!</p>

{% endif %} <p>Tiempo empleado: {{ respuesta.tiempo }} segundos.</p> {% endblock %}

Sencillo, simplemente mostramos los datos del objeto respuesta, la pregunta asociada y lo que el usuario respondi.

Conclusiones

Hemos hecho una aplicacin web muy bsica y sencilla, pero demuestra que muchas tareas complicadas y/o tediosas de implementar nos la proporciona la herramienta Django. La interfaz administrativa y el sistema de autentificacin nos han salido "gratis". Probablemente, la implementacin de estas dos funcionalidades nos hubiesen llevado bastante tiempo. Observamos tambin la extrema sencillez en el desarrollo: con cuatro elementos con unas funciones muy concretas (urls, modelos, vistas y plantillas) tenemos perfectamente separadas la lgica, los datos y la presentacin. Comprese esta simplicidad con la tpica aplicacin J2EE, por sencilla que sea. No hay color. Que las soluciones basadas en J2EE sean ms "potentes", escalables, robustas, etc. no lo voy a negar. Pero, acaso se necesita siempre esa "potencia"?. No hemos desarrollado una aplicacin similar con Rails, por lo que no sera muy justo decir que Django es ms fcil o mejor. Lo que si que podemos decir, por lo que hemos visto y ledo es que Django aporta varias caractersticas que ahorran

mucho trabajo, en este caso la interfaz administrativa y el sistema de autentificacin ( 9).

Notas
1. Estoy un poco cansado de la palabra "framework". A partir de ahora y en lo que queda de tutorial voy a emplear el trmino "herramienta", aunque no sea el ms adecuado ;-) 2. Disculpen el lenguaje soez, a veces se me escapa alguna palabrota ;-) 3. La documentacin que he utilizado son estas dos fuentes: la documentacin de Django y el libro "The Django Book". 4. Una de las cosas que ms me ha gustado de Django es la documentacin: completa, accesible y, sobre todo, centralizada. Me gusta mucho ms que, por ejemplo, la documentacin de Rails: tutoriales dispersos por internet, falta de un ndice por tpicos, ... No voy a ser malpensado, pero da la impresin que lo que quieren es que compres el libro que ha escrito el autor de Rails. 5. No voy a ser muy estricto con cuestiones de normalizacin del diseo de la base de datos. Probablemente sera mejor tener una tabla aparte para las posibles respuestas con una clave apuntando a la pregunta. Mantendremos este diseo (incorrecto) por sencillez. 6. Es una aplicacin web muy bien terminda y personalizable, nada que ver con el "scaffold" bsico que nos puede proporcionar Ruby On Rails. Si una aplicacin requiere una interfaz administrativa para manejar los distintos objetos en Rails hay que programarla. Django nos la da ya hecha. 7. Rails por defecto no tiene nada similar, aunque existe un "plugin" para Rails que implementa un sistema de autentificacin. No lo he utilizado, as que no puedo valorarlo. 8. Funcin, no mtodo. Intencionadamente, los diseadores de Django han decidido que no hay necesidad de lidiar con objetos para atender a una simple peticin. 9. Rails tambin proporciona un montn de funciones, atajos y utilidades que ahorran tiempo (la primera que se me viene a la cabeza es la maravillosa options_from_collection_for_select). 10. Rollo legal: este es un documento de libre difusin (licencia Creative Commons "by-nc-sa") con algunas limitaciones. Leer el texto de la licencia completo para ms detalles. Subir (cc) David Asorey lvarez Mayo de 2007 David.Asorey.Alvarez@gmail.com

You might also like