I am trying to pass a custom object (a list
of dict
in this case) to a Jinja2Template
using FastAPI; however, it seems that it only accepts the first item from the list
, instead of the entire list
of items.
I'm webscraping a site and I want the data to be displayed in the frontend page (i.e., /scrape
) in real time; however, it doesn't work, the only thing that I'm able to do is this:
main.py
from fastapi import FastAPI, Request, Form
from scrapes.scrape_corner import C_t3
from apis.general_pages.route_homepage import general_pages_router
from fastapi.templating import Jinja2Templates
import pathlib
from fastapi.responses import HTMLResponse
BASE_DIR = pathlib.Path(__file__).resolve().parent # app/
TEMPLATE_DIR = BASE_DIR / "templates" # / "general_pages"
app = FastAPI()
templates = Jinja2Templates(directory=str(TEMPLATE_DIR))
data_t3 = C_t3()
def include_router(app):
return app.include_router(general_pages_router)
@app.get("/scrape", status_code=200)
async def scraper():
context = {
"request": data_t3.scraper_t3()[0] # HERE IS THE ISSUE
}
print(context)
return templates.TemplateResponse("general_pages/homepage.html", context)
As you can see I'm able to retrieve the data from the API and display it in the frontend through a Jinja2 template.
Jinja2 template
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}my title{% endblock %}</title>
</head>
<body>
{% block content %}
<h1>Hello World</h1>
<p> {{ request.id }} {{ request.brand }} {{ request.availability_status }} {{ request.price }}</p>
<div style='margin-bottom:15px;'>
<p style='margin-bottom:2px;'>Campos:</p>
{% for items in request.items() %}
<li style='margin-bottom:2px;'><b>{{ items[0] }}</b>: {{ items[1] }}</li>
{% endfor %}
</div>
{% endblock %}
</body>
</html>
The scraper
import requests
class C_t3:
@staticmethod
def scraper_t3():
products_list = []
ids_list_whole = [1170046, 1191407, 1265903, 1191384] 1265903, 1191384, 1203972, 1170046, 1454790, 2065015]
ids_list = [1988649]
# req_prod_id = requests.get('https://cornershopapp.com/api/v2/branches/6558/products/1170046')
for x in ids_list_whole:
r = requests.get(f'https://cornershopapp.com/api/v2/branches/6558/products/{x}').json()
try:
id = r[0]['id']
except (TypeError, IndexError):
id = None
try:
availability_status = r[0]['availability_status']
except (TypeError, IndexError):
availability_status = None
try:
price = r[0]['original_price'].replace('NaN', '')
except (AttributeError, TypeError, IndexError):
price = None
try:
brand = r[0]['brand']['name']
except (TypeError, IndexError):
brand = None
# print(r)
if isinstance(id, (int, float)):
prod_dict = {
'id': id,
'availability_status': availability_status,
'price': price,
'brand': brand,
}
products_list.append(prod_dict)
return products_list
data_t3 = C_t3()
data_t3.scraper_t3()
The issue just starts here:
In main.py
, I have to use data_t3.scraper_t3()[0]
which returns the first item
from the list
.
If I replace the above statement with this: data_t3.scraper_t3()
, I'm getting this error:
jinja2.exceptions.UndefinedError: 'list object' has no attribute 'items'
And the response is this one:
{'request':[
{'id': 1191407, 'availability_status': 'AVAILABLE', 'price': None, 'brand': 'Intimus'},
{'id': 1265903, 'availability_status': 'AVAILABLE', 'price': None, 'brand': 'Intimus'},
]}
To be honest, I spent hours trying to solve this by myself, but I couldn't, and thus, I am asking for some help.
The expected result should be to be able to iterate over all items in the list within the Jinja2 templdate, and not only the first one.
The complete project code can be found here.
You shouldn't be using the request
key in the Jinja2 context
(when returning the TemplateResponse
) to pass your own custom object. The request
key is used to pass the Request
object—see Jinja2Templates
documentation—which you should always pass as part of the key-value pairs in the context
for Jinja2; otherwise, you would get a ValueError: context must include a "request" key
. Hence, you should use different key-value pairs for other data that you would like to pass to the Jinja2Template
. Example:
from fastapi import Request
@app.get("/scrape")
def scraper(request: Request):
context = {
"request": request,
"data": data_t3.scraper_t3()
}
return templates.TemplateResponse("general_pages/homepage.html", context)