Search code examples
pythonhtmlfrontendjinja2fastapi

How to pass a custom object to Jinja2 Template using FastAPI?


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.


Solution

  • 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)