API:Feiertags-Betrachter

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

Überblick

Diese Anleitung erklärt, wie man eine Demo-App erstellt, die Feiertage und Feste für ein bestimmtes Datum aus Wikipedia erhält, mit der Option, sich anzumelden, um neue Feiertage hinzuzufügen.

Die zur Erstellung dieser Demo-App genutzten Werkzeuge und Technologien sind:

Eine Schritt-für-Schritt-Anleitung, um diese Anwendung zu erstellen

Schritt 1: Python- und Flask-Entwicklungsumgebung aufsetzen

Python ist auf den meisten Linux-Systemen vorinstalliert. Für andere Betriebssysteme siehe die Python-Anfängeranleitung für Hinweise zur Installation.

Installiere Flask, indem du pip install flask ausführst. Wenn du pip noch nicht installiert hast, erhalte es von der offiziellen Pip-Webseite.


Schritt 2: Eine einfache Flask-Anwendung erstellen

Erstelle in deinem Heimverzeichnis einen Ordner namens holidays-viewer, der alle Dateien der App enthalten wird. Erstelle in dem Ordner eine Datei namens app.py und füge in sie folgenden Code ein:

#!/usr/bin/python3

from flask import Flask

APP = Flask(__name__)

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

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

Führe die App mit dem Befehl python app.py aus und öffne http://127.0.0.1:5000/ in deinem Browser. Du solltest "Holidays and observances" sehen können.

Schritt 3: Das grundlegende Layout erstellen

Die App wird vier Seiten haben: Die Hauptseite, eine Suchseite, eine Anmeldeseite und eine Seite zum Hinzufügen. Jede Seite wird gemeinsame Elemente nutzen, sodass wir eine grundlegende Layout-Datei namens layout.html erstellen müssen, die diese Elemente enthält.

Beachte, dass wir Bootstrap-Klassen nutzen, um einen bestimmten CSS-Style auf ein Element anzuwenden, Materialize-Symbole für die Symbole zum Hinzufügen, Suchen und Zurückgehen und Jinja, um das grundlegende Layout auf andere Seiten auszuweiten und Variablen aus Python nach HTML weiterzugeben.

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

Andere Seiten werden layout.html durch den Code unten erweitern:

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

Schritt 4: Feiertage auflisten

Die Root-URL der App wird die list_holidays(...)-Funktion auslösen, die Feiertage für ein bestimmtes Datum auflistet.

In der Funktion und über die App bezieht sich holidays_date auf das Datum der Feiertage, die aufgelistet werden sollen, header auf den Titel der Seite und holidays_html auf die HTML, die die aufzulistenden Feiertage enthält. Wir werden auch die Funktion render_template(...) nutzen, die eine bestimmte HTML-Datei aus dem Vorlagen-Verzeichnis rendert. Andere zur Funktion hinzugefügte Argumente sind Variablen, die an die HTML-Datei übergeben werden.

Aktualisiere list_holidays() in app.py mit dem Code unten:

@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 %}

Aktuelles Datum erhalten

Wenn kein Datum angegeben ist, führen wir die Feiertage für das aktuelle Datum auf. Um das Python-Modul datetime zu nutzen, um das heutige Datum zu erhalten, importiere das Modul mit from datetime import datetime und erstelle dann die folgende Funktion:

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

Rufe die Funktion in list_holidays(...) an:

if holidays_date is None:
        holidays_date = get_todays_date()

Die aufzulistenden Feiertage erhalten

Sobald wir das Datum haben, erhalten wir die Feiertage für das Datum. Wikipedia hat eine Seite für jedes Datum und die Feiertage befinden sich in einem Abschnitt mit dem Titel "Holidays and observances". Um die Feiertage zu erhalten, müssen wir die Nummer des Abschnitts und den Inhalt dieser Abschnittnummer erhalten.

Erstelle eine Funktion, um die Abschnittsnummer über API:Parse zu erhalten:

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

