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, вы можете напрямую вернуть dict из представления. Он будет автоматически сериализован в 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()