如何使用FastAPI和Jinja2模板提交HTML表单<input>值?

问题描述 投票:0回答:2

我在尝试将值从 HTML 表单

<input>
元素传递到表单的
action
属性并将其发送到 FastAPI 服务器时遇到以下问题。

这就是 Jinja2 (HTML) 模板的加载方式:

# Test TEMPLATES
@app.get("/test",response_class=HTMLResponse)
async def read_item(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

我的 HTML 表单:

<form action="/disableSubCategory/{{subCatName}}">
    <label for="subCatName">SubCategory:</label><br>
    <input type="text" id="subCatName" name="subCatName" value=""><br>
    <input type="submit" value="Disable">
</form>

要在表单操作中调用我的 FastAPI 端点:

# Disable SubCategory
@app.get("/disableSubCategory/{subCatName}")
async def deactivateSubCategory(subCatName: str):
    disableSubCategory(subCatName)
    return {"message": "SubCategory [" + subCatName + "] Disabled"}

我得到的错误:

"GET /disableSubCategory/?subCatName=Barber HTTP/1.1" 404 Not Found

我想要实现的是以下 FastAPI 调用:

/disableSubCategory/{subCatName} ==> "/disableSubCategory/Barber"

任何人都可以帮助我理解我做错了什么?

谢谢。 狮子座

python html jinja2 fastapi
2个回答
10
投票

选项1

您可以在后端将类别名称定义为

Form
参数,并使用 HTML
<form>
从前端提交 POST 请求,如此答案的方法 1 中所述。

app.py

from fastapi import FastAPI, Form, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates

app = FastAPI()
templates = Jinja2Templates(directory='templates')

@app.post('/disable')
def disable_cat(cat_name: str = Form(...)):
    return f'{cat_name} category has been disabled.'

@app.get('/', response_class=HTMLResponse)
def main(request: Request):
    return templates.TemplateResponse('index.html', {'request': request})

模板/index.html

<!DOCTYPE html>
<html>
   <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
   </head>
   <body>
      <h1>Disable a category</h1>
      <form method="post" action="/disable">
         <label for="cat_name">Enter a category name to disable:</label><br>
         <input type="text" id="cat_name" name="cat_name">
         <input class="submit" type="submit" value="Submit">
      </form>
   </body>
</html>

要实现相同的结果,即通过

Form
请求向后端提交
POST
数据,使用 JavaScript 的 Fetch API 代替,您可以在前端使用以下模板(请参阅本答案后面的选项 4)对于类似的方法):

模板/index.html

<!DOCTYPE html>
<html>
   <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
   </head>
   <body>
      <h1>Disable a category</h1>
      <label for="cat_name">Enter a category name to disable:</label><br>
      <input type="text" id="cat_name" name="cat_name">
      <input type="button" value="Submit" onclick="send()">
      <p id="resp"></p>
      <script>
         function send() {
            var resp = document.getElementById("resp");
            var formData = new FormData();
            const cat_name = document.getElementById("cat_name").value;
            formData.append("cat_name", cat_name);
         
            fetch('/disable', {
                  method: 'POST',
                  body: formData,
               })
               .then(response => response.json())
               .then(data => {
                  resp.innerHTML = JSON.stringify(data); // data is a JSON object
               })
               .catch(error => {
                  console.error(error);
               });
         }
      </script>
   </body>
</html>

选项2

您可以在端点中将类别名称声明为查询参数,并在前端使用与问题中演示的类似方法将

<form>
<input>
元素中的值转换为查询参数,然后将其添加到 URL 的查询字符串(在
action
属性中)。

注意,与上面相反,下面使用 GET 请求(在这种情况下,您需要在后端使用

@app.get()
,在前端使用
<form method="get" ...
,无论如何,这是默认方法)。 请注意,大多数浏览器都会缓存 GET 请求(即保存在浏览器的历史记录中),因此与 POST 相比,它们的安全性较低,因为发送的数据是 URL 的一部分,并且对有权访问该设备的任何人都可见。因此,在发送密码或其他敏感信息时不应使用 GET 方法。

app.py

from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates

app = FastAPI()
templates = Jinja2Templates(directory='templates')

@app.get('/disable')
def disable_cat(cat_name: str):
    return f'{cat_name} category has been disabled.'

@app.get('/', response_class=HTMLResponse)
def main(request: Request):
    return templates.TemplateResponse('index.html', {'request': request})

模板/index.html

<!DOCTYPE html>
<html>
   <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
   </head>
   <body>
      <h1>Disable a category</h1>
      <form method="get" id="myForm" action='/disable{{ cat_name }}'>
         <label for="cat_name">Enter a category name to disable:</label><br>
         <input type="text" id="cat_name" name="cat_name">
         <input class="submit" type="submit" value="Submit">
      </form>
   </body>
</html>

如果您想使用 POST 请求,这比 GET 更安全,因为参数不会存储在浏览器的历史记录中,并且与 GET 相比,在更新服务器上的内容/状态时更有意义。在请求(而不是修改)数据时使用 - 您可以使用

@app.post()
定义 FastAPI 端点,并将上面的模板替换为下面的模板(类似于 thisanswer 的方法 2),该模板在转换后使用 POST 方法提交表单将数据形成查询参数:

<!DOCTYPE html>
<html>
   <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <script>
         document.addEventListener('DOMContentLoaded', (event) => {
            document.getElementById("myForm").addEventListener("submit", function (e) {
               var myForm = document.getElementById('myForm');
               var qs = new URLSearchParams(new FormData(myForm)).toString();
               myForm.action = '/disable?' + qs;
            });
         });
      </script>
   </head>
   <body>
      <h1>Disable a category</h1>
      <form method="post" id="myForm">
         <label for="cat_name">Enter a category name to disable:</label><br>
         <input type="text" id="cat_name" name="cat_name">
         <input class="submit" type="submit" value="Submit">
      </form>
   </body>
