7.1. Quiz

Realizacja aplikacji internetowej Quiz w oparciu o framework Flask. Na stronie wyświetlamy pytania, użytkownik zaznacza poprawne odpowiedzi, przesyła je na serwer i otrzymuje informację o wynikach.

7.1.1. Projekt i aplikacja

W katalogu użytkownika tworzymy nowy katalog dla aplikacji quiz, a w nim plik główny quiz.py:

Terminal nr
~$ mkdir quiz; cd quiz; touch quiz.py

Utworzymy szkielet aplikacji Flask, co pozwoli na uruchomienie testowego serwera www, umożliwiającego wygodne rozwijanie kodu. W pliku quiz.py wpisujemy:

Kod nr
1
2
3
4
5
6
7
8
9
# -*- coding: utf-8 -*-
# quiz/quiz.py

from flask import Flask

app = Flask(__name__)

if __name__ == '__main__':
    app.run(debug=True)

Serwer uruchamiamy komendą:

Terminal nr
~/quiz$ python quiz.py
../../_images/serwer.jpg

Domyślnie serwer uruchamia się pod adresem http://127.0.0.1:5000. Po wpisaniu go do przeglądarki internetowej otrzymamy kod odpowiedzi HTTP 404, tj. błąd “nie znaleziono”, co wynika z faktu, że nasza aplikacja nie ma jeszcze zdefiniowanego żadnego widoku dla tego adresu.

../../_images/quiz1.png

7.1.2. Widok (strona główna)

Jeżeli chcemy, aby nasza aplikacja zwracała użytkownikowi jakieś strony www, tworzymy tzw. widok. Jest to funkcja Pythona powiązana z określonymi adresami URL za pomocą tzw. dekoratorów. Widoki pozwalają nam obsługiwać podstawowe żądania protokołu HTTP, czyli: GET, wysyłane przez przeglądarkę, kiedy użytkownik chce zobaczyć stronę, i POST, kiedy użytkownik przesyła dane na serwer za pomocą formularza.

W odpowiedzi aplikacja może odsyłać różne dane. Najczęściej będą to znaczniki HTML oraz żądane treści, np. wyniki quizu. Flask ułatwia tworzenie takich dokumentów za pomocą szablonów.

W pliku quiz.py umieszczamy funkcję index(), czyli widok strony głównej:

Kod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# -*- coding: utf-8 -*-
# quiz/quiz.py

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return 'Cześć, tu Python!'

if __name__ == '__main__':
    app.run(debug=True)

Widok (czyli funkcja) index() powiązana jest z adresem głównym (/) za pomocą dekoratora @app.route('/'). Dzięki temu, jeżeli użytkownik wpisze w przeglądarce adres serwera, jego żądanie (GET) zostanie przechwycone i obsłużone właśnie w tej funkcji.

Najprostszą odpowiedzią na żądanie GET jest zwrócenie jakiegoś tekstu. Tak też robimy wywołując funkcję return 'Cześć, tu Python!', która odeśle podany tekst do przeglądarki, a ta wyświetli go użytkownikowi.

../../_images/quiz2.png

Zazwyczaj będziemy prezentować bardziej skomplikowane dane, w dodatku sformatowane wizualnie. Potrzebujemy szablonu. Tworzymy więc plik ~/quiz/templates/index.html. Można to zrobić w terminalu po ewentualnym zatrzymaniu serwera (CTRL+C):

Terminal nr
~/quiz$ mkdir templates; touch templates/index.html

Jak widać szablony umieszczamy w podkatalogu templates aplikacji. Do pliku index.html wstawiamy poniższy kod HTML:

Plik index.html. Kod nr
1
2
3
4
5
6
7
8
9
<!-- quiz/templates/index.html -->
<html>
    <head>
        <title>Quiz Python</title>
    </head>
<body>
    <h1>Quiz Python</h1>
</body>
</html>

Na koniec modyfikujemy funkcje index() w pliku quiz.py:

Kod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# -*- coding: utf-8 -*-
# quiz/quiz.py

from flask import Flask
from flask import render_template

app = Flask(__name__)

