Yandex Cloud
Поиск
Связаться с экспертомПопробовать бесплатно
  • Кейсы
  • Документация
  • Блог
  • Все сервисы
    • Cloud Interconnect
    • Cloud Backup
    • Cloud Registry
    • Yandex AI Studio
    • Compute Cloud
    • Object Storage
    • Managed Service for Kubernetes®
    • Yandex BareMetal
    • Smart Web Security
    • Security Deck
    • Managed Service for PostgreSQL
    • Managed Service for ClickHouse®
    • Monium
    • Cloud CDN
    • Network Load Balancer
    • Virtual Private Cloud
    • Cloud DNS
    • Application Load Balancer
    • Yandex Cloud Video
    • Stackland
    • Yandex Cloud Router
    • Yandex Managed Service for Trino
    • Managed Service for MySQL®
    • Managed Service for Valkey™
    • Managed Service for Apache Spark™
    • Yandex StoreDoc
    • Managed Service for OpenSearch
    • Managed Service for Apache Kafka®
    • Data Transfer
    • Yandex MPP Analytics Engine for PostgreSQL
    • Yandex Managed Service for Apache Airflow®
    • Data Processing
    • Yandex MetaData Hub
    • Managed Service for YDB
    • Managed Service for Sharded PostgreSQL
    • Managed Service for YTsaurus
    • Yandex WebSQL
    • DataLens
    • Yandex Search API
    • SpeechSense
    • SpeechKit
    • DataSphere
    • Vision OCR
    • Translate
    • VibeCraft
    • Yandex Cloud Detection and Response
    • Yandex Identity Hub
    • Key Management Service
    • Certificate Manager
    • Yandex Lockbox
    • Audit Trails
    • SmartCaptcha
    • Cloud Desktop
    • GOST Gateway
    • Yandex SIEM
    • SourceCraft Code Assistant
    • Container Registry
    • Managed Service for GitLab
    • SourceCraft
    • Managed Service for Prometheus®
    • Cloud Functions
    • API Gateway
    • Yandex Cloud Postbox
    • Message Queue
    • Serverless Integrations
    • IoT Core
    • Data Streams
    • Serverless Containers
    • Cloud Notification Service
    • Yandex Query
    • Identity and Access Management
    • Yandex Cloud Console
    • Resource Manager
    • Yandex Cloud Billing
    • Yandex Cloud Quota Manager
    • Cloud Apps
  • Статус работы сервисов
  • Marketplace
    • Популярные
    • Инфраструктура и сеть
    • Платформа данных
    • Искусственный интеллект
    • Безопасность
    • Инструменты DevOps
    • Бессерверные вычисления
    • Управление ресурсами
  • Все решения
    • По отраслям
    • По типу задач
    • Экономика платформы
    • Yandex Cloud Trust
    • Техническая поддержка
    • Каталог партнёров
    • Обучение и сертификация
    • Облако для стартапов
    • Облако для крупного бизнеса
    • Центр технологий для общества
    • Облако для интеграторов
    • Поддержка IT-бизнеса
    • Облако для фрилансеров
    • Обучение и сертификация
    • Блог
    • Документация
    • Контент-программа
    • Мероприятия и вебинары
    • Контакты, чаты и сообщества
    • Идеи
    • Калькулятор цен
    • Тарифы
    • Акции и free tier
  • Кейсы
  • Документация
  • Блог
