API:Prohlížeč svátků

This page is a translated version of the page API:Holidays viewer and the translation is 100% complete.

Přehled

Tento návod popisuje, jak vytvořit ukázkovou aplikaci, která načte svátky a dodržování pro dané datum z Wikipedie, s možností přihlášení a přidání nových svátků.

Nástroje a technologie použité k vytvoření demo aplikace jsou:

Postup pro vytvoření této aplikace krok za krokem

Krok 1: Nastavení vývojového prostředí Python a Flask

Python je předinstalovaný na většině distribucí Linuxu. Pro ostatní operační systémy viz Python příručka pro začátečníky, kde najdete pokyny k instalaci.

Nainstalujte Flask spuštěním pip install flask. Pokud Pip nemáte, získejte ho z oficiálního webu Pip


Krok 2: Vytvořte jednoduchou aplikaci Flask

Ve svém domovském adresáři vytvořte složku s názvem holidays-viewer, která bude obsahovat všechny soubory aplikace. Uvnitř složky vytvořte soubor s názvem app.py a vložte do něj následující kód:

#!/usr/bin/python3

from flask import Flask

APP = Flask(__name__)

@APP.route("/")
def list_holidays():
  return "Holidays and observances"

if __name__ == "__main__":
  APP.run()

Spusťte aplikaci pomocí příkazu python app.py a otevřete http://127.0.0.1:5000/ ve svém prohlížeči. Měli byste vidět zobrazení "Holidays and observances".

Krok 3: Vytvořte základní návrh

Aplikace bude mít čtyři stránky: domovskou stránku, stránku vyhledávání, přihlašovací stránku a stránku pro přidání. Každá stránka bude mít některé společné prvky, takže musíme vytvořit soubor základního návrhu nazvaný layout.html, který bude tyto prvky obsahovat.

Všimněte si, že používáme třídy Bootstrap k aplikaci konkrétního stylu CSS na prvek, Materialize icons pro ikony přidání, vyhledávání a šipky zpět a Jinja k rozšíření základního rozvržení na další stránky a k předání proměnných z Pythonu. do HTML.

$HOME/holidays-viewer/templates/layout.html
<title>Holidays</title>

<link rel="stylesheet" href="//tools-static.wmflabs.org/cdnjs/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css">
<link rel="stylesheet" href="//tools-static.wmflabs.org/fontcdn/css?family=Material+Icons">

<div class="content bg-secondary rounded m-auto">
  <div class="title-bar bg-primary-dark text-white pl-2">
    <small>Holidays and observances</small>
  </div>
  <div class="header-bar bg-primary text-white shadow p-2">
    {% if request.path != url_for('list_holidays') %}
    <a class=" btn text-white" href="{{ url_for('list_holidays') }}">
      <i class="material-icons">arrow_back</i>
    </a>
    {% endif %}
    <h5>{{header}}</h5>
    <div class="filler"></div>
    <a class="btn text-white" href="{{ url_for('add') }}">
      <i class="material-icons">add</i>
    </a>
    <a class="btn text-white" href="{{ url_for('search') }}">
      <i class="material-icons">search</i>
    </a>
  </div>
  {% with messages = get_flashed_messages() %}
    {% if messages %}
    <div class="alert alert-primary mb-0" role="alert">
      {% for message in messages %}
        {{ message }}
      {% endfor %}
      <button type="button" class="close" data-dismiss="alert" aria-label="Close">
        <span aria-hidden="true">×</span>
      </button>
    </div>
    {% endif %}
  {% endwith %}
  {% block content %}{% endblock %}
</div>
<script src="//tools-static.wmflabs.org/cdnjs/ajax/libs/twitter-bootstrap/4.0.0/js/bootstrap.min.js"></script>

Ostatní stránky se rozšíří o layout.html pomocí níže uvedeného kódu:

{% extends "layout.html" %}
{% block content %}
  <!--content for other pages-->
{% endblock %}

Krok 4: Seznam svátků

Kořenová adresa URL aplikace spustí funkci list_holidays(...), která uvádí svátky pro určité datum.

Ve funkci a v celé aplikaci, holidays_date odkazuje na datum svátků, které mají být uvedeny, header odkazuje na název stránky a holidays_html odkazuje na html, který obsahuje svátky, které mají být uvedeny. Použijeme také funkci render_template(...), která vykreslí konkrétní html soubor z adresáře šablon. Dalšími argumenty přidanými do funkce jsou proměnné, které jsou předávány do html souboru.

