htmlx - ghdrako/doc_snipets GitHub Wiki


tags:

Htmlx

Python:

Popularne polaczenie z pythonem dla niewielkich projektow, zeby nie trzeba bylo projektowac interfejsow w json miedzy frontend a backend jak w React. Wszystko jest w kodzie Pythona.

Przyklad

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()

# Nasza "baza danych"
MOVIES = [
    "Matrix", "Mroczny Rycerz", "Incepcja", 
    "Interstellar", "Władca Pierścieni", "Gladiator", "Gwiezdne Wojny"
]

# 1. Główny widok zwracający stronę HTML
@app.get("/", response_class=HTMLResponse)
def home():
    return """
    <!DOCTYPE html>
    <html>
    <head>
        <script src="https://unpkg.com/[email protected]"></script>
    </head>
    <body style="font-family: sans-serif; padding: 20px;">
        <h2>Wyszukiwarka filmów (htmx + Python)</h2>
        
        <!-- Magia htmx w jednym tagu input: -->
        <input type="text" 
               name="q"
               placeholder="Zacznij wpisywać tytuł..." 
               hx-get="/search" 
               hx-trigger="keyup changed delay:300ms" 
               hx-target="#wyniki">
               
        <ul id="wyniki">
            <!-- Tutaj htmx dynamicznie wklei wyniki z serwera -->
        </ul>
    </body>
    </html>
    """

# 2. Endpoint wyszukiwania (zwraca HTML, NIE JSON!)
@app.get("/search", response_class=HTMLResponse)
def search(q: str = ""):
    if not q:
        return "" # Jeśli pole jest puste, zwracamy pusty string, żeby wyczyścić listę
        
    # Szukamy filmów pasujących do zapytania
    znalezione = [m for m in MOVIES if q.lower() in m.lower()]
    
    if not znalezione:
        return "<li><i>Brak wyników</i></li>"
        
    # Python generuje i zwraca gotowe elementy <li> HTML
    html_wynik = ""
    for film in znalezione:
        html_wynik += f"<li>{film}</li>"
        
    return html_wynik

Przyklad z jinja template

mój_projekt/
├── main.py
└── templates/
    ├── index.html        (Główna, pełna strona)
    └── partials/
        └── results.html  (Tylko fragment z wynikami)

templates/index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Wyszukiwarka htmx</title>
    <script src="https://unpkg.com/[email protected]"></script>
</head>
<body style="font-family: sans-serif; padding: 20px;">
    <h2>Wyszukiwarka filmów (Jinja2)</h2>
    
    <input type="text" 
           name="q"
           placeholder="Zacznij wpisywać tytuł..." 
           hx-get="/search" 
           hx-trigger="keyup changed delay:300ms" 
           hx-target="#wyniki">
           
    <ul id="wyniki">
        <!-- Zostawiamy puste. Htmx wklei tutaj zawartość z results.html -->
    </ul>
</body>
</html>

templates/partials/results.html

{% if not q %}
    <!-- Jeśli zapytanie jest puste, nie wyświetlamy nic (czyści listę) -->
{% elif znalezione %}
    <!-- Jeśli znaleziono filmy, pętla generuje tagi <li> -->
    {% for film in znalezione %}
        <li>{{ film }}</li>
    {% endfor %}
{% else %}
    <!-- Jeśli wpisano tekst, ale nic nie znaleziono -->
    <li><i>Brak wyników dla hasła "{{ q }}"</i></li>
{% endif %}

main.py

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

app = FastAPI()

# Konfiguracja Jinja2 - wskazujemy folder z szablonami
templates = Jinja2Templates(directory="templates")

MOVIES = [
    "Matrix", "Mroczny Rycerz", "Incepcja", 
    "Interstellar", "Władca Pierścieni", "Gladiator", "Gwiezdne Wojny"
]

@app.get("/", response_class=HTMLResponse)
async def home(request: Request):
    # Zwracamy pełną stronę
    return templates.TemplateResponse(
        "index.html", 
        {"request": request}
    )

@app.get("/search", response_class=HTMLResponse)
async def search(request: Request, q: str = ""):
    # Logika wyszukiwania
    if not q:
        znalezione = []
    else:
        znalezione = [m for m in MOVIES if q.lower() in m.lower()]
    
    # Zwracamy TYLKO MAŁY FRAGMENT (partial) z odpowiednimi zmiennymi
    return templates.TemplateResponse(
        "partials/results.html", 
        {"request": request, "q": q, "znalezione": znalezione}
    )

test_main.py

from fastapi.testclient import TestClient
from bs4 import BeautifulSoup
from main import app  # Importujesz aplikację z pliku main.py

client = TestClient(app)

def test_strona_glowna_zawiera_formularz():
    response = client.get("/")
    assert response.status_code == 200
    # Sprawdzamy, czy poprawnie wpięliśmy atrybuty htmx
    assert 'hx-get="/search"' in response.text

def test_wyszukiwanie_znajduje_film():
    # Symulujemy zapytanie wysłane przez htmx
    response = client.get("/search?q=mat")
    
    assert response.status_code == 200
    # Zamiast asercji na strukturze JSON, sprawdzamy zwrócony HTML (partial)
    assert "<li>Matrix</li>" in response.text
    assert "<li>Mroczny Rycerz</li>" not in response.text

def test_wyszukiwanie_brak_wynikow():
    response = client.get("/search?q=avengers")
    assert response.status_code == 200
    assert "Brak wyników dla hasła" in response.text

def test_wyszukiwanie_puste_zapytanie():
    response = client.get("/search?q=")
    assert response.status_code == 200
    # Puste zapytanie zgodnie z logiką zwraca pusty string
    assert response.text.strip() == ""

def test_wyszukiwanie_zwraca_odpowiednia_liczbe_elementow():
    response = client.get("/search?q=w")
    soup = BeautifulSoup(response.text, "html.parser")
    
    # Weryfikujemy, czy wygenerowały się dokładnie 2 elementy <li> (Władca Pierścieni, Gwiezdne Wojny)
    elementy_li = soup.find_all("li")
    assert len(elementy_li) == 2
⚠️ **GitHub.com Fallback** ⚠️