Создавайте контент и получайте гранты!Готовы написать своё руководство? Участвуйте в контент-программе и получайте гранты на работу с облачными сервисами!
Подробнее о программе
Сделано на Gravity UI
Проект Яндекса
© 2026 ООО «Яндекс.Облако»
Yandex Compute Cloud
  • Yandex Container Solution
    • Все руководства
    • Настройка синхронизации часов с помощью NTP
    • Автомасштабирование группы ВМ для обработки сообщений из очереди
    • Обновление группы ВМ под нагрузкой
    • Развертывание Remote Desktop Gateway
    • Начало работы с Packer
    • Передача логов с ВМ в Yandex Cloud Logging
    • Сборка образа ВМ с набором инфраструктурных инструментов с помощью Packer
    • Миграция в Yandex Cloud с помощью Хайстекс Акура
    • Защита от сбоев с помощью Хайстекс Акура
    • Резервное копирование ВМ с помощью Хайстекс Акура
    • Развертывание отказоустойчивой архитектуры с прерываемыми ВМ
    • Настройка отказоустойчивой архитектуры в Yandex Cloud
    • Создание триггера для бюджетов, который вызывает функцию для остановки ВМ
    • Создание триггеров, которые вызывают функции для остановки ВМ и отправки уведомлений в Telegram
    • Создание веб-приложения на Python с использованием фреймворка Flask
    • Создание SAP-программы в Yandex Cloud
    • Развертывание сервера Minecraft в Yandex Cloud
    • Автоматизация сборки образов с помощью Jenkins и Packer
    • Создание тестовых виртуальных машин через GitLab CI
    • Высокопроизводительные вычисления (HPC) на прерываемых ВМ
    • Настройка SFTP-сервера на Centos 7
    • Развертывание параллельной файловой системы GlusterFS в высокодоступном режиме
    • Развертывание параллельной файловой системы GlusterFS в высокопроизводительном режиме
    • Резервное копирование в Object Storage с помощью Bacula
    • Построение пайплайна CI/CD в GitLab с использованием serverless-продуктов
    • Реализация защищенной высокодоступной сетевой инфраструктуры с выделением DMZ на основе Check Point NGFW
    • Сегментация облачной инфраструктуры с помощью решения Check Point Next-Generation Firewall
    • Настройка защищенного туннеля GRE поверх IPsec
    • Создание бастионного хоста
    • Реализация отказоустойчивых сценариев для сетевых виртуальных машин
    • Создание туннеля между двумя подсетями при помощи OpenVPN Access Server
    • Создание внешней таблицы на базе таблицы из бакета Object Storage с помощью конфигурационного файла
    • Настройка сетевой связности между подсетями BareMetal и Virtual Private Cloud
    • Работа со снапшотами в Managed Service for Kubernetes
    • Запуск языковой модели DeepSeek-R1 в кластере GPU
    • Запуск библиотеки vLLM с языковой моделью Gemma 3 на ВМ с GPU
    • Доставка USB-устройств на виртуальную машину или сервер BareMetal
    • Запуск Docker-образа на ВМ с помощью Cloud Registry
    • Развертывание воркера SourceCraft на ВМ
  • Управление доступом
  • Правила тарификации
  • Справочник Terraform
  • Метрики Monitoring
  • Аудитные логи Audit Trails
  • История изменений
  • Обучающие курсы

В этой статье:

  • Перед началом работы
  • Создайте облачную сеть и подсеть
  • Создайте группу безопасности
  • Создайте и настройте виртуальную машину
  • Создайте и запустите приложение
  • Создайте и настройте шаблоны HTML
  • Создайте HTML-шаблон главной страницы
  • Настройте другие шаблоны HTML
  • Настройте базу данных
  • Настройте отображение постов блога
  • Отобразите список всех постов блога
  • Отобразите отдельные посты блога
  • Добавьте действия с постами
  • Создание нового поста
  • Редактирование существующего поста
  • Удаление поста
  • Подведите итоги
  • Удалите созданные ресурсы
  1. Практические руководства
  2. Создание веб-приложения на Python с использованием фреймворка Flask

Создание веб-приложения на Python с использованием фреймворка Flask

Статья создана
Yandex Cloud
Обновлена 5 октября 2026 г.
Открыть в Markdown
  • Перед началом работы
    • Создайте облачную сеть и подсеть
    • Создайте группу безопасности
  • Создайте и настройте виртуальную машину
  • Создайте и запустите приложение
  • Создайте и настройте шаблоны HTML
    • Создайте HTML-шаблон главной страницы
    • Настройте другие шаблоны HTML
  • Настройте базу данных
  • Настройте отображение постов блога
    • Отобразите список всех постов блога
    • Отобразите отдельные посты блога
  • Добавьте действия с постами
    • Создание нового поста
    • Редактирование существующего поста
    • Удаление поста
  • Подведите итоги
  • Удалите созданные ресурсы

