Время прочтения: 11 мин.

Привет! В третьей части поста я научу создавать микроweb-сервер с помощью Python, покажу на примерах, как можно передавать и получать данные от клиента к серверу, не перегружая страницы, используя JS и jQuery.

В предыдущих публикациях я показывала на примерах: как использовать наследование, инкапсуляцию и полиморфизм в JS, и как применять библиотеку jQuery. Если вы ещё не знакомы с первой и  второй частями поста, то предлагаю сначала прочитать их.

В материале использовался jQuery 3.4.1 и браузеры ЯндексБраузер 22.7.5.940 (64-bit), Microsoft Edge 105.0.1343.27 (Официальная сборка) (64-разрядная версия), Anaconda 4.4.0 (64 bit), Python 3.6.1. Также необходимо иметь доступ к БД PostgreSQL и установленную программу SQL редактора: pgAdmin или DBeaver.

Подготовка к работе

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

  1. скачать и установить Anaconda с официального сайта;
  2. проверить установлены ли у вас Python библиотеки (после установки Anaconda):
  • Flask – библиотека для реализации web-приложений, микрофреймворк, построенный для работы с WSGI;
  • requests – библиотека для передачи данных HTTP методами GET, POST.

Для этого, после установки Anaconda, в строке поиска найти и запустить Anaconda Promt и ввести команду:

pip list

Результат:

alabaster (0.7.10)

anaconda-client (1.6.3)

anaconda-navigator (1.6.2)

anaconda-project (0.6.0)

asn1crypto (0.22.0)

astroid (1.4.9)

astropy (1.3.2)

Babel (2.4.0)

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

Traceback (most recent call last):
  File "C:\workspace\form_es6\registry.py", line 2, in <module>
    from flask import Flask, request, jsonify, make_response, Response
ModuleNotFoundError: No module named 'flask'

Наберите поочередно следующие команды в Anaconda Promt, чтобы установить необходимые библиотеки для используемого примера:

pip install flask
pip install requests

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

Версию Python можно посмотреть командой в Anaconda Promt:

python -V

Результат:

Python 3.6.1 :: Anaconda 4.4.0 (64-bit)

3. добавьте в корень проекта файловой структуры, созданной во второй части поста, ещё одну директорию — «upload». Пока эта директория будет пустой, но она понадобится позднее для сохранения данных.

Реализация веб-приложения

— Сохранение данных, введённых в html-форму, в файл на сервере

— Сохранение данных, введённых в html-форму, в БД

Для того чтобы наладить коммуникацию клиент-сервера, необходимо поднять веб-сервер, который умеет по http протоколу принимать и отправлять данные. Можно серьёзно подойти к вопросу: установить и настроить http apache, IIS и другие веб-серверы. Но для тестирования может быть вполне достаточно микро веб-фреймворка, который будет обрабатывать запросы не хуже, чем настоящие веб-серверы. Поэтому сымитирую с помощью скрипта Python маленький веб-сервер.

Создайте файл registry.py в корне проекта и вставьте в него скрипт:

C:/workspace/registry.py

# импортируем библиотеки (или пакеты) codecs и flask. Из библиотеки flask в скрипт
# импортируются классы: Flask, request, jsonify, make_response, Response.

import codecs
from flask import Flask, request, jsonify, make_response, Response

# в переменную app создается экземпляр класса Flask с параметрами (
#   1.	__name__ {- имя пакета приложения, в данном случае используется по default},
#   2.	static_url_path="/static" {- наименование пути в URL, которое из запроса будет
#      обращаться к физической папке что указана в параметре «static_folder»},  
#   3.	static_folder="static" {- относительный путь до папки со статическими файлами, 
#      в нашем примере - это файлы, которые лежат в C:\workspace\form_es6\static},
#   4.	template_folder="template" {- относительный путь до папки, содержащей шаблоны}
# )

