如何使用 FastAPI 和 Jinja2 在 HTML 页面中显示上传的图像?
- 2025-03-06 08:52:00
- admin 原创
- 84
问题描述:
我正在使用 FastAPI 和 Jinja2 提供一个 HTML 页面来上传图像文件,然后打开另一个带有上传图像名称的 HTML 链接来显示该图像。尽管我能够使上传部分和上传图像链接正常工作,但我无法使标签src
正常工作以实际显示图像。我该如何解决这个问题?
这是main.py:
from fastapi import FastAPI, File, UploadFile,Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
""" upload file and save it to local """
@app.post("/upload-file")
async def upload_file(file: UploadFile = File(...)):
with open(file.filename, "wb") as f:
f.write(file.file.read())
return {"filename": file.filename}
''' create html page to show uploaded file '''
@app.get("/")
async def root():
html_content = """
<html>
<head>
<title>Upload File</title>
</head>
<body>
<h1>Upload File</h1>
<form action="/upload-file/" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" />
</form>
</body>
</html>
"""
return HTMLResponse(content=html_content)
templates = Jinja2Templates(directory="templates")
@app.get("/{filename}", response_class=HTMLResponse)
async def read_item(request: Request, filename: str):
return templates.TemplateResponse("template.html", {"request": request,"filename": filename})
这是template.html:
<!DOCTYPE html>
<html>
<head>
<title>HTML img Tag</title>
</head>
<body>
<img src= "{{./filename}} " alt="uploaded image" width="400" height="400">
</body>
</html>
我愿意尝试更好/更简单的方法来实现所需的结果。谢谢。
解决方案 1:
在同一个 HTML 页面中预览上传的图像
以下是评论部分中建议的解决方案的示例(之前已在此答案中描述),您可以使用它将FileReader.readAsDataURL()
图像转换为客户端的 base64 编码字符串并在页面上显示图像,而无需 FastAPI 后端将其发送回给您,因为您需要显示用户上传的相同(未处理的)图像。 有关解决方案,请参见此处、此处以及此处和此处。 此外,要使用异步写入将图像文件写入磁盘,请查看此答案。
应用程序
from fastapi import File, UploadFile, Request, FastAPI, HTTPException
from fastapi.templating import Jinja2Templates
app = FastAPI()
templates = Jinja2Templates(directory="templates")
@app.post("/upload")
def upload(file: UploadFile = File(...)):
try:
contents = file.file.read()
with open("uploaded_" + file.filename, "wb") as f:
f.write(contents)
except Exception:
raise HTTPException(status_code=500, detail='Something went wrong')
finally:
file.file.close()
return {"message": f"Successfuly uploaded {file.filename}"}
@app.get("/")
def main(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
模板/index.html
<script type="text/javascript">
function previewFile() {
const preview = document.querySelector('img');
const file = document.querySelector('input[type=file]').files[0];
const reader = new FileReader();
reader.addEventListener("load", function() {
preview.src = reader.result; // show image in <img> tag
uploadFile(file)
}, false);
if (file) {
reader.readAsDataURL(file);
}
}
function uploadFile(file) {
var formData = new FormData();
formData.append('file', file);
fetch('/upload', {
method: 'POST',
body: formData,
})
.then(response => {
console.log(response);
})
.catch(error => {
console.error(error);
});
}
</script>
<input type="file" onchange="previewFile()"><br>
<img src="" height="200" alt="Image preview...">
单击按钮即可上传图片
如果你需要在用户点击按钮后将图像上传到 FastAPI 服务器Upload Image
(而不是像上面的模板那样在用户选择图像后立即自动上传),并且你想显示来自服务器的消息,无论图像是否已成功上传,你都可以使用下面的模板。
模板/index.html
<script type="text/javascript">
function previewFile() {
const preview = document.querySelector('img');
var file = document.getElementById('fileInput').files[0];
const reader = new FileReader();
reader.addEventListener("load", function() {
preview.src = reader.result; // show image in <img> tag
}, false);
if (file) {
reader.readAsDataURL(file);
}
}
function uploadFile(file) {
var file = document.getElementById('fileInput').files[0];
if (file) {
var formData = new FormData();
formData.append('file', file);
fetch('/upload', {
method: 'POST',
body: formData,
})
.then(response => response.json())
.then(data => {
document.getElementById("serverMsg").innerHTML = data.message;
})
.catch(error => {
console.error(error);
});
}
}
</script>
<input type="file" id="fileInput" onchange="previewFile()"><br>
<input type="button" value="Upload Image" onclick="uploadFile()">
<p id="serverMsg"></p>
<img height="200">
在新标签页中预览图片
要在新选项卡而不是同一选项卡中预览图像,您可以使用以下命令。一旦用户单击按钮,下面的代码将在新选项卡中打开图像(使用此处"Upload Image"
描述的方法) 。如果您需要在用户选择图像时打开选项卡,则注释掉函数previewFile()
中的行uploadFile()
并取消注释使用注释的 HTML<input type="file">
元素onchange="previewFile()"
。
模板/index.html
<script type="text/javascript">
function previewFile() {
const preview = document.querySelector('img');
var file = document.getElementById('fileInput').files[0];
const reader = new FileReader();
reader.addEventListener("load", function () {
displayImgInNewTab(reader.result)
}, false);
if (file) {
reader.readAsDataURL(file);
}
}
function uploadFile() {
var file = document.getElementById('fileInput').files[0];
if (file) {
var formData = new FormData();
formData.append('file', file);
fetch('/upload', {
method: 'POST',
body: formData,
})
.then(response => response.json())
.then(data => {
document.getElementById("serverMsg").innerHTML = data.message;
})
.catch(error => {
console.error(error);
});
previewFile()
}
}
function displayImgInNewTab(data) {
var image = new Image();
image.src = data
var w = window.open("");
w.document.write(image.outerHTML);
}
</script>
<!--<input type="file" id="fileInput" onchange="previewFile()"><br>-->
<input type="file" id="fileInput"><br>
<input type="button" value="Upload Image" onclick="uploadFile()">
<p id="serverMsg"></p>
<img height="200">
在新的 Jinja2 模板中返回并显示上传的图像
如果您想要在新的 Jinja2 模板中显示上传的图像,则可以将图像转换为 base64 编码的字符串并使用 返回TemplateResponse
,您可以在其中显示它。下面给出了工作示例。或者,您可以将上传的图像保存在目录下,并使用函数(例如)StaticFiles
在新模板中将其显示给用户;但是 - 如本答案中所述,它演示了显示/下载从服务器返回的文件的另外两种方法 - 您需要考虑是否希望服务器为多个用户提供服务,以及用户是否应该能够查看/访问其他用户上传的图像,以及您可能需要考虑为文件名生成随机名称/UUID(因为用户可能会上传具有相同的图像),并有一种机制在不再需要时从磁盘中删除图像(类似于此答案)。在这种情况下,下面演示的方法可能是您的更好选择。url_for()
`{{ url_for('static', path='/uploaded_img.png') }}`filename
应用程序
from fastapi import File, UploadFile, Request, FastAPI, HTTPException
from fastapi.templating import Jinja2Templates
import base64
app = FastAPI()
templates = Jinja2Templates(directory="templates")
@app.get("/")
def main(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
@app.post("/upload")
def upload(request: Request, file: UploadFile = File(...)):
try:
contents = file.file.read()
with open("uploaded_" + file.filename, "wb") as f:
f.write(contents)
except Exception:
raise HTTPException(status_code=500, detail='Something went wrong')
finally:
file.file.close()
base64_encoded_image = base64.b64encode(contents).decode("utf-8")
return templates.TemplateResponse("display.html", {"request": request, "myImage": base64_encoded_image})
模板/index.html
<html>
<body>
<form method="post" action="/upload" enctype="multipart/form-data">
<label for="file">Choose image to upload</label>
<input type="file" id="files" name="file"><br>
<input type="submit" value="Upload">
</form>
</body>
</html>
模板/display.html
<html>
<head>
<title>Display Uploaded Image</title>
</head>
<body>
<h1>My Image<h1>
<img src="data:image/jpeg;base64,{{ myImage | safe }}">
</body>
</html>
上述方法的替代方法是使用StaticFiles
目录,您可以在其中保存用户上传的图像,然后返回一个TemplateResponse
,将该图像的路径作为 Jinja2“上下文”(例如)中的键值对之一传递'imgPath': /static/uploaded_img.png'
,您可以使用它在中显示图像Jinja2Template
,例如<img src="{{ imgPath }}">
。注意:使用此方法,/static
使用系统的任何人都可以访问保存在目录下的图像。因此,如果这对您的任务来说是一个问题,最好不要采用这种方法。此外,使用此方法,您可能需要(根据项目的要求)设置一些流程以在一段有限的时间后删除图像,以防止耗尽磁盘空间。返回文件/图像的更多方法可以在此答案和此答案中看到。
扫码咨询,免费领取项目管理大礼包!