Tietokantaa käyttävä flask ohjelma - Viikkotehtävä 5
TLDR (Too long didnt read)
Tehtävässä loin onnistuneesti paikalliselle koneelle tietokantaa käyttävän sovelluksen. Julkaisun jälkeen sovellus ei kyennyt enää kirjoittamaan tietokantaan. Yritin parhaani mukaan korjata käyttöoikeusongelmaa tuloksetta. Palaan tähän siis myöhemmin.
Edit 5.3.2021
Korjasin tehtävän toiminnallisuuden ja lisäsin dokumentointia virheistä.
/Edit
Tehtävän aloitus
Tehtävänä oli luoda jokin hyödyllinen tietokantaa, sekä formeja käyttävä ohjelma ja julkaista se käyttäen flaskiä. Tehtäväni aiheeksi päätin luoda ostoslistan joka näyttää listan järjestettynä tuotteen osaston mukaan. Tietokannaksi tehtävää varten valitsin SQLiten.
Tietokannan luominen
from flask import Flask, render_template, redirect, url_for
from wtforms import *
from wtforms.validators import DataRequired
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
app = Flask(__name__)
db = SQLAlchemy(app)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database.db'
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config['SECRET_KEY'] = 'Tähän oma turvallinen secret key ohjelmaa varten'
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 = db.Column(db.String, nullable=False)
class addForm(FlaskForm):
nimi = StringField("Tuotteen nimi", validators=[DataRequired()])
maara = StringField("Määrä", validators=[DataRequired()])
osasto = StringField("Tuotteen osasto", validators=[DataRequired()])
@app.before_first_request
def beforefirstrequest():
db.create_all()
Testipoyta luokka mallintaa SQLAlchemyä varten tietokantapöydän ja sen sisällön. Appin configuroinnissa sekä pöydän luomisessa seurasin Tero Karvisen ohjetta, mutta loin lomakkeen sivua varten hieman eri tavalla. Beforefirstrequest funktio nimensä mukaan ajetaan ennen ensimmäistä pyyntöä joka palvelimelle tehdään. Create_all metodi luo Testipoyta luokan mukaisen pöydän tietokantaan, mikäli sitä ei entuudestaan ole olemassa. Validatorsin avulla wtforms pitää huolen, että lomakkeen kohta ei voi jäädä tyhjäksi.
Toiminnallisuuden rakentaminen
@app.route("/shopping_list", methods=["GET", "POST"])
def shopping_list():
form = addForm()
if form.validate_on_submit():
item = Testipoyta()
form.populate_obj(item)
db.session.add(item)
db.session.commit()
return redirect(url_for("shopping_list"))
items=db.session.query(Testipoyta).order_by(Testipoyta.osasto)
return render_template("shopping_list.html", form=form, items=items)
@app.route("/delete/<id>/", methods=["get", "post"])
def delete(id):
poistettava = Testipoyta.query.get(id)
db.session.delete(poistettava)
db.session.commit()
return redirect(url_for("shopping_list"))
Ostoslistalle luotiin uusi reititys ja määritettiin mitä tapahtuu, kun lomakkeen syöttö on validoitu aikaisempien validators määritysten mukaan. Tässä tapauksessa riittää, mikäli jokaiseen tarjolla olevaan kenttään on syötetty jotakin. Tietokannasta haetut tulokset järjestetään osaston mukaan järjestykseen order_by(Testipoyta.osasto) avulla.
Lomakkeen HTML sivu
Sivun lomake (form) on kopioitu Tero Karvisen esimerkistä.
{ 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 in items
<tr>
<td>item.nimi</td>
<td>item.maara</td>
<td>item.osasto</td>
<td><a href="/delete/{item.id}" class="btn btn-danger btn-xs">Delete</a>
</tr>
endfor
</tbody>
</table>
{ endblock content }
For loopin avulla luodaan taulukko tietokannasta haetuista tiedoista. Lomake, johon tavaroiden tiedot syötetään muodostuu automaattisesti WTFormsin määrittelyn sekä for loopin avulla.
Julkaisu
Julkaisu tapahtui siirtämällä paikallisella koneella luomani tiedostot scp komennolla virtuaalipalvelimelleni. Tuotantokelpoisen flask asennuksen loin palvelimelleni Tero Karvisen ohjeen mukaisesti. Julkaisun yhteydessä ilmeni käyttöoikeusongelmia tietokantatiedoston kanssa. Yritin korjata tilannetta muokkaamalla käyttöoikeuksia tuloksetta.
Edit 5.3.2021
Error viesti apache2 error lokista oli seuraava:
Luennolla Opettaja suositteli käyttämään sqliten sijaan Postgresql.
sudo apt-get -y install postgresql python3-psycopg2
komennolla sai
asennettua sekä postgres tietokannan, sekä flaskin vaatiman psycopg2 adapterin.
Postgresin käyttöönotto
Postgresin käyttöönotto vaatii yhteensä neljä komentoa.
"sudo systemctl start postgresql"
vaaditaan debianilla ohjeiden kolmen komennon lisäksi.
SQLAlchemyn ansiosta postgresin käyttöönotto toimii vaihtamalla python koodia hieman
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database.db' # Vanha
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql:///käyttäjän nimi' # Uusi
Tämän jälkeen sivua uudelleen ladatessa vastaani tuli 500 internal server error. Apachen lokeista löytyi seuraavaa:
Otin sähköpostilla yhteyttä opettajaan ja selvisi, etten ollut seurannut ohjeita tarpeeksi tarkasti mod_wsgi tiedoston luomisessa. Minulta puuttui .conf tiedostosta tagin sisältä WSGI lisäykset.
Muutoksien jälkeen "sudo systemctl restart apache2"
jotta muutokset astuvat voimaan.
Näin tehtävä saatiin korjattua ja valmiiksi!
/Edit