V app.py aktualizujte list_holidays() pomocí kódu níže:

@APP.route('/', methods=['GET', 'POST'])
@APP.route('/<holidays_date>', methods=['GET', 'POST'])
def list_holidays(holidays_date=None):
    holidays_html = ""

    return render_template("index.html", header=holidays_date.replace('_', ' '),
                           holidays_html=holidays_html)
$HOME/holidays-viewer/templates/index.html
{% extends "layout.html" %}
{% block content %}
  <div class="holidays-html">
    {{holidays_html|safe}}
  </div>
{% endblock %}

Získání dnešního data

Pokud není zadáno žádné datum, uvedeme svátky pro dnešní datum. Chcete-li použít modul datetime Pythonu k získání dnešního data, importujte modul s from datetime import datetime a poté vytvořte následující funkci:

def get_todays_date():
    current_month = datetime.now().strftime('%B')
    current_day = datetime.now().strftime('%d')
    if current_day.startswith('0'):
        current_day = current_day.replace('0', '')

    return current_month + "_" + current_day

Zavolejte funkci v list_holidays(...):

if holidays_date is None:
        holidays_date = get_todays_date()

Získejte seznam svátků

Jakmile máme datum, získáme pro toto datum svátky. Wikipedia má stránku pro každé datum a svátky jsou v sekci s názvem "Holidays and observances". Abychom získali svátky, potřebujeme získat její číslo sekce a obsah v tomto čísle sekce.

Vytvořte funkci pro získání čísla sekce pomocí API:Parse :

def get_holidays_section(url, page, date_to_get):
    params = {
        "format":"json",
        "action":"parse",
        "prop":"sections",
        "page":page
    }

    response = S.get(url=url, params=params)
    data = response.json()
    sections = data['parse']['sections']
    section_number = "0"

    for index, value in enumerate(sections):
        if value['anchor'] == "Holidays_and_observances":
            section_number = index + 1

        if url == TEST_URL:
            if value['anchor'] == date_to_get:
                section_number = index + 1

    return section_number

Vytvořte funkci nazvanou get_holidays(...), abyste získali svátky v této sekci také pomocí API:Parse , pak zavolejte funkce v list_holidays(...):

section_number = get_holidays_section(URL, holidays_date, None)
holidays = get_holidays(URL, holidays_date, section_number)
holidays_html = holidays

Aktualizace odkazů na svátky

Vrácený kód HTML svátků obsahuje interní odkazy, které ukazují na tyto svátky, např. "/wiki/New_Years_Day". K těmto odkazům musíme přidat "//en.wikipedia.org" pomocí jQuery, abychom z nich vytvořili externí odkazy v naší aplikaci a otevřeli je na nové kartě. Chcete-li to provést, přidejte následující kód do $HOME/holidays-viewer/static/update-links.js:

$( document ).ready( function() {
    $( ".holidays-html a" ).attr( "target", "_blank" );

    $( ".holidays-html a" ).attr( "href", function( i, href ) {
      return "//en.wikipedia.org" + href;
    });
});

Poté přidejte jQuery do layout.html pomocí:

<script src="//tools-static.wmflabs.org/cdnjs/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="static/update-links.js"></script>


Krok 5: Vyhledejte svátky jiných dat

Chcete-li získat svátky pro jiná data, vytvořte trasu pro vyhledávání, aby se zobrazil formulář, který shromažďuje měsíc a den k hledání:

@APP.route("/search")
def search():

    return render_template("search.html", header="Search date")
$HOME/holidays-viewer/templates/search.html
{% extends "layout.html" %}
{% block content %}
  <div class="instructions m-3">
    Search for holidays by date
  </div>
  <div class="base rounded shadow bg-white m-3">
    <form class="m-auto" action="/" method="POST">
      <fieldset>
        <div class="label-field">Select Month</div>
        <select class="bg-secondary mb-5 border-0" name="monthList">
          <option value="January">January
          <option value="February">February
          <option value="March">March
          <option value="April">April
          <option value="May">May
          <option value="June">June
          <option value="July">July
          <option value="August">August
          <option value="September">September
          <option value="October">October
          <option value="November">November
          <option value="December">December
        </select>
      </fieldset>
      <fieldset>
        <div class="label-field">Select Day</div>
        <select class="bg-secondary mb-5 border-0" name="dayList">
          <option value="1">1
          <option value="2">2
          <option value="3">3
          <option value="4">4
          <option value="5">5
          <option value="6">6
          <option value="7">7
          <option value="8">8
          <option value="9">9
          <option value="10">10
          <option value="11">11
          <option value="12">12
          <option value="13">13
          <option value="14">14
          <option value="15">15
          <option value="16">16
          <option value="17">17
          <option value="18">18
          <option value="19">19
          <option value="20">20
          <option value="21">21
          <option value="22">22
          <option value="23">23
          <option value="24">24
          <option value="25">25
          <option value="26">26
          <option value="27">27
          <option value="28">28
          <option value="29">29
          <option value="30">30
          <option value="31">31
        </select>
      </fieldset>
      <button type="submit" name="search" class="bg-primary btn btn-submit text-white">Submit</button>
    </form>
  </div>
{% endblock %}

