User:Martyav/Apps/Tutorial

From mediawiki.org

Overview[edit]

In this tutorial, you will learn how to build a Wikipedia:Picture of the day viewer using the MediaWiki Action API.

This tutorial will teach you how to do this using:

Step 1: Set up Python and Flask development environment[edit]

Setting up Python[edit]

This tutorial uses Python 3. You can download the latest Python version from here:

See the Python beginner's guide for further instructions on installing Python on different operating systems.

Setting up Flask[edit]

Pip is a package manager that you can use to install Flask: pip install flask. If you don't have pip already, install it from the official Pip website.

Step 2: Create a simple Flask application[edit]

If you have everything successfully installed, place the following script in app.py, inside your project folder: $HOME/picture-of-the-day-viewer/app.py. When you run it, it should display "Hello world" at http://localhost:5000/:

#!/usr/bin/python3

from flask import Flask
APP = Flask(__name__)

@APP.route("/")
def hello():
  return "Hello World!"

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

Step 3: Picture of the day viewer[edit]

Now that you have everything set up, you can start writing code for the Picture of the Day viewer. Picture of the day, or POTD, is a featured image displayed daily on the home page of Wikipedia. You'll be getting the picture from a wiki template that changes every day.

Getting today's date[edit]

The first order of business is simply knowing what day it is. Because POTD updates daily, you need today's date to access the archives and get at a stable version of the correct picture. To do this, import Python's date class.

Next, define a new function, index(). Index() will render the web page and pass along any data associated with our API calls. See #Displaying the page for more information on the index.html file we'll be using as a template. For now, index() should contain a variable holding the current date. We'll be using it soon to compose a query to access the POTD.

#!/usr/bin/python3
from datetime import date
from flask import Flask, render_template

APP = Flask(__name__)

@APP.route("/")
def index():
  todays_date = date.today().isoformat()

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

Calling the Action API[edit]

The Action API works by sending back data in response to HTTP requests. To do so, you will import the Python Requests library.

Next add two new variables: SESSION = requests.Session() and ENDPOINT = "https://en.wikipedia.org/w/api.php". You will use the SESSION object to make requests to the ENDPOINT url.

In a new function, fetch_potd(), call API:Images to request the picture embedded within a protected POTD page (example). From this call, use the picture's filename to call API:Imageinfo, and retrieve the image's source url. In this example, the second API call is handled in the helper function, fetch_image_src().

Wikipedia's archives lists dates by ISO standard format -- for instance, 2019-01-31, for 31 January, 2019. You can get the correct format by using the date method, isoformat().

def fetch_potd(cur_date):
  date_iso = cur_date.isoformat()
  title = "Template:POTD_protected/" + date_iso

  params = {
    "action": "query",
    "format": "json",
    "formatversion": "2",
    "prop": "images",
    "titles": title
  }

  response = SESSION.get(url = ENDPOINT, params = params)
  data = response.json()
  filename = data["query"]["pages"][0]["images"][0]["title"]
  image_page_url = "https://en.wikipedia.org/wiki/" + title
    
  image_data = {
    "filename": filename,
    "image_page_url": image_page_url,
    "image_src": fetch_image_src(filename),
    "date": day
  }

  return image_data


def fetch_image_src(filename):
  params = {
    "action": "query",
    "format": "json",
    "prop": "imageinfo",
    "iiprop": "url",
    "titles": file_name
  }

  response = SESSION.get(url = ENDPOINT, params = params)
  data = response.json()
  page = next(iter(data["query"]["pages"].values()))
  image_info = page["imageinfo"][0]
  image_url = image_info["url"]

  return image_url

Finally, alter index() to call fetch_potd(). Import render_template from flask, and make index() return render_template("index.html", data=data).

Displaying the page[edit]

Flask templates mostly contain HTML markup, but they also use Jinja to render dynamic content. Jinja markup looks like this -- {{ variable }} -- and is used to inject Python variables or expressions into our basic page structure. Add some basic HTML 5 boilerplate and a few elements to index.html. Make sure to save it to a directory within your app, named /templates.

<!DOCTYPE html>
<html lang="en">
  <meta charset="utf-8">
  <title>Picture of the Day</title>
  <link rel="stylesheet" href="/static/style.css">

  <main>
    <h1>Picture of the day:</h1>
    <div class="card">
      <div class="potd">
        <h2>{{ data.filename }}</h2>
        <a href="{{ data.image_page_url }}" target="blank">
        <figure>
          <img src="{{ data.image_src }}">
          <figcaption>View on Wikipedia</figcaption>
        </figure>
        </a>
      </div>
      <div class = "date-container">
      <div class = "current-date">{{ data.date.strftime("%d %B %Y") }}</div>
    </div>
    </div>
</main>

Making it interactive[edit]

Add a <form> element to index.html, and give it the following submit button inputs: Back and Next. When either button is selected, the form will submit a POST request, and the selected value will be passed back to app.py. This will allow users to browse through the Picture of the Day archives.

Next, update app.py with a change_date() function, for setting the date presented to the user. Also extend the / route to handle POST requests from the form. To allow app.py to read the POST request message, import Flask's Request class.

The complete Python and HTML code:

$HOME/picture-of-the-day-viewer/app.py
"""
    app.py
    MediaWiki Action API Code Samples

    Fetches Wikipedia Picture of the Day (POTD) and displays it on a webpage.
    Also allows users to go backward or forward a date to view other POTD.

    MIT License
"""

#!/usr/bin/python3

from datetime import date, timedelta
from flask import Flask, render_template, request
import requests


APP = Flask(__name__)
SESSION = requests.Session()
ENDPOINT = "https://en.wikipedia.org/w/api.php"
CURRENT_DATE = date.today()


