Tutorial Django Documentation

Tutorial Django Documentation
Publicación 1.0
Salvador Nicolas<[email protected]>
16 de August de 2016
Índice general
1. Tabla de contenidos:
1.1. Antes de empezar . . . . . . .
1.2. Instalación Django . . . . . . .
1.3. Creación de un proyecto Django
1.4. Crear nuestra primera app . . .
1.5. Creación de la Vista . . . . . .
1.6. URLs . . . . . . . . . . . . . .
1.7. Templates . . . . . . . . . . . .
1.8. Archivos static . . . . . . . . .
1.9. Creación accounts . . . . . . .
1.10. Creacion blog . . . . . . . . . .
1.11. Pagina About . . . . . . . . . .
1.12. Pagina Contacto . . . . . . . .
1.13. Notas finales . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
1
1
2
7
8
8
11
13
16
35
71
72
74
I
II
CAPÍTULO 1
Tabla de contenidos:
1.1 Antes de empezar
Esta es un pequeño tutorial para empezar a usar Django, no pretende ser una manual técnico y la mejor manera de
dominar el Framework es usarlo y mojarse con la ayuda de Documentación Django y/o si tienes dudas puedes buscar
o exponer tus dudas en stackoverflow y como no, con San Google.
Los requisitos mínimos para poder seguir sin problemas el tutorial seria tener conocimientos básicos de Python, Html.
He usado para el tutorial Python 3, Django 1.8 y Fedora 22.
Puedes descargar el código del tutorial en Github
Para el tutorial, vamos a crear un pequeño Blog con las siguientes características.
Un registro de usuarios.
Login y Logout.
Editar información del usuario.
Añadir y editar las entradas.
Ver en detalles las entradas de manera individual.
Editar y eliminar las entradas.
Añadir Disqus en las entradas.
Pagina ‘Sobre mi’.
Pagina de contacto.
Cualquier sugerencia o corrección, puedes contactar con migo a través de www.snicoper.com
Nota: La guía esta en proceso de revisión, puede contener errores tipográficos y de programación.
Ya sin mas, comencemos a crear nuestro Blog :)
1.2 Instalación Django
Antes de instalar Django, se ha de tener Python en el sistema, para no repetirme dejo aquí unas guías para instalar
Python y Django en varios sistemas.
1
Tutorial Django Documentation, Publicación 1.0
Instalación Python en Windows
Instalación Python en Fedora
Instalación Python en Ubuntu
Nota: Uso Python 3 para el tutorial.
1.2.1 Crear un entorno virtual
La mejor manera de desarrollar con Python, es usando entornos virtuales, de esta manera podemos tener versiones de
paquetes sin que se mezclen.
Para crear un entorno virtual, abrimos la terminal y escribimos:
mkvirtualenv tutorial_django
Ahora en la terminal, podemos ver que pone (terminal_django), eso nos indica que entorno estamos usando y
el que siempre usaremos en todo el tutorial.
Para salir del entorno virtual, simplemente con deactivate en la terminal, saldremos y para entrar o cambiar en
entorno, usamos workon nombre_entorno.
1.2.2 Instalación de Django
Para instalar Django, usando el entorno virtual, escribimos:
Nota: Omito (terminal_django)snicoper@lxmaq1 ~/projects/tutorial_django en la terminal cuando pongo los comandos, asumo que a partir de ahora siempre se estará usando el entorno virtual
terminal_django.
pip install django
Una vez instalado, comprobamos si todo ha salido bien:
python -c 'import django; print(django.get_version())'
# 1.8.2
Si nos muestra la versión, es que todo ha salido correcto y ahora podemos crear nuestro primer proyecto.
1.3 Creación de un proyecto Django
Vamos a crear un proyecto, nos desplazamos desde la terminal hasta el directorio donde vayamos a crear el proyecto
y ejecutamos:
django-admin startproyect tutorial_django
Nota: Si el comando anterior da algún error, probar con python django-admin.py startproyect
tutorial_django
Esto nos crea un directorio con la siguiente estructura:
2
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
tutorial_django/
-- manage.py
-- tutorial_django
-- __init__.py
-- settings.py
-- urls.py
-- wsgi.py
1 directory, 5 files
tutorial_django es la raíz del proyecto y este directorio se puede renombrar tranquilamente, para diferenciarlo
con el que hay en el interior con el mismo nombre, lo renombramos a src.
mv tutorial_django src
manage.py es un archivo Python que se encarga de poner el proyecto en sys.path, establecer la variable de
entorno DJANGO_SETTINGS_MODULE para que apunte al archivo settings.py y llama a django.setup().
Nota: En linux, en principio manage.py tiene permisos de ejecución, en caso de no tenerlos chmod +x
manage.py
Todos los comandos (argumentos) (poco a poco veremos unos cuantos) que se pasan a manage.py son pasados a
django-admin estableciendo los parámetros del párrafo anterior, por lo tanto es un wrapper de django-admin.
Nota: Para ver la lista de argumentos de manage.py usar ./manage.py help
Dentro de src hay otro directorio con el mismo nombre tutorial_django y cuatro archivos mas, el directorio es
posible cambiarle el nombre, pero abría que cambiar algunos parámetros en manage.py, nosotros lo dejaremos tal
y como esta.
__init__.py es para decirle que trate tutorial_django como un paquete Python.
settings.py aquí pondremos/cambiaremos las configuraciones del sitio.
urls.py archivo URLconf, aquí es donde manejaremos las rutas que apuntan a las vistas.
wsgi.py WSGI en pocas palabras es una interfaz entre un servidor web y la propia aplicación, no hablaremos mas
sobre este tema en este tutorial.
1.3.1 Primera migración
Una de las primeras cosas que se hace después de crear un proyecto Django, es elegir el motor de base de datos en la que almacenaremos todos nuestros datos, decir a Django que RDBMS vamos a usar, en
tutorial_django/settings.py, buscamos la zona donde esta la configuración DATABASES.
Para este tutorial usaremos el mas simple, SQLite que es la que viene por defecto, pero si quieres usar otro distinto
puedes ver de la documentación
La configuración por defecto:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
1.3. Creación de un proyecto Django
3
Tutorial Django Documentation, Publicación 1.0
}
}
Ahora, tenemos que hacer la primera migración y la creación de un administrador, para la migración usamos el siguiente comando.
./manage.py migrate
Esto nos crea las tablas de algunas aplicaciones que vienen por defecto en Django (si quieres ver las apps que se usan
en Django, puedes mirar en el archivo settings.py en la tupla INSTALLED_APPS).
Si usas el gestor de la base de datos que hayas elegido, puedes ver que se han creado varias tablas en la base de datos,
también has podido ver que tablas se han creado, con la salida de ./manage.py migrate
Operations to perform:
Synchronize unmigrated apps: staticfiles, messages
Apply all migrations: sessions, admin, auth, contenttypes
Synchronizing apps without migrations:
Creating tables...
Running deferred SQL...
Installing custom SQL...
Running migrations:
Rendering model states... DONE
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying sessions.0001_initial... OK
1.3.2 Creación del super usuario
Ahora que tenemos nuestra primera migración en nuestra base de datos y por consiguiente la tabla auth_user,
vamos a crear el super usuario, desde la terminal ejecutamos ./manage.py createsuperuser
$ ./manage.py createsuperuser
Username (leave blank to use 'snicoper'):
Email address: [email protected]
Password:
Password (again):
Superuser created successfully.
Vamos a ver si todo ha salido bien, o como se esperaba. Para ello se usa el comando runserver
./manage.py runserver
Advertencia: Django tiene un servidor escrito en Python exclusivamente para el desarrollo, cada vez que se
modifica un archivo, el servidor se reinicia y recompila los archivos, ahorrándonos mucho tiempo y molestias, pero
eso solo eso, un servidor para desarrollo, que soporta una o unas pocas peticiones, no lo uses para un servidor de
producción! para eso tienes Nginx, Apache entre otros.
Accedemos en el navegador a la url http://127.0.0.1:8000 y si todo ha salido bien, veras una pantalla como la siguiente:
4
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
También podemos ver la administración que viene incorporada con Django y que ahorra muchas horas de trabajo, genera un sistema CRUD simplemente genial, para acceder a la administración vamos a la url http://127.0.0.1:8000/admin/
1.3. Creación de un proyecto Django
5
Tutorial Django Documentation, Publicación 1.0
Eso es todo!, ya tenemos un proyecto creado, nuestra primera migración y creado el super usuario (administrador) del
sitio con uno pocos pasos.
Mas adelante, modificaremos settings.py y urls.py y los veremos mas en detalle, ahora vamos a crear nuestra
primera aplicación.
6
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
1.4 Crear nuestra primera app
Una aplicación (app), no es mas que un paquete Python con una estructura básica para hacer algo concreto, por ejemplo
blog, about, contact seria cada una de ellas una aplicación. Las aplicaciones en Django, están (en un principio)
totalmente desacopladas y puedes tener apps en varios proyectos sin modificar ni una sola linea de código, el único
requisito es que este en la variable de entorno PYTHONPATH
Nota: Cuando ejecutamos un archivo .py Python añade el directorio actual en PYTHONPATH, por lo tanto, durante
el desarrollo, cuando levantamos el servidor, al hacerlo a través de manage.py src estará en PYTHONPATH.
Vamos a crear nuestra primera app, crearemos el home de nuestro sitio, para crear nuestra app, abriremos la terminal
e iremos hasta src y ejecutaremos:
./manage.py startapp home
Nota: Si el comando anterior da algun error (en Windows creo), usar python manage.py startapp home o
en windows .\manage.py startapp home.
El comando anterior es posible crearlo con django-admin, muy útil cuando se crean apps en otros directorios
que no sean en la raíz del proyecto. Lo que el comando manage.py startapp hace es crear una estructura, que
tranquilamente se podría crear los directorios y archivos a mano. El comando anterior crea la siguiente estructura:
home/
-- migrations
|
-- __init__.py
-- __init__.py
-- admin.py
-- models.py
-- tests.py
-- views.py
Examinemos los distintos archivos que ha creado el comando anterior, nos crea un directorio migrations que
contiene un archivo __init__.py (para decirle a Python que es un paquete), migrations
Las migraciones son manera de propagar los cambios que realice en sus modelos (la adición de un campo,
la eliminación de un modelo , etc.) en el esquema de base de datos de Django. Están diseñados para ser
en su mayoría automática.
fuente
admin.py donde crearemos la forma que se vera la app en la administración de Django. models.py donde crearemos los modelos y es el ‘puente’ entre Django y la base de datos. test.py para test unitarios, no se trata el tema
en el tutorial. views.py donde crearemos las vistas (o controladores, para los que vengan de otros Frameworks).
Con todo esto en mente, vamos a crear el primer Hello world :), en primer lugar, tenemos que decirle a Django que
incluya la aplicación, para ello, editamos tutotial_django/settings.py
Vamos a INSTALLED_APPS y añadimos la siguiente linea:
# tutotial_django/settings.py
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
1.4. Crear nuestra primera app
7
Tutorial Django Documentation, Publicación 1.0
'django.contrib.messages',
'django.contrib.staticfiles',
'home',
)
Ahora, Django conocerá que tenemos una aplicación y buscara templates (plantillas), archivos de migración, etc,
dentro de esa app.
En la siguiente sección, modificaremos las urls para poder acceder a las vistas.
1.5 Creación de la Vista
Las views (controladores en otros Frameworks), es el puente entre los templates (vistas en otros Frameworks) y el
modelo.
En este primer contacto, no vamos a usar los modelos para mantener las cosas lo mas simple posible, así que vamos a
escribir la vista mas sencilla posible.
Abrimos home/views.py y escribimos lo siguiente:
# home/views.py
from django.http import HttpResponse
def index_view(request):
return HttpResponse("Hello world")
Tenemos que escribir una url para que al acceder ella, Django y el sistema de enrrutamiento sepa a que view ha de
acceder.
1.6 URLs
Se
puede
ver
en
tutotial_django/settings.py
una
variable
ROOT_URLCONF =
’tutorial_django.urls’, esto es el punto de entrada a las rutas en la barra de navegación del navegador. Cuando insertamos una URI en el navegador (o pinchamos algún link), Django obtiene la URI y la va
comparando con expresiones regulares con el archivo tutorial_django/urls.py y si hay coincidencia, cagara
la vista asociada al patrón, en caso contrario mostrara un error 404.
Este archivo es solo un punto de entrada y algo cómodo (que ademas mantiene al máximo el desacoplamiento), es
crear un archivo urls.py en cada app y en tutorial_django/urls.py indicar que se ha creado otro archivo
urls para que siga comprobando.
Primero decimos a tutorial_django/urls.py que vamos a crear un archivo con urls y donde encontrar el
archivo, a parte pondremos parte de un patrón.
Echemos un ojo al archivo que crea por defecto Django al crear proyecto.
# tutorial_django/urls.py
from django.conf.urls import include, url
from django.contrib import admin
urlpatterns = [
8
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
url(r'^admin/', include(admin.site.urls)),
]
urlpatterns es una lista que contiene un conjunto de funciones url(), en este caso, se le pasan dos parámetros
r’^admin/’ que es el patrón, una expresión regular que luego comparara con la URI que insertamos en el navegador
e include(admin.site.urls) que es una función para ‘incluir’ otros URLconf.
En el argumento de regex, es una expresión regular pura en Python, por lo que no debemos aprender otros tipo de
patrones!.
Para entender como funciona esto (haber si yo me se explicar, que es lo difícil de verdad :P), vamos a crear nuestro
archivo home/urls.py y le insertamos el siguiente código.
# home/urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
# /home/
url(regex=r'^$', view=views.index_view, name='home'),
]
Tenemos que incluir este archivo URLconf al URLconf principal tutorial_django/urls.py
# tutorial_django/urls.py
from django.conf.urls import include, url
from django.contrib import admin
urlpatterns = [
url(r'^$', include('home.urls')),
url(r'^home/', include('home.urls')),
url(r'^admin/', include(admin.site.urls)),
]
En este ultimo URLconf, hemos añadido dos url(), con una expresión regular ^$ que coincidirá con la URI ‘/’
(vació) y la otra que coincidirá con la URI ‘home/’, e ‘incluirán’ home/urls.py y sera nuestra pagina principal.
Ahora, imaginamos que ponemos en el navegador http://127.0.0.1:8000/home/about/, ¿como funcionan
las comparaciones?, en primer lugar Django separa la parte del dominio con el resto, es decir se queda con la URI, que
en este caso es /home/about/ y empieza a compararlas con tutorial_django/urls.py, va de arriba hacia
abajo en las url().
En primer lugar compara /home/about/ con el patrón r’^home/’ y ya encuentra una coincidencia, que es
home/, ahora, parte el string y se queda con el resto, en este caso about/. Pero ahora, continua las comparaciones
con los patrones en home/urls.py. En home/urls.py, no encontrara ninguna coincidencia, por lo que volverá
a tutorial_django/urls.py, cuando termine, no abra encontrado nada, así que devolverá un error 404.
En cambio si la URI, es /home/, encuentra una coincidencia con r’^home/’, cuando parte el string se queda con
’/’ y cuando pasa a home/urls.py encuentra una coincidencia con r’^$’ (vacio) y devuelve la vista asociada.
Si nos fijamos en el archivo URLconf de home/urls.py y examinamos el resto de la url()
url(regex=r'^$', view=views.index_view, name='home.index')
Podemos ver que tiene tres argumentos (aunque a la función se le pueden pasar mas), regex ya lo conocemos,
view es simplemente la vista a cargar en caso de coincidencia, que en este caso el modulo views y la función
index_view, el name=’home’ se usa mucho en los templates para generar links, mas adelante los iremos usando.
1.6. URLs
9
Tutorial Django Documentation, Publicación 1.0
Entonces para mostrar el contenido de nuestra vista creada en la sección anterior, hay dos URIs validas para llegar a la
vista, / y /home/, si vamos al navegador (tenemos que tener el servidor en ejecución) http://127.0.0.1:8000
o también http://127.0.0.1:8000/home/ veremos que nos muestra Hello world que es justo lo que se esperaba.
Si probamos la URL de antes http://127.0.0.1:8000/home/about/, veremos que nos muestra una pagina
igual a esta:
Nota: Ese mensaje tan feo para el usuario, pero tan útil para nosotros, solo lo muestra cuando se esta en modo
desarrollo, por defecto en tutorial_django/settings.py la variable de configuración DEBUG, dice a Django
el modo en el que estamos, por defecto al crear un proyecto se establece en True.
Advertencia: En producción JAMAS usar DEBUG = True, siempre DEBUG = False, a parte de que las
peticiones son mas lentas, expone información que puede poner en peligro la seguridad del sitio.
Para recordar la vista
from django.http import HttpResponse
def index_view(request):
return HttpResponse("Hello world")
Toda vista, obtiene un objeto HttpRequest y devuelve un objeto HttpResponse, en este caso dentro de
HttpResponse es solo un string, si vamos al navegador y vemos el código, veremos que es limpiamente el string
devuelto por la función de la vista index_view y aunque podemos poner html dentro del HttpResponse, no
seria muy útil crear paginas de este modo!, tardaríamos mas, eso sin contar con lo horrible que seria el código...
Para evitarlo, la mejor manera es por medio de las plantillas o templates de Django.
10
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
1.7 Templates
Hasta ahora, ya sabemos como crear un proyecto, una app, configurar URLconfs y enlazarlas a las vistas, ahora es
hora de renderizar desde las vistas a los templates o plantillas e incluir los archivos.
Empezamos con el archivo de configuración tutorial_django/settings.py, buscamos la lista TEMPLATES
y vemos que contiene un diccionario, un elemento de ese diccionario es ’DIRS’: [], una clave DIRS con un valor
que es una lista vacía. Dentro de la lista vamos a decir donde guardaremos los archivos de plantillas, quedando de la
siguiente manera.
# tutorial_django/settings.py
TEMPLATES = [
{
# ...
'DIRS': [os.path.join(BASE_DIR, 'templates')],
# ...
}
]
Ahora, tenemos que crear el directorio templates en el directorio raíz del proyecto
mkdir templates
Como puedes ver dentro del diccionario dentro de TEMPLATES hay un elemento ’APP_DIRS’: True, que le dice
si buscar dentro de las apps un directorio templates donde contendrán plantillas que pertenecen a esa **a**pp (mas
adelante lo veremos como funciona) y terminare de explicar como busca las plantillas Django, por ahora, quédate con
’APP_DIRS’: True,.
Creamos dentro del directorio templates un archivo .html que sera el archivo base, el que se leerá en todas nuestras
paginas con el nombre base.html y añadimos el siguiente código html.
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<h1>Pagina principal</h1>
</body>
</html>
Volvemos al archivo de vista en la app home y cambiamos todo el código por el siguiente:
# home/views.py
from django.shortcuts import render
def index_view(request):
return render(request, 'base.html')
Como se pude ver, se ha cambiado el from... y el return..., ahora vemos que en vez de devolver HttpResponse como
hacíamos antes, ahora devolvemos render que es un atajo donde volvemos a pasar el HttpRequest, el nombre de
plantilla a usar y mas adelante, veremos como pasar un diccionario con datos (un diccionario con pares clave/valor)
para poderlos mostrar desde las plantillas.
1.7. Templates
11
Tutorial Django Documentation, Publicación 1.0
Ahora si iniciamos el servidor ./manage.py runserver y vamos a http:/127.0.0.1:8000 veremos que la salida,
es la del html creado.
Vamos a empezar con la herencia de plantillas, para ver como podemos crear bloques, vamos a editar el archivo
base.html y creamos un par de bloques.
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>{% block title %}{% endblock title %}</title>
</head>
<body>
<h1>Pagina principal</h1>
{% block content %}{% endblock content %}
</body>
</html>
Podemos observar que hemos creado dos bloques, ¿para que sirven?, pues bien, cuando una plantilla extiende de otra,
la plantilla que llama sustituye el contenido del bloque por la de la plantilla ‘padre’, para verlo, dentro de home,
creamos un directorio templates y dentro creamos otro directorio con el nombre home y dentro un archivo .html
con el nombre index.html.
mkdir -p home/templates/home
touch home/templates/home/index.html
y ponemos el siguiente código:
<!-- home/templates/home/index.html -->
{% extends 'base.html' %}
{% block title %}home{% endblock title %}
{% block content %}
<h2>Home page</h2>
{% endblock content %}
La manera de extender una plantilla es con la etiqueta (tag) { % extends ’base.html’ %} donde le estamos
diciendo que extienda la plantilla a base.html y a partir de hay, los bloques (block) de código, cambiaran los datos
de la plantilla actual con la de la plantilla extendida, en este caso base.html.
Como se puede apreciar, puede parecer un poco follón crear un directorio template y dentro otro con el mismo
nombre de la app, en este caso home, ¿porque este lío?, sencillo, imagina que creas diez apps en un proyecto y tres
de ellos tienen una plantilla index.html, ¿como sabe Django que plantilla cargar?, de hay que se crea siempre un
directorio con el nombre de la app (los nombres de las apps, son siempre únicos).
Otra manera o estructura de crear las plantillas, es dentro del directorio templates de la raíz del proyecto, es crear
directorios con el mismo nombre que la apps y dentro las plantillas, pero yo por costumbre, siempre los creo en los
directorios de la app.
Bien, continuemos... ahora, vamos a cambiar en la vista index_view la plantilla que requiere.
def index_view(request):
return render(request, 'home/index.html')
Vemos que hemos añadido la ruta home/index.html y lo mejor de todo, vemos que ahora imprime lo de
12
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
base.html y lo de index.html, ahora la pagina muestra un titulo y debajo de Pagina principal inserta el contenido que hay dentro de ‘block’ en home/index.html.
Para terminar con las plantillas (seguiremos a lo largo del tutorial), vamos a ver como pasar un contexto de la vista a
la plantilla.
Volvemos a editar home/views.py
# home/views.py
from django.shortcuts import render
from django.utils import timezone
def index_view(request):
context = {
'ahora': timezone.now()
}
return render(request, 'home/index.html', context)
y ahora, en home/templates/home/index.html
<!-- home/templates/home/index.html -->
{% extends 'base.html' %}
{% block title %}home{% endblock title %}
{% block content %}
<h2>Home page</h2>
<p>Ahora es {{ ahora }}</p>
{% endblock content %}
Y vemos que hemos pasado la fecha y hora de la maquina en la que estamos. Para ‘imprimir’ el valor de una variable, se
usa dobles llaves de apertura y cierre {{ nombre_variable }}, los espacios son opcionales, pero aconsejables.
Nota:
Si te sale la fecha en ingles y una hora que no corresponde a la de tu sistema, ves a
tutorial_django/settings.py para editar las variables de configuración LANGUAGE_CODE = ’es-es’
ver identificadores y TIME_ZONE = ’Europe/Madrid’ ver timezones
En la siguiente sección, veremos como incluir archivos estáticos a nuestro proyecto.
1.8 Archivos static
Ahora es el momento de ver como incluir los archivos estáticos (css, js, imagenes) al proyecto.
Si abres el archivo de configuración tutorial_django/settings.py y bajas al final del archivo, vemos una
variable de configuración STATIC_URL = ’/static/’, de esta manera le esta diciendo que, cuando en las plantillas llamamos a un archivo estático, anteponga en la URI /static/ (ahora veremos como funciona), a parte tenemos
que decirle a Django donde estarán esos archivos estáticos, así que, debajo de STATIC_URL ponemos lo siguiente:
# tutorial_django/settings.py
STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static'),
)
1.8. Archivos static
13
Tutorial Django Documentation, Publicación 1.0
STATICFILES_DIRS es una tupla, que dice las rutas absolutas del sistema para buscar archivos estáticos, en nuestro
caso, solo hemos añadido uno.
Ahora, ya sabrá Django que los archivos estarán en la src/static (con una ruta absoluta), pero ese directorio no
existe, así que lo creamos y dentro creamos tres directorios mas, js, css e img
mkdir -p static/{js,css,img}
dentro de css creamos un archivo main.css y dentro de js un archivo main.js. Ya para ‘terminar’, los incluimos
en templates/base.html
<!-- templates/base.html -->
<!DOCTYPE html>
{% load staticfiles %}
<html lang="es">
<head>
<meta charset="utf-8">
<title>{% block title %}{% endblock title %}</title>
<link rel="stylesheet" href="{% static 'css/main.css' %}">
</head>
<body>
<h1>Pagina principal</h1>
{% block content %}{% endblock content %}
<script src="{% static 'js/main.js' %}"></script>
</body>
</html>
Hemos incluido una tag o etiqueta { % load staticfiles %} (si no, dará error al cargar la pagina),
cuando llama un archivo estático como { % static ’js/main.js’ %}, Django cambiara { % static
’js/main.js’ %} por el valor de settings.STATIC_URL + js/main.js que en este caso sera <script
src="/static/js/main.js"></script>
Dentro de static/css/main.css añadimos:
/* static/css/main.css */
body { background-color: blue; }
Ahora, reiniciamos el servidor (por si acaso) y vamos a http://127.0.0.1:8000/ y vemos que el fondo es azul,
lo importante es ver el código html generado, si nos fijamos ha puesto la ruta <link rel="stylesheet"
href="/static/css/main.css">, eso significa que podríamos cambiar el directorio a otro ruta y cambiando
en settings la ruta, sin modificar nada mas, seguiremos teniendo a nuestra disposición los archivos estáticos.
Para el resto del tutorial, vamos a poner Bootstrap y JQuery, descargamos ambas librerías y las descomprimimos
dentro del directorio static (jquery no lo usaremos), quedando de esta manera la estructura.
static/
-- bootstrap
|
-- css
|
|
-- bootstrap.css
|
|
-- bootstrap.css.map
|
|
-- bootstrap.min.css
|
|
-- bootstrap-theme.css
|
|
-- bootstrap-theme.css.map
|
|
-- bootstrap-theme.min.css
|
-- fonts
|
|
-- glyphicons-halflings-regular.eot
|
|
-- glyphicons-halflings-regular.svg
14
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
|
|
|
|
|
|
|
-|
--|
--
|
-- glyphicons-halflings-regular.ttf
|
-- glyphicons-halflings-regular.woff
|
-- glyphicons-halflings-regular.woff2
-- js
-- bootstrap.js
-- bootstrap.min.js
-- npm.js
css
-- main.css
img
jquery
-- jquery.js
js
-- main.js
8 directories, 18 files
E incluimos en nuestro templates/base.html. Ademas añadimos el típico navbar superior de Bootstrap, quedando de la siguiente manera nuestro archivo base.html
<!-- templates/base.html -->
<!DOCTYPE html>
{% load staticfiles %}
<html lang="es">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}{% endblock title %}</title>
<link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.css' %}">
<link rel="stylesheet" href="{% static 'css/main.css' %}">
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Project name</a>
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="active"><a href="#">Home</a></li>
<li><a href="#about">About</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</nav>
<div class="container">
1.8. Archivos static
15
Tutorial Django Documentation, Publicación 1.0
<div class="starter-template">
{% block content %}{% endblock content %}
</div>
</div><!-- /.container -->
<script src="{% static 'jquery/jquery.js' %}"></script>
<script src="{% static 'bootstrap/js/bootstrap.js' %}"></script>
<script src="{% static 'js/main.js' %}"></script>
</body>
</html>
El archivo css también le vamos a quitar ese color azul tan molón :P y ponerle el margen de la barra.
/* static/css/main.css */
body {
margin: 70px 0 20px 0;
}
Si actualizamos la pagina, ahora se ve que la cosa cambia :) y es hora de empezar a profundizar mas en todos los
componentes que hasta ahora hemos tocado, a parte de que queda mucho por ver como los modelos.
1.9 Creación accounts
Vamos a ver como crear un pequeño sistema para los usuarios, como poder registrar, hacer login, logout y como editar
datos del perfil de usuarios.
1.9.1 Creación de registros de usuarios
Una de las primeras cosas que se suele hacer, es el manejo de usuarios, como el registro, login, logout, etc.
Django tiene un sistema de usuarios, recuerda que nosotros ya tenemos un usuario registrado y ademas es administrador del sitio, es decir que también maneja permisos ademas de sesiones.
Vamos a crear una manera para registrar usuarios en el sitio, primero vamos a crear una app accounts donde muestre
el formulario para registrar usuario. Un modelo para extender los datos del usuario, donde el usuario podrá añadir una
imagen/avatar, dentro de esta app, añadiremos la manera para que el usuario pueda hacer login y logout.
Django, como he comentado, incorpora un sistema de manejo de usuarios, si vemos en la base de datos las tablas
que creó cuando usamos la primera vez el comando ./manage.py migrate, vemos que creó las tablas auth_*
y otra para las sesiones, django_session. Si nos fijamos en la tabla auth_user vemos las columnas id,
password, is_superuser, username, etc, eso significa que Django tiene un modelo que hace de ‘puente’ entre Django y la base de datos.
Vamos a crear nuestro primer modelo que tenga una relación One to One donde una campo del modelo que crearemos
sera una relación con el modelo User en la base de datos, la tabla es auth_user.
En primer lugar, vamos a crear la app accounts
./manage.py startapp accounts
Ahora, en tutorial_django/settings.py al final del archivo añadimos
# tutorial_django/settings.py
LOGIN_URL = '/accounts/login/'
LOGOUT_URL = '/accounts/logout/'
16
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
LOGIN_URL y LOGOUT_URL, los pongo para mostrarlos, pero usa los mismos valores que tienen por defecto, cuando
una pagina requiera que el usuario este logueado (por medio de decoradores en las vistas, por ejemplo), lo redireccionara automáticamente a LOGIN_URL y cuando un usuario haga logout lo redireccionara al valor de LOGOUT_URL
El siguiente paso es decirle a Django que hemos creado una app, en
tutorial_django/settings.py, en INSTALLED_APPS añadimos accounts
el
mismo
archivo
# tutorial_django/settings.py
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'home',
'accounts',
)
Ahora, vamos a crear nuestro modelo, para ello, editamos accounts/models.py con el siguiente código
# accounts.models.py
from django.db import models
from django.conf import settings
class UserProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
photo = models.ImageField(upload_to='profiles', blank=True, null=True)
Cada modelo es una subclase de django.db.models.Model y cada atributo de la clase UserProfile representa una columna en la base de datos.
Cuando hagamos una migración (ahora, dentro de un rato), veremos que los nombres de tablas en la base de datos,
por defecto las crea de la siguiente manera, nombreapp_nombremodelo, convierte todo a minusculas y pone un guion
bajo _ entre el nombre de la app y el nombre del modelo, es decir, cuando cree la tabla en la base de datos nuestro
modelo UserProfile, creara una tabla con el nombre accounts_userprofile.
Cada propiedad en el modelo UserProfile, es una clase en el modulo models y aquí tienes una lista con todos
los campos y opciones en los argumentos de cada campo.
Otra cosa a comentar, es la linea from django.conf import settings, también podíamos haber puesto
from django.contrib.auth import User, pero tal y como lo hemos puesto, nos aseguramos que siempre
leemos el modelo User que estamos usando, ya que todo esto lo podemos personalizar y crear nuestros propios
modelos User, por defecto settings.AUTH_USER_MODEL es auth.User.
Antes de continuar, tenemos que instalar un paquete Python Pillow para manejar imágenes.
# Dentro del entorno virtual (tutorial_django)
pip install Pillow
Ahora vamos a crear nuestra primera migración de una app creada, para ello, en la terminal usamos el comando
makemigrations nombre_app
./manage.py makemigrations accounts
1.9. Creación accounts
17
Tutorial Django Documentation, Publicación 1.0
De momento, si vemos en el explorador de archivos, dentro de src/accounts/migrations se ha creado un archivo 0001_initial.py y si lo abrimos, podemos ver que creara tres campos, id, que lo crea de manera implícita
(todos lo modelos crea un campo id de manera implícita a no ser que se diga de manera explicita), photo y user.
Pero si vemos en la base de datos, aun no ha creado nada y antes de que lo cree, vamos a usar el comando
sqlmigrate nombre_app nombre_migracion. En este caso, el nombre_app es accounts y el nombre_migracion 0001_initial (omitimos el .py)(con solo la numeración es suficiente, en este caso 0001), de
esta manera podemos ver la sentencia SQL que ejecutara cuando usemos el comando migrate.
./manage.py sqlmigrate accounts 0001_initial
BEGIN;
CREATE TABLE "accounts_userprofile" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"photo" varchar(100) NULL,
"user_id" integer NOT NULL UNIQUE REFERENCES "auth_user" ("id")
);
Nota: La sentencia SQL puede variar según el RDBMS elegido. La mostrada es la que usara con SQLite.
Si nos parece bien lo que va hacer, ejecutamos migrate y ya si que los cambios se reflejaran en la base de datos.
./manage.py migrate
Vamos a ir a la administración de Django http://127.0.0.1:8000/admin/ y podemos observar que no hay nada diferente!,
¿donde configuramos los nuevos perfiles de los usuarios? :), hay que decirle a la administración Django, que nos
muestre el modelo recién creado.
Cuando creamos las apps, un archivo que nos crea en la estructura es admin.py, vamos a editarlo y poner lo
siguiente.
# accounts/admin.py
from django.contrib import admin
from .models import UserProfile
admin.site.register(UserProfile)
Y ahora si nos sale el modelo User Profile
y si pinchamos, podemos ver que no sale ningún campo
18
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
Si pinchamos en Añadir user profile, podemos añadir datos a usuarios, por que como se puede ver, el campo User:,
nos muestra los usuarios que tenemos registrados, en nuestro caso, solo uno y podemos incluir una imagen o no,
ya que el campo, cuando lo creemos como parámetros, pusimos blank=True, null=True, blank es para los
formularios, con True decimos que permitimos que los campos podrán estar vacíos y con null es para las base de
datos, con True decimos el el campo permite datos nulos (por defecto, ambos campos son False).
También, podemos observar algo no deseado, si vamos al gestor de archivos, podemos ver que nos ha creado un directorio profiles en la raíz del proyecto, lo ideal es contener los archivos media, dentro de un directorio, para configurar
donde almacenar los archivos media, vamos al archivo de configuración tutorial_django/settings.py y al
final del archivo añadimos:
# tutorial_django/settings.py
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
Donde
MEDIA_ROOT
indica
la
ruta
física
del
directorio
(en
este
caso
/path/directorio/proyecto/src/media, que lo genera dinamicamente con os.path.join()) y
MEDIA_URL es lo mismo que STATIC_URL, en las plantillas, antepondrá en la URI el valor, en este caso también
/media/.
Ahora vamos a crear el directorio media en la raíz del proyecto y a mover profiles dentro de media.
mkdir media
mv profiles/ media/
Ahora, si cambias la imagen, veras que la nueva imagen la sube a src/media/profiles.
Si nos fijamos en la administración, podemos ver que ahora tenemos una entrada
Pero, el nombre que muestra UserProfile object no es muy intuitivo, ahora solo hay uno, pero si hubiesen 100,
haber como averiguamos que elemento pertenece a X usuario...
Vamos a solucionar esto, vamos a editar el modelo accounts/models.py y añadimos el siguiente método
# accounts/models.py
class UserProfile(models.Model):
# ...
def __str__(self):
return self.user.username
Aquí podemos observar, primero, lo fácil que es acceder los campos de las columnas relacionales, en este caso,
obtenemos el campo username de la clase django.contrib.auth.models.User que tenga relación con el
objeto actual UserProfile, esto es gracias al ORM que incorpora Django y en segundo lugar, si actualizamos la
pagina de administración, ahora observamos que nos muestra el username al que pertenece la fila de UserProfile
1.9. Creación accounts
19
Tutorial Django Documentation, Publicación 1.0
Con __str__ obtenemos ‘algo’ y no el objeto en si, que era lo que antes nos mostraba. (Se puede pensar en __str__
como toString en otros lenguajes)
Siguiente paso, crear formularios .py para la representación .html, creamos un archivo forms.py dentro de la app
accounts y le añadimos el siguiente código:
# accounts/forms.py
from django import forms
from django.contrib.auth.models import User
class RegistroUserForm(forms.Form):
username = forms.CharField(min_length=5)
email = forms.EmailField()
password = forms.CharField(min_length=5, widget=forms.PasswordInput())
password2 = forms.CharField(widget=forms.PasswordInput())
photo = forms.ImageField(required=False)
def clean_username(self):
"""Comprueba que no exista un username igual en la db"""
username = self.cleaned_data['username']
if User.objects.filter(username=username):
raise forms.ValidationError('Nombre de usuario ya registrado.')
return username
def clean_email(self):
"""Comprueba que no exista un email igual en la db"""
email = self.cleaned_data['email']
if User.objects.filter(email=email):
raise forms.ValidationError('Ya existe un email igual en la db.')
return email
def clean_password2(self):
"""Comprueba que password y password2 sean iguales."""
password = self.cleaned_data['password']
password2 = self.cleaned_data['password2']
if password != password2:
raise forms.ValidationError('Las contraseñas no coinciden.')
return password2
Los formularios son muy parecidos a los modelos, pero en vez de usar un objeto model usa forms.
Puedes ver aquí la lista completa de campos y widgets para los formularios.
A groso modo, podemos ver que username requiere de al menos 5 caracteres y es un campo tipo text, email es
un campo tipo email, password y password2 son campos tipo password y requiere de al menos 5 caracteres y
photo es un campo tipo file que ademas sera tratado como un archivo de imagen (comprobara que sea un tipo de
imagen).
Con clean_nombre_campo, donde nombre_campo es un campo de propiedad, lo que hace, es una validación
personalizada cuando el usuario le da al botón del formulario, en este caso, comprueba que password y password2
sean iguales (por eso password2 no le puse min_length=5, por que aquí han de ser iguales y si password no
cumple con los requisitos, lanzara un forms.ValidationError).
20
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
Se puede ver que clean_username y clean_email comprueba si existe un username o email en la base de
datos, si existe lanzara un forms.ValidationError().
Ahora, nos queda ver como implementar esto para que lo muestre en un archivo html, primero vamos a crear la vista
para el registro (de momento, simplificada).
# accounts/views.py
from django.shortcuts import render
from .forms import RegistroUserForm
def registro_usuario_view(request):
if request.method == 'POST':
form = RegistroUserForm(request.POST, request.FILES)
else:
form = RegistroUserForm()
context = {
'form': form
}
return render(request, 'accounts/registro.html', context)
Lo que hacemos es importar el formulario que acabamos de crear RegistroUserForm, después creamos la vista
para manejar los datos y dentro de la vista esto es lo que hace.
Comprueba si method de la solicitud (request), es POST, es decir, si le a dado al botón del formulario, en
caso de afirmativo, crea una instancia de RegistroUserForm y lo rellena con los datos request.POST,
request.FILES, que son los datos del formulario (si no tuviera un tipo file, no haría falta request.FILES),
en caso contrario, es decir la primera carga de la pagina que el method seria GET, simplemente instanciaria
RegistroUserForm sin datos.
Por ultimo almacenos el formulario en el contexto y renderizamos la pagina, devolviendo la respuesta, el ruta/nombre
plantilla y el contexto.
Es necesario crear una url() en el archivo URLconf, primero, vamos a tutorial_django/urls.py y añadimos la siguiente url dentro de la lista urlpatterns:
# tutorial_django/urls.py
urlpatterns = [
# ...
url(r'^accounts/', include('accounts.urls')),
]
Creamos el archivo accounts/urls.py y añadimos lo siguiente
# accounts/urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^registro/$', views.registro_usuario_view, name='accounts.registro'),
]
Ya como paso final, creamos el directorio accounts/templates/accounts y dentro creamos el archivo html
registro.html con el siguiente contenido:
1.9. Creación accounts
21
Tutorial Django Documentation, Publicación 1.0
# accounts/templates/accounts/registro.html
{% extends 'base.html' %}
{% block title %}Registro de usuario{% endblock title %}
{% block content %}
<div class="row">
<div class="col-md-6 col-sm-offset-3">
<div class="page-header">
<h2>Registro Usuario</h2>
</div>
<form method="post" action="" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">Registrar</button>
</form>
</div>
</div>
{% endblock content %}
Una vez mas extendemos la plantilla usando base.html, le damos un <title></title> dentro del bloque { %
block title %}Registro de usuario{ % endblock title %} y le añadimos el contenido dentro del
bloque { % block content %}{ % endblock content %}.
Nota: { % block content %}{ % endblock content %} se puede escribir { % block content %}{ %
endblock %} pero a mi personalmente me gusta añadir en el endblock el nombre al que pertenece el bloque, por
claridad.
En cuanto al formulario hay una tag nueva { % csrf_token %} (wikipedia y documentacion Django lectura obligatoria) la etiqueta es obligatoria por defecto en formularios con method="post".
{{ form.as_p }} form es la variable de contexto que pasamos desde la vista (un objeto RegistroUserForm,
que a su vez es subclase de django.forms.Form), muestra una representación en html de los campos. Al usar
as_p, rodea los elementos del formulario en etiquetas <p>.
A parte de form.as_p hay dos opciones mas form.as_table y form.as_ul, todos hacen los mismo, lo único a tener en cuanta es que as_ul y as_table insertan las propiedades del form en
<tr><td>label</td><input></tr> es decir, omite <table> y </table> (as_ul omite <ul> y
</ul>), por otro lado, también saber que {{ form.as_X }} no añade las etiquetas html <form></form>
ni lo botones.
Si vamos al navegador con la url http://127.0.0.1:8000/accounts/registro/ podemos ver el siguiente resultado:
22
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
Realmente sencillo, ahora se puede añadir o quitar campos de una manera muy sencilla o usar este formulario en otras
vistas/plantillas sin cambiar nada.
Vamos a poner un poco de estilo con los widgets, volvemos al archivo accounts/forms.py y añadimos los
widgets.
# accounts/forms.py
# ...
username = forms.CharField(
min_length=5,
widget=forms.TextInput(attrs={'class': 'form-control'}))
email = forms.EmailField(
widget=forms.EmailInput(attrs={'class': 'form-control'}))
password = forms.CharField(
min_length=5,
widget=forms.PasswordInput(attrs={'class': 'form-control'}))
password2 = forms.CharField(
min_length=5,
widget=forms.PasswordInput(attrs={'class': 'form-control'}))
photo = forms.ImageField(required=False)
# ...
Para ver y comprender en mas profundidad los formularios, te recomiendo visitar las documentacion de Django.
Ahora falta ‘¿Que hacer cuando se han validado los datos?’, para ello abrimos el archivo accounts/views.py y
lo re escribimos de la siguiente manera.
# accounts/views.py
from
from
from
from
django.shortcuts import render
django.contrib.auth.models import User
django.shortcuts import redirect
django.core.urlresolvers import reverse
1.9. Creación accounts
23
Tutorial Django Documentation, Publicación 1.0
from .forms import RegistroUserForm
from .models import UserProfile
def registro_usuario_view(request):
if request.method == 'POST':
# Si el method es post, obtenemos los datos del formulario
form = RegistroUserForm(request.POST, request.FILES)
# Comprobamos si el formulario es valido
if form.is_valid():
# En caso de ser valido, obtenemos los datos del formulario.
# form.cleaned_data obtiene los datos limpios y los pone en un
# diccionario con pares clave/valor, donde clave es el nombre del campo
# del formulario y el valor es el valor si existe.
cleaned_data = form.cleaned_data
username = cleaned_data.get('username')
password = cleaned_data.get('password')
email = cleaned_data.get('email')
photo = cleaned_data.get('photo')
# E instanciamos un objeto User, con el username y password
user_model = User.objects.create_user(username=username, password=password)
# Añadimos el email
user_model.email = email
# Y guardamos el objeto, esto guardara los datos en la db.
user_model.save()
# Ahora, creamos un objeto UserProfile, aunque no haya incluido
# una imagen, ya quedara la referencia creada en la db.
user_profile = UserProfile()
# Al campo user le asignamos el objeto user_model
user_profile.user = user_model
# y le asignamos la photo (el campo, permite datos null)
user_profile.photo = photo
# Por ultimo, guardamos tambien el objeto UserProfile
user_profile.save()
# Ahora, redireccionamos a la pagina accounts/gracias.html
# Pero lo hacemos con un redirect.
return redirect(reverse('accounts.gracias', kwargs={'username': username}))
else:
# Si el mthod es GET, instanciamos un objeto RegistroUserForm vacio
form = RegistroUserForm()
# Creamos el contexto
context = {'form': form}
# Y mostramos los datos
return render(request, 'accounts/registro.html', context)
def gracias_view(request, username):
return render(request, 'accounts/gracias.html', {'username': username})
El código ya esta comentado, pero me gustaría comentar el redireccionamiento que se ha hecho cuando el formulario
ha sido valido.
Las partes de código importantes son las siguiente:
Importar los módulos a usar, mientras redirect redirecciona a otra pagina (como si lo escribiera en la barra de
navegación del explorador), reverse obtiene a que URI rediseccionar, el primer parámetro de reverse es el
name=’’ que se pone en los URLconf y los kwargs son los parámetros dinámicos que espera en el patrón de
24
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
url().
# accounts/urls.py
url(
r'gracias/(?P<username>[\w]+)/$',
views.gracias_view,
name='accounts.gracias'
),
Esta es la url que se ha añadido al URLconf y se pude ver (?P<username>[\w]+) que es una simple expresión
regular, donde <username> es la variable que pasara a la vista con el valor [\w]+.
Ahora queda la plantilla accounts/templates/accounts/gracias.html con el siguiente código
# accounts/templates/accounts/gracias.html
{% extends 'base.html' %}
{% block title %}Gracias por registrarte{% endblock title %}
{% block content %}
<h2>{{ username }} gracias por registrarte!</h2>
{% endblock content %}
Ya tenemos un registro básico en la base de datos, podemos ir a la administración y ver que se han creado 2 filas, una
en la tabla Usuarios y otra en User profiles.
Esto ha sido una manera de hacer un registro de usuario con un profile, hay varias maneras de hacer las cosas e incluso
paquetes de terceros, el objetivo era ver los formularios con forms.Form, pero también es posible crear formularios
obteniendo los datos de los modelos ‘forms.ModelForm‘ que veremos mas adelante. También hemos visto como
podemos validar los datos de los campos y lanzar errores forms.ValidationError().
En la siguiente sección veremos como hacer login y logout :)
1.9.2 Login usuario
Django ya incorpora funciones para hacer login (e incluso vistas y formularios, pero los vamos a omitir en este
tutorial).
En primer lugar creamos la vista
# accounts/views.py
# Añadimos
from django.contrib.auth import authenticate, login
from django.contrib.auth.decorators import login_required
@login_required
def index_view(request):
return render(request, 'accounts/index.html')
def login_view(request):
# Si el usuario esta ya logueado, lo redireccionamos a index_view
if request.user.is_authenticated():
return redirect(reverse('accounts.index'))
1.9. Creación accounts
25
Tutorial Django Documentation, Publicación 1.0
mensaje = ''
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
return redirect(reverse('accounts.index'))
else:
# Redireccionar informando que la cuenta esta inactiva
# Lo dejo como ejercicio al lector :)
pass
mensaje = 'Nombre de usuario o contraseña no valido'
return render(request, 'accounts/login.html', {'mensaje': mensaje})
La vista, lo primero que comprueba es si esta autenticado, si lo esta, mostrar el formulario es tontería y lo redirecciona
a la pagina principal index_view, después comprueba si la respuesta viene por el método POST, si no lo es,
renderiza la plantilla accounts/login.html, que seria la primea vez que se accede a la pagina.
En caso contrario, significa que la respuesta es por método POST, entonces obtiene los valores de los campos
username y password del formulario con request.POST.get(’nombre_campo’)
Ahora, comprueba una autenticación, que lo que hace es comprobar si username y password coinciden con un usuario
en la base de datos y en caso de éxito devolverá un objeto con el usuario, en caso contrario devolverá None con lo que
podemos redirecionar otra vez al formulario con el mensaje Nombre de usuario o contraseña no valido.
Si user no es None, significa que es un objeto User y comprueba si el usuario tiene una cuanta activa con if
user.is_active, si no la tiene, se debería cambiar el mensaje, para que el usuario sea consciente de por que no
puede hacer login. De lo contrario, si todo ha ido bien, el usuario se loguea y lo redirecciona a la vista index_view.
La vista index_view, tiene un decorador que comprueba si esta logueado o no, si no lo esta, redireccionara a la
pagina de login, de lo contrario, podrá acceder a la vista.
Nota: el decorador acepta un parámetro (entre otros) @login_required(login_url=’’), en caso de omitir
el parámetro redireccionara al valor de tutorial_django.settings.LOGIN_URL.
Ahora vamos a crear las dos plantillas, la del formulario para hacer login y la pagina index de accounts, esta ultima
pagina, simplemente mostrara un mensaje de bienvenida y la foto del usuario.
touch accounts/templates/accounts/{index.html,login.html}
# accounts/templates/accounts/index.html
{% extends 'base.html' %}
{% block title %}Perfil de usuario{% endblock title %}
{% block content %}
<div class="page-header"><h2><h2>Perfil de usuario</h2></div>
Hola de nuevo {{ user.username }}<br>
<div>
<img src="{{ MEDIA_URL }}{{ user.userprofile.photo }}" alt="Imagen de {{ user.username }}" />
</div>
{% endblock content %}
Se puede observar que cuando queremos mostrar un archivo media, lo hacemos con {{ MEDIA_URL }}, con esto,
obtiene la url de settings.MEDIA_URL, el resto, lo obtiene con {{ user.userprofile.photo }}. El
26
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
objeto user esta accesible en todos las plantillas, accede a UserProfile con el mismo nombre pero en minúsculas
y finalmente accede a la propiedad photo
# accounts/templates/accounts/login.html
{% extends 'base.html' %}
{% block title %}Login{% endblock title %}
{% block content %}
<div class="row">
<div class="col-md-4 col-md-offset-4">
<div class="page-header"><h2>Login</h2></div>
{% if mensaje %}
<div class="alert alert-danger">
{{ mensaje }}
</div>
{% endif %}
<form method="post" action="">
{% csrf_token %}
<div class="form-group">
<label class="control-label" for="username">Nombre de usuario</label>
<input type="text" id="username" name="username" class="form-control" value="{{ u
</div>
<div class="form-group">
<label for="password">Contraseña</label>
<input type="password" name="password" id="password" class="form-control">
</div>
<button type="submit" class="btn btn-primary">Login</button>
</form>
</div>
</div>
{% endblock content %}
Por ultimo, tenemos que añadir las dos urls en el URLconf.
# accounts/urls.py
# Añadir dentro de urlpatterns
urlpatterns = [
url(r'^$', views.index_view, name='accounts.index'),
url(r'^login/$', views.login_view, name='accounts.login'),
# ...
]
Vamos a ver si todo funciona mas o menos bien :P, para ello, si estas logueado (hasta ahora, la única manera de
hacerlo era a través de la administración) y entras a http://127.0.0.1:8000/accounts/login/, veras que te redirecciona a
http://127.0.0.1:8000/accounts/ (así que, deslogueate desde la administración y prueba de nuevo).
Y si lo haces al revés, si no estas logueado e intentas acceder a http://127.0.0.1:8000/accounts/, te redireccionara a
http://127.0.0.1:8000/accounts/login/.
¿No puedes ver la imagen?, :), primero en tutorial_django/settings en la lista TEMPLATES, hay otra
lista context_processors, asegúrate que ’django.template.context_processors.media’, esta
incluido en la lista (en Django 1.8, no viene por defecto), a parte, cuando estés con el servidor de desarrollo, en
totorial_django/urls.py inserta lo siguiente:
1.9. Creación accounts
27
Tutorial Django Documentation, Publicación 1.0
# tutorial_django/urls.py
# Añade esto, al inicio del documento
from django.conf import settings
# Añade esto, al final del documento
if settings.DEBUG:
# static files (images, css, javascript, etc.)
urlpatterns.append(
# /media/:<mixed>path/
url(
regex=r'^media/(?P<path>.*)$',
view='django.views.static.serve',
kwargs={'document_root': settings.MEDIA_ROOT}))
Prueba ahora a ver si puedes ver la imagen!
Pues ya tenemos casi terminado el sistema de usuarios, queda el contrario, poder hacer logout, que sera en la próxima
sección y la manera de que el usuario pueda modificar sus datos.
1.9.3 Logout de un usuario
Ahora nos queda que el usuario pueda desloguearse del sitio, de lo que llevamos hecho, esto es lo mas fácil y sobre
todo, como ya debes entender el funcionamiento de las vistas y plantillas, pues todo sera rápido y sin dolor!.
Como siempre, se ha de crear una vista y añadir la url() al URLconf, pero en esta ocasión, no vamos a crear una
plantilla, a cambio, lo mostraremos con un mensaje para así echar un ojo al sistema django.contrib.messages
(hasta los haremos un poco bonitos :P).
Empecemos con la vista, es tan simple que ni la comentare.
# accounts/views.py
# Añadir import logout y messages
from django.contrib.auth import authenticate, login, logout
from django.contrib import messages
def logout_view(request):
logout(request)
messages.success(request, 'Te has desconectado con exito.')
return redirect(reverse('accounts.login'))
Antes de continuar, vamos a crear un pequeño sistema que muestra los mensajes con un poco de estilo CSS gracias a
Bootstrap.
<!-- templates/base.html ->
<!-- Antes de {% block content %}{% endblock content %} añadimos -->
{% include '_messages.html' %}
{% block content %}{% endblock content %}
Con la etiqueta { % include ‘_nombre_plantilla.html’ %} importa un archivo html y lo ‘vuelca’ en el lugar donde es
llamado.
El archivo _messages.html es una plantilla que ahora vamos a crear y lo creamos en templates de la raíz del
proyecto.
28
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
<!-- templates/_messages.html -->
{% if messages %}
<div class="row dj-messages">
<div class="col-md-6 col-md-offset-3">
{% for message in messages %}
{% if message.tags == 'error' %}
<div class="alert alert-danger" role="alert">
{% else %}
<div class="alert alert-{{ message.tags }}" role="alert">
{% endif %}
{{ message }}<button type="button" class="close" data-dismiss="alert">&times;</bu
</div>
{% endfor %}
</div>
</div>
{% endif %}
Primero comprueba si existe una variable messages y en caso de existir, mostrara el mensaje con un estilo (usando
Bootstrap), con un diseño decente, si no existe messages, no insertara nada.
También vamos a poner en la barra de navegación, un link para que un usuario pueda hacer login/logout.
<!-- templates/base.html -->
<!DOCTYPE html>
{% load staticfiles %}
<html lang="es">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}{% endblock title %}</title>
<link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.css' %}">
<link rel="stylesheet" href="{% static 'css/main.css' %}">
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Project name</a>
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="active"><a href="#">Home</a></li>
<li><a href="#about">About</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
{% if user.is_authenticated %}
<li><a href="{% url 'accounts.index' %}">{{ user.username }}</a></li>
<li><a href="{% url 'accounts.logout' %}">Logout</a></li>
1.9. Creación accounts
29
Tutorial Django Documentation, Publicación 1.0
{% else %}
<li><a href="{% url 'accounts.registro' %}">Registro</a></li>
<li><a href="{% url 'accounts.login' %}">Login</a></li>
{% endif %}
</ul>
</div><!--/.nav-collapse -->
</div>
</nav>
<div class="container">
{% include "_messages.html" %}
{% block content %}{% endblock content %}
</div><!-- /.container -->
<script src="{% static 'jquery/jquery.js' %}"></script>
<script src="{% static 'bootstrap/js/bootstrap.js' %}"></script>
<script src="{% static 'js/main.js' %}"></script>
</body>
</html>
Resalto la parte añadida:
<!-- templates/base.html -->
<ul class="nav navbar-nav navbar-right">
{% if user.is_authenticated %}
<li><a href="{% url 'accounts.index' %}">{{ user.username }}</a></li>
<li><a href="{% url 'accounts.logout' %}">Logout</a></li>
{% else %}
<li><a href="{% url 'accounts.registro' %}">Registro</a></li>
<li><a href="{% url 'accounts.login' %}">Login</a></li>
{% endif %}
</ul>
Como se vio anteriormente, el objeto user, esta disponible en las plantillas y un método es is_autenthicate
(como también se vio en una vista), por lo que si esta autenticado, mostrara el botón para acceder al indice de la cuenta
con el nombre del usuario y Logout para darle la posibilidad de desloguearse del sistema, en caso contrario, mostrara
los botones Registro y Login para que pueda registrarse o loguearse respectivamente.
También se puede ver, que cuando haces logout, mostrara el mensaje Te has desconectado con éxito con
una x para cerrar el mensaje, el mensaje solo lo mostrara una vez (si actualizas la pagina, no aparecerá) y solo lo ve el
usuario que ha hecho logout.
Ya tenemos casi terminado el sistema de usuarios (una versión simple), en la siguiente sección, veremos como actualizar algunos datos del usuario.
1.9.4 Editar perfil de usuario
Ahora tenemos que darle la oportunidad al usuario de poder modificar datos, como cambiar el email, contraseña y la
foto.
Para empezar, vamos a modificar la pagina principal de accounts para añadir links a la edición por separado de los
datos.
Vamos a añadir un menú lateral, para poner los links y después, añadiremos
editar_contrasena.html, editar_foto.html, editar_email.html.
tres
plantillas,
Como las tres paginas (a parte de index.html), incluirán el menú lateral, seria repetirse cuatro veces la parte del menú,
por lo que vamos a crear una plantilla _menu.html.
30
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
Aparte, necesitamos una plantilla para representa el menú en un lado y el contenido, en el resto de la pagina, crearemos
también una platilla base_accounts.html que represente ambos espacios.
Vamos a crear los archivos:
touch accounts/templates/accounts/{base_accounts.html,editar_contrasena.html,editar_foto.html,editar_
Editamos accounts/templates/accounts/base_accounts.html
<!-- accounts/templates/accounts/base_accounts.html -->
{% extends 'base.html' %}
{% block content %}
<div class="row">
<div class="col-md-2">
<div class="list-group">
<a href="#" class="list-group-item">Editar email</a>
<a href="#" class="list-group-item">Editar contraseña</a>
<a href="#" class="list-group-item">Editar foto</a>
</div>
</div>
<div class="col-md-10">
{% block accounts_content %}{% endblock accounts_content %}
</div>
</div>
{% endblock content %}
Mas tarde, ya añadiremos los links, por ahora, para hacerse una idea ya vale.
Editamos accounts/templates/accounts/index.html
<!-- accounts/templates/accounts/index.html -->
{% extends 'accounts/base_accounts.html' %}
{% block title %}Perfil de usuario{% endblock title %}
{% block accounts_content %}
<div class="page-header"><h2><h2>Perfil de usuario</h2></div>
Hola de nuevo {{ user.username }}<br>
<div>
<img src="{{ MEDIA_URL }}{{ user.userprofile.photo }}" alt="Imagen de {{ user.username }}" />
</div>
{% endblock accounts_content %}
Los cambios ({ % extends ’accounts/base_accounts.html’ %} y los blocks { % block
accounts_content %} y { % endblock accounts_content %}), son fáciles de entender, en vez
de usar extends de base.html (base_accounts.html usa extends a base.html, por lo que no
perderemos el diseño anterior) usara uno para que las cuatro paginas, tengan el mismo aspecto.
Editar Email
Empecemos con editar email, necesitamos un formulario de un solo campo, después el email ha de ser único en la base
de datos (si lo ha cambiado, comprobar que no exista uno igual), en caso de éxito, redireccionamos otra vez a index,
en caso contrario, se lo notificaremos a usuario.
El formulario
1.9. Creación accounts
31
Tutorial Django Documentation, Publicación 1.0
# accounts/forms.py
class EditarEmailForm(forms.Form):
email = forms.EmailField(
widget=forms.EmailInput(attrs={'class': 'form-control'}))
def __init__(self, *args, **kwargs):
"""Obtener request"""
self.request = kwargs.pop('request')
return super().__init__(*args, **kwargs)
def clean_email(self):
email = self.cleaned_data['email']
# Comprobar si ha cambiado el email
actual_email = self.request.user.email
username = self.request.user.username
if email != actual_email:
# Si lo ha cambiado, comprobar que no exista en la db.
# Exluye el usuario actual.
existe = User.objects.filter(email=email).exclude(username=username)
if existe:
raise forms.ValidationError('Ya existe un email igual en la db.')
return email
La vista
# accounts/views.py
@login_required
def editar_email(request):
if request.method == 'POST':
form = EditarEmailForm(request.POST, request=request)
if form.is_valid():
request.user.email = form.cleaned_data['email']
request.user.save()
messages.success(request, 'El email ha sido cambiado con exito!.')
return redirect(reverse('accounts.index'))
else:
form = EditarEmailForm(
request=request,
initial={'email': request.user.email})
return render(request, 'accounts/editar_email.html', {'form': form})
Es la misma rutina de siempre, comprueba el método, y actúa en consecuencia, lo único distinto, es ver como pasamos
el objeto HttpRequest al instanciar el formulario y como rellenamos datos en el formulario con el argumento
initial.
La plantilla
<!-- accounts/templates/accounts/editar_email.html -->
{% extends 'accounts/base_accounts.html' %}
{% block title %}Editar email{% endblock title %}
{% block accounts_content %}
<h2 class="page-header">Editar Email</h2>
<form method="post" action="">
32
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
{% csrf_token %}
{{ form.as_p }}
<button class="btn btn-primary" type="submit">Actualizar Email</button>
<a href="{% url 'accounts.index' %}" class="btn btn-warning" type="submit">Cancelar</a>
</form>
{% endblock accounts_content %}
URLconf
# accounts/urls.py
# Añadimos en urlpatterns
url(r'^editar_email/$', views.editar_email, name='accounts.editar_email'),
Y actualizamos el link en accounts/templates/accounts/base_accounts.html
<!-- accounts/templates/accounts/base_accounts.html -->
<a href="{% url 'accounts.editar_email' %}" class="list-group-item">Editar email</a>
Editar contraseña
La contraseña requiere de tres campos, uno con la contraseña actual, otro para insertar nueva contraseña y un ultimo
para repetir de nueva contraseña.
El formulario
# accounts/forms.py
class EditarContrasenaForm(forms.Form):
actual_password = forms.CharField(
label='Contraseña actual',
min_length=5,
widget=forms.PasswordInput(attrs={'class': 'form-control'}))
password = forms.CharField(
label='Nueva contraseña',
min_length=5,
widget=forms.PasswordInput(attrs={'class': 'form-control'}))
password2 = forms.CharField(
label='Repetir contraseña',
min_length=5,
widget=forms.PasswordInput(attrs={'class': 'form-control'}))
def clean_password2(self):
"""Comprueba que password y password2 sean iguales."""
password = self.cleaned_data['password']
password2 = self.cleaned_data['password2']
if password != password2:
raise forms.ValidationError('Las contraseñas no coinciden.')
return password2
La vista
# accounts/views.py
1.9. Creación accounts
33
Tutorial Django Documentation, Publicación 1.0
# Añadir al inicio
from django.contrib.auth.hashers import make_password
# Modificar al inicio
from .forms import (
RegistroUserForm, EditarEmailForm, EditarContrasenaForm)
# Añadir al final
@login_required
def editar_contrasena(request):
if request.method == 'POST':
form = EditarContrasenaForm(request.POST)
if form.is_valid():
request.user.password = make_password(form.cleaned_data['password'])
request.user.save()
messages.success(request, 'La contraseña ha sido cambiado con exito!.')
messages.success(request, 'Es necesario introducir los datos para entrar.')
return redirect(reverse('accounts.index'))
else:
form = EditarContrasenaForm()
return render(request, 'accounts/editar_contrasena.html', {'form': form})
Observa como usamos make_password() para generar un password con hash (no se traducir esto, lo siento :)),
es muy importante, ya que si no, guardara la contraseña en texto plano y es un gran error por motivos de seguridad!.
(Lo pongo aquí, aunque seria parte del EditarContrasenaForm), también hay una función
check_password() <https://docs.djangoproject.com/en/1.4/topics/auth/#django.contrib.auth.ha
que podríamos haber comprobado en un método ‘‘clean_actual_password() y comprobar
si actual_password es igual a password informar al usuario que esta usando la misma contraseña que la actual
(lo dejo como ejercicio para el lector).
La plantilla
<!-- accounts/templates/accounts/editar_contrasena.html -->
{% extends 'accounts/base_accounts.html' %}
{% block title %}Editar email{% endblock title %}
{% block accounts_content %}
<h2 class="page-header">Editar contraseña</h2>
<form method="post" action="">
{% csrf_token %}
{{ form.as_p }}
<button class="btn btn-primary" type="submit">Actualizar contraseña</button>
<a href="{% url 'accounts.index' %}" class="btn btn-warning" type="submit">Cancelar</a>
</form>
{% endblock accounts_content %}
El URLconf
# accounts/urls.py
# Añadir a urlpatterns
url(r'^editar_contrasena/$', views.editar_contrasena, name='accounts.editar_contrasena'),
Actualizar base_accounts.html
34
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
<!-- accounts/templates/accounts/base_accounts.html -->
<a href="{% url 'accounts.editar_contrasena' %}" class="list-group-item">Editar contraseña</a>
Ya solo queda editar la imagen, pero lo dejo como ejercicio para el lector, ademas, también dejo como ejercicio, si nos
fijamos, las plantillas editar_x son prácticamente iguales, es decir nos repetimos demasiado!!, intenta que con una
plantilla muestra los datos que quieres mostrar. Como pista podrías crear una sola pagina de formulario y dentro de la
pagina añadir variables de contexto {{ titulo }}, etc y pasarlas de las vistas a las plantillas.
1.10 Creacion blog
En esta ocasión, vamos a crear un blog, donde se explicara en mas detalles partes como los modelos y vistas, que se
vio en accounts, pero se profundizara mas.
En esta parte del tutorial, se usaran CBV y ModelForm para mostrar otros conceptos del Framework.
1.10.1 Creación APP Blog
Hasta ahora, tenemos creado toda la parte de gestión de usuario, registro, login, logout el usuario puede cambiar
algunos datos de su perfil.
Comprendemos como estructurar las plantillas, como incluir archivos media y static, comprendemos las relaciones
model, view, template MVT (o MVC), como añadir patrones en el router, etc.
A partir de ahora, cambiare un poco las maneras de hacer el tutorial, ya que si no, seria repetirse un poco.
En vez de usar vistas basadas en funciones (Function Based Views FBV), usaremos vistas basadas en clases (Class
Based Views CBV), intentare explicar los fundamentos del ORM de Django, los fundamentos de las plantillas,
inclusion tags y otras cosas.
De momento, solo vamos a crear la app y añadirla en INSTALLED_APPS y e incluir las urls.
./manage.py startapp blog
touch blog/urls.py
mkdir -p blog/templates/blog
# tutorial_django/settings.py
INSTALLED_APPS = (
# ...
'home',
'accounts',
'blog',
)
La app ya esta creada, en la siguiente sección, empezamos con los modelos.
1.10.2 Creación del modelo
Una aplicación siempre que use una base de datos, se comienza por el modelo, la representación de la base de datos,
sus tablas y sus campos (clases y propiedades en Python), el blog se compone de dos tablas, una para las etiquetas Tag
y otra para los artículos Article.
1.10. Creacion blog
35
Tutorial Django Documentation, Publicación 1.0
Tag tendra tres campos
id Primary Key creado de manera implícita.
name string, único.
slug string, nombre amigable de name, único.
Article tendrá mas campos, el id, titulo, slug, owner (propietario del articulo)
id Primary Key creado de manera implícita.
title string, único.
slug string, nombre amigable de title, único.
body text, cuerpo del articulo.
owner ForeignKey (User), propietario del articulo.
tags ManyToMany (Tag), lista de etiquetas.
create_at DateTime, fecha y hora de creación.
update_at DateTime, fecha y hora de ultima modificación.
Los Primary Key, los crea de manera implícita a no ser que se cree explícitamente, owner es una relación un
usuario sera propietario de muchos artículos o muchos artículos pueden pertenecer a un mismo propietario,
entonces desde la perspectiva Article es una relación muchos a uno y desde la perspectiva User uno a muchos
(corríjanme si estoy equivocado). En cuanto a tags es una relación muchos a muchos, una articulo puede pertenecer
a muchas Tag y un Tag pueden contener muchos Article.
Cuando se crea un campo Foreign Key añade un campo extra en la base de datos, en el caso de owner la tabla
article creara un campo owner_id que sera la relación con auth_user.id, en cuanto a tags, creara una
nueva tabla blog_article_tag con tres columnas, id, article_id y tag_id
Con esto en mente, vamos a crear el modelo en blog/models.py, lo mas simple posible, ya mas adelante iremos
añadiendo mas código.
from django.db import models
from django.contrib.auth.models import User
class Tag(models.Model):
name = models.CharField(max_length=100, unique=True)
slug = models.CharField(max_length=100, unique=True)
class Article(models.Model):
title = models.CharField(max_length=100, unique=True)
slug = models.CharField(max_length=100, unique=True)
body = models.TextField()
owner = models.ForeignKey(User, related_name='article_owner')
tags = models.ManyToManyField(Tag, related_name='article_tags')
create_at = models.DateTimeField(auto_now_add=True)
# update_at = models.DateTimeField(auto_now=True)
Nota: He dejado un campo comentado a propósito update_at = models.DateTimeField(auto_now=True)
que mas tarde añadiremos, lo hago para mostrar como actúan las migraciones.
36
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
Creo que con lo que se comento anteriormente, mas lo que llevamos hecho con accounts, es bastante claro lo que
hace, pero quizá haya un par de cosas a comentar, los argumentos de las propiedades, algunos argumentos como
max_length en los clases CharField son obligatorios, y especifica el tamaño del campo en la base de datos,
ademas, en los formularios, comprobara que el texto insertado por el usuario, no pase de la cantidad puesta. En cuanto
a unique, declaramos que el campo sera único.
Otro apunte son los campos owner y tags de la clase Article, el primer argumento, hace referencia a la relación, en este caso la clase a la que tiene relación y related_name es un nombre al que podremos acceder cuando
hagamos relación inversa, en el caso de Tag, cuando queramos acceder al titulo, lo haremos de la siguiente manera tag_object.article_tags.title. related_name, es opcional y en caso de omitirlo, para acceder al
campo title se haria tag_object.article.title, es decir, nombre de clase relacional en minúsculas.
auto_now_add y auto_now, argumentos dice: auto_now_add inserta de manera implícita la fecha y hora (en
este caso ‘DateTime’) cuando se crea un articulo y auto_now, actualiza la fecha y hora, cuando se actualice un
articulo.
Nota: En este caso, La clase Article esta debajo de Tag en el código Python y por eso tags =
models.ManyToManyField(Tag) es posible poner Tag, es decir el literal de clase, en caso de que la clase
Tag estuviera debajo de Article, para decirle que la relación es la clase Tag, se deberá usar entre comillas, tags
= models.ManyToManyField(’Tag’).
El siguiente paso es crear una migración (preparar los cambios) y migrar (crear los cambios en la base de datos).
./manage.py makemigrations blog
Migrations for 'blog':
0001_initial.py:
- Create model Article
- Create model Tag
- Add field tags to article
Antes de crear la migración, vamos a ver que código SQL nos va a generar (el código es el generado para SQLite).
./manage.py sqlmigrate blog 0001_initial
BEGIN;
CREATE TABLE "blog_article" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"title" varchar(100) NOT NULL UNIQUE,
"slug" varchar(100) NOT NULL UNIQUE,
"body" text NOT NULL,
"create_at" datetime NOT NULL,
"owner_id" integer NOT NULL REFERENCES "auth_user" ("id")
);
CREATE TABLE "blog_tag" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" varchar(100) NOT NULL UNIQUE,
"slug" varchar(100) NOT NULL UNIQUE
);
CREATE TABLE "blog_article_tags" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"article_id" integer NOT NULL REFERENCES "blog_article" ("id"),
"tag_id" integer NOT NULL REFERENCES "blog_tag" ("id"), UNIQUE ("article_id", "tag_id")
);
CREATE INDEX "blog_article_5e7b1936" ON "blog_article" ("owner_id");
CREATE INDEX "blog_article_tags_a00c1b00" ON "blog_article_tags" ("article_id");
CREATE INDEX "blog_article_tags_76f094bc" ON "blog_article_tags" ("tag_id");
1.10. Creacion blog
37
Tutorial Django Documentation, Publicación 1.0
COMMIT;
Si nos parece bien, ejecutamos la migración para hacer los cambios (en este caso crear las tablas) en la base de datos.
./manage.py migrate
Operations to perform:
Synchronize unmigrated apps: messages, staticfiles
Apply all migrations: accounts, contenttypes, admin, auth, blog, sessions
Synchronizing apps without migrations:
Creating tables...
Running deferred SQL...
Installing custom SQL...
Running migrations:
Rendering model states... DONE
Applying blog.0001_initial... OK
Como se puede ver en la base de datos, ahora se han creado las tres tablas. Antes comentemos un campo (propiedad)
en la clase Article, la descomentamos y ejecutamos makemigrations blog y migrate.
./manage.py makemigrations blog
You are trying to add a non-nullable field 'update_at' to article without a default; we can't do that
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows)
2) Quit, and let me add a default in models.py
Select an option:
Nos esta diciendo que el campo update_at es un campo not null por lo que no puede añadir un campo sin datos
(en este caso no hay filas, pero bueno :)), así que nos pregunta si queremos añadir un dato ahora o cambiar el modelo
y añadir un dato por defecto.
En este caso, vamos a añadir la opcion 1º y añadimos timezone.now().
Select an option: 1
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now()
>>> timezone.now()
Migrations for 'blog':
0002_article_update_at.py:
- Add field update_at to article
Y hacemos la migración para actualizar la base de datos:
./manage.py migrate
Ahora el campo update_at ya esta en la base de datos :)
Antes de continuar, explicar que las clases de modelo creadas Tag y Article son subclases de
django.db.models.Model, que tienen una propiedad objects (por lo tanto Tag y Article también tienen la propiedad objects), que es una clase django.db.models.Manager. Es una interface a través de la
cual las operaciones de consulta de base de datos se proporcionan a los modelos de Django, en resumen, el manager
(gestor) de un modelo es un objeto a través del cual los modelos de Django realizan consultas de bases de datos. Cada
modelo de Django tiene al menos un manager, y puedes crear managers personalizados con el fin de personalizar el
acceso de base de datos.
Django tiene un argumento con ./manage.py que es shell y es lo mismo que la consola interactiva de Python,
pero que añade al path el proyecto (ejecuta internamente django.setup()) y tenemos a nuestra disposición los
modelos del proyecto.
38
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
Desde la consola vamos a ver 4 cosillas para interactuar con el ORM incorporado de Django y añadir, actualizar, etc
filas en la base de datos.
./manage.py shell
Ahora, como si estuviéramos en un archivo .py, vamos a crear artículos, primero importamos los módulos y creamos
un par de tags, después creamos algunos artículos.
Iré # comentando paso a paso las instrucciones, >>> es indicativo de que es una instrucción y se ha de omitir en la
escritura en la terminal, por ultimo también mostrare la salida.
>>> from blog.models import Tag, Article
# Comprobar cuantas etiquetas hay, para ello se usa el metodo all()
# que obtiene una lista con todos los elementos existentes en la db (si los hay)
>>> Tag.objects.all()
[]
# Creamos un objeto Tag, insertamos datos y guardamos con save() el objeto
# Con save(), guardara en la base de datos la fila
>>> tag = Tag()
>>> tag.name = 'Linux'
>>> tag.slug = 'linux'
>>> tag.save()
>>> Tag.objects.all()
[<Tag: Tag object>]
Como se puede ver, nos devuelve [<Tag: Tag object>], una lista con un elemento, vamos a modificar
blog/models.py
# blog/models.py
class Tag(models.Model):
# .....
def __str__(self):
return self.name
class Article(models.Model):
# .....
def __str__(self):
return self.title
Al haber realizado un cambio en el archivo models.py, se ha de salir del interprete
>>> exit()
./manage.py shell
>>> from blog.models import Tag, Article
>>> Tag.objects.all()
[<Tag: Linux>]
# Vamos a crear 2 tags mas, pasando los datos en el 'constructor' de Tag
>>> tag1 = Tag(name='Windows', slug='windows')
>>> tag1.save()
1.10. Creacion blog
39
Tutorial Django Documentation, Publicación 1.0
>>> Tag.objects.all()
[<Tag: Linux>, <Tag: Windows>]
>>> tag2 = Tag(name='Mac OS X', slug='mac-os-x')
>>> tag2.save()
>>> Tag.objects.all()
[<Tag: Linux>, <Tag: Windows>, <Tag: Mac OS X>]
Ahora vamos a modificar un elemento, primero obtendremos el elemento que queremos modificar con
filter(nombre_campo=’valor_campo’), donde nombre_campo, es el nombre de la propiedad Tag y
valor_campo es un valor, por ejemplo Windows. Nos devolverá siempre, una lista con 0 o mas elementos, tantos
como campos tengan un valor Windows (en este caso, la propiedad name es unique, por lo que obtendremos 0 o 1
elemento) y en este caso, si no existe, no lanzara una excepción (mas tarde veremos lo de en este caso)
Después de obtener el elemento (que sabemos de antemano, que sera 0 o 1 elemento), lo modificaremos y por ultimo
actualizaremos los datos en la base de datos.
>>> Tag.objects.all()
[<Tag: Linux>, <Tag: Windows>, <Tag: Mac OS X>]
# Obtener el primer elemento
>>> w = Tag.objects.filter(name='Windows')[0]
>>> w
<Tag: Windows>
>>> type(w)
<class 'blog.models.Tag'>
>>> w.name = 'Microsoft Windows'
>>> w.slug = 'microsoft-windows'
>>> w.save()
>>> w
<Tag: Microsoft Windows>
>>> Tag.objects.all()
[<Tag: Linux>, <Tag: Microsoft Windows>, <Tag: Mac OS X>]
Si observamos en el filtro Tag.objects.filter(name=’Windows’)[0] el [0], es puro Python, la manera
de obtener x elementos, Django lo traduce como:
Tag.objects.filter(field=’valor’)[0] LIMIT 1 El elemento que corresponda a X, el elemento
0 es el primer elemento.
Tag.objects.filter(field=’valor’)[:5] LIMIT 5 Los 5 primeros elementos
Tag.objects.filter(field=’valor’)[5:10] OFFSET 5 LIMIT 5 Cinco elementos a partir del
5 elemento.
Por lo tanto, solo devuelve 1 elemento y es el objeto, en caso de no utilizar [x] o [x:x], siempre devolverá una lista.
Así que w es un objeto Tag por lo que se puede acceder a sus propiedades y métodos directamente y lo que hacemos
es modificar el name y slug, por ultimo, guardamos los cambios en la base de datos.
Vamos primero a modificar el modelo, la clase django.db.models.Model tiene un método save(), que
se ejecuta justo antes de guardar/actualizar datos en la base de datos. Vamos a aprovecharlo para cambiar el
slug dinamicamente, asi solo sera necesario cambiar/poner el name y justo antes de guardar/actualizar, Django(Python), nos cambiara el slug. A la vez, Django tiene una función para generar slugs validos que se encuentra
en django.utils.text.slugify.
# blog/models.py
# Añadir al inicio
from django.utils.text import slugify
class Tag(models.Model):
40
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
# ...
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
return super().save(*args, **kwargs)
class Article(models.Model):
# ...
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
return super().save(*args, **kwargs)
Vamos a cambiar de nuevo ‘Microsoft Windows’ por ‘Windows’ y a ver que pasa
>>> w = Tag.objects.filter(name='Microsoft Windows')[0]
>>> w
<Tag: Microsoft Windows>
>>> w.name = 'Windows'
>>> w.save()
>>> Tag.objects.filter(name='Windows')[0].slug
'windows'
Se puede observar, que ahora el slug es generado dinamicamente :)
Otro método de Manager, es get(**kwargs), como argumentos, acepta pares clave/valor, como
filter() a excepción que siempre devuelve un solo elemento y si hay mas de un elemento lanzara
MultipleObjectsReturned y que si no hay coincidencia, lanzara DoesNotExist.
>>> Tag.objects.get(pk=1)
<Tag: Linux>
>>> Tag.objects.get(slug='windows')
<Tag: Windows>
>>> Tag.objects.get(id=1)
<Tag: Linux>
>>> Tag.objects.get(id=10)
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/home/snicoper/.virtualenvs/tutorial_django/lib/python3.4/site-packages/django/db/models/mana
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/home/snicoper/.virtualenvs/tutorial_django/lib/python3.4/site-packages/django/db/models/quer
self.model._meta.object_name
blog.models.DoesNotExist: Tag matching query does not exist.
>>> Tag.objects.get(name__icontains='i')
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/home/snicoper/.virtualenvs/tutorial_django/lib/python3.4/site-packages/django/db/models/mana
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/home/snicoper/.virtualenvs/tutorial_django/lib/python3.4/site-packages/django/db/models/quer
(self.model._meta.object_name, num)
blog.models.MultipleObjectsReturned: get() returned more than one Tag -- it returned 2!
Aunque el campo de clave primaria pk es id, también es posible usar pk en los campos y Django, usara el nombre del
campo que sea PRIMARY KEY (que por defecto es siempre id).
1.10. Creacion blog
41
Tutorial Django Documentation, Publicación 1.0
name__icontains es un Field lookups (Búsquedas de campo), contiene el nombre de la propiedad y
__tipodebusqueda=valor‘‘(dos guiones bajos), en este caso ‘‘icontains y la i de insensitive, hace una búsqueda insensitiva (me van a matar los puristas del castellano, sry ^^), es decir en SQL seria
algo así:
Tag.objects.filter(name__icontains=’algo’) se traduciría a SELECT * FROM blog_tag
WHERE name ILIKE ’ %algo %’
Hay muchas búsquedas de campo y le puedes echar un ojo en la documentación de Django .
Ahora, vamos a crear algunas entradas.
# Importar Tag y Article y el usuario creado
>>> from blog.models import Tag, Article
>>> from django.contrib.auth.models import User
>>> u = User.objects.get(pk=1)
>>> u
<User: snicoper>
>>> u.email
'[email protected]'
# Obtenemos 2 de 3 elementos tags que hay en la db
>>> ts = Tag.objects.all()[1:]
>>> ts
[<Tag: Windows>, <Tag: Mac OS X>]
# Creamos un articulo
>>> a = Article()
>>> a.title = 'Primer articulo'
>>> a.body = 'Contenido del articulo'
>>> a.owner = u
# Es necesario guardar el objeto antes de añadir relaciones many to many
>>> a.save()
# y añadimos las relaciones, una lista con 2 elementos
>>> a.tags.add(*ts)
# Guardamos los cambios
>>> a.save()
# Comprobamos los resultados
>>> article = Article.objects.get(title='Primer articulo')
>>> article.owner
<User: snicoper>
>>> article.tags
<django.db.models.fields.related.create_many_related_manager.<locals>.ManyRelatedManager object at 0x
>>> article.tags.all()
[<Tag: Windows>, <Tag: Mac OS X>]
>>> article.slug
'primer-articulo'
Ahora, vamos a ver cuantos artículos ha publicado el usuario
>>> from django.contrib.auth.models import User
>>> u = User.objects.get(pk=1)
# Accedemos al modelo Article, con el nombre de related_name que pusimos
>>> u.article_owner
<django.db.models.fields.related.create_foreign_related_manager.<locals>.RelatedManager object at 0x7
>>> u.article_owner.all()
[<Article: Primer articulo>]
>>> u.article_owner.all()[0].title
42
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
'Primer articulo'
# Rizar el rizo
# obtener el primer articulo de todos los que haya publicado el usuario
# obtener la primera tag, de todas las que tenga el articulo y mostrar el name
>>> u.article_owner.all()[0].tags.all()[0].name
'Windows'
Poco a poco iremos viendo y comentado nuevos métodos que tiene Manager, pero para empezar, creo que se hace
uno una idea del tema, para ver mas sobre el tema, te recomiendo la documentación
En la siguiente sección, veremos un poco por encima la administración Django.
1.10.3 Administración
La administración de Django es un añadido espectacular, genera dinamicamente un sistema CRUD que nos ahorra
cientos de horas, que incluso usando otros lenguajes como PHP o ASP.NET MVC 6 (vnext), yo al menos incluiría el
sistema al menos para la parte del desarrollo por lo cómodo que es.
Lo vimos con Creación accounts, donde mostrábamos como podíamos editar, añadir o ver los perfiles de usuario.
Para empezar vamos a hacer lo mismo, en primer lugar arrancamos el servidor ./manage.py runserver y vamos
a ir a la administración http://127.0.0.1:8000/admin/
Se observa (y ahora, se esperaba) que no muestra los modelos creados en el blog, para ello, como con accounts,
editamos el archivo blog/admin.py para incluir los modelos a la administración.
# blog/admin.py
from django.contrib import admin
from .models import Article, Tag
admin.site.register(Article)
admin.site.register(Tag)
Ahora si actualizamos veremos que muestra ambos modelos (también, algo que ya esperábamos).
1.10. Creacion blog
43
Tutorial Django Documentation, Publicación 1.0
Lo primero que vamos hacer es cambiar los nombres que muestra de los modelos, por defecto (usa reglas del ingles),
añade s o es al final del nombre del modelo, Article lo cambia a Articles y Country lo cambiaría a Countries y no
siempre es lo que desearíamos, a parte, si vemos en User profiles que pertenece al modelo UserProfile, se intuye
que parte las palabras en las letras mayúsculas y lo convierte en minúsculas excepto el primer carácter.
Para cambiar este comportamiento por defecto, vamos a crear unas clases dentro de las clases del modelo llamada
Meta y dentro, añadiremos meta información.
Primero cambiamos el modelo UserProfile
# accounts/models.py
class UserProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
photo = models.ImageField(upload_to='profiles', blank=True, null=True)
class Meta:
verbose_name = 'Perfil de usuario'
verbose_name_plural = 'Perfiles de usuarios'
# ...
Y ahora, el modelo del blog
# blog/models.py
class Tag(models.Model):
# ...
class Meta:
verbose_name = 'Etiqueta'
verbose_name_plural = 'Etiquetas'
# ...
class Article(models.Model):
# ...
class Meta:
verbose_name = 'Articulo'
44
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
verbose_name_plural = 'Articulos'
# ...
Realmente fácil como cambiar el nombre que mostrara los modelos.
Ahora si pinchamos en Articulos vemos que nos muestra un elemento
Siendo sinceros, no es mucha información, para ver quien lo creo y cuando, abría que pinchar para editarlo y ver la
información (que las fechas ni las podríamos ver en un principio).
Nota:
Las fechas en los modelos en los campos pasamos los argumentos auto_now=True y
auto_now_add=True (se añadirán o modificaran automáticamente) por lo que en la administración no muestra los campos. Si queremos modificarlos a mano, en los campos del modelo, deberemos añadir el argumento editable=True de esta manera create_at = models.DateTimeField(auto_now_add=True,
editable=True)
De igual manera, los slugs podríamos hacer que no muestre el campo en la administración (recuerda que se generan automáticamente antes de guardar el objeto) de esta manera slug = models.SlugField(max_length=100,
editable=False)
Vamos a modificar el comportamiento, para que nos muestre los campos que consideremos que nos de una información
útil.
# blog/admin.py
class ArticleAdmin(admin.ModelAdmin):
list_display = ('title', 'owner', 'create_at', 'update_at')
admin.site.register(Article, ArticleAdmin)
Hemos creado una clase ArticleAdmin que es subclase de django.contrib.admin.ModelAdmin, hemos añadido una propiedad list_display con una tupla (podría ser una lista), con las propiedades del modelo
1.10. Creacion blog
45
Tutorial Django Documentation, Publicación 1.0
Article que queremos mostrar.
Después añadimos (registramos) ArticleAdmin en admin.site.register(Article, ArticleAdmin)
donde el primer argumento es el modelo, y el segundo la subclase de ModelAdmin y el resultado podemos ver en la
siguiente captura.
Podemos crear nuestros propios métodos en el modelo para mostrar información. Por ejemplo, vamos a obtener un
string con las etiquetas con las Tags, que tiene el articulo.
# blog/models.py
# Añadimos el metodo en la clase(modelo) Article
def get_string_tags(self):
return ', '.join([tag.name for tag in self.tags.all()])
# blog/admin.py
# Modificamos el list_display
list_display = ('title', 'owner', 'create_at', 'update_at', 'get_string_tags')
Simplemente añadiendo como string get_string_tags (no podemos añadir un método que acepte parámetros),
obtenemos la devolución del método (debe devolver valores simples, strings, fechas, int, etc). Se puede ver el resultado en la siguiente captura.
Ahora, si pinchamos dentro de Primer articulo podemos ver lo siguiente.
Vamos solo a cambiar una cosa aquí (aunque se podrían hacer muchas cosas), si nos fijamos en Tags:, muestra las 2
tags que tiene el articulo.
Vamos a cambiar la forma en que muestra las etiquetas, para que seas mas fáciles de seleccionar, usando la propiedad filter_horizontal, añadimos en una tupla o lista, las propiedades del modelo ManyToManyField que
queremos que muestre dos cajas los elementos.
46
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
# blog/admin.py
class ArticleAdmin(admin.ModelAdmin):
list_display = ('title', 'owner', 'create_at', 'update_at', 'get_string_tags')
filter_horizontal = ('tags',)
Y el resultado:
Esta sección la damos por terminada, aunque esto es solo la punta del iceberg, te recomiendo la documentación para
una lista completa de lo que se puede hacer en la administración.
En las siguientes secciones, vamos a generar un sistema CRUD (aunque teniendo la administración, no es necesario,
pero para demostrar lo fácil que es crear, editar y eliminar elementos, lo haremos)
1.10.4 Preparar plantilla base_blog.html
Al igual que hicimos con las plantillas en accounts, cuando creemos accounts/base_accounts.html como
base de para el perfil de usuario, aquí haremos lo mismo, en realidad algo parecido.
Primero una idea de lo que queremos hacer
1.10. Creacion blog
47
Tutorial Django Documentation, Publicación 1.0
La barra de navegación superior es la misma que en todo el sitio, el bloque central, sera donde mostraremos la lista
de articulo o un articulo en detalle (que serán las plantillas normales con extends ’base.html’) y los bloques
laterales, donde, por ejemplo, podemos crear una lista de ultimas entradas o lista de etiquetas o cualquier cosa que se
nos ocurra.
Los bloques laterales es lo que hay de diferente en el sitio hasta el momento y añadirlos en todas las platillas seria un
curro grande, sin contar que una modificación, significaría modificar todas las paginas que tengan esos bloques.
A diferencia de accounts, que incluíamos el bloque con contenido estático con un { % include
’_nombre_plantilla.html’ %}, aquí puede ser dinámico, intento explicarme...
Hay dos maneras de pasar contenido, la primera es a través del contexto cuando renderizamos una plantilla a través
del método render(), eso significaría estar pasando los datos de ultimas entradas y lista de etiquetas y eso seria
molesto, seria incluir ese contexto en todas las vistas.
Hay una manera para solucionar esto y es con inclusion_tag. Con una inclusion tag podemos tener acceso a una
plantilla que generara los datos de manera dinámica sin importarnos el contexto pasado en las vistas.
Vamos a empezar por el principio, creamos el base_blog.html para crear la estructura de la app blog.
touch blog/templates/blog/base_blog.html
# <!-- blog/templates/blog/base_blog.html -->
{% extends 'base.html' %}
{% block content %}
<div class="row">
<div class="col-md-9">
{% block blog_content %}{% endblock blog_content %}
</div>
<div class="col-md-3">
{% include 'blog/_menus.html' %}
</div>
</div>
{% endblock content %}
48
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
Ahora, vamos a crear el index para el blog, que sera el que muestra la lista de artículos.
touch blog/templates/blog/article_list.html
# <!-- blog/templates/blog/article_list.html -->
{% extends 'blog/article_list.html' %}
{% block title %}Blog - Lista de articulos{% endblock title %}
{% block blog_content %}
<h2>Aqui mostrar la lista de articulos</h2>
{% endblock blog_content %}
Creamos un archivo de plantilla, para crear los menús laterales
touch blog/templates/blog/_menus.html
<p>Menus laterales</p>
De momento, aunque sin funcionalidad, ya esta mas o menos preparado, separando los componentes de plantilla, para
hacerlos mas fáciles de modificar y que un cambio, se refleje en varias paginas.
Vamos a crear la vista, a modo de demostración, vamos a crear una clase basada en función, pero que mas tarde
cambiaremos a una vista basada en clase.
# blog/views.py
from django.shortcuts import render
from .models import Article
def article_list_view(request):
articles = Article.objects.all()
context = {'articles': articles}
return render(request, 'blog/article_list.html', context)
Nada nuevo, obtenemos todos los artículos del modelo Article los añadimos a un diccionario para el contexto y
renderizamos la pagina blog/article_list.html
Ahora article_list.html, para mostrar los artículos típico de los blogs, titulo y el contenido, así que modificamos el
archivo, quedando de esta manera.
# <!-- blog/templates/blog/article_list.html -->
{% extends 'blog/base_blog.html' %}
{% block title %}Blog - Lista de articulos{% endblock title %}
{% block blog_content %}
{% for article in articles %}
<h2>{{ article.title }}</h2>
<p>{{ article.body }}</p>
{% endfor %}
{% endblock blog_content %}
Se puede ver que es posible iterar sobre elementos en las plantillas, una variable de contexto es articles, un objeto
django.db.models.query.QuerySet iterable, por lo que podemos recorrer sus elementos (filas de datos), es
1.10. Creacion blog
49
Tutorial Django Documentation, Publicación 1.0
igual que un for Python, lo único diferente es la forma de crearlos en las plantillas con los caracteres { % %} (tags) y
que contiene una etiqueta de cierre { % enfor %} obligatoria.
Recorre todos sus elementos y podemos acceder a las propiedades con las sintaxis de punto . como lo haríamos en
Python.
Como ya vimos en accounts, la forma de imprimir una variable es encerrando la variable entre llaves {{ }}.
Para probarlo, tenemos que añadir en el URLconf principal tutorial_django/urls.py las urls del blog, haremos un pequeño cambio, pondremos el blog como pagina principal.
# tutorial_django/urls.py
# ...
urlpatterns = [
url(r'^$', include('blog.urls')),
url(r'^blog/', include('blog.urls')),
url(r'^home/', include('home.urls')),
url(r'^accounts/', include('accounts.urls')),
url(r'^admin/', include(admin.site.urls)),
]
# ...
Ahora insertamos en blog/urls.py la url para la vista creada.
# blog/urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
# /blog/ | /
url(r'^$', views.article_list_view, name='blog.article_list'),
]
Vamos también a modificar templates/base.html para poner los links.
<!-- templates/base.html -->
<!-- buscamos la parte -->
<a class="navbar-brand" href="#">Project name</a>
<!-- y la remplazamos por -->
<a class="navbar-brand" href="{% url 'blog.article_list' %}">Tutorial Django</a>
<!-- buscamos la parte -->
<ul class="nav navbar-nav">
<li class="active"><a href="#">Home</a></li>
<li><a href="#about">About</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
<!-- y la remplazamos por -->
<ul class="nav navbar-nav">
<li><a href="{% url 'home' %}">Home</a></li>
<li><a href="#about">About</a></li>
<li><a href="#contact">Contact</a></li>
50
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
</ul>
Vemos que para crear links vasta con usar el tag { % url ’nombre_url’ %} donde nombre_url es el
name=’nombre_url’ de las funciones url() en urlpatterns = [] de los archivos urls.py, de esta manera, si cambiamos una vista a otra, no tendremos que cambiar el link html en todas las plantillas (un lujo) como se
podrá observar mas tarde cuando cambiemos la vista article_list_view.
Si probamos ahora, con el servidor en marcha, vamos a http://127.0.0.1:8000 vemos que ahora muestra los artículos
en un lado y un espacio para luego insertar los menús en el lado lateral (en este caso a la derecha).
El siguiente paso, seria crear la inclusion tag, para ello, vamos a crear un directorio templatetags dentro de la raíz
de la app blog y dentro, el archivo __init__.py para que Python lo trate como un modulo y otro blog_tags.py
donde crearemos nuestra inclusion tag.
mkdir blog/templatetags
touch blog/templatetags/{__init__.py,blog_tags.py}
# blog/templatetags/blog_tags.py
from django import template
from ..models import Article, Tag
register = template.Library()
@register.inclusion_tag('blog/_menus.html')
def get_menus_blog():
context = {
# Obtener lista completa de etiquetas
'lista_tags': Tag.objects.all(),
# Obtener las 5 ultimas entradas
'ultimos_articulos': Article.objects.order_by('-create_at')[:5]
}
return context
Importamos el modulo template y los modelos Article y Tag, creamos una instancia de Library para
mas tarde usarlo como decorador @register.inclusion_tag(’blog/_menus.html’) pasándole como
argumento la plantilla que usara para la representación de los datos devueltos por la función get_menus_blog,
dentro generamos un contexto con dos elementos, lista_tags con todas las etiquetas en la base de datos y
ultimos_articulos con los últimos 5 artículos.
Nota: order_by() obtiene todos los elementos ordenados por el argumento, en este caso create_at, por defecto,
se obtienen de manera ascendente, al incluir un guion ‘-‘, los datos obtenidos serán de manera descendente.
Ahora nos queda leer los datos y representarlos, modificamos la plantilla blog/templates/blog/_menu.html
1.10. Creacion blog
51
Tutorial Django Documentation, Publicación 1.0
teniendo en mente que tendremos acceso al contexto devuelto por get_menus_blog
<!-- blog/templates/blog/_menu.html -->
<div class="bloque-menu">
<h4>Ultimos articulos</h4>
<hr>
{% for articulo in ultimos_articulos %}
<a href="#">{{ articulo.title }}</a><br>
{% endfor %}
</div>
<div class="bloque-menu">
<h4>Lista de etiquetas</h4>
<hr>
{% for tag in lista_tags %}
<a href="#">{{ tag.name }}</a><br>
{% endfor %}
</div>
Si actualizamos la pagina, vemos que no muestra los resultados, debido a que debemos incluir en el template
blog_tags
Lo incluimos en blog/templates/blog/base_blog.html
<!-- blog/templates/blog/base_blog.html -->
<!-- Añadimos al inicio del documento -->
{% extends 'base.html' %}
{% load blog_tags %} <!-- añadir -->
<!-- Eliminamos {% include 'blog/_menus.html' %} y lo cambiamos por -->
{% get_menus_blog %
Si ahora, actualizamos la pagina, veremos lo siguiente:
Primero se ha de leer las tags { % load blog_tags %}, da igual donde se ponga, lo importante es ponerlas antes
de la llamada a la función que queremos usar, en este caso { % get_menus_blog %} con sintaxis de tag { % %}.
52
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
Vemos a editar el articulo Primer articulo desde la administración de Django, el titulo lo dejamos igual y el contenido
el campo body, le añadimos 2 lorem ipsum separándolos con un espacio (2 párrafos)
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore
et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum.</p>
Si nos fijamos en la imagen:
se puede observar, como en realidad lo muestra como un solo párrafo, eso es porque por defecto, Django, no permite
html por razones de seguridad, si miras el código generado en el navegador, veras que <p> lo traduce a &lt;p&gt;
(entidades html), pero en este caso (hemos creado nosotros la entrada, no un usuario desconocido!, nunca te fíes de
un usuario, puede comenter errores involuntarios... o no...), vamos a permitir html en las entradas. Para ello, usaremos
uno de lo muchos filtros que tiene por defecto Django (a parte nosotros podemos crear nuestros propios filtros). El
filtro que usaremos es safe <https://docs.djangoproject.com/en/1.8/ref/templates/builtins/#safe>.
Volvemos a la plantilla y añadimos el filtro
<!-- blog/templates/blog/article_list.html -->
<!-- añadimos el filtro a {{ article.body }} -->
<p>{{ article.body|safe }}</p>
Los filtros se añaden con los ¿pipers? ‘|’ al elemento que quieres que se aplique, en este caso es al article.body,
con safe no convierte el html a entidades.
Si ahora actualizamos la pagina, vemos que ahora esta mucho mejor!
Nota: Otra opción, que es la que prefiero, es usar el filtro linebreaksbr y en el articulo puedo omitir poner <p>
y </p>, lo que hace es sustituir el final de linea \n por <br>.
Yo utilizo markdown para crear el contenido, puedes ver un articulo que cree para añadir markdown a Django.
Ahora es el momento para ver las CBV, pero lo dejamos para la siguiente sección!
1.10. Creacion blog
53
Tutorial Django Documentation, Publicación 1.0
1.10.5 Clases basada en Vista ListView
Las clases basadas en vista CBV aporta lo que aporta la programación orientada a objetos, herencia, mixins, etc.
A parte de clases predefinidas que con solo 3 lineas de código es capaz de mostrar los datos de un modelo, y dar
funcionalidad para actualizarlos.
Un mixin no es mas que una clase que ofrece una funcionalidad común a otras clases, pero que no pertenece a un tipo
concreto.
A diferencia de las funciones FBV, una clase tiene métodos para las respuestas, por ejemplo, tiene un método get()
y otro post() y dependiendo el método de la respuesta, usa uno u otro.
Las clases al tener herencia, podemos crear clases personalizadas con la misma funcionalidad (o similar) o crear
nuestras CBV, pero por defecto tiene clases para las cosas mas comunes, como un sistema CRUD con, ListView,
UpdateView, CreateView, DeleteView e incluso otras como FormView para formularios, entre otras.
ListView
Creo que lo mejor es mostrarlo, vamos a editar la única función de vista que tenemos en blog/views.py y la
vamos a cambiar por una CBV.
# blog/views.py
from django.views import generic
from .models import Article
class ArticleListView(generic.ListView):
template_model = 'blog/article_list.html'
model = Article
context_object_name = 'articles'
En nuestra clase se ha añadido tres propiedades, pero por defecto solo es obligatorio uno, model = MiModel que
es el modelo sobre el que va a trabajar, pero para ser explicito se han añadido template_model que es la plantilla
a renderizar y context_objects_name que sera el nombre de contexto con los elementos importados de la base
de datos, en este caso un lista de objectos Article.
El siguiente paso es editar blog/urls.py, eliminando la url() que contiene y cambiándola por
# blog/urls.py
from django.conf.urls import url
from . import view
urlpatterns = [
# /blog/ | /
url(r'^$', views.ArticleListView.as_view(), name='blog.article_list'),
]
regex es lo mismo, al igual que name, lo único que cambia es la llamada a la vista
views.ArticleListView.as_view(), donde views es el modulo con las vistas, ArticleListView es
la clase de la vista, que a su vez, es subclase de django.views.generic.ListView, todas las vistas heredan
de django.views.generic.View, que es la CBV mas simple de todas y provee de un método (entre otros),
as_view() que devuelve un objeto HttpResponse.
54
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
Si probamos el sitio y vamos a la pagina principal, veremos que muestra exactamente lo mismo hacia con la función
article_list_view.
Posiblemente, en cuanto a lineas de código, pueda parecer que no valga la pena, pero este ejemplo ha sido simple y si
conoces la programación orientada a objetos, conocerás sus ventajas.
Paginación
Para continuar, vamos primero a añadir 10 artículos, así que, ves a la administración y crea 9 artículos mas, ponle los
títulos que quieras y usar un par de lorem ipsum (con etiquetas <p></p> en cada párrafo) en cada articulo (o los que
quieras), en la parte de tags, ponle de 1 a 3, a tu gusto!.
Los 10 artículos creados desde la administrador.
Como se puede ver, la pagina donde muestra los articulo, muestra los 10 artículos, si hubieran 50, mostraría los 50,
algo que pondría mas lenta las cargas de la pagina y gasto de ancho de banda.
Vamos a añadir una propiedad a la vista creada anteriormente, paginate_by, que mandara como contexto a la
plantilla información sobre paginación que usaremos mas tarde.
# blog/views.py
class ArticleListView(generic.ListView):
1.10. Creacion blog
55
Tutorial Django Documentation, Publicación 1.0
# ...
paginate_by = 3
Ahora editamos la plantilla
<!-- blog/templates/blog/article_list.html -->
<!-- Añadir debajo del {% endfor %} -->
<nav>
{% if is_paginated %}
<ul class="pagination">
{% if page_obj.has_previous %}
<li><a href="?page={{ page_obj.previous_page_number }}">&laquo;</a></li>
{% endif %}
{% for i in paginator.page_range %}
<li {% if page_obj.number == i %} class="active" {% endif %}>
<a href="?page={{i}}">{{ i }}</a>
</li>
{% endfor %}
{% if page_obj.has_next %}
<li><a href="?page={{ page_obj.next_page_number }}">&raquo;</a></li>
{% endif %}
</ul>
{% endif %}
</nav>
La vista pasa una variable de contexto is_paginated, si devuelve True, entonces es que hay paginación.
También pasa un objeto page_obj con métodos para obtener datos como si tiene mas paginas respecto a
la actual page_obj.has_next o si tiene mas paginas previas a la actual page_obj.has_previous.
page_obj.number es la pagina actual, por lo que se puede comparar con la iteración de
paginator.page_range que es el rango de paginas disponibles y así cambiar el CSS para marcar en la
pagina que se encuentra..
Y podemos ver, que ha generado un Query string en la URI, en este caso ?page=2
Con muy pocas lineas de código, hemos generado mucho, eso es innegable :)
También podemos observar que los artículos los muestra por orden de creación (por orden de id), vamos a cambiar el
orden.
# blog/views.py
class ArticleListView(generic.ListView):
56
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
# ...
ordering = '-create_at'
Ahora, el orden es inverso al que mostraba antes los artículos :), tan solo con la propiedad ordering y como valor
una propiedad del modelo.
Información del articulo
Ahora, vamos añadir debajo del titulo del articulo, la fecha y autor. Toda esa información ya tenemos acceso desde la
plantilla, así que vamos añadir el siguiente código.
<!-- blog/templates/blog/article_list.html -->
<!-- Modificar el interior del {% for article in articles %} -->
{% for article in articles %}
<h2>{{ article.title }}</h2>
<div class="article-info">
<small>
<strong>Por: </strong>{{ article.owner.username }}
<strong>Hace: </strong>{{ article.create_at|timesince }}
<strong>El: </strong>{{ article.create_at|date:'d F Y' }}
</small><hr>
</div>
<p>{{ article.body|safe }}</p>
{% endfor %}
Lo hago simple y no genero CSS para centrarnos en lo importante.
En primer lugar, se puede ver lo facil que es acceder a los modelos relaciones con article.owner.username‘, la propiedad article.owner devuelve django.contrib.auth.models.User, que tiene acceso a username,
simplemente lo mostramos.
Por motivos de demostración, he añadido las 2 siguientes lineas, que maneja la fecha y hora de la propiedad
create_at con filtros, el primer filtro timesince es una función que nos devuelve el tiempo que ha pasado
desde la publicación hasta la fecha y hora actual. Con el filtro date se le pasa un argumento, que es un string y le
decimos como representar la fecha y hora, puedes ver una lista de caracteres en la documentación de PHP
Por ultimo, vamos a añadir, el footer del articulo con las etiquetas que tiene el articulo:
1.10. Creacion blog
57
Tutorial Django Documentation, Publicación 1.0
<!-- blog/templates/blog/article_list.html -->
<!-- Modificar el interior del {% for article in articles %} -->
{% for article in articles %}
<h2>{{ article.title }}</h2>
<div class="article-info">
<small>
<strong>Por: </strong>{{ article.owner.username }}
<strong>Hace: </strong>{{ article.create_at|timesince }}
<strong>El: </strong>{{ article.create_at|date:'d F Y' }}
</small><hr>
</div>
<p>{{ article.body|safe }}</p>
<div class="article-footer">
<strong>Etiquetas: </strong>{{ article.get_string_tags }}
</div>
{% endfor %}
Se puede observar, que es posible llamar a funciones del modelo (siempre que no requieran de parámetros) y el
resultado es el siguiente:
En la siguiente sección, se añadirá una plantilla que muestra un único articulo seleccionado por el usuario (detalles)
con sus comentarios.
1.10.6 Detalles articulo DetailView
Ahora, vamos a añadir la pagina de detalles del articulo, donde mostrar el articulo en una pagina separada.
Los pasos, los de siempre, la vista, la plantilla y crear la url en URLconf.
La vista, al igual que hicimos con ArticleListView que es una subclase de ListView, ahora vamos hacer
prácticamente lo mismo, ArticleDetailView que sera una subclase de DetailView.
# blog/views.py
class ArticleDetailView(generic.DetailView):
template_model = 'blog/article_detail.html'
model = Article
context_object_name = 'article'
El template
58
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
<!-- blog/templates/blog/article_detail.html -->
{% extends 'blog/base_blog.html' %}
{% block title %}{{ article.title }}{% endblock title %}
{% block blog_content %}
<h2>{{ article.title }}</h2>
<div class="article-info">
<small>
<strong>Por: </strong>{{ article.owner.username }}
<strong>Hace: </strong>{{ article.create_at|timesince }}
<strong>El: </strong>{{ article.create_at|date:'d F Y' }}
</small><hr>
</div>
<p>{{ article.body|safe }}</p>
<div class="article-footer">
<strong>Etiquetas: </strong>{{ article.get_string_tags }}
</div>
<div class="comentarios">
<!-- Aqui los comentarios -->
</div>
{% endblock blog_content %}
Se puede ver que es prácticamente igual que article_list.html omitiendo la paginación, dentro de un rato,
volveremos con este tema (para no repetirnos) y lo modificaremos.
en URLconf añadimos la siguiente url
# blog/urls.py
urlpatterns = [
# ...
# /blog/detalles/:<string>slug/
url(r'^detalle/(?P<slug>[-\w]+)/$', views.ArticleDetailView.as_view(), name='blog.article_detail'
]
Por ultimo, tenemos que añadir un link en la lista de artículos para acceder a los detalles del articulo.
<!-- blog/templates/blog/article_list.html
<!-- Cambiar <h2>{{ article.title }}</h2> por -->
<h2><a href="{% url 'blog.article_detail' article.slug %}">{{ article.title }}</a></h2>
Como se puede observar, en { % url ’blog.article_detail’ article.slug %}, le pasamos el slug como parámetro, que lo requiere en la URLconf (?P<slug>[-\w]+). Por defecto, la vista ArticleDetailView,
para obtener el item a obtener del modelo exige la id o el slug (se puede cambiar con slug_url_kwarg,
slug_field y pk_url_kwarg)
Si vamos a la pagina, podemos ver que la lista de artículos, el titulo es un link que nos mandara al detalle del articulo.
Si recordamos, la platilla se repite, prácticamente es igual a la plantilla article_list.html, la única diferencia
es el link para ver el articulo en detalles.
Primero vamos a crear una nueva plantilla, _article.html y añadimos.
<!-- blog/templates/blog/_article.html -->
1.10. Creacion blog
59
Tutorial Django Documentation, Publicación 1.0
{% if articles %}
<h2><a href="{% url 'blog.article_detail' article.slug %}">{{ article.title }}</a></h2>
{% else %}
<h2>{{ article.title }}</h2>
{% endif %}
<div class="article-info">
<small>
<strong>Por: </strong>{{ article.owner.username }}
<strong>Hace: </strong>{{ article.create_at|timesince }}
<strong>El: </strong>{{ article.create_at|date:'d F Y' }}
</small><hr>
</div>
<p>{{ article.body|safe }}</p>
<div class="article-footer">
<strong>Etiquetas: </strong>{{ article.get_string_tags }}
</div>
Y modificamos article_detail.html y article_list.html
<!-- blog/templates/blog/article_list.html -->
<!-- la parte del {% for article in articles %} -->
{% for article in articles %}
{% include 'blog/_article.html' %}
{% endfor %}
<!-- blog/templates/blog/article_detail.html -->
{% extends 'blog/base_blog.html' %}
{% block title %}{{ article.title }}{% endblock title %}
{% block blog_content %}
{% include 'blog/_article.html' %}
<div class="comentarios">
<!-- Aquí los comentarios -->
</div>
{% endblock blog_content %}
¿Como funciona?, cuando añadimos { % include ’blog/_article.html’ %} importamos parte de un documento html en el mismo punto donde lo incluimos, por lo tanto, la plantilla incluida, tiene acceso al mismo
contexto, por lo tanto en article_list.html tiene una variable de contexto articles mientras que en
article_detail, no. Ambos contextos, tienen la variable article que es el objeto de un modelo Article,
en el caso de article_list.html se genera dinamicamente dentro de for por lo que incluira tantas plantillas
_article.html como artículos muestre y el objeto Article varia en cada loop
De esta manera, podemos tener una plantilla y un cambio se reflejara en ambas plantillas article_list.html y
article_detail.
Puedes observar, que he creado un comentario <!-- Aquí los comentarios --> en article_detail,
que seria para añadir el sistema Disqus, pero no lo voy a incluir en el tutorial, te recomiendo un articulo que cree en
mi blog www.snicoper.com
En la siguiente sección, vamos a crear el típico leer mas... y así crearemos nuestro primer filtro.
60
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
1.10.7 Creación de un filtro
Vamos a crear un filtro personalizado, vamos a crear el típico leer mas..., que sera un link para leer el articulo entero.
En la lista de artículos, los artículos se cortaran donde pongamos una marca <!-- read_more -->. El filtro comprobara si tiene un <!-- read_more -->, si lo tiene, cambiara ese texto por un link hacia detalles del articulo.
Primero, vamos añadir un método al modelo Article, get_absolute_url
# blog/models.py
# Añadir al inicio
from django.core.urlresolvers import reverse
class Article(models.Model):
# ...
def get_absolute_url(self):
return reverse('blog.article_detail', kwargs={'slug': self.slug})
Primero importamos del modulo urlresolvers la función reverse, que generara una URI en base al archivo
URLconf, el primer parámetro es el name de url() y el segundo parámetro es un diccionario kwargs con los
argumentos de regex (?P<slug>[-\w]+), en este caso requiere un slug, por lo tanto se le pasa el nombre (el
parámetro de regex) y el valor, en este caso self.slug.
La función, como veremos, no es una invención o convención nuestra, pertenece a django.db.models.Model y
es utilizada a menudo como iremos viendo.
Los filtros, se crean en el mismo archivo que las inclusion_tag, es decir, en el archivo
nombre_app/templatetags/app_tags.py, en este caso en blog/templatetags/blog_tags.py.
# blog/templatetags/blog_tags.py
@register.filter
def read_more(article):
pattern = '<!-- read_more -->'
body = article.body
if pattern in body:
pos = body.index(pattern)
replace = '<a href="{}" class="small">Sigue leyendo...</a>'.format(
article.get_absolute_url())
body_return = body.replace(pattern, replace)
return body_return[:pos + len(replace)]
return body
Y modificamos la plantilla _article.html
<!-- blog/templates/blog/_article.html -->
<!-- cambiamos <p>{{ article.body|safe }}</p> por -->
{% if articles %}
<p>{{ article|read_more|safe }}</p>
{% else %}
<p>{{ article.body|safe }}</p>
{% endif %}
¿Como funciona?, el filtro read_more es una simple función Python que requiere de un parámetro article, espera
un objeto blog.models.Article.
La función (o filtro), lo único que hace es buscar un substring <!-- read_more -->, si lo encuentra sustitu-
1.10. Creacion blog
61
Tutorial Django Documentation, Publicación 1.0
ye <!-- read_more --> por un link. El link es generado gracias al método get_absolute_url del objeto
Article.
La plantilla, usamos la misma técnica anterior, si existe un contexto articles, significa que estamos en la plantilla
article_list.html y llamamos al filtro de la siguiente manera {{ article|read_more|safe }}.
read_more, como hemos comentado, requiere un parámetro, un objeto Article, los filtros se aplican a la parte
izquierda del filtro se_aplica|filtro. En este caso el se_aplica es pasado como primer argumento (en este caso,
un objeto Article).
Ademas, podemos observar que los filtros pueden a su vez tener otros filtros, a la devolución de
article|read_more que ahora es un string, le aplicamos el filtro safe anteriormente comentado.
Si estamos llamando a la plantilla desde article_detail.html, la variable de contexto articles no existe,
por lo que se ejecutara {{ article.body|safe }}.
Aquí podemos ver el resultado
En la siguiente sección, vamos a ver como crear artículos.
1.10.8 Creación de artículos CreateView
A modo de demostración, vamos a crear en esta sección y las siguientes un sistema para añadir, editar y eliminar
artículos. Con la administración Django, en este caso no seria necesario (la administración también controla permisos).
Vamos a empezar con la creación de artículos y comprobaremos si el usuario tiene permisos para añadir nuevos
artículos, si no tiene permisos, crearemos una plantilla informando que no tiene acceso.
Va a ser rutinario, añadir la vista, crear la plantilla y añadir el url, así que por partes...
Creamos la vista, en esta ocasión, creamos una clase que deriva de CreateView
62
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
# blog/views.py
class ArticleCreateView(generic.CreateView):
template_model = 'blog/article_form.html'
model = Article
En este ocasión no añadimos la propiedad context_object_name que por defecto es form en una clase
CreateView.
La plantilla es article_form.html, ya que la reutilizaremos con la vista de edición de artículos. Por este motivo,
luego añadiremos un método mas a ArticleCreateView para ver como podemos añadir contexto personalizado.
<!-- blog/templates/blog/article_form.html -->
{% extends 'blog/base_blog.html' %}
{% block title %}{{ title }}{% endblock title %}
{% block blog_content %}
<h2 class="page-header">{{ title }}</h2>
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">{{ nombre_btn }}</button>
</form>
{% endblock blog_content %}
Se puede observar variables de contexto {{ title }} que esta en 2 partes y {{ nombre_btn }}, dentro de un
rato volveremos a esto.
Vamos con al URLconf y añadir la url
# blog/urls.py
urlpatterns = [
# ...
# /blog/crear/
url(r'^crear/$', views.ArticleCreateView.as_view(), name='blog.crear'),
]
Y por ultimo, vamos a crear un botón en la barra de navegación superior, si tiene permisos para crear un articulo,
saldrá el link.
<!-- templates/base.html -->
<!-- debajo de <li><a href="#contact">Contact</a></li> añadimos -->
{% if perms.article.can_add %}
<li><a href="#contact">Crear articulo</a></li>
{% endif %}
perms tambien esta disponible en las plantillas, y la forma de saber si un usuario tiene permisos es:
perms.nombre_modelo.permiso, donde nombre_modelo es el nombre del modelo en minúsculas y
permiso es uno de los siguientes:
can_add: Puede añadir
can_change: Puede cambiar (editar)
1.10. Creacion blog
63
Tutorial Django Documentation, Publicación 1.0
can_delete: Puede eliminar
Si probamos y vamos primero a http://127.0.0.1:8000/ <http://127.0.0.1:8000/>, si estamos logueados, veremos un
link para crear un articulo.
Y si estamos deslogueados, no mostrara el link
Si pinchamos encima de Crear articulo veremos que nos mostrara un error! http://127.0.0.1:8000/blog/crear/ el error
es por que Django 1.8 obliga añadir la propiedad fields (en este caso dentro de la vista, pero podría ser también en
un ModelForm), con los campos que vamos a mostrar.
Así que, vamos a añadir en la vista la siguiente linea:
# blog/views.py
class ArticleCreateView(generic.CreateView):
template_model = 'blog/article_form.html'
model = Article
fields = ('title', 'body')
Si actualizamos la pagina, veremos que nos muestra un formulario con los campos que queríamos que mostrase.
Podemos ver varias cosas, primero lo fácil que ha sido crear un formulario a partir de un modelo!, con la clase
64
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
CreateView, segundo, donde esta el titulo de la pagina?, y ¿un botón sin texto? :)
Vamos añadir contexto en ArticleCreateView, para ello, vamos a usar un método get_context_data()
# blog/views.py
class ArticleCreateView(generic.CreateView):
# ...
def get_context_data(self, **kwargs):
# Obtenemos el contexto de la clase base
context = super().get_context_data(**kwargs)
# añadimos nuevas variables de contexto al diccionario
context['title'] = 'Crear articulo'
context['nombre_btn'] = 'Crear'
# devolvemos el contexto
return context
y podemos ver los resultados
De esta manera, podemos mas tarde reutilizar la plantilla cuando hagamos la de edición.
¿Y el slug y el propietario (owner)? bueno, el slug, si recuerdas, se genera automáticamente cuando se crea/guarda el
objeto y el owner, lo recuperaremos mas tarde del usuario que esta logueado (que actualmente tenemos un problema,
por que cualquiera puede crear un articulo, luego lo arreglamos)
1.10. Creacion blog
65
Tutorial Django Documentation, Publicación 1.0
Otra cosa que podemos apreciar, es el diseño de los elementos del formulario, no tienen el diseño de Bootstrap! como
antes :(, a parte los campos tienen un nombre no muy útil para los usuarios.
Empezaremos por lo nombres de los campos (labels), en principio, añade el nombre del campo del modelo, en este
caso de blog.models.Article, pero es posible cambiar el nombre en el mismo modelo.
# blog/models.py
class Article(models.Model):
# Remplazamos
title = models.CharField(max_length=100, unique=True)
# por
title = models.CharField(verbose_name='titulo', max_length=100, unique=True)
El del body, lo dejamos para el formulario, para demostrar otra posibilidad de hacerlo.
En accounts creamos los formularios con subclases de django.forms.Form, pero hay otra manera cuando se
crean formularios que son parte de un modelo (como es el caso), vamos a crear un formulario ArticleCreateForm
que es subclase de django.forms.ModelForm.
touch blog/forms.py
# blog/forms.py
from django import forms
from .models import Article
class ArticleCreateForm(forms.ModelForm):
class Meta:
model = Article
fields = ('title', 'body')
labels = {
'body': 'Texto'
}
widgets = {
'title': forms.TextInput(attrs={'class': 'form-control'}),
'body': forms.Textarea(attrs={'class': 'form-control'})
}
Como se puede apreciar, las opciones de formulario están en la clase Meta, donde el model es el modelo, en este
caso Article, fields una tupla o lista con los campos a mostrar, labels, al igual que en el modelo usábamos
verbose_name, en los formularios es labels, un diccionario con la clave (el nombre del campo/elemento) y el
valor (el texto a mostrar). Por ultimo vemos los widgets, también un diccionario donde la clave es el nombre del
campo y el valor es el tipo de campo que queremos con un argumento attrs con los atributos que insertara en el
elemento del formulario.
Si actualizamos la pagina, nos dará un error diciendo que no podemos tener fields en 2 sitios, así que quitamos
fields = (’title’, ’body’) en la vista ArticleCreateView.
Vamos añadir un método en la vista, el método se ejecuta después de instanciar la clase, para determinar el tipo
method de la solicitud.
# blog/views.py
# Añadimos al inicio
from django.shortcuts import redirect
66
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
from django.conf import settings
class ArticleCreateView(generic.CreateView):
# ...
def dispatch(self, request, *args, **kwargs):
# Nota: Dejo para el lector como ejercicio que cambie este
# comportamiento, actualmente, solo comprueba si es usuario
# tiene permisos para añadir un articulo, si no lo tiene, lo
# redirecciona a login, pero, ¿y si esta logueado y simplemente
# no tiene permisos?
# El compportamiento logico seria:
#
¿Estas logueado?
#
¿Tiene permisos?
#
Continuar con la ejecución
#
¿No tienes permisos?
#
Redireccionar a una pagina informando que no tiene permisos
#
para añadir un articulo.
#
Si no estas logueado:
#
Redireccionar a pagina de login
if not request.user.has_perms('blog.add_article'):
return redirect(settings.LOGIN_URL)
return super().dispatch(request, *args, **kwargs)
has_perms(’blog.add_article’) a diferencia de las plantillas, se le pasa un argumento como string con el
nombre_app.tipopermiso_nombremodelo donde tipopermiso es add_, change_ y delete_ (por defecto).
Si vamos a la web, estando logueados y le damos al botón de Crear, vemos el formulario se valida, y si no pasa la
validación, recarga otra vez el formulario indicando los errores.
La validación va en este orden, ModelForm sobrescribe a Model (otra cosa es que luego nos mande un error el
modelo por no cumplir los requisitos).
1.10. Creacion blog
67
Tutorial Django Documentation, Publicación 1.0
Vamos a crear un articulo, ponemos un Titulo y ponemos algo en Text y le damos al botón Crear
OPS!, se nos ha olvidado asignar a owner al articulo!
CreateView tiene dos métodos form_valid(self, form) y form_invalid(self, form)‘‘, que podemos tomar
decisiones si el formulario se ha validado con éxito o no.
En este caso, queremos hacer algo cuando sea valido, asignar el usuario actual a owner.
68
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
# blog/views.py
class ArticleCreateView(generic.CreateView):
# ...
def form_valid(self, form):
form.instance.owner = self.request.user
return super().form_valid(form)
form.instance es un objeto del modelo (una propiedad de FormModel), pero aun no se ha guardado. (Puede
parece un lío), después de ejecutar CreateView.form_valid, se ejecutara FormModel.save() que a su vez,
ejecutara Model.save()
Por ultimo, cuando todo lo anterior termina bien, ArticleCreateView tiene una propiedad success_url y un
método get_success_url(), si no tiene datos, ni el método ni la propiedad (es la url de redirección en caso de
éxito), intentara obtener Model.get_absolute_url(), si no se tuviera declarado al menos 1 de las 3 opciones,
lanzara un error, por que no sabe donde queremos ir después de crear el articulo. En este caso, tenemos declarada en
el modelo get_absolute_url(), sera donde redireccionara cuando todo haya salido bien, que sera a detalles del
articulo.
En la siguiente sección, vamos a poner la opción de editar el articulo
1.10.9 Editar articulo UpdateView
Con editar ya si que no vamos a ver nada nuevo, prácticamente es lo mismo que la creación, lo único que cambia es
(internamente), es que el formulario, muestra los datos de un articulo a modificar.
Tampoco necesitamos saber que usuario lo ha creado (no lo cambiaremos), así que pondré los pasos e iré mas rápido
y pondré las capturas para una vista rápida.
La vista
# blog/views.py
class ArticleUpdateView(generic.UpdateView):
template_model = 'blog/article_form.html'
model = Article
form_class = ArticleCreateForm
def dispatch(self, request, *args, **kwargs):
# Al igual que con ArticleCreateView, dejo al lector
# que cambie el comportamiento de este método para saber
# si esta logueado y tiene permisos.
# Ver el comentario de ArticleCreateView en el método
# dispatch
if not request.user.has_perms('blog.change_article'):
return redirect(settings.LOGIN_URL)
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
# Obtenemos el contexto de la clase base
context = super().get_context_data(**kwargs)
# añadimos nuevas variables de contexto al diccionario
context['title'] = 'Editar articulo'
context['nombre_btn'] = 'Editar'
1.10. Creacion blog
69
Tutorial Django Documentation, Publicación 1.0
# devolvemos el contexto
return context
dispatch cambia el tipo de permisos, ahora comprueba que el usuario pueda editar y en el contexto
get_context_data, cambia el title y nombre_btn.
La plantilla es la misma que la de crear articulo, así que pasamos a la URLconf
# blog/urls.py
# Añadir
urlspatterns = [
# ...
url(r'^editar/(?P<slug>[-\w]+)/$', views.ArticleUpdateView.as_view(), name='blog.editar'),
]
Vamos a añadir un link en los detalles del articulo y probar si puede editar, que muestre el botón para editar.
<!-- blog/templates/blog/article_detail.html -->
<!-- añadimos debajo de {% include 'blog/_article.html' %} -->
{% if perms.article.can_change %}
<a href="{% url 'blog.editar' article.slug %}" class="btn btn-primary">Editar</a>
{% endif %}
Y ya esta!! nada mas :)
Como ejercicio para el lector: Te propongo que cuando creas y editas un articulo, si el formulario es valido (si
se ha creado o editado un articulo), muestre un mensaje al usuario como lo hace la función logout_view de
accounts/views.py cuando te deslogueas.
En la siguiente sección ya terminamos el sistema CRUD del blog y prácticamente terminamos este pequeño tutorial.
1.10.10 Eliminar articulo DeleteView
Eliminar un articulo, también es muy fácil:
La vista:
# blog/views.py
# Añadir al inicio
from django.core.urlresolvers import reverse_lazy
class ArticleDeleteView(generic.DeleteView):
template_name = 'blog/confirmar_eliminacion.html'
success_url = reverse_lazy('blog.article_list')
model = Article
def dispatch(self, request, *args, **kwargs):
if not request.user.has_perms('blog.delete_article'):
return redirect(settings.LOGIN_URL)
return super().dispatch(request, *args, **kwargs)
Aquí se puede ver que hemos usado success_url, por que si lo dejamos sin poner como anteriormente, cuando lea
del modelo get_absolute_url, dará un error, por que el articulo no existe (lo acabamos de eliminar).
70
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
reverse_lazy es igual que reverse que ya hemos usado varias veces, la diferencia, es que reverse_lazy es
útil usarla cuando URLconf aun no se ha cargado.
URLconf:
# blog/urls.py
urlpatterns = [
url(r'^eliminar/(?P<slug>[-\w]+)/$', views.ArticleDeleteView.as_view(), name='blog.eliminar'),
]
La plantila:
<!-- blog/templates/blog/confirmar_eliminacion.html -->
{% extends 'blog/base_blog.html' %}
{% block title %}Confirmar la eliminación{% endblock title %}
{% block blog_content %}
<h2 class="page-header">Confirmacion para eliminar {{ article.title }}</h2>
<form method="post" action="">
{% csrf_token %}
<p>¿Seguro que quieres eliminar el articulo {{ article.title }}?</p>
<button type="submit" class="btn btn-danger">Si, eliminar</button>
<a href="{% url 'blog.article_detail' article.slug %} class="btn btn-success">Cancelar</a>
</form>
{% endblock blog_content %}
Y añadimos un enlace en detalles del articulo.
<!-- blog/templates/blog/article_detail.html -->
<!-- añadir antes de <div class="comentarios"> -->
{% if perms.article.can_delete %}
<a href="{% url 'blog.eliminar' article.slug %}" class="btn btn-danger">Eliminar</a>
{% endif %}
Ta hemos terminado el blog, un blog básico, pero funcional, espero que te haya servido para aprender los fundamentos
de temas como las CBV y los ModelForm.
Ahora, nos queda la pagina about y contact que veremos en las próximas secciones.
1.11 Pagina About
About (Sobre mi o Acerca de), la vamos a crear con la vista mas simple, View para mostrarla, tanto about como
contact, crearemos las vistas en la app home.
Vamos a empezar como siempre, si, la vista...
# home/views.py
# Añadimos al inicio
from django.views import generic
# Creamos la vista
class AboutView(generic.View):
1.11. Pagina About
71
Tutorial Django Documentation, Publicación 1.0
def get(self, request, *args, **kwargs):
return render(request, 'home/about.html')
La clase View es la clase de vista mas simple, la que menos propiedades y métodos tiene, 2 de los métodos que podemos usar es get() y post() que ejecutara uno u otro dependiendo del method. En este caso, get() simplemente
renderiza una pagina html (una simple función lo podría haber hecho, pero quería mostrar la clase View).
Ahora ponemos la url en URLconf
# home/urls.py
urlpatterns = [
# ...
url(regex=r'^about/$', view=views.AboutView.as_view(), name='about'),
]
la plantilla
<!-- blog/templates/blog/about.html -->
{% extends 'base.html' %}
{% block title %}About{% endblock title %}
{% block content %}
<h2 class="page-header">About</h2>
{% endblock content %}
Y añadimos en base.html el link
<!-- templates/base.html -->
<!-- buscar -->
<li><a href="#about">About</a></li>
<!-- cambiar por -->
<li><a href="{% url 'about' %}">About</a></li>
El contenido de about es cosa de cada uno :)
Bueno, pues ya solo queda contact donde daremos la posibilidad a los usuarios que puedan contactar con nosotros,
eso sera en la próxima sección.
1.12 Pagina Contacto
Django proporciona una envoltura de smtplib para que el envió de correos electrónicos sea una tarea muy sencilla.
Para empezar, es necesario añadir variables en el archivo de configuración tutorial_django/settings.py
Al igual que about, lo haremos en la app home. Añadimos al final:
# tutorial_django/settings.py
EMAIL_USE_TLS = True
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_HOST_USER = '[email protected]'
EMAIL_HOST_PASSWORD = 'contraseña'
72
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
Esta es la configuración típica para usar Gmail como SMTP, modifica los datos de tu cuenta Gmail.
Vamos a empezar con los formularios. La idea es la siguiente, si un usuario esta logueado (tenemos su email de
contacto), no mostrara el campo de email, en caso contrario, le pediremos su email por si tenemos que contactar con
el.
touch home/forms.py
from django import forms
class ContactUsuarioAnonimoForm(forms.Form):
email = forms.EmailField(
label='Email',
widget=forms.EmailInput(attrs={'class': 'form-control'})
)
subject = forms.CharField(
label='Asunto',
widget=forms.TextInput(attrs={'class': 'form-control'})
)
body = forms.CharField(
label='Mensaje',
widget=forms.Textarea(attrs={'class': 'form-control'})
)
class ContactUsuarioLoginForm(forms.Form):
subject = forms.CharField(
label='Asunto',
widget=forms.TextInput(attrs={'class': 'form-control'})
)
body = forms.CharField(
label='Mensaje',
widget=forms.Textarea(attrs={'class': 'form-control'})
)
Creamos 2 formularios, uno para el usuario logueado y otro para el usuario anónimo.
La vista, en esta ocasión, vamos a usar otra CBV que es un FormView
# home/views.py
# Añadimos en el inicio
from django.contrib import messages
from django.core.urlresolvers import reverse_lazy
from django.core.mail import send_mail
from .forms import ContactUsuarioAnonimoForm, ContactUsuarioLoginForm
# Creamos una función para el envió de email
# (es muy simple, solo para demostrar como enviar un email)
def send_email_contact(email_usuario, subject, body):
body = '{} ha enviado un email de contacto\n\n{}\n\n{}'.format(email_usuario, subject, body)
send_mail(
subject='Nuevo email de contacto',
message=body,
from_email='[email protected]',
recipient_list=['[email protected]']
)
1.12. Pagina Contacto
73
Tutorial Django Documentation, Publicación 1.0
class ContactView(generic.FormView):
template_name = 'home/contact.html'
success_url = reverse_lazy('home')
def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated():
self.form_class = ContactUsuarioLoginForm
else:
self.form_class = ContactUsuarioAnonimoForm
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
subject = form.cleaned_data.get('subject')
body = form.cleaned_data.get('body')
if self.request.user.is_authenticated():
email_usuario = self.request.user.email
send_email_contact(email_usuario, subject, body)
else:
email_usuario = form.cleaned_data.get('email')
send_email_contact(email_usuario, subject, body)
messages.success(self.request, 'Email enviado con exito')
return super().form_valid(form)
Por defecto, un FormView requiere una propiedad form_class, en este caso, como no sabemos que formulario
vamos a usar, en el método dispatch (es el método encargado en saber el method del tipo de respuesta) comprobamos si el usuario esta logueado o no y dependiendo de si lo esta o no, asignaremos a form_class un formulario u
otro.
Después ya la clase ContactView se encarga de renderizar la plantilla, que la lee de template_name.
Ahora, tenemos que generar el método form_valid y decirle que
django.core.mail.send_mail, una función que acepta los siguientes parámetros
envié
el
email
con
send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_pas
Volvemos hacer la comprobación de si es un usuario autenticado, si lo esta, recuperamos el email con
self.request.user.email y si no lo es, entonces el usuario ha introducido el email en el formulario y lo
recuperamos con form.cleaned_data.get(’email’).
Después pasamos los datos a la función send_email_contact, que prepara el email (de una manera muy simple, solo para mostrar como funciona) y lo envía de una manera muy sencilla (recuerda configurar las variables de
configuración en tutorial_django/settings.py)
Pues yo creo que ya tenemos un blog (básico) creado, con la funcionalidad básica de cualquier formulario, y por ahora
ya lo dejamos, espero que lo hayas disfrutado y te haya servido de algo el tutorial.
Ya solo unas palabras de despedida en la siguiente sección.
1.13 Notas finales
Espero que te haya sido útil y hayas aprendido al menos los conceptos básicos de Django, que a partir de ahora te
cueste un poco menos crear algo con Django.
Seguiré mejorando y actualizando este pequeño tutorial, al menos que sea compatible con la ultima versión de Django.
Si quieres seguir mis trabajos, opiniones u ojear mis apuntes, visita mi blog personal en http://www.snicoper.com
74
Capítulo 1. Tabla de contenidos:
Tutorial Django Documentation, Publicación 1.0
Si has llegado hasta aquí, por favor, mándame tus impresiones desde http://www.snicoper.com/contact/, cualquier cosa,
desde un error, que estoy equivocado o simplemente las gracias (ayuda después de las horas que le he dedicado).
Y si crees que lo ha merecido la pena, por favor, considera en hacer una pequeña donación
1.13. Notas finales
75