Po odeslání vyhledávacího formuláře aktualizujte holidays_date na datum, které jste zadali. Chcete-li to provést, přidejte následující kód do list_holidays(...):

if request.method == 'POST' and 'search' in request.form:
        search_month = str(request.form.get('monthList'))
        search_day = str(request.form.get('dayList'))
        holidays_date = search_month +"_"+search_day

Krok 6: Přidání svátku

Stránka, na kterou přidáme nový svátek, je chráněna před úpravami anonymních uživatelů, takže se musíme nejprve přihlásit pomocí API:Login#clientlogin.

Chcete-li přidat svátek, pošlete požadavek na API:Úpravy s datem a popisem svátku. Úprava přidává nové svátky na tuto stránku na Test Wikipedia: Sandbox/Holidays_and_observances. Je to proto, aby se zabránilo přidávání testovacích svátků do anglické Wikipedie.

Po přidání svátků přesměrujte na domovskou stránku, kde se zobrazí také přidané svátky a zformátujte je tučně, abyste je odlišili od skutečných svátků. Chcete-li načíst zkušební svátky vedle skutečných svátků, aktualizujte list_holidays(...):

test_section_number = get_holidays_section(TEST_URL, TEST_PAGE, holidays_date)
test_holidays = get_holidays(TEST_URL, TEST_PAGE, test_section_number)

holidays_html = test_holidays + holidays
flash("Holidays added through this app are in bold")
$HOME/holidays-viewer/templates/login.html
{% extends "layout.html" %}
{% block content %}
  <div class="instructions m-3">
    <p>You need to login to Wikipedia in order to add a new holiday
  </div>
  <div class="base rounded shadow bg-white m-3">
    <form class="m-auto" action="/login" method="POST">
      <div class="form-group">
        <div class="form-field">
          <div class="label-field">Username</div>
          <input class="bg-secondary mb-5 border-0" name="username">
        </div>
        <div class="form-field">
          <div class="label-field">Password</div>
          <input class="bg-secondary mb-5 border-0" type="password" name="password">
        </div>
      </div>
      <button type="submit" name="login" class="bg-primary btn btn-submit text-white">Login</button>
    </form>
  </div>
{% endblock %}
$HOME/holidays-viewer/templates/add.html
{% extends "layout.html" %}
{% block content %}
  <div class="instructions m-3">
    <p>Add a new test holiday
  </div>
  <div class="base rounded shadow bg-white m-3">
    <form class="m-auto" action="" method="POST">
      <div class="form-group">
        <div class="form-field">
          <div class="label-field">Date [MMMM dd]</div>
          <input class="bg-secondary border-0 mb-5" name="date" placeholder="e.g April 1">
        </div>
        <div class="form-field">
          <div class="label-field">Description</div>
          <input class="bg-secondary border-0 mb-5" name="description" placeholder="e.g April fools' day">
        </div>
      </div>
      <button type="submit" name="add" class="bg-primary btn btn-submit text-white">Add</button>
    </form>
  </div>
{% endblock %}

Krok 7: Styling aplikace

Chcete-li do naší aplikace přidat další styl, vytvořte šablonu stylů s názvem style.css a propojte ji z layout.html přidáním <link rel="stylesheet" href="static/style.css">.

$HOME/holidays-viewer/static/style.css
.content {
    width: 420px;
    min-height: 100vh;
}

.holidays-html{
    overflow-y: auto;
    overflow-x: hidden;
    max-height: 88vh;
    scrollbar-width: thin;
}

.base {
    height: 400px;
    display: flex;
}

input, select {
    width: 300px;
    height: 40px;
}