</html>

选项3

您仍然可以将其定义为路径参数(如您的问题所示),并在前端使用 JavaScript 来修改 HTML

action
<form>
属性,通过传递
<form>
<input>
的值元素作为 URL 的路径参数,类似于前面描述的内容。

app.py

from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates

app = FastAPI()
templates = Jinja2Templates(directory='templates')

@app.post('/disable/{name}')
def disable_cat(name: str):
    return f'{name} category has been disabled.'

@app.get('/', response_class=HTMLResponse)
def main(request: Request):
    return templates.TemplateResponse('index.html', {'request': request})

模板/index.html

<!DOCTYPE html>
<html>
   <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <script>
         document.addEventListener('DOMContentLoaded', (event) => {
            document.getElementById("myForm").addEventListener("submit", function (e) {
               var myForm = document.getElementById('myForm');
               var catName = document.getElementById('catName').value;
               myForm.action = '/disable/' + catName;
            });
         });
      </script>
   </head>
   <body>
      <h1>Disable a category</h1>
      <form method="post" id="myForm">
         <label for="catName">Enter a category name to disable:</label><br>
         <input type="text" id="catName" name="catName">
         <input class="submit" type="submit" value="Submit">
      </form>
   </body>
</html>

选项 4

如果您想在点击 HTML

submit
<form>
按钮时阻止页面重新加载/重定向,而是在同一页面中获取结果,您可以使用 Fetch API,如前面简要描述的,在命令发出异步 HTTP 请求,类似于 thisanswer,以及 thisanswerthisanswer。此外,可以调用
Event.preventDefault()
函数,如这个答案中所述,以防止提交 HTML 时的默认操作
<form>

下面的例子是基于之前的选项(即选项3);但是,如果您希望阻止浏览器在

<form>
提交时刷新页面,下面相同的方法(即发出异步 HTTP 请求)也可以用于前面演示的选项 1 和 2。请注意,此选项将
<input>
值作为路径参数提交到后端,但如果您想将其作为
Form
参数提交,请查看前面选项 1 中给出的相关代码。

app.py

from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates

app = FastAPI()
templates = Jinja2Templates(directory='templates')

@app.post('/disable/{name}')
def disable_cat(name: str):
    return f'{name} category has been disabled.'

@app.get('/', response_class=HTMLResponse)
def main(request: Request):
    return templates.TemplateResponse('index.html', {'request': request})

模板/index.html

<!DOCTYPE html>
<html>
   <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <script>
         document.addEventListener('DOMContentLoaded', (event) => {
            document.getElementById("myForm").addEventListener("submit", function (e) {
               e.preventDefault() // Cancel the default action
               var catName = document.getElementById('catName').value;
               fetch('/disable/' + catName, {
                     method: 'POST',
                  })
                  .then(resp => resp.text()) // or, resp.json(), etc.
                  .then(data => {
                     document.getElementById("response").innerHTML = data;
                  })
                  .catch(error => {
                     console.error(error);
                  });
            });
         });
      </script>
   </head>
   <body>
      <h1>Disable a category</h1>
      <form id="myForm">
         <label for="catName">Enter a category name to disable:</label><br>
         <input type="text" id="catName" name="catName">
         <input class="submit" type="submit" value="Submit">
      </form>
      <div id="response"></div>
   </body>
</html>

0
投票

只是为了向您提供反馈并跟踪我已实施的解决方案。

正如@Chris 提到的,我转到了建议的解决方案 3。

请在下面找到我的新代码:

== FastAPI ==

# Test TEMPLATES
@app.get("/test",response_class=HTMLResponse)
async def read_item(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

# Disable SubCategory
@app.post("/disableSubCategory/{subCatName}")
async def deactivateSubCategory(subCatName: str):
    disableSubCategory(subCatName)
    return {"message": "Sub-Category [" + subCatName + "] Disabled"}

# Enable SubCategory
@app.post("/enableSubCategory/{subCatName}")
async def activateSubCategory(subCatName: str):
    enableSubCategory(subCatName)
    return {"message": "Sub-Category [" + subCatName + "] Enabled"}

== HTML ==

<html>
<head>
    <title>Item Details</title>
    <link href="{{ url_for('static', path='/styles.css') }}" rel="stylesheet">

    <script>
        document.addEventListener('DOMContentLoaded', (event) => {
           document.getElementById("disableSubCategory").addEventListener("submit", function (e) {
              var myForm = document.getElementById('disableSubCategory');
              var disableSubCatName = document.getElementById('id_disableSubCategory').value;
              myForm.action = '/disableSubCategory/' + disableSubCatName;
           });
        });
     </script>

    <script>
        document.addEventListener('DOMContentLoaded', (event) => {
           document.getElementById("enableSubCategory").addEventListener("submit", function (e) {
              var myForm2 = document.getElementById('enableSubCategory');
              var enableSubCatName = document.getElementById('id_enableSubCategory').value;
              myForm2.action = '/enableSubCategory/' + enableSubCatName;
           });
        });
     </script>

</head>
<body>

    <form id="disableSubCategory" enctype="multipart/form-data" method="post">
        <label for="subCatName">SubCategory:</label><br>
        <input type="text" id="id_disableSubCategory" value=""><br>
        <input type="submit" value="Disable" id="disable">
    </form>

    <form id="enableSubCategory" enctype="multipart/form-data" method="post">
        <label for="subCatName">SubCategory:</label><br>
        <input type="text" id="id_enableSubCategory" value=""><br>
        <input type="submit" value="Enable" id="enable">
    </form>

</body>
</html>
© www.soinside.com 2019 - 2024. All rights reserved.