С помощью этого руководства вы с нуля разработаете простое веб-приложение — мини-блог, в котором пользователь сможет создавать, просматривать, редактировать и удалять посты. Для этого в Yandex Cloud вы создадите виртуальную машину Yandex Compute Cloud и запустите на ней веб-сервер.

Веб-приложение написано на языке Python и использует СУБД SQLite для работы с данными. Визуальное оформление выполнено с помощью инструментария Bootstrap.

В качестве основы приложения выбран фреймворк Flask, как один из наиболее доступных фреймворков для создания веб-приложений на языке программирования Python.

Фреймворк Flask позволяет создавать веб-приложения с использованием всего одного файла с исходным кодом, не требует соблюдения какой-то определенной структуры директорий или написания сложного шаблонного кода перед началом использования. Также Flask предоставляет набор инструментов Werkzeug и механизм шаблонов Jinja для динамического создания HTML-страниц.

Исходный код приложения доступен в файле flask_blog.zip.

Схема работы создаваемого веб-приложения:

  1. HTTP-запросы поступают из браузера пользователя на виртуальную машину под управлением ОС Linux Ubuntu с установленным и запущенным веб-сервером Flask.
  2. Веб-сервер передает запрос в веб-приложение, маршрутизатор которого в зависимости от URL запроса вызывает подходящую функцию-обработчик.
  3. Функция-обработчик выполняет запрос к базе данных SQLite, чтобы получить или записать необходимые данные.
  4. Полученные из БД данные функция передает в подходящий HTML-шаблон Jinja, который возвращает готовый HTML-код страницы.
  5. Полученный HTML-код передается на веб-сервер, который в свою очередь передает этот код в браузер пользователя.

Чтобы создать веб-приложение с помощью Flask:

  1. Подготовьте облако к работе.
  2. Создайте и настройте виртуальную машину.
  3. Создайте и запустите приложение.
  4. Создайте и настройте шаблоны HTML.
  5. Настройте базу данных.
  6. Настройте отображение постов блога.
  7. Добавьте действия с постами.
  8. Подведите итоги.

Если веб-приложение вам больше не нужно, удалите все используемые им ресурсы.

Перед началом работыПеред началом работы

В Yandex Cloud оплачиваются только потребленные ресурсы и время их фактического использования, а для идентификации пользователя, оплачивающего такие ресурсы, нужен платежный аккаунт.

В стоимость поддержки создаваемого приложения входят:

  • плата за использование публичного IP-адреса (тарифы Yandex Virtual Private Cloud);
  • плата за вычислительные ресурсы и диски ВМ (тарифы Yandex Compute Cloud).

Зарегистрируйтесь в Yandex Cloud и создайте платежный аккаунт:

  1. Перейдите в консоль управления, затем войдите в Yandex Cloud или зарегистрируйтесь.
  2. На странице Yandex Cloud Billing убедитесь, что у вас подключен платежный аккаунт, и он находится в статусе ACTIVE или TRIAL_ACTIVE. Если платежного аккаунта нет, создайте его и привяжите к нему облако.

Если у вас есть активный платежный аккаунт, вы можете создать или выбрать каталог, в котором будет работать ваша инфраструктура, на странице облака.

Подробнее об облаках и каталогах.

Создайте облачную сеть и подсетьСоздайте облачную сеть и подсеть

В Yandex Cloud ресурсы связаны между собой и с интернетом при помощи облачных сетей, в которых ресурсам выделяются публичные IP-адреса, а также диапазоны внутренних IP-адресов или подсети.

Чтобы создать виртуальную сеть и подсеть для вашего веб-сервера:

