Create Read Update Delete tominnallisuus Flask ohjelma - Viikkotehtävä 6
TLDR (Too Long Didn’t Read)
Kurssin materiaali sekä tehtävänannot
Tehtävät A ja B
Ensimmäiset kaksi tehtävää olivat luoda flaskillä tietokantaa käyttävä ohjelma, sekä saada se toimimaan mod_wsgin kanssa. Tämän tein jo edellisen viikon tehtävässä jonka voi lukea täältä.
Tehtävä C - Tauluja!
Tässä tehtävässä piti tehdä flask ohjelma, jossa on kaksi taulua. Lisäsin shopping list ohjelmaan valittavissa olevat osastot omaan tietokantatauluunsa ja loin relaation tuotteiden, sekä osastojen välille. Aluksi tehtävän suorittaminen tuntui haastavalta, sillä flask-SQLAlchemy on erilaista kuin pelkkien SQL komentojen kirjoittaminen. Aikaisemmasta kokemuksesta SQL:n parissa oli hyötyä tehtävää suorittaessa ja jälkeenpäin katsottuna tämä flask-SQLAlchemyn tapa on melko yksinkertainen kunhan sen ensiksi ymmärtää.
Alkutoimet
Aluksi täytyi muuttaa flask sovelluksen sisältöä. Osastoille täytyi luoda oma pöytä, sekä muokata Testipoyta pöytään relaatio osastoja varten. Myös automaattiseen WTFormiin täytyi vaihtaa tyyppi StringFieldistä -> SelectFieldiksi
class Osastot(db.Model):
id = db.Column(db.Integer, primary_key=True)
osastonnimi = db.Column(db.String, nullable=False)
tuotteet = db.relationship('Testipoyta', backref='tuotteet', lazy=True)
class Testipoyta(db.Model):
id = db.Column(db.Integer, primary_key=True)
nimi = db.Column(db.String, nullable=False)
maara = db.Column(db.String, nullable=False)
osasto_id = db.Column(db.Integer, db.ForeignKey('osastot.id'), nullable=False)
class addForm(FlaskForm):
nimi = StringField("Tuotteen nimi", validators=[DataRequired()])
maara = StringField("Määrä", validators=[DataRequired()])
osasto_id = SelectField("Tuotteen osasto", choices=[], coerce=int, validators=[DataRequired()])
Vielä piti lisätä shopping_list funktioon yksi rivi, jotta SelectField saa vaihtoehdot osastoille tietokannasta
form.osasto_id.choices = [(osasto.id, osasto.osastonnimi) for osasto in db.session.query(Osastot).all()]
Tämän rivin avulla SelectField tietää näyttää osaston id:n sekä näyttää sen nimen valikossa
Päätin tehtävää varten pudottaa(poistaa) kaikki olemassa olevat pöydät tietokannassa ja luoda uudet ennen ensimmäistä kutsua. Samalla lisäsin pythonin kautta osastot pöytään osastojen nimet.
@app.before_first_request
def beforefirstrequest():
db.drop_all()
db.create_all()
o1 = Osastot(osastonnimi ="Hevi")
o2 = Osastot(osastonnimi ="Maitohylly")
o3 = Osastot(osastonnimi ="Hygienia")
o4 = Osastot(osastonnimi ="Vapaa-aika")
o5 = Osastot(osastonnimi ="Kuivatuotteet")
o6 = Osastot(osastonnimi ="Juomat")
o7 = Osastot(osastonnimi ="Makeiset")
o8 = Osastot(osastonnimi ="Siivous")
db.session.add_all([o1, o2, o3, o4, o5, o6, o7, o8])
db.session.commit()
Nyt kun ostoslistan avaa näemme muutokset
Kun näin muutosten tulleen voimaan poistin muut paitsi db.create_all() rivin.
Lopulta shopping_list funktioni näyttää tältä
@app.route("/shopping_list", methods=["GET", "POST"])
def shopping_list():
form = addForm()
# Luodaan valikolle vaihtoehdot hakemalla Osastot taulusta kaikki kohdat
form.osasto_id.choices = [(osasto.id, osasto.osastonnimi) for osasto in db.session.query(Osastot).all()]
if form.validate_on_submit():
# Testipöytä (tietokantataulu) toimii itemin mallina
item = Testipoyta()
# WTForms osaa automaattisesti täyttää samannimiset kohdat
form.populate_obj(item)
# Valmistellaan item tietokantaan lisäykseen
db.session.add(item)
# Commit komennolla viedään tallennus perille asti
db.session.commit()
# Palautetaan käyttäjä listaukseen
return redirect(url_for("shopping_list"))
items=db.session.query(Testipoyta, Osastot).join(Osastot).order_by(Osastot.osastonnimi).all()
return render_template("shopping_list.html", form=form, items=items)
Tietokantakyselyyn täytyi tehdä muutoksia, jottei listaukseen tule näkyviin vain osaston id numeroa. Tämän lisäksi täytyi muuttaa shopping_list.html tiedostosta kuinka kyselyn tuloksia näytetään
{ for item, osasto in items }
<tr>
<td>{item.nimi}</td>
<td>{item.maara}</td>
<td>{osasto.osastonnimi}</td>
<td>
Näillä muutoksilla saimme Create sekä Read toiminnallisuuden taulujen välisen relaation kanssa.
Tehtävä D - CRUD
Edellisessä tehtävässä tein Delete toiminnallisuuden ja edellisessa kappaleessa muokkasin Create sekä Read toiminnon ymmärtämään taulujen välisen relaation. Jäljellä on siis Update toiminto.
Update
Ensimmäisenä tein tuotteiden päivitystä varten oman sivun, joka oli kopioitu versio shopping listin lomakkeesta.
{ extends "base.html" }
{ block title }Edit Page{ endblock title }
{ block content }
<h2 class="mt-4">Edit item</h2>
<form method="POST" action="/edit/{item.id}/" autocomplete="off" class="mt-4">
{ form.csrf_token }
{ for field in form if not field.name in ["csrf_token"] }
<p>{ field.label }: { field } <b>{ " ".join(field.errors) }</b></p>
{ endfor }
<input type="submit" value="Muokkaa"></input>
</form>
{ endblock content }
Jälleen joudun poistamaan % merkit aaltosulkujen sisältä, sekä kaksoisaaltosuluista toisen jotta jinja ei mene sekaisin sivua muodostaessa.
Seuraavaksi vuorossa oli /edit/{item.id} routen luonti flask tiedostoon.
@app.route("/edit/<id>/", methods=["GET", "POST"])
def edit(id):
#Haetaan id:n perusteella tietokannasta muutettava tuote
muutettava = Testipoyta.query.get(id)
# Annetaan formille kenttiin valmiiksi muutettavan tiedot, jotta nähdään mitä muutetaan
form = addForm(nimi = muutettava.nimi, maara = muutettava.maara, osasto_id = muutettava.osasto_id)
# Jälleen annetaan dropdownille tietokannasta osastot
form.osasto_id.choices = [(osasto.id, osasto.osastonnimi) for osasto in db.session.query(Osastot).all()]
if form.validate_on_submit():
# Sama kuin shopping_listissä. item saa Testipoydan muodon
item = Testipoyta()
# Otetaan formista automaattisesti saman nimisille paikoille itemiin arvot
form.populate_obj(item)
# Haetaan jälleen id:n perusteella muokattava tuote. Tässä ehkä voisi käyttää aikaisempaa muuttujaa
# En testannut sitä. Tämä toimi mainiosti
editable = Testipoyta.query.get(id)
#Määritetään muutettavalle uudet arvot formista haettujen perusteella
editable.nimi = item.nimi
editable.maara = item.maara
editable.osasto_id = item.osasto_id
# Lisätään tietokantaan muokattu tuote
db.session.add(editable)
# Hyväksytään muutokset
db.session.commit()
# Palautetaan käyttäjä listaukseen
return redirect(url_for("shopping_list"))
# Muutettavan tiedot vietävä lomakkeelle eteenpäin
# Jotta formiin voidaan laittaa oikea id
return render_template('edit.html', item=muutettava, form=form)
Vielä viimeisenä kävin lisäämässä shopping_list.html sivulle napin, jota painamalla pääsee edit.html sivulle. Lopullinen shopping_list.html näyttää tältä
{ extends "base.html" }
{ block title }Shopping List{ endblock title }
{ block content }
<form method="post" action="/shopping_list" autocomplete="off" class="mt-4">
{ form.csrf_token }
{ for field in form if not field.name in ["csrf_token"] }
<p>{ field.label }: { field } <b>{ " ".join(field.errors) }</b></p>
{ endfor }
<input type="submit" value="Lisää"></input>
</form>
<h2 class="mt-4">Shopping List</h2>
<table class="table">
<thead>
<tr>
<td>Tuote</td>
<td>Määrä</td>
<td>Osasto</td>
<td></td>
</tr>
</thead>
<tbody>
{ for item, osasto in items }
<tr>
<td>{item.nimi}</td>
<td>{item.maara}</td>
<td>{osasto.osastonnimi}</td>
<td>
<a href="/edit/{item.id}" class="btn btn-secondary btn-xs">Edit</a>
<a href="/delete/{item.id}/" class="btn btn-danger btn-xs">Delete</a>
</td>
</tr>
{ endfor }
</tbody>
</table>
{ endblock content }
Ja näin meillä on CRUD valmis.
Linkkejä
Kurssin materiaali sekä tehtävänannot