.btn-submit {
    width: 300px;
}

.btn {
    cursor: pointer;
    align-content: center;
    background-color: transparent;
}

.bg-primary {
    background-color: #36c !important;
}

.bg-primary-dark {
    background-color: #2a4b8d !important;
}

.bg-secondary {
    background-color: #eaecf0 !important;
}

.header-bar {
    height: 48px;
    display: flex;
    flex: 1;
    align-items: center;
}

.filler {
    flex-grow: 1;
    text-align: center
}

h2 {
    display: none;
}

ul {
    margin: 5px;
    padding: 0;
}

li  {
    list-style-type: none;
    margin-bottom: 4px;
    background-color: white;
    padding: 8px;
    border-radius: 5px;
}

ul li li {
    box-shadow: 0 .5rem 1rem rgba(0,0,0,.15);
}

Rozvržení aplikace

V tomto okamžiku by struktura vaší aplikace měla být:

$HOME/holidays-viewer
├── templates/
│   └── add.html
    └── index.html
    └── layout.html
    └── login.html
    └── search.html
├── static/
│   └── style.css
    └── update-links.js
├── app.py

S app.py a layout.html jsou:

$HOME/holidays-viewer/app.py
#!/usr/bin/python3

"""
    app.py

    Ukázky MediaWiki API

    Prohlížeč svátků: Ukázková aplikace, která načte den svátků z Wikipedie s možností vyhledávat svátky jiných dat a přihlásit se pro přidání nových svátků.

    Licence MIT
"""

from datetime import datetime
from flask import Flask, render_template, flash, request, url_for, redirect
import requests


APP = Flask(__name__)
APP.secret_key = 'your_secret_key'

URL = "https://en.wikipedia.org/w/api.php"
TEST_URL = "https://test.wikipedia.org/w/api.php"
TEST_PAGE = "Sandbox/Holidays_and_observances"
S = requests.Session()
IS_LOGGED_IN = False

@APP.route('/', methods=['GET', 'POST'])
@APP.route('/<holidays_date>', methods=['GET', 'POST'])
def list_holidays(holidays_date=None):
    """ Uvádí svátky pro aktuální datum nebo vlastní datum
    """

    if holidays_date is None:
        holidays_date = get_todays_date()

    # Update date to a custom date
    if request.method == 'POST' and 'search' in request.form:
        search_month = str(request.form.get('monthList'))
        search_day = str(request.form.get('dayList'))
        holidays_date = search_month +"_"+search_day

    # Get the section numbers for the holidays on Wikipedia and for those on the test page
    section_number = get_holidays_section(URL, holidays_date, None)
    test_section_number = get_holidays_section(TEST_URL, TEST_PAGE, holidays_date)

    holidays = get_holidays(URL, holidays_date, section_number)
    test_holidays = get_holidays(TEST_URL, TEST_PAGE, test_section_number)

    holidays_html = test_holidays + holidays
    flash('Holidays added through this app are in bold')

    return render_template("index.html", header=holidays_date.replace('_', ' '),
                           holidays_html=holidays_html)


def get_todays_date():
    """ Získá aktuální měsíc jako text a aktuální den jako číslo
    """

    current_month = datetime.now().strftime('%B')
    current_day = datetime.now().strftime('%d')
    if current_day.startswith('0'):
        current_day = current_day.replace('0', '')

    return current_month + "_" + current_day

def get_holidays_section(url, page, date_to_get):
    """ Získá číslo sekce pro svátky na Wikipedii a svátky na testovací stránce
    """

    params = {
        "format":"json",
        "action":"parse",
        "prop":"sections",
        "page":page
    }

    response = S.get(url=url, params=params)
    data = response.json()
    sections = data['parse']['sections']
    section_number = "0"

    for index, value in enumerate(sections):
        if value['anchor'] == "Holidays_and_observances":
            section_number = index + 1

        if url == TEST_URL:
            if value['anchor'] == date_to_get:
                section_number = index + 1

    return section_number

def get_holidays(url, page, section_number):
    """ Získá html, který obsahuje svátky
    """

    params = {
        "format":"json",
        "action":"parse",
        "prop":"text",
        "page": page,
        "section": section_number,
        "disableeditsection":1
    }

    response = S.get(url=url, params=params)
    data = response.json()
    text = data['parse']['text']['*']

    return text

@APP.route("/search")
def search():
    """ Vyhledá svátky podle vlastních dat
    """

    return render_template("search.html", header="Search date")