Консоль управления
CLI
API
  1. В консоли управления выберите ваш каталог.
  2. Перейдите в сервис Virtual Private Cloud.
  3. Нажмите кнопку Создать сеть.
  4. В поле Имя укажите webserver-network.
  5. В поле Дополнительно отключите опцию Создать подсети.
  6. Нажмите кнопку Создать сеть.
  7. На панели слева выберите Подсети.
  8. Нажмите кнопку Создать.
  9. В поле Имя укажите webserver-subnet-ru-central1-b.
  10. В поле Зона доступности выберите зону доступности ru-central1-b.
  11. В поле Сеть выберите облачную сеть webserver-network.
  12. В поле CIDR укажите 192.168.1.0/24.
  13. Нажмите кнопку Создать подсеть.
  1. Создайте сеть 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.

  2. Создайте подсеть в зоне доступности 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.

  1. Чтобы создать сеть, воспользуйтесь методом REST API create для ресурса Network или вызовом gRPC API NetworkService/Create.

  2. Чтобы создать подсеть, воспользуйтесь методом REST API create для ресурса Subnet или вызовом gRPC API SubnetService/Create.

Создайте группу безопасностиСоздайте группу безопасности

Группы безопасности служат основным механизмом разграничения сетевого доступа в Yandex Cloud и помогают ограничить для облачных ресурсов несанкционированный трафик на уровне облачной сети.

Создайте группу безопасности, разрешающую входящий TCP-трафик для портов 5000 и 22, а также любой исходящий трафик. Порт 22 необходим для подключения к ВМ по SSH, порт 5000 по умолчанию используется для работы веб-сервера Flask.

Консоль управления
CLI
API
  1. В консоли управления выберите ваш каталог.

  2. Перейдите в сервис Virtual Private Cloud.

  3. На панели слева выберите Группы безопасности.

  4. Нажмите кнопку Создать группу безопасности.

  5. В поле Имя укажите имя webserver-sg.

  6. В поле Сеть выберите созданную ранее сеть webserver-network.

  7. В блоке Правила создайте следующие правила для управления трафиком:

    Направление
    трафика
    Диапазон портов Протокол Источник /
    Назначение
    IPv4 CIDR Описание
    Исходящий Весь Любой Диапазон адресов 0.0.0.0/0 any
    Входящий 5000 TCP Диапазон адресов 0.0.0.0/0 Flask
    Входящий 22 TCP Диапазон адресов 0.0.0.0/0 ssh
  8. Нажмите кнопку Сохранить.