app = Flask(__name__,static_url_path="/static",static_folder="static",template_folder="template")


# @app.route('/') и @app.route('/index') – метод route объекта app создаст правило: 
# что при обращении по URL в браузере http://127.0.0.1:8080/ и 
# http://127.0.0.1:8080/index будет запускаться функция def index(). 
# В функции index() переменная path_file передает путь до шаблона index.html и 
# читает его с помощью модуля codecs и метода open в правильной кодировке
# return Response(file.read(), mimetype = 'text/html') – возвращает содержание 
# ответа сервера, представленное в виде «text/html».

@app.route('/')
@app.route('/index') 
def index():
      path_file="C:\\workspace\\form_es6\\template\\index.html"
      with codecs.open (path_file, "r", "utf-8") as file:
            return Response(file.read(), mimetype = 'text/html')

# проверяем был ли запущен файл	
# и запускаем приложение 

if __name__== "__main__":
      app.run(host='127.0.0.1', port=8080, debug=True)

Теперь необходимо запустить веб-приложение во фреймворке.

Для этого нужно запустить созданный скрипт в консоли Anaconda Promt с помощью интерпретатора python:

$ python C:/workspace/form_es6/registry.py

Чтобы проверить работу созданного веб-приложения, необходимо в браузере ввести http://127.0.0.1:8080/ или http://127.0.0.1:8080/index. Если приложение запустилось без ошибок, то браузер отобразит html форму, которая была создана в 1 и 2 части:

Теперь возникает вопрос: а для чего создавать веб-сервер, ведь можно просто открыть файл с html формой, и браузер также её отобразит? Да, можно до тех пор, пока не понадобится передавать данные от клиента к серверу и наоборот. Например, есть задача: зарегистрировать пользователей и собрать данные о них в файле или БД. Т.е. html форма регистрации, что отобразит браузер – это клиентская часть, а файл или БД – серверная часть. Теперь попробую введённые в html форму данные сохранить в файл. Нужно остановить веб-приложение, нажав в консоли Anaconda Promt Ctrl+C.

Добавлю строки в файле C:/workspace/form_es6/registry.py:

# импортируем библиотеки (или пакеты) codecs и flask. Из библиотеки flask в скрипт
# импортируются классы: Flask, request, jsonify, make_response, Response.

import codecs
from flask import Flask, request, jsonify, make_response, Response

# в переменную app создается экземпляр класса Flask с параметрами (
#   1.	__name__ {- имя пакета приложения, в данном случае используется по default},
#   2.	static_url_path="/static" {- наименование пути в URL, которое из запроса будет
#      обращаться к физической папке что указана в параметре «static_folder»},  
#   3.	static_folder="static" {- относительный путь до папки со статическими файлами, 
#      в нашем примере - это файлы, которые лежат в C:\workspace\form_es6\static},
#   4.	template_folder="template" {- относительный путь до папки, содержащей шаблоны}
# )

app = Flask(__name__,static_url_path="/static",static_folder="static",template_folder="template")


# @app.route('/') и @app.route('/index') – метод route объекта app создаст правило: 
# что при обращении по URL в браузере http://127.0.0.1:8080/ и 
# http://127.0.0.1:8080/index будет запускаться функция def index(). 
# В функции index() переменная path_file передает путь до шаблона index.html и 
# читает его с помощью модуля codecs и метода open в правильной кодировке
# return Response(file.read(), mimetype = 'text/html') – возвращает содержание 
# ответа сервера, представленное в виде «text/html».

@app.route('/')
@app.route('/index') 
def index():
      path_file="C:\\workspace\\form_es6\\template\\index.html"
      with codecs.open (path_file, "r", "utf-8") as file:
            return Response(file.read(), mimetype = 'text/html')

