Diferencia entre revisiones de «Curso Python DGA 2011/django/contenidos»
(5 revisiones intermedias por el mismo usuario no mostrado) | |||
Línea 2: | Línea 2: | ||
{{MiTitulo| Contenido}} | {{MiTitulo| Contenido}} | ||
− | {{ | + | {{TOC|right}} |
== Evolución del desarrollo web == | == Evolución del desarrollo web == | ||
Línea 24: | Línea 24: | ||
* Template (plantillas): capa de Presentación. Describe cómo se van a presentar los datos al usuario. | * Template (plantillas): capa de Presentación. Describe cómo se van a presentar los datos al usuario. | ||
− | + | {{:Curso_Python_DGA_2011/django/intro}} | |
− | + | {{:Curso_Python_DGA_2011/django/admin}} | |
− | + | {{:Curso_Python_DGA_2011/django/contenidos/vistas}} | |
− | + | {{:Curso_Python_DGA_2011/django/forms}} | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | }} | + | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | {{ | + | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + |
Última revisión de 04:37 26 ago 2011
Contenido
- 1 Evolución del desarrollo web
- 2 Django
- 3 Instalación
- 4 Creación de un proyecto
- 5 Servidor web de desarrollo
- 6 Configuración del proyecto
- 7 Proyectos y aplicaciones
- 8 Definición de modelos
- 9 Acceso a los datos
- 10 El poderoso Admin de Django
- 11 Creación de la interfaz pública
- 12 Formularios
Evolución del desarrollo web
- Páginas HTML estáticas
- CGI: código para generar páginas web dinámicas:
- Código embebido en páginas web (PHP, JSP, ASP, ...)
- Frameworks web:
- RoR, Struts, Symphony
- Django, Pylons, Web2Py
Django
Django permite a los desarrolladores crear de forma rápida sitios web de altas prestaciones basados en información almacenada en bases de datos bajo el principio de no repetición (Do Not Repeat Yourself: DRY).
El nombre
Adrian Holovaty, co-creador de Django, es un guitarrista inspirado por Django Reinhardt.
MVC / MVT
MTV: Desarrollo web según un patrón Modelo - Vista - Template (similar al patrón MVC)
- Modelo: capa de Datos. Describe los datos que gestiona la aplicación. Cada modelo enlaza una tabla en la base de datos.
- Vista: capa de Negocio. Describe a qué datos hay que acceder y los transforma.
- Template (plantillas): capa de Presentación. Describe cómo se van a presentar los datos al usuario.
Instalación
- Comprobamos que está instalado (y la versión)
>>> import django >>> django.VERSION (1, 3, 0, 'final', 0)
Creación de un proyecto
$ django-admin.py startproject sitio_encuestas $ ls -l sitio_encuestas/ __init__.py manage.py settings.py urls.py
Un proyecto en Django son 4 ficheros:
- __init__.py
- Indica que se trata de un paquete.
- manage.py
- Programa de gestión de django. A partir de ahora será el que usemos para interactuar con el proyecto.
- settings.py
- Fichero de configuración del proyecto.
- urls.py
- Gestiona la tabla de urls a las que responde el proyecto.
Servidor web de desarrollo
El proyecto incorpora un servidor web:
$ cd sitio_encuestas $ python manage.py runserver Validating models... 0 errors found Django version 1.3, using settings 'sitio_encuestas.settings' Development server is running at http://127.0.0.1:8000/ Quit the server with CONTROL-C.
Comprobamos la dirección con el navegador o
$ curl -I 127.0.0.1:8000 HTTP/1.0 200 OK Date: Mon, 22 Aug 2011 20:33:33 GMT Server: WSGIServer/0.1 Python/2.7.1+ Content-Type: text/html
$ python manage.py runserver 8080
y También la ip:
$ python manage.py runserver 0.0.0.0:8000
Este servidor ligero nos servirá para desarrollar el proyecto. No está pensado para sitios en producción.
Para usar Django en producción, revisa la documentación: https://docs.djangoproject.com/en/1.3/howto/deployment/
Configuración del proyecto
Editar settings.py
Rutas absolutas
Como la configuración necesita rutas absolutas, podemos añadir esta función a settings.py:
import os def ruta_abs(x): return os.path.join(os.path.abspath(os.path.dirname(__file__)), x)
Configuración de la base de datos
Por defecto, tiene sopote para Postgresql, MySQL, Sqlite y Oracle.
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. 'NAME': '', # Or path to database file if using sqlite3. 'USER': '', # Not used with sqlite3. 'PASSWORD': '', # Not used with sqlite3. 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. 'PORT': '', # Set to empty string for default. Not used with sqlite3. } }
Para usar sqlite:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. 'NAME': ruta_abs('encuestas.db'), # Or path to database file if using sqlite3. } } <source> Para usar otra base de datos, tenemos que configurar el servidor con una base de datos y un usuario que tenga acceso a ella. ===configuración regional=== <source lang="python"> TIME_ZONE = 'Europe/Madrid' LANGUAGE_CODE = 'es-es'
Aplicar los cambios
$python manage.py syncdb
Crea las tablas necesarias para el desarrollo de proyecto, incluyendo todas las aplicaciones incuidas en INSTALLED_APPS
Proyectos y aplicaciones
Para optimizar los recursos, un proyecto puede tener varias aplicaciones. Estas aplicaciones se pueden instalar en varios proyectos. Por eso se dice que las aplicaciones de django son "pluglables".
Creación de una aplicación
$ python manage.py startapp encuestas
Esto creará el siguiente directorio:
encuestas/
__init__.py
models.py
views.py
Incluir la aplicación en el proyecto
Hay que añadir la nueva apliación a INSTALLED_APPS dentro de settings.py
INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', 'sitio_encuestas.encuestas', )
Definición de modelos
models.py
from django.db import models class Encuesta(models.Model): pregunta = models.CharField(max_length=200) fecha_pub = models.DateTimeField(u'fecha de publicación') def __unicode__(self): return self.pregunta class Opcion(models.Model): encuesta = models.ForeignKey(Encuesta) opcion = models.CharField(max_length=200) votos = models.IntegerField() def __unicode__(self): return self.opcion
- Tipos de datos: https://docs.djangoproject.com/en/1.3/ref/models/fields/
- Más opciones
Ver sql
$ python manage.py sql encuestas
Esto hará que veamos el siguiente código SQL:
BEGIN; CREATE TABLE "encuestas_encuesta" ( "id" INTEGER NOT NULL PRIMARY KEY, "pregunta" VARCHAR(200) NOT NULL, "fecha_pub" datetime NOT NULL ) ; CREATE TABLE "encuestas_opcion" ( "id" INTEGER NOT NULL PRIMARY KEY, "encuesta_id" INTEGER NOT NULL REFERENCES "encuestas_encuesta" ("id"), "opcion" VARCHAR(200) NOT NULL, "votos" INTEGER NOT NULL ) ; COMMIT;
Syncdb
Si el código es correcto, ejecutamos:
$ python manage.py syncdb Creating table encuestas_encuesta Creating table encuestas_opcion You just installed Django's auth system, which means you don't have any superusers defined. Would you like to create one now? (yes/no): yes Username (Leave blank to use 'lm'): lm E-mail address: lm@micorreo.com Password: Password (again): Superuser created successfully. Installing custom SQL ... Installing indexes ... No fixtures found.
Si las tablas tienen datos, ya no modificará la estructura. Soluciones: eliminar los datos de esa aplicación o usar South: http://south.aeracode.org/
Acceso a los datos
$ python manage.py shell
>>> from encuestas.models import Encuesta, Pregunta # Importa las clases del modelo # No hay encuestas >>> Encuesta.objects.all() [] # Creamos una nueva encuesta >>> import datetime >>> p = Encuesta(pregunta="¿Cómo va el curso?", fecha_pub=datetime.datetime.now()) # Guardamos el objeto en la base de datos >>> p.save() # Creación de ID automática >>> p.id 1 # Acceso a los valores >>> p.pregunta "¿Cómo va el curso?" >>> p.fecha_pub datetime.datetime(2011, 7, 15, 12, 00, 53) # Cambio de valores de los atributos >>> p.pub_date = datetime.datetime(2011, 7, 20, 0, 0) >>> p.save() # objects.all() muestra todas las encuestas de la base de datos >>> Encuesta.objects.all() [<Encuesta: ¿Cómo va el curso?]
Vemos el contenido de la encuesta porque hemos añadido el método __unicode__. Si no, veríamos <Encuesta: Encuesta object> Se crea el método __unicode__ y no __str__ porque los modelos de Django tratan con unicode por defecto.
# API de búsquedas de Django >>> Encuesta.objects.filter(id=1) [<Encuesta: ¿Cómo va el curso?>] >>> Encuesta.objects.filter(pregunta__startswith='¿Cómo') [<Encuesta: ¿Cómo va el curso?>] # Obtener las encuestas de 2011 >>> Encuesta.objects.get(fecha_pub__year=2011) <Encuesta: ¿Cómo va el curso?> >>> Encuesta.objects.get(id=2) Traceback (most recent call last): ... DoesNotExist: Encuesta matching query does not exist. # Búsqueda por clave primaria >>> Encuesta.objects.get(pk=1) <Encuesta: ¿Cómo va el curso?> # Vamos a insertar unas opciones para ver cómo funcionan las relaciones. # Podemos crear las opciones desde la misma encuesta: >>> p = Encuesta.objects.get(pk=1) # Opciones relacionadas con nuestra encuesta: >>> p.opcion_set.all() [] # Creamos tres opciones >>> p.opcion_set.create(opcion='Muy bien', votos=0) <Opcion: Muy bien> >>> p.opcion_set.create(opcion='Bien', votos=0) <Opcion: Bien> >>> c = p.opcion_set.create(opcion='Regular', votos=0) # Los objetos Opción tienen acceso desde la API a la encuesta a la que pertenecen: >>> c.encuesta <Encuesta: ¿Cómo va el curso?> # Y viceversa. Los objetos Encuesta acceden a sus Opciones: >>> p.opcion_set.all() [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>] >>> p.opcion_set.count() 3 # La API sigue de forma automática las relaciones. # Para separar las relaciones se usan dobles guiones bajos. # Busca todas las Opciones para cualquier encuesta publicada en 2011 >>> Opcion.objects.filter(encuesta__fecha_pub__year=2011) [<Opcion: Muy bien>, <Opcion: Bien>, <Opcion: Regular>] # Elimina una de las opciones: >>> c = p.opcion_set.filter(opcion__startswith='Regular') >>> c.delete()
Otros métodos en los modelos
Podemos añadir métodos normales a los modelos:
import datetime class Encuesta(models.Model): #... def publicada_hoy(self): return self.fecha_pub.date() == datetime.date.today()
Y podemos usarlos así:
>>> p = Escuesta.objects.get(pk=1) >>> p.publicada_hoy() False
Más información sobre las relaciones entre modelos: Acceder a objetos relacionados. Para más infor sobre cómo usar los dobles guiones bajos para hacer búsquedas por campos mediante la API: búsquedas mediante los campos Para más detalles sobre la API de la base de datos: Referencia de la Base de Datos.
El poderoso Admin de Django
- El Admin es una herramienta pensada para el "backend", no para el acceso de los usuarios
- Simplifica la tarea de insertar, modificar y eliminar contenido de la web.
Activar el Admin
- Por defecto está desactivado.
- Añade django.contrib.admin a la lista de INSTALLED_APPS en settings.py.
- Ejecuta
$ python manage.py syncdb
- Edita el fichero urls.py de tu proyecto y descomenta las líneas que hacen referencia al admin (3 líneas en total)
El fichero tiene que quedar al final algo así:
from django.conf.urls.defaults import patterns, include, url
# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
# Examples:
# url(r'^$', 'sitio_encuestas.views.home', name='home'),
# url(r'^sitio_encuestas/', include('sitio_encuestas.foo.urls')),
# Uncomment the admin/doc line below to enable admin documentation:
# url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin:
url(r'^admin/', include(admin.site.urls)),
)
Lanzar Admin
$ python manage.py runserver
$ curl http://127.0.0.1:8000/admin/ -I HTTP/1.0 200 OK Date: Tue, 23 Aug 2011 15:33:25 GMT Server: WSGIServer/0.1 Python/2.7.1+ Last-Modified: Tue, 23 Aug 2011 15:33:25 GMT Expires: Tue, 23 Aug 2011 15:33:25 GMT Content-Type: text/html; charset=utf-8 Vary: Cookie Cache-Control: max-age=0 Set-Cookie: csrftoken=b53bd036b15251d45ed07b15c60e5116; expires=Tue, 21-Aug-2012 15:33:25 GMT; Max-Age=31449600; Path=/ Set-Cookie: sessionid=2d639f8c86e4e380698c1991fec9d848; expires=Tue, 06-Sep-2011 15:33:25 GMT; Max-Age=1209600; Path=/
HTTP/1.0 301 MOVED PERMANENTLY Date: Tue, 23 Aug 2011 15:35:12 GMT Server: WSGIServer/0.1 Python/2.7.1+ Content-Type: text/html; charset=utf-8 Location: http://127.0.0.1:8000/admin/ HTTP/1.0 200 OK Date: Tue, 23 Aug 2011 15:35:12 GMT Server: WSGIServer/0.1 Python/2.7.1+ Last-Modified: Tue, 23 Aug 2011 15:35:12 GMT Expires: Tue, 23 Aug 2011 15:35:12 GMT Content-Type: text/html; charset=utf-8 Vary: Cookie Cache-Control: max-age=0 Set-Cookie: csrftoken=3365df1f4fc610f6d4e9a7ea5f5d97b2; expires=Tue, 21-Aug-2012 15:35:12 GMT; Max-Age=31449600; Path=/ Set-Cookie: sessionid=1206648ff52d951049b0d830e66a12d9; expires=Tue, 06-Sep-2011 15:35:12 GMT; Max-Age=1209600; Path=/
Puedes probar la dirección en el navegador: http://127.0.0.1:8000/admin/
Usuarios
Tendrás que entrar con el superurser que creaste la hacer syncdb. Si no recuerdas, puedes crear otro superusuario:
$ python manage.py createsuperuser
Hacer el proyecto modificable por el Admin
La aplicación Encuestas no aparece. Hay que activarla dentro del admin:
- Crea un fichero admin.py dentro de la aplicación encuestas con este contenido:
from encuestas.models import Encuesta
from django.contrib import admin
admin.site.register(Encuesta)
- Reinicia el servidor de desarrollo. El servidor no detecta la creación de un nuevo archivo.
Se ha creado un completo entorno CRUD para nuestras encuestas:
- Se genera un formulario automático para el modelo
- Incluye validación de campos
Configuración del formulario
La configuración se hace mediante el archivo admin.py creado.
- Preparamos una clase especial para las encuestas (EncuestaAdmin)
- Indicamos a admin que use esa clase para gestionar nuestro modelo
admin.site.register(Encuesta, EncuestaAdmin)
Vamos a cambiar el orden en que aparecen los datos:
# -*-coding: utf-8 -*-
from encuestas.models import Encuesta
from django.contrib import admin
class EncuestaAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['pregunta']}),
('Publicación', {'fields': ['fecha_pub']}),
]
admin.site.register(Encuesta, EncuestaAdmin)
Se pueden asignar clases html a cada fielset para personalizar la prsentación. Django ofrece una clase collapse que muestra el fielset sin expandir por defecto. Útil cuando hay que distribuir muchos contenidos.
class PollAdmin(admin.ModelAdmin):
fieldsets = [ (None, {'fields': ['question']}), ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}), ]
Añadir objetos relacionados
Nuestro Admin todavía no muestra las opciones para las encuestas. Tenemos dos opciones:
- Registrar Opcion con el admin como hemos hecho con las Encuestas
from polls.models import Choice admin.site.register(Choice)
- Admin crea de forma automática un <select> box para la clave foránea, que incluye todas las encuestas de la base de datos.
- Podemos crear las Opciones desde la misma encuesta.
from encuestas.models import Encuesta, Opcion
from django.contrib import admin
class OpcionInline(admin.TabularInline):
model = Opcion
extra = 3
class EncuestaAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['pregunta']}),
('Publicación', {'fields': ['fecha_pub'], 'classes': ['collapse']}),
]
inlines = [OpcionInline]
admin.site.register(Encuesta, EncuestaAdmin)
Con el inline indicamos a Django que los objetos Opcion se editan en la página de administración de Encuestas y que por defecto añade los campos suficientes para añadir 3 Opciones más.
Admin ofrece dos tipos de inlines: StackedInline y TabularInline.
Modificar la página de listados
La página inicial muestra el listado de todas las Encuestas del sistema. Por defecto Django muestra el str() de cada objeto. Pero es más usable mostrar campos individuales. Useremos la opción list_display, que es una tupla de nombres de campos (también podemos añadir los métodos creados).
class EncuestaAdmin(admin.ModelAdmin):
# ...
list_display = ('pregunta', 'fecha_pub', 'publicada_hoy')
Podemos especificar el nombre de la columna:
def publicada_hoy(self):
return self.fecha_pub.date() == datetime.date.today()
publicada_hoy.short_description = '¿Publicada hoy?'
Filtros
Añade a EncuestaAdmin
list_filter = ['fecha_pub']
Añade una columna de filgro que permite filtrar los listados por el campo de fecha de publicación. El filtro se muestra según el tipo del campo de filtrado.
Búsqueda
search_fields = ['pregunta']
Añade una caja de búsqueda en la parte superior de los listados. Usa LIKE en las búsquedas.
Fechas
Como las encuestas tienen un campo con fechas podemos facilitar la navegación cronológica:
date_hierarchy = 'fecha_pub'
Paginación
list_per_page = 100
Personalizar la presentación
No tiene sentido ver "Django administration" en la parte superior de las páginas.
Es fácil cambiar usando el sistema de plantillas de Django:
- Configura TEMPLATE_DIRS (tupla de rutas donde busca plantillas Django) en el fichero settings.py.
TEMPLATE_DIRS = ( "/home/my_username/mytemplates", # configura la ruta: rutas absolutas. )
- Copia la plantilla admin/base_site.html' del directorio de plantillas de Django(django/contrib/admin/templates) a un directorio admin dentro de una de las rutas que has configurado en el paso anterior. Por ejemplo, si la ruta era /home/mi_usuario/sitio_encuestas/plantillas, habrá que copiar la plantilla como /home/my_username/sitio_encuestas/plantillas/admin/base_site.html
- Edita la plantilla. La pantilla tiene textos como {% block branding %} y {{ title }} Esas etiquetas {% y {{ son parte del elenguaje de plantillas de Django.
Configurar la página índice
Por defecto aparecen todas las aplicaciones de INSTALLED_APPS por orden alfabético. Para eso hay que cambiar la plantilla admin/index.html. En lugar de usar la variable app_list, puedes forzar las aplicaciones como quieras.
Creación de la interfaz pública
En la filosofía de Django una vista es un tipo de página web que sirve para una función específica y tiene una plantilla específica. Por ejemplo en un blog podemos tener:
- La página inicial del Blog (muestra las últimas entradas)
- El detalle de una entada
- Página de las entradas por ffechas (día, mes, año)
- Comentarios
En la aplicación de las encuestas:
- Página índice de Encuestas, que muestre las últimas encuestas
- Página de detalle de encuestas: Muestra una pregunta con el formulario para votar
- Página de resultados de una encuesta.
Philosophy A view is a “type” of Web page in your Django application that generally serves a specific function and has a specific template. For example, in a Weblog application, you might have the following views:
Blog homepage – displays the latest few entries. Entry “detail” page – permalink page for a single entry. Year-based archive page – displays all months with entries in the given year. Month-based archive page – displays all days with entries in the given month. Day-based archive page – displays all entries in the given day. Comment action – handles posting comments to a given entry. In our poll application, we’ll have the following four views:
En Django cada vista la gestiona una fución.
Diseño de URLs
El primer paso para escribir una vista es diseñar la estructura de la URL. Django asocia una URL con código Python mediante el módulo URLconf.
Las urls se controlan con la variable urlpatterns, que es una secuencia de tuplas con el siguiente formato:
(expresión regular, function python [, diccionario opcional])
Django busca por orden y lanza la primera función que cumple el patrón. Cuando Django la encuentra, llama a la función Python con un objeto HttpRequest como primer argumento, valores capturados de la url como argumentos por clave y otros argumentos opcionales (tercer elemento de la tupla)
Al crear el proyecto se crea un URLconf por defecto en urls.py Esto se configura en settings.py (ver variable ROOT_URLCONF)
ROOT_URLCONF = 'mysite.urls'
Ejemplo. Edita urls.py como el siguiente ejemplo:
from django.conf.urls.defaults import patterns, include, url
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
(r'^encuestas/$', 'encuestas.views.index'),
(r'^encuestas/(?P<encuesta_id>\d+)/$', 'encuestas.views.detalle'),
(r'^encuestas/(?P<encuesta_id>\d+)/resultado/$', 'encuestas.views.resultado'),
(r'^encuestas/(?P<encuesta_id>\d+)/votar/$', 'encuestas.views.votar'),
url(r'^admin/', include(admin.site.urls)),
)
Según nuestro ejemplo si el usuario pide
/encuestas/23
Django ejecutará
detalle((request=<HttpRequest object>, encuesta_id='23')
Los paréntesis de la expresión regular capturan el texto que coincide con el patrón.
El módulo URLconf compila las expresiones regulares para que se ejecuten más rápidas.
Primera vista
Todavía no hemos creado ninguna vista. Vamos a comprobar que el URLconf funciona bien.
python manage.py runserver
Ve a "http://localhost:8000/encuestas/" con el navegador. Tenemos que ver el error de que la vista no existe.
Exception Type: ViewDoesNotExist
Escribimos las vistas en encuestas/views.py
from django.http import HttpResponse
def index(request):
return HttpResponse(u"Hola, este es el índice de las encuestas.")
def detalle(request, encuesta_id):
return HttpResponse(u"Estás viendo la encuesta %s." % encuesta_id)
def resultado(request, encuesta_id):
return HttpResponse(u"Estás viendo el resultado de la encuesta %s." % encuesta_id)
def votar(request, encuesta_id):
return HttpResponse(u"Estás votando la encuesta %s." % encuesta_id)
Vistas que hacen algo
Ahora vamos a escribir vistas que hagan algo realmente Las vistas tienen que devolver:
- El objeto HttpResponse con el contenido de la página solicitada o
- Una excepción (por ej. Http404)
index
def index(request):
# Extrae 5 últimas encuestas de la bbdd. Orden: la última primero.
listado_ultimas = Encuesta.objects.all().order_by('-fecha_pub')[:5]
# prepara el resultado para mostrarlo
output = ', '.join([p.pregunta for p in listado_ultimas])
return HttpResponse(output)
Problema: el diseño de la página está integrado en la vista. Si queremos cambiar el aspecto de la página, hay que editar código python. Idea: separar código y presentación usando el sistema de plantillas de Django:
from django.http import HttpResponse
from django.template import Context, loader
from encuestas.models import Encuesta
def index(request):
# Extrae 5 últimas encuestas de la bbdd. Orden: la última primero.
listado_ultimas = Encuesta.objects.all().order_by('-fecha_pub')[:5]
t = loader.get_template('encuestas/index.html')
c = Context({
'listado_ultimas': listado_ultimas,
})
return HttpResponse(t.render(c))
Primera plantilla
Es necesario crear la plantilla en una ruta incluida en TEMPLATE_DIRS. Editamos [directorio_plantillas]/encuestas/index.html" Por> seguridad no se extraen las plantillas de la raíz del proyecto.
{% if listado_ultimas %} <ul> {% for encuesta in listado_ultimas %} <li><a href="/encuestas/{{ encuesta.id}}/">{{ encuesta.pregunta }}</a></li> {% endfor %} </ul> {% else %} <p>No hay encuestas disponibles.</p> {% endif %}
Atajo: render_to_response()
¿Para qué repetir tanto? Ya no es necesario importar loader, Context y HttpResponse.
from django.shortcuts import render_to_response
from encuestas.models import Encuesta
def index(request):
listado_ultimas = Encuesta.objects.all().order_by('-fecha_pub')[:5]
return render_to_response('encuestas/index.html',
{'listado_ultimas': listado_ultimas,})
Lanzando una respuesta 404 (no encontrado)
from django.shortcuts import render_to_response
from django.http import Http404
from encuestas.models import Encuesta
# ...
def detalle(request, encuesta_id):
try:
enc = Encuesta.objects.get(pk=encuesta_id)
except Encuesta.DoesNotExist:
raise Http404 # Si la encuesta no existe, lanza un httpresponse: 404
return render_to_response('encuestas/detalle.html', {'encuesta': enc})
Creamos una plantilla para encuestas/detalle.html:
{{encuesta}}
Resultado:
$ curl -I http://127.0.0.1:8000/encuestas/12/ HTTP/1.0 404 NOT FOUND Date: Wed, 24 Aug 2011 05:27:20 GMT Server: WSGIServer/0.1 Python/2.7.1+ Content-Type: text/html
Otro atajo: get_object_or_404()
from django.shortcuts import render_to_response, get_object_or_404
# ...
def detalle(request, encuesta_id):
enc = get_object_or_404(Encuesta, pk=encuesta_id)
return render_to_response('encuestas/detalle.html', {'encuesta': enc})
La función get_object_or_404() toma como primer argumento un modelo de Django y un número no determinado de argumentos por clave. Si el objeto no existe, lanza un Http404.
Si buscamos una secuencia de objetos, podemos usar get_list_or_404(), que usa filter() en lugar de get(). Lanza Http404 si la lista está vacía.
Escribir una vista 404 (página no encontrada)
Cuando DEBUG está activado, la vista 404 no se usa y Django muestra el error. Cuando DEBUG está a False, va a buscar un template 404.html en la raíz del directorio de templates. Si no lo encuentra, lanza un Http500. También hay que escribir una plantilla para 500.html.
Plantilla de detalle.html
<h1>{{ encuesta.pregunta }}</h1> <ul> {% for opcion in encuesta.opcion_set.all %} <li>{{ opcion.opcion }}</li> {% endfor %} </ul>
El sistema de plantillas usa la sintaxis del punto para accceder a los atributos. Fíjate en como usa el {% for %} y como accede a las opciones desde el objeto encuestas
encuesta.opcion_set.all
equivale a
encuesta.opcion_set.all()
Que devuelve el iterable que recorre el for.
Más información en la guía de plantillas
Simplificando URLconfs
Podemos usar patrones para evitar repeticiones:
urlpatterns = patterns('encuestas.views',
(r'^encuestas/$', 'index'),
(r'^encuestas/(?P<encuesta_id>\d+)/$', 'detalle'),
(r'^encuestas/(?P<encuesta_id>\d+)/resultado/$', 'resultado'),
(r'^encuestas/(?P<encuesta_id>\d+)/votar/$', 'votar'),
)
urlpatterns += patterns('',
(r'^admin/', include(admin.site.urls)),)
También se pueden desacoplar, para hacer más reutilizables las aplicaciones (aplicaciones pluggables) Pasos:
- Movemos sitio_encuestas/urls.py a encuestas/urls.py
- Cambiamos sitio_encuestas/urls.py para eliminar las urls de encuestas e insertamos un include():
from django.conf.urls.defaults import *
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
(r'^encuestas/', include('encuestas.urls')),
(r'^admin/', include(admin.site.urls)),
)
Así quedará encuestas/urls.py
from django.conf.urls.defaults import *
urlpatterns = patterns('encuestas.views',
(r'^$', 'index'),
(r'^(?P<encuesta_id>\d+)/$', 'detalle'),
(r'^(?P<encuesta_id>\d+)/resultado/$', 'resultado'),
(r'^(?P<encuesta_id>\d+)/votar/$', 'votar'),
)
Como ahora tienen una ruta relativa, podemos instalar la aplicación en distintas URLs: /encuestas, /encuestas_alumnos, /encuestas_corporativas y la aplicación seguirá funcionando.
Formularios
Primer formulario
Vamos a actualizar la plantilla del detalle (encuesta/detalle.html) para que tenga una etiqueta <form>
<h1>{{ encuesta.pregunta }}</h1> {% if mensaje_error %}<p><strong>{{ mensaje_error }}</strong></p>{% endif %} <form action="/encuestas/{{ encuesta.id }}/votar/" method="post"> {% csrf_token %} {% for opcion in encuesta.opcion_set.all %} <input type="radio" name="opcion" id="opcion{{ forloop.counter }}" value="{{ opcion.id }}" /> <label for="opcion{{ forloop.counter }}">{{ opcion.opcion }}</label><br /> {% endfor %} <input type="submit" value="Votar" /> </form>
Mirando el código:
- Se usa un radio button para cada opción de la encuesta. El valor de cada opción está asociado al de su id. El nombre de los radio es opcion, así cuando un usuario selecciona una opción y pulsa el botón de votar, envia por POST la información opcion=3 (con el número de id elegido)
- La acción del formulario es /encuestas/Plantilla:Encuesta.id/votar. Los datos se envían por POST. Muy importante porque enviar datos al servidor puede alterar el estado (a diferencia de GET)
- el forloop.counter indica cuántas veces ha entrado en el bucle.
- Se usa {% csrf_token %} para evitar Cross Site Request Forgeries. Importante usarlo siempre con POST. Para permitir el trabajo de csrf hay que añadir un método a la vista detalle.
from django.template import RequestContext
# ...
def detalle(request, encuesta_id):
enc = get_object_or_404(Encuesta, pk=encuesta_id)
return render_to_response('encuestas/detalle.html',
{'encuesta': enc},
context_instance=RequestContext(request))
Acción del formulario
Según la entrada de URLconf, cuando ejecutamos la acción de votar, se llama a la función votar, que gestiona los datos enviados y hace algo con ellos:
(r'^(?P<encuesta_id>\d+)/votar/$', 'votar')
La acción puede quedar así:
from django.shortcuts import render_to_response, get_object_or_404
from django.http import HttpResponseRedirect, HttpResponse
from django.core.urlresolvers import reverse
from django.template import RequestContext
from encuestas.models import Encuesta, Opcion
# ...
def votar(request, encuesta_id):
enc = get_object_or_404(Encuesta, pk=encuesta_id)
try:
opcion_seleccionada = enc.opcion_set.get(pk=request.POST['opcion'])
except (KeyError, Opcion.DoesNotExist):
# Vuelve a mostrar el formulario de votación
return render_to_response('encuestas/detalle.html', {
'encuesta': enc,
'mensaje_error': "No has seleccionado ninguna opción.",
}, context_instance=RequestContext(request))
else:
opcion_seleccionada.votos += 1
opcion_seleccionada.save()
# Siempre devuelve un HttpResponseRedirect después de tratar con éxito
# un formulario POST. Así se evita enviar dos veces la información cuando
# el usuario pulsa el botón retroceder.
return HttpResponseRedirect(reverse('encuestas.views.resultado', args=(enc.id,)))
return HttpResponse(u"Estás votando la encuesta %s." % encuesta_id)
Algunas cosas que todavía no hemos visto:
- request.POST es un objeto tipo diccionario que permite acceder a los datos enviados por su clave (request.POST['opcion'] devuelve el ID de la opción seleccionada como una cadena) Los valores de request.POST son siempre cadenas.
- Django también proporciona request.GET para acceder a los datos pasados por GET.
- request.POST['opcion'] lanza una excepción KeyError si no se envía esa información en POST. Por eso se trata la excepción.
- Después de incrementar el contador de la opción, el código devuelve un HttpResponseRedirect en lugar de un HttpResponse como hace normalmente.
- Se usa la función reverse() para evitar escribir a mano la URL en la vista. Parámetros: vista y argumentos.
La vista resultado puede ser así:
def resultado(request, encuesta_id):
enc = get_object_or_404(Encuesta, pk=encuesta_id)
return render_to_response('encuestas/resultado.html', {'encuesta': enc})
Y la plantilla resultado.html:
<h1>{{ encuesta.pregunta}}</h1> <ul> {% for opcion in encuesta.opcion_set.all %} <li>{{ opcion.opcion }} -- {{ opcion.votos }} voto{{ opcion.votos|pluralize }}</li> {% endfor %} </ul> <a href="/encuestas/{{ encuesta.id }}/">¿Votar otra vez?</a>
Vistas genéricas: ¡todavía menos código!!
Hay tareas muy repetitivas en el desarrollo web: obtener datos de la base de datos, según unos parámetros de una URL, cargar una plantilla y devolver la plantilla procesada. Django proporciona un atajo llamado sistema de generic views. Para convertir nuestra aplicación al uso de vistas genéricas hay que:
- Convertir el URLconf.
- Eliminar algunas vistas innecesarias.
- Corregir la gestión de las URLs por la nuevas vistas
En un proyecto decidiremos antes si es conveniente el uso de vistas genéricas (mejor que refactorizar)
Modificar encuestas/urls.py URLconf
from django.conf.urls.defaults import *
from django.views.generic import DetailView, ListView
from encuestas.models import Encuesta
urlpatterns = patterns('',
(r'^$',
ListView.as_view(
queryset=Encuesta.objects.order_by('-fecha_pub')[:5],
context_object_name='listado_ultimas',
template_name='encuestas/index.html')),
(r'^(?P<pk>\d+)/$',
DetailView.as_view(
model=Encuesta,
template_name='encuestas/detalle.html')),
url(r'^(?P<pk>\d+)/resultado/$',
DetailView.as_view(
model=Encuesta,
template_name='encuestas/resultado.html'),
name='resultado_encuesta'),
(r'^(?P<encuesta_id>\d+)/votar/$', 'encuestas.views.votar'),
)
Vamos a usar dos vistas genéricas: ListView y DetailView. Cada vista tiene que conocer:
- el modelo sobre el que actúa (model)
- DetailView necesita la clave primaria, llamada pk.(hay que cambiar encuesta_id a pk)
- El nombre resultado_encuesta de la vista resultado sirve para referirse a esta URL después.
- Por defecto la plantilla de DetailView se llama <nombre aplic.>/<nombre modelo>_detail.html. en nuestro caso será: encuestas/encuesta_detail.html Se puede usar el argumento template_name
- La plantilla de ListView se llama por defecto <nombre_modelo>_list.html
- El contexto lo gestiona de forma autmática generando encuesta_list. La copción context_object_name permite especificar ultimas_encuestas.
- Podemos eliminar ya las vistas creadas: index, detalle, resultado.
- Sobre la redicrección:
return HttpResponseRedirect(reverse('resultado_encuesta', args=(enc.id,)))