Выполните команду:

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.

  1. Создайте виртуальную машину:

    Перед тем как приступать к созданию виртуальной машины, подготовьте пару ключей (открытый и закрытый) для доступа к ВМ по SSH.

    Консоль управления
    CLI
    API
    1. В консоли управления выберите каталог, в котором будет создана ВМ.

    2. Перейдите в сервис Compute Cloud.

    3. На панели слева выберите Виртуальные машины.

    4. Нажмите кнопку Создать виртуальную машину.

    5. В блоке Образ загрузочного диска выберите образ Ubuntu 22.04 LTS.

    6. В блоке Расположение выберите зону доступности ru-central1-b.

    7. В блоке Сетевые настройки:

      • В поле Подсеть выберите созданную ранее подсеть webserver-subnet-ru-central1-b.
      • В поле Публичный IP-адрес выберите Автоматически.
      • В поле Группы безопасности выберите созданную ранее группу безопасности webserver-sg.
    8. В блоке Доступ выберите SSH-ключ и укажите данные для доступа к ВМ:

      • В поле Логин укажите имя пользователя yc-user.
      • В поле SSH-ключ выберите SSH-ключ, сохраненный в вашем профиле пользователя организации.

        Если в вашем профиле нет сохраненных SSH-ключей или вы хотите добавить новый ключ:

        1. Нажмите кнопку Добавить ключ.

        2. Задайте имя SSH-ключа.

        3. Выберите вариант:

          • Ввести вручную — вставьте содержимое открытого SSH-ключа. Пару SSH-ключей необходимо создать самостоятельно.

          • Загрузить из файла — загрузите открытую часть SSH-ключа. Пару SSH-ключей необходимо создать самостоятельно.

          • Сгенерировать ключ — автоматическое создание пары SSH-ключей.

            При добавлении сгенерированного SSH-ключа будет создан и загружен архив с парой ключей. В ОС на базе Linux или macOS распакуйте архив в папку /home/<имя_пользователя>/.ssh. В ОС Windows распакуйте архив в папку C:\Users\<имя_пользователя>/.ssh. Дополнительно вводить открытый ключ в консоли управления не требуется.

        4. Нажмите кнопку Добавить.

        SSH-ключ будет добавлен в ваш профиль пользователя организации. Если в организации отключена возможность добавления пользователями SSH-ключей в свои профили, добавленный открытый SSH-ключ будет сохранен только в профиле пользователя внутри создаваемого ресурса.

    9. В блоке Общая информация задайте имя ВМ: mywebserver.

    10. Нажмите кнопку Создать ВМ.

    Выполните команду, указав сохраненный на предыдущем шаге идентификатор группы безопасности:

    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-адрес этой ВМ статическим.

  2. Создайте и активируйте виртуальное окружение.

    Виртуальное окружение обеспечивает изолированное пространство для проектов Python на сервере. Все необходимые зависимости — исполняемые файлы, библиотеки и прочие файлы — копируются в выбранный каталог, и приложение использует их, а не установленные в системе. Это позволяет обеспечить стабильность среды разработки и чистоту основной системы.

    Для создания виртуального окружения используйте модуль библиотеки Python 3 venv:

    1. Подключитесь к виртуальной машине mywebserver.

    2. В директории текущего пользователя создайте поддиректорию проекта flask_blog, в которой будет находиться приложение, и перейдите в нее:

      mkdir flask_blog && cd flask_blog
      
    3. Обновите список пакетов в репозиториях apt:

      sudo apt-get update
      
    4. Установите модуль создания виртуальных сред venv:

      sudo apt install python3.10-venv
      
    5. Создайте виртуальное окружение env:

      python3 -m venv env
      
    6. Активируйте созданное виртуальное окружение:

      source env/bin/activate
      

      После активации виртуального окружения в командной строке появится префикс с именем окружения:

      (env) yc-user@ubuntu:~/flask_blog$
      

      Примечание

      Чтобы эффективно отслеживать процесс разработки проекта и управлять им, вы можете использовать систему контроля версий. В этом случае добавьте директорию env в файл .gitignore, чтобы не отслеживать файлы, не связанные с проектом.

      Чтобы деактивировать виртуальное окружение, выполните команду:

      deactivate
      
  3. Установите 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 и запустите его для начала работы сервера.

  1. В директории проекта flask_blog создайте и откройте файл app.py:

    nano app.py
    

    Этот файл позволяет понять, как приложение будет обрабатывать HTTP-запросы.

  2. Добавьте в файл следующий код:

    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.

  3. Задайте переменные окружения Flask:

    export FLASK_APP=app && export FLASK_DEBUG=true
    

    Где:

    • FLASK_APP=app указывает на расположение приложения — файла app.py.
    • FLASK_DEBUG=true указывает, что приложение необходимо запустить в режиме разработки.
  4. Запустите приложение:

    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.

  5. Откройте браузер и введите в адресной строке http://<публичный_IP-адрес_ВМ>:5000/.

    Браузер отобразит строку Hello, World!. Это подтверждает, что ваше приложение успешно работает и к нему можно обращаться из интернета.

Важно

Flask использует простой веб-сервер для обслуживания приложения в среде разработки. Он предназначен для тестирования и обнаружения ошибок и не должен использоваться при развертывании в производственной среде. Подробнее в документации Flask.

Оставьте веб-сервер работать в текущем окне и откройте новое окно терминала. В новом окне терминала вы будете выполнять дальнейшую настройку приложения. Это позволит проверять работу внесенных в приложение изменений без необходимости постоянно останавливать и вновь запускать веб-сервер.

При открытии нового окна терминала:

  1. Подключитесь к ВМ.

  2. Перейдите в директорию проекта flask_blog, активируйте виртуальное окружение и задайте переменные окружения:

    cd flask_blog && source env/bin/activate && export FLASK_APP=app && export FLASK_DEBUG=true
    

Создайте и настройте шаблоны HTMLСоздайте и настройте шаблоны HTML

В текущем виде приложение отображает в браузере только простое текстовое сообщение. Добавьте в приложение файлы-шаблоны HTML, которые позволят отображать нужные пользователю данные и элементы управления.

