Использование Python-Markdown с Flask и SQLite
Development, Python | Комментировать запись
Flask – это фреймворк для создания веб-приложений на языке Python. Flask позволяет использовать SQLite в качестве движка БД для хранения данных приложения.
Markdown – это язык разметки, который обычно используют для написания контента в удобном для чтения текстовом формате. Markdown позволяет форматировать простой текст, – оформлять заголовки, вставлять ссылки и изображения, – а затем преобразовывать текст в HTML, который сохранить исходное форматирование. Рекомендуем ознакомиться со стандартом синтаксиса Markdown.
Python-Markdown – это библиотека Python, которая позволяет конвертировать текст Markdown в HTML; она в основном отвечает стандарту Markdown (небольшие отличия от стандартного синтаксиса Markdown все же есть).
В этом руководстве мы используем Flask, SQLite и Python-Markdown, чтобы разработать небольшое веб-приложение для создания заметок. Наше тестовое приложение будет поддерживать форматирование текста с помощью Markdown: оно позволит посетителям создавать и форматировать заметки с заголовками, ссылками, списками, изображениями и другими элементами. Чтобы стилизовать приложение, мы используем набор инструментов Bootstrap.
Требования
- Локальная среда разработки Python 3. Следуйте инструкциям для вашего дистрибутива: CentOS, Debian, Ubuntu. В этом руководстве каталог проекта будет называться flask_notes.
- Понимание основных понятий Flask, среди которых создание маршрутов, рендеринг HTML-шаблонов и подключение к базе данных SQLite. Рекомендуем ознакомиться с мануалом Как работает модуль sqlite3 в Python 3.
1: Настройка зависимостей
Прежде всего мы должны включить среду Python и установить Flask и Python-Markdown с помощью менеджера pip. Затем мы создадим базу данных, которую будем использовать для хранения заметок, и добавим в нее немного тестовых данных.
Во-первых, активируйте среду разработки, если вы еще этого не сделали:
source env/bin/activate
Во-вторых, установите Flask и библиотеку Python-Markdown:
pip install flask markdown
А теперь создайте схему базы данных – файл schema.sql. В нем будет храниться SQL-команда для создания таблицы notes. Откройте этот файл в каталоге flask_notes:
nano schema.sql
Введите в файл flask_notes/schema.sql следующие команды:
DROP TABLE IF EXISTS notes;
CREATE TABLE notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
content TEXT NOT NULL
);
Сначала этот файл удалит любую уже существующую таблицу по имени notes, которая может вызвать проблемы – благодаря этому шагу наша таблица notes будет в точности такой, как описано в этом файле. Обратите внимание, что всякий раз, когда вы используете эти команды SQL, все ваше содержимое, которое хранится в БД, будет удалено. Не вносите в БД веб-приложения никаких важных данных, пока не закончите это руководство и не будете уверены в надежности результата.
Оператор CREATE TABLE notes создает таблицу notes со следующими столбцами:
- id: целое число, представляющее первичный ключ; база данных автоматически присвоит этому столбцу уникальное значение для каждой записи.
- created: дата создания заметки; сюда будет автоматически внесено время, когда заметка была добавлена в БД.
- content: содержание заметки.
Сохраните и закройте файл.
Чтобы создать базу данных с помощью файла schema.sql, сначала откройте файл по имени init_db.py внутри каталога flask_notes:
nano init_db.py
Затем добавьте в файл flask_notes/init_db.py следующий код:
import sqlite3
connection = sqlite3.connect('database.db')
with open('schema.sql') as f:
connection.executescript(f.read())
cur = connection.cursor()
cur.execute("INSERT INTO notes (content) VALUES (?)", ('# The First Note',))
cur.execute("INSERT INTO notes (content) VALUES (?)", ('_Another note_',))
cur.execute("INSERT INTO notes (content) VALUES (?)", ('Visit [this page](https://www.8host.com/blog/) for more tutorials.',))
connection.commit()
connection.close()
Здесь мы импортируем модуль sqlite3, а после этого подключаемся к файлу database.db, который будет создан после выполнения этой программы. Файл database.db – это база данных, в которой будут храниться все данные приложения. После этого мы открываем файл schema.sql и запускаем его с помощью метода executescript(), который выполняет несколько SQL операторов одновременно. Это создаст таблицу notes.
При помощи объекта Cursor мы выполняем несколько SQL-операторов INSERT для создания трех заметок. Здесь используется синтаксис Markdown: первая заметка – это заголовок <h1>, вторая заметка выделена курсивом, а третья содержит ссылку. Для безопасной вставки данных в БД мы используем заполнитель «?» в методе execute() и передаем кортеж, в котором находится содержимое заметки. Наконец, мы фиксируем изменения и закрываем соединение.
Сохраните и закройте файл.
Давайте запустим нашу программу:
python init_db.py
После ее выполнения в каталоге flask_notes появится новый файл по имени database.db.
Итак, вы активировали среду разработки, установили Flask и Python-Markdown и создали базу данных SQLite. Далее вы узнаете, как извлечь заметки Markdown из базы данных, конвертировать их в HTML и отобразить на главной странице приложения.
2: Отображение заметок
На этом этапе мы создадим приложение Flask, которое сможет подключаться к БД и отображать те данные, которые мы туда поместили. Также мы преобразуем текст Markdown, находящийся в базе данных, в HTML, а затем отобразим его на странице приложения.
Сначала создайте в каталоге flask_notes файл приложения app.py:
nano app.py
Добавьте туда следующий код:
import sqlite3
import markdown
from flask import Flask, render_template, request, flash, redirect, url_for
def get_db_connection():
conn = sqlite3.connect('database.db')
conn.row_factory = sqlite3.Row
return conn
Сначала мы импортируем модуль sqlite3, пакет markdown и вспомогательные пакеты Flask.
Функция get_db_connection() открывает соединение с файлом БД, database.db, а затем устанавливает значение sqlite3.Row для атрибута row_factory. Это открывает нам доступ к столбцам по имени (то есть соединение с БД будет возвращать строки, которые ведут себя как обычные словари Python). Затем функция возвращает объект conn, который мы будем использовать для доступа к базе данных.
После этого добавьте следующий фрагмент кода:
#. . .
app = Flask(__name__)
app.config['SECRET_KEY'] = 'this should be a secret random string'
Здесь мы создаем объект приложения Flask и устанавливаем секретный ключ для защиты сеансов.
Затем добавьте в файл следующий код:
#. . .
@app.route('/')
def index():
conn = get_db_connection()
db_notes = conn.execute('SELECT id, created, content FROM notes;').fetchall()
conn.close()
notes = []
for note in db_notes:
note = dict(note)
note['content'] = markdown.markdown(note['content'])
notes.append(note)
return render_template('index.html', notes=notes)
index() – это функция просмотра Flask, оформленная с помощью специального декоратора @app.route. Flask преобразует возвращаемое значение этой функции в HTTP-ответ, который будет отображать HTTP-клиент (например, ваш веб-браузер).
В функции index() мы открываем соединение с базой данных и выполняем SQL-оператор SELECT для получения ID, даты создания и содержимого всех строк таблицы notes. Метод fetchall() позволяет получить список всех строк и сохранить эти данные в переменной db_notes. Затем соединение закрывается.
Чтобы преобразовать содержимое заметок из Markdown в HTML, мы создаем новый пустой список по имени notes. Список db_notes просматривается и каждая заметка конвертируется из sqlite3.Row в обычный словарь Python при помощи функции dict(). Функция markdown.markdown() нужна для преобразования значения note[‘content’] в HTML. Например, при вызове markdown.markdown(‘#Hi’) мы получим строку ‘<h1>Hi</h1>’, потому что в Markdown символ # представляет заголовок <h1>. После изменения note[‘content’] она добавляется в список notes.
В конце файл шаблона index.html получает список notes и визуализируется.
После внесения всех изменений ваш файл flask_notes/app.py будет выглядеть так:
import sqlite3
import markdown
from flask import Flask, render_template, request, flash, redirect, url_for
def get_db_connection():
conn = sqlite3.connect('database.db')
conn.row_factory = sqlite3.Row
return conn
app = Flask(__name__)
app.config['SECRET_KEY'] = 'this should be a secret random string'
@app.route('/')
def index():
conn = get_db_connection()
db_notes = conn.execute('SELECT id, created, content FROM notes;').fetchall()
conn.close()
notes = []
for note in db_notes:
note = dict(note)
note['content'] = markdown.markdown(note['content'])
notes.append(note)
return render_template('index.html', notes=notes)
Сохраните и закройте файл.
Затем создайте базовый шаблон и файл шаблона index.html. В каталоге flask_notes создайте каталог templates и откройте в нем файл по имени base.html:
mkdir templates
nano templates/base.html
Добавьте в файл flask_notes/templates/base.html следующий код; обратите внимание, что здесь используется Bootstrap.
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<title>{% block title %} {% endblock %}</title>
</head>
<body>
<nav class="navbar navbar-expand-md navbar-light bg-light">
<a class="navbar-brand" href="{{ url_for('index')}}">FlaskNotes</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="#">About</a>
</li>
</ul>
</div>
</nav>
<div class="container">
{% for message in get_flashed_messages() %}
<div class="alert alert-danger">{{ message }}</div>
{% endfor %}
{% block content %} {% endblock %}
</div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
</body>
</html>
Большая часть кода в предыдущем блоке – это стандартный HTML и код Bootstrap. Теги <meta> содержат информацию для веб-браузера, тег <link> связывает CSS-файлы Bootstrap, а теги <script> – это ссылки на код JavaScript, который позволяет использовать некоторые дополнительные функции Bootstrap.
Тег <title>{% block title %} {% endblock %}</title> позволяет наследующим шаблонам определять собственный заголовок. Цикл for message in get_flashed_messages() используется для отображения срочных сообщений, например предупреждений. Заполнитель {% block content %} {% endblock %} – это место, куда наследующие шаблоны помещают контент; таким образом, чтобы избежать повторения, все шаблоны получают дополнительный код, предоставляемый этим базовым шаблоном.
Сохраните и закройте файл.
Затем создайте файл index.html, который расширит base.html:
nano templates/index.html
И поместите в flask_notes/templates/index.html такие строки:
{% extends 'base.html' %}
{% block content %}
<h1>{% block title %} Welcome to FlaskNotes {% endblock %}</h1>
{% for note in notes %}
<div class="card">
<div class="card-body">
<span class="badge badge-primary">#{{ note['id'] }}</span>
<span class="badge badge-default">{{ note['created'] }}</span>
<hr>
<p>{{ note['content'] |safe }}</p>
</div>
</div>
<hr>
{% endfor %}
{% endblock %}
Как сказано выше, этот файл расширяет предыдущий, base.html, он определяет заголовок и использует цикл for для перебора заметок, отображая каждую заметку в карточке Bootstrap. Здесь отображается идентификатор, дата создания и содержание заметки, которую вы преобразовали в HTML с помощью функции index().
Мы используем фильтр Jinja |safe, который применяем к контенту с помощью символа |; это похоже на вызов функции в Python (аналогично работает safe(note[‘content’])). Фильтр |safe позволяет браузеру отображать HTML-код – без этого фильтра браузер будет отображать HTML как обычный текст. Как правило, такое называется «HTML escaping», это функция безопасности, которая предотвращает интерпретацию в браузере вредоносного HTML-кода, что может привести к межсайтовому скриптингу (XSS). Поскольку библиотека Python-Markdown возвращает безопасный HTML, вы можете разрешить браузеру отображать его – это и делает фильтр |safe. Не используйте этот фильтр, пока не убедитесь, что HTML-код безопасен и надежен.
Для получения дополнительной информации прочтите документацию Flask по управлению функцией autoescaping.
Сохраните и закройте файл.
Задайте необходимые Flask переменные среды и запустите приложение, используя следующие команды:
export FLASK_APP=app
export FLASK_ENV=development
flask run
Переменная среды FLASK_APP указывает приложение, которое нужно запустить (у нас это файл app.py). Переменная среды FLASK_ENV определяет режим; development значит, что приложение будет работать в режиме разработки с запущенным отладчиком (в производственной среде этот режим лучше не использовать). Команда flask run запускает приложение.
Откройте браузер и введите URL-адрес http://127.0.0.1:5000/.
В браузере вы увидите, что каждая заметка отформатирована и отображается как HTML, а не как обычный текст.
Итак, вы создали приложение Flask, которое подключается к базе данных, извлекает заметки, преобразует их содержимое из Markdown в HTML, а затем отображает их на странице. Далее мы расскажем, как добавить маршрут, позволяющий пользователям создавать новые заметки, которые они смогут писать при помощи синтаксиса Markdown.
3: Добавление новых заметок
На этом этапе мы добавим новый маршрут, который позволит пользователям создавать новые заметки. Для написания своих заметок они смогут использовать синтаксис Markdown –приложение сохранит заметки в базе данных, а затем отобразит их на странице с соответствующим форматированием. Кроме того, мы добавим на панель навигации кнопку, которая переведет пользователей на эту новую страницу.
Чтобы позволить пользователям отправлять данные в наше приложение Flask, мы будем использовать веб-формы. А отправленные пользователями данные будут храниться в нашей БД.
Для начала откройте файл app.py, чтобы добавить новый маршрут:
nano app.py
Затем вставьте в конец файла следующий код:
#. . .
@app.route('/create/', methods=('GET', 'POST'))
def create():
conn = get_db_connection()
if request.method == 'POST':
content = request.form['content']
if not content:
flash('Content is required!')
return redirect(url_for('index'))
conn.execute('INSERT INTO notes (content) VALUES (?)', (content,))
conn.commit()
conn.close()
return redirect(url_for('index'))
return render_template('create.html')
Поскольку этот маршрут мы будем использовать для вставки новых данных в БД через веб-форму, мы должны разрешить запросы GET и POST, добавив methods=(‘GET’, ‘POST’) в декоратор app.route(). В функции create() мы открываем соединение с базой данных.
Если пользователь отправил форму, для которой условие request.method == ‘POST’ истинно, мы извлекаем содержимое заметки, отправленное с помощью request.form [‘content’], и сохраняем его в переменной content. Если содержимое пусто, мы получаем срочное сообщение ‘Content is required!’ и перенаправляем пользователя на индексную страницу. Если содержимое не было пустым, мы используем SQL-оператор INSERT, чтобы добавить заметку в таблицу notes. Затем изменения фиксируются, а соединение закрывается, после чего пользователь перенаправляется на индексную страницу.
Если отправлен GET запрос (что означает, что пользователь только что посетил страницу), мы визуализируем файл шаблона create.html.
Сохраните и закройте файл.
Затем откройте файл create.html:
nano templates/create.html
И добавьте к его текущему содержимому следующий код:
{% extends 'base.html' %}
{% block content %}
<h1>{% block title %} Add a New Note {% endblock %}</h1>
<form method="post">
<div class="form-group">
<label for="content">Note Content</label>
<textarea type="text" name="content"
placeholder="Note content, you can use Markdown syntax" class="form-control"
value="{{ request.form['content'] }}" autofocus></textarea>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
{% endblock %}
Это создаст форму с текстовым полем для заметки. request.form дает доступ к данным формы, которые хранятся на случай, если что-то пойдет не так в процессе ее отправки (например, если пользователь не добавил контент). Также это создаст под текстовым полем кнопку отправки, которую пользователь должен нажимать для отправки данных в приложение в POST-запросе.
Сохраните и закройте файл.
Затем откройте файл base.html, чтобы добавить кнопку New Note в панель навигации:
nano templates/base.html
Вставьте в файл выделенный код:
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<title>{% block title %} {% endblock %}</title>
</head>
<body>
<nav class="navbar navbar-expand-md navbar-light bg-light">
<a class="navbar-brand" href="{{ url_for('index')}}">FlaskNotes</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="#">About</a>
</li>
<li class="nav-item active">
<a class="nav-link" href="{{ url_for('create') }}">New Note</a>
</li>
</ul>
</div>
</nav>
<div class="container">
{% for message in get_flashed_messages() %}
<div class="alert alert-danger">{{ message }}</div>
{% endfor %}
{% block content %} {% endblock %}
</div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
</body>
</html>
Мы добавляем новый элемент <li> в панель навигации с помощью функции url_for(), которая ссылается на функцию create(). При этом вы можете получить доступ к странице создания новой заметки из панели навигации.
Запустите сервер разработки, если вы еще этого не сделали:
flask run
Откройте браузер, перейдите по адресу http://127.0.0.1:5000/create и добавьте следующую заметку markdown:
### Flask
Flask is a **web** framework for _Python_.
Here is the Flask logo:

Этот markdown содержит заголовок h3, слово web, выделенное жирным шрифтом, слово Python, выделенное курсивом, и изображение.
Отправьте заметку. Вы обнаружите, что ваше приложение форматирует ее в HTML.
Итак, у вас есть новый маршрут, который позволяет пользователям добавлять в базу данных новые заметки. Пользователи могут писать заметки в формате Markdown, и приложение будет автоматически преобразовывать их в HTML.
Примечание: Полный код приложения можно найти здесь.
Заключение
Вы разработали простое приложение Flask для создания заметок в формате Markdown, чтобы пользователи могли форматировать текст: вставлять заголовки, использовать полужирный и курсивный текст, а также добавлять изображения и ссылки. Кроме того, вы подключили свое приложение к SQLite для хранения и извлечения данных. Приложение преобразовывает текст Markdown в HTML, чтобы заметки отображались на странице.
Если вам нужна дополнительная информация о Flask, ознакомьтесь со следующими руководствами:
- Обслуживание приложений Flask с помощью uWSGI и Nginx в Ubuntu 20.04
- Базовое развертывание приложения Flask с помощью Kubernetes и Docker