Katsotaan seuraavaksi miten taustajärjestelmä yhdistetään tietokantaan jotta sovellus voi tallentaa, muokata, hakea ja poistaa tietoja.
Vanha, vaarallinen tapa
Ennen vanhaan oli tyypillistä että web-sovellukset juttelivat tietokannan kanssa raakoja SQL-kyselyitä rakentamalla, usein yhdistäen käyttäjän syötettä kyselyyn. Jos ajatellaan vaikkapa yksinkertaista tehtävälistasovellusta, niin koodi joka lisää uuden tehtävän voisi näyttää tältä:
@app.route('/todos', methods=['POST'])
def create_todo():
title = request.form['title']
result = connection.execute(f"INSERT INTO todos (title) VALUES ('{title}')")
todo_id = result.lastrowid
return '', 204
Jos muistat HTML-pohjia käsittelevästä moduulista että tällainen vanhanaikainen malli HTML-rakennuksessa johti XSS-haavoittuvuuksiin, voit ehkä jo arvata minkälainen haavoittuvuus tästä tulee? Oikea vastaus on SQL-injektio. Jos käyttäjä syöttäisi title -muuttujassa heittomerkin, niin käyttäjähän voisi muuttaa SQL-kyselyn rakennetta mielensä mukaan.
SQL-injektioista voi oppia lisää Hakatemian SQL-injektiokurssilla, mutta katsotaan nyt oikea, moderni tapa käyttää tietokantaa web-sovelluksen taustajärjestelmästä.
Moderni, oikea tapa: ORM
Nykyään modernit web-sovellukset juttelevat tietokannan kanssa abstraktiokerroksen läpi, jota kutsutaan nimellä ORM (Object Relational Mapper). Tämä tekee tietokantakoodin kirjoittamisesta paitsi helpompaa ja hauskempaa, myös valtavasti turvallisempaa.
Aloitetaan määrittämällä data-luokka joka määrittää Todo-nimisen taulun sekä id, task ja done sarakkeet.
class Todo(db.Model):
id = db.Column(db.Integer, primary_key=True)
task = db.Column(db.String(200))
done = db.Column(db.Boolean)
Voimme nyt käyttää luokkaa tietojen käsittelyyn. Esimerkiksi kaikkien tehtävien hakeminen tietokannasta on näin helppoa:
todos = Todo.query.all()
Uusi tehtävä voidaan luoda seuraavasti:
new_todo = Todo(task='Osta ketsuppia', done=False)
db.session.add(new_todo)
db.session.commit()
Ja näin edelleen. Katsotaan seuraavaksi kokonaista esimerkkiä.
Yksinkertainen esimerkki
Tämä sovellus on yksinkertainen tehtävälista, joka on luotu käyttämällä Pythonin Flask- ja SQLAlchemy-kirjastoja. Sovelluksen avulla voit lisätä tehtäviä listaan, merkitä ne valmiiksi tai poistaa niitä.
Kun avaat sovelluksen, näet kaikki tietokannassa olevat tehtävät. Voit lisätä uusia tehtäviä syöttämällä ne tekstikenttään ja napsauttamalla "Lisää" -painiketta. Jokaisen tehtävän vieressä on kaksi linkkiä: yksi merkitä tehtävä valmiiksi ja toinen poistaa tehtävä.
Kun merkitset tehtävän valmiiksi, sen nimi yliviivataan. Jos haluat poistaa tehtävän, voit napsauttaa "x" -painiketta sen vieressä.
Tämän sovelluksen taustalla on SQLite-tietokanta, joka tallentaa kaikki lisäämäsi tehtävät todo.db nimiseen tietokantaan paikallisesti levylle.
main.py
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///todo.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
class Todo(db.Model):
id = db.Column(db.Integer, primary_key=True)
task = db.Column(db.String(200))
done = db.Column(db.Boolean)
@app.route('/')
def index():
todos = Todo.query.all()
return render_template('index.html', todos=todos)
@app.route('/add', methods=['POST'])
def add():
task = request.form['task']
new_todo = Todo(task=task, done=False)
db.session.add(new_todo)
db.session.commit()
return redirect(url_for('index'))
@app.route('/update/<int:id>')
def update(id):
todo = Todo.query.filter_by(id=id).first()
todo.done = not todo.done
db.session.commit()
return redirect(url_for('index'))
@app.route('/delete/<int:id>')
def delete(id):
todo = Todo.query.filter_by(id=id).first()
db.session.delete(todo)
db.session.commit()
return redirect(url_for('index'))
if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(host='0.0.0.0', port=81)
templates/index.html
<!DOCTYPE html>
<html>
<head>
<title>Tehtävälista</title>
</head>
<body>
<h1>Tehtävälista</h1>
<form method="POST" action="{{ url_for('add') }}">
<input type="text" name="task" placeholder="Lisää tehtävä">
<button type="submit">Lisää</button>
</form>
<br>
<ul>
{% for todo in todos %}
<li {% if todo.done %}style="text-decoration: line-through"{% endif %}>
<a href="{{ url_for('update', id=todo.id) }}">✓</a>
{{ todo.task }}
<a href="{{ url_for('delete', id=todo.id) }}">x</a>
</li>
{% endfor %}
</ul>
</body>
</html>
Harjoittele
Valitettavasti Replit-palvelu on muuttnut lennosta eikä enää anna suorittaa näitä koodeja suoraan selaimessa. Voit klikata alla olevaa "Open in Replit" linkkiä ja avata koodin Replit-palvelussa. Meillä on työn alla etsiä Replitille korvaaja.
Valmis ryhtymään eettiseksi hakkeriksi?
Aloita jo tänään.
Hakatemian jäsenenä saat rajoittamattoman pääsyn Hakatemian moduuleihin, harjoituksiin ja työkaluihin, sekä pääset discord-kanavalle jossa voit pyytää apua sekä ohjaajilta että muilta Hakatemian jäseniltä.