Tietokantaa käyttävä flask ohjelma

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:

SqliteError

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:

psygopg2 error

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.

Päivitetty mod_wsgi tiedosto

Muutoksien jälkeen "sudo systemctl restart apache2" jotta muutokset astuvat voimaan.

Toimiva shoppinglist

Näin tehtävä saatiin korjattua ja valmiiksi!

/Edit

Linkkejä

Kurssin materiaalit

Flaskin dokumentaatio

WTForms dokumentaatio

SQLAlchemy dokumentaatio