django Posts

Creando un blog con django – parte 4

Después de una eternidad sin escribir y casi abandonar el tema de django, hoy por fin retomo el asunto. Hasta el momento tenemos los post clasificado por tags, pero un blog sin comentarios no es nada, así que hoy veremos como crear el formulario de comentarios.

En django existen dos formas de crear un formulario, una es crear cada campo manualmente y la otra es crearlos automáticamente basados en el modelo de datos.

Ya que casi siempre los formulario se usan para agregar/editar una tabla en una BD, y los campos del formulario reflejan los campos de la tabla, seria un desperdicio de tiempo crearlos manualmente.

Dejemos de hablar y empecemos a escribir código, editemos el archivo forms.py en el directorio de nuestra aplicación:

from django.forms import ModelForm #1
from blog.models import Comment #2

class CommentForm(ModelForm): #3
"""Formulario de comentarios"""
class Meta: #4
model = Comment #5
exclude = ("post","deleted") #6

Hora de explicar lo que hemos hecho:

  1. Importamos ModelForm, la base para los formularios basados en modelos de datos.
  2. Importamos el modelo para el que se creará el formulario
  3. Creamos una subclase de ModelForm.
  4. En la clase Meta especificamos como crear el formulario
  5. model: modelo en el que se basa el formulario.
  6. exclude: campos a excluir, en este ejemplo post lo asignamos según el articulo desde el que se envió el comentario y deleted lo decidimos en la pagina de administración si es un comentario insultante. Si son muchos los campos a excluir podemos hacer lo contrario y especificar los que se deben incluir.

    Ahora podemos incluir el formulario en la vista single:

    # Archivo views.py
    from blog.models import Post, Tag, Comment
    from blog.forms import CommentForm
    ...
    # la función recibe el numero capturado
    def single(request, id=0):
    # recuperar post según id
    post = Post.objects.get(id=int(id))
    # Crear formulario
    form = CommentForm()

    return render_to_response("single.html", locals())

    Para crear el formulario se instancia el objeto adecuado y en la vista se usa esta instancia para mostrar el formulario:

    {# plantilla single.html #}
    {% extends 'base.html' %}

    {% block main_content %}
    {% include 'include/article.html' %}
    {# necesitamos crear el tag form #}
    <form action="" method="form">
    {{ form }}
    </form>
    {% endblock %}

    Con esto veremos el formulario en nuestro blog, lo llenamos y enviamos…

    Wow un error. Django requiere que incluyamos protección contra ataques csrf, este tipo de ataque permite que alguien engañe a un usuario registrado para enviar datos a nuestro sitio usando un formulario en otra pagina web. Afortunadamente django tiene todo lo necesario para protegernos con unas cuantas lineas de código.

    # Archivo views.py
    from blog.models import Post, Tag, Comment
    from blog.forms import CommentForm
    # importamos el decorador
    from django.views.decorators.csrf import csrf_protect
    # importamos render
    from django.shortcuts import render

    ...
    # aplicamos el decorador función que recibe datos de formulario
    @csrf_protect
    def single(request, id=0):
    # recuperar post según id
    post = Post.objects.get(id=int(id))
    form = CommentForm()

    # usamos render en lugar de render_to_response
    return render(request, "single.html", locals())

    Ahora incluimos el tag {% csrf_token %} dentro del formulario en la plantilla single.html. Esto crea un elemento input oculto con un token único que se verifica al enviar datos al servidor, si este no coincide, se produce un error 403 (prohibido) y tenemos una app algo mas segura.

    Ok, ya podemos enviar datos del formulario al servidor, hora debemos validarlos y guardarlos en la BD.

    # Archivo views.py
    @csrf_protect
    def single(request, id=0):
    # recuperar post según id
    post = Post.objects.get(id=int(id))
    # recuperar comentarios de este post
    comments = Comment.objects.filter(post=post)

    # Si la petición es POST es porque enviaron el formulario
    if request.method == "POST":
    # Creamos una instancia del modelo Comment asignado el post actual
    comment = Comment(post=post)
    # Creamos una instancia del formulario con los datos recibidos
    form = CommentForm(request.POST, instance=comment)

    # Validamos los datos
    if form.is_valid():
    # Guardamos el comentario en la BD
    form.save()
    # Enviamos al usuario de nuevo al post
    return HttpResponseRedirect("/post/{0}".format(slug))

    # De lo contrario creamos un formulario vacío
    else:
    form = CommentForm()

    return render(request, "single.html", locals())

    Podemos notar algunas cosas:

    1. Validamos el formulario con el método is_valid que no definimos. ¿como es esto posible?; Al crear el modelo Comment especificamos el tipo de datos admitidos, el formulario “sabe” que si esto datos no corresponden, el formulario contiene errores.
    2. Para guardar los datos usamos form.save y no comment.save. Esto se debe a que hay datos que no están incluidos en el formulario (post) pero si en la instancia comment y viceversa, por esto pasamos comment al formulario en el momento de crearlo.

    Solo resta mostrar los comentarios, editamos la plantilla single.html:

    {# plantilla single.html #}
    {% extends 'base.html' %}

    {% block main_content %}
    {% include 'include/article.html' %}
    {% for comment in comments %}
    <div class="comment">
    <div class="user">{{ comment.name }} dijo:</div>
    <div class="body">{{ comment.body }}</div>
    <small>{{ comment.date|date:"d-m-Y" }}</small>
    </div>
    {% endfor %}
    {# necesitamos crear el tag form #}
    <form action="" method="form">
    {{ form }}
    </form>
    {% endblock %}

    Con esto solo falta crear el sitio de administración para agregar post y moderar los comentarios y tendremos un blog terminado. Estén pendientes por la ultima parte de esta serie que seguro saldrá antes que se acabe el mundo este 21 de diciembre XD.

    Creando un blog con django – parte 3

    En la parte anterior dejamos la base de datos lista, es momento de crear las plantillas para visualizar estos datos.
    Lo primero que haremos es configurar el directorio donde estaran guardas las plantillas. Creamos el directorio templates en la raíz del proyecto y editamos el archivo settings.py.

    .....
    import os

    TEMPLATE_DIRS = (
    os.path.join(os.path.dirname(__file__) ,"templates"),
    )
    ....

    Así el script puede encontrar el directorio templates aun cuando cambiemos de ubicación la carpeta del proyecto. Ahora editamos el archivo urls.py para incluir la url a la pagina.

    ....
    urlpatterns = patterns('',
    (r'^$', 'blog.views.index'),
    ....
    )

    Con esto le decimos a django que toda petición hecha a la raíz del blog sera manejada por la función index ubicada en el archivo views, la cual luce así:

    from django.shortcuts import render_to_response

    def index(request):
    return render_to_response("index.html")

    Al visitar la raíz del sitio, django llamara la función index pasando como argumento un objeto request que contiene información sobre la solicitud (datos POST y GET entre otros). La función index realizar algún proceso y retornar un objeto response.

    Si iniciamos el servidor de desarrollo y entramos a la dirección localhost:8000 veremos un mensaje de error como el de la imagen ya que la plantilla index.html aun no existe, así que manos a la obra.

    <!DOCTYPE HTML>
    <html lang="es-ES">
    <head>
    <title>Mi Blog</title>
    </head>
    <body>
    <!-- hasta aquí siempre es igual -->
    <h1>Hola Django</h1>
    </body>

    No muy útil, pero por el momento es todo lo que necesitamos. Imagine que creamos otras plantillas para post individuales, contacto, faq, quejas… debemos repetir la cabecera en cada una de ellas.

    El sistema de plantillas de django permite crear plantillas base con bloques que luego pueden ser reemplazados por las plantillas que heredan de ésta; para tal efecto creamos la plantilla base.html:

    <!DOCTYPE HTML>
    <html lang="es-ES">
    <head>
    <title>Mi Blog</title>
    </head>
    <body>
    {% block content %}
    {% endblock %}
    </body>

    Y editamos la plantilla index.html para que extienda base.html:

    {% extends "base.html" %}
    {% block content %}
    <h1>Hola Django</h1>
    {% endblock %}

    Ok, teniendo una idea general del sistema de plantillas, seguimos con la pagina inicial del blog empezando por editar el archivo views.py.

    from django.shorcuts import render_to_response
    from blog.models import Post

    def index(request):
    # recuperamos 5 primeras entradas
    posts = Post.objects.filter()[:5]
    return render_to_response("index.html", locals())

    Cada vez que visitemos a la raíz del blog se consultaran los primeros 5 post y se pasaran a la plantilla index.

    {# plantilla index.html #}
    {% extends "base.html" %}

    {% block content %}
    {% for post in posts %}
    <h1><a href="/{{ post.id }}">{{ post.title }}</a></h1>
    <p>{{ post.pub_date}}</p>
    {{ post.body }}
    {# esto no es muy eficiente* #}
    {% for tag in post.tags.filter %}
    <a href="/tags/{{ tag.tag }}">{{ tag.tag }}</a>
    {% endfor %}
    {% endfor %}
    {% endblock %}

    * Extraer las etiquetas de esta forma no es eficiente ya que al llamar a filter (5 veces) se hace una nueva consulta a la base de datos, algunas soluciones se pueden encontrar aquí.

    Con esto la pagina principal esta lista. Sigamos con la pagina individual, veremos que al trabajar con django se sigue mas o menos el mismo patrón:

    # archivo urls.py
    urlpatterns = patterns('',
    (r'^$', 'blog.views.index'),
    # expresión regular que captura el numero después de post/
    (r'^post/(?P<id>d+)$', 'blog.views.single'),
    )
    # Archivo views.py
    ...
    # la funcion recibe el numero capturado
    def single(request, id=0):
    # recuperar post según id
    post = Post.objects.get(id=int(id))

    return render_to_response("single.html", locals())
    {# plantilla single.html #}
    {% block content %}
    <h1><a href="/{{ post.id }}">{{ post.title }}</a></h1>
    <p>{{ post.pub_date}}</p>
    {{ post.body }}
    {# esto no es muy eficiente* #}
    {% for tag in post.tags.filter %}
    <a href="/tags/{{ tag.tag }}">{{ tag.tag }}</a>
    {% endfor %}
    {% endblock %}

    Notan algo raro? el código de single.html e index.html luce muy similar; podemos eliminar el código repetido moviéndolo a otra plantilla (article.html) que luego incluimos en estas.

    {# plantilla article.html #}
    </div>
    <h1><a href="/{{ post.id }}">{{ post.title }}</a></h1>
    <p>{{ post.pub_date}}</p>
    {{ post.body }}
    {# esto no es muy eficiente* #}
    {% for tag in post.tags.filter %}
    <a href="/tags/{{ tag.tag }}">{{ tag.tag }}</a>
    {% endfor %}
    </div>

    Ahora editamos las plantillas index.html y single.html para que utilicen article.html.

    {# plantilla inde.html #}
    {% extends 'base.html' %}

    {% block main_content %}
    {% for post in posts %}
    {# pasamos la variable post como argumento #}
    {% include 'include/article.html' with post=post %}
    {% endfor %}

    {% endblock %}
    {# plantilla single.html #}
    {% extends 'base.html' %}

    {% block main_content %}
    {% include 'include/article.html' %}
    {% endblock %}

    Con esto damos por terminado esta parte. No se olviden de comentar.

    P.D.: Procurare tener la próxima parte mas rápido.

    Creando un blog con django – parte 2

    Llego el momento de crear los modelos, pero antes analicemos los datos que necesitamos guardar. Nuestro blog tendrá post, comentarios y tags, los que a su vez se componen de:

    • Post: fecha, titulo, contenido y etiquetas.
    • Etiqueta: nombre.
    • Comentario: autor, fecha, contenido.

    Ahora bien, una etiqueta puede estar relacionada con varios post, así como un post tener varias etiqueta, por lo que tienen una relación many to many. Los comentarios por otro lado solo pertenecen a un post por lo que necesitan una clave foránea que lo relacione con el respectivo post.

    Teniendo claro el modelo de la base de datos, es hora de escribir el código necesario para crear las tablas, afortunadamente django nos facilita la tarea. Editamos el archivo models.py ubicado en la carpeta de la aplicación:

    # 1
    from django.db import models

    # 2
    class Tag(models.Model):
    # 3
    tag = models.CharField(max_length=100, unique=True, db_index=True)
    # 4
    def __unicode__(self):
    return self.tag

    Veamos que hicimos:

    1. Importamos el modulo models.
    2. Creamos una subclase de models.Model, 
    3. Cada columna de la bd es representada por un atributo, en este caso tag es de tipo caracter (CharField), existen otros tipos de campo según el valor almacenar. Los argumentos describen las propiedades del campo como la cantidad de caracteres que puede contener (max_length), que no se permiten valores repetidos (unique), ademas de hacer que se indexe (db_index) ya que es comun hacer búsquedas como: buscar todos los post con tag django.
    4. el método __unicode__ permite dar un nombre mas significativo a las instancias, este es utilizarlo en el sitio administrativo.

    Sabiendo esto es fácil crear las demás tablas:

    # post
    class Post(models.Model):
    # la relación many to many entre Post y Tag
    tags = models.ManyToManyField(Tag, blank=True)
    title = models.CharField(max_length=200)
    body = models.TextField()
    # fecha de publicación, se agrega fecha automáticamente
    pub_date = models.DateField(auto_now_add=True)

    def __unicode__(self):
    return self.title

    class Comment(models.Model):
    # Cada comentario pertenece a un post
    post = models.ForeignKey(Post)
    # nombre es opcional (blank) y por defecto es Anonimus (default)
    name = models.CharField(max_length=100, blank=True, default="Anonimus")
    # el cuerpo del comentario
    body = models.TextField(verbose_name="comment")
    # fecha de publicación, se agrega fecha automáticamente
    date = models.DateField(auto_now_add=True)

    def __unicode__(self):
    return "Por " + self.name + " en " + self.post.title

    La clave foránea y relación many to many se crea en una simple linea de código. Ahora sincronizamos nuevamente y entramos al interprete de python para introducir información en la base de datos:

    $ python manage.py syncdb
    $ python manage.py shell
    # importamos los modelos
    from blog.models import Tag, Post

    # aun no hay tags así que creamos una
    django = Tag(tag="django")
    django.save()

    # creamos un post
    new_post = Post(title="mi primer post", body="El primer post que realizo")
    new_post.save()
    # agregamos un tag y volvemos a guardar
    new_post.tags.add(django)
    new_post.save()

    Como vemos, para introducir un registro en la base de datos creamos una instancia de la clase adecuada y seguidamente llamamos el método save, ¿pero como recuperamos un registro? existen tres métodos para consultar la base de datos:

    • get: retorna un solo registro, lanza una excepción si la consulta retorna cero o mas de un registro.
    • filter: retorna un array con los registros que coinciden la consulta o un array vacío.
    • exclude: similar a filter pero retorna los registros que no cumplen con la consulta.
    Estos métodos se acceden a través del atributo objects que poseen las subclases de models.Model.
    # recuperar todos los post
    Post.objects.filter()

    # recuperar los primeros 10
    Post.objects.filter()[:10]

    Para refinar la búsqueda usamos argumentos que corresponden con los campos de la tabla:

    # post segun titulo
    Post.objects.filter(title="mi primer post")

    Los campos que son claves foraneas (Post.tags, Comment.post) son algo especial: podemos seguir la relación escribiendo el nombre de la columna seguido de dos guiones bajos (_), pudiendo acceder el modelo al que apunta dicha relación. veamos un ejemplo:

    # post con tag django
    Post.objects.filter(tags__tag="django")

    Al usar tags__ ya no estamos mas en Post sino en Tag, ahora podemos consultar los campos de esta tabla (tag). otro ejemplo:

    # comentarios de posts con tag django
    Comment.objects.filter(post__tags__tag="django")

    Seguimos la relación a Post (post__) y consultamos la columna tags__ lo que nos lleva a Tag donde finalmente consultamos el atributo tag o_O.

    Con esto tenemos lista la base de datos, en la próxima parte veremos como crear plantillas para visualizar estos datos.

    Muchas gracias por su atención, no olviden dejar sus sugerencias, inquietudes y amenazas en los comentarios, hasta la próxima.

    Creando un blog con django – parte 1

    the killer framework

    Primero quiero dejar claro que no soy un experto en django, así que si cometo algún error no duden en hacérmelo saber.

    Este tutorial se divide en varias partes:

    1. instalación y configuración
    2. creación de modelos
    3. creación de vistas
    4. formularios
    Instalación y configuración

    Procedemos a descargar la ultima versión de django e instalarla como dice la pagina oficial:

    $ wget http://www.djangoproject.com/download/1.3.1/tarball/
    $ tar xzvf Django-1.3.1.tar.gz
    $ cd Django-1.3.1
    $ sudo python setup.py install

    Ahora haciendo uso del script de administración creamos nuestro proyecto:

    $ django-admin startproject tutorial

    Esto crea el directorio tutorial, con los siguientes archivos:

    • manage.py: script para administrar el proyecto (sincronizar bd, crear aplicaciones, iniciar servidor…)
    • urls.py: las urls admitidas.
    • settings.py: configuración del proyecto (aplicaciones instaladas, directorios de plantillas, bd…)

    Lo primero que haremos es crear nuestra aplicación (blog) usando el script manage.py

    $ python manage.py startapp blog

    Este comando crea el directorio blog con varios archivos en su interior, los cuales analizaremos en otra ocasión. Para terminar editamos el archivo settings.py e incluir la configuración de la base de datos y agregamos nuestra aplicación a la lista de aplicaciones instaladas:

    DATABASES = {
    'default': {
    'ENGINE': 'django.db.backends.sqlite3',
    'NAME': 'myblog',
    # lo demás no es necesario para una bd sqlite
    'USER': '',
    'PASSWORD': '',
    'HOST': '',
    'PORT': '',
    }
    }
    ...
    ...
    INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog', # Nuestra aplicación
    )

    Sincronizamos la base de datos para crear las tablas necesarias:

    $ python manage.py syncdb

    Se pedirán algunos datos para crear la cuenta de administrador. Luego iniciamos el servidor de desarrollo con:

    $ python manage.py runserver

    Y listo, si entramos a la dirección localhost:8000 veremos una flamante pagina web confirmando que django esta correctamente configurado y listo para iniciar a desarrollar nuestra aplicación.

    Eso fue todo por ahora, muchas gracias por su atención y no olviden estar pasando para la segunda parte.

    ConceptMapp – webapp para crear mapas conceptuales

    Después de varios meses de posponerlo, por fin esta disponible mi primera aplicación web: ConceptMapp, una herramienta para la creación de mapas conceptuales completamente gratis.

    La idea nació luego de estar experimentando con el elemento canvas, entonces me acorde de Cmaptools, uno de los mejores programas que he utilizado para crear mapas conceptuales, pero que a mi parecer tiene los siguientes inconvenientes:

    • Consume muchos recursos.
    • No es muy común: no se puede esperar encontrarlo instalado en otros computadores diferentes al personal, lo que impide realizar cambios cuando mas se necesitan.

    Con ConceptMapp intento resolver estos problema, permitiendo acceder y editar mapas conceptuales desde cualquier computador con un navegador web moderno (a.k.a no ie <= 8) y una conexión a Internet, ademas de ser liviano*.

    Si les parece útil, encuentran algún error o tienen sugerencias pueden dejarlas en los comentarios o usando el formulario de contacto.

    * Probado en un PC de: 1 GB de RAM con procesador de 1.8 GHz.