@APP.route("/login", methods=['GET', 'POST'])
def login():
    """ Přihlásí se na Wikipedii
    """

    if request.method == 'POST' and 'login' in request.form:
        params_0 = {
            "action": "query",
            "meta": "tokens",
            "type": "login",
            "format": "json"
        }

        response = S.get(url=URL, params=params_0)
        data = response.json()

        login_token = data['query']['tokens']['logintoken']

        params_1 = {
            "action": "clientlogin",
            "username": str(request.form.get('username')),
            "password": str(request.form.get('password')),
            "loginreturnurl": "http://127.0.0.1:5000/login",
            "logintoken": login_token,
            "format": "json"
        }

        response = S.post(url=URL, data=params_1)
        data = response.json()

        if data['clientlogin']['status'] != 'PASS':
            flash('Oops! Something went wrong -- ' + data['clientlogin']['messagecode'])
        else:
            global IS_LOGGED_IN
            IS_LOGGED_IN = True
            flash('Login success! Welcome, ' + data['clientlogin']['username'] + '!')
            return redirect(url_for('add'))

    return render_template("login.html", header="Login")

@APP.route("/add", methods=['GET', 'POST'])
def add():
    """ Přidá nový svátek na testovací stránku a přesměrujte se na svátky daného data, abyste zobrazili přidané svátky
    """

    if not IS_LOGGED_IN:
        return redirect(url_for('login'))

    if request.method == 'POST' and 'add' in request.form:

        # Wiki markup to format the added holiday's text as a list item and in bold
        holiday_text = "* '''" + str(request.form.get('description')) + "'''"
        date = str(request.form.get('date'))

        params_2 = {
            "action": "query",
            "meta": "tokens",
            "format": "json"
        }

        response = S.get(url=TEST_URL, params=params_2)
        data = response.json()

        csrf_token = data['query']['tokens']['csrftoken']

        params_4 = {
            "action": "edit",
            "title": TEST_PAGE,
            "token": csrf_token,
            "format": "json",
            "section": "new",
            "sectiontitle": date,
            "text": holiday_text,
        }

        response = S.post(url=TEST_URL, data=params_4)
        data = response.json()

        if data['edit']['result'] != 'Success':
            flash('Oops! Something went wrong -- ' + data['clientlogin']['messagecode'])
        else:
            flash('New holiday added successfully!')
            return redirect(url_for('list_holidays', holidays_date=date.replace(' ', '_')))

    return render_template("add.html", header="Add holiday")

if __name__ == "__main__":
    APP.run()
$HOME/holidays-viewer/templates/layout.html
<title>Holidays</title>

<link rel="stylesheet" href="//tools-static.wmflabs.org/cdnjs/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css">
<link rel="stylesheet" href="//tools-static.wmflabs.org/fontcdn/css?family=Material+Icons">
<link rel="stylesheet" href="static/style.css">

<script src="//tools-static.wmflabs.org/cdnjs/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="static/update-links.js"></script>

<div class="content bg-secondary rounded m-auto">
  <div class="title-bar bg-primary-dark text-white pl-2">
    <small>Holidays and observances</small>
  </div>
  <div class="header-bar bg-primary text-white shadow p-2">
    {% if request.path != url_for('list_holidays') %}
    <a class=" btn text-white" href="{{ url_for('list_holidays') }}">
      <i class="material-icons">arrow_back</i>
    </a>
    {% endif %}
    <h5>{{header}}</h5>
    <div class="filler"></div>
    <a class="btn text-white" href="{{ url_for('add') }}">
      <i class="material-icons">add</i>
    </a>
    <a class="btn text-white" href="{{ url_for('search') }}">
      <i class="material-icons">search</i>
    </a>
  </div>
  {% with messages = get_flashed_messages() %}
    {% if messages %}
    <div class="alert alert-primary mb-0" role="alert">
      {% for message in messages %}
        {{ message }}
      {% endfor %}
      <button type="button" class="close" data-dismiss="alert" aria-label="Close">
        <span aria-hidden="true">×</span>
      </button>
    </div>
    {% endif %}
  {% endwith %}
  {% block content %}{% endblock %}
</div>
<script src="//tools-static.wmflabs.org/cdnjs/ajax/libs/twitter-bootstrap/4.0.0/js/bootstrap.min.js"></script>
 
Snímek obrazovky domovské stránky prohlížeče Holidays

Další kroky

Související odkazy