Aplikacja webowa w Pythonie – Flask – Formularze – #6
W szóstym odcinku tutorialu, który masz właśnie przed sobą, przejdziemy do tematu formularzy. Bazę danych już podpięliśmy – pytanie, jak przetwarzać dane wysłane przez naszą aplikację w stronę serwera? Zaraz się dowiesz! Napisaliśmy już skrypt pobierający z bazy wpisy, więc logicznie rzecz biorąc, powinniśmy teraz napisać taki umożliwiający dodanie posta.
Napiszmy formularz!
Na początek – prosty HTML do wyświetlenia naszych inputów. Umieścimy go w pliku database_posts.html, zaraz nad wpisami:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<h2>Dodaj wpis</h2> <div class="row"> <form class="col s12" action="{{ url_for('database_posts')}}" method="post"> <div class="row"> <div class="input-field col s6"> <input name="tytul" id="tytul" type="text" class="validate"> <label for="tytul">Tytuł</label> </div> <div class="input-field col s6"> <input name="autor" id="autor" type="text" class="validate"> <label for="autor">Autor</label> </div> <div class="input-field col s12"> <textarea id="tresc" name="tresc" class="materialize-textarea"></textarea> <label for="tresc">Treść</label> </div> </div> <button class="btn waves-effect waves-light" type="submit" name="action">Zapisz</button> </form> </div> |
Na co należy zwrócić uwagę? Przede wszystkim na tag <form> i to co znajduje się wewnątrz. Akcję skierujemy na funkcję wykonywaną w routingu tej podstrony ( url_for('database_posts')), a do przekazania zawartości wykorzystamy metodę post.
Nad samymi inputami nie będę się rozpisywać – ważne, aby miały konkretne id oraz name. Cała reszta to tak naprawdę sprawa wyglądu (tutaj korzystam z formularzy MaterializeCSS). Pamiętaj tylko, aby uwzględnić button o typie submit :)
Kolejnym krokiem będzie ulepszenie routingu dla database_posts.
Metody, routing, requesty…
Aby móc stronę nie tylko wyświetlić, ale również przesłać do niej dane, będziemy musieli zdefiniować metody. Staną się one drugim parametrem app.route(): @app.route("/wszystkie-posty", methods=['POST', 'GET']). Oprócz dodania POST, musimy również uwzględnić już używane wcześniej (domyślnie) GET.
Kolejnym krokiem, będzie edycja głównej funkcji w routingu. Dodamy do niej warunek, który wykona się jedynie, gdy rozpoznana zostanie metoda POST:
1 2 3 4 |
if request.method == 'POST': name = request.form['tytul'] author = request.form['autor'] content = request.form['tresc'] |
Pod zmienne name, author i content podstawimy to, co przekazane zostanie z formularzy za pomocą request.form. Następnie wykona się cała magia:
1 2 3 4 5 6 |
conn = mysql.connect() cursor = conn.cursor() to_db = ("", name, content, author) cursor.execute("INSERT INTO posty VALUES (%s, %s, %s, %s)", to_db) conn.commit() cursor.close() |
Jak to działa? Na początek definiuję połączenie oraz kursor. Tworzę krotkę zawierającą wszystkie zmienne, które chcę mieć w danym wierszu. Pierwszą pozostawiam pustą – w swojej tabeli zdefiniowałam pole ID jako auto increment, w związku z czym nie muszę się nim przejmować. Linijka z cursor.execute wykona MySQL-owy skrypt, który pozwoli nam dodać wartości do bazy. Ważne jest, aby nie przekazywać parametrów bezpośrednio, ale właśnie poprzez %s i krotkę. Na koniec zacommituję zmiany (inaczej rezultat się nie zapisze) i zamknę kursor.
Całość funkcji database_posts() powinna teraz wyglądać tak:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@app.route("/wszystkie-posty", methods=['POST', 'GET']) def database_posts(): if request.method == 'POST': name = request.form['tytul'] author = request.form['autor'] content = request.form['tresc'] conn = mysql.connect() cursor = conn.cursor() to_db = ("", name, content, author) cursor.execute("INSERT INTO posty VALUES (%s, %s, %s, %s)", to_db) conn.commit() cursor.close() cursor = mysql.connect().cursor() cursor.execute("SELECT * from posty") data = cursor.fetchall() cursor.close() return render_template('database_posts.html', data = data) |
Wywołanie wszystkich postów przełożyłam na koniec, aby po wysłaniu formularza nasz nowy wpis również się wyświetlił :) Jak widać, możemy już dodawać posty – co powiecie na usuwanie?
Usuwanie postów
Tak jak wcześniej, zaczniemy od templatki. Zazwyczaj chcemy wiedzieć, która operacja się wykonała, dlatego na początku layoutu database_posts.html dodamy sobie miejsce na wiadomość: <p class="red white-text">{{message}}</p>. Tak naprawdę powinna ona wyświetlić się dopiero wtedy, gdy zapytanie do bazy wykona się poprawnie, ale obsługę błędów zostawimy sobie na następny raz.
Aby móc odczytać, czy w metodzie POST przesyłamy formularz dodający nowy wpis, czy raczej chcemy coś usunąć, potrzebujemy zmienić minimalnie przycisk submit dla stworzonej już formatki:
1 |
<button class="btn waves-effect waves-light" type="submit" name="action" value="add">Zapisz</button> |
Dodamy mu name="action" i value="add" – jak łatwo się domyślić, pozostałe buttony będą mieć value="delete". Nasz wcześniejszy kod do wyświetlenia każdego z artykułów prezentował się tak:
1 2 3 4 5 6 7 |
{% for row in data %} <article> <h3>{{ row[0] }}. {{ row[1] }}</h3> <p>{{ row[2] }}</p> <p><i>Autor: {{ row[3] }}</i></p> </article> {% endfor %} |
Teraz między treścią a autorem dodamy jeszcze nasz nieduży formularz:
1 2 3 4 5 6 7 8 9 10 11 |
{% for row in data %} <article> <h3>{{ row[0] }}. {{ row[1] }}</h3> <p>{{ row[2] }}</p> <form class="col s12" action="{{ url_for('database_posts')}}" method="post"> <input id='{{row[0]}}' type='hidden' value='{{row[0]}}' name='id-delete'> <button class="btn waves-effect waves-light red" type="submit" name="action" value="delete">Usuń mnie</button> </form> <p><i>Autor: {{ row[3] }}</i></p> </article> {% endfor %} |
Oprzemy go na metodzie POST, tak samo jak ten do dodawania wpisu. Ma jednego inputa – ukrytego – którym przekażemy ID wpisu przeznaczonego do usunięcia. Czerwony przycisk wykonuje akcję, którą teraz musimy opisać w views.py. Tak naprawdę zmienia się tylko treść funkcji – oprócz sprawdzenia czy metodą jest POST, musimy jeszcze sprawdzić którą konkretnie akcję użytkownik chce wykonać ( add czy delete) i dopisać skrypt, który będzie potrzebny w przypadku tej drugiej.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
@app.route("/wszystkie-posty", methods=['POST', 'GET']) def database_posts(): message = "" if request.method == 'POST': if request.form["action"] == "add": name = request.form['tytul'] author = request.form['autor'] content = request.form['tresc'] conn = mysql.connect() cursor = conn.cursor() to_db = ("", name, content, author) cursor.execute("INSERT INTO posty VALUES (%s, %s, %s, %s)", to_db) conn.commit() cursor.close() message = "Dodano!" if request.form["action"] == "delete": id = request.form['id-delete'] conn = mysql.connect() cursor = conn.cursor() cursor.execute("DELETE from posty where ID='" + id + "'") conn.commit() cursor.close() message = "Usunięto post numer " + id + "!" cursor = mysql.connect().cursor() cursor.execute("SELECT * from posty") data = cursor.fetchall() cursor.close() return render_template('database_posts.html', data = data, message = message) |
Na początku definiujemy jeszcze pustą wiadomość, którą przy dodawaniu wpisu wypełniamy treścią “Dodano!”, a w przypadku usunięcia – ID postu, którego się pozbyliśmy. Struktura skryptu na usuwanie jest podobna do tego dodawania – definiujemy połączenie, kursor, wykonujemy zapytanie i commitujemy rezultat. Posty pobierają się później, dlatego usunięty nie zostanie już wyświetlony.
Jak zauważysz w aplikacji i w bazie danych – wszystko działa jak trzeba :)