Web-kehityksen Perusteet

Tietokantayhteydet

Helppo
25 min

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

Kokeile itse!

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.

hakatemia pro

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ä.