7.1. Quiz

Realizacja aplikacji internetowej Quiz w oparciu o framework Flask 0.12.x. 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 aplikacji o nazwie quiz:

Terminal nr
~$ mkdir quiz; cd quiz;

Utworzymy szkielet aplikacji Flask, co pozwoli na uruchomienie testowego serwera www, umożliwiającego wygodne rozwijanie kodu. W nowym pliku o nazwie quiz.py wpisujemy poniższy kod i zapisujemy w katalogu aplikacji.

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$ python3 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

Wskazówka

Działanie serwera w terminalu zatrzymujemy skrótem CTRL+C.

7.1.2. 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 treści, np. wyniki quizu. Flask ułatwia tworzenie takich dokumentów za pomocą szablonów.

W pliku quiz.py umieszczamy kod:

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

app = Flask(__name__)


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


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

Widok (czyli funkcja) index() powiązany jest z adresem głównym (/) za pomocą dekoratora @app.route('/'). Funkcja zostanie wykonana w odpowiedzi na żądanie GET wysłane przez przeglądarkę po wpisaniu i zatwierdzeniu przez użytkownika adresu serwera.

Najprostszą odpowiedzią jest zwrócenie jakiegoś tekstu: return 'Cześć, tu Python!'.

../../_images/quiz2.png

Zazwyczaj będziemy prezentować bardziej skomplikowane dane, w dodatku sformatowane wizualnie. Potrzebujemy szablonów. Będziemy je zapisywać w katalogu quiz/templates, który utworzymy np. poleceniem:

Terminal nr
~/quiz$ mkdir templates

Następnie w nowym pliku templates/index.html umieszczamy kod:

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 funkcję index() w pliku quiz.py:

Kod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# -*- 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)

Do renderowania szablonu (zob: renderowanie szablonu) używamy funkcji render_template('index.html'), która jako argument przyjmuje nazwę pliku szablonu. 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 DANE 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
# -*- 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ń
DANE = [{
    'pytanie': 'Stolica Hiszpani, to:',  # pytanie
    'odpowiedzi': ['Madryt', 'Warszawa', 'Barcelona'],  # możliwe odpowiedzi
    'odpok': 'Madryt'},  # poprawna odpowiedź
    {
    'pytanie': 'Objętość sześcianu o boku 6 cm, wynosi:',
    'odpowiedzi': ['36', '216', '18'],
    'odpok': '216'},
    {
    'pytanie': 'Symbol pierwiastka Helu, to:',
    'odpowiedzi': ['Fe', 'H', 'He'],
    'odpok': 'He'},
]


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


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

W konfiguracji aplikacji dodaliśmy sekretny klucz, wykorzystywany podczas korzystania z sesji (zob sesja).

Dane aplikacji

Każda aplikacja korzysta z jakiegoś źródła danych. W najprostszym przypadku dane zawarte są w samej aplikacji. Dodaliśmy więc listę słowników DANE, którą przekazujemy dalej jako drugi argument do funkcji render_template(). Dzięki temu będziemy mogli odczytać je w szablonie w zmiennej pytania.

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 polecenia dostępne w szablonach, pozwalają wypełnić go danymi.

  • {% instrukcja %} – tak wstawiamy instrukcje sterujące;
  • {{ zmienna }} – tak wstawiamy wartości zmiennych przekazanych do szablonu.

Z przekazanej 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(). Na początku pliku quiz.py dodajemy potrzebne importy:

Kod nr
6
from flask import request, redirect, url_for, flash

– i uzupełniamy kod funkcji index():

Kod nr
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@app.route('/', methods=['GET', 'POST'])
def index():

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

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

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

    # return 'Cześć, tu Python!'
    return render_template('index.html', pytania=DANE)
  • methods=['GET', 'POST'] – lista zawiera obsługiwane typy żądań, chcemy obsługiwać zarówno żądania GET (odesłanie żądanej strony), jak i POST (ocena przesłanych odpowiedzi i odesłanie wyniku);
  • if request.method == 'POST': – instrukcja warunkowa, która wykrywa żądania POST i wykonuje blok kodu zliczający poprawne odpowiedzi;
  • odpowiedzi = request.form – przesyłane dane z formularza pobieramy z obiektu request i zapisujemy w zmiennej odpowiedzi;
  • for pnr, odp in odpowiedzi.items() – w pętli odczytujemy kolejne pary danych, czyli numer pytania i udzieloną odpowiedź;
  • if odp == DANE[int(pnr)]['odpok']: – sprawdzamy, czy nadesłana odpowiedź jest zgodna z poprawną, którą wydobywamy z listy pytań.

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:


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:2022-05-22 o 19:52 w Sphinx 1.5.3
Autorzy:Patrz plik “Autorzy”