@app.route('/')
def index():
    #return 'Cześć, tu Python!'
    return render_template('index.html')

if __name__ == '__main__':
    app.run(debug=True)

Po zaimportowaniu (!) potrzebnej funkcji używamy jej do wyrenderowania podanego jako argument szablonu: return render_template('index.html'). Pod adresem http://127.0.0.1:5000 strony głównej, zobaczymy dokument HTML:

../../_images/quiz3.png

7.1.3. Pytania i odpowiedzi

Dane aplikacji, a więc pytania i odpowiedzi, umieścimy w liście PYTANIA w postaci słowników zawierających: treść pytania, listę możliwych odpowiedzi oraz poprawną odpowiedź.

Modyfikujemy plik quiz.py. Podany kod wstawiamy po inicjacji zmiennej app, ale przed dekoratorem widoku index():

Kod nr
 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
29
30
31
32
33
34
35
36
37
38
39
# -*- coding: utf-8 -*-
# quiz/quiz.py

from flask import Flask
from flask import render_template

app = Flask(__name__)

# konfiguracja aplikacji
app.config.update(dict(
    SECRET_KEY='bradzosekretnawartosc',
))

# lista pytań
PYTANIA = [
    {
        'pytanie': u'Stolica Hiszpani, to:',# pytanie
        'odpowiedzi': [u'Madryt', u'Warszawa', u'Barcelona'], # możliwe odpowiedzi
        'odpok': u'Madryt', # poprawna odpowiedź
    },
    {
        'pytanie': u'Objętość sześcianu o boku 6 cm, wynosi:',
        'odpowiedzi': [u'36', u'216', u'18'],
        'odpok': u'216',
    },
    {
        'pytanie': u'Symbol pierwiastka Helu, to:',
        'odpowiedzi': [u'Fe', u'H', u'He'],
        'odpok': u'He',
    }
]

@app.route('/')
def index():
    #return 'Cześć, tu Python!'
    return render_template('index.html', pytania=PYTANIA)

if __name__ == '__main__':
    app.run(debug=True)

Dodaliśmy konfigurację aplikacji w postaci słownika, ustalając sekretny klucz, potrzebny do zarządzania sesjami różnych użytkowników. Najważniejszą zmianą jest dołożenie drugiego argumentu funkcji render_template(), czyli słownika PYTANIA w zmiennej pytania. Dzięki temu będziemy mogli odczytać je w szablonie.

Do szablonu index.html wstawiamy poniższy kod po nagłówku <h1>.

Plik index.html. Kod nr
 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
        <!-- formularz z quizem -->
        <form method="POST">
            <!-- przeglądamy listę pytań -->
            {% for p in pytania %}
                <p>
                    <!-- wyświetlamy treść pytania -->
                    {{ p.pytanie }}
                    <br>
                    <!-- zapamiętujemy numer pytania licząc od zera -->
                    {% set pnr = loop.index0 %}
                    <!-- przeglądamy odpowiedzi dla danego pytania -->
                    {% for o in p.odpowiedzi %}
                        <label>
                            <!-- odpowiedzi wyświetlamy jako pole typu radio -->
                            <input type="radio" value="{{ o }}" name="{{ pnr }}">
                            {{ o }}
                        </label>
                        <br>
                    {% endfor %}
                </p>
            {% endfor %}

            <!-- przycisk wysyłający wypełniony formularz -->
            <button type="submit">Sprawdź odpowiedzi</button>
        </form>

Znaczniki HTML w powyższym kodzie tworzą formularz (<form>). Natomiast tagi, czyli polececnia dostępne w szablonach, pozwalają wypełnić go danymi. Warto zapamiętać, że jeżeli potrzebujemy w szablonie instrukcji sterującej, umieszczamy ją w znacznikach {% %}, natomiast kiedy chcemy wyświetlić jakąś zmienną używamy notacji {{ }}.

Z przekazaneej do szablonu listy pytań, czyli ze zmiennej pytania odczytujemy w pętli {% for p in pytania %} kolejne słowniki; dalej tworzymy elementy formularza, czyli wyświetlamy treść pytania {{ p.pytanie }}, a w kolejnej pętli {% for o in p.odpowiedz %} odpowiedzi w postaci grupy opcji typu radio.

