Создание веб-приложения на Python с использованием фреймворка Flask
С помощью этого руководства вы с нуля разработаете простое веб-приложение — мини-блог, в котором пользователь сможет создавать, просматривать, редактировать и удалять посты. Для этого в Yandex Cloud вы создадите виртуальную машину Yandex Compute Cloud и запустите на ней веб-сервер.
Веб-приложение написано на языке Python
В качестве основы приложения выбран фреймворк Flask
Фреймворк Flask позволяет создавать веб-приложения с использованием всего одного файла с исходным кодом, не требует соблюдения какой-то определенной структуры директорий или написания сложного шаблонного кода перед началом использования. Также Flask предоставляет набор инструментов Werkzeug
Исходный код приложения доступен в файле flask_blog.zip
Схема работы создаваемого веб-приложения:
- HTTP-запросы поступают из браузера пользователя на виртуальную машину под управлением ОС Linux Ubuntu с установленным и запущенным веб-сервером Flask.
- Веб-сервер передает запрос в веб-приложение, маршрутизатор которого в зависимости от URL запроса вызывает подходящую функцию-обработчик.
- Функция-обработчик выполняет запрос к базе данных SQLite, чтобы получить или записать необходимые данные.
- Полученные из БД данные функция передает в подходящий HTML-шаблон Jinja, который возвращает готовый HTML-код страницы.
- Полученный HTML-код передается на веб-сервер, который в свою очередь передает этот код в браузер пользователя.
Чтобы создать веб-приложение с помощью Flask:
- Подготовьте облако к работе.
- Создайте и настройте виртуальную машину.
- Создайте и запустите приложение.
- Создайте и настройте шаблоны HTML.
- Настройте базу данных.
- Настройте отображение постов блога.
- Добавьте действия с постами.
- Подведите итоги.
Если веб-приложение вам больше не нужно, удалите все используемые им ресурсы.
Перед началом работы
В Yandex Cloud оплачиваются только потребленные ресурсы и время их фактического использования, а для идентификации пользователя, оплачивающего такие ресурсы, нужен платежный аккаунт.
В стоимость поддержки создаваемого приложения входят:
- плата за использование публичного IP-адреса (тарифы Yandex Virtual Private Cloud);
- плата за вычислительные ресурсы и диски ВМ (тарифы Yandex Compute Cloud).
Зарегистрируйтесь в Yandex Cloud и создайте платежный аккаунт:
- Перейдите в консоль управления
, затем войдите в Yandex Cloud или зарегистрируйтесь. - На странице Yandex Cloud Billing
убедитесь, что у вас подключен платежный аккаунт, и он находится в статусеACTIVEилиTRIAL_ACTIVE. Если платежного аккаунта нет, создайте его и привяжите к нему облако.
Если у вас есть активный платежный аккаунт, вы можете создать или выбрать каталог, в котором будет работать ваша инфраструктура, на странице облака
Подробнее об облаках и каталогах.
Создайте облачную сеть и подсеть
В Yandex Cloud ресурсы связаны между собой и с интернетом при помощи облачных сетей, в которых ресурсам выделяются публичные IP-адреса, а также диапазоны внутренних IP-адресов или подсети.
Чтобы создать виртуальную сеть и подсеть для вашего веб-сервера:
- В консоли управления
выберите ваш каталог. - Перейдите
в сервис Virtual Private Cloud. - Нажмите кнопку Создать сеть.
- В поле Имя укажите
webserver-network. - В поле Дополнительно отключите опцию Создать подсети.
- Нажмите кнопку Создать сеть.
- На панели слева выберите Подсети.
- Нажмите кнопку Создать.
- В поле Имя укажите
webserver-subnet-ru-central1-b. - В поле Зона доступности выберите зону доступности
ru-central1-b. - В поле Сеть выберите облачную сеть
webserver-network. - В поле CIDR укажите
192.168.1.0/24. - Нажмите кнопку Создать подсеть.
-
Создайте сеть
webserver-network:yc vpc network create webserver-networkРезультат:
id: enp1gg8kr3pv******** folder_id: b1gt6g8ht345******** created_at: "2023-12-20T20:08:11Z" name: webserver-network default_security_group_id: enppne4l2eg5********Подробнее о команде
yc vpc network createчитайте в справочнике CLI. -
Создайте подсеть в зоне доступности
ru-central1-b:yc vpc subnet create webserver-subnet-ru-central1-b \ --zone ru-central1-b \ --network-name webserver-network \ --range 192.168.1.0/24Результат:
id: e2li9tcgi7ii******** folder_id: b1gt6g8ht345******** created_at: "2023-12-20T20:11:16Z" name: webserver-subnet-ru-central1-b network_id: enp1gg8kr3pv******** zone_id: ru-central1-b v4_cidr_blocks: - 192.168.1.0/24Подробнее о команде
yc vpc subnet createчитайте в справочнике CLI.
-
Чтобы создать сеть, воспользуйтесь методом REST API create для ресурса Network или вызовом gRPC API NetworkService/Create.
-
Чтобы создать подсеть, воспользуйтесь методом REST API create для ресурса Subnet или вызовом gRPC API SubnetService/Create.
Создайте группу безопасности
Группы безопасности служат основным механизмом разграничения сетевого доступа в Yandex Cloud и помогают ограничить для облачных ресурсов несанкционированный трафик на уровне облачной сети.
Создайте группу безопасности, разрешающую входящий TCP-трафик для портов 5000 и 22, а также любой исходящий трафик. Порт 22 необходим для подключения к ВМ по SSH, порт 5000 по умолчанию используется для работы веб-сервера Flask.
-
В консоли управления
выберите ваш каталог. -
Перейдите
в сервис Virtual Private Cloud. -
На панели слева выберите Группы безопасности.
-
Нажмите кнопку Создать группу безопасности.
-
В поле Имя укажите имя
webserver-sg. -
В поле Сеть выберите созданную ранее сеть
webserver-network. -
В блоке Правила создайте следующие правила для управления трафиком:
Направление
трафикаДиапазон портов Протокол Источник /
НазначениеIPv4 CIDR Описание Исходящий ВесьЛюбойДиапазон адресов0.0.0.0/0anyВходящий 5000TCPДиапазон адресов0.0.0.0/0FlaskВходящий 22TCPДиапазон адресов0.0.0.0/0ssh -
Нажмите кнопку Сохранить.
Выполните команду:
yc vpc security-group create \
--name webserver-sg \
--rule "description=Flask,direction=ingress,port=5000,protocol=tcp,v4-cidrs=[0.0.0.0/0]" \
--rule "description=ssh,direction=ingress,port=22,protocol=tcp,v4-cidrs=[0.0.0.0/0]" \
--rule "description=any,direction=egress,port=any,protocol=any,v4-cidrs=[0.0.0.0/0]" \
--network-name webserver-network
Результат:
id: enpv1c0q7j01********
folder_id: b1gt6g8ht345********
created_at: "2024-03-23T18:54:05Z"
name: webserver-sg
network_id: enp9mji1m7b3********
status: ACTIVE
rules:
- id: enpmbsk2hjfd********
description: Flask
direction: INGRESS
ports:
from_port: "5000"
to_port: "5000"
protocol_name: TCP
protocol_number: "6"
cidr_blocks:
v4_cidr_blocks:
- 0.0.0.0/0
- id: enpna5id9265********
description: ssh
direction: INGRESS
ports:
from_port: "22"
to_port: "22"
protocol_name: TCP
protocol_number: "6"
cidr_blocks:
v4_cidr_blocks:
- 0.0.0.0/0
- id: enpen3vf7rui********
description: any
direction: EGRESS
protocol_name: ANY
protocol_number: "-1"
cidr_blocks:
v4_cidr_blocks:
- 0.0.0.0/0
Сохраните полученный идентификатор (id) группы безопасности: он потребуется при создании виртуальной машины.
Подробнее о команде yc vpc security-group create читайте в справочнике CLI.
Чтобы создать группу безопасности, воспользуйтесь методом REST API create для ресурса SecurityGroup или вызовом gRPC API SecurityGroupService/Create.
Создайте и настройте виртуальную машину
Виртуальная машина — это аналог сервера в облачной инфраструктуре. В Yandex Cloud вы можете создавать
виртуальные машины с самыми разными аппаратными характеристиками по производительности, объему оперативной памяти и дискового пространства, а также под управлением разных операционных систем.
Создаваемое веб-приложение будет развернуто на виртуальной машине под управлением ОС Linux Ubuntu 22.04 LTS.
-
Создайте виртуальную машину:
Перед тем как приступать к созданию виртуальной машины, подготовьте пару ключей (открытый и закрытый) для доступа к ВМ по SSH.
Консоль управленияCLIAPI-
В консоли управления
выберите каталог, в котором будет создана ВМ. -
Перейдите
в сервис Compute Cloud. -
На панели слева выберите
Виртуальные машины. -
Нажмите кнопку Создать виртуальную машину.
-
В блоке Образ загрузочного диска выберите образ Ubuntu 22.04 LTS.
-
В блоке Расположение выберите зону доступности
ru-central1-b. -
В блоке Сетевые настройки:
- В поле Подсеть выберите созданную ранее подсеть
webserver-subnet-ru-central1-b. - В поле Публичный IP-адрес выберите
Автоматически. - В поле Группы безопасности выберите созданную ранее группу безопасности
webserver-sg.
- В поле Подсеть выберите созданную ранее подсеть
-
В блоке Доступ выберите SSH-ключ и укажите данные для доступа к ВМ:
- В поле Логин укажите имя пользователя
yc-user. -
В поле SSH-ключ выберите SSH-ключ, сохраненный в вашем профиле пользователя организации.
Если в вашем профиле нет сохраненных SSH-ключей или вы хотите добавить новый ключ:
-
Нажмите кнопку Добавить ключ.
-
Задайте имя SSH-ключа.
-
Выберите вариант:
-
Ввести вручную— вставьте содержимое открытого SSH-ключа. Пару SSH-ключей необходимо создать самостоятельно. -
Загрузить из файла— загрузите открытую часть SSH-ключа. Пару SSH-ключей необходимо создать самостоятельно. -
Сгенерировать ключ— автоматическое создание пары SSH-ключей.При добавлении сгенерированного SSH-ключа будет создан и загружен архив с парой ключей. В ОС на базе Linux или macOS распакуйте архив в папку
/home/<имя_пользователя>/.ssh. В ОС Windows распакуйте архив в папкуC:\Users\<имя_пользователя>/.ssh. Дополнительно вводить открытый ключ в консоли управления не требуется.
-
-
Нажмите кнопку Добавить.
SSH-ключ будет добавлен в ваш профиль пользователя организации. Если в организации отключена возможность добавления пользователями SSH-ключей в свои профили, добавленный открытый SSH-ключ будет сохранен только в профиле пользователя внутри создаваемого ресурса.
-
- В поле Логин укажите имя пользователя
-
В блоке Общая информация задайте имя ВМ:
mywebserver. -
Нажмите кнопку Создать ВМ.
Выполните команду, указав сохраненный на предыдущем шаге идентификатор группы безопасности:
yc compute instance create \ --name mywebserver \ --zone ru-central1-b \ --network-interface subnet-name=webserver-subnet-ru-central1-b,nat-ip-version=ipv4,security-group-ids=<идентификатор_группы_безопасности> \ --create-boot-disk image-folder-id=standard-images,image-id=fd8ne6e3etbrr2ve9nlc \ --ssh-key <файл_открытого_SSH-ключа>Где
--ssh-key— путь к файлу с открытым SSH-ключом. Например,~/.ssh/id_ed25519.pub.Результат:
done (32s) id: epdv79cu67np******** folder_id: b1gt6g8ht345******** created_at: "2024-03-23T19:17:09Z" name: mywebserver zone_id: ru-central1-b platform_id: standard-v2 resources: memory: "2147483648" cores: "2" core_fraction: "100" status: RUNNING metadata_options: gce_http_endpoint: ENABLED aws_v1_http_endpoint: ENABLED gce_http_token: ENABLED aws_v1_http_token: DISABLED boot_disk: mode: READ_WRITE device_name: epdg0926k12t******** auto_delete: true disk_id: epdg0926k12t******** network_interfaces: - index: "0" mac_address: d0:0d:1f:3a:59:e3 subnet_id: e2l3qffk0h6t******** primary_v4_address: address: 192.168.1.14 one_to_one_nat: address: 62.84.***.*** ip_version: IPV4 security_group_ids: - enpv1c0q7j01******** serial_port_settings: ssh_authorization: INSTANCE_METADATA gpu_settings: {} fqdn: epdv79cu67np********.auto.internal scheduling_policy: {} network_settings: type: STANDARD placement_policy: {}Подробнее о команде
yc compute instance createчитайте в справочнике CLI.Чтобы создать виртуальную машину, воспользуйтесь методом REST API create для ресурса Instance или вызовом gRPC API InstanceService/Create.
В вашем каталоге будет создана виртуальная машина
mywebserver. Чтобы подключиться к ней по SSH, используйте логинyc-userи публичный IP-адрес этой ВМ. Если вы планируете пользоваться созданным веб-сервером длительное время, сделайте публичный IP-адрес этой ВМ статическим. -
-
Создайте и активируйте виртуальное окружение.
Виртуальное окружение обеспечивает изолированное пространство для проектов Python на сервере. Все необходимые зависимости — исполняемые файлы, библиотеки и прочие файлы — копируются в выбранный каталог, и приложение использует их, а не установленные в системе. Это позволяет обеспечить стабильность среды разработки и чистоту основной системы.
Для создания виртуального окружения используйте модуль библиотеки Python 3
venv:-
Подключитесь к виртуальной машине
mywebserver. -
В директории текущего пользователя создайте поддиректорию проекта
flask_blog, в которой будет находиться приложение, и перейдите в нее:mkdir flask_blog && cd flask_blog -
Обновите список пакетов в репозиториях apt:
sudo apt-get update -
Установите модуль создания виртуальных сред
venv:sudo apt install python3.10-venv -
Создайте виртуальное окружение
env:python3 -m venv env -
Активируйте созданное виртуальное окружение:
source env/bin/activateПосле активации виртуального окружения в командной строке появится префикс с именем окружения:
(env) yc-user@ubuntu:~/flask_blog$Примечание
Чтобы эффективно отслеживать процесс разработки проекта и управлять им, вы можете использовать систему контроля версий. В этом случае добавьте директорию
envв файл.gitignore, чтобы не отслеживать файлы, не связанные с проектом.Чтобы деактивировать виртуальное окружение, выполните команду:
deactivate
-
-
Установите Flask:
pip install flaskРезультат:
Successfully installed Jinja2-3.1.3 MarkupSafe-2.1.5 Werkzeug-3.0.1 blinker-1.7.0 click-8.1.7 flask-3.0.2 itsdangerous-2.1.2
Создайте и запустите приложение
Создайте простое веб-приложение внутри файла Python и запустите его для начала работы сервера.
-
В директории проекта
flask_blogсоздайте и откройте файлapp.py:nano app.pyЭтот файл позволяет понять, как приложение будет обрабатывать HTTP-запросы.
-
Добавьте в файл следующий код:
from flask import Flask app = Flask(__name__) @app.route('/') def hello(): return 'Hello, World!'В этом коде:
- Импортируется объект
Flask из пакетаflask. - Создается экземпляр приложения Flask с именем
app. Специальная переменная__name__содержит имя текущего модуля Python и указывает экземпляру его расположение. Это необходимо, так как Flask устанавливает ряд путей внутри приложения. - С помощью декоратора
@app.route('/')функция Python превращается в функцию визуализации Flask. Функция визуализации конвертирует возвращаемое значение в HTTP-ответ, который может быть обработан HTTP-клиентом, таким как веб-браузер. Значение'/'в маршруте@app.route()устанавливает, что эта функция будет отвечать на веб-запросы для URL/, который является основным URL-адресом веб-приложения. - Создается функция
hello(), которая возвращает строкуHello, World!в качестве ответа.
Сохраните и закройте файл
app.py. - Импортируется объект
-
Задайте переменные окружения
Flask:export FLASK_APP=app && export FLASK_DEBUG=trueГде:
FLASK_APP=appуказывает на расположение приложения — файлаapp.py.FLASK_DEBUG=trueуказывает, что приложение необходимо запустить в режиме разработки.
-
Запустите приложение:
flask run \ --host=0.0.0.0Где параметр
--host=0.0.0.0запускает сервер приложения на всех IP-адресах виртуальной машины. Не указав этот параметр, вы не сможете обратиться к приложению по публичному IP-адресу ВМ.Результат:
* Serving Flask app 'app' * Debug mode: on WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Running on all addresses (0.0.0.0) * Running on http://127.0.0.1:5000 * Running on http://192.168.1.14:5000 Press CTRL+C to quit * Restarting with stat * Debugger is active! * Debugger PIN: 884-371-***Для нас важна следующая информация:
- Название работающего приложения —
app. Debug mode: onозначает, что отладчик Flask работает. Эта функция полезна при разработке, так как при возникновении проблем она выдает детализированные сообщения об ошибках, что упрощает работу по их устранению.- Приложение работает на всех адресах.
Вы можете изменить порт веб-сервера на любое другое значение, задав дополнительный параметр
--port=<номер_порта>. Например, командаflask run --host=0.0.0.0 --port=5001запустит веб-сервер Flask на порте5001. - Название работающего приложения —
-
Откройте браузер и введите в адресной строке
http://<публичный_IP-адрес_ВМ>:5000/.Браузер отобразит строку
Hello, World!. Это подтверждает, что ваше приложение успешно работает и к нему можно обращаться из интернета.
Важно
Flask использует простой веб-сервер для обслуживания приложения в среде разработки. Он предназначен для тестирования и обнаружения ошибок и не должен использоваться при развертывании в производственной среде. Подробнее в документации Flask
Оставьте веб-сервер работать в текущем окне и откройте новое окно терминала. В новом окне терминала вы будете выполнять дальнейшую настройку приложения. Это позволит проверять работу внесенных в приложение изменений без необходимости постоянно останавливать и вновь запускать веб-сервер.
При открытии нового окна терминала:
-
Подключитесь к ВМ.
-
Перейдите в директорию проекта
flask_blog, активируйте виртуальное окружение и задайте переменные окружения:cd flask_blog && source env/bin/activate && export FLASK_APP=app && export FLASK_DEBUG=true
Создайте и настройте шаблоны HTML
В текущем виде приложение отображает в браузере только простое текстовое сообщение. Добавьте в приложение файлы-шаблоны HTML, которые позволят отображать нужные пользователю данные и элементы управления.
Использовать в приложении шаблоны позволяет вспомогательная функция render_template()
Создайте HTML-шаблон главной страницы
-
Откройте файл
app.py:nano app.py -
Замените содержимое файла следующим кодом:
from flask import Flask, render_template app = Flask(__name__) @app.route('/') def index(): return render_template('index.html')В этом коде:
- Дополнительно импортируется вспомогательная функция
render_template(), которая позволяет визуализировать шаблоны HTML. - Функция
hello()заменяется на функциюindex(), которая возвращает результат вызова вспомогательной функцииrender_template()с аргументомindex.html. Аргумент функции указывает на файл шаблона, который должен быть расположен в директории шаблонов и который будет использован для визуализации.
Примечание
Директория шаблонов и файл
index.htmlеще не созданы. Вы получите сообщение об ошибке, если запустите приложение на этом этапе.Сохраните и закройте файл
app.py. - Дополнительно импортируется вспомогательная функция
-
В директории проекта
flask_blogсоздайте поддиректориюtemplates:mkdir templates -
Создайте и откройте файл-шаблон
index.htmlв директорииtemplates:nano templates/index.html -
Добавьте в файл
index.htmlследующий код:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>FlaskBlog</title> </head> <body> <h1>Welcome to FlaskBlog</h1> </body> </html>В этом HTML-коде описана простейшая страница с заголовком
FlaskBlogи содержимымWelcome to FlaskBlogв заголовке первого уровня.Сохраните и закройте файл
index.html. -
Обновите главную страницу приложения
http://<публичный_IP-адрес_ВМ>:5000/.Результат:
Welcome to FlaskBlog. -
Кроме директории
templatesв веб-приложениях Flask обычно также существует директорияstatic, в которой сохраняются статичные файлы, такие как стили CSS, скрипты JavaScript или изображения, используемые приложением.В директории проекта
flask_blogсоздайте поддиректориюstatic:mkdir static -
Во вновь созданной директории
staticсоздайте поддиректориюcss:mkdir static/cssПримечание
Вложенные директории используются для упорядочивания статичных файлов в специальных папках. Например, файлы JavaScript обычно хранятся в директории
js, изображения — в директорииimages(илиimg) и т.д. -
Создайте и откройте файл
style.cssв директорииstatic/css:nano static/css/style.css -
Добавьте в файл
style.cssследующий код:h1 { border: 2px #eee solid; color: #fc3d17; text-align: center; padding: 10px; }Этот CSS-код изменяет оформление текста внутри HTML-тегов
<h1>:- свойство
borderдобавляет границу; - свойство
colorизменяет цвет текста; - свойство
text-alignвыравнивает текст; - свойство
paddingдобавляет отступ до текста от границы элемента.
Сохраните и закройте файл
style.css. - свойство
-
Откройте шаблон
index.html:nano templates/index.html -
Добавьте ссылку на файл
style.cssвнутри раздела<head>:... <head> <meta charset="UTF-8"> <link rel="stylesheet" href="{{ url_for('static', filename= 'css/style.css') }}"> <title>FlaskBlog</title> </head> ...В ссылке используется вспомогательная функция
url_for() для генерации адреса расположения файла. Первый аргумент указывает на то, что ссылка связана со статическими файлами, второй аргумент — путь к файлу в поддиректории статических файлов.Сохраните и закройте файл
index.html. -
Обновите главную страницу приложения
http://<публичный_IP-адрес_ВМ>:5000/.Вы увидите, что цвет текста
Welcome to FlaskBlogтеперь красный и расположен по центру внутри рамки.
Используйте язык CSS
Настройте другие шаблоны HTML
В других HTML-шаблонах потребуется повторить основную часть кода HTML, который уже присутствует в шаблоне index.html. Чтобы избежать ненужного дублирования кода, используйте файл базового шаблона, который будет служить родителем для всех остальных HTML-шаблонов проекта. Подробнее в документации Jinja
-
Создайте и откройте базовый файл-шаблон
base.htmlв директорииtemplates: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 href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous"> <title>{% block title %} {% endblock %}</title> </head> <body> <nav class="navbar navbar-expand-lg bg-light"> <div class="container-fluid"> <a class="navbar-brand" href="{{ url_for('index')}}">FlaskBlog</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav me-auto mb-2 mb-lg-0"> <li class="nav-item"> <a class="nav-link active" href="#">About</a> </li> </ul> </div> </div> </nav> <div class="container"> {% block content %} {% endblock %} </div> <!-- Optional JavaScript --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script> </body> </html>В этот файл входит HTML-код, а также дополнительный код, необходимый для Bootstrap. Теги
<meta>содержат информацию для браузера, тег<link>— привязывает файлы CSS Bootstrap, а тег<script>— ссылается на сценарий JavaScript, который позволяет реализовать ряд дополнительных функций Bootstrap. Подробнее в документации Bootstrap .Следующие части кода относятся к механизму шаблонов Jinja:
{% block title %} {% endblock %}— блок , замещающий заголовок. Вы будете использовать его в других шаблонах для написания уникальных заголовков для разных страниц приложения. Это позволит избежать перезаписи раздела<head>.{{ url_for('index')}}— вызов функции, которая возвращает URL для функции визуализацииindex(). Этот вызов отличается от предыдущего вызоваurl_for(), который использовался для привязки файла CSS. Сейчас в функцию передается только один аргумент — имя функции визуализации, которая вместо файла привязывается к маршруту, связанному с этой функцией визуализации.{% block content %} {% endblock %}— блок, в который будет подставлен определенный контент в зависимости от конечного шаблона.
Сохраните и закройте файл
base.html. -
Настройте в HTML-шаблонах наследование из базового шаблона. Откройте шаблон
index.html:nano templates/index.html -
Замените содержимое файла следующим кодом:
{% extends 'base.html' %} {% block title %} Welcome to FlaskBlog {% endblock %} {% block content %} <h1> Welcome to FlaskBlog </h1> {% endblock %}В этом коде:
{% extends 'base.html' %}— указывает, что текущий шаблон наследует из шаблонаbase.html.{% block title %} ... {% endblock %}— содержит заголовок, который будет подставлен вместо переменной в соответствующий блок шаблона-родителяbase.html.{% block content %} ... {% endblock %}— содержит контент, который будет подставлен вместо переменной в соответствующий блок шаблона-родителяbase.html.
Поскольку один и тот же текст
Welcome to FlaskBlogиспользуется и в качестве названия страницы, и в качестве заголовка, который появляется под панелью навигации, то этот код можно сократить, избежав повторения:{% extends 'base.html' %} {% block content %} <h1>{% block title %} Welcome to FlaskBlog {% endblock %}</h1> {% endblock %}Сохраните и закройте файл
index.html. -
Обновите главную страницу приложения
http://<публичный_IP-адрес_ВМ>:5000/.Вы увидите страницу с панелью навигации и оформленным заголовком.
Настройте базу данных
Создайте базу данных, в которой будут храниться посты блога, и добавьте в нее несколько примеров постов.
Для работы с данными веб-приложения вы будете использовать СУБД SQLite
Подготовьте файл schema.sql, содержащий набор SQL-команд для создания базы данных, состоящей из таблицы posts с несколькими столбцами.
-
Создайте и откройте файл
schema.sqlв директории проектаflask_blog:nano schema.sql -
Добавьте в файл следующий код:
DROP TABLE IF EXISTS posts; CREATE TABLE posts ( id INTEGER PRIMARY KEY AUTOINCREMENT, created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, title TEXT NOT NULL, content TEXT NOT NULL );В этом коде:
- Команда
DROP TABLE IF EXISTS posts;удаляет таблицуposts, если она уже существует. Этот шаг рекомендуется выполнять перед созданием любой новой таблицы, так как он позволяет исключить возможные ошибки. - Команда
CREATE TABLE postsсоздает таблицуpostsсо следующими столбцами:id— целое число, первичный ключ (PRIMARY KEY) таблицы.AUTOINCREMENTозначает, что значением будет уникальное число, сгенерированное автоматически при вставке новой записи (поста блога) в таблицу.created— время создания новой записи.NOT NULLозначает, что значение в столбце не может быть пустым, аDEFAULT CURRENT_TIMESTAMP— что значением по умолчанию будет момент добавления новой записи в таблицу. Это позволит не указывать вручную время создания поста, оно будет подставляться автоматически.title— заголовок поста.content— основной текст поста.
Сохраните и закройте файл
schema.sql.Примечание
При использовании этих SQL-команд любое предыдущее содержимое таблицы
postsв файлеdatabase.dbбудет удалено. Не сохраняйте ничего важного в базу данных через веб-приложение до окончания изучения этого руководства. - Команда
-
Создайте и откройте файл
init_db.pyв директории проектаflask_blog:nano init_db.pyЭтот файл позволит сформировать файл базы данных SQLite для вашего веб-приложения.
-
Добавьте в файл следующий код:
import sqlite3 connection = sqlite3.connect('database.db') with open('schema.sql') as f: connection.executescript(f.read()) cur = connection.cursor() cur.execute("INSERT INTO posts (title, content) VALUES (?, ?)", ('First Post', 'Content for the first post') ) cur.execute("INSERT INTO posts (title, content) VALUES (?, ?)", ('Second Post', 'Content for the second post') ) connection.commit() connection.close()В этом коде:
- Импортируется модуль
sqlite3. - Открывается соединение с файлом базы данных
database.db, который будет создан сразу после запуска файла Python. - С помощью функции
open()открывается созданная ранее SQL-схемаschema.sql. - С помощью метода
executescript()оформляется содержание схемы: выполняется одновременно несколько операторов SQL и формируется таблицаposts. - С помощью метода
cursor()открывается курсор, необходимый для отправки запросов к базе данных. - С помощью метода
execute()выполняются два оператора SQLINSERT, которые добавляют два поста-примера в таблицуposts. - Изменения фиксируются, и соединение закрывается.
Сохраните и закройте файл
init_db.py. - Импортируется модуль
-
Запустите выполнение файла с помощью
python:python init_db.py -
Убедитесь, что в директории проекта
flask_blogпоявился файлdatabase.db:ls -lРезультат:
total 40 -rw-rw-r-- 1 yc-user yc-user 105 Mar 23 19:45 app.py -rw-r--r-- 1 yc-user yc-user 12288 Mar 24 12:27 database.db drwxrwxr-x 5 yc-user yc-user 4096 Mar 23 19:30 env -rw-rw-r-- 1 yc-user yc-user 472 Mar 24 12:27 init_db.py drwxrwxr-x 2 yc-user yc-user 4096 Mar 23 19:45 __pycache__ -rw-rw-r-- 1 yc-user yc-user 204 Mar 24 12:26 schema.sql drwxrwxr-x 2 yc-user yc-user 4096 Mar 24 12:29 static drwxrwxr-x 2 yc-user yc-user 4096 Mar 24 12:30 templates
Настройте отображение постов блога
На главной странице приложения настройте отображение полного списка постов блога и создайте страницу просмотра содержимого отдельных постов по их идентификатору (ID).
Отобразите список всех постов блога
Измените функцию визуализации index() и шаблон index.html, чтобы отобразить список всех постов, сохраненных в базе данных.
-
Откройте файл
app.py:nano app.py -
Замените содержимое файла следующим кодом:
import sqlite3 from flask import Flask, render_template def get_db_connection(): conn = sqlite3.connect('database.db') conn.row_factory = sqlite3.Row return conn app = Flask(__name__) @app.route('/') def index(): conn = get_db_connection() posts = conn.execute('SELECT * FROM posts').fetchall() conn.close() return render_template('index.html', posts=posts)В этом коде дополнительно:
- Импортируется модуль
sqlite3. - Создается функция
get_db_connection(), в которой:- создается объект подключения
connк базе данныхdatabase.db; - данные БД присваиваются атрибуту
row_factoryобъектаconn; - возвращается объект подключения
conn, который будет использоваться для доступа к БД.
- создается объект подключения
- В измененной функции
index():- объекту
connприсваивается объект подключения к БД, возвращенный функциейget_db_connection(); - с помощью метода
fetchall()объектуpostsприсваивается результат выполнения SQL-запроса для выбора всех записей из таблицыposts; - с помощью метода
close()закрывается подключение к базе данных; - возвращается HTML-код главной страницы приложения, сформированный из шаблона
index.html, в который в качестве аргумента передается объектposts, содержащий все записи таблицы с постами.
- объекту
Сохраните и закройте файл
app.py. - Импортируется модуль
-
Откройте шаблон главной страницы приложения
index.html:nano templates/index.html -
Замените содержимое файла следующим кодом:
{% extends 'base.html' %} {% block content %} <h1>{% block title %} Welcome to FlaskBlog {% endblock %}</h1> {% for post in posts %} <a href="#"> <h2>{{ post['title'] }}</h2> </a> <span class="badge text-bg-primary">{{ post['created'] }}</span> <hr> {% endfor %} {% endblock %}В этом коде добавляются:
{% for post in posts %} ... {% endfor %}— цикл Jinja , который отображает по порядку каждый элемент, содержащийся в объектеposts, переданном функциейindex()в файлеapp.py. Внутри этого цикла для каждого поста в HTML-заголовке<h2>отображается заголовок поста со ссылкой. В дальнейшем эти ссылки будут использоваться для перехода на страницы просмотра отдельных постов.{{ post['title'] }}— значение заголовка поста, который содержится в переменнойpost, то есть в элементе объектаposts, обрабатываемом в текущей итерации циклаfor.- Аналогично отображается дата создания поста
post['created'], к дате применяется CSS-классbadge.
Сохраните и закройте файл
index.html. -
Обновите главную страницу приложения
http://<публичный_IP-адрес_ВМ>:5000/.Вы увидите список заголовков двух постов, которые вы добавили в базу данных при ее создании.
Отобразите отдельные посты блога
Создайте новый маршрут Flask с функцией визуализации, которая будет обрабатывать новый HTML-шаблон для отображения отдельного поста блога по его идентификатору. С помощью нового маршрута приложение по URL http://<публичный_IP-адрес_ВМ>:5000/ID будет отображать пост с соответствующим номером ID, если такой идентификатор существует в базе данных.
-
Откройте файл
app.py:nano app.py -
Замените содержимое файла следующим кодом:
import sqlite3 from flask import Flask, render_template from werkzeug.exceptions import abort def get_db_connection(): conn = sqlite3.connect('database.db') conn.row_factory = sqlite3.Row return conn def get_post(post_id): conn = get_db_connection() post = conn.execute('SELECT * FROM posts WHERE id = ?', (post_id,)).fetchone() conn.close() if post is None: abort(404) return post app = Flask(__name__) @app.route('/') def index(): conn = get_db_connection() posts = conn.execute('SELECT * FROM posts').fetchall() conn.close() return render_template('index.html', posts=posts) @app.route('/<int:post_id>') def post(post_id): post = get_post(post_id) return render_template('post.html', post=post)В этом коде дополнительно:
- Импортируется функция abort()
из библиотеки Werkzeug . Она позволит формировать ответ Flask с сообщением404 Not Found, если пост блога с указанным в URL идентификатором не существует. - Создается функция
get_post(), принимающая аргументpost_idс идентификатором поста блога, который необходимо получить. В этой функции:- объекту
connприсваивается объект подключения к БД, возвращенный функциейget_db_connection(); - с помощью метода
fetchone()объектуpostприсваивается результат выполнения SQL-запроса, получающего из БД строку с постом блога с идентификатором, соответствующим значениюpost_id; - с помощью метода
close()закрывается подключение к базе данных; - если пост с переданным в
post_idидентификатором не найден, используется функцияabort()для ответа с кодом ошибки 404, после чего выполнение функции завершается; - если пост с переданным в
post_idидентификатором найден, функция возвращает объектpost, содержащий строку таблицы БД для этого поста.
- объекту
- Создается функция визуализации
post(post_id). В этой функции:- декоратором добавляется правило переменной
<int:post_id>, которое указывает, что URL после/представляет собой положительное целое число (отмеченное конвертеромint), использующееся функцией визуализации; - с помощью функции
get_post(post_id)объектуpostприсваивается содержимое строки таблицы БД, соответствующей посту блога с указанным идентификатором; - возвращается HTML-код страницы просмотра поста, сформированный из шаблона
post.html, в который в качестве аргумента передается объектpostс содержимым поста.
- декоратором добавляется правило переменной
Сохраните и закройте файл
app.py. - Импортируется функция abort()
-
Создайте и откройте файл-шаблон
post.htmlв директорииtemplates:nano templates/post.html -
Добавьте в файл следующий код:
{% extends 'base.html' %} {% block content %} <h2>{% block title %} {{ post['title'] }} {% endblock %}</h2> <p>{{ post['content'] }}</p> <span class="badge text-bg-primary">{{ post['created'] }}</span> {% endblock %}Этот код похож на код в шаблоне
index.html. Различие в том, что здесь не используется циклfor, так как шаблон должен отображать единичный пост с его содержимымpost['content'].Сохраните и закройте файл
post.html. -
Откройте URL
http://<публичный_IP-адрес_ВМ>:5000/1иhttp://<публичный_IP-адрес_ВМ>:5000/2, чтобы просмотреть два первых поста, созданных одновременно с базой данных.Откройте URL
http://<публичный_IP-адрес_ВМ>:5000/3, и вы увидите страницу с ошибкой о том, что запрашиваемый пост блога не найден. -
Добавьте на главную страницу приложения ссылки, ведущие с заголовков постов на страницы просмотра их содержимого.
Откройте шаблон
index.html:nano templates/index.html -
Замените значение атрибута
hrefс#на{{ url_for('post', post_id=post['id']) }}, чтобы циклforвыглядел следующим образом:... {% for post in posts %} <a href="{{ url_for('post', post_id=post['id']) }}"> <h2>{{ post['title'] }}</h2> </a> <span class="badge text-bg-primary">{{ post['created'] }}</span> <hr> {% endfor %} ...Функция
url_for()в качестве аргументов принимает:'post'— функцию визуализацииpost().post_id— переменную со значением идентификатора постаpost['id'], необходимого для функции визуализации.
Функция
url_for()для каждого заголовка поста возвращает корректный URL, основанный на его идентификаторе.Сохраните и закройте файл
index.html. -
Перейдите к главной странице приложения
http://<публичный_IP-адрес_ВМ>:5000/и убедитесь, что заголовки постов работают как ссылки для перехода к их содержимому.
Добавьте действия с постами
Добавьте в ваше веб-приложение возможность создавать в блоге новые посты, а также редактировать существующие и удалять ненужные.
Создание нового поста
Создайте страницу, на которой пользователь сможет написать новый пост, состоящий из заголовка и содержания.
-
Откройте файл
app.py:nano app.py -
Замените содержимое файла следующим кодом:
import sqlite3 from flask import Flask, render_template, request, url_for, flash, redirect from werkzeug.exceptions import abort def get_db_connection(): conn = sqlite3.connect('database.db') conn.row_factory = sqlite3.Row return conn def get_post(post_id): conn = get_db_connection() post = conn.execute('SELECT * FROM posts WHERE id = ?', (post_id,)).fetchone() conn.close() if post is None: abort(404) return post app = Flask(__name__) app.config['SECRET_KEY'] = 'your secret key' @app.route('/') def index(): conn = get_db_connection() posts = conn.execute('SELECT * FROM posts').fetchall() conn.close() return render_template('index.html', posts=posts) @app.route('/<int:post_id>') def post(post_id): post = get_post(post_id) return render_template('post.html', post=post) @app.route('/create', methods=('GET', 'POST')) def create(): return render_template('create.html')В этом коде дополнительно:
- Импортируются:
- глобальный объект
request , необходимый для доступа к данным, передающимся через HTML-форму; - функция
url_for() , необходимая для формирования URL-адресов; - функция
flash() , необходимая для отображения сообщения при обработке запроса; - функция
redirect() , необходимая для перенаправления клиента на другую страницу.
- глобальный объект
- Через объект
app.configдобавляется конфигурация секретного ключаSECRET_KEY, необходимая для работы функцииflash(), чтобы хранить всплывающие сообщения в сеансе браузера клиента. Секретный ключ представляет собой длинную строку случайных символов. Он используется для создания защищенных сеансов и позволяет Flask запоминать информацию между запросами, например переходить от страницы создания нового поста к главной странице приложения. Пользователь может получить доступ к информации, хранящейся в сеансе, но не может изменить ее без секретного ключа. Поэтому никогда не передавайте никому доступ к вашему секретному ключу. Подробнее в документации Flask . - Функция визуализации
create()возвращает сформированный из шаблонаcreate.htmlHTML-код страницы создания поста блога. Декоратор создает маршрут/create, который принимает GET и POST-запросы. По умолчанию принимаются только GET-запросы. Для того чтобы маршрут также принимал POST-запросы, которые используются браузером при передаче данных форм, в аргументеmethodsпередается кортеж с допустимыми типами запросов.
Сохраните и закройте файл
app.py. - Импортируются:
-
Создайте и откройте файл-шаблон
create.htmlв директорииtemplates:nano templates/create.html -
Добавьте в файл следующий код:
{% extends 'base.html' %} {% block content %} <h1>{% block title %} Create a New Post {% endblock %}</h1> <form method="post"> <div class="mb-3"> <label for="title" class="col-sm-2 col-form-label">Title</label> <input type="text" name="title" placeholder="Post title" class="form-control" value="{{ request.form['title'] }}"> </div> <div class="mb-3"> <label for="content" class="col-sm-2 col-form-label">Content</label> <textarea name="content" placeholder="Post content" class="form-control" rows="3">{{ request.form['content'] }}</textarea> </div> <div class="mb-3"> <button type="submit" class="btn btn-primary">Submit</button> </div> </form> {% endblock %}В основном это обычный HTML-код, который будет отображать поля для ввода заголовка и основного текста будущего поста, а также кнопку для отправки формы.
Значение заголовка поста сохраняется в объекте
{{ request.form['title'] }}, а значение содержания — в объекте{{ request.form['content'] }}. Это сделано для того, чтобы вводимые вами данные не потерялись, если что-то пойдет не по плану. Например, если вы напишете основной текст поста, но забудете указать заголовок, отобразится сообщение о том, что заголовок является обязательным для заполнения полем. При этом вы не потеряете уже введенный основной текст, так как он будет сохранен в глобальном объектеrequest, к которому у вас есть доступ в шаблонах.Сохраните и закройте файл
create.html. -
Откройте URL
http://<публичный_IP-адрес_ВМ>:5000/createи убедитесь, что отображается страница Create a New Post с полями для заголовка и содержания поста, а также кнопкой для отправки формы.Форма с этой страницы отправляет POST-запрос в функцию визуализации
create(). Но в функции сейчас отсутствует код обработки POST-запроса, поэтому после заполнения и отправки формы введенные данные не будут сохранены в БД. Исправьте это, изменив функцию визуализацииcreate(). -
Откройте файл
app.py:nano app.py -
Замените содержимое функции визуализации
create()на следующий код:... @app.route('/create', methods=('GET', 'POST')) def create(): if request.method == 'POST': title = request.form['title'] content = request.form['content'] if not title: flash('Title is required!') else: conn = get_db_connection() conn.execute('INSERT INTO posts (title, content) VALUES (?, ?)', (title, content)) conn.commit() conn.close() return redirect(url_for('index')) return render_template('create.html')В коде этой функции:
- Условие
if request.method == 'POST'обеспечивает, чтобы код выполнялся только в отношении POST-запросов. - Из глобального объекта
request.form, содержащего данные формы, извлекаются отправленные в полях формы заголовок и содержание нового поста. - Если в форме не задан заголовок поста, будет выполнено условие
if not title, и пользователь увидит сообщение с информацией о необходимости заполнения поля заголовка. - Если заголовок указан:
- с помощью функции
get_db_connection()открывается подключение к БД, и в таблицуpostsдобавляется запись о новом посте, содержащая полученные через форму заголовок и текст поста; - изменения фиксируются, соединение закрывается;
- с помощью функции
redirect()пользователь перенаправляется на главную страницу приложения.
- с помощью функции
Сохраните и закройте файл
app.py. - Условие
-
Откройте URL
http://<публичный_IP-адрес_ВМ>:5000/create, введите заголовок и содержание поста и отправьте форму. После отправки формы вы будете перенаправлены на главную страницу приложения, где вы увидите список всех постов, включая новый. -
В шаблоне
base.htmlдобавьте отображение всплывающего сообщения и добавьте на панель навигации ссылку на форму создания нового поста. Откройте шаблонbase.html:nano templates/base.html -
Перед ссылкой
Aboutвнутри тега<nav>добавьте тег<li>:... <nav class="navbar navbar-expand-lg bg-light"> <div class="container-fluid"> <a class="navbar-brand" href="{{ url_for('index')}}">FlaskBlog</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav me-auto mb-2 mb-lg-0"> <li class="nav-item"> <a class="nav-link active" href="{{url_for('create')}}">New Post</a> </li> <li class="nav-item"> <a class="nav-link active" href="#">About</a> </li> </ul> </div> </div> </nav> ... -
В блоке
<div class="container">непосредственно перед блокомcontentдобавьте циклfor, необходимый для отображения всплывающих сообщений под панелью навигации:... <div class="container"> {% for message in get_flashed_messages() %} <div class="alert alert-danger">{{ message }}</div> {% endfor %} {% block content %} {% endblock %} </div> ...Всплывающие сообщения доступны в специальной функции Flask
get_flashed_messages().Сохраните и закройте файл
base.html.На панели навигации появился управляющий элемент New Post, связанный с маршрутом
/create. -
Перейдите на главную страницу приложения
http://<публичный_IP-адрес_ВМ>:5000и убедитесь, что по ссылке New Post на панели навигации можно создать новый пост.
Редактирование существующего поста
Создайте страницу, на которой можно отредактировать существующий пост.
-
Откройте файл
app.py:nano app.py -
В конце файла добавьте функцию визуализации
edit():... @app.route('/<int:id>/edit', methods=('GET', 'POST')) def edit(id): post = get_post(id) if request.method == 'POST': title = request.form['title'] content = request.form['content'] if not title: flash('Title is required!') else: conn = get_db_connection() conn.execute('UPDATE posts SET title = ?, content = ?' ' WHERE id = ?', (title, content, id)) conn.commit() conn.close() return redirect(url_for('index')) return render_template('edit.html', post=post)Процесс редактирования существующего поста похож на процесс создания нового, поэтому эта функция визуализации почти идентична функции визуализации
create(). Пост, который необходимо отредактировать, задается в URL, а идентификатор поста передается в функциюedit()в аргументеid. Это же значение передается и в функциюget_post(), чтобы получить текущее содержимое поста из базы данных. Измененные данные поступят в приложение в POST-запросе, который обрабатывается внутри условияif request.method == 'POST'.Как и при создании нового поста, функция извлекает в отдельные переменные данные из объекта
request.form, после чего выполняет проверку на заполнение пользователем заголовка поста. Если заголовок не заполнен, функция показывает всплывающее сообщение об обязательности заполнения этого поля. В остальных случаях функция открывает подключение к базе данных и обновляет поля в строке с нужным постом в таблицеposts. Идентификатор поста в базе данных соответствует идентификатору ID, указанному в URL.В случае GET-запроса функция возвращает HTML-код, сформированный из шаблона
edit.htmlс подставленными из объектаpostзначениями заголовка и текста поста.Сохраните и закройте файл
app.py. -
Создайте и откройте файл-шаблон
edit.htmlв директорииtemplates:nano templates/edit.html -
Добавьте в файл следующий код:
{% extends 'base.html' %} {% block content %} <h1>{% block title %} Edit "{{ post['title'] }}" {% endblock %}</h1> <form method="post"> <div class="mb-3"> <label for="title" class="col-sm-2 col-form-label">Title</label> <input type="text" name="title" placeholder="Post title" class="form-control" value="{{ request.form['title'] or post['title'] }}"> </div> <div class="mb-3"> <label for="content" class="col-sm-2 col-form-label">Content</label> <textarea name="content" placeholder="Post content" class="form-control" rows="3">{{ request.form['content'] or post['content'] }}</textarea> </div> <div class="mb-3"> <button type="submit" class="btn btn-primary">Submit</button> </div> </form> <hr> {% endblock %}Код аналогичен коду шаблона
create.htmlза исключением выражений{{ request.form['title'] or post['title'] }}и{{ request.form['content'] or post['content'] }}. Эти выражения отображают данные, сохраненные в запросе, а если запроса не существует, — данные из переданной в шаблон переменнойpostс текущими значениями из базы данных.Сохраните и закройте файл
edit.html. -
Откройте URL
http://<публичный_IP-адрес_ВМ>:5000/1/editдля редактирования первого поста. Вы увидите страницу Edit "First Post" с данными поста. Отредактируйте пост, отправьте форму и убедитесь, что пост был обновлен. -
На главной странице приложения добавьте для каждого поста из списка ссылку на страницу редактирования. Откройте шаблон
index.html:nano templates/index.html -
После даты создания поста добавьте ссылку для его редактирования:
... {% for post in posts %} <a href="{{ url_for('post', post_id=post['id']) }}"> <h2>{{ post['title'] }}</h2> </a> <span class="badge text-bg-primary">{{ post['created'] }}</span> <a href="{{ url_for('edit', id=post['id']) }}"> <span class="badge text-bg-warning">Edit</span> </a> <hr> {% endfor %} ...К заголовкам постов на главной странице добавляется ссылка Edit, которая указывает на функцию визуализации
edit()и передает в нее значение идентификатора поста изpost['id'].Сохраните и закройте файл
index.html. -
Перейдите на главную страницу приложения
http://<публичный_IP-адрес_ВМ>:5000и убедитесь, что на ней появились ссылки Edit, с помощью которых можно изменять содержимое постов.
Удаление поста
-
Откройте файл
app.py:nano app.py -
В конце файла добавьте функцию визуализации
delete():... @app.route('/<int:id>/delete', methods=('POST',)) def delete(id): post = get_post(id) conn = get_db_connection() conn.execute('DELETE FROM posts WHERE id = ?', (id,)) conn.commit() conn.close() flash('"{}" was successfully deleted!'.format(post['title'])) return redirect(url_for('index'))Эта функция принимает только POST-запросы. Это значит, что просто переход в браузере по адресу
http://<публичный_IP-адрес_ВМ>:5000/ID/deleteвернет ошибку, поскольку веб-браузеры по умолчанию используют GET-запросы. POST-запрос по этому маршруту можно отправить только через форму, передающую идентификатор поста, который требуется удалить.Получив POST-запрос, функция открывает подключение к базе данных и выполняет SQL-команду
DELETE FROM, после чего изменения фиксируются, а подключение закрывается. Приложение перенаправляет пользователя на главную страницу и показывает сообщение о том, что пост был успешно удален.Кнопка Delete будет добавлена на страницу редактирования поста, поэтому для этой функции не нужен отдельный шаблон.
Сохраните и закройте файл
app.py. -
Откройте шаблон
edit.html:nano templates/edit.html -
Непосредственно после тега
<hr>и перед строкой{% endblock %}добавьте тег<form>:... <hr> <form action="{{ url_for('delete', id=post['id']) }}" method="POST"> <input type="submit" value="Delete Post" class="btn btn-danger btn-sm" onclick="return confirm('Are you sure you want to delete this post?')"> </form> {% endblock %}В этом коде используется метод
confirm() , запрашивающий подтверждение согласия пользователя перед отправкой запроса.Сохраните и закройте файл
edit.html. -
Перейдите на главную страницу приложения
http://<публичный_IP-адрес_ВМ>:5000и убедитесь, что на странице редактирования поста появилась кнопка Delete Post, удаляющая пост.
Разработка вашего приложения завершена. Если вы все сделали правильно, итоговый исходный код вашего проекта соответствует коду в репозитории
Подведите итоги
В ходе выполнения данного руководства вы использовали фреймворк Flask
Вы можете самостоятельно продолжить разработку этого проекта с помощью большого количества расширений Flask
- Flask-Login
— позволяет управлять сеансами пользователей: решает задачи входа в систему и выхода из системы, а также управления сеансами пользователей. - Flask-SQLAlchemy
— упрощает использование Flask за счет поддержки SQLAlchemy . Это инструментарий Python SQL и Object Relational Mapper, разработанный для взаимодействия с базами данных. - Flask-Mail
— помогает отправлять сообщения электронной почты из приложений Flask.
Удалите созданные ресурсы
Чтобы перестать платить за развернутый веб-сервер, удалите виртуальную машину.
Если вы зарезервировали для вашего веб-сервера статический публичный IP-адрес, удалите его.