logo cosasdedevs

Crear un blog con Django. Parte 6: Vistas (Views)

Crear un blog con Django. Parte 6: Vistas (Views)

My Profile
Dic 15, 2019

Bienvenidos a la última parte de esta serie de tutoriales para crear un blog con Python y Django, en esta parte vamos a conectar todas las partes de nuestra aplicación.

Lo primero que haremos será poblar con datos nuestra base de datos, yo he añadido unas cuantas categorías y un par de posts para que ahora los podamos ver al crear las vistas. Recordad que podéis añadirlas desde el panel de admin.

Ahora vamos a cambiar las urls para que apunten a la vista que crearemos en el siguiente paso, para ello editamos nuestro archivo simple_blog/urls.py y modificamos el código existente por el siguiente:

from django.contrib import admin
from django.urls import path, include
from django.views.generic import TemplateView
from django.conf.urls.static import static
from django.conf import settings


urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include(('posts.urls', 'posts'), namespace='posts')),
    path(
        route='sobre-mi',
        view=TemplateView.as_view(template_name='about.html'),
        name='about'
    ),
    path('', include(('users.urls', 'users'), namespace='users')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Como podéis ver hemos hecho lo mismo que para los usuarios, ahora vamos a guardar todas las urls de los posts dentro de posts/urls.py. También hemos añadido la línea static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) que ya estaba en settings y se utiliza para poder acceder a los archivos alojados en la carpeta media.

Después creamos el archivo posts/urls.py y añadimos el siguiente código para que nuestras urls apunten a las funciones que vamos a crear en nuestras vistas:

"""Posts URLs."""

# Django
from django.urls import path

# Views
from posts import views

urlpatterns = [
    path(
        route='',
        view=views.PostsFeedView.as_view(),
        name='blog'
    ),
    path(
        route='posts/<slug:url>/',
        view=views.PostDetailView.as_view(),
        name='detail'
    ),
    path(
        route='posts/save_comment',
        view=views.save_comment,
        name='save_comment'
    ),
]

Aquí hemos creado tres urls, una para la página principal, otra que será el detalle del post y en el que usamos el método slug para recuperar la última parte del path de la url y otra que se encargará de guardar los comentarios.

Antes de ponernos con la vista necesitaremos crear un formulario para posteriormente guardar los comentarios que los usuarios escriban en nuestros posts así que creamos el archivo comments/forms.py e insertamos el siguiente código:

"""User forms."""

# Django
from django import forms

# Models
from comments.models import Comment
from django.contrib.auth.models import User

class CreateCommentForm(forms.ModelForm):
    """Post model form."""

    comment = forms.CharField(widget=forms.Textarea)


    class Meta:
        """Form settings."""

        model = Comment
        fields = ('user', 'profile', 'post', 'comment')

Aquí estamos creando un campo de tipo textarea que será el que guarde el comentario del usuario y en la clase Meta le decimos el modelo de referencia y los campos que tiene que guardar, en este caso el usuario que lo ha escrito, el post al que va referenciado y el comentario.

Ahora que tenemos algo de información que pintar vamos a recuperarla en la vista para que se muestre luego en nuestra web, para ello abrimos el archivo posts/views.py y añadimos las siguientes líneas:

from django.shortcuts import render, redirect
from django.views.generic import DetailView, ListView
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse

# Models
from posts.models import Post
from categories.models import Category
from comments.models import Comment

# Forms
from comments.forms import CreateCommentForm


class PostsFeedView(ListView):
    """Index."""
    template_name = 'posts/index.html'
    model = Post
    ordering = ('-created',)
    paginate_by = 10
    context_object_name = 'posts'
    queryset = Post.objects.filter(is_draft=False)

    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['categories'] = Category.objects.all()
        return context

    
class PostDetailView(DetailView):
    """Detail post."""
    template_name = 'posts/detail.html'
    model = Post
    context_object_name = 'post'
    slug_field = 'url'
    slug_url_kwarg = 'url'


    def get_queryset(self):
        return Post.objects.filter(is_draft=False)

    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['categories'] = Category.objects.all()
        context['comments'] = Comment.objects.filter(post=self.get_object()).all()
        context['form_comments'] = CreateCommentForm()
        return context


@login_required
def save_comment(request):
    if request.method == 'POST':
        url = request.POST['url']
        post = {
            'user': request.user.id,
            'profile': request.user.id,
            'comment': request.POST['comment'],
            'post': request.POST['post']
        }
        form = CreateCommentForm(post)
        if form.is_valid():
            form.save()
            return redirect('posts:detail', url=url)
    else:
        return HttpResponse(status=405)
    return HttpResponse(status=500)

