3. Weather app rakendus¶
OpenWeatherMap on ilmaennustust ja hetkese ilma andmeid pakkuv veebileht. Sellel lehel on väga hea ja lihtne API, mille kaudu on võimalik igal arendajal luua oma ilmarakendus.
Käesolevas juhendis loome samm-sammult algelise ilmaandmeid kuvava rakenduse "skeleti". Koodinäidete põhjal loodud toimiv, kuid kohati poolik versioon weather appist on kättesaadav 4. peatükis. Parema lõpptulemuse saavutamiseks tasub tähele panna punasega välja toodud soovitusi ning nende põhjal iseseisvalt rakendust täiendada.
Juhendist aitavad paremini aru saada algteadmised järgnevates teemades:
API
HTTP request
Python
HTML, CSS
SQL (väga algeline)
3.1. API võtmed ja config¶
API võtmeid kasutatakse näiteks päringute arvu piiramiseks või tasuliste teenuste ligipääsu haldamiseks. OpenWeatherMapist ilmaandmete saamiseks on vaja konkreetse kasutajaga seotud API võtit, mis tuleb iga päringuga kaasa anda. Tasuta pakett võimaldab ühel kasutajal teha kuni 1000 API päringut päevas.
Loo tasuta konto OpenWeatherMapis.
Vali ülemiselt menüüribalt "API keys" või kasuta seda linki, et kontoga seotud API võtmeid hallata.
Vaikimisi peaks olema igale kasutajale genereeritud Default nimeline API key, mis näeb välja umbes selline: "5g6a09fa42d2285ed29f78bf7842aai7". Soovi korral on võimalik rohkem API võtmeid genereerida, kuid nende aktiveerumine võib veidi aega võtta.
Lisa API võti oma projekti config faili. Nii on kõige lihtsam salajast infot muust projektist eraldi hoida, kuid vajadusel siiski mugavalt kasutada.
Loo projekti juurkausta config.py
fail.
Mall config.py
class Config:
OPEN_WEATHER_API_KEY = "<sinu API võti>"
Lisa config.py
ka .gitignore
faili
# Private API keys
config.py
# Other .gitignore rules
# ...
Soovitus: Miks me lisasime äsja loodud faili .gitignore
i?
Üldjuhul on ükskõik milliste privaatsete API
võtmete või muu salajase info Giti versioonihalduses hoiustamine halb tava. Sellega kaasneb
uus probleem. Kuidas teavad teised meeskonnas, et milline peaks olema
config.py
fail? Selle probleemi lahendamiseks loome uue faili nimega config.example.py
,
millesse kopeerime algse faili sisu ning eemaldame kõik võtmed.
Näitefaili saame koos muu koodiga giti panna, vältides turvaaukude tekitamist.
Muudame
app.py
faili, et Flask saaks aru, millist config faili lugeda
from flask import Flask
from flask import render_template
app = Flask(__name__)
app.config.from_object('config:Config')
@app.route('/')
def hello_world():
api_key = app.config['OPEN_WEATHER_API_KEY']
app.logger.info(api_key)
return render_template('index.html')
if __name__ == '__main__':
app.run()
Soovitus: Pane rakendus käima ning võid leida, et su konsoolis on API võti välja logitud. Proovi API võti teha nähtavaks ka veebilehel. Selle probleemi lahendamiseks võid vastuseid leida Googlest.
Soovitus: Rakenduse config on igal pool kättesaadav. Selleks, et
rakenduse configi lugeda mõnes muus failis peale app.py
:
Impordi
from flask import current_app
Kasuta
current_app.config["<YOUR-CONFIG-FIELD-NAME>"]
3.2. API kasutamine¶
OpenWeatherMap API dokumentatsioon on kättesaadav siit.
Soovime saada praeguse hetke ilmaandmeid mingi kindla asukoha kohta. Selleks kasutame dokumentatsioonist leitud endpointi:
https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={api_key}
Nagu näha siis endpoint nõuab kolme parameetrit (lat, lon, API key). Soovitus: Sa võid nendest parameetritest mõelda nagu funktsiooni parameetritest Pythonis.
Loome uue faili openweather.py
ning proovime seal selle funktsiooni
realiseerida.
Mall openweather.py
import requests
def weather_data(lat: int, lon: int, api_key: str) -> dict:
"""
Retrieves weather data from the OpenWeatherMap API based on latitude and longitude coordinates.
Args:
lat (int): The latitude coordinate.
lon (int): The longitude coordinate.
api_key (str): The API key for accessing the OpenWeatherMap API.
Returns:
dict: A dictionary containing the weather data.
"""
url = f"https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={api_key}"
response = requests.get(url)
return response.json()
Mall app.py
:
from flask import Flask
from flask import render_template
from openweather import weather_data
app = Flask(__name__)
app.config.from_object('config:Config')
@app.route('/')
def hello_world():
data = weather_data(40.7128, -74.0060, app.config['OPEN_WEATHER_API_KEY'])
return render_template('index.html', data=data)
if __name__ == '__main__':
app.run()
Mall index.html
{% extends 'base.html' %}
{% block title %}
Home
{% endblock %}
{% block content %}
{{ data }}
{% endblock %}
Soovitus: Ilmselt ei lähe see rakendus enam tööle ning näitab errorit.
Põhjuseks on pip package requests
, mis on puudu sinu projektis.
Lae see endale alla ning ära unusta ka pakke freezida. Loe selle kohta
täpsemalt siit.
Nüüd peaks su koduleht sisaldama andmeid New Yorki ilma kohta.
3.3. Vaated¶
Veebilehtedel on üldjuhul mitu erinevat vaadet, kuhu navigeerida saab. Selles juhendis näitame, kuidas seda Flaskiga teha ning loome eraldi lehe ilmaandmete jaoks.
Loome uue faili
weather.html
templates kausta ning kasutame sama sisu naguindex.html
kasutab.Muudame
index.html
faili ning lisame lingi, mis viitab meie ilmaandmete lehele, mis asub URLil/weather
Mall index.html
{% extends 'base.html' %}
{% block title %}
Home
{% endblock %}
{% block content %}
<h1>Welcome to weather app!</h1>
<a href="{{ url_for('weather') }}">Click to see weather raport</a>
{% endblock %}
Soovitus: Genereerisime href jaoks URLi kasutades Flaski
url_for()
meetodit. Uuri selle kohta lähemalt
siit.
Muudame
app.py
faili ning lisame sinna uue vaate jaoks vajaliku URLi/weather
Mall app.py
from flask import Flask
from flask import render_template
from openweather import weather_data
app = Flask(__name__)
app.config.from_object('config:Config')
@app.route('/')
def home():
return render_template('index.html')
@app.route('/weather')
def weather():
data = weather_data(40.7128, -74.0060, app.config['OPEN_WEATHER_API_KEY'])
return render_template('weather.html', data=data)
if __name__ == '__main__':
app.run()
Nüüd saame navigeeruda kodulehelt ilmaandmete lehele, kuid ilmaandmete lehel on alati ühe ja sama asukoha andmed (New York). Selle probleemi lahendame järgmises peatükis.
Soovitus: Proovi luua veel vaateid erinevate linnade jaoks. Selleks
loo veel erinevaid funktsioone app.py
failis. Ära unusta ka
neile vaadetele linke lisada oma index.html
failis.
3.4. Otsinguparameetrid¶
Soovime lisada veebilehele otsingumootori, mille abil on võimalik
ilmaennustust saada ükskõik millise asukoha jaoks. Selleks kasutame HTML
<form>
tagi, ning URL otsinguparameetreid.
Muudame
index.html
ning lisame sinna otsimisfunktsionaalsuse kasutades<form>
elementi.
Mall index.html
{% extends 'base.html' %}
{% block title %}
Home
{% endblock %}
{% block content %}
<h1>Welcome to weather app!</h1>
<form action="{{ url_for('weather')}}" method="GET">
<label for="lat">Latitude</label>
<input type="text" name="lat" required>
<label for="lon">Longitude</label>
<input type="text" name="lon" required>
<button type="submit">Get weather</button>
</form>
{% endblock %}
Soovitus: Loe <form>
elemendi kohta lähemalt
siit.
Nüüd tekkisid su veebilehele otsinguväljad, kuhu saad sisestada andmeid. Pane tähele - kui andmed on sisestatud ning vajutad “Get weather”, siis kanduvad sisestatud väärtused ka uude URLi.
Näiteks kui sisestad (1, 1):
http://localhost:5000/weather?lat=1&lon=1
Kasutame URLis olevaid otsinguparameetreid, et asukohapõhist ilma näidata. Selleks peame muutma
weather
funktsiooniapp.py
failis.
Mall app.py
from flask import Flask
from flask import render_template
from flask import request
from openweather import weather_data
app = Flask(__name__)
app.config.from_object('config:Config')
@app.route('/')
def home():
return render_template('index.html')
@app.route('/weather')
def weather():
lat = request.args.get('lat')
lon = request.args.get('lon')
data = weather_data(lat, lon, app.config['OPEN_WEATHER_API_KEY'])
return render_template('weather.html', data=data)
if __name__ == '__main__':
app.run()
Nüüd saab ilmaandmeid küsida ükskõik millise asukoha kohta meie planeedil.
Soovitus: Võib-olla oled tähele pannud, et Latitude ja Longitude
väljadele saab sisestada ükskõik millist infot. Siiski ainult väga väike
hulk sisendeid annavad meile päris infot ilma kohta. Loo
sisendite validaator, mis võtab sisse lat ja lon väärtused ning
tagastab boolean väärtuse selle kohta, et kas andmed on sobivad. Kui ei
ole sobivad, siis muuda muutuja data
väärtuseks “ilmaandmed puuduvad”.
3.5. Andmete visualiseerimine¶
Hetkel on otsingutulemused inimese jaoks halvasti loetavad. Muudame need paremaks!
Paneme andmed tabelisse, et neid oleks parem visualiseerida.
Mall weather.html
{% extends 'base.html' %}
{% block title %}
Weather data
{% endblock %}
{% block content %}
<h1>Weather data</h1>
<table>
<tr>
<th>Location</th>
<th>Temperature</th>
<th>Weather</th>
<th>Icon</th>
</tr>
<tr>
<td>
{{ location["county"] }}
<br>
{{ location["state"] }}
<br>
{{ location["city"] }}
</td>
<td>
<!-- Converting K to C -->
{{ (data["main"]["temp"] - 273.15) // 1 }}°C
</td>
<td>
{{ data["weather"][0]["main"] }}
<br>
{{ data["weather"][0]["description"] }}
</td>
<td>
<img src="https://openweathermap.org/img/wn/{{ data['weather'][0]['icon'] }}@2x.png" alt="Weather Icon">
</td>
</tr>
</table>
{% endblock %}
Muudame koordinaatide info asukoha infoks, inimloetaval kujul.
Mall app.py
from flask import Flask
from flask import render_template
from flask import request
from openweather import weather_data
import reverse_geocode
app = Flask(__name__)
app.config.from_object('config:Config')
@app.route('/')
def home():
return render_template('index.html')
@app.route('/weather')
def weather():
lat = request.args.get('lat')
lon = request.args.get('lon')
# get location name data from lat and lon
location = reverse_geocode.get((lat, lon))
data = weather_data(lat, lon, app.config['OPEN_WEATHER_API_KEY'])
return render_template('weather.html', data=data, location=location)
if __name__ == '__main__':
app.run()
Laeme alla
reverse_geocode
paki.
Soovitus: Aktiveeri venv enne allalaadimist, pärast ära unusta pakke freezida.
pip install reverse_geocode
Käivitame rakenduse
flask --debug run
3.6. BONUS Interaktiivusus¶
Praegune lahendus koosneb kahest vaatest. Esiteks vaade, kus kasutaja saab andmeid sisestada ning teiseks vaade, kus kasutaja saab ilmaennustust näha. Tegelikult pole selle jaoks kahte vaadet vaja. Piisab ühest, kus on nii otsinguväljad kui ka ilmaennustuse tulemused. Loome sellise lahenduse kasutades HTMXi. HTMX on lihtne töörist, millega saab oma veebilehte peale esmast laadimist uuendada uute andmetega.
Lisame HTMXi võimekuse oma veebilehele. Selleks lisame
<script>
tagi oma veebilehe päisesse. Seda teeme failisbase.html
.
Mall base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{ url_for('static', filename='normalize.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<!--for HTMX support-->
<script src="https://unpkg.com/htmx.org@2.0.1"
integrity="sha384-QWGpdj554B4ETpJJC9z+ZHJcA/i59TyjxEPXiiUgN2WmTyV5OEZWCD6gQhgkdpB/"
crossorigin="anonymous"></script>
<title>{% block title %}{% endblock %}</title>
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
Muudame otsinguvälja selliselt, et vajutades nupule “Get weather” ei navigeeru kasutaja uuele lehele, vaid ilmaandmed ilmuvad samale lehele, kus otsing teostati.
Mall index.html
{% extends 'base.html' %}
{% block title %}
Home
{% endblock %}
{% block content %}
<h1>Welcome to weather app!</h1>
<form hx-get="{{ url_for('weather') }}" hx-target="#results">
<label for="lat">Latitude</label>
<input type="text" name="lat" required>
<label for="lon">Longitude</label>
<input type="text" name="lon" required>
<button type="submit">Get weather</button>
</form>
<div id="results">Input latitude and longitude to search for weather.</div>
{% endblock %}
Mis tagataustal toimub:
hx-get
teebGET
requesti aadressilehttp://localhost:5000/weather
.Request tuleb tagasi ilmainfo lehe andmetega.
hx-target
asendab elemendi (milleid="result"
) sisemise teksti response andmetega, milleks hetkel on ilmainfo.
Soovitus: Proovi luua lahendus, kus peale igat otsingut vana tulemust ei kustutata, vaid vana tulemus liigutatakse allapoole ja uus ilmub ülemisse otsa. Mõtle sellest kui listist, mille algusesse andmeid juurde lisatakse.
3.7. Andmebaas¶
Soovitus: Enne selle osaga alustamist tutvu juhendiga: PostgreSQL setup. Paigalda näiteks pgAdmin4 abil andmebaasi server ning uuenda config faili.
Andmebaasiga saab suhelda kahte moodi:
Suhtluse tüüp |
Pros |
Cons |
---|---|---|
Otsene suhtlus (SQL) |
Lihtsam mõista |
Raskem kirjutada |
Lihtsam üles seada |
Rohkem ülalpidamist |
|
Turvaaugud |
||
Kaudne suhtlus (ORM) |
Lihtsam kirjutada |
Raskem mõista |
Ühildub hästi keelega |
Raskem üles seada |
|
Vähem ülalpidamist |
Selles juhendis kirjeldame kuidas kasutada otsest suhtlust andmebaasi ja programmi vahel, kuna sellega on lihtsam algust teha.
Soovitus: Kui sul on soov minna teist teed ja kasutada ORM lahendust, siis uuri Flask-SQLAlchemy.
Lisame projektile
psycopg[binary]
paki, mis lubab meil lihtsasti ühenduda PostgreSQL andmebaasiga.
pip install "psycopg[binary]"
Lisame
config.py
faili andmebaasi ühenduseks vajaliku connection stringi. Mallis on välja toodud näidis, mis tuleks asendada enda andmebaasi andmetega.
Mall config.py
klass
class Config:
# Enter your OpenWeather API key here
OPEN_WEATHER_API_KEY = "<your-open-weather-api-key>"
# Connection string used for postgres database access.
POSTGRES_CONNECTION_STRING = "host=localhost user=user password=pass port=5432 dbname=app"
Soovitus: Ära unusta ka config.example.py
faili uuendada, et ka
teised sinu meeskonnas teaksid, milline peaks see fail välja nägema.
Loo uus kaust
database
. Seal saad hoiustada kõiki andmebaasiga seotud tegevusi.Loo sinna kausta fail
schema.sql
, kus saad kirjeldada milline su andmebaas välja peaks nägema. Selle rakenduse raames soovime hoiustada andmeid kasutajate ja kasutajate ajaloo kohta. SQLi kohta lähemalt loe siit juhendist.
Mall schema.sql
DROP TABLE IF EXISTS history_entries;
DROP TABLE IF EXISTS users;
CREATE TABLE users (
id INT GENERATED ALWAYS AS IDENTITY,
name VARCHAR(20) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
PRIMARY KEY(id)
);
CREATE TABLE history_entries(
id INT GENERATED ALWAYS AS IDENTITY,
lat FLOAT NOT NULL,
lon FLOAT NOT NULL,
user_id INT,
PRIMARY KEY(id),
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);
Soovime seda SQLi rakendada iga kord kui Flask käivitatakse. Selleks loome uue faili
database.py
ning lisame sinna funktsioonicreate_database
.
Mall database.py
import os.path
import psycopg
from flask import current_app
def create_database():
# load schema.sql file into variable
schema: str
with open(os.path.join(os.path.abspath("."), "database", "schema.sql"), "r") as file:
schema = file.read()
# Connect to an existing database
with psycopg.connect(current_app.config["POSTGRES_CONNECTION_STRING"]) as conn:
# Open a cursor to perform database operations
with conn.cursor() as cur:
# Execute the schema script to initialize the database
cur.execute(schema)
# Commit the changes to the database
# When this is not called, no changes will be made to database.
conn.commit()
Kutsume
create_database
funktsiooni väljaapp.py
failis
Mall app.py
import reverse_geocode
from flask import Flask
from flask import render_template
from flask import request
from database.database import create_database
from openweather import weather_data
app = Flask(__name__)
app.config.from_object('config:Config')
with app.app_context(): # must be in application context to execute
create_database()
Flaski rakendust käima pannes võid näha muudatusi oma andmebaasis. Muudatuste paremaks vaatlemiseks võid kasutada näiteks pgAdmin4 või DBeaver.
3.8. Kasutaja lisamine¶
Süsteemi peaks olema võimalik lisada kasutajaid. Selleks tuleb luua veebilehele registreerimise vorm, kus kasutaja saab sisestada oma andmed. Samuti tuleb neid andmeid hoiustada andmebaasis.
Loome kasutajate registreerimise vormi kodulehele. Selleks täiendame
index.html
faili. Kasutaja poolt sisestatud andmed suuname/signup
endpointile. Muudame veebilehte ka paremini loetavaks, lisades paarstyle
atribuuti.
Soovitus: Malli täielik kopeerimine eeldab ka BONUS osa.
Mall index.html
{% extends 'base.html' %}
{% block title %}
Home
{% endblock %}
{% block content %}
<div style="display:flex; justify-content:space-between;">
<div>
<h1>Welcome to weather app!</h1>
<form hx-get="{{ url_for('weather') }}" hx-target="#results" style="display:flex; flex-direction:column;">
<label for="lat" id="lat">Latitude</label>
<input type="text" name="lat" required>
<label for="lon" id="lon">Longitude</label>
<input type="text" name="lon" required>
<button type="submit">Get weather</button>
</form>
<div id="results">Input latitude and longitude to search for weather.</div>
</div>
<div>
<h1>Become a user</h1>
<form action="{{ url_for('signup') }}" method="POST" style="display:flex; flex-direction:column;">
<label for="name" id="name">Name</label>
<input type="text" name="name" required>
<label for="password" id="password">Password</label>
<input type="password" name="password" required>
<button type="submit">Register</button>
</form>
</div>
</div>
{% endblock %}
Soovitus: Pane tähele, et seekord kasutasime form
tagis POST
meetodit.
Täiendame ka
app.py
faili, lisades sinnasignup
funktsiooni.
Mall signup
@app.route('/signup', methods=["POST"])
def signup():
return render_template('index.html')
Kui panna hetkel rakendus käima, ei tee andmete sisestamine mitte midagi. Lisame kasutaja andmed andmebaasi. Selleks lisame database kausta uue faili nimega
user.py
, kuhu lisame kasutaja andmete andmebaasi lisamise loogika.
Mall user.py
import psycopg
from flask import current_app
def create_user(name: str, password: str):
with psycopg.connect(current_app.config["POSTGRES_CONNECTION_STRING"]) as conn:
with conn.cursor() as cur:
cur.execute(
"INSERT INTO users (name, password) VALUES (%s, %s)",
(name, password)
)
conn.commit()
Soovitus: Kui lisada 2 sama nimega kasutajat, tuleb veebilehel error. Proovi enne kasutaja lisamist teada saada, kas sellise nimega kasutaja on juba olemas, ja kui on, siis ära lisa uut kasutajat.
Täiendame viimast korda
signup
funktsiooni ning kutsumecreate_user
funktsiooni seal välja.
Mall signup
@app.route('/signup', methods=["POST"])
def signup():
name = request.form.get('name')
password = request.form.get('password')
create_user(name, password)
return redirect("/")
Soovitus: Tavaliselt on enamustel veebilehtedel parooli osas nõudmised, näiteks parool peab olema 8 tähte pikk, peab sisaldama numbreid jms. Proovi luua parooli validaator, mis ei loo kasutajat, kui parool on liiga nõrk.
Soovitus: Hetkel lisame parooli andmebaasi sellisel kujul nagu kasutaja selle meile andis. SEE EI OLE HEA TAVA. Hea tava ei ole see sellepärast, et kui mõni häkker pääseb andmebaasile ligi, siis on tal võimalik teisi kasutajaid jäljendada, kuna ta teab nende paroole. Lahendus sellele probleemile on paroolide hashimine. Proovi luua funktsioon, mis enne parooli andmebaasi panemist selle ära hashib.
3.8.1. Sessioonid¶
Hetkel lisatakse kastaja andmed küll andmebaasi, kuid veebilehe kasutamise osas see otseselt midagi ei muuda. Kasutaja olemasolu rakendamiseks ja kontrollimiseks on lihtsaim lahendus Flaski sisseehitatud sessioonid.
Seadistame SECRET_KEY, mida kasutatakse kasutaja sessiooni hashimiseks.
Mall app.py
app.secret_key = 'your_secret_key'
Soovitus: Hea praktika järgi tuleks ‘your_secret_key’ placeholderi
asemele kirjutada midagi muud. Lisaks võib secret_key
config faili tõsta.
Sessioonid põhinevad kasutajanimel ja user_idl. Seega lisame
user.py
faili funktsiooni, mille parameetriteks on kasutajanimi ja parool. Juhul kui kasutaja on olemas ja parool õige, tagastab funktsioon user_id, mida kasutame sessiooni alustamiseks.
Mall user.py
def check_user(name: str, password: str):
with psycopg.connect(current_app.config["POSTGRES_CONNECTION_STRING"]) as conn:
with conn.cursor() as cur:
cur.execute(
"SELECT id, password FROM users WHERE name = %s",
(name,)
)
user_data = cur.fetchone()
if user_data and password == user_data[1]:
return user_data[0]
return None
Täiendame
signup
funktsiooni nii, et peale kasutaja loomist algaks kohe uus sessioon.
Mall signup
from flask import session
@app.route('/signup', methods=["POST"])
def signup():
name = request.form.get('name')
password = request.form.get('password')
create_user(name, password)
user_id = check_user(name, password)
if user_id:
session['user_id'] = user_id
session['username'] = name
return redirect("/")
return redirect("/")
Lisame veel
login
välja, endpointi ja funktsiooni, et juba olemasoleva kasutajaga sessiooni alustada.
Mall index.html
<h1>Login</h1>
<form action="{{ url_for('login') }}" method="POST" style="display:flex; flex-direction:column;">
<label for="name" id="login-name">Name</label>
<input type="text" name="name" required>
<label for="password" id="login-password">Password</label>
<input type="password" name="password" required>
<button type="submit">Login</button>
</form>
Mall user.py
@app.route('/login', methods=["POST"])
def login():
name = request.form.get('name')
password = request.form.get('password')
user_id = check_user(name, password)
if user_id:
session['user_id'] = user_id
session['username'] = name
return redirect("/")
return redirect("/")
Tähelepanek: Backendis toimub küll palju toiminguid (kasutaja loomine, sisselogimine, parooli ja kasutajanime kontrollimine), kuid kasutaja ei saa nende toimingute kohta tagasisidet. Selle probleemi lahendab peatükk 3.9.
3.8.2. Conditional rendering¶
Soovime oma rakendusse lisada logout nupu, kuid seda oleks mõistlik kuvada ainult siis, kui kasutaja on juba sisse logitud. Samuti oleks mõistlik login ja signup väljasid kuvada ainult enne sisselogimist.
Lisame avalehele
logout
nupu, mida kuvatakse ainult sisselogitud kasutajale.session
on justkui muutuja, mille väljade väärtusele pääseb ligi nii backendis kui ka html faili sees.
Mall index.html
{% if session.get('username') %}
<h1>Welcome, {{ session.get('username') }}!</h1>
<button>
<a href="{{ url_for('logout') }}">Logout</a>
</button>
{% endif %}
Märkus: {% if %}
ja {% endif %}
vahele jäävad HTML elemendid
kuvatakse juhul kui session.get('username')
tõeväärtuseks on True.
Mall user.py
@app.route('/logout')
def logout():
session.pop('user_id', None)
session.pop('username', None)
flash('Logged out')
return redirect("/")
Muudame
{% if %}
ja{% endif %}
plokkide abilsignup
jalogin
väljade kuvamist.
Mall index.html
<div>
{% if not session.get('username') %}
<h1>Become a user</h1>
<form action="{{ url_for('signup') }}" method="POST" style="display:flex; flex-direction:column;">
<label for="name" id="name">Name</label>
<input type="text" name="name" required>
<label for="password" id="password">Password</label>
<input type="password" name="password" required>
<button type="submit">Register</button>
</form>
{% endif %}
</div>
<div>
{% if not session.get('username') %}
<h1>Login</h1>
<form action="{{ url_for('login') }}" method="POST" style="display:flex; flex-direction:column;">
<label for="name" id="login-name">Name</label>
<input type="text" name="name" required>
<label for="password" id="login-password">Password</label>
<input type="password" name="password" required>
<button type="submit">Login</button>
</form>
{% endif %}
</div>
Märkus: Erinevalt logout
väljast kasutame
not session.get('username')
.
3.8.3. Otsinguajalugu¶
Lisame rakendusele funktsionaalsuse, mille jaoks on varasemalt loodud kasutajate süsteem päriselt vajalik - otsinguajalugu. Iga ilmaandmete päring seotakse sisselogitud kasutajaga (selle olemasolul) ja salvestatakse andmebaasi.
history_entries
tabel on juba varasemaltschema.sql
failis defineeritud. Seega loome database kaustasearch_history.py
faili koos funktsioonidega, mille abil andmeid salvestada ja pärida.
Mall search_history.py
import psycopg
from flask import current_app
def log_search_query(user_id, lat, lon):
with psycopg.connect(current_app.config["POSTGRES_CONNECTION_STRING"]) as conn:
with conn.cursor() as cur:
cur.execute(
"INSERT INTO history_entries (lat, lon, user_id) VALUES (%s, %s, %s)",
(lat, lon, user_id)
)
conn.commit()
def get_user_search_histroy(user_id):
with psycopg.connect(current_app.config["POSTGRES_CONNECTION_STRING"]) as conn:
with conn.cursor() as cur:
cur.execute(
"SELECT lat, lon FROM history_entries WHERE user_id = %s",
(user_id,)
)
user_search_history = cur.fetchall()
return user_search_history
log_search_query
salvestab andmebaasi koordinaadid ning päringu
sooritanud kasutaja id. get_user_search_history
võtab argumendiks
kasutaja id ning tagastab kõik seotud koordinaadid.
Täiendame
weather
funktsiooni nii, et sisselogitud kasutaja päring salvestataks andmebaasi.
Mall app.py
@app.route('/weather')
def weather():
lat = request.args.get('lat')
lon = request.args.get('lon')
if 'user_id' in session:
log_search_query(session['user_id'], lat, lon)
location = reverse_geocode.get((lat, lon))
data = weather_data(lat, lon, app.config['OPEN_WEATHER_API_KEY'])
return render_template('weather.html', data=data, location=location)
BONUS: Lisa app.py faili endpoint
/history
, mis kutsub välja funktsiooniget_user_search_history
ning tagastab sisselogitud kasutaja otsinguajaloo.
3.9. Teavitused¶
Rakenduse kasutajakogemust aitaks parandada teavitused, mis kuvaksid infot näiteks vale parooli sisestamise korral.
Lisame HTML faili elemendi, milles backendist tulevaid sõnumeid kuvada.
Mall index.html
<div style="display:flex; justify-content:center;">
{% for msg in get_flashed_messages() %}
<h1>{{ msg }}</h1>
{% endfor %}
</div>
Flashi kasutades on backendist teavituste saatmine väga lihtne, piisab sobivasse kohta ühe koodirea lisamisest.
from flask import flash
Mall app.py
@app.route('/signup', methods=["POST"])
def signup():
name = request.form.get('name')
password = request.form.get('password')
create_user(name, password)
user_id = check_user(name, password)
if user_id:
session['user_id'] = user_id
session['username'] = name
flash("User created")
return redirect("/")
flash("Couldn't create user")
return redirect("/")
@app.route('/login', methods=["POST"])
def login():
name = request.form.get('name')
password = request.form.get('password')
user_id = check_user(name, password)
if user_id:
session['user_id'] = user_id
session['username'] = name
flash("Login successful")
return redirect("/")
flash('Invalid credentials')
return redirect("/")
Soovitus: Hea praktika kohaselt tuleks API route sisaldav fail
võimalikult tühjana hoida ehk importida muudes failides defineeritud
funktsioone. Proovi signup ja login loogika koos sõnumite kuvamisega
näiteks user.py
faili tõsta.