Использовать в приложении шаблоны позволяет вспомогательная функция render_template(), в которой доступен механизм шаблонов Jinja. Вы будете использовать шаблоны HTML для создания страниц приложения: главной страницы со списком текущих постов блога и страниц создания, просмотра и редактирования поста.

Создайте HTML-шаблон главной страницыСоздайте HTML-шаблон главной страницы

  1. Откройте файл app.py:

    nano app.py
    
  2. Замените содержимое файла следующим кодом:

    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.

  3. В директории проекта flask_blog создайте поддиректорию templates:

    mkdir templates
    
  4. Создайте и откройте файл-шаблон index.html в директории templates:

    nano templates/index.html
    
  5. Добавьте в файл 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.

  6. Обновите главную страницу приложения http://<публичный_IP-адрес_ВМ>:5000/.

    Результат:

    Welcome to FlaskBlog.
    
  7. Кроме директории templates в веб-приложениях Flask обычно также существует директория static, в которой сохраняются статичные файлы, такие как стили CSS, скрипты JavaScript или изображения, используемые приложением.

    В директории проекта flask_blog создайте поддиректорию static:

    mkdir static
    
  8. Во вновь созданной директории static создайте поддиректорию css:

    mkdir static/css
    

    Примечание

    Вложенные директории используются для упорядочивания статичных файлов в специальных папках. Например, файлы JavaScript обычно хранятся в директории js, изображения — в директории images (или img) и т.д.

  9. Создайте и откройте файл style.css в директории static/css:

    nano static/css/style.css
    
  10. Добавьте в файл style.css следующий код:

    h1 {
        border: 2px #eee solid;
        color: #fc3d17;
        text-align: center;
        padding: 10px;
    }
    

    Этот CSS-код изменяет оформление текста внутри HTML-тегов <h1>:

    • свойство border добавляет границу;
    • свойство color изменяет цвет текста;
    • свойство text-align выравнивает текст;
    • свойство padding добавляет отступ до текста от границы элемента.

    Сохраните и закройте файл style.css.

  11. Откройте шаблон index.html:

    nano templates/index.html
    
  12. Добавьте ссылку на файл 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.

  13. Обновите главную страницу приложения http://<публичный_IP-адрес_ВМ>:5000/.

    Вы увидите, что цвет текста Welcome to FlaskBlog теперь красный и расположен по центру внутри рамки.

Используйте язык CSS, чтобы сформировать единый и уникальный стиль оформления вашего веб-приложения. Если вы не знакомы с языком CSS, воспользуйтесь инструментарием Bootstrap, который предлагает простые и удобные в использовании элементы оформления приложения. В этом руководстве вы будете использовать Bootstrap.

Настройте другие шаблоны HTMLНастройте другие шаблоны HTML