En este archivo tenemos las tres vistas a las que se llaman desde las urls, vamos a explicar lo que hacen:

Lo que estamos haciendo en la clase PostsFeedView es utilizar una de las vistas predeterminadas que tiene Django, en este caso ListView que permite retornar una lista de objectos.

template_name: La template que será la que reciba toda la información.
model: El modelo que vamos a usar, en este caso Post.
ordering: El orden por el que queremos que aparezcan los posts,, en nuestro caso queremos que aparezcan por orden de creación de forma descendente.
paginate_by: El número de posts que se mostrarán por página.
queryset: Para añadir condiciones adicionales, nosotros no queremos que se muestren los borradores así que los descartamos.

A parte utilizamos la función get_context_data para añadir información extra a nuestra vista, en este caso las categorías para mostrarlas en el menú lateral.

La clase llamada PostDetailView es de tipo DetailView y se encargar de mostrar un post en concreto, para ello recupera el título en la url y lo utiliza a modo de consulta. Para recuperalo utiliza slug_field = 'url' y slug_url_kwarg = 'url'

Aquí también utilizamos la función get_context_data para traernos las categorías y los comentarios que pueda tener el post además del formulario que creamos anteriormente para guardar comentarios y que solo se mostrará en caso de que el usuario esté logeado.

Por último tenemos la función saveComment con el decorador @login_required que lo que hace es que nos obliga a estar logeados para poder guardar un comentario. También revisamos que la llamada la haga mediante el método POST y sino devolvemos un error. Si todo ha ido bien lo guardamos y recargamos la página en la que estamos.

Ahora que tenemos la vista completada vamos a darle un poco de funcionalidad a todos los enlaces de la web y a mostrar los posts y categorías, para ello abrimos nuestro archivo templates/base.html y sustituimos su código por el siguiente:

<!DOCTYPE html>
<html lang="es">
   <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>{% block title %}Simple blog{% endblock %}</title>
      {% load bootstrap4 %}
      {% bootstrap_css %}
      {% load static %}
      <link rel="stylesheet" href="{% static 'css/blog-home.css' %}">
   </head>
   <body>
      <!-- Navigation -->
      <nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
         <div class="container">
            <a class="navbar-brand" href="{% url 'posts:blog' %}">Simple Blog</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarResponsive">
               <ul class="navbar-nav ml-auto">
                  <li class="nav-item active">
                     <a class="nav-link" href="{% url 'posts:blog' %}">Blog
                     <span class="sr-only">(current)</span>
                     </a>
                  </li>
                  <li class="nav-item">
                     <a class="nav-link" href="{% url 'about' %}">Sobre mi</a>
                  </li>
                  {% if user.is_authenticated %}
                  <li class="nav-item dropdown">
                     <a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">{{request.user.username}}</a>
                     <div class="dropdown-menu">
                        <a class="dropdown-item" href="{% url 'users:logout' %}">Logout</a>
                     </div>
                  </li>
                  {% else %}
                  <li class="nav-item">
                     <a class="nav-link" href="{% url 'users:login' %}">Login</a>
                  </li>
                  <li class="nav-item">
                     <a class="nav-link" href="{% url 'users:register' %}">Registro</a>
                  </li>
                  {% endif %}
               </ul>
            </div>
         </div>
      </nav>
      <!-- Page Content -->
      <div class="container">
         {% block content %}{% endblock %}    
         <!-- Sidebar Widgets Column -->
         <div class="col-md-4">
            <!-- Categories Widget -->
            <div class="card my-4">
               <h5 class="card-header">Categories</h5>
               <div class="card-body">
                  <div class="row">
                     <div class="col-lg-12">
                        <ul class="list-unstyled mb-0">
                          {% for category in categories %}
                          <li>
                              <a href="#">{{category.name}}</a>
                          </li>
                          {% endfor %}
                        </ul>
                     </div>
                  </div>
               </div>
            </div>
            <!-- Side Widget -->
            <div class="card my-4">
               <h5 class="card-header">Side Widget</h5>
               <div class="card-body">
                  You can put anything you want inside of these side widgets. They are easy to use, and feature the new Bootstrap 4 card containers!
               </div>
            </div>
         </div>
      </div>
      <!-- Footer -->
      <footer class="py-5 bg-dark">
         <div class="container">
            <p class="m-0 text-center text-white">Copyright &copy; Simple Blog {% now "Y" %}</p>
         </div>
         <!-- /.container -->
      </footer>
      {% bootstrap_javascript jquery='full' %}
   </body>