# создаем новое правило, которое при обращении по URL http://127.0.0.1:8080/login 
# будет срабатывать функция def login()
# а также укажем метод обмена данными POST
@app.route('/login', methods=["POST"])	
def login():
      if request.method == 'POST':
            fio = request.form.get('fio')
            region = request.form.get('region')
            np = request.form.get('np')
            email = request.form.get('email')
            birthdate = request.form.get('birthdate')
            sex = request.form.get('sex[]')
            about = request.form.get('about')

            # объединяем значения полей формы в одну строку
            str = fio+" "+region+" "+np+" "+email+" "+birthdate+" "+sex+" "+about+"\n"

            # Сохраняем строку в файл logins2.txt
            # Можно заменить сохранение данных в файл на сохранение данных 
            # в базу данных

            save_data(str)

            # возвращаем ответ в виде json для отображения ответа
            answer_json = '{"fio":"'+fio+'", "status":"OK"}'
            return answer_json

# сохраняет данные в файл logins2.txt
def save_data(content):
      with open("C:\\workspace\\form_es6\\upload\\logins2.txt", "a") as file:
            file.write(content)
      return

# проверяем был ли запущен файл	
# и запускаем приложение 
if __name__== "__main__":
      app.run(host='127.0.0.1', port=8080, debug=True)

Чтобы показать пользователю, что данные были сохранены, можно на стороне серверной части вернуть данные, которые потом передать клиентской части и, в свою очередь, использовать этот ответ в удобном отображении. Для этого в функции def login() возвращу строку в формате JSON:

            # возвращаем ответ в виде json для отображения ответа
            answer_json = '{"fio":"'+fio+'", "status":"OK"}'
            return answer_json

А на стороне клиентской части обработаю серверный ответ и покажу пользователю в читабельном виде. В файле C:\workspace\form_es6\static\js\form1ES6.js в методе form1.on(‘submit’,(e)) добавлю строки в части ответа от сервера:

this.form1.on('submit',(e)=>{					
      var self = this;
			
      $.ajax({
            type: "POST",
            dataType: 'html',
            url: "/login",		
            data: $("#registrity").serialize(),
            success: function(data) { //ответ от сервера
                                                    //console.log(data);
                                                    //ответ от серверной части в виде строки JSON 
                                                    //превращаем в JSON объект
                                                    var data1 = JSON.parse(data); 
                                                    //если у объекта JSON элемент status равен «OK», 
                                                    //тогда выводим значение элемента fio в отдельном теге.
                                                    if(data1.status=='OK'){ 
                                                          $("#p_answer_hello").html("Уважаемый "+data1.fio+"! Ваши данные записаны.");
                                                    }
                            },
                                                 error: function() {
	                                                                 // ignored
                                                           }
     });
    	return false;

});

Если вывести ответ от сервера в лог консоли: в console.log(data), то можно увидеть строку:

Это та самая строка, которую возвращает сервер в функции def login():

…
            # возвращаем ответ в виде json для отображения ответа
            answer_json = '{"fio":"'+fio+'", "status":"OK"}'
            return answer_json
…

Все готово. В консоли Anaconda Promt нужно перезапустить веб-приложение с добавленными строками:

$ python C:/workspace/form_es6/registry.py

Далее необходимо обновить страницу в браузере и заполнить html форму данными и нажать кнопку «Отправить»:

И теперь, если посмотреть в файл C:\workspace\form_es6\upload\logins2.txt, то можно увидеть сохраненные данные, которые были введены в html форму:

Аналогичным способом можно сохранить данные в БД. В качестве примера создам скрипт, в котором данные будут сохраняться в БД PostgreSQL.

Для этого нужно установить библиотеку psycopg2, которая позволит общаться python с PostgreSQL:

pip install psycopg2

Далее импортирую библиотеку в код C:/workspace/form_es6/registry.py:

# импортируем библиотеки (или пакеты) codecs и flask. Из библиотеки flask в скрипт
# импортируются классы: Flask, request, jsonify, make_response, Response.

import codecs
from flask import Flask, request, jsonify, make_response, Response

