User:Martyav/Apps/Tutorial
This page is currently a draft.
|
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:
- Python 3 and Flask framework
- MediaWiki Action API’s Images and Imageinfo modules
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 */
}
|
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]- API:Main page: Overview of the Action API
- API:FAQ: FAQ describing the Action API in more depth
- API:Images
- API:Imageinfo
- Mediawiki Action API Code Samples repo: Contains more demos and sample code