На этом занятии
продолжим изучать работу с БД во 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">
<p><label>Название статьи: </label> <input type="text" name="name" value="" requied />
<p><label>Текст статьи:</label>
<p><textarea name="post" rows=7 cols=40></textarea>
<p><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 для физического
сохранения изменений в БД. Также этот метод использует два вспомогательных
модуля:
для вычисления
текущего времени. Все, теперь наша страница готова и выглядит следующим
образом:
При нажатии на
кнопку «Добавить» данные будут переданы обработчику /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;
}
Все, теперь,
переходя на главную страницу сайта, увидим что-то вроде следующего:
Вот так в самом
простом случае можно записывать данные в таблицы БД и выполнять их считывание.