# импортируем библиотеку – адаптер python и postgreSQL
import psycopg2

…

Теперь нужна таблица, куда будут вставляться данные из html формы. Поэтому создам её в БД PostgreSQL. Необходимо подключиться к БД PostgreSQL в pgAdmin или DBeaver (свой SQL редактор) и выполнить скрипт:

CREATE TABLE sch.users (
       id serial NOT NULL,
       fio varchar(150) NOT NULL,
       region varchar(100) NOT NULL,
       np varchar(100) NULL,
       email varchar(50) NOT NULL,
       birthdate date NULL,
       sex varchar(2) NULL,
       about varchar(1000) null,
       constraint users_pk primary key (id)
);

Если ошибок при выполнении не возникло, то при обновлении дерева таблиц в DBeaver должна появится таблица users в схеме sch.

Теперь необходимо создать соединение к БД PostgreSQL, вставить данные в таблицу, выполнив SQL запрос в скрипте python. Для этого создам функцию save_data_in_db в файле C:/workspace/form_es6/registry.py:

# сохраняет данные в БД
def save_data_in_db(fio, region, np, email, birthdate, sex, about):
      try:
            # создадим соединение к БД 
            # Буква r перед строкой означает, что строка будет передается в байтах, 
            # что позволит избежать передачи неэкранированных спецсимволов 
            p_user_name = r"Login0005"
            p_user_password = "Pass0005"
            p_database_name = "pgdb"
            p_host = "host-db-pg0005.ru"
        
            conn = psycopg2.connect(host=p_host, dbname=p_database_name, user=p_user_name, password=p_user_password, port=5433)
            # сохраняем данные о пользователе в таблицу users
            row_data = conn.cursor()
            row_data.execute("INSERT INTO sch.users (fio, region, np, email, birthdate, sex, about) VALUES (%s,%s,%s,%s,%s,%s,%s)", (fio, region, np, email, birthdate, sex, about))
            conn.commit()
            # Не забывайте закрывать коннекцию к БД - это правило хорошего тона
            # Это поможет избежать генерации пустых сессий в БД 
            if conn:
                  conn.close()
            return True
      Finally:
            if conn:
                  conn.close()
            return False

В функцию def login() добавляю вызов функции save_data_in_db(fio, region, np, email, birthdate, sex, about) после функции save_data:

def login():
	if request.method == 'POST':
		fio = request.form.get('fio')
		region = request.form.get('region')
		np = request.form.get('np')
		email = request.form.get('email')
		birthdate = request.form.get('birthdate')
		sex = request.form.get('sex[]')
		about = request.form.get('about')

             # объединяем значения полей формы в одну строку
		str = fio+" "+region+" "+np+" "+email+" "+birthdate+" "+sex+" "+about+"\n"

             # Сохраняем строку в файл logins2.txt
             save_data(str)
             # Можно заменить сохранение данных в файле на сохранение данных 
             # в базу данных
             save_data_in_db(fio, region, np, email, birthdate, sex, about)

             # возвращаем ответ в виде json для отображения на ответной странице
		answer_json = '{"fio":"'+fio+'", "status":"OK"}'
		return answer_json

Запущу приложение в консоли:

$ python C:/workspace/form_es6/registry.py

Заполню html форму в браузере и нажму кнопку «Отправить»:

Если все отработает без ошибок, то введённые данные должны сохраниться и в файл C:\workspace\form_es6\upload\logins2.txt, и в таблицу users БД PostgreSQL:

В публикации я постаралась показать на простых примерах, как можно использовать сразу несколько технологий и «подружить» их между собой. Теперь вы понимаете основы технологии обмена данными клиент-сервера и можете получать серверные данные в веб-приложении. Можно создать диалоговое окно и использовать введенные данные на клиентской части и отправлять их серверной части в качестве фильтра данных, потом выводить полученные данные на экран.

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