如何使用 url_for() 将路径和查询数据传递到使用 FastAPI 和 Jinja2 的路由?

Mip*_*ips 5 python jinja2 fastapi

我想从 Jinja2 模板中调用 FastAPI 路由,并将路径查询数据(参数)传递给该路由。我在 Jinja2 模板中尝试过如下所示:

{{ url_for('function1', uustr=data.uustr, interval=1) }}
Run Code Online (Sandbox Code Playgroud)

这是我想要调用的 FastAPI 路由(为了演示目的,语法已被简化):

@app.get("/updates/data/{uustr}",response_class=HTMLResponse)
async def function1(request: Request, uustr:str, interval:int):

  return"""
<html>
    <head>
        <title>{{ uustr }}</title>
    </head>
    <body>
        <h1>{{ interval }}</h1>
    </body>
</html>
"""
Run Code Online (Sandbox Code Playgroud)

我收到此错误:

raise ValueError('context must include a "request" key') 
ValueError: context must include a "request" key
Run Code Online (Sandbox Code Playgroud)

有人有想法吗?

Chr*_*ris 5

这不是FastAPI的问题,而是Starlette的问题(即request.url_for()接收path参数,而不是 query参数)。因此,受#560#1385的启发,我创建了以下工作示例,用于从 Jinja2 模板中调用 FastAPI 路由,并传递query参数(单独或与path参数一起传递)。

请注意,这个功能可能会被引入到 Starlette #1385的下一版本中。因此,最好在它出来时使用它。

应用程序.py

import uvicorn
from fastapi import FastAPI, Response
from fastapi.templating import Jinja2Templates
from fastapi import Request
from fastapi.responses import HTMLResponse
import urllib

app = FastAPI()

 
class CustomURLProcessor:
    def __init__(self):  
        self.path = "" 
        self.request = None

    def url_for(self, request: Request, name: str, **params: str):
        self.path = request.url_for(name, **params)
        self.request = request
        return self
    
    def include_query_params(self, **params: str):
        parsed = list(urllib.parse.urlparse(self.path))
        parsed[4] = urllib.parse.urlencode(params)
        return urllib.parse.urlunparse(parsed)


templates = Jinja2Templates(directory='templates')
templates.env.globals['CustomURLProcessor'] = CustomURLProcessor


@app.get('/updates/page/{page_no}/item/{item_id}')
def updates(request: Request, page_no: int, item_id: int, user: str, msg: str):
    return templates.TemplateResponse("item.html", {"request": request, "page_no": page_no, "item_id":item_id, "user": user, "msg": msg})


@app.get('/updates_query_only')
def updates_query_only(request: Request, user: str, msg: str):
    return templates.TemplateResponse("item.html", {"request": request, "user": user, "msg": msg})

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

if __name__ == '__main__':
    uvicorn.run(app, host='127.0.0.1', port=8000, debug=True)
Run Code Online (Sandbox Code Playgroud)

模板/index.html

import uvicorn
from fastapi import FastAPI, Response
from fastapi.templating import Jinja2Templates
from fastapi import Request
from fastapi.responses import HTMLResponse
import urllib

app = FastAPI()

 
class CustomURLProcessor:
    def __init__(self):  
        self.path = "" 
        self.request = None

    def url_for(self, request: Request, name: str, **params: str):
        self.path = request.url_for(name, **params)
        self.request = request
        return self
    
    def include_query_params(self, **params: str):
        parsed = list(urllib.parse.urlparse(self.path))
        parsed[4] = urllib.parse.urlencode(params)
        return urllib.parse.urlunparse(parsed)


templates = Jinja2Templates(directory='templates')
templates.env.globals['CustomURLProcessor'] = CustomURLProcessor


@app.get('/updates/page/{page_no}/item/{item_id}')
def updates(request: Request, page_no: int, item_id: int, user: str, msg: str):
    return templates.TemplateResponse("item.html", {"request": request, "page_no": page_no, "item_id":item_id, "user": user, "msg": msg})


@app.get('/updates_query_only')
def updates_query_only(request: Request, user: str, msg: str):
    return templates.TemplateResponse("item.html", {"request": request, "user": user, "msg": msg})

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

if __name__ == '__main__':
    uvicorn.run(app, host='127.0.0.1', port=8000, debug=True)
Run Code Online (Sandbox Code Playgroud)

模板/ item.html

<!DOCTYPE html>
<html>
    <body>
        <div class="container">
            {% set cu = CustomURLProcessor() %}
            {% set _url = cu.url_for(request, 'updates', page_no=5, item_id=3).include_query_params(user='foo', msg='bar') %}
            <!-- if only query params required, use as follows: -->
            {# {% set _url = cu.url_for(request, 'updates_query_only').include_query_params(user='foo', msg='bar') %} #}
            
            <iframe src="{{ _url }}" width = 300 height = 300 style= "border: none;"></iframe>
        </div>
    </body>
</html>
Run Code Online (Sandbox Code Playgroud)

更新 1 - 包括查询参数

您现在可以使用 Starlette 的starlette.datastructures.URL,它提供了一种方法include_query_params。下面的例子:

app.py中导入该类URL并使其可从 Jinja2 模板访问:

<!DOCTYPE html>
<html>
    <body>
        <h1>Page No {{ page_no }}</h1>
        <h2>Item {{ item_id }}</h2>
        <h3>{{ user }}</h3>
        <h4>{{ msg }}</h4>
    </body>
</html>
Run Code Online (Sandbox Code Playgroud)

在 templates/ item.html中使用如下:

from starlette.datastructures import URL

templates = Jinja2Templates(directory="templates")
templates.env.globals['URL'] = URL
Run Code Online (Sandbox Code Playgroud)

更新 2 - 包括查询参数

request.url_for()函数现在返回一个starlette.datastructures.URL对象。因此,您可以简单地将查询参数添加到 URL,如下所示:

<!DOCTYPE html>
<html>
   <body>
      <div class="container">
         <iframe src="{{ URL(url_for('updates', page_no=5, item_id=3)).include_query_params(user='foo', msg='bar') }}" width = 300 height = 300 style= "border: none;"></iframe>
      </div>
   </body>
</html>
Run Code Online (Sandbox Code Playgroud)


Mip*_*ips -1

我找到了一个非常简单的解决方案,无需使用url_for()

如果来自 Flask 世界的人在这里遇到类似的问题,我的解决方案是:我在 jinja2 模板中创建了一个简单的 HTML 按钮:

<button onclick="myFunction()">pass data to route</button>
Run Code Online (Sandbox Code Playgroud)

我创建了一个非常简单的 JavaScript 函数来将数据传递给路由:

<script>
function myFunction() {
  window.location.href = "/updates/data/{{data.uustr}}?interval=2";
}
</script>
Run Code Online (Sandbox Code Playgroud)

就是这样。