Każda grupa odpowiedzi nazywana jest dla odróżnienia numerem pytania liczonym od 0. Odpowiednią zmienną ustawiamy w instrukcji {% set pnr = loop.index0 %}, a używamy w postaci name="{{ pnr }}". Dzięki temu przyporządkujemy przesłane odpowiedzi do kolejnych pytań podczas ich sprawdzania.

Po ponownym uruchomieniu serwera powinniśmy otrzymać następującą stronę internetową:

../../_images/quiz4.png

7.1.4. Oceniamy odpowiedzi

Mechanizm sprawdzana liczby poprawnych odpowiedzi umieścimy w funkcji index(). Uzupełniamy więc plik quiz.py:

Kod nr
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from flask import request, redirect, url_for, flash

@app.route('/', methods=['GET', 'POST'])
def index():

    if request.method == 'POST':
        punkty = 0
        odpowiedzi = request.form

        for pnr, odp_u in odpowiedzi.items():
            if odp_u == PYTANIA[int(pnr)]['odpok']:
                punkty += 1

        flash(u'Liczba poprawnych odpowiedzi, to: {0}'.format(punkty))
        return redirect(url_for('index'))

    #return 'Cześć, tu Python!'
    return render_template('index.html', pytania=PYTANIA)

Przede wszystkim importujemy potrzebne funkcje. Następnie uzupełniamy dekorator app.route(), aby obsługiwał zarówno żądania GET (odesłanie żądanej strony), jak i POST (ocena przesłanych odpowiedzi i odesłanie wyniku).

Instrukcja warunkowa if request.method == 'POST': wykrywa żądania POST i wykonuje blok kodu zliczający poprawne odpowiedzi. Dane pobieramy z przesłanego formularza i zapisujemy w zmiennej: odpowiedzi = request.form. Następnie w pętli for pnr, odp_u in odpowiedzi.items() odczytujemy kolejne pary danych, czyli numer pytania i udzieloną odpowiedź.

Instrukcja if odp_u == PYTANIA[int(pnr)]['odpok']: sprawdza, czy nadesłana odpowiedź jest zgodna z poprawną, którą wydobywamy z listy pytań za pomocą zmiennej pnr i klucza odpok. Zwróćmy uwagę, że wartości zmiennej pnr, czyli numery pytań liczone od zera, ustaliliśmy wcześniej w szablonie.

Jeżeli nadesłana odpowiedź jest poprawna, doliczamy punkt (punkty += 1). Informacje o wyniku przekazujemy użytkownikowi za pomocą funkcji flash(), która korzysta z tzw. sesji HTTP (wykorzystującej SECRET_KEY), czyli mechanizmu pozwalającego na rozróżnianie żądań przychodzących w tym samym czasie od różnych użytkowników.

W szablonie index.html między znacznikami <h1> i <form> wstawiamy instrukcje wyświetlające wynik:

Plik index.html. Kod nr
 9
10
11
12
13
14
        <!-- wyświetlamy komunikaty z funkcji flash -->
        <p>
            {% for message in get_flashed_messages() %}
                {{ message }}
            {% endfor %}
        </p>

Po uruchomieniu aplikacji, zaznaczeniu odpowiedzi i ich przesłaniu otrzymujemy ocenę.

../../_images/quiz5.png

7.1.5. Materiały

Źródła:

Kolejne wersje tworzenego kodu znajdziesz w katalogu ~/python101/docs/webapps/quiz. Uruchamiamy je wydając polecenia:

~/python101$ cd docs/webapps/quiz
~/python101/docs/webapps/bazy$ python quizx.py

- gdzie x jest numerem kolejnej wersji kodu.


Licencja Creative Commons Materiały Python 101 udostępniane przez Centrum Edukacji Obywatelskiej na licencji Creative Commons Uznanie autorstwa-Na tych samych warunkach 4.0 Międzynarodowa.

Utworzony:2017-09-08 o 19:38 w Sphinx 1.4.5
Autorzy:Patrz plik “Autorzy”