В других HTML-шаблонах потребуется повторить основную часть кода HTML, который уже присутствует в шаблоне index.html. Чтобы избежать ненужного дублирования кода, используйте файл базового шаблона, который будет служить родителем для всех остальных HTML-шаблонов проекта. Подробнее в документации Jinja.

  1. Создайте и откройте базовый файл-шаблон base.html в директории templates:

    nano templates/base.html
    
  2. Добавьте в файл следующий код:

    <!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.

  3. Настройте в HTML-шаблонах наследование из базового шаблона. Откройте шаблон index.html:

    nano templates/index.html
    
  4. Замените содержимое файла следующим кодом:

    {% 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.

  5. Обновите главную страницу приложения http://<публичный_IP-адрес_ВМ>:5000/.

    Вы увидите страницу с панелью навигации и оформленным заголовком.

Настройте базу данныхНастройте базу данных

Создайте базу данных, в которой будут храниться посты блога, и добавьте в нее несколько примеров постов.

Для работы с данными веб-приложения вы будете использовать СУБД SQLite, поскольку необходимый для взаимодействия с БД модуль sqlite3 уже входит в состав Python.

Подготовьте файл schema.sql, содержащий набор SQL-команд для создания базы данных, состоящей из таблицы posts с несколькими столбцами.

  1. Создайте и откройте файл schema.sql в директории проекта flask_blog:

    nano schema.sql
    
  2. Добавьте в файл следующий код:

    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 будет удалено. Не сохраняйте ничего важного в базу данных через веб-приложение до окончания изучения этого руководства.

  3. Создайте и откройте файл init_db.py в директории проекта flask_blog:

    nano init_db.py
    

    Этот файл позволит сформировать файл базы данных SQLite для вашего веб-приложения.

  4. Добавьте в файл следующий код:

    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() выполняются два оператора SQL INSERT, которые добавляют два поста-примера в таблицу posts.
    • Изменения фиксируются, и соединение закрывается.

    Сохраните и закройте файл init_db.py.

  5. Запустите выполнение файла с помощью python:

    python init_db.py
    
  6. Убедитесь, что в директории проекта 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, чтобы отобразить список всех постов, сохраненных в базе данных.

  1. Откройте файл app.py:

    nano app.py
    
  2. Замените содержимое файла следующим кодом:

    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.

  3. Откройте шаблон главной страницы приложения index.html:

    nano templates/index.html
    
  4. Замените содержимое файла следующим кодом:

    {% 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.

  5. Обновите главную страницу приложения http://<публичный_IP-адрес_ВМ>:5000/.

    Вы увидите список заголовков двух постов, которые вы добавили в базу данных при ее создании.

Отобразите отдельные посты блогаОтобразите отдельные посты блога

Создайте новый маршрут Flask с функцией визуализации, которая будет обрабатывать новый HTML-шаблон для отображения отдельного поста блога по его идентификатору. С помощью нового маршрута приложение по URL http://<публичный_IP-адрес_ВМ>:5000/ID будет отображать пост с соответствующим номером ID, если такой идентификатор существует в базе данных.

  1. Откройте файл app.py:

    nano app.py
    
  2. Замените содержимое файла следующим кодом:

    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.

  3. Создайте и откройте файл-шаблон post.html в директории templates:

    nano templates/post.html
    
  4. Добавьте в файл следующий код:

    {% 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.

  5. Откройте URL http://<публичный_IP-адрес_ВМ>:5000/1 и http://<публичный_IP-адрес_ВМ>:5000/2, чтобы просмотреть два первых поста, созданных одновременно с базой данных.

    Откройте URL http://<публичный_IP-адрес_ВМ>:5000/3, и вы увидите страницу с ошибкой о том, что запрашиваемый пост блога не найден.

  6. Добавьте на главную страницу приложения ссылки, ведущие с заголовков постов на страницы просмотра их содержимого.

    Откройте шаблон index.html:

    nano templates/index.html
    
  7. Замените значение атрибута 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.

  8. Перейдите к главной странице приложения http://<публичный_IP-адрес_ВМ>:5000/ и убедитесь, что заголовки постов работают как ссылки для перехода к их содержимому.

Добавьте действия с постамиДобавьте действия с постами

Добавьте в ваше веб-приложение возможность создавать в блоге новые посты, а также редактировать существующие и удалять ненужные.

Создание нового постаСоздание нового поста

Создайте страницу, на которой пользователь сможет написать новый пост, состоящий из заголовка и содержания.

  1. Откройте файл app.py:

    nano app.py
    
  2. Замените содержимое файла следующим кодом:

    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.html HTML-код страницы создания поста блога. Декоратор создает маршрут /create, который принимает GET и POST-запросы. По умолчанию принимаются только GET-запросы. Для того чтобы маршрут также принимал POST-запросы, которые используются браузером при передаче данных форм, в аргументе methods передается кортеж с допустимыми типами запросов.

    Сохраните и закройте файл app.py.

  3. Создайте и откройте файл-шаблон create.html в директории templates:

    nano templates/create.html
    
  4. Добавьте в файл следующий код:

    {% 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.

  5. Откройте URL http://<публичный_IP-адрес_ВМ>:5000/create и убедитесь, что отображается страница Create a New Post с полями для заголовка и содержания поста, а также кнопкой для отправки формы.

    Форма с этой страницы отправляет POST-запрос в функцию визуализации create(). Но в функции сейчас отсутствует код обработки POST-запроса, поэтому после заполнения и отправки формы введенные данные не будут сохранены в БД. Исправьте это, изменив функцию визуализации create().

  6. Откройте файл app.py:

    nano app.py
    
  7. Замените содержимое функции визуализации 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.

  8. Откройте URL http://<публичный_IP-адрес_ВМ>:5000/create, введите заголовок и содержание поста и отправьте форму. После отправки формы вы будете перенаправлены на главную страницу приложения, где вы увидите список всех постов, включая новый.

  9. В шаблоне base.html добавьте отображение всплывающего сообщения и добавьте на панель навигации ссылку на форму создания нового поста. Откройте шаблон base.html:

    nano templates/base.html
    
  10. Перед ссылкой 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>
    ...
    
  11. В блоке <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.

  12. Перейдите на главную страницу приложения http://<публичный_IP-адрес_ВМ>:5000 и убедитесь, что по ссылке New Post на панели навигации можно создать новый пост.

Редактирование существующего постаРедактирование существующего поста

Создайте страницу, на которой можно отредактировать существующий пост.

  1. Откройте файл app.py:

    nano app.py
    
  2. В конце файла добавьте функцию визуализации 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.

  3. Создайте и откройте файл-шаблон edit.html в директории templates:

    nano templates/edit.html
    
  4. Добавьте в файл следующий код:

    {% 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.

  5. Откройте URL http://<публичный_IP-адрес_ВМ>:5000/1/edit для редактирования первого поста. Вы увидите страницу Edit "First Post" с данными поста. Отредактируйте пост, отправьте форму и убедитесь, что пост был обновлен.

  6. На главной странице приложения добавьте для каждого поста из списка ссылку на страницу редактирования. Откройте шаблон index.html:

    nano templates/index.html
    
  7. После даты создания поста добавьте ссылку для его редактирования:

    ...
      {% 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.

  8. Перейдите на главную страницу приложения http://<публичный_IP-адрес_ВМ>:5000 и убедитесь, что на ней появились ссылки Edit, с помощью которых можно изменять содержимое постов.

Удаление постаУдаление поста

  1. Откройте файл app.py:

    nano app.py
    
  2. В конце файла добавьте функцию визуализации 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.

  3. Откройте шаблон edit.html:

    nano templates/edit.html
    
  4. Непосредственно после тега <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.

  5. Перейдите на главную страницу приложения http://<публичный_IP-адрес_ВМ>:5000 и убедитесь, что на странице редактирования поста появилась кнопка Delete Post, удаляющая пост.

Разработка вашего приложения завершена. Если вы все сделали правильно, итоговый исходный код вашего проекта соответствует коду в репозитории.

Подведите итогиПодведите итоги

В ходе выполнения данного руководства вы использовали фреймворк Flask, СУБД SQLite, набор инструментов Werkzeug, механизм шаблонов Jinja и инструментарий Bootstrap, чтобы создать небольшое веб-приложение. В результате вы получили полностью функциональный блог, позволяющий создавать, читать, редактировать и удалять посты при помощи параметров URL и HTML-форм.

Вы можете самостоятельно продолжить разработку этого проекта с помощью большого количества расширений Flask, созданных сообществом, например:

  • Flask-Login — позволяет управлять сеансами пользователей: решает задачи входа в систему и выхода из системы, а также управления сеансами пользователей.
  • Flask-SQLAlchemy — упрощает использование Flask за счет поддержки SQLAlchemy. Это инструментарий Python SQL и Object Relational Mapper, разработанный для взаимодействия с базами данных.
  • Flask-Mail — помогает отправлять сообщения электронной почты из приложений Flask.

Удалите созданные ресурсыУдалите созданные ресурсы

Чтобы перестать платить за развернутый веб-сервер, удалите виртуальную машину.

Если вы зарезервировали для вашего веб-сервера статический публичный IP-адрес, удалите его.

Была ли статья полезна?

Предыдущая
Terraform
Следующая
Создание SAP-программы в Yandex Cloud
Создавайте контент и получайте гранты!Готовы написать своё руководство? Участвуйте в контент-программе и получайте гранты на работу с облачными сервисами!
Подробнее о программе
Сделано на Gravity UI
Проект Яндекса
© 2026 ООО «Яндекс.Облако»