</html>

En este archivo los cambios más importantes que hemos añadido son las urls, una condición para comprobar si el usuario está logeado con {% if user.is_authenticated %} y si es así pintamos el botón con la opción de logout, sino la opción de ir a la pantalla de registro y de login. También listamos las categorías.

Ahora que ya tenemos la base completa abrimos el archivo templates/posts/index.html y modificamos el contenido por el siguiente código:

{% extends "base.html" %}
{% block content %}
    <div class="row">
      <div class="col-md-8">

        <h1 class="my-4">Simple Blog
        </h1>
        {% for post in posts %}
        <div class="card mb-4">
          <img class="card-img-top" src="{{ post.image_header.url }}" alt="Card image cap">
          <div class="card-body">
            <h2 class="card-title">{{post.title}}</h2>
            <p class="card-text">{{post.post|safe|truncatechars:300}}</p>
            <a href="{% url 'posts:detail' post.url%}" class="btn btn-primary">Leer más &rarr;</a>
          </div>
          <div class="card-footer text-muted">
            Escrito el {{post.created}}
            <a href="#">{{post.user.username}}</a>
          </div>
        </div>
        {% endfor %}
        <ul class="pagination justify-content-center mb-4">
          <li class="page-item">
            <a class="page-link" href="#">&larr; Older</a>
          </li>
          <li class="page-item disabled">
            <a class="page-link" href="#">Newer &rarr;</a>
          </li>
        </ul>

      </div>
{% endblock %}

Aquí lo que hacemos es añadir los datos de los posts y sustituirlos por los que teníamos de prueba. Lo más destacable sería el enlace al detalle que es este {% url 'posts:detail' post.url%}. Lo que estamos haciendo es coger la url del detalle y concatenarle la url que generamos con slug para crear el enlace al detalle de post.

Por último editamos el archivo templates/posts/details.html y sustituimos el código existente por este:

{% extends "base.html" %}
{% block content %}
  <div class="container">
    <div class="row">
      <div class="col-lg-8">
        <h1 class="mt-4">{{post.title}}</h1>
        <p class="lead">
          por 
          <a href="#">{{post.user.username}}</a>
        </p>
        <hr>
        <p>Escrito el {{post.created}}</p>
        <hr>
        <img class="img-fluid rounded" src="{{ post.image_header.url }}" alt="">
        <hr>
        <p class="lead">{{post.post|safe}}</p>
        <hr>
        {% if user.is_authenticated %}
        <div class="card my-4">
          <h5 class="card-header">Deja un comentario:</h5>
          <div class="card-body">
            <form method="POST" action="{% url "posts:save_comment" %}">
              {% csrf_token %}
              <input type="hidden" name="url" value="{{post.url}}" />
              <input type="hidden" name="post" value="{{post.id}}" />
              <div class="form-group">
                <textarea class="form-control" name="comment" rows="3"></textarea>
              </div>
              <button type="submit" class="btn btn-primary">Enviar</button>
            </form>
          </div>
        </div>
        {% endif %}
        {% for comment in comments %}
        <div class="media mb-4">
          {% if comment.user.profile.photo %}
          <img class="d-flex mr-3 rounded-circle" width="50px" height="50px" src="/media/{{ comment.user.profile.photo }}" alt="">
          {% else %}
          <img class="d-flex mr-3 rounded-circle" src="http://placehold.it/50x50" alt="">
          {% endif %}
          <div class="media-body">
            <h5 class="mt-0">{{comment.user.username}}</h5>
            {{comment.comment}}
          </div>
        </div>
        {% endfor %}
      </div>
{% endblock %}

Al igual que en index añadimos los datos del post y modificamos el formulario para poder guardar los comentarios que escriba un usuario. También se muestra la lista de todos los comentarios si el post los tiene.

Con esto ya tendríamos todo lo que quería explicar en el tutorial, aun faltarían algunas cosas como la páginación, el login con email y la validación del email cuando un usuario se registra.

Para acceder al proyecto al completo he creado un repositorio en mi github.

Espero que esta serie de tutoriales os pueda ayudar en vuestros inicios con Django :)

845 vistas

Nos tomamos en serio tu privacidad

Utilizamos cookies propias y de terceros para mejorar la experiencia del usuario a través de su navegación. Si pulsas entendido aceptas su uso. Ver política de cookies.

🐍 Sígueme en Twitter

Si te gusta el contenido que subo y no quieres perderte nada, sígueme en Twitter y te avisaré cada vez que cree contenido nuevo 💪
Luego ¡Te sigo!