Использование Python-Markdown с Flask и SQLite

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:
![Flask Logo](https://flask.palletsprojects.com/en/1.1.x/_images/flask-logo.png)

Этот markdown содержит заголовок h3, слово web, выделенное жирным шрифтом, слово Python, выделенное курсивом, и изображение.

Отправьте заметку. Вы обнаружите, что ваше приложение форматирует ее в HTML.

Итак, у вас есть новый маршрут, который позволяет пользователям добавлять в базу данных новые заметки. Пользователи могут писать заметки в формате Markdown, и приложение будет автоматически преобразовывать их в HTML.

Примечание: Полный код приложения можно найти здесь.

Заключение

Вы разработали простое приложение Flask для создания заметок в формате Markdown, чтобы пользователи могли форматировать текст: вставлять заголовки, использовать полужирный и курсивный текст, а также добавлять изображения и ссылки. Кроме того, вы подключили свое приложение к SQLite для хранения и извлечения данных. Приложение преобразовывает текст Markdown в HTML, чтобы заметки отображались на странице.

Если вам нужна дополнительная информация о Flask, ознакомьтесь со следующими руководствами:

Tags: , , , , ,

Добавить комментарий