@APP.route("/", methods=["GET", "POST"])
def index():
    """
    Requests data from Action API via 'fetch_potd' function & renders it on the
    index page accessible at '/'
    """

    if request.method == "POST":
        change_date()

    data = fetch_potd(CURRENT_DATE)

    return render_template("index.html", data=data)


def change_date():
    """
    Changes current date in response to input from the web form
    """

    global CURRENT_DATE

    user_input = request.form["change_date"]
    new_date = CURRENT_DATE
    last_date = date.today()
    first_date = date(year=2004, month=5, day=14)

    if user_input == "← Back":
        new_date = new_date - timedelta(days=1)
    elif user_input == "Next →":
        new_date = new_date + timedelta(days=1)

    if new_date > last_date or new_date < first_date:
        return

    CURRENT_DATE = new_date


def fetch_potd(cur_date):
    """
    Returns image data related to the current POTD
    """

    date_iso = cur_date.isoformat()
    title = "Template:POTD protected/" + date_iso

    params = {
        "action": "query",
        "format": "json",
        "formatversion": "2",
        "prop": "images",
        "titles": title
    }

    response = SESSION.get(url=ENDPOINT, params=params)
    data = response.json()

    filename = data["query"]["pages"][0]["images"][0]["title"]
    image_src = fetch_image_src(filename)
    image_page_url = "https://en.wikipedia.org/wiki/Template:POTD_protected/" + date_iso

    image_data = {
        "filename": filename,
        "image_src": image_src,
        "image_page_url": image_page_url,
        "date": cur_date
    }

    return image_data


def fetch_image_src(filename):
    """
    Returns the POTD's image url
    """

    params = {
        "action": "query",
        "format": "json",
        "prop": "imageinfo",
        "iiprop": "url",
        "titles": filename
    }

    response = SESSION.get(url=ENDPOINT, params=params)
    data = response.json()
    page = next(iter(data["query"]["pages"].values()))
    image_info = page["imageinfo"][0]
    image_url = image_info["url"]

    return image_url


if __name__ == "__main__":
    APP.run()
$HOME/picture-of-the-day-viewer/templates/index.html
<!DOCTYPE html>
<html lang="en">
  <meta charset="utf-8">
  <title>Picture of the Day</title>
  <link rel="stylesheet" href="/static/style.css">

  <main>
    <h1>Picture of the day:</h1>
    <div class="card">
      <div class="potd">
        <h2>{{ data.filename }}</h2>
        <a href="{{ data.image_page_url }}" target="blank">
        <figure>
          <img src="{{ data.image_src }}">
          <figcaption>View on Wikipedia</figcaption>
        </figure>
        </a>
      </div>
      <div class="date-container">
        <time class="current-date">{{ data.date.strftime("%d %B %Y") }}</time>
        <div class="date-navigator">
          <form action="/" method="POST">
            {% if data.date.strftime("%d %B %Y") == "14 May 2004" %}
            <input type="submit" name="change_date" value="← Back" disabled>
            {% else %}
            <input type="submit" name="change_date" value="← Back">
            {% endif %}
            {% if data.date == data.date.today() %}
            <input type="submit" name="change_date" value="Next →" disabled>
            {% else %}
            <input type="submit" name="change_date" value="Next →">
            {% endif %}
          </form>
        </div>
      </div>
    </div>
</main>

Styling your app[edit]

Flask uses a directory, named static, to contain any helper files that stay the same throughout the lifecycle of the app. This is a useful place to put any stylesheets or additional scripts. Our stylesheet will be using some colors and visual motifs based on the Wikimedia Style Guide. Place a CSS file in $HOME/picture-of-the-day-viewer/static/style.css.

$HOME/picture-of-the-day-viewer/style.css
html {
    margin: 0;
    padding: 0;
    height: 100vh;
    width: 100vw;
}

body {
    margin: 0;
    background: #f8f9fa; /* light grey */
    font-family: Arial, Helvetica, sans-serif;
    font-size: 16px;
}

h1 {
    margin: 0;
    padding: 12px;
    background: #2a4b8d; /* dark blue */
    color: #ffffff;
}

h2 {
    margin-top: 8px;
    padding: 12px;
    font-size: 1em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    background: #f3f9ff; /* very light blue */
    border: 1px solid #2a4b8d; /* dark blue */
}

a {
    color: #3366cc; /* blue */
}

p {
    margin: 8px;
}

.card {
    position: relative;
    margin: auto;
    min-width: 200px;
    max-width: 67vw;
    height: 90vh;
    background: #ffffff;
    border-radius: 8px;
    box-shadow: 3px 6px 10px rgba(0, 0, 0, 0.16);
}

.potd {
    width: inherit;
    height: 60vh;
}

figure {
    width: 100%;
    margin: auto;
    text-align: center;
}

figure img {
    display: block;
    margin: 12px auto; 
    max-width: 64vw;
    max-height: 50vh;
    border: 1px solid#3366cc; /* blue */
}

figure a {
    margin: 8px;
}

.date-container {
    display: block;
    position: absolute;
    bottom: 0;
    left: 0;
    right:0;
    text-align: center;
    font-weight: bold;
}

.current-date {
    margin: 16px auto;
    font-size: 2em;
    background: #ffffff;
    color: #72777d; /* grey */
}
A screenshot of the Picture of the Day Viewer available on the MediaWiki Code Samples repo, at Github
Picture of the Day Viewer app screenshot

Additional notes[edit]

There are several ways to add descriptive text from the POTD page:

  • API:Search provides snippets which can be used as a link preview
  • action=cirrusdump can be used to obtain the whole parsed text from articles on Wikis that have the Cirrussearch extension installed (see example).

See also[edit]