Erstelle eine Funktion mit dem Namen get_holidays(...), um die Feiertage in diesem Abschnitt ebenfalls über API:Parse zu erhalten. Anschließend können wir die Funktionen in list_holidays(...) anrufen:

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

Feiertagslinks aktualisieren

Die HTML der zurückgegebenen Feiertage enthält interne Links, die auf diese Feiertage verweisen, z.B. "/wiki/New_Years_Day". Wir müssen diesen Links in jQuery "//en.wikipedia.org" voranstellen, um sie in unserer App zu externen Links zu machen und zu bewirken, dass sie in einem neuen Tab geöffnet werden. Um dies zu tun, füge folgenden Code zu $HOME/holidays-viewer/static/update-links.js hinzu:

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

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

Füge dann jQuery zu layout.html hinzu mit:

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


Schritt 5: Nach Feiertagen für andere Daten suchen

Um Feiertage für andere Daten zu erhalten, erstelle eine Such-Route, um ein Formular anzuzeigen, dass Monat und Tag aufnimmt, nach denen gesucht werden soll:

@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 %}

Nachdem das Such-Formular übermittelt wurde, aktualisiere holidays_date, damit es das eingegebene Datum wird. Um dies zu tun, füge folgenden Code zu list_holidays(...) hinzu:

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

Schritt 6: Einen Feiertag hinzufügen

Die Seite, zu der wir einen neuen Feiertag hinzufügen werden, ist für Bearbeitungen von unangemeldeten Benutzern geschützt, sodass wir uns zunächst über API:Anmelden#Clientlogin anmelden müssen.

Um einen Feiertag hinzuzufügen, sende eine Abfrage an API:Bearbeiten mit dem Datum und der Beschreibung des Feiertags. Die Bearbeitung fügt neue Feiertage zu dieser Seite in der Test-Wikipedia hinzu: Sandbox/Holidays_and_observances. Dies dient dazu, zu verhindern, dass Test-Feiertage zur englischsprachigen Wikipedia hinzugefügt werden.

Nachdem der Feiertag hinzugefügt wurde, leite zur Hauptseite weiter, wo die hinzugefügten Feiertage ebenfalls angezeigt und fett formatiert werden, um sie von den echten Feiertagen zu unterscheiden. Um die Test-Feiertage zusammen mit den echten Feiertagen zu erhalten, aktualisiere 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 %}

Schritt 7: Die App gestalten

Füge weiteren Style zu unserer App hinzu, erstelle ein Stylesheet namens style.css und verlinke es von layout.html, indem du <link rel="stylesheet" href="static/style.css"> hinzufügst.

$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);
}

Anwendungslayout

An diesem Punkt sollte die Struktur der App wie folgt aussehen:

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

Wobei app.py und layout.html sind:

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

"""
    app.py

    MediaWiki-API-Demos

    Ferien-Betrachter: Eine Demo-App, die Feiertage des Tages aus Wikipedia erhält, mit Optionen, nach Feiertagen für ein anderes Datum zu suchen und sich anzumelden, um neue Feiertage hinzuzufügen.

    MIT-Lizenz
"""

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):
    """ Listet Feiertage für das aktuelle Datum oder ein benutzerdefiniertes Datum auf
    """

    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():
    """ Erhält den aktuellen Monat als Text und den aktuellen Tag als Zahl
    """

    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):
    """ Erhält die Abschnittsnummer für Feiertage in der Wikipedia und Feiertage auf der Testseite
    """

    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):
    """ Erhält die HTML, die Feiertage enthält
    """

    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():
    """ Sucht nach Feiertagen für ein bestimmtes Datum
    """

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

@APP.route("/login", methods=['GET', 'POST'])
def login():
    """ Anmelden bei Wikipedia
    """

    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():
    """ Fügt einen neuen Feiertag zur Testseite hinzu und leitet zu den Feiertagen des Datums weiter, um die hinzugefügten Feiertage anzuzeigen
    """

    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>
Bildschirmfoto der Hauptseite des Feiertags-Betrachters

Nächste Schritte

Siehe auch