CRUD flask

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

Kuva ostoslistasta dropdown valikon kanssa

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.

Kuva listasta tuotteiden 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.

Edit sivu maidolla

Muutettu listaus

Linkkejä

Kurssin materiaali sekä tehtävänannot

Flask-SQLAlchemy documentaatio

Jinja2 documentaatio