jueves, 4 de noviembre de 2010

Tutorial Django

En esta entrada publicare un ejemplo para que se conozca mejor lo que es una aplicacion en Django. Espero les parezca interesante y en un futuro muy util.Saludos

Estructura de (una aplicación) 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. . 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 módulo Python válido.
  • manage.py: Utilidad para gestionar nuestro proyecto: arrancar servidor de pruebas, sincronizar mod elos, etc.
  • settings.py: Configuración del proyecto.
  • urls.py: Gestión de las urls. Este fichero sería el controlador de la aplicación. Mapea las url entrantes a funciones Python definidas en módulos.

Para crear una aplicación 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 módulo Python válido.
  • models.py: Aquí se definen los modelos u objetos que serán mapeados a una base de datos relacional.
  • views.py: Define las funciones que van a responder a las urls entrantes.

Esto es un diseño MVC: modelo (models.py), vista (views.py), controlador(urls.py).

Aclaración: los desarrolladores de Django llaman a su arquitectura MVT: Model - View - Template, ya que consideran que el controlador es el propio framework.

Aplicación web: Trivial trivial

Es un Trivial multiusuario..(es un juego de mesa donde el avance está determinado por la habilidad del jugador para contestar pregun tas sobre conocimientos generales)

Especificaciones:

  • Las preguntas y los usuarios los crea un administrador.
  • Existen diferentes categorías 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 aplicación de prueba, usaremos el servidor de desarrollo q ue viene con Djang o. Los ficheros estáticos (CSS, imágenes, etc) también los servirá Django, Como sistema relacional usaremos sqlite (hay un "driver" para Python).

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.

Tras esta breve disquisición, pasemos a definir los modelos. Encontramos las siguientes entidades:

  • Usuario, caracterizado por un nombre, "login", contraseña, ...
  • Categoría
  • Pregunta, con un título, conjunto de respuestas posibles, respuesta correcta, un gráfico asociado, ...
  • Respuesta, asociada a un usuario concreto y una pregunta concreta, guardando el resultado (acierto/fallo), etc.

Creación del proyecto y la aplicación

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

Ajustamos algunos parámetros en settings.py y urls.py. Habilitaremos la interfaz administrativa , el directorio desde el que se sirven los contenidos estáticos y algunos ajustes más.

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 producción, sólo tendremos que eliminar la última entrada en urls.py

y editar MEDIA_URL en settings.py.

También tenemos que crear la base de datos sqlite (comando sqlite data/datos.db).

Ahora, desde el directorio Trivial (directorio raíz) creamos la aplicación 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 autentificación y gestión de usuarios, así que no vamos a reinventar la rueda y utilizaremos este sistema .

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("Categoría", maxlength=200)

def __str__(self):
return self.nombre

class Admin:
    pass


class Pregunta(models.Model):
   categoria = models.ForeignKey(Categoria, verbose_na
me="Categoría la que pertenece")
titulo = models.CharField("Título", 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 decíamos antes, "mejor explícito que implícito". Definimos un método __str__ en todas las clases para tener una descripción "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 información que tiene de los modelos con el sistema relacional (vamos, que cree las tablas necesarias): python manage.py syncdb Este comando también creará las tablas necesarias para la aplicación administrativa y el sistema de gestión 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 administración:

Lo primero que hemos hecho ha sido crear un grupo (Concursantes) y asignarle el permiso de "crear objetos del tipo Respuesta". Después creamos unos cuantos usuarios y les hacemos miembros del grupo. Ya tenemos un sistema de control de acceso, permisos bastante granulares (estos usuarios sólo podrán crear objetos del tipo Respuesta, pero no modificarlos o borrarlos) sin escribir nada de código.

Siguente: implementando la autentificación

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
template de validación (registration/login.html por defecto): funciona de la siguiente manera : si el usuario no está autentificado, redirige a una plantilla o

Si está autentificado, la función "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 categorías. Éste sería nuestro "index.html", pero, como hemos dicho, queremos que el usuario se valide antes. Veamos cómo hacerlo.

En urls.py añadimos una entrada para "mapear" la dirección "/" (raíz del sitio) a la función

"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',

Y nuestra función en
views.py sería algo así:
(r'^site_media/(.*)$', 'static.serve', {'document_root': MEDIA_ROOT}),
@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 categorías, preguntas
y respuestas del usuario validado y se las "pasa" a una plantilla o
template llamada "index.html". También 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.

{% extends "base.html" %}

{% block cuerpo %}

Listado de preguntas
{% if categorias %}
{% regroup preguntas by categoria as agrupado %}

    {% for grupo in agrupado %}
  • {{ grupo.grouper }}


    • {% for item in grupo.list %}
    • {{ item.titulo }}

      {% for r in respuestas %}

      {% ifequal item r.pregunta %}
      La pregunta ya ha sido respondida.
      {% endifequal %}
      {% endfor %}

    • {% endfor %}



    {% endfor %}



{% else %}


No hay categorías



{% endif %}

Desconectar




{% endblock %}

en este template comprobamos si hay categorías {% if
categorias %}
y mostramos en forma de listas anidadas todas las
preguntas de cada categoría 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 %}
La pregunta ya ha sido respondida.
{% endifequal %}
{% endfor %}

