Cansada de ser feliz

Bienvenidos a mi flujo de conciencia

Cómo hacer ListView en Flask

| Comments

Muchos de nosotros, pasando de Django a Flask, extrañamos las vistas basadas en clases (CBV).

Vamos a hacer un ListView que nos ayude a crear listados de objetos, generados a partir de un modelo específico.

Primero definimos una clase base BaseView que nos permitirá analizar las peticiones y devolver el contexto y renderizar la plantilla:

base.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from flask.views import View
from flask import render_template, request, current_app


class BaseView(View):
    methods = ['GET']
    template_name = None
    model = None

    def get_template_name(self):
        if self.template_name is None:
            raise NotImplementedError()
        return self.template_name

    def render_template(self, context):
        return render_template(self.get_template_name(), **context)

    def get_context(self):
        return {}

    def dispatch_request(self):
        return self.render_template(self.get_context())

Luego heredamos BaseView para definir el método get_objects, que devuelve un listado de objetos del modelo especificado, y get_context para añadir ese listado el contexto y agregar la paginación:

base.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class ListView(BaseView):
    methods = ['GET', 'POST']
    template_name = None
    context_object_name = None
    paginate_by = None
    model = None

    def get_objects(self):
        return self.model.query.all()

    def get_context(self):
        context = {}
        context_object_name = self.context_object_name or 'object_list'
        object_list = self.get_objects()

        # Pagination
        page = request.args.get('page', 1, type=int)
        pagination = object_list.paginate(
            page,
            per_page=(
                self.paginate_by or
                current_app.config['PAGINATION_ITEMS_PER_PAGE']
            ),
            error_out=False,
        )
        context['pagination'] = pagination
        context[context_object_name] = pagination.items
        return context

    def dispatch_request(self):
        return self.render_template(self.get_context())

Para usar ListView tenemos que especificar el modelo (model), ruta hacía la plantilla (template_name) y número de objetos por página (paginate_by o en la configuracón de nuestra aplicación current_app.config['PAGINATION_ITEMS_PER_PAGE']):

views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from flask import Blueprint

from passengers.models import Passenger


passengers = Blueprint('passengers', __name__, template_folder='templates')


class PassengersListView(ListView):
    template_name = 'passengers/list.html'
    context_object_name = 'passengers'
    model = Passenger
    paginate_by = 10

    def get_objects(self):
        return Passenger.query.order_by(Passenger.created_at.desc())

passengers.add_url_rule('/', view_func=PassengersListView.as_view('list'))

Donde el modelo de pasajeros se ve así:

views.py
1
2
3
4
5
6
7
class Passenger(db.Model):
    __tablename__ = 'passengers'

    id = Column(Integer, primary_key=True)
    name = Column(String(255))
    email = Column(String(255), unique=True)
    phone = Column(String(255))

Ahora podemos crear la plantilla:

list.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{% block content %}
{% extends "layouts/base.html" %}
{% import "_macros.html" as macros %}

{% block content %}
<table class="table">
  <tbody>
  {% for passenger in passengers %}
    <tr>
      <td><a href="{{ url_for('passengers.passenger_detail', pk=passenger.id) }}">{{ passenger.id }}</a></td>
      <td>{{ passenger.name }}</td>
      <td>{{ passenger.email }}</td>
      <td>{{ passenger.phone }}</td>
    </tr>
  {% endfor %}
  </tbody>
</table>

<div class="pagination">
  {{ macros.pagination_widget(pagination, '.index') }}
</div>
{% endblock %}

El widget para la paginación está basado en el snippet de Simple Pagination:

_macros.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{% macro pagination_widget(pagination, endpoint) %}
<ul class="pagination">
    <li{% if not pagination.has_prev %} class="disabled"{% endif %}>
        <a href="{% if pagination.has_prev %}{{ url_for(endpoint, page = pagination.page - 1, **kwargs) }}{% else %}#{% endif %}">
            &laquo;
        </a>
    </li>
    {% for p in pagination.iter_pages() %}
        {% if p %}
            {% if p == pagination.page %}
                <li class="active">
                    <a href="{{ url_for(endpoint, page = p, **kwargs) }}">{{ p }}</a>
                </li>
            {% else %}
                <li>
                    <a href="{{ url_for(endpoint, page = p, **kwargs) }}">{{ p }}</a>
                </li>
            {% endif %}
        {% else %}
            <li class="disabled"><a href="#">&hellip;</a></li>
        {% endif %}
    {% endfor %}
    <li{% if not pagination.has_next %} class="disabled"{% endif %}>
        <a href="{% if pagination.has_next %}{{ url_for(endpoint, page = pagination.page + 1, **kwargs) }}{% else %}#{% endif %}">
            &raquo;
        </a>
    </li>
</ul>
{% endmacro %}

Lee más acerca de cómo agregar un formulario de búsqueda a tu lista: Formulario de búsqueda en Flask

Comments