One place for hosting & domains

      вебприложения

      Создание веб-приложения с помощью Flask в Python 3


      Автор выбрал фонд Free and Open Source Fund для получения пожертвования в рамках программы Write for DOnations.

      Введение

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

      В рамках этого обучающего руководства вы будете использовать инструментарий Bootstrap, чтобы сделать ваше приложение визуально привлекательным. Bootstrap поможет вам включить в ваше веб-приложение быстрые веб-страницы, чтобы приложение можно было использовать на мобильных браузерах без написания для этого собственных кодов HTML, CSS и JavaScript. Инструментарий дает возможность изучить работу Flask.

      Flask использует механизм шаблонов Jinja для динамического создания HTML-страниц с использованием знакомых понятий в Python, таких как переменные, циклы, списки и т. д. Вы будете использовать эти шаблоны в рамках этого проекта.

      С помощью этого обучающего руководства вы создадите небольшой веб-блог с использованием Flask и SQLite в Python 3. Пользователи приложения могут видеть все посты в вашей базе данных и нажимать на заголовки постов для просмотра их содержания. Кроме того, присутствует возможность добавлять новый пост в базу данных и редактировать или удалять существующий пост.

      Предварительные требования

      Перед началом выполнения этого руководства вам потребуется:

      Шаг 1 — Установка Flask

      На этом этапе вы активируете вашу среду Python и установите Flask с помощью установщика пакетов pip.

      Если вы еще не активировали среду программирования, убедитесь, что находитесь в директории проекта (flask_blog), и с помощью следующей команды активируйте среду:

      После активации среды программирования в вашей командной строке появится префикс env, и она будет выглядеть следующим образом:

      Данный префикс служит индикатором того, что среда env активна (имя среды может быть другим, в зависимости от того, как вы назвали ее при создании).

      Примечание. Вы можете использовать Git, систему контроля версий, для эффективного управления и отслеживания процесса разработки вашего проекта. О том, как использовать Git, вы можете узнать из нашей статьи Введение в Git: установка, использование и ответвления.

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

      Теперь вы можете установить пакеты Python и изолировать ваш код проекта от основной системы Python. Это делается с помощью pip и python.

      Чтобы установить Flask, запустите следующую команду:

      После завершения установки запустите следующую команду, чтобы подтвердить установку:

      • python -c "import flask; print(flask.__version__)"

      Используйте интерфейс командной строки python с опцией -c для выполнения кода Python. Далее вы импортируете пакет flask с помощью import flask; и затем распечатываете версию Flask через переменную flask.__version__.

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

      Output

      1.1.2

      Вы создали папку проекта, виртуальную среду и установили Flask. Теперь вы готовы переходить к настройке базового приложения.

      Шаг 2 — Создание базового приложения

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

      В директории flask_blog откройте файл с именем hello.py для редактирования. Используйте nano или любой другой текстовый редактор:

      Этот файл hello.py будет служить минимальным примером того, как обрабатывать запросы HTTP. Внутри него импортируйте объект Flask и создайте функцию, которая возвращает ответ HTTP. Напишите следующий код внутри файла hello.py​​​:

      flask_blog/hello.py

      from flask import Flask
      
      app = Flask(__name__)
      
      
      @app.route('/')
      def hello():
          return 'Hello, World!'
      

      В предыдущем блоке кода необходимо предварительно импортировать объект Flask из пакета flask. Затем вы создаете ваш экземпляр приложения Flask с именем app. Вы передаете специальную переменную __name__, которая содержит имя текущего модуля Python. Она указывает экземпляру его расположение. Это необходимо, так как Flask устанавливает ряд путей за кадром.

      Создав экземпляр app, вы начинаете используете его для обработки поступающих веб-запросов и отправки ответов пользователю. @app.route​​​ — это декоратор, который превращает стандартную функцию Python в функцию просмотра Flask, конвертирующую возвращаемое значение функции в ответ HTTP, который отображается клиентом HTTP, например веб-браузером. Вы передаете значение '/' в @app.route()​​​ для обозначения того, что эта функция будет отвечать на веб-запросы для URL /, который является основным URL-адресом.

      Функция просмотра hello() возвращает строку 'Hello, World!' в качестве ответа.

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

      Для запуска вашего веб-приложения сначала укажите Flask, где искать приложение (в вашем случае файл hello.py) с помощью переменной среды FLASK_APP:

      Затем запустите его в режиме разработки с помощью переменной среды FLASK_ENV:

      • export FLASK_ENV=development

      И наконец, запустите приложение, используя команду flask run:

      После запуска приложения вы увидите примерно следующее:

      Output

      * Serving Flask app "hello" (lazy loading) * Environment: development * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger PIN: 813-894-335

      В предыдущем выводе представлена следующая информация:

      • Название работающего приложения.
      • Среда, в которой работает приложение.
      • Debug mode: on означает, что отладчик Flask работает. Эта функция полезна при разработке, так как при возникновении проблем она выдает детализированные сообщения об ошибке, что упрощает работу по их устранению.
      • Приложение работает локально на URL http://127.0.0.1:5000/, где 127.0.0.1 — это IP-адрес localhost вашего компьютера, а :5000 — номер порта.

      Откройте браузер и введите URL ​​​​​​http://127.0.0.1:5000/​​​​​​. Вы получите строку Hello, World! в качестве ответа. Это подтверждает то, что ваше приложение успешно работает.

      Внимание. Flask использует простой веб-сервер для обслуживания нашего приложения в среде разработки, что также означает, что отладчик Flask работает для облегчения работы по обнаружению ошибок. Этот сервер для разработки не должен использоваться при развертывании в производственной среде. Дополнительную информацию можно найти в документации Flask на странице Опции развертывания или в обучающем руководстве по развертыванию Flask.

      Теперь вы можете оставить сервер разработки работать в терминале и открыть другое окно терминала. Перейдите в папку проекта, где находится файл hello.py​​​, активируйте виртуальную среду, установите переменные среды FLASK_ENV и FLASK_APP, а затем выполните следующие шаги (эти команды перечислены ранее на этом шаге).

      Примечание. При открытии нового терминала важно помнить об активации виртуальной среды и настройке переменных среды FLASK_ENV и FLASK_APP.

      Если сервер разработки приложения Flask работает, невозможно запустить еще одно приложение Flask с помощью такой же команды flask run. Это связано с тем, что flask run по умолчанию использует номер порта 5000, следовательно, когда он занят, невозможно запустить другое приложение, поэтому вы увидите ошибку аналогичную этой:

      Output

      OSError: [Errno 98] Address already in use

      Для решения этой проблемы нужно либо остановить работающий в настоящий момент сервер с помощью команды CTRL+C, а затем снова задать команду flask run, или, если вам нужно,чтобы оба сервера работали одновременно, передать другой номер порта в аргумент -p, например запустить другое приложение через порт 5001, используя следующую команду:

      Теперь у вас есть небольшое веб-приложение Flask. Вы запустили ваше приложение и вывели информацию в браузере. Далее вы будете использовать файлы HTML в вашем приложении.

      Шаг 3 — Использование шаблонов HTML

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

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

      На этом этапе вы создадите свое основное приложение Flask в новом файле.

      Сначала в директории flask_blog с помощью nano или другого редактора создайте и отредактируйте файл app.py. Здесь будет хранить весь код, который вы будете использовать для создания приложения с блогом:

      В этом новом файле вы сможете импортировать объект Flask для создания экземпляра приложения Flask так же, как вы делали ранее. Кроме того, вы сможете импортировать вспомогательную функцию render_template(), позволяющую поставлять файлы шаблонов HTML, существующих в папке templates, которую вы создадите. Файл будет иметь единичную функцию просмотра, отвечающую за обработку запросов по основному маршруту /. Добавьте следующее содержимое:

      flask_blog/app.py

      from flask import Flask, render_template
      
      app = Flask(__name__)
      
      @app.route('/')
      def index():
          return render_template('index.html')
      

      Функция просмотра index() возвращает результат вызова render_template() с index.html​​ в качестве аргумента. Это дает указание render_template() искать файл с именем index.html в папке шаблонов. И папка, и файл еще отсутствуют. Вы получите сообщение об ошибке, если запустите приложение на этом этапе. Тем не менее при запуске вы уже будете знать об этой часто встречающейся ошибке. Затем вы сможете исправить ошибку, создав необходимые папку и файл.

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

      Остановите сервер разработки на другом терминале, на котором работает приложение hello с помощью CTRL+C.

      Перед запуском приложения убедитесь, что вы корректно указали значение для переменной среды FLASK_APP, так как вы больше не используете приложение hello:

      • export FLASK_APP=app
      • flask run

      После открытия URL http://127.0.0.1:5000/ в вашем браузере страница отладчика сообщит вам, что шаблон index.html не был найден. Основная строка кода, которая привела к этой ошибке, будет выделена. В данном случае это строка return render_template('index.html').

      Если вы нажмете на эту строку, отладчик откроет код, и у вас будет дополнительный контекст для понимания и решения проблемы.

      Отладчик Flask

      Для устранения данной ошибки создайте директорию с именем templates внутри директории flask_blog. Затем внутри нее откройте файл с именем index.html​​​ для редактирования:

      • mkdir templates
      • nano templates/index.html

      После этого добавьте следующий код HTML внутри index.html​​:

      flask_blog/templates/index.html

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>FlaskBlog</title>
      </head>
      <body>
         <h1>Welcome to FlaskBlog</h1>
      </body>
      </html>
      

      Сохраните файл и используйте свой браузер для перехода на http://127.0.0.1:5000/ или обновите страницу. Теперь браузер должен отобразить текст Welcome to FlaskBlog в теге <h1>.

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

      Вы можете создать файл стилей style.css, чтобы добавить CSS в ваше приложение. Сначала создайте директорию с именем static внутри основной директории flask_blog:

      Затем создайте другую директорию с именем css внутри директории static для хостинга файлов .css. Обычно это делается для упорядочивания статичных файлов в специальных папках. Например, файлы JavaScript обычно находятся в директории с именем js, изображения хранятся в директории images (или img) и т. д. Следующая команда создает директорию css внутри директории static:

      После этого откройте файл style.css в директории css для редактирования:

      • nano static/css/style.css

      Добавьте следующее правило CSS в файл style.css:

      flask_blog/static/css/style.css

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

      Код CSS добавит границу, изменит цвет на коричневый, выравняет текст по центру и добавит небольшое дополнение к меткам <h1>.

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

      Далее откройте файл шаблона index.html для редактирования:

      • nano templates/index.html

      Добавьте ссылку в файл style.css внутри раздела <head> файла шаблона index.html:

      flask_blog/templates/index.html

      . . .
      <head>
          <meta charset="UTF-8">
          <link rel="stylesheet" href="https://www.digitalocean.com/{{ url_for("static', filename= 'css/style.css') }}">
          <title>FlaskBlog</title>
      </head>
      . . .
      

      Для этого вы используете вспомогательную функцию url_for()​​​ для генерирования подходящего расположения файла. Первый аргумент указывает, что вы связаны со статичным файлом, а второй — это путь файла внутри статичной директории.

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

      После обновления страницы индекса вашего приложения вы увидите, что цвет текста Welcome to FlaskBlog​​​ теперь коричневый, расположен по центру и добавлен внутри границы.

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

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

      Для создания базового шаблона сначала необходимо создать файл с именем base.html внутри директории templates:

      Введите следующий код в шаблоне base.html​​:

      flask_blog/templates/base.html

      <!doctype html>
      <html lang="en">
        <head>
          <!-- Required meta tags -->
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
      
          <!-- Bootstrap CSS -->
          <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
      
          <title>{% block title %} {% endblock %}</title>
        </head>
        <body>
          <nav class="navbar navbar-expand-md navbar-light bg-light">
              <a class="navbar-brand" href="https://www.digitalocean.com/{{ url_for('index')}}">FlaskBlog</a>
              <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
                  <span class="navbar-toggler-icon"></span>
              </button>
              <div class="collapse navbar-collapse" id="navbarNav">
                  <ul class="navbar-nav">
                  <li class="nav-item active">
                      <a class="nav-link" href="https://www.digitalocean.com/#">About</a>
                  </li>
                  </ul>
              </div>
          </nav>
          <div class="container">
              {% block content %} {% endblock %}
          </div>
      
          <!-- Optional JavaScript -->
          <!-- jQuery first, then Popper.js, then Bootstrap JS -->
          <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
          <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
          <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
        </body>
      </html>
      

      Сохраните и закройте файл после редактирования.

      Основная часть кода в предыдущем блоке состоит из стандартного HTML и кода, необходимого для Bootstrap. Теги <meta> содержат информацию для веб-браузера, тег <link> привязывает файлы CSS Bootstrap, а теги <script> являются ссылками на код JavaScript, который позволяет выполнить ряд дополнительных функций Bootstrap. Дополнительную информацию по этому вопросу можно найти в документации Bootstrap.

      Однако следующие выделенные части относятся к механизму шаблонов Jinja:

      • {% block title %} {% endblock %}: блок, замещающий заголовок. Позже вы будете использовать его в других шаблонах для написания пользовательского заголовка для каждой страницы в приложении без перезаписи всего раздела <head> каждый раз.
      • {{ url_for('index')}}: вызов функции, который возвращает URL для функции просмотра index(). Этот вариант отличается от прошлого вызова url_for(), который использовался для привязки статичного файла CSS. Он использует только один аргумент, а именно имя функции просмотра, и привязывается к маршруту, связанному с функцией вместо статичного файла.
      • {% block content %} {% endblock %}: еще один блок, который будет замещен контентом в зависимости от дочернего шаблона (шаблоны, которые наследуются из base.html), который заменит его.

      Теперь, когда у вас есть базовый шаблон, вы можете воспользоваться преимуществом, используя наследование. Откройте файл index.html:

      • nano templates/index.html

      Замените его содержание на следующее:

      flask_blog/templates/index.html

      {% extends 'base.html' %}
      
      {% block content %}
          <h1>{% block title %} Welcome to FlaskBlog {% endblock %}</h1>
      {% endblock %}
      

      В этой новой версии шаблона index.html вы используете тег {% extends %} для наследования из шаблона base.html. Затем вы расширите его, заменив блок content в базовом шаблоне на содержимое блока content из предыдущего блока кода.

      Этот блок content содержит тег <h1> с текстом Welcome to FlaskBlog внутри блока title, который в свою очередь заменяет оригинальный блок title в шаблоне base.html текстом Welcome to FlaskBlog. Так вы можете избежать повторения одного и того же текста дважды, так как он используется и в качестве названия страницы, и в качестве заголовка, который появляется под панелью навигации, унаследованной от базового шаблона.

      Наследование шаблона также дает возможность использовать код HTML из других шаблонов (в данном случае base.html) без повторения его при каждой необходимости.

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

      Страница индекса с Bootstrap

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

      Шаг 4 — Настройка базы данных

      На этом этапе вы настроите базу данных для хранения данных, в случае с вашим приложением — это посты блога. Вы также наполните базу несколькими примерами.

      Вы будете использовать файл базы данных SQLite для хранения ваших данных, так как модуль sqlite3, который мы будем использовать для взаимодействия с базой, уже присутствует в стандартной библиотеке Python. Дополнительную информацию о SQLite можно найти в этом обучающем руководстве.

      Поскольку данные в SQLite хранятся в таблицах и столбцах, а ваши данные в основном состоят из постов блога, в первую очередь вам понадобится создать таблицу с именем posts и необходимыми столбцами. Вы создадите файл .sql, содержащий команды SQL для создания таблицы posts с несколькими столбцами. Затем вы будете использовать этот файл для создания базы данных.

      Откройте файл с именем schema.sql внутри директории flask_blog:

      Введите следующие команды SQL в этом файле:

      flask_blog/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
      );
      

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

      Первая команда SQL — это DROP TABLE IF EXISTS posts;.Она удаляет все уже существующие таблицы с именем posts, чтобы не создавать путаницу. Обратите внимание, что при использовании этих команд SQL будет удалено все содержимое базы данных, поэтому не записывайте ничего важного в веб-приложении до окончания изучения этого руководства и экспериментируйте с финальным результатом. Далее, команда CREATE TABLE posts​​​ используется для создания таблицы posts со следующими столбцами:

      • id: целое число, которое представляет собой первичный ключ. В базе данных будет присвоено уникальное значение каждой записи (посту блога).
      • created: время создания поста блога. NOT NULL означает, что столбец не может быть пустым, а значение DEFAULT — это CURRENT_TIMESTAMP​​​, т. е. время добавления поста в базу. Точно так же как и в случае с id, вам не нужно указывать значение для этого столбца, так как он будет заполнен автоматически.
      • title: заголовок поста.
      • content: содержание поста.

      Теперь, когда у вас есть схема SQL в файле schema.sql, вы сможете использовать ее для создания базы данных с помощью файла Python, который сформирует файл базы данных SQLite .db. Откройте файл с именем init_db.py внутри директории flask_blog с помощью любого предпочитаемого редактора:

      А затем добавьте следующий код.

      flask_blog/init_db.py

      import sqlite3
      
      connection = sqlite3.connect('database.db')
      
      
      with open('schema.sql') as f:
          connection.executescript(f.read())
      
      cur = connection.cursor()
      
      cur.execute("INSERT INTO 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() для открытия файла schema.sql. Далее вы оформляете его содержание с помощью метода executescript(), при котором выполняется несколько операторов SQL одновременно, что формирует таблицу posts. Вы создаете объект курсор, который позволяет вам использовать метод execute() для выполнения двух операторов SQL INSERT, чтобы добавить два поста блога в таблицу posts. И наконец, вы вносите изменения и закрываете соединение.

      Сохраните и закройте файл, а затем запустите его в терминале с помощью команды python:

      После завершения выполнения файла новый файл с именем database.db появится в директории flask_blog. Это означает, что вы успешно настроили свою базу данных.

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

      Шаг 5 — Отображение всех постов

      Теперь, когда вы настроили базу данных, можно изменить функцию просмотра index(), чтобы отображать все посты, внесенные в базу данных.

      Откройте файл app.py, чтобы произвести следующие изменения:

      Для первого изменения необходимо импортировать модуль sqlite3 в верхней части файла:

      flask_blog/app.py

      import sqlite3
      from flask import Flask, render_template
      
      . . .
      

      Далее вы создадите функцию, которая обеспечивает подключение к базе данных, и вернете ее. Добавьте ее непосредственно после импорта:

      flask_blog/app.py

      . . .
      from flask import Flask, render_template
      
      def get_db_connection():
          conn = sqlite3.connect('database.db')
          conn.row_factory = sqlite3.Row
          return conn
      
      . . .
      

      Эта функция get_db_connection() открывает соединение с файлом базы данных database.db, а затем устанавливает атрибут row_factory в sqlite3. Row, чтобы получить доступ к столбцам на основе имен. Это означает, что подключение к базе данных будет возвращать строки, которые ведут себя как обычные словари Python. И наконец, функция возвращает объект подключения conn, который вы будете использовать для доступа к базе данных.

      После определения функции get_db_connection() необходимо изменить функцию index() следующим образом:

      flask_blog/app.py

      . . .
      
      @app.route('/')
      def index():
          conn = get_db_connection()
          posts = conn.execute('SELECT * FROM posts').fetchall()
          conn.close()
          return render_template('index.html"https://www.digitalocean.com/, posts=posts)
      

      В этой новой версии функции index() сначала откройте подключение к базе данных, используя функцию get_db_connection(), которую вы определили ранее. После этого выполните запрос SQL, чтобы выбрать все записи из таблицы posts. Вы применяете метод fetchall(), чтобы доставить все строки результата запроса. Это вернет список постов, внесенных в базу данных на предыдущем шаге.

      Вы закрываете подключение к базе данных, используя метод close(), и возвращаете результат отображения шаблона index.html. Вы также передаете объект posts в качестве аргумента, который содержит результаты, полученные из базы данных. Это откроет вам доступ к постам блога в шаблоне index.html.

      После внесения этих изменений сохраните и закройте файл app.py.

      Теперь, после передачи постов, доставленных из базы данных в шаблон index.html, вы можете использовать цикл for для отображения каждого поста на странице индекса.

      Откройте файл index.html:

      • nano templates/index.html

      Затем измените его следующим образом:

      flask_blog/templates/index.html

      {% extends 'base.html' %}
      
      {% block content %}
          <h1>{% block title %} Welcome to FlaskBlog {% endblock %}</h1>
          {% for post in posts %}
              <a href="https://www.digitalocean.com/#">
                  <h2>{{ post['title'] }}</h2>
              </a>
              <span class="badge badge-primary">{{ post['created'] }}</span>
              <hr>
          {% endfor %}
      {% endblock %}
      

      В данном случае синтаксис {% for post in posts %} — это цикл for Jinja , который аналогичен циклу for Python, за исключением того, что позже он закрывается с помощью синтаксиса {% endfor %}. Вы используете этот синтаксис для создания цикла для каждого элемента в списке posts, который был передан функцией index() в строке return render_template('index.html', posts=posts). Внутри этого цикла for вы отображаете название поста в заголовке <h2>​​​ в теге <a> (позже вы будете использовать этот тег для привязки каждого поста в отдельности).

      Вы отображаете заголовок с помощью буквального разделителя переменной ({{ ... }}​​​​​​). Не забывайте, что post представляет собой объект словарного типа, поэтому вы сможете получить доступ к заголовку поста с помощью post['title']. Также вы отображаете дату создания поста помощью такого же метода.

      После редактирования файла сохраните и закройте его. Затем перейдите на страницу индекса в браузере. Вы увидите на своей странице два поста, которые вы добавили в базу данных.

      Страница индекса с отображенными постами

      Теперь, когда вы изменили функцию просмотра index(), чтобы отобразить все посты, находящиеся в базе данных на домашней странице приложения, вы можете переходить к отображению каждого поста на отдельной странице и разрешить пользователям переходить по ссылке к каждому отдельному посту.

      Шаг 6 — Отображение отдельного поста

      На этом шаге вы создадите новый маршрут Flask с функцией просмотра и новым шаблоном HTML для отображения отдельного поста блога по его ID.

      К концу этого шага на URL http://127.0.0.1:5000/1 будет находиться страница, на которой отображается первый пост (так как его ID 1). На URL http://127.0.0.1:5000/ID будет отображаться пост с соответствующим номером ID, если такой существует.

      Откройте app.py для редактирования:

      Поскольку позже в рамках данного проекта вам потребуется получить пост блога по его ID из базы данных в нескольких расположениях, вы создадите отдельную функцию с именем get_post(). Вы можете вызвать его, передав ID, и получить обратно пост блога, связанный с предоставленным ID, или создать ответ Flask с сообщением 404 Not Found​​​, если пост блога не существует.

      Для ответа в виде страницы 404 вам необходимо импортировать функцию abort() из библиотеки Werkzeug, установленной вместе с Flask в верхней части файла:

      flask_blog/app.py

      import sqlite3
      from flask import Flask, render_template
      from werkzeug.exceptions import abort
      
      . . .
      

      Затем добавьте функцию get_post() сразу после функции get_db_connection(), созданной вами на предыдущем шаге:

      flask_blog/app.py

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

      Эта новая функция имеет аргумент post_id, который определяет, какой пост блога предназначен для возврата.

      Внутри функции вы используете функцию get_db_connection() для открытия подключения к базе данных и выполнения запроса SQL, чтобы получить пост блога, связанный с указанным значением post_id. Вы добавляете метод fetchone() для получения результата и хранения его в переменной post, а затем закрываете подключение. Если переменная post имеет значение None​​​, т. е. результат не найден в базе данных, вы используете функцию abort(), импортированную вами ранее, для ответа с помощью кода ошибки 404, и функция завершит выполнение. Если же пост был найден, вы возвращаете значение переменной post.

      Далее добавьте следующую функцию просмотра в конце файла app.py:

      flask_blog/app.py

      . . .
      
      @app.route('/<int:post_id>')
      def post(post_id):
          post = get_post(post_id)
          return render_template('post.html', post=post)
      

      В этой новой функции просмотра вы добавляете правило переменной <int:post_id>, чтобы указать, что часть после слеша (/) представляет собой положительное целое число (отмеченное конвертером int), которое вам необходимо в функции просмотра. Flask распознает это и передает его значение аргументу ключевого слова post_id​​ вашей функции просмотра post(). Затем вы используете функцию get_post() для получения поста блога, связанного с заданным ID, и хранения результата в переменной post, которую вы передаете шаблону post.html. Этот шаблон вы создадите вскоре.

      Сохраните файл app.py и новый файл шаблона post.html для редактирования:

      Введите следующий код в этом новом файле post.html. Это будет аналогично файлу index.html, за исключением того, что он будет отображать единичный пост, так же как и содержание поста:

      flask_blog/templates/post.html

      {% extends 'base.html' %}
      
      {% block content %}
          <h2>{% block title %} {{ post['title'] }} {% endblock %}</h2>
          <span class="badge badge-primary">{{ post['created'] }}</span>
          <p>{{ post['content'] }}</p>
      {% endblock %}
      

      Вы добавляете блок title, который вы определили в шаблоне base.html, чтобы заголовок страницы отражал заголовок поста, в то же время отображаемый в заголовке <h2>.

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

      Теперь вы можете переходить к следующим URL-адресам для просмотра двух постов из вашей базы данных вместе со страницей, которая указывает пользователю, что запрашиваемый пост блога не обнаружен (так как пост с номером ID 3 пока отсутствует):

      http://127.0.0.1:5000/1
      http://127.0.0.1:5000/2
      http://127.0.0.1:5000/3
      

      Вернувшись на страницу индекса, вы осуществите привязку заголовка каждого поста к соответствующей странице. Сделать это можно с помощью функции url_for(). Сначала откройте шаблон index.html для редактирования:

      • nano templates/index.html

      Затем измените значение атрибута href с # на {{ url_for('post', post_id=post['id']) }}, чтобы цикл for выглядел следующим образом:

      flask_blog/templates/index.html

      {% for post in posts %}
          <a href="https://www.digitalocean.com/{{ url_for('post', post_id=post['id']) }}">
              <h2>{{ post['title'] }}</h2>
          </a>
          <span class="badge badge-primary">{{ post['created'] }}</span>
          <hr>
      {% endfor %}
      

      Здесь вы передаете 'post' в функцию url_for() в качестве первого аргумента. Это имя функции просмотра post(), и поскольку она принимает аргумент post_id, вы присваиваете значение post['id']. Функция url_for() возвращает правильный URL для каждого поста на основании его ID.

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

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

      Шаг 7 — Изменение постов

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

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

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

      Откройте файл app.py для редактирования:

      Сначала импортируйте следующее из фреймворка Flask:

      • Глобальный объект request для доступа к входящим данным запроса, которые будут подаваться через форму HTML.
      • Функция url_for() для генерирования URL-адресов.
      • Функция flash() для появления сообщения при обработке запроса.
      • Функция redirect() для перенаправления клиента в другое расположение.

      Добавьте импортированные функции в ваш файл следующим образом:

      flask_blog/app.py

      import sqlite3
      from flask import Flask, render_template, request, url_for, flash, redirect
      from werkzeug.exceptions import abort
      
      . . .
      

      Функция flash() хранит всплывающие сообщения в сеансе браузера клиента, что требует настройки секретного ключа. Этот секретный ключ используется для защищенных сеансов, что позволяет Flask запоминать информацию от одного запроса к другому, например, переходить от страницы нового поста к странице индекса. Пользователь может получить доступ к информации, хранящейся в сеансе, но не может изменить ее без секретного ключа. Поэтому никогда никому не передавайте доступ к вашему секретному ключу. Дополнительную информацию можно найти в документации Flask о сеансах.

      Для настройки секретного ключа вам нужно добавить конфигурацию SECRET_KEY в ваше приложение через объект app.config. Добавьте ее непосредственно после определения app перед определением функции просмотра index():

      flask_blog/app.py

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

      Не забывайте, что секретный ключ должен представлять собой длинную случайную строку.

      После настройки секретного ключа вы создадите функцию просмотра, которая будет отображать шаблон, показывающий форму, которую вы можете заполнить для создания нового поста в блоге. Добавьте эту новую функцию внизу файла:

      flask_blog/app.py

      . . .
      
      @app.route('/create', methods=('GET', 'POST'))
      def create():
          return render_template('create.html')
      

      Это создает маршрут /create, который принимает запросы GET и POST. Запросы GET принимаются по умолчанию. Для того чтобы также принимать запросы POST, которые посылаются браузером при подаче форм, вы передаете кортеж с приемлемыми типами запросов в аргумент methods декоратора @app.route().

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

      Для создания шаблона откройте файл с именем create.html внутри папки templates:

      • nano templates/create.html

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

      flask_blog/templates/create.html

      {% extends 'base.html' %}
      
      {% block content %}
      <h1>{% block title %} Create a New Post {% endblock %}</h1>
      
      <form method="post">
          <div class="form-group">
              <label for="title">Title</label>
              <input type="text" name="title"
                     placeholder="Post title" class="form-control"
                     value="{{ request.form['title'] }}"></input>
          </div>
      
          <div class="form-group">
              <label for="content">Content</label>
              <textarea name="content" placeholder="Post content"
                        class="form-control">{{ request.form['content'] }}</textarea>
          </div>
          <div class="form-group">
              <button type="submit" class="btn btn-primary">Submit</button>
          </div>
      </form>
      {% endblock %}
      

      Большая часть этого кода — стандартный HTML. Он будет отображать поле ввода для заголовка поста, текстовое пространство для содержания поста и кнопку для отправки формы.

      Значением ввода заголовка поста является {{ request.form['title'] }}, а текстовое пространство имеет значение {{ request.form['content'] }}. Это сделано для того, чтобы вводимые вами данные не потерялись, если что-то пойдет не по плану. Например, если вы напишете длинный пост и забудете дать ему заголовок, отобразится сообщение, информирующее вас о том, что указание заголовка является обязательным. Это произойдет без потери уже написанного поста, так как он будет сохранен в глобальном объекте request, к которому у вас есть доступ в шаблонах.

      Теперь, когда сервер разработки работает, используйте браузер для перехода к маршруту /create:

      http://127.0.0.1:5000/create
      

      Вы увидите страницу Create a New Post​​​ (Создать новый пост) с полями для заголовка и содержания.

      Страница создания нового поста

      Эта форма подает запрос POST в функцию просмотра create(). Но в функции пока отсутствует код обработки запроса POST, поэтому после заполнения формы и ее отправки ничего не происходит.

      Вы будете обрабатывать входящий запрос POST после отправки формы. Это можно сделать в функции просмотра create(). Вы можете отдельно обрабатывать запрос POST, проверяя значение request.method. Когда задано значение 'POST'​​​, это означает, что полученный запрос — это запрос POST. Тогда вы можете переходить к извлечению отправленных данных, их проверке и внесению в базу.

      Откройте файл app.py для редактирования:

      Измените функцию просмотра create() следующим образом:

      flask_blog/app.py

      . . .
      
      @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 вы обеспечиваете, что следующий за ним код выполняется только в случае, если запрос является запросом POST через сравнение request.method == 'POST'​​​.

      Затем вы извлекаете отправленные заголовок и содержание из объекта request.form, который дает вам доступ к данным формы в запросе. Если заголовок не указан, будет выполнено условие if not title, и отобразится сообщение для пользователя с информацией о необходимости заголовка. С другой стороны, если заголовок указан, вы открываете подключение с помощью функции get_db_connection() и вставляете полученные заголовок и содержание в таблицу posts.

      Затем вы вносите изменения в базу данных и закрываете подключение. После добавления поста блога в базу данных вы перенаправляете клиента на страницу индекса с помощью функции redirect(), передавая URL, сгенерированный функцией url_for() со значением 'index'​​ в качестве аргумента.

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

      Теперь перейдите к маршруту /create, используя ваш веб-браузер:

      http://127.0.0.1:5000/create
      

      Внесите в форму любой заголовок и какой-либо контент. После отправки формы вы увидите новый пост, указанный на странице индекса.

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

      Отредактируйте файл, добавив новый тег <li> после ссылки About​​ внутри тега <nav>​​. Затем добавьте новый цикл for непосредственно над блоком content для отображения всплывающих сообщений под панелью навигации. Эти сообщения доступны в специальной функции get_flashed_messages()​​​​​​, имеющейся в Flask:

      flask_blog/templates/base.html

      <nav class="navbar navbar-expand-md navbar-light bg-light">
          <a class="navbar-brand" href="https://www.digitalocean.com/{{ url_for("index')}}">FlaskBlog</a>
          <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
              <span class="navbar-toggler-icon"></span>
          </button>
          <div class="collapse navbar-collapse" id="navbarNav">
              <ul class="navbar-nav">
              <li class="nav-item">
                  <a class="nav-link" href="https://www.digitalocean.com/#">About</a>
              </li>
              <li class="nav-item">
                  <a class="nav-link" href="https://www.digitalocean.com/{{url_for("create')}}">New Post</a>
              </li>
              </ul>
          </div>
      </nav>
      <div class="container">
          {% for message in get_flashed_messages() %}
              <div class="alert alert-danger">{{ message }}</div>
          {% endfor %}
          {% block content %} {% endblock %}
      </div>
      

      Сохраните и закройте файл. Теперь на панели навигации будет присутствовать элемент New Post, который связан с маршрутом /create.

      Редактирование поста

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

      Сначала вам нужно добавить новый маршрут в файл app.py. Его функция просмотра получит ID поста, который необходимо отредактировать. А URL будет представлен в формате /post_id/edit, где переменная post_id — это ID​​​ поста. Откройте файл app.py для редактирования:

      Далее добавьте следующую функцию просмотра edit() в конце файла. Редактирование существующего поста аналогично созданию нового поста, поэтому эта функция просмотра будет такой же, как и функция просмотра create():

      flask_blog/app.py

      . . .
      
      @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)
      

      Пост, который вы редактируете, определяется с помощью URL, а Flask будет передавать номер ID функции edit() через аргумент id. Вы добавляете это значение к функции get_post(), чтобы доставить пост, связанный с указанным ID, из базы данных. Новые данные будут поступать в запросе POST, который обрабатывается внутри условия if request.method == 'POST'.

      Как и при создании нового поста, сначала вы извлекаете данные из объекта request.form, затем показываете всплывающее сообщение, если заголовок имеет пустое значение, в противном случае открываете подключение к базе данных. После этого вы обновляете таблицу posts, указывая новый заголовок и новое содержание, где ID поста в базе данных соответствует ID, который был указан в URL.

      В случае запроса GET вы используете шаблон edit.html, передавая переменную post, которая содержит возвращенное значение функции get_post(). Вы будете это использовать для отображения существующих заголовка и содержания на странице редактирования.

      Сохраните и закройте файл, а затем создайте новый шаблон edit.html:

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

      flask_blog/templates/edit.html

      {% extends 'base.html' %}
      
      {% block content %}
      <h1>{% block title %} Edit "{{ post['title'] }}" {% endblock %}</h1>
      
      <form method="post">
          <div class="form-group">
              <label for="title">Title</label>
              <input type="text" name="title" placeholder="Post title"
                     class="form-control"
                     value="{{ request.form['title'] or post['title'] }}">
              </input>
          </div>
      
          <div class="form-group">
              <label for="content">Content</label>
              <textarea name="content" placeholder="Post content"
                        class="form-control">{{ request.form['content'] or post['content'] }}</textarea>
          </div>
          <div class="form-group">
              <button type="submit" class="btn btn-primary">Submit</button>
          </div>
      </form>
      <hr>
      {% endblock %}
      

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

      Этот код пишется по той же схеме, за исключением синтаксиса {{ request.form['title'] or post['title'] }} и {{ request.form['content'] or post['content'] }}. Здесь отображаются данные, сохраненные в запросе, если он существует. В противном случае отображаются данные из переменной post, которая была передана в шаблон, содержащий данные текущей базы данных.

      Теперь вы можете перейти к следующему URL для редактирования первого поста:

      http://127.0.0.1:5000/1/edit
      

      Вы увидите страницу Edit “First Post”(Редактировать «Первый пост»).

      Страница редактирования поста

      Отредактируйте пост и отправьте форму, затем убедитесь, что пост был обновлен.

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

      • nano templates/index.html

      Отредактируйте файл, чтобы он выглядел в точности, как показано ниже:

      flask_blog/templates/index.html

      {% extends 'base.html' %}
      
      {% block content %}
          <h1>{% block title %} Welcome to FlaskBlog {% endblock %}</h1>
          {% for post in posts %}
              <a href="https://www.digitalocean.com/{{ url_for("post', post_id=post['id']) }}">
                  <h2>{{ post['title'] }}</h2>
              </a>
              <span class="badge badge-primary">{{ post['created'] }}</span>
              <a href="https://www.digitalocean.com/{{ url_for("edit', id=post['id']) }}">
                  <span class="badge badge-warning">Edit</span>
              </a>
              <hr>
          {% endfor %}
      {% endblock %}
      

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

      Здесь вы добавляете тег <a> для ссылки на функцию просмотра edit(), передавая значение post['id'] для того, чтобы подключиться к странице редактирования каждого поста с помощью ссылки Edit​​​.

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

      Бывают случаи, когда вам больше не нужно, чтобы пост был общедоступен, поэтому важно наличие функции удаления поста. На этом шаге вы добавите функцию удаления в ваше приложение.

      Сначала вам нужно добавить новый маршрут /ID/delete, который принимает запросы POST, аналогично функции просмотра edit(). Ваша новая функция просмотра delete() будет получать ID поста для удаления из URL. Откройте файл app.py:

      Добавьте следующую функцию просмотра внизу файла:

      flask_blog/app.py

      # ....
      
      @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. Это означает, что навигация по маршруту /ID/delete в вашем браузере вернет ошибку, так как веб-браузеры по умолчанию настроены на запросы GET.

      Но вы можете получить доступ к этому маршруту через форму, которая отправляет запрос POST, передающий ID поста, который вы хотите удалить. Функция получит значение ID и будет использовать его для получения поста из базы данных с помощью функции get_post().

      Затем вы открываете подключение к базе данных и выполняете команду SQL DELETE FROM для удаления поста. Вы вносите изменения в базу данных и закрываете подключение, одновременно отображая сообщение для пользователя, о том, что пост был успешно удален, и перенаправляете на страницу индекса.

      Обратите внимание, что вы не отображаете шаблон. Это связано с тем, что вы просто добавите кнопку Delete на страницу редактирования.

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

      Затем добавьте следующий тег <form> после тега <hr> и непосредственно перед строкой {% endblock %}:

      flask_blog/templates/edit.html

      <hr>
      
      <form action="https://www.digitalocean.com/{{ 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() для отображения сообщения подтверждения перед отправкой запроса.

      Теперь снова перейдите на страницу редактирования поста блога и попробуйте удалить ее:

      http://127.0.0.1:5000/1/edit
      

      В конце этого шага исходный код вашего проекта будет выглядеть, как код на этой странице.

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

      Заключение

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

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

      У Flask есть множество расширений, созданных сообществом. Ниже представлен список расширений, который вы можете использовать для упрощения процесса разработки:

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



      Source link

      Создание с помощью Django и React современного веб-приложения для управления данными клиентов в Ubuntu 18.04


      Автор выбрал организацию Open Sourcing Mental Illness Ltd для получения пожертвований в рамках программы Write for DOnations.

      Введение

      Для подключения к Интернету и просмотра сайтов люди используют разные устройства. В связи с этим, приложения должны быть доступными из разных мест. Для традиционных сайтов часто достаточно иметь быстрый пользовательский интерфейс, однако для более сложных приложений обычно требуются другие методики и архитектуры. В ним относятся отдельные серверные и клиентские приложения REST, которые можно реализовать как клиентские веб-приложения, прогрессивные веб-приложения (PWA) или мобильные приложения.

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

      • React, система JavaScript, позволяющая разработчикам создавать веб-приложения и собственные клиентские приложения для серверных REST API.
      • Django, бесплатная веб-система Python с открытым исходным кодом, соответствующая архитектурной схеме программного обеспечения контроллера отображения моделей (MVC).
      • Система Django REST, представляющая собой мощный и гибкий набор инструментов для создания REST API в Django.

      В этом обучающем руководстве вы создадите современное веб-приложение с отдельными серверным и клиентским REST API, используя React, Django и систему Django REST. Используя React вместе с Django, вы сможете воспользоваться последними достижениями JavaScript и разработки клиентской части. Вместо создания приложения Django, использующего встроенный механизм шаблонов, вы используете React как библиотеку пользовательского интерфейса. Это позволяет воспользоваться преимуществами виртуальной объектной модели документов (DOM), декларативного подхода и компонентов, быстро обрабатывающих изменения данных.

      Создаваемое веб-приложение будет хранить записи о клиентах в базе данных, и вы сможете использовать его как основу для приложения CRM. После завершения обучения вы научитесь создавать, читать, обновлять и удалять записи, используя интерфейс в стиле React с Bootstrap 4.

      Предварительные требования

      Для данного курса обучения вам потребуется следующее:

      Шаг 1 — Создание виртуальной среды Python и установка зависимостей

      На этом шаге мы создадим виртуальную среду и установим требуемые зависимости для нашего приложения, в том числе Django, систему Django REST и заголовки django-cors-headers.

      Наше приложение будет использовать два разных сервера разработки для Django и React. Они будут работать на разных портах и функционировать как два отдельных домена. В связи с этим нам потребуется совместное использование ресурсов между разными источниками (CORS) для отправки запросов HTTP из React в Django без блокировки браузером.

      Перейдите в каталог home и создайте виртуальную среду, используя модуль venv Python 3:

      • cd ~
      • python3 -m venv ./env

      Активируйте созданную виртуальную среду с помощью source:

      Установите зависимости проекта с помощью pip. К ним будут относиться:

      • Django: веб-система для данного проекта.
      • Система Django REST: стороннее приложение, создающее REST API с помощью Django.
      • django-cors-headers: пакет для активации CORS.

      Установите систему Django:

      • pip install django djangorestframework django-cors-headers

      После установки зависимостей проекта вы сможете создать проект Django и клиентскую часть React.

      Шаг 2 — Создание проекта Django

      На этом шаге мы создадим проект Django, используя следующие команды и утилиты:

      • django-admin startproject project-name: django-admin— утилита командной строки, используемая для выполнения задач с помощью Django. Команда startproject создает новый проект Django.

      • python manage.py startapp myapp: manage.py — утилитарный сценарий, автоматически добавляемый в каждый проект Django. Он выполняет ряд административных задач, включая создание новых приложений, миграцию базы данных и локальное обслуживание проекта Django. Его команда startapp создает приложение Django внутри проекта Django. В Django термин приложение **описывает пакет Python, предоставляющий определенный набор функций проекта.

      Для начала создайте проект Django с помощью команды django-admin startproject. Назовем наш проект djangoreactproject:

      • django-admin startproject djangoreactproject

      Прежде чем продолжить, посмотрим структуру каталогов нашего проекта Django с помощью команды tree.

      Подсказка: tree — это полезная команда для просмотра структур файлов и каталогов из командной строки. Для установки вы можете использовать следующую команду:

      • sudo apt-get install tree

      Для использования перейдите в желаемый каталог с помощью команды cd, а затем введите команду tree или укажите путь к начальной точке команды tree /home/sammy/sammys-project.

      Перейдите в папку проекта djangoreactproject в корневом каталоге проекта и запустите команду tree:

      • cd ~/djangoreactproject
      • tree

      Результат будет выглядеть следующим образом:

      Output

      ├── djangoreactproject │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── manage.py

      Папка ~/djangoreactproject является корневым каталогом проекта. В этой папке содержится несколько файлов, важных для вашей работы:

      • manage.py: утилитарный сценарий, выполняющий ряд административных задач.
      • settings.py: основной файл конфигурации проекта Django, где вы можете изменять настройки проекта. В число этих настроек входят переменные, такие как INSTALLED_APPS, список строк с обозначением приложений, активированных для вашего проекта. Более подробную информацию по доступным настройкам можно найти в документации по Django.
      • urls.py: этот файл содержит список шаблонов URL и связанных просмотров. В каждом шаблоне сопоставляются URL и функция, вызываемая для этого URL. Более подробную информацию об URL и представлениях можно найти в обучающем руководстве «Создание представления Django».

      Первым шагом в работе с проектом будет настройка пакетов, установленных на предыдущем шаге, включая систему Django REST и пакет Django CORS. Для этого их нужно добавить в settings.py. Откройте файл с помощью nano или своего любимого редактора:

      • nano ~/djangoreactproject/djangoreactproject/settings.py

      Перейдите в настройки INSTALLED_APPS и добавьте приложения rest_framework и corsheaders в нижнюю часть списка:

      ~/djangoreactproject/djangoreactproject/settings.py

      ...
      INSTALLED_APPS = [
          'django.contrib.admin',
          'django.contrib.auth',
          'django.contrib.contenttypes',
          'django.contrib.sessions',
          'django.contrib.messages',
          'django.contrib.staticfiles',
          'rest_framework',
          'corsheaders'
      ]
      

      Затем добавьте промежуточный уровень corsheaders.middleware.CorsMiddleware из ранее установленного пакета CORS в параметр MIDDLEWARE. Этот параметр представляет собой перечень промежуточных уровней. Промежуточный уровень — это класс Python, который содержит код, выполняемый каждый раз, когда ваше веб-приложение обрабатывает запрос или отклик:

      ~/djangoreactproject/djangoreactproject/settings.py

      ...
      
      MIDDLEWARE = [
      ...
      'django.contrib.messages.middleware.MessageMiddleware',
      'django.middleware.clickjacking.XFrameOptionsMiddleware',
      'corsheaders.middleware.CorsMiddleware'
      ]
      

      Далее вы можете активировать CORS. Параметр CORS_ORIGIN_ALLOW_ALL указывает, нужно ли разрешать CORS для всех доменов, а CORS_ORIGIN_WHITELIST — это запись Python, содержащая перечень разрешенных URL. В нашем случае сервер разработки React будет работать по URL http://localhost:3000, и поэтому мы добавим новые параметры CORS_ORIGIN_ALLOW_ALL = False и CORS_ORIGIN_WHITELIST('localhost:3000',) в наш файл settings.py. Эти параметры можно добавить в любую часть файла:

      ~/djangoreactproject/djangoreactproject/settings.py

      
      ...
      CORS_ORIGIN_ALLOW_ALL = False
      
      CORS_ORIGIN_WHITELIST = (
             'localhost:3000',
      )
      ...
      

      Дополнительные параметры конфигурации можно найти в документации по django-cors-headers.

      После завершения работы сохраните файл и закройте редактор.

      Находясь в каталоге ~/djangoreactproject, создайте новое приложение Django под названием customers:

      • python manage.py startapp customers

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

      Затем добавьте это приложение в список установленных приложений в файле проекта settings.py, чтобы Django распознавал его как часть проекта. Откройте файл settings.py еще раз:

      • nano ~/djangoreactproject/djangoreactproject/settings.py

      Добавьте приложение customers:

      ~/djangoreactproject/djangoreactproject/settings.py

      ...
      INSTALLED_APPS = [
          ...
          'rest_framework',
          'corsheaders',
          'customers'
      ]
      ...
      

      Затем выполните миграцию базы данных и запустите локальный сервер разработки. Миграция используется Django для записи изменений модели в схему базы данных. Изменения могут включать добавление поля, удаление модели и т. д. Более подробную информацию о моделях и миграции можно найти в документе «Создание моделей Django».

      Выполните миграцию базы данных:

      Запустите локальный сервер разработки:

      • python manage.py runserver

      Вы увидите примерно следующий результат:

      Output

      Performing system checks... System check identified no issues (0 silenced). October 22, 2018 - 15:14:50 Django version 2.1.2, using settings 'djangoreactproject.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C.

      Ваше веб-приложение будет работать с http://127.0.0.1:8000. Если вы откроете этот адрес в браузере, вы должны увидеть следующую страницу:

      Демонстрационная страница Django

      Теперь оставьте приложение работать и откройте новый терминал, чтобы продолжить разработку проекта.

      Шаг 3 — Создание клиентской части React

      В этом разделе мы создадим клиентское приложение нашего проекта с помощью React.

      В React есть официальная утилита, позволяющая быстро генерировать проекты React без прямой настройки Webpack. Webpack — это компоновщик модулей, используемый для компоновки веб-ресурсов, таких как код JavaScript, стили CSS и изображения. Обычно перед использованием Webpack необходимо настроить различные параметры конфигурации, однако утилита create-react-app позволяет не работать с Webpack напрямую, если вы не захотите внести более детальные настройки. Для запуска create-react-app вы можете использовать инструмент npx, служащий для выполнения двоичных пакетов npm.

      Переключитесь на второй терминал и убедитесь, что вы находитесь в каталоге проекта:

      Создайте проект React под названием frontend с помощью create-react-app и npx:

      • npx create-react-app frontend

      Воспользуйтесь навигацией по вашему приложению React и запустите сервер разработки:

      • cd ~/djangoreactproject/frontend
      • npm start

      Ваше приложение будет запускаться с http://localhost:3000/:

      Демонстрационная страница React

      Оставьте сервер разработки React работать и откройте другое окно терминала, чтобы продолжить.

      Чтобы увидеть полную структуру каталогов проекта на этом этапе, перейдите в корневую папку root и снова запустите команду tree:

      • cd ~/djangoreactproject
      • tree

      Вы увидите следующую структуру:

      Output

      ├── customers │ ├── admin.py │ ├── apps.py │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── djangoreactproject │ ├── __init__.py │ ├── __pycache__ │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── frontend │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ ├── README.md │ ├── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ └── registerServiceWorker.js │ └── yarn.lock └── manage.py

      Наше приложение будет использовать Bootstrap 4 для стилизации интерфейса React, и поэтому мы добавим его в файл frontend/src/App.css, управляющий настройками CSS. Откройте файл:

      • nano ~/djangoreactproject/frontend/src/App.css

      Добавьте следующий элемент import в начало файла. Вы можете удалить содержание файла, хотя это не обязательно:

      ~/djangoreactproject/frontend/src/App.css

      @import  'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css';
      

      Здесь @import — это инструкция CSS, используемая для импорта правил стиля из других таблиц стилей.

      Мы создали серверную и клиентскую часть приложения. Теперь мы создадим модель клиентов и добавим демонстрационные данные.

      Шаг 4 — Создание модели клиентов и начального набора данных

      После создания приложения Django и клиентской части React мы создадим модель клиентов, представляющую таблицу базы данных, где будет храниться информация о клиентах. Вам не потребуется SQL, поскольку Django Object Relational Mapper (ORM) будет обрабатывать операции базы данных посредством сопоставления классов и переменных Python с таблицами и столбцами SQL. Так Django ORM абстрагирует взаимодействия SQL с базой данных через интерфейс Python.

      Запустите свою виртуальную среду еще раз:

      • cd ~
      • source env/bin/activate

      Перейдите в каталог customers и откройте models.py, файл Python, где хранятся модели вашего приложения:

      • cd ~/djangoreactproject/customers/
      • nano models.py

      Файл будет иметь следующее содержание:

      ~/djangoreactproject/customers/models.py

      from django.db import models
      # Create your models here.
      

      API клиентской модели уже импортирован в файл из django.db с помощью выражения import models. Теперь вы добавите класс Customer как расширение models.Model. Каждая модель в Django представляет собой класс Python, являющийся расширением django.db.models.Model.

      Customer модель будет иметь следующие поля базы данных:

      • first_name — имя клиента.
      • last_name — фамилия клиента.
      • email — адрес электронной почты клиента.
      • phone — номер телефона клиента.
      • address — адрес клиента.
      • description — описание клиента.
      • createdAt — дата добавления клиента.

      Также мы добавим функцию __str__(), определяющую способ отображения модели. В нашем случае это будет имя клиента. Более подробную информацию по построению классов и определению объектов можно найти в документе «Построение классов и определение объектов в Python 3».

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

      ~/djangoreactproject/customers/models.py

      from django.db import models
      
      class Customer(models.Model):
          first_name = models.CharField("First name", max_length=255)
          last_name = models.CharField("Last name", max_length=255)
          email = models.EmailField()
          phone = models.CharField(max_length=20)
          address =  models.TextField(blank=True, null=True)
          description = models.TextField(blank=True, null=True)
          createdAt = models.DateTimeField("Created At", auto_now_add=True)
      
          def __str__(self):
              return self.first_name
      

      Затем выполните миграцию базы данных для создания таблиц базы данных. Команда makemigrations создает файлы миграции, куда добавляются изменения модели, в команда migrate применяет все изменения в файлах миграции к базе данных.

      Перейдите обратно в корневую папку проекта root:

      Запустите следующую команду для создания файлов миграции:

      • python manage.py makemigrations

      Вы получите результат, выглядящий следующим образом:

      Output

      customers/migrations/0001_initial.py - Create model Customer

      Примените эти изменения в базе данных:

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

      Output

      Operations to perform: Apply all migrations: admin, auth, contenttypes, customers, sessions Running migrations: Applying customers.0001_initial... OK

      Затем вы используете файл миграции данных для создания начального набора данных клиента. Файл миграции данных используется для добавления или изменения данных в базе данных при миграции. Создайте пустой файл миграции данных дл приложения customers:

      • python manage.py makemigrations --empty --name customers customers

      Вы увидите следующее подтверждение с именем файла миграции:

      Output

      Migrations for 'customers': customers/migrations/0002_customers.py

      Обратите внимание, что имя файла миграции 0002_customers.py.

      Затем перейдите в папку migrations приложения customers:

      • cd ~/djangoreactproject/customers/migrations

      Откройте созданный файл миграции:

      Это первоначальное содержание файла:

      ~/djangoreactproject/customers/migrations/0002_customers.py

      from django.db import migrations
      
      class Migration(migrations.Migration):
          dependencies = [
              ('customers', '0001_initial'),
          ]
          operations = [
          ]        
      

      Команда import импортирует API migrations. Это API Django для создания миграции из django.db, встроенного пакета, содержащего классы для работы с базами данных.

      Класс Migration — это класс Python, описывающий операции, выполняемые во время миграции баз данных. Этот класс является расширением migrations.Migration и содержит два списка:

      • dependencies: содержит зависимые миграции.
      • operations: содержит операции, которые будут выполняться во время проведения миграции.

      Затем добавьте метод для создания данных демо-клиента. Следующий метод нужно добавить перед определением класса Migration:

      ~/djangoreactproject/customers/migrations/0002_customers.py

      ...
      def create_data(apps, schema_editor):
          Customer = apps.get_model('customers', 'Customer')
          Customer(first_name="Customer 001", last_name="Customer 001", email="[email protected]", phone="00000000", address="Customer 000 Address", description= "Customer 001 description").save()
      
      ...
      

      В этом методе мы берем класс Customer нашего приложения customers и создаем демо-клиента для добавления в базу данных.

      Чтобы получить класс Customer, который позволит нам создавать новых клиентов, мы используем метод get_model() объекта apps. Объект apps представляет реестр установленных приложений и их моделей баз данных.

      Объект apps передается из метода RunPython(), когда мы используем его для запуска create_data(). Добавьте метод migrations.RunPython() в пустой список operations:

      ~/djangoreactproject/customers/migrations/0002_customers.py

      
      ...
          operations = [
              migrations.RunPython(create_data),
          ]  
      

      RunPython() — часть API Migrations, позволяющая запускать заданный код Python при миграции. Список operations указывает, что этот метод будет выполняться при проведении миграции.

      Полный файл будет выглядеть следующим образом:

      ~/djangoreactproject/customers/migrations/0002_customers.py

      from django.db import migrations
      
      def create_data(apps, schema_editor):
          Customer = apps.get_model('customers', 'Customer')
          Customer(first_name="Customer 001", last_name="Customer 001", email="[email protected]", phone="00000000", address="Customer 000 Address", description= "Customer 001 description").save()
      
      class Migration(migrations.Migration):
          dependencies = [
              ('customers', '0001_initial'),
          ]
          operations = [
              migrations.RunPython(create_data),
          ]        
      

      Дополнительную информацию по миграции данных можно найти в документации по миграции данных в Django

      Чтобы выполнить миграцию базы данных, вернитесь в папку root вашего проекта:

      Выполните миграцию базы данных для создания демонстрационных данных:

      Вы увидите результат, подтверждающий миграцию:

      Output

      Operations to perform: Apply all migrations: admin, auth, contenttypes, customers, sessions Running migrations: Applying customers.0002_customers... OK

      Более подробную информацию об этом процессе можно найти в документе «Создание моделей Django».

      После создания модели Customer и демо-данных, мы можем продолжить работу по созданию API REST.

      Шаг 5 — Создание API REST

      На этом шаге мы создадим API REST с помощью системы Django REST. Мы создадим несколько разных представлений API. Представление API представляет собой функцию, выполняющую запрос или вызов API, а конечная точка API — уникальный URL, представляющий собой элемент связи с системой REST. Например, когда пользователь направляет запрос GET в конечную точку API, Django вызывает соответствующую функцию или представление API для обработки запроса и возвращения любых возможных результатов.

      Также мы используем сериализаторы. Сериализатор в системе Django REST позволяет преобразовывать экземпляры сложных моделей и наборы QuerySets в формат JSON для использования API. Класс serializer может работать и в другом направлении, предоставляя механизмы для синтаксического анализа и десериализации данных в моделях Django и QuerySets.

      Мы получим следующие конечные точки API:

      • api/customers: эта конечная точка используется для создания клиентов и возврата разбитых на страницы наборов клиентов.
      • api/customers/: эта конечная точка испольузется для получения, обновления и удаления отдельных клиентов по первичным ключам или идентификаторам.

      Мы также создадим URL в файле проекта urls.py для соответствующих конечных точек (т.е. api/customers и api/customers/).

      Начнем с создания класса serializer для нашей модели Customer.

      Добавление класса Serializer

      Создание класса serializer для нашей модели Customer необходимо для преобразования экземпляров клиентов и наборов QuerySets в JSON и обратно. Чтобы создать класс serializer, сначала создайте файл serializers.py в приложении customers:

      • cd ~/djangoreactproject/customers/
      • nano serializers.py

      Добавьте следующий код для импорта API serializers и модели Customer:

      ~/djangoreactproject/customers/serializers.py

      from rest_framework import serializers
      from .models import Customer
      

      Затем создайте класс serializer, являющийся расширением serializers.ModelSerializer и указывающий поля для сериализации:

      ~/djangoreactproject/customers/serializers.py

      
      ...
      class CustomerSerializer(serializers.ModelSerializer):
      
          class Meta:
              model = Customer
              fields = ('pk','first_name', 'last_name', 'email', 'phone','address','description')
      

      Класс Meta указывает модель и поля для сериализации: pk, first_name, last_name, email, phone, address, description:

      Это полное содержание файла:

      ~/djangoreactproject/customers/serializers.py

      from rest_framework import serializers
      from .models import Customer
      
      class CustomerSerializer(serializers.ModelSerializer):
      
          class Meta:
              model = Customer
              fields = ('pk','first_name', 'last_name', 'email', 'phone','address','description')
      

      Создав класс serializer, мы можем добавить представления API.

      Добавление представлений API

      В этом разделе мы создадим представления API для нашего приложения, которое будет вызываться Django, когда пользователь будет посещать конечную точку, соответствующую функции представления.

      Откройте ~/djangoreactproject/customers/views.py:

      • nano ~/djangoreactproject/customers/views.py

      Удалите содержимое и добавьте следующие операции импорта:

      ~/djangoreactproject/customers/views.py

      from rest_framework.response import Response
      from rest_framework.decorators import api_view
      from rest_framework import status
      
      from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
      from .models import Customer
      from .serializers import *
      

      Мы импортируем созданный сериализатор, а также модель Customer и API Django и системы Django REST.

      Затем добавьте представление для обработки запросов POST и GET HTTP:

      ~/djangoreactproject/customers/views.py

      ...
      
      @api_view(['GET', 'POST'])
      def customers_list(request):
          """
       List  customers, or create a new customer.
       """
          if request.method == 'GET':
              data = []
              nextPage = 1
              previousPage = 1
              customers = Customer.objects.all()
              page = request.GET.get('page', 1)
              paginator = Paginator(customers, 10)
              try:
                  data = paginator.page(page)
              except PageNotAnInteger:
                  data = paginator.page(1)
              except EmptyPage:
                  data = paginator.page(paginator.num_pages)
      
              serializer = CustomerSerializer(data,context={'request': request} ,many=True)
              if data.has_next():
                  nextPage = data.next_page_number()
              if data.has_previous():
                  previousPage = data.previous_page_number()
      
              return Response({'data': serializer.data , 'count': paginator.count, 'numpages' : paginator.num_pages, 'nextlink': '/api/customers/?page=' + str(nextPage), 'prevlink': '/api/customers/?page=' + str(previousPage)})
      
          elif request.method == 'POST':
              serializer = CustomerSerializer(data=request.data)
              if serializer.is_valid():
                  serializer.save()
                  return Response(serializer.data, status=status.HTTP_201_CREATED)
              return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
      

      Вначале мы используем декоратор @api_view(['GET', 'POST']) для создания представления API, способного принимать запросы GET и POST. Декоратор — это функция, которая берет другую функцию и динамически расширяет ее.

      В теле метода мы используем переменную request.method для проверки текущего метода HTTP и выполнения соответствующей логики в зависимости от типа запроса:

      • Если это запрос GET, метод разбивает данные на страницы с помощью Django Paginator и возвращает первую страницу данных после сериализации, количество доступных клиентов, количество доступных страниц и ссылки на предыдущие и последующие страницы. Paginator — это встроенный класс Django, выполняющий разбивку списка данных на страницы и предоставляющий методы доступа к элементам на каждой странице.
      • Если это запрос POST, метод выполняет сериализацию полученных данных клиента, а затем вызывает метод save() с помощью объекта serializer. Затем он возвращает объект Response, являющийся экземпляром HttpResponse, с кодом состояния 201. Каждое создаваемое представление отвечает за возврат объекта HttpResponse. Метод save() сохраняет сериализированные данные в базе данных.

      Дополнительную информацию об HttpResponse и представлениях можно найти в этой дискуссии о создании функций представления.

      Теперь добавьте представление API, которое будет отвечать за обработку запросов GET, PUT и DELETE для получения, обновления и удаления клиентов по pk (первичный ключ):

      ~/djangoreactproject/customers/views.py

      
      ...
      @api_view(['GET', 'PUT', 'DELETE'])
      def customers_detail(request, pk):
       """
       Retrieve, update or delete a customer by id/pk.
       """
          try:
              customer = Customer.objects.get(pk=pk)
          except Customer.DoesNotExist:
              return Response(status=status.HTTP_404_NOT_FOUND)
      
          if request.method == 'GET':
              serializer = CustomerSerializer(customer,context={'request': request})
              return Response(serializer.data)
      
          elif request.method == 'PUT':
              serializer = CustomerSerializer(customer, data=request.data,context={'request': request})
              if serializer.is_valid():
                  serializer.save()
                  return Response(serializer.data)
              return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
      
          elif request.method == 'DELETE':
              customer.delete()
              return Response(status=status.HTTP_204_NO_CONTENT)
      

      Метод оформлен с помощью декораторов @api_view(['GET', 'PUT', 'DELETE']). Это означает, что он является представлением API, который может принимать запросы GET, PUT и DELETE.

      Проверка поля request.method проверяет метод запроса и вызывает нужную логику в зависимости от значения:

      • Если это запрос GET, выполняется сериализация данных клиента и их отправка с помощью объекта Response.
      • Если это запрос PUT, метод создает сериализатор для новых данных клиента. Затем он вызывает метод save() созданного объекта serializer. Наконец, он отправляет объект Response с обновленными данными клиента.
      • Если это запрос DELETE, метод вызывает метод delete() объекта customer для его удаления, после чего возвращает объект Response, не содержащий никаких данных.

      Завершенный файл выглядит следующим образом:

      ~/djangoreactproject/customers/views.py

      from rest_framework.response import Response
      from rest_framework.decorators import api_view
      from rest_framework import status
      
      from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
      from .models import Customer
      from .serializers import *
      
      
      @api_view(['GET', 'POST'])
      def customers_list(request):
          """
       List  customers, or create a new customer.
       """
          if request.method == 'GET':
              data = []
              nextPage = 1
              previousPage = 1
              customers = Customer.objects.all()
              page = request.GET.get('page', 1)
              paginator = Paginator(customers, 5)
              try:
                  data = paginator.page(page)
              except PageNotAnInteger:
                  data = paginator.page(1)
              except EmptyPage:
                  data = paginator.page(paginator.num_pages)
      
              serializer = CustomerSerializer(data,context={'request': request} ,many=True)
              if data.has_next():
                  nextPage = data.next_page_number()
              if data.has_previous():
                  previousPage = data.previous_page_number()
      
              return Response({'data': serializer.data , 'count': paginator.count, 'numpages' : paginator.num_pages, 'nextlink': '/api/customers/?page=' + str(nextPage), 'prevlink': '/api/customers/?page=' + str(previousPage)})
      
          elif request.method == 'POST':
              serializer = CustomerSerializer(data=request.data)
              if serializer.is_valid():
                  serializer.save()
                  return Response(serializer.data, status=status.HTTP_201_CREATED)
              return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
      
      @api_view(['GET', 'PUT', 'DELETE'])
      def customers_detail(request, pk):
          """
       Retrieve, update or delete a customer by id/pk.
       """
          try:
              customer = Customer.objects.get(pk=pk)
          except Customer.DoesNotExist:
              return Response(status=status.HTTP_404_NOT_FOUND)
      
          if request.method == 'GET':
              serializer = CustomerSerializer(customer,context={'request': request})
              return Response(serializer.data)
      
          elif request.method == 'PUT':
              serializer = CustomerSerializer(customer, data=request.data,context={'request': request})
              if serializer.is_valid():
                  serializer.save()
                  return Response(serializer.data)
              return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
      
          elif request.method == 'DELETE':
              customer.delete()
              return Response(status=status.HTTP_204_NO_CONTENT)
      

      Теперь мы можем перейти к созданию конечных точек.

      Добавление конечных точек API

      Теперь мы создадим конечные точки API: api/customers/ для запроса и создания клиентов и api/customers/<pk> для получения, обновления и удаления отдельных клиентов по pk.

      Откройте ~/djangoreactproject/djangoreactproject/urls.py:

      • nano ~/djangoreactproject/djangoreactproject/urls.py

      Оставьте его содержание без изменений, но добавьте в начало файла импорт в представление customers:

      ~/djangoreactproject/djangoreactproject/urls.py

      from django.contrib import admin
      from django.urls import path
      from customers import views
      from django.conf.urls import url
      

      Затем добавьте URL-адреса api/customers/ и api/customers/ в список urlpatterns, содержащий URL приложения:

      ~/djangoreactproject/djangoreactproject/urls.py

      ...
      
      urlpatterns = [
          path('admin/', admin.site.urls),
          url(r'^api/customers/$', views.customers_list),
          url(r'^api/customers/(?P<pk>[0-9]+)$', views.customers_detail),
      ]
      

      Мы создали конечные точки REST, теперь рассмотрим их употребление.

      Шаг 6 — Употребление REST API с Axios

      На этом шаге мы установим Axios, клиент HTTP, служащий для вызова API. Также мы создадим класс для потребления созданных нами конечных точек API.

      Вначале отключите свою виртуальную среду:

      Затем перейдите в папку frontend:

      • cd ~/djangoreactproject/frontend

      Установите axios из npm с помощью следующей команды:

      Опция --save добавляет зависимость axios в файл package.json вашего приложения.

      Затем создайте файл JavaScript под названием CustomersService.js, который будет содержать код для вызова API REST. Это будет реализовано в папке src, где будет располагаться код приложения для нашего проекта:

      • cd src
      • nano CustomersService.js

      Добавьте следующий код, содержащий методы подключения к Django REST API:

      ~/djangoreactproject/frontend/src/CustomersService.js

      import axios from 'axios';
      const API_URL = 'http://localhost:8000';
      
      export default class CustomersService{
      
          constructor(){}
      
      
          getCustomers() {
              const url = `${API_URL}/api/customers/`;
              return axios.get(url).then(response => response.data);
          }  
          getCustomersByURL(link){
              const url = `${API_URL}${link}`;
              return axios.get(url).then(response => response.data);
          }
          getCustomer(pk) {
              const url = `${API_URL}/api/customers/${pk}`;
              return axios.get(url).then(response => response.data);
          }
          deleteCustomer(customer){
              const url = `${API_URL}/api/customers/${customer.pk}`;
              return axios.delete(url);
          }
          createCustomer(customer){
              const url = `${API_URL}/api/customers/`;
              return axios.post(url,customer);
          }
          updateCustomer(customer){
              const url = `${API_URL}/api/customers/${customer.pk}`;
              return axios.put(url,customer);
          }
      }
      

      Класс CustomersService будет вызывать следующие методы Axios:

      • getCustomers(): получает первую страницу клиентов.
      • getCustomersByURL(): получает клиентов по URL. Это позволяет получить следующие страницы клиентов путем передачи таких ссылок, как /api/customers/?page=2.
      • getCustomer(): получает клиента по первичному ключу.
      • createCustomer(): создает клиента.
      • updateCustomer(): обновляет клиента.
      • deleteCustomer(): удаляет клиента.

      Теперь мы можем выводить данные нашего API в пользовательском интерфейсе React, создавая компонент CustomersList.

      Шаг 7 — Отображение данных из API в приложении React

      На этом шаге мы создадим компонент CustomersList React. Компонент React представляет часть пользовательского интерфейса; также он позволяет разделять пользовательский интерфейс на независимые элементы многоразового использования.

      Для начала создайте CustomersList.js в папке frontend/src:

      • nano ~/djangoreactproject/frontend/src/CustomersList.js

      Вначале импортируйте React и Component для создания компонента React:

      ~/djangoreactproject/frontend/src/CustomersList.js

      import  React, { Component } from  'react';
      

      Затем импортируйте и создайте экземпляр модуля CustomersService, который вы создали на предыдущем шаге и который предоставляет методы взаимодействия с серверной частью REST API:

      ~/djangoreactproject/frontend/src/CustomersList.js

      
      ...
      import  CustomersService  from  './CustomersService';
      
      const  customersService  =  new  CustomersService();
      

      Затем создайте компонент CustomersList, расширяющий Component для вызова REST API. Компонент React должен расширять класс Component или создавать его подкласс. Дополнительную информацию о классах E6 и наследовании можно найти в обучающем руководстве «Понимание классов в JavaScript».

      Добавьте следующий код для создания компонента React, являющегося расширением react.Component:

      ~/djangoreactproject/frontend/src/CustomersList.js

      
      ...
      class  CustomersList  extends  Component {
      
          constructor(props) {
              super(props);
              this.state  = {
                  customers: [],
                  nextPageURL:  ''
              };
              this.nextPage  =  this.nextPage.bind(this);
              this.handleDelete  =  this.handleDelete.bind(this);
          }
      }
      export  default  CustomersList;
      

      Внутри конструктора мы выполняем инициализацию объекта state. В нем хранятся переменные state нашего компонента, для чего используется пустой массив customers. В этом массиве будут храниться клиенты и nextPageURL, где будет храниться URL следующей страницы для получения от серверного API. Также мы выполняем привязку методов nextPage() и handleDelete(), this чтобы они были доступны из кода HTML.

      Затем добавьте метод componentDidMount() и вызов getCustomers() из класса CustomersList, после чего закройте фигурную скобку.

      Метод componentDidMount() — это метод жизненного цикла компонента, вызываемый, когда компонент создается и вставляется в DOM. getCustomers() вызывает объект Customers Service для получения первой страницы данных и ссылки на следующую страницу из серверной части Django:

      ~/djangoreactproject/frontend/src/CustomersList.js

      
      ...
      componentDidMount() {
          var  self  =  this;
          customersService.getCustomers().then(function (result) {
              self.setState({ customers:  result.data, nextPageURL:  result.nextlink})
          });
      }
      

      Теперь добавьте метод handleDelete(), который обрабатывает удаление клиента, под componentDidMount():

      ~/djangoreactproject/frontend/src/CustomersList.js

      
      ...
      handleDelete(e,pk){
          var  self  =  this;
          customersService.deleteCustomer({pk :  pk}).then(()=>{
              var  newArr  =  self.state.customers.filter(function(obj) {
                  return  obj.pk  !==  pk;
              });
              self.setState({customers:  newArr})
          });
      }
      

      Метод handleDelete() вызывает метод deleteCustomer() для удаления клиента по pk (первичный ключ). Если операция выполняется успешно, удаленный customer отфильтровывается из массива

      Затем добавьте метод nextPage() для получения данных следующей страницы и обновления ссылки на следующую страницу:

      ~/djangoreactproject/frontend/src/CustomersList.js

      
      ...
      nextPage(){
          var  self  =  this;
          customersService.getCustomersByURL(this.state.nextPageURL).then((result) => {
              self.setState({ customers:  result.data, nextPageURL:  result.nextlink})
          });
      }
      

      Метод nextPage() вызывает метод getCustomersByURL(), который берет URL следующей страницы из объекта состояния this.state.nextPageURL и обновляет массив customers, добавляя в него возвращаемые данные.

      Наконец, добавьте метод render() компонента, который выполняет рендеринг таблицы клиентов из состояния компонента:

      ~/djangoreactproject/frontend/src/CustomersList.js

      
      ...
      render() {
      
          return (
          <div  className="customers--list">
              <table  className="table">
                  <thead  key="thead">
                  <tr>
                      <th>#</th>
                      <th>First Name</th>
                      <th>Last Name</th>
                      <th>Phone</th>
                      <th>Email</th>
                      <th>Address</th>
                      <th>Description</th>
                      <th>Actions</th>
                  </tr>
                  </thead>
                  <tbody>
                      {this.state.customers.map( c  =>
                      <tr  key={c.pk}>
                          <td>{c.pk}  </td>
                          <td>{c.first_name}</td>
                          <td>{c.last_name}</td>
                          <td>{c.phone}</td>
                          <td>{c.email}</td>
                          <td>{c.address}</td>
                          <td>{c.description}</td>
                          <td>
                          <button  onClick={(e)=>  this.handleDelete(e,c.pk) }> Delete</button>
                          <a  href={"/customer/" + c.pk}> Update</a>
                          </td>
                      </tr>)}
                  </tbody>
              </table>
              <button  className="btn btn-primary"  onClick=  {  this.nextPage  }>Next</button>
          </div>
          );
      }
      

      Это полное содержание файла:

      ~/djangoreactproject/frontend/src/CustomersList.js

      import  React, { Component } from  'react';
      import  CustomersService  from  './CustomersService';
      
      const  customersService  =  new  CustomersService();
      
      class  CustomersList  extends  Component {
      
      constructor(props) {
          super(props);
          this.state  = {
              customers: [],
              nextPageURL:  ''
          };
          this.nextPage  =  this.nextPage.bind(this);
          this.handleDelete  =  this.handleDelete.bind(this);
      }
      
      componentDidMount() {
          var  self  =  this;
          customersService.getCustomers().then(function (result) {
              console.log(result);
              self.setState({ customers:  result.data, nextPageURL:  result.nextlink})
          });
      }
      handleDelete(e,pk){
          var  self  =  this;
          customersService.deleteCustomer({pk :  pk}).then(()=>{
              var  newArr  =  self.state.customers.filter(function(obj) {
                  return  obj.pk  !==  pk;
              });
      
              self.setState({customers:  newArr})
          });
      }
      
      nextPage(){
          var  self  =  this;
          console.log(this.state.nextPageURL);        
          customersService.getCustomersByURL(this.state.nextPageURL).then((result) => {
              self.setState({ customers:  result.data, nextPageURL:  result.nextlink})
          });
      }
      render() {
      
          return (
              <div  className="customers--list">
                  <table  className="table">
                  <thead  key="thead">
                  <tr>
                      <th>#</th>
                      <th>First Name</th>
                      <th>Last Name</th>
                      <th>Phone</th>
                      <th>Email</th>
                      <th>Address</th>
                      <th>Description</th>
                      <th>Actions</th>
                  </tr>
                  </thead>
                  <tbody>
                  {this.state.customers.map( c  =>
                      <tr  key={c.pk}>
                      <td>{c.pk}  </td>
                      <td>{c.first_name}</td>
                      <td>{c.last_name}</td>
                      <td>{c.phone}</td>
                      <td>{c.email}</td>
                      <td>{c.address}</td>
                      <td>{c.description}</td>
                      <td>
                      <button  onClick={(e)=>  this.handleDelete(e,c.pk) }> Delete</button>
                      <a  href={"/customer/" + c.pk}> Update</a>
                      </td>
                  </tr>)}
                  </tbody>
                  </table>
                  <button  className="btn btn-primary"  onClick=  {  this.nextPage  }>Next</button>
              </div>
              );
        }
      }
      export  default  CustomersList;
      

      Мы создали компонент CustomersList для отображения списка клиентов, и теперь можем добавить компонент для обработки создания и обновления клиентов.

      Шаг 8 — Добавление компонентов React для создания и обновления клиентов

      На этом шаге мы создадим компонент CustomerCreateUpdate, который будет заниматься созданием и обновлением клиентов. Для этого он будет предоставлять форму, где пользователи смогут вводить данные о новом клиенте или изменять уже записанные данные.

      Создайте в папке frontend/src файл CustomerCreateUpdate.js:

      • nano ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      Добавьте следующий код для создания компонента React, импортировав классы React и Component:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      import  React, { Component } from  'react';
      

      Также мы можем импортировать класс CustomersService, созданный на предыдущем шаге и создать экземпляр этого класса, который предоставляет методы взаимодействия с серверной частью API REST:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      ...
      import  CustomersService  from  './CustomersService';
      
      const  customersService  =  new  CustomersService();
      

      Далее создайте компонент CustomerCreateUpdate, расширяющий класс Component для создания и обновления клиентов:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      
      ...
      class  CustomerCreateUpdate  extends  Component {
      
          constructor(props) {
              super(props);
          }
      
      }
      export default CustomerCreateUpdate;
      

      Добавьте в определение класса метод компонента render(), отвечающий за рендеринг формы HTML, куда вводится информация о клиенте:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      
      ...
      render() {
              return (
                <form onSubmit={this.handleSubmit}>
                <div className="form-group">
                  <label>
                    First Name:</label>
                    <input className="form-control" type="text" ref='firstName' />
      
                  <label>
                    Last Name:</label>
                    <input className="form-control" type="text" ref='lastName'/>
      
                  <label>
                    Phone:</label>
                    <input className="form-control" type="text" ref='phone' />
      
                  <label>
                    Email:</label>
                    <input className="form-control" type="text" ref='email' />
      
                  <label>
                    Address:</label>
                    <input className="form-control" type="text" ref='address' />
      
                  <label>
                    Description:</label>
                    <textarea className="form-control" ref='description' ></textarea>
      
      
                  <input className="btn btn-primary" type="submit" value="Submit" />
                  </div>
                </form>
              );
        }
      

      Для каждого элемента ввода формы метод добавляет свойство ref для доступа и установки значения элемента формы.

      Над методом render() создайте определение метода handleSubmit(event), чтобы при нажатии на кнопку Submit (Отправить) функция работала надлежащим образом:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      
      ...
      handleSubmit(event) {
          const { match: { params } } =  this.props;
          if(params  &&  params.pk){
              this.handleUpdate(params.pk);
          }
          else
          {
              this.handleCreate();
          }
          event.preventDefault();
      }
      
      ...
      

      Метод handleSubmit(event) обрабатывает отправку форм и (в зависимости от маршрута) вызывет метод handleUpdate(pk) для обновления клиента с переданным pk или метод handleCreate() для создания нового клиента. Мы дадим краткое определение этих методов.

      Выполните в конструкторе компонентов привязку добавленного метода handleSubmit() к этому методу, чтобы вы могли использовать его в своей форме:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      ...
      class CustomerCreateUpdate extends Component {
      
      constructor(props) {
          super(props);
          this.handleSubmit = this.handleSubmit.bind(this);
      }
      ...
      

      Затем дайте определение методу handleCreate() для создания клиента на основе данных формы. Над методом handleSubmit(event) добавьте следующий код:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      
      ...
      handleCreate(){
          customersService.createCustomer(
              {
              "first_name":  this.refs.firstName.value,
              "last_name":  this.refs.lastName.value,
              "email":  this.refs.email.value,
              "phone":  this.refs.phone.value,
              "address":  this.refs.address.value,
              "description":  this.refs.description.value
              }).then((result)=>{
                      alert("Customer created!");
              }).catch(()=>{
                      alert('There was an error! Please re-check your form.');
              });
      }
      
      ...
      

      Метод handleCreate() будет использоваться для создания клиента на основе введенных данных. Он вызывает соответствующий метод CustomersService.createCustomer(), который API использует для вызова серверной части для создания клиента.

      Под методом handleCreate() дайте определение метода handleUpdate(pk) для выполнения обновлений:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      
      ...
      handleUpdate(pk){
      customersService.updateCustomer(
          {
          "pk":  pk,
          "first_name":  this.refs.firstName.value,
          "last_name":  this.refs.lastName.value,
          "email":  this.refs.email.value,
          "phone":  this.refs.phone.value,
          "address":  this.refs.address.value,
          "description":  this.refs.description.value
          }
          ).then((result)=>{
      
              alert("Customer updated!");
          }).catch(()=>{
              alert('There was an error! Please re-check your form.');
          });
      }
      

      Метод updateCustomer() обновляет клиента по pk, используя новую информацию из формы данных о клиенте. Он вызывает метод customersService.updateCustomer().

      Затем добавьте метод componentDidMount(). Если пользователь посещает маршрут customer/:pk, нужно заполнить форму информацией о клиенте, используя первичный ключ из URL. Для этого мы можем добавить метод getCustomer(pk) после монтирования компонента в событии жизненного цикла componentDidMount(). Для добавления этого метода добавьте следующий код после конструктора компонентов:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      
      ...
      componentDidMount(){
          const { match: { params } } =  this.props;
          if(params  &&  params.pk)
          {
              customersService.getCustomer(params.pk).then((c)=>{
                  this.refs.firstName.value  =  c.first_name;
                  this.refs.lastName.value  =  c.last_name;
                  this.refs.email.value  =  c.email;
                  this.refs.phone.value  =  c.phone;
                  this.refs.address.value  =  c.address;
                  this.refs.description.value  =  c.description;
              })
          }
      }
      

      Это полное содержание файла:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      import React, { Component } from 'react';
      import CustomersService from './CustomersService';
      
      const customersService = new CustomersService();
      
      class CustomerCreateUpdate extends Component {
          constructor(props) {
              super(props);
      
              this.handleSubmit = this.handleSubmit.bind(this);
            }
      
            componentDidMount(){
              const { match: { params } } = this.props;
              if(params && params.pk)
              {
                customersService.getCustomer(params.pk).then((c)=>{
                  this.refs.firstName.value = c.first_name;
                  this.refs.lastName.value = c.last_name;
                  this.refs.email.value = c.email;
                  this.refs.phone.value = c.phone;
                  this.refs.address.value = c.address;
                  this.refs.description.value = c.description;
                })
              }
            }
      
            handleCreate(){
              customersService.createCustomer(
                {
                  "first_name": this.refs.firstName.value,
                  "last_name": this.refs.lastName.value,
                  "email": this.refs.email.value,
                  "phone": this.refs.phone.value,
                  "address": this.refs.address.value,
                  "description": this.refs.description.value
              }          
              ).then((result)=>{
                alert("Customer created!");
              }).catch(()=>{
                alert('There was an error! Please re-check your form.');
              });
            }
            handleUpdate(pk){
              customersService.updateCustomer(
                {
                  "pk": pk,
                  "first_name": this.refs.firstName.value,
                  "last_name": this.refs.lastName.value,
                  "email": this.refs.email.value,
                  "phone": this.refs.phone.value,
                  "address": this.refs.address.value,
                  "description": this.refs.description.value
              }          
              ).then((result)=>{
                console.log(result);
                alert("Customer updated!");
              }).catch(()=>{
                alert('There was an error! Please re-check your form.');
              });
            }
            handleSubmit(event) {
              const { match: { params } } = this.props;
      
              if(params && params.pk){
                this.handleUpdate(params.pk);
              }
              else
              {
                this.handleCreate();
              }
      
              event.preventDefault();
            }
      
            render() {
              return (
                <form onSubmit={this.handleSubmit}>
                <div className="form-group">
                  <label>
                    First Name:</label>
                    <input className="form-control" type="text" ref='firstName' />
      
                  <label>
                    Last Name:</label>
                    <input className="form-control" type="text" ref='lastName'/>
      
                  <label>
                    Phone:</label>
                    <input className="form-control" type="text" ref='phone' />
      
                  <label>
                    Email:</label>
                    <input className="form-control" type="text" ref='email' />
      
                  <label>
                    Address:</label>
                    <input className="form-control" type="text" ref='address' />
      
                  <label>
                    Description:</label>
                    <textarea className="form-control" ref='description' ></textarea>
      
      
                  <input className="btn btn-primary" type="submit" value="Submit" />
                  </div>
                </form>
              );
            }  
      }
      
      export default CustomerCreateUpdate;
      

      После создания компонента CustomerCreateUpdate мы можем обновить компонент main App и добавить ссылки на различные созданные нами компоненты.

      Шаг 9 — Обновление компонента Main App

      На этом шаге мы обновим компонент App нашего приложения для создания ссылок на компоненты, созданные на предыдущих шагах.

      Запустите из папки frontend следующую команду для установки React Router, что позволит добавить маршрутизацию и навигацию между разными компонентами React:

      • cd ~/djangoreactproject/frontend
      • npm install --save react-router-dom

      Далее откройте ~/djangoreactproject/frontend/src/App.js:

      • nano ~/djangoreactproject/frontend/src/App.js

      Удалите все содержимое и добавьте следующий код, чтобы импортировать необходимые классы для добавления маршрутизации. В их число входит класс BrowserRouter, создающий компонент Router, и класс Route, создающий компонент route:

      ~/djangoreactproject/frontend/src/App.js

      import  React, { Component } from  'react';
      import { BrowserRouter } from  'react-router-dom'
      import { Route, Link } from  'react-router-dom'
      import  CustomersList  from  './CustomersList'
      import  CustomerCreateUpdate  from  './CustomerCreateUpdate'
      import  './App.css';
      

      BrowserRouter обеспечивает синхронизацию пользовательского интерфейса с URL, используя API истории HTML5.

      Далее создайте базовую схему, обеспечивающую базовый компонент для включения в компонент BrowserRouter:

      ~/djangoreactproject/frontend/src/App.js

      ...
      
      const  BaseLayout  = () => (
      <div  className="container-fluid">
          <nav  className="navbar navbar-expand-lg navbar-light bg-light">
              <a  className="navbar-brand"  href="https://www.digitalocean.com/#">Django React Demo</a>
              <button  className="navbar-toggler"  type="button"  data-toggle="collapse"  data-target="#navbarNavAltMarkup"  aria-controls="navbarNavAltMarkup"  aria-expanded="false"  aria-label="Toggle navigation">
              <span  className="navbar-toggler-icon"></span>
          </button>
          <div  className="collapse navbar-collapse"  id="navbarNavAltMarkup">
              <div  className="navbar-nav">
                  <a  className="nav-item nav-link"  href="/">CUSTOMERS</a>
                  <a  className="nav-item nav-link"  href="http://www.digitalocean.com/customer">CREATE CUSTOMER</a>
              </div>
          </div>
          </nav>
          <div  className="content">
              <Route  path="/"  exact  component={CustomersList}  />
              <Route  path="/customer/:pk"  component={CustomerCreateUpdate}  />
              <Route  path="/customer/"  exact  component={CustomerCreateUpdate}  />
          </div>
      </div>
      )
      

      Мы используем компонент Route для определения маршрутов нашего приложения; компонент, который должен загружать маршрутизатор после обнаружения совпадения. Для каждого маршрута требуются параметры path для указания пути сопоставления и component для указания загружаемого компонента. Свойство exact указывает маршрутизатору на необходимость точного соответствия пути.

      Наконец, создайте компонент App. Это корневой компонент или компонент самого верхнего уровня нашего приложения React:

      ~/djangoreactproject/frontend/src/App.js

      ...
      
      class  App  extends  Component {
      
      render() {
          return (
          <BrowserRouter>
              <BaseLayout/>
          </BrowserRouter>
          );
      }
      }
      export  default  App;
      

      Мы поместили компонент BaseLayout в компонент BrowserRouter, потому что наше приложение предусматривает работу через браузер.

      Завершенный файл выглядит следующим образом:

      ~/djangoreactproject/frontend/src/App.js

      import React, { Component } from 'react';
      import { BrowserRouter } from 'react-router-dom'
      import { Route, Link } from 'react-router-dom'
      
      import  CustomersList from './CustomersList'
      import  CustomerCreateUpdate  from './CustomerCreateUpdate'
      import './App.css';
      
      const BaseLayout = () => (
        <div className="container-fluid">
      <nav className="navbar navbar-expand-lg navbar-light bg-light">
        <a className="navbar-brand" href="https://www.digitalocean.com/#">Django React Demo</a>
        <button className="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
          <span className="navbar-toggler-icon"></span>
        </button>
        <div className="collapse navbar-collapse" id="navbarNavAltMarkup">
          <div className="navbar-nav">
            <a className="nav-item nav-link" href="/">CUSTOMERS</a>
            <a className="nav-item nav-link" href="http://www.digitalocean.com/customer">CREATE CUSTOMER</a>
      
          </div>
        </div>
      </nav>  
      
          <div className="content">
            <Route path="/" exact component={CustomersList} />
            <Route path="/customer/:pk"  component={CustomerCreateUpdate} />
            <Route path="/customer/" exact component={CustomerCreateUpdate} />
      
          </div>
      
        </div>
      )
      
      class App extends Component {
        render() {
          return (
            <BrowserRouter>
              <BaseLayout/>
            </BrowserRouter>
          );
        }
      }
      
      export default App;
      

      После добавления маршрутизации в приложение можно начать его тестирование. Откройте адрес http://localhost:3000. Вы увидите первую страницу приложения:

      Главная страница приложения

      С этим приложением вы получили основу для создания приложения CRM.

      Заключение

      Это обучающее руководство помогло вам научиться создавать демонстрационное приложение с помощью Django и React. Вы использовали систему Django REST для создания REST API, Axios для потребления API, и Bootstrap 4 для стилизации CSS. Исходный код этого проекта можно найти в хранилище GitHub.

      В этом обучающем руководстве были использованы отдельные приложения для клиентской части и серверной части. Другой подход к интеграции React с Django можно найти в этом руководстве и этом руководстве.

      Дополнительную информацию о создании приложений с помощью Django можно найти в серии публикаций «Разработка в Django». Также вы можете ознакомиться с официальной документацией по Django.



      Source link