En este template también estamos utilizando una característica 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í quedaría nuestra pantalla inicial:

Formulario de preguntas

Cuando el usuario sigue el enlace ({{ item.titulo }}) que presenta cada pregunta en la plantilla index.html se le dirige a la página 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 función "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 función 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 excepción, 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 %}      

Categoría: {{ pregunta.categoria

{{ pregunta.titulo }

{% if texto_error %}

{{ texto_error }}

{% endif %}

{{ pregunta.texto }}

{% if respuesta %}

Ya has respondido antes a la pregunta.

Tiempo empleado: {{ respuesta.tiempo }} segundos.

El resultado fue

{% if respuesta.resultado %} CORRECTO {% else %} INCORRECTO {% endif %}

{% else %}
name="respuesta">{{ pregunta.respuesta_1 }}
name="respuesta">{{ pregunta.respuesta_2 }}
name="respuesta">{{ pregunta.respuesta_3 }}
name="respuesta">{{ pregunta.respuesta_4 }}

{% endif %} {% endblock %}

Nos encontramos en esta plantilla con una variable (texto_error) que no hemos asignado desde la función pregunta. Esta variable puede tener un valor cuando esta plantilla es invocada desde otra función definida en views.py (respuesta). Lo veremos un poco más 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/

Respondiendo a la pregunta

De nuevo, añadimos una regla al fichero urls.py para procesar las respuestas de los usuarios. El fichero quedaría así (versión 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 función 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 opción" 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'). Después comprobamos que han pulsado uno de los "radiobutton" de respuesta (request.POST['respuesta']). Si no han respondido, redirigimos de nuevo a la página de pregunta pasando un mensaje de error.

Si han respondido, creamos un objeto Respuesta asociado a la pregunta y al usuario. También asignamos a esta respuesta el tiempo que se ha tardado en resolver la pregunta y el resultado. Después, redirigimos a la plantilla 'respuesta.html' pasando el objeto pregunta, el objeto respuesta recién creado y la opción que habían seleccionado.

respuesta.html:

{% extends "base.html" %}

{% block cuerpo %}

Resultado de la pregunta


{{ pregunta.titulo }}




{{ pregunta.texto }}


Respuesta seleccionada: {{ opcion }}


Respuesta correcta: {{ pregunta.respuesta_correcta }}


{% if respuesta.resultado %}

¡Enhorabuena!


{% else %}

¡Has fallado!


{% endif %}

Tiempo empleado: {{ respuesta.tiempo }} segundos.


{% endblock %}

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


Conclusiones

  • Hemos hecho una aplicación web muy básica 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 autentificación nos han salido "gratis". Probablemente, la implementación de estas dos funcionalidades nos hubiesen llevado bastante tiempo.
  • Observamos también la extrema sencillez en el desarrollo: con cuatro elementos con unas funciones muy concretas (urls, modelos, vistas y plantillas) tenemos perfectamente separadas la lógica, los datos y la presentación. Compárese esta simplicidad con la típica aplicación J2EE, por sencilla que sea. No hay color. Que las soluciones basadas en J2EE sean más "potentes", escalables, robustas, etc. no lo voy a negar. Pero, ¿acaso se necesita siempre esa "potencia"?.
  • No hemos desarrollado una aplicación similar con Rails, por lo que no sería muy justo decir que Django es más fácil o mejor. Lo que si que podemos decir, por lo que hemos visto y leído es que Django aporta varias características que ahorran mucho trabajo, en este caso la interfaz administrativa y el sistema de autentificación

4 comentarios:

  1. por favor necesito ayuda en la autenticacion a una aplicacion para poder acceder desde un formulario index que pueda utilizar el login de django.. ayudaaaaaaa....!!!

    ResponderEliminar
  2. aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

    ResponderEliminar