# Flask vaja 2 - Vizualizacija API podatkov V tej vaji boš ustvaril preprosto Flask aplikacijo, ki uporablja javno dostopen API in prikaže podatke na spletni strani. ## Potrebne knjižnice ```bash pip install flask requests ``` ## Osnove - ponovitev ### Flask osnove ```python # Uvozimo potrebne knjižnice from flask import Flask, render_template, request # Ustvarimo instanco Flask aplikacije # __name__ je posebna spremenljivka v Pythonu, ki vsebuje ime trenutnega modula app = Flask(__name__) # Definiramo pot (route) za domačo stran # @app.route je dekorator, ki pove Flasku, kateri URL naj sproži to funkcijo @app.route('/') def domov(): # render_template poišče HTML datoteko v mapi 'templates' in jo prikaže # podatek="vrednost" pošlje spremenljivko 'podatek' v HTML predlogo, # kjer jo lahko uporabimo s {{ podatek }} return render_template('index.html', podatek="vrednost") # Ta del poskrbi, da se aplikacija zažene, ko poženemo ta Python file # debug=True omogoča samodejno osveževanje ob spremembah kode if __name__ == '__main__': app.run(debug=True) ``` ### Jinja osnove ```html <!-- Primer Jinja sintakse --> <!-- Jinja je sistem predlog za Python, ki ga Flask uporablja za prikazovanje HTML. Omogoča nam dinamično generiranje HTML s Pythonovimi spremenljivkami in izrazi. --> <!-- Prikazovanje spremenljivke: uporabimo {{ ime_spremenljivke }} --> <h1>{{ podatek }}</h1> <!-- Zanka for: sprehajanje po seznamu elementov --> {% for x in seznam %} <!-- Za vsak element v seznamu se prikaže en odstavek --> <p>{{ x }}</p> {% endfor %} <!-- Pogojni stavek if: prikažemo vsebino samo, če je pogoj izpolnjen --> {% if pogoj %} <p>To se prikaže, če je pogoj True</p> {% endif %} ``` ## Dodajanje forme v Flask ### Osnovna forma v HTML ```html <!-- templates/index.html --> <!DOCTYPE html> <html> <head> <title>Moja API Aplikacija</title> </head> <body> <h1>Iskanje</h1> <!-- Forma za vnos podatkov: - method="POST": podatke pošljemo na strežnik (POST zahteva) - action="/iskanje": URL, kamor pošljemo podatke (Flask route) --> <form method="POST" action="/iskanje"> <!-- Label (oznaka) in input (vnosno polje) za vnos iskalnega niza - for="iskalni_niz": poveže oznako z vnosnim poljem - name="iskalni_niz": ime, pod katerim bo dostopen vnešen podatek v Flasku - required: polje mora biti izpolnjeno --> <label for="iskalni_niz">Iskalni niz:</label> <input type="text" id="iskalni_niz" name="iskalni_niz" required> <!-- Spustni seznam (dropdown) za izbiro kategorije - name="kategorija": ime, pod katerim bo dostopna izbira v Flasku --> <label for="kategorija">Kategorija:</label> <select id="kategorija" name="kategorija"> <!-- Vsaka možnost ima vrednost (value), ki se pošlje na strežnik --> <option value="filmi">Filmi</option> <option value="knjige">Knjige</option> <option value="glasba">Glasba</option> </select> <!-- Gumb za pošiljanje forme --> <button type="submit">Išči</button> </form> </body> </html> ``` ### Obdelava forme v Flask ```python # Definiramo pot '/iskanje' in dovolimo samo POST zahteve (poglej form method v index.html) # methods=['POST'] pomeni, da ta funkcija sprejema samo POST zahteve (iz obrazca) @app.route('/iskanje', methods=['POST']) def iskanje(): # Pridobimo podatke iz forme # request.form.get('ime_polja') vrne vrednost, ki jo je uporabnik vnesel v formo iskalni_niz = request.form.get('iskalni_niz') # Vrednost iz polja z name="iskalni_niz" kategorija = request.form.get('kategorija') # Vrednost iz polja z name="kategorija" # TUKAJ BI NORMALNO POSLALI ZAHTEVO NA API # Na tem mestu bi dodali kodo za pridobivanje podatkov iz API-ja # (primer je v naslednjem odseku kode) # Prikažemo predlogo 'rezultati.html' in ji pošljemo pridobljene podatke # Ti podatki bodo dostopni v predlogi kot {{ iskalni_niz }} in {{ kategorija }} return render_template('rezultati.html', iskalni_niz=iskalni_niz, kategorija=kategorija) ``` ## Povezava z API-jem ```python # Uvozimo knjižnico requests, ki nam omogoča pošiljanje HTTP zahtev import requests def pridobi_podatke_iz_api(iskalni_niz): """Funkcija za pridobivanje podatkov iz API-ja Args: iskalni_niz (str): Besedilo, po katerem iščemo Returns: list: Seznam rezultatov iz API-ja ali prazen seznam, če ni rezultatov """ # Sestavimo URL za API zahtevo # f-string (f"...") omogoča vključevanje spremenljivk v niz z uporabo {} # Primer za Open Movie Database API (OMDb) url = f"http://www.omdbapi.com/?s={iskalni_niz}&apikey=TVOJ_API_KLJUC" # Pošljemo GET zahtevo na API # requests.get() pošlje HTTP GET zahtevo na podan URL odgovor = requests.get(url) # Preverimo, ali je bila zahteva uspešna # Koda 200 pomeni "OK" (uspešna zahteva) if odgovor.status_code == 200: # Pretvorimo odgovor iz JSON formata v Python slovar ali seznam # .json() metoda pretvori JSON v Python podatkovne strukture # .get('Search', []) vrne vrednost ključa 'Search' ali prazen seznam, če ključa ni return odgovor.json().get('Search', []) # Če zahteva ni bila uspešna, vrnemo prazen seznam return [] ``` ### Prikaz rezultatov v HTML ```html <!-- templates/rezultati.html --> <!DOCTYPE html> <html> <head> <title>Rezultati iskanja</title> </head> <body> <!-- Prikažemo naslov z vrednostmi, ki smo jih dobili iz Flask funkcije --> <h1>Rezultati iskanja za "{{ iskalni_niz }}" v kategoriji {{ kategorija }}</h1> <!-- Povezava nazaj na domačo stran --> <a href="/">Novo iskanje</a> <div class="rezultati"> <!-- Preverimo, ali imamo kaj rezultatov --> {% if rezultati %} <!-- Če imamo rezultate, se sprehodimo po seznamu --> {% for rezultat in rezultati %} <!-- Za vsak rezultat ustvarimo div z ustreznimi podatki --> <div class="element"> <!-- Prikažemo naslov filma (Title je ključ v JSON odgovoru) --> <h2>{{ rezultat.Title }}</h2> <!-- Če obstaja poster, ga prikažemo --> {% if rezultat.Poster %} <!-- src je URL slike, alt je nadomestno besedilo (za dostopnost) --> <img src="{{ rezultat.Poster }}" alt="{{ rezultat.Title }}"> {% endif %} <!-- Prikažemo leto izida filma --> <p>Leto: {{ rezultat.Year }}</p> </div> {% endfor %} {% else %} <!-- Če ni rezultatov, prikažemo sporočilo --> <p>Ni rezultatov.</p> {% endif %} </div> </body> </html> ``` ## Celoten primer: Vremenska aplikacija ### app.py ```python # Uvozimo potrebne knjižnice from flask import Flask, render_template, request # Flask za spletno aplikacijo import requests # Za pošiljanje zahtev na API # Ustvarimo instanco Flask aplikacije app = Flask(__name__) # Če potrebuješ API ključ (za OpenWeatherMap ga potrebuješ) # Za pridobitev ključa se moraš registrirati na https://openweathermap.org/ API_KLJUC = 'tvoj-api-kljuc' # Tukaj vstavi svoj API ključ! # Definiramo pot '/' (domača stran) in dovolimo obe vrsti zahtev: GET in POST # GET zahteve so za prikaz strani, POST zahteve so za obdelavo podatkov iz forme @app.route('/', methods=['GET', 'POST']) def index(): # Najprej preverimo, ali je prišla POST zahteva (iz forme) if request.method == 'POST': # Pridobimo ime mesta iz forme mesto = request.form.get('mesto') # Sestavimo URL za API zahtevo # Dodamo parametre: # - q: ime mesta # - units=metric: za temperaturo v Celzijih (namesto Fahrenheit) # - appid: naš API ključ url = f"https://api.openweathermap.org/data/2.5/weather?q={mesto}&units=metric&appid={API_KLJUC}" # Pošljemo GET zahtevo na API odgovor = requests.get(url) # Preverimo, ali je bila zahteva uspešna (koda 200) if odgovor.status_code == 200: # Pretvorimo JSON odgovor v Python slovar podatki = odgovor.json() # Ustvarimo nov slovar z le tistimi podatki, ki jih potrebujemo # JSON struktura iz OpenWeatherMap API-ja je precej kompleksna, # zato izluščimo samo potrebne podatke vreme = { 'mesto': podatki['name'], # Ime mesta 'temperatura': podatki['main']['temp'], # Temperatura v °C 'opis': podatki['weather'][0]['description'], # Opis vremena (npr. "oblačno") 'ikona': podatki['weather'][0]['icon'], # ID ikone za prikaz 'vlaga': podatki['main']['humidity'] # Vlažnost v % } # Prikažemo predlogo 'vreme.html' s pridobljenimi podatki return render_template('vreme.html', vreme=vreme) else: # Če je prišlo do napake, prikažemo indeks z napako return render_template('index.html', napaka="Mesta ni mogoče najti!") # Če je GET zahteva (prvi obisk strani), samo prikažemo indeks return render_template('index.html') # Zažene aplikacijo, če je ta datoteka glavna (se izvaja neposredno) if __name__ == '__main__': app.run(debug=True) # debug=True omogoča samodejno osveževanje ob spremembah ``` ### templates/index.html ```html <!DOCTYPE html> <html> <head> <title>Vremenska aplikacija</title> </head> <body> <div> <h1>Vremenska aplikacija</h1> <!-- Prikaz sporočila o napaki, če obstaja: - Če spremenljivka 'napaka' obstaja (jo je Flask poslal), se prikaže sporočilo o napaki --> {% if napaka %} <p>{{ napaka }}</p> {% endif %} <!-- Obrazec za vnos mesta: - method="POST": podatke pošljemo na strežnik s POST metodo - Ker ni atributa action, se form pošlje na trenutni URL (/) --> <form method="POST"> <div> <!-- Label in input za vnos imena mesta --> <label for="mesto">Vnesite ime mesta:</label> <input type="text" id="mesto" name="mesto" required> </div> <!-- Gumb za pošiljanje forme --> <button type="submit">Prikaži vreme</button> </form> </div> </body> </html> ``` ### templates/vreme.html ```html <!DOCTYPE html> <html> <head> <!-- Naslov strani z imenom mesta (dinamično iz Flask-a) --> <title>Vreme za {{ vreme.mesto }}</title> </head> <body> <div> <!-- Povezava za vrnitev na domačo stran --> <a href="/">← Nazaj</a> <!-- Glavni podatki o vremenu --> <div> <!-- Ime mesta --> <h1>{{ vreme.mesto }}</h1> <!-- Glavni vremenski podatki --> <div> <!-- Vremenska ikona: - OpenWeatherMap ponuja ikone na tem URL-ju - vreme.ikona je ID ikone, ki smo ga dobili iz API-ja - @2x.png pomeni večjo različico ikone --> <img src="https://openweathermap.org/img/wn/{{ vreme.ikona }}@2x.png" alt="{{ vreme.opis }}"> <!-- Temperatura: - vreme.temperatura|round(1) zaokroži vrednost na 1 decimalno mesto - Pipe (|) v Jinji uvaja filter, ki preoblikuje vrednost --> <div>{{ vreme.temperatura|round(1) }}°C</div> <!-- Opis vremena (npr. "oblačno", "jasno"...) --> <div>{{ vreme.opis }}</div> </div> <!-- Dodatni vremenski podatki --> <div> <!-- Vlažnost v odstotkih --> <p>Vlažnost: {{ vreme.vlaga }}%</p> </div> </div> </div> </body> </html> ``` ### Opomba glede CSS Pri tej vaji se ne bomo osredotočali na CSS. Za delujočo aplikacijo je dovolj, da pripravite samo Python kodo in HTML predloge. ## Ideje za API-je Nekaj javno dostopnih API-jev za tvojo nalogo: ~~1. **Vremenska napoved** - OpenWeatherMap: `https://openweathermap.org/api`~~ 2. **Države** - REST Countries: `https://restcountries.com/` 3. **Slike psov** - Dog API: `https://dog.ceo/dog-api/` 4. **Informacije o filmih** - OMDb API: `https://www.omdbapi.com/` 5. **Vesoljske slike** - NASA API: `https://api.nasa.gov/` 6. **Šale** - JokeAPI: `https://jokeapi.dev/` 7. **Pokemoni** - PokéAPI: `https://pokeapi.co/` 8. https://github.com/public-apis/public-apis ## Naloga Tvoja naloga je: 1. Izberi poljuben javno dostopen API iz zgornjega seznama (ali najdi drug API) 2. Ustvari preprosto Flask aplikacijo z dvema HTML predlogama: - Prva za prikaz forme za vnos parametrov - Druga za prikaz rezultatov iz API-ja 3. Dodaj preprosto CSS stiliranje (lahko AI) 4. Pošlji zahtevo na API in prikaži podatke na spletni strani ## Struktura projekta ``` projekt/ ├── app.py # Glavna Flask aplikacija └── templates/ # Mapa za HTML predloge ├── index.html # Začetna stran s formo └── vreme.html # Stran za prikaz vremena ```