На предыдущем
занятии мы рассмотрели общий принцип добавления и считывания публикаций из БД. И
прописали меню непосредственно в шаблоне base.html. Я верну его
считывание из списка:
{% for p in menu %}
<li><a href="{{p.url}}">{{p.title}}</a></li>
{% endfor %}
И здесь у нас
возникала ошибка: при отображении страницы:
http://127.0.0.1:5000/post/1
ссылка «Добавить
статью» становилась следующей:
http://127.0.0.1:5000/post/add_post
Я здесь вначале
сделаю небольшую работу над ошибками и поправлю этот баг при работе с БД.
Исправляется все очень просто: в таблице в поле url вместо «add_post» нужно
прописать «/add_post». После
сохранения изменений и обновления страницы сайта, все стало работать как и
должно быть, ссылка стала:
http://127.0.0.1:5000/
add_post
А теперь о
главном. Все наши прежние статьи были очень простыми: заголовок и обычный
текст. Реальные HTML-страницы содержат гораздо более сложную информацию:
различные теги, ссылки на изображения и прочее. Как их хранить на сервере,
добавлять на сайт и отдавать пользователю по определенному запросу? Вот об этом
и пойдет речь на этом занятии.
В качестве
примера я подготовил страницу
framework-flask-intro.htm
которая содержит
множество различных изображений, расположенных в отдельном каталоге framework-flask-intro.files.
И первый вопрос: где на сервере разместить эти изображения? Мы их расположим в
каталоге static и в нем
создадим специальный подкаталог images_html для хранения изображений наших HTML-страниц. И в
этот подкаталог скопируем каталог framework-flask-intro.files с нашими
изображениями. Почему мы их разместили именно здесь? Об этом чуть позже, а пока
давайте реализуем функционал для добавления самого HTML-документа в БД.
Для начала
заметим, что отображать HTML-страницу по номеру ее id не лучшая
практика, так как поисковые системы лучше ранжируют страницы, у которых URL имеет более
читабельный вид. Например, для добавляемой страницы URL может быть
такой:
домен/post/framework-flask-intro
Как видите, это
более понятный для человека адрес. Так вот, чтобы наше приложение «знало» что
подставлять после /post/, дополнительно в таблицу posts добавим поле url и структура
таблицы будет следующая:
CREATE TABLE IF NOT EXISTS posts (
id integer PRIMARY KEY AUTOINCREMENT,
title text NOT NULL,
text text NOT NULL,
url text NOT NULL,
time integer NOT NULL
);
Обновим нашу БД,
чтобы таблицы posts приняла такой
вид.
Добавление статьи в БД
Далее, в форме
добавления статьи (в add_post.html) пропишем еще
одно поле ввода для url:
<form action="{{url_for('addPost')}}" method="post" class="form-contact">
<p><label>Название статьи: </label> <input type="text" name="name" value="" requied />
<p><label>URL статьи: </label> <input type="text" name="url" value="" requied />
<p><label>Текст статьи:</label>
<p><textarea name="post" rows=7 cols=40></textarea>
<p><input type="submit" value="Добавить" />
</form>
А в
функции-представления addPost в метод dbase.addPost передадим
этот третий параметр с url:
res = dbase.addPost(request.form['name'], request.form['post'], request.form['url'])
Соответственно,
сам метод в классе FDataBase поправим, следующим образом:
def addPost(self, title, text, url):
try:
self.__cur.execute("SELECT COUNT() as `count` FROM posts WHERE url LIKE ?", (url,))
res = self.__cur.fetchone()
if res['count'] > 0:
print("Статья с таким url уже существует")
return False
tm = math.floor(time.time())
self.__cur.execute("INSERT INTO posts VALUES(NULL, ?, ?, ?, ?)", (title, text, url, tm))
self.__db.commit()
except sqlite3.Error as e:
print("Ошибка добавления статьи в БД "+str(e))
return False
return True
Смотрите, мы здесь
вначале проверяем: существует ли такой url в таблице posts, и если
существует, то статья не добавляется, т.к. все статьи должны иметь уникальный url. После этой
проверки происходит добавление записи.
Все статья
добавляется. Давайте, перейдем на страницу добавления и пропишем там заголовок,
url и скопируем HTML-текст в тело
статьи. После нажатия на кнопку добавить, первая статья будет добавлена в БД.
Отображение списка статей
Далее, нам нужно
отобразить список статей. Для начала в методе getPostsAnonce добавим выборку
еще по url:
self.__cur.execute(f"SELECT id, title, text, url FROM posts ORDER BY time DESC")
А в шаблоне index.html, укажем его:
<p class="title"><a href="{{ url_for('showPost', alias=p.url)}}">{{p.title}}</a></p>
Дополнительно,
чтобы не отображать в анонсе теги HTML-документа,
пропишем фильтр striptags:
<p class="annonce">{{ p.text[:50] | striptags }}</p>
Все, теперь при
обновлении главной страницы, увидим нашу добавленную статью с анонсом.
Отображение статьи
Осталось
отобразить статью. Для начала перепишем обработчик showPost, следующим
образом:
@app.route("/post/<alias>")
def showPost(alias):
db = get_db()
dbase = FDataBase(db)
title, post = dbase.getPost(alias)
if not title:
abort(404)
return render_template('post.html', menu=dbase.getMenu(), title=title, post=post)
Мы теперь после post/ указываем
переменную alias, которая может
принимать строковые значения. Затем, в метод getPost передаем alias из URL и по нему уже
отбираем статью:
def getPost(self, alias):
try:
self.__cur.execute("SELECT title, text FROM posts WHERE url LIKE ? LIMIT 1", (alias,))
res = self.__cur.fetchone()
if res:
return res
except sqlite3.Error as e:
print("Ошибка получения статьи из БД "+str(e))
return (False, False)
Если сейчас
попробовать отобразить страницу, то получим также и отображение тегов, т.к. по
умолчанию шаблон преобразовывает наши данные и теги заменяются специальными
символами. Чтобы этого не происходило и теги страницы передавались браузеру в
чистом виде, необходимо в шаблоне post.html указать фильтр safe:
Теперь, обновляя
страницу, увидим разметку тегами. Но изображения не отображаются, т.к. к ним
указан неверный путь. Поправим это.
Прописываем пути к изображениям
В самом простом
варианте мы можем поступить следующим образом. В методе getPost добавить
следующую обработку HTML-страницы:
res = self.__cur.fetchone()
if res:
base = url_for('static', filename='images_html')
text = re.sub(r"(?P<tag><img\s+[^>]*src=)(?P<quote>[\"'])(?P<url>.+?)(?P=quote)>",
"\\g<tag>"+base+"/\\g<url>>",
res['text'])
return (res['title'], text)
Мы здесь с
помощью регулярного выражения в тексте выделяем все URL тегов img и корректируем
их с поправкой на папку static, полученной с помощью функции
base
= url_for('static', filename='images_html')
Чтобы
воспользоваться модулем регулярных выражений и функцией url_for, выполним их
импорт в начале файла:
import re
from flask import url_for
Все, теперь, при
запуске мы увидим изображения в нашем HTML-документе.
Если вы не
знаете как работают регулярные выражения, то смотрите плейлист с занятиями по этой теме.
Модификация HTML-страницы перед ее добавлением в БД
У приведенной
реализации есть один серьезный недостаток: выполнять регулярное выражение для
каждой отдаваемой по запросу страницы – это ресурсоемкая операция, которая
негативно скажется на нагрузке сервера. Лучше это преобразование выполнять при
добавлении поста в БД. Сделаем это. В методе addPost пропишем
следующие строчки:
def addPost(self, title, text, url):
try:
self.__cur.execute("SELECT COUNT() as `count` FROM posts WHERE url LIKE ?", (url,))
res = self.__cur.fetchone()
if res['count'] > 0:
print("Статья с таким url уже существует")
return False
base = url_for('static', filename='images_html')
text = re.sub(r"(?P<tag><img\s+[^>]*src=)(?P<quote>[\"'])(?P<url>.+?)(?P=quote)>",
"\\g<tag>"+base+"/\\g<url>>",
text)
tm = math.floor(time.time())
self.__cur.execute("INSERT INTO posts VALUES(NULL, ?, ?, ?, ?)", (title, text, url, tm))
self.__db.commit()
except sqlite3.Error as e:
print("Ошибка добавления статьи в БД "+str(e))
return False
return True
Смотрите, мы
здесь перед добавлением текста в БД, выполняем регулярное выражение и
корректируем URL-адреса для
изображений. Теперь, наш документ полностью готов для отправки пользователю по
запросу. Соответственно, в методе getPost регулярное
выражением можно просто убрать.
Давайте для
примера добавим еще одну статью и посмотрим как это будет работать.
У этого подхода
есть только один существенный недостаток: если в будущем потребуется изменить
путь хранения изображений, то придется переделывать все HTML-документы. Но,
как правило, это редкая ситуация и в целом страницы сайта можно представлять по
описанной схеме.