Добавление и отображение статей из БД

На этом занятии продолжим изучать работу с БД во Flask и давайте для начала наполним таблицу mainmenu, которую создали на прошлом занятии. Для этого воспользуемся вспомогательной программой

DB Browser for SQLite

И введем несколько записей:

Затем, в программе в обработчике главной страницы обратимся к этой БД и сформируем меню:

@app.route("/")
def index():
    db = get_db()
    dbase = FDataBase(db)
    return render_template('index.html', menu = dbase.getMenu())

Смотрите, я здесь для удобства создал вспомогательный класс FDataBase, который запоминает ссылку на БД и, затем, с помощью метода getMenu возвращает список для отображения меню в шаблоне index.html. Сама реализация класса, следующая. В новом файле FDataBase.py, запишем следующие строчки:

import sqlite3
 
class FDataBase:
    def __init__(self, db):
        self.__db = db
        self.__cur = db.cursor()
 
    def getMenu(self):
        sql = '''SELECT * FROM mainmenu'''
        try:
            self.__cur.execute(sql)
            res = self.__cur.fetchall()
            if res: return res
        except:
            print("Ошибка чтения из БД")
        return []

Здесь в конструкторе запоминается ссылка на БД и ссылка на класс курсор, через который осуществляется взаимодействие с таблицами этой БД. Далее, идет метод getMenu и в блоке try/except осуществляется выборка всех записей из таблицы mainmenu. Если операция прошла успешно, то возвращается список словарей из записей, а иначе – пустой список.

Чтобы воспользоваться этим классом в файле flsite.py, его нужно импортировать:

from FDataBase import FDataBase

Все, теперь при запуске программы и отображения главной страницы мы увидим меню, взятое из нашей БД.

Добавление статьи

Далее реализуем функционал по добавлению статей в БД. Для начала нам нужно создать таблицу, которая будет их хранить. Добавим в файл sq_db.sql следующий SQL-запрос:

CREATE TABLE IF NOT EXISTS posts (
id integer PRIMARY KEY AUTOINCREMENT,
title text NOT NULL,
text text NOT NULL,
time integer NOT NULL
);

Мы здесь создаем таблицу posts с четырьмя вполне очевидными полями. Затем, в программе добавим обработчик для адреса /add_post:

@app.route("/add_post", methods=["POST", "GET"])
def addPost():
    db = get_db()
    dbase = FDataBase(db)
 
    if request.method == "POST":
        if len(request.form['name']) > 4 and len(request.form['post']) > 10:
            res = dbase.addPost(request.form['name'], request.form['post'])
            if not res:
                flash('Ошибка добавления статьи', category = 'error')
            else:
                flash('Статья добавлена успешно', category='success')
        else:
            flash('Ошибка добавления статьи', category='error')
 
    return render_template('add_post.html', menu = dbase.getMenu(), title="Добавление статьи")

Вначале идет подключение к БД, и после этого проверка: если были переданы данные от формы методом POST, то нужно осуществить добавление статьи в таблицу posts. Для этого вначале проверяем наличие данных в полях name и post и, если все нормально, то вызываем метод addPost класса FDataBase (позже мы его пропишем). Кроме того, формируются мгновенные сообщения об успешности или ошибке при добавлении статьи. В конце возвращается шаблон 'add_post.html', который выглядит так:

{% extends 'base.html' %}
 
{% block content %}
{{ super() }}
{% for cat, msg in get_flashed_messages(True) %}
<div class="flash {{cat}}">{{msg}}</div>
{% endfor %}
 
<form action="{{url_for('addPost')}}" method="post" class="form-contact">
<label>Название статьи: </label> <input type="text" name="name" value="" requied />
<label>Текст статьи:</label>
<textarea name="post" rows=7 cols=40></textarea>
<input type="submit" value="Добавить" />
</form>
{% endblock %}

Здесь все вам уже знакомо по предыдущим занятиям. Последнее, что нам нужно сделать – это добавить метод addPost в класс FDataBase:

    def addPost(self, title, text):
        try:
            tm = math.floor(time.time())
            self.__cur.execute("INSERT INTO posts VALUES(NULL, ?, ?, ?)", (title, text, tm))
            self.__db.commit()
        except sqlite3.Error as e:
            print("Ошибка добавления статьи в БД "+str(e))
            return False
 
        return True

Ему передаются два аргумента: title и text. Затем, вычисляется текущее время добавления статьи (в секундах) и выполняется запрос на добавление переданных данных. После этого обязательно нужно вызвать метод commit для физического сохранения изменений в БД. Также этот метод использует два вспомогательных модуля:

import time
import math

для вычисления текущего времени. Все, теперь наша страница готова и выглядит следующим образом:

При нажатии на кнопку «Добавить» данные будут переданы обработчику /add_post и при успешной проверке принятых значений, статья будет помещена в таблицу posts.

Отображение статьи

После того, как статьи добавлены в БД, их можно отобразить. Как это сделать? Для начала запишем обработчик для следующего URL-адреса:

@app.route("/post/<int:id_post>")
def showPost(id_post):
    db = get_db()
    dbase = FDataBase(db)
    title, post = dbase.getPost(id_post)
    if not title:
        abort(404)
 
    return render_template('post.html', menu=dbase.getMenu(), title=title, post=post)

С его помощью будут отображаться статьи с указанным id_post. Вначале также устанавливается соединение с БД, затем, вызывается метод getPost класса FDataBase, который возвращает заголовок статьи и ее текст, а, иначе, при ошибке считывания данных из таблицы posts, формируется ответ сервера 404 – страница не найдена. В конце возвращается шаблон 'post.html' с содержимым статьи. Шаблон имеет вот такой простой вид:

{% extends 'base.html' %}
 
{% block content %}
{{ super() }}
{{ post }}
{% endblock %}

Наконец, метод getPost, имеет следующую реализацию:

    def getPost(self, postId):
        try:
            self.__cur.execute(f"SELECT title, text FROM posts WHERE id = {postId} LIMIT 1")
            res = self.__cur.fetchone()
            if res:
                return res
        except sqlite3.Error as e:
            print("Ошибка получения статьи из БД "+str(e))
 
        return (False, False)

Здесь все вполне очевидно. Сначала выбираются поля title и text для статьи, у которой id равен posted. Если метод fetchone возвращает не None, то есть, статья была найдена в БД, то возвращается кортеж из ее названия и текста. Иначе, возвращается кортеж из значений False.

Теперь можно перейти в браузер и набрать что-то вроде:

http://127.0.0.1:5000/post/1

Будет произведена попытка отобразить статью с id равным 1.

Отображение списка статей

Последнее, что мы сделаем на этом занятии – это отобразим список статей на главной странице сайта. Для этого, в ее обработчике запишем следующий код:

@app.route("/")
def index():
    db = get_db()
    dbase = FDataBase(db)
    return render_template('index.html', menu = dbase.getMenu(), posts=dbase.getPostsAnonce())

То есть, мы здесь добавили один параметр posts, который будет ссылаться на список кортежей статей. Сам метод getPostsAnonce, имеет вид:

    def getPostsAnonce(self):
        try:
            self.__cur.execute(f"SELECT id, title, text FROM posts ORDER BY time DESC")
            res = self.__cur.fetchall()
            if res: return res
        except sqlite3.Error as e:
            print("Ошибка получения статьи из БД "+str(e))
 
        return []

Мы здесь выбираем все записи из таблицы posts и сортируем их по новизне: сначала самые свежие, затем, более позние. После этого выбираем все записи с помощью метода fetchall и при успешности этой операции, возвращаем список. Иначе, возвращается пустой список.

Осталось прописать шаблон главной страницы для отображения этого списка. Он выглядит так:

{% extends 'base.html' %}
 
{% block content %}
{{ super() }}
<hr>
<h2>Список статей</h2>
<ul class="list-posts">
{% for p in posts %}
<li>
<p class="title"><a href="{{ url_for('showPost', id_post=p.id)}}">{{p.title}}</a></p>
<p class="annonce">{{ p.text[:50]}}</p>
</li>
{% endfor %}
</ul>
{% endblock %}

Мы здесь в блоке for перебираем список posts и формируем теги li с названием статьи и ее анонсом. Чтобы все это выглядело более-менее красиво, добавим в файл styles.css следующие стили:

ul.list-posts {
         list-style: none;
         margin: 0;
         padding 0;
         max-width: 600px;
}
ul.list-posts li {
         margin: 20px 0 0 0;
         border: 1px solid #eee;
}
ul.list-posts .title {
         margin: 0;
         padding: 5px;
         background: #eee;
}
ul.list-posts .annonce {
         margin: 0;
         padding: 10px 5px 10px 5px;
}

Все, теперь, переходя на главную страницу сайта, увидим что-то вроде следующего:

Вот так в самом простом случае можно записывать данные в таблицы БД и выполнять их считывание.

Видео по теме