List Blueprint
Utilizarás las mismas técnicas que aprendiste al escribir el blueprint de autenticación para escribir el blueprint del “Todo List”. Se deberá listar todas los items, permitir a los usuarios registrados crear tareas y permitir al autor de una tarea editarla o eliminarla.
A medida que implementes cada vista, mantén el servidor de desarrollo en ejecución. A medida que guardes tus cambios, intenta acceder a la URL en tu navegador y probarlos.
El Blueprint
Defina el blueprint y regístrelo en la fábrica de aplicaciones.
flask_todolist/list.py
from flask import (
Blueprint, flash, g, redirect, render_template, request, url_for
)
from werkzeug.exceptions import abort
from flask_todolist.auth import login_required
from flask_todolist.db import get_db
bp = Blueprint('list', __name__)
Importe y registre el blueprint de fábrica usando app.register_blueprint(). Coloque el nuevo código al final de la función de fábrica antes de devolver la aplicación.
flask_todolist/__init__.py
def create_app():
app = ...
# existing code omitted
from . import list
app.register_blueprint(list.bp)
app.add_url_rule('/', endpoint='index')
return app
A diferencia del blueprint de autenticación (auth), el blueprint del “list” no tiene un "url_prefix". Por lo tanto, la vista de índice estará en /, la vista de creación en /create, y así sucesivamente. El “list” es la característica principal de Flask_todolist, por lo que tiene sentido que el índice del todo list sea el índice principal.
Sin embargo, el endpoint para la vista de índice definida a continuación será "list.index". Algunas de las vistas de autenticación se referían a un endpoint simple llamado "index". app.add_url_rule() asocia el nombre del endpoint "index" con la URL /, de modo que tanto url_for('index') como url_for('list.index') funcionarán generando la misma URL, /.
En otra aplicación, podrías asignarle al blueprint del todo list un "url_prefix" y definir una vista de índice separada en la fábrica de la aplicación, similar a la vista de "hello". En ese caso, los endpoints y las URLs para el índice y "list.index" serían diferentes.
Index
El índice mostrará todas los items de la lista, comenzando por las más recientes. Se utiliza una operación JOIN para que la información del autor desde la tabla de usuarios esté disponible en el resultado.
flask_todolist/list.py
@bp.route('/')
def index():
db = get_db()
lists = db.execute(
'SELECT p.id, title, body, created, author_id, username'
' FROM item p JOIN user u ON p.author_id = u.id'
' ORDER BY created DESC'
).fetchall()
return render_template('list/index.html', lists=lists)
flask_todolist/templates/list/index.html
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Todo List{% endblock %}</h1>
{% if g.user %}
<a class="action" href="{{ url_for('list.create') }}">New</a>
{% endif %}
{% endblock %}
{% block content %}
{% for item in items %}
<article class="post">
<header>
<div>
<h1>{{ item['title'] }}</h1>
<div class="about">by {{ item['username'] }} on {{ item['created'].strftime('%Y-%m-%d') }}</div>
</div>
{% if g.user['id'] == item['author_id'] %}
<a class="action" href="{{ url_for('list.update', id=item['id']) }}">Edit</a>
{% endif %}
</header>
<p class="body">{{ item['body'] }}</p>
</article>
{% if not loop.last %}
<hr>
{% endif %}
{% endfor %}
{% endblock %}
Cuando un usuario ha iniciado sesión, el bloque de encabezado agrega un enlace a la vista de creación. Cuando el usuario es el autor de una publicación, verá un enlace "Editar" que lleva a la vista de actualización para esa publicación. loop.last es una variable especial disponible en Jinja para bucles. Se utiliza para mostrar una línea después de cada publicación, excepto la última, para separarlas visualmente.
Create
La vista de creación funciona de la misma manera que la vista de registro de autenticación. Se muestra el formulario o se valida la información enviada y se agrega la publicación a la base de datos, o se muestra un error.
Se utiliza el decorador login_required que escribiste anteriormente en las vistas del list. Un usuario debe haber iniciado sesión para visitar estas vistas, de lo contrario, se le redirigirá a la página de inicio de sesión.
flask_todolist/list.py
@bp.route('/create', methods=('GET', 'POST'))
@login_required
def create():
if request.method == 'POST':
title = request.form['title']
body = request.form['body']
error = None
if not title:
error = 'Title is required.'
if error is not None:
flash(error)
else:
db = get_db()
db.execute(
'INSERT INTO item (title, body, author_id)'
' VALUES (?, ?, ?)',
(title, body, g.user['id'])
)
db.commit()
return redirect(url_for('list.index'))
return render_template('list/create.html')
flask_todolist/templates/list/create.html
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}New Item{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="title">Title</label>
<input name="title" id="title" value="{{ request.form['title'] }}" required>
<label for="body">Body</label>
<textarea name="body" id="body">{{ request.form['body'] }}</textarea>
<input type="submit" value="Save">
</form>
{% endblock %}
Update
Tanto las vistas de actualización (update) como las de eliminación (delete) deberán obtener una publicación por su id y verificar si el autor coincide con el usuario que ha iniciado sesión. Para evitar duplicar código, puedes escribir una función para obtener la publicación y llamarla desde cada vista.
flask_todolist/list.py
def get_item(id, check_author=True):
item = get_db().execute(
'SELECT p.id, title, body, created, author_id, username'
' FROM item p JOIN user u ON p.author_id = u.id'
' WHERE p.id = ?',
(id,)
).fetchone()
if item is None:
abort(404, f"Item id {id} doesn't exist.")
if check_author and item['author_id'] != g.user['id']:
abort(403)
return item
abort() generará una excepción especial que devuelve un código de estado HTTP. Toma un mensaje opcional para mostrar con el error; de lo contrario, se utiliza un mensaje predeterminado.
404 significa "No encontrado" y 403 significa "Prohibido". (401 significa "No autorizado", pero se redirecciona a la página de inicio de sesión en lugar de devolver ese estado).
El argumento check_author se define para que la función pueda obtener una publicación sin verificar el autor. Esto sería útil si escribieras una vista para mostrar una publicación individual en una página, donde el usuario no importa porque no está modificando la publicación.
flask_todolist/list.py
@bp.route('//update', methods=('GET', 'POST'))
@login_required
def update(id):
item = get_item(id)
if request.method == 'POST':
title = request.form['title']
body = request.form['body']
error = None
if not title:
error = 'Title is required.'
if error is not None:
flash(error)
else:
db = get_db()
db.execute(
'UPDATE item SET title = ?, body = ?'
' WHERE id = ?',
(title, body, id)
)
db.commit()
return redirect(url_for('list.index'))
return render_template('list/update.html', item=item)
A diferencia de las vistas que has escrito hasta ahora, la función update toma un argumento, id. Eso corresponde a en la ruta. Una URL real se verá como /1/update. Flask capturará el 1, asegurará que sea un entero y lo pasará como argumento id. Si no especificas int: y en su lugar pones , será una cadena. Para generar una URL a la página de actualización, url_for() necesita recibir el id para saber qué completar: url_for('list.update', id=item['id']). Esto también está en el archivo index.html arriba.
Las vistas create y update se ven muy similares. La diferencia principal es que la vista update utiliza un objeto item y una consulta UPDATE en lugar de una INSERT. Con un poco de refactorización inteligente, podrías utilizar una sola vista y una sola plantilla para ambas acciones, pero para el tutorial es más claro mantenerlas separadas.
flask_todolist/templates/list/update.html
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Edit "{{ item['title'] }}"{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="title">Title</label>
<input name="title" id="title"
value="{{ request.form['title'] or item['title'] }}" required>
<label for="body">Body</label>
<textarea name="body" id="body">{{ request.form['body'] or item['body'] }}</textarea>
<input type="submit" value="Save">
</form>
<hr>
<form action="{{ url_for('list.delete', id=item['id']) }}" method="post">
<input class="danger" type="submit" value="Delete" onclick="return confirm('Are you sure?');">
</form>
{% endblock %}
Esta plantilla tiene dos formularios. El primero envía los datos editados a la página actual (//update). El otro formulario contiene solo un botón y especifica un atributo de acción que envía los datos a la vista delete. El botón utiliza JavaScript para mostrar un cuadro de diálogo de confirmación antes de enviar los datos.
El patrón {{ request.form['title'] or item['title'] }} se utiliza para elegir qué datos aparecen en el formulario. Cuando el formulario no se ha enviado, aparecen los datos originales del item, pero si se envían datos de formulario no válidos, se desea mostrar esos datos para que el usuario pueda corregir el error, por lo que se utiliza request.form en su lugar. request es otra variable que está automáticamente disponible en las plantillas.
Delete
La vista de eliminación no tiene su propia plantilla, el botón de eliminar forma parte de update.html y se envía a la URL //delete. Dado que no hay una plantilla específica, esta vista solo manejará el método POST y luego redireccionará a la vista index.
flask_todolist/list.py
@bp.route('//delete', methods=('POST',))
@login_required
def delete(id):
get_item(id)
db = get_db()
db.execute('DELETE FROM item WHERE id = ?', (id,))
db.commit()
return redirect(url_for('list.index'))
¡Felicitaciones! Has terminado de escribir tu aplicación. Tómate un tiempo para probar todo en el navegador. Pero es bueno que revises cada sección y entiendas los conceptos.
← Archivos estáticos | Indice del tutorial →
En este Post aprenderemos a personalizar la tienda Magento 2. Descubre cómo incorporar JavaScript personalizado para mejorar la funcionalidad y ...
Leer más...Current version of RDBMS is not supported. Used Version: 10.6.17-MariaDB-1:10.6.17+maria~ubu2004. Supported versions: MySQL-8, MySQL-5....
Leer más...Los principales motivos para aprenderLa motivación es uno de los pilares fundamentales en el proceso de aprendizaje del desarrollo web....
Leer más...✨ Dominando el Estilo: Un Viaje al Mundo de CSSEn el universo del desarrollo web, CSS (Cascading Style Sheets) desempeña un papel funda...
Leer más...