JavaScript, fetch, и JSON
Вы можете сделать вашу HTML-страницу динамичной, изменяя данные без перезагрузки всей страницы. Вместо отправки HTML <form> и перенаправления для повторного рендеринга шаблона, вы можете добавить JavaScript, который вызывает fetch() и заменяет содержимое на странице.
fetch() — это современное встроенное решение JavaScript для отправки запросов со страницы. Вы, возможно, слышали о других методах и библиотеках «AJAX», таких как XMLHttpRequest() или jQuery. Они больше не нужны в современных браузерах, хотя вы можете использовать их или другую библиотеку в зависимости от требований вашего приложения. Эти документы будут сосредоточены только на встроенных функциях JavaScript.
Рендеринг Шаблонов
Важно понимать разницу между шаблонами и JavaScript. Шаблоны рендерятся на сервере, прежде чем ответ будет отправлен браузеру пользователя. JavaScript выполняется в браузере пользователя после рендеринга и отправки шаблона. Поэтому невозможно использовать JavaScript для влияния на то, как рендерится шаблон Jinja, но можно отобразить данные в JavaScript, который будет выполняться.
Чтобы предоставить данные JavaScript при рендеринге шаблона, используйте фильтр tojson() в блоке <script>. Это преобразует данные в допустимый объект JavaScript и гарантирует, что любые небезопасные символы HTML будут отображаться безопасно. Если вы не используете фильтр tojson, вы получите ошибку SyntaxError в консоли браузера.
data = generate_report()
return render_template("report.html", chart_data=data)
<script>
const chart_data = {{ chart_data|tojson }}
chartLib.makeChart(chart_data)
</script>
Менее распространённый способ — добавить данные в атрибут data- тега HTML. В этом случае вы должны использовать одинарные кавычки вокруг значения, а не двойные, иначе вы получите недопустимый или небезопасный HTML.
<div data-chart='{{ chart_data|tojson }}'></div>
Генерация URL-адресов
Другой способ получения данных с сервера в JavaScript — это отправка запроса за ними. Сначала вам нужно знать URL-адрес для запроса.
Самый простой способ сгенерировать URL-адреса — продолжить использование url_for() при рендеринге шаблона. Например:
const user_url = {{ url_for("user", id=current_user.id)|tojson }}
fetch(user_url).then(...)
Однако, вам может потребоваться сгенерировать URL-адрес на основе информации, известной только в JavaScript. Как обсуждалось выше, JavaScript выполняется в браузере пользователя, а не в рамках рендеринга шаблона, поэтому вы не можете использовать url_for в этот момент.
В этом случае вам нужно знать «корневой URL», под которым обслуживается ваше приложение. В простых настройках это /, но это также может быть что-то другое, например https://example.com/myapp/.
Простой способ сообщить вашему JavaScript-коду об этом корне — задать его как глобальную переменную при рендеринге шаблона. Тогда вы можете использовать его для генерации URL-адресов из JavaScript.
const SCRIPT_ROOT = {{ request.script_root|tojson }}
let user_id = ... // do something to get a user id from the page
let user_url = `${SCRIPT_ROOT}/user/${user_id}`
fetch(user_url).then(...)
Отправка запроса с помощью fetch
fetch() принимает два аргумента: URL-адрес и объект с другими параметрами и возвращает Promise. Мы не будем рассматривать все доступные параметры и будем использовать then() по обещанию, а не другие обратные вызовы или синтаксис await. Подробнее об этих функциях см. в документации MDN.
По умолчанию используется метод GET. Если ответ содержит JSON, он может использоваться с then() цепочкой обратных вызовов.
const room_url = {{ url_for("room_detail", id=room.id)|tojson }}
fetch(room_url)
.then(response => response.json())
.then(data => {
// data is a parsed JSON object
})
Для отправки данных используйте метод, такой как POST, и передайте параметр body. Наиболее распространённые типы данных — данные формы или данные JSON.
Для отправки данных формы передайте заполненный объект FormData. Это использует тот же формат, что и HTML-форма, и будет обращаться к нему с помощью request.form в представлении Flask.
let data = new FormData()
data.append("name": "Flask Room")
data.append("description": "Talk about Flask here.")
fetch(room_url, {
"method": "POST",
"body": data,
}).then(...)
В общем случае отдавайте предпочтение отправке данных запроса в формате данных формы, как это используется при отправке HTML-формы. JSON может представлять более сложные данные, но если вам это не нужно, лучше придерживаться более простого формата. При отправке данных JSON также должен быть отправлен заголовок Content-Type: application/json, в противном случае Flask вернёт ошибку 400.
let data = {
"name": "Flask Room",
"description": "Talk about Flask here.",
}
fetch(room_url, {
"method": "POST",
"headers": {"Content-Type": "application/json"},
"body": JSON.stringify(data),
}).then(...)
Следование Перенаправлениям
Ответ может быть перенаправлением, например, если вы вошли в систему с помощью JavaScript вместо традиционной HTML-формы, и ваше представление вернуло перенаправление вместо JSON. JavaScript-запросы следуют перенаправлениям, но не изменяют страницу. Если вы хотите изменить страницу, вы можете проверить ответ и вручную применить перенаправление.
fetch("/login", {"body": ...}).then(
response => {
if (response.redirected) {
window.location = response.url
} else {
showLoginError()
}
}
)
Замена Содержимого
Ответ может быть новым HTML, либо новым разделом страницы для добавления или замены, либо совершенно новой страницей. В целом, если вы возвращаете всю страницу, лучше обработать это с помощью перенаправления, как показано в предыдущем разделе. Следующий пример показывает, как заменить <div> HTML, возвращённым запросом.
<div id="geology-fact">
{{ include "geology_fact.html" }}
</div>
<script>
const geology_url = {{ url_for("geology_fact")|tojson }}
const geology_div = getElementById("geology-fact")
fetch(geology_url)
.then(response => response.text)
.then(text => geology_div.innerHtml = text)
</script>
Возврат JSON из представлений
Чтобы вернуть объект JSON из вашего представления API, вы можете напрямую вернуть словарь из представления. Он будет сериализован в JSON автоматически.
@app.route("/user/<int:id>")
def user_detail(id):
user = User.query.get_or_404(id)
return {
"username": User.username,
"email": User.email,
"picture": url_for("static", filename=f"users/{id}/profile.png"),
}
Если вы хотите вернуть другой тип JSON, используйте функцию jsonify(), которая создаёт объект ответа с заданными данными, сериализованными в JSON.
from flask import jsonify
@app.route("/users")
def user_list():
users = User.query.order_by(User.name).all()
return jsonify([u.to_json() for u in users])
Обычно не рекомендуется возвращать данные файлов в ответе JSON. JSON не может напрямую представлять двоичные данные, поэтому они должны быть закодированы в base64, что может быть медленным, занимает больше пропускной способности для отправки и не так легко кешируется. Вместо этого используйте одно представление для обработки файлов и генерируйте URL-адрес для нужного файла, который будет включён в JSON. Затем клиент может сделать отдельный запрос для получения связанного ресурса после получения JSON.
Получение JSON в представлениях
Используйте свойство json объекта request, чтобы декодировать тело запроса как JSON. Если тело не является допустимым JSON или заголовок Content-Type не установлен на application/json, будет выброшена ошибка 400 Bad Request.
from flask import request
@app.post("/user/<int:id>")
def user_update(id):
user = User.query.get_or_404(id)
user.update_from_json(request.json)
db.session.commit()
return user.to_json()
© 2007–2022 Pallets
Licensed under the BSD 3-clause License.
https://flask.palletsprojects.com/en/2.2.x/patterns/javascript/