You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

205 lines
6.3 KiB
Python

# Flask application to serve the web pages
# mwclient to interact with the MediaWiki API
# BS to read the html table from the wiki
# os and dotenv to store the mediawiki credentials in a safe place
from flask import Flask, request, redirect, url_for, jsonify, render_template
import mwclient
from bs4 import BeautifulSoup
import os
from dotenv import load_dotenv
from pathlib import Path
import textwrap
# load the mediawiki credentials from the shared folder
dotenv_path = Path("/var/www/.mw-credentials")
load_dotenv(dotenv_path=dotenv_path)
3 years ago
# load the configuration env
load_dotenv()
2 years ago
DEFAULT_PAGE = os.environ.get("DEFAULT_PAGE", '')
# prefix to add /soupboat/padliography to all the routes
class PrefixMiddleware(object):
def __init__(self, app, prefix=""):
self.app = app
self.prefix = prefix
def __call__(self, environ, start_response):
if environ["PATH_INFO"].startswith(self.prefix):
environ["PATH_INFO"] = environ["PATH_INFO"][len(self.prefix):]
environ["SCRIPT_NAME"] = self.prefix
return self.app(environ, start_response)
else:
start_response("404", [("Content-Type", "text/plain")])
return ["This url does not belong to the app.".encode()]
# create flask application
app = Flask(__name__)
# Get the URL prefix for the soupboat
# register the middleware to use our base_url as prefix on all the requests
3 years ago
base_url = os.environ.get('BASE_URL', '')
app.wsgi_app = PrefixMiddleware(app.wsgi_app, prefix=base_url)
2 years ago
def add_pad(padliography, link, title, overview, categories, date):
'''Add a new pad to the wiki page'''
2 years ago
# 1. Connect to the wiki
site = mwclient.Site('pzwiki.wdka.nl', path='/mw-mediadesign/')
# 2. Authenticate using the credential of a bot user registered in the wiki
### This is necesary the edit the contents of the page
site.login(
username=os.environ.get('MW_BOT'),
password=os.environ.get('MW_KEY')
)
# 3. Select the page and get the contents
2 years ago
# --> prefix the page title with Padliography/
# so we dont erase eventual pages with the same title
padliography = f'Padliography/{padliography}'
page = site.pages[padliography]
text = page.text()
# 4. Append the pad as new row in the table of pads
3 years ago
new_row = f'|-\n| {link} || {title} || {overview} || {categories} || {date} \n|-\n' + '|}'
text = text.replace('|}', new_row)
# 5. Apply the edit
page.edit(text, f'New pad in the {padliography}: {title}')
2 years ago
def get_pads(padliography):
'''Retrieve pads from the wiki'''
# 1. Connect to the wiki
site = mwclient.Site('pzwiki.wdka.nl', path='/mw-mediadesign/')
# 2. Log in using the credential of a bot user registered in the wiki
site.login(
username=os.environ.get('MW_BOT'),
password=os.environ.get('MW_KEY'),
3 years ago
)
# 3. Use the MediaWiki API to get the wikitext contents in HTML
2 years ago
# Pages in the padliography comes with the Padliography/ prefix
padliography = f'Padliography/{padliography}'
html = site.api('parse', prop='text', page=padliography)
# 4. Parse the HTML with BeautifulSoup to extract data from the table of pads
table = BeautifulSoup(html['parse']['text']['*'], features="html.parser").find(
"table", attrs={"class": "padliography"})
3 years ago
# 5. Find the headers of the table
3 years ago
headers = [header.text.lower().strip() for header in table.find_all('th')]
# 6. Create a list of pad, using each header as property of the object pad
3 years ago
pads = [
{headers[i]: cell.text.rstrip('\n')
for i, cell in enumerate(row.find_all('td'))}
3 years ago
for row in table.find_all('tr')]
#7. Remove empty pads from the list
pads = [pad for pad in pads if pad != {}]
return pads
2 years ago
def init_page(padliography, description):
'''Initialize a new instance of the padliography a the given page'''
# 1. Connect to the wiki
site = mwclient.Site('pzwiki.wdka.nl', path='/mw-mediadesign/')
# 2. Authenticate using the credential of a bot user registered in the wiki
2 years ago
# This is necesary the edit the contents of the page
2 years ago
site.login(
username=os.environ.get('MW_BOT'),
password=os.environ.get('MW_KEY')
)
# 3. Select the page and get the contents
2 years ago
# page in the padliography comes with the Padliography/ prefix
padliography = f'Padliography/{padliography}'
2 years ago
page = site.pages[padliography]
2 years ago
# 4. Insert the table template and a user-provided description
2 years ago
2 years ago
text = f'''
{description}
2 years ago
== Padliography ==
2 years ago
{{| class = "wikitable sortable padliography"
2 years ago
|-
!link !! title !! overview !! categories !! date
|-
|}}
[[Category:Padliography]]
'''
2 years ago
# 5. Apply the edit
2 years ago
page.edit(textwrap.dedent(text), f'New padliographish page created: Pads/{padliography}')
2 years ago
# Routes
@app.route('/')
3 years ago
def home():
'''Serve the homepage layout'''
2 years ago
return render_template('home.html', page=DEFAULT_PAGE, base_url=base_url)
2 years ago
2 years ago
@app.route('/<padliography>/')
def page(padliography):
'''Serve a specific padliography'''
2 years ago
return render_template('home.html', page=padliography, base_url=base_url)
2 years ago
@app.route('/api/<padliography>/', methods=['GET', 'POST'])
2 years ago
def api(padliography):
'''Manage the interaction with the MediaWiki API'''
3 years ago
if request.method == 'POST':
# Add a new pad
3 years ago
link = request.json.get('link', None)
title = request.json.get('title', None)
overview = request.json.get('overview', '')
categories = request.json.get('categories', '')
date = request.json.get('date', None)
2 years ago
add_pad(padliography, link, title, overview, categories, date)
redirect(url_for('home'))
# Return the pad list
3 years ago
response = jsonify({
2 years ago
'pads': get_pads(padliography)
3 years ago
})
response.headers.add('Access-Control-Allow-Origin', '*')
3 years ago
return response
3 years ago
2 years ago
@app.route('/api/<padliography>/init', methods=['GET', 'POST'])
def init(padliography):
if request.method == 'POST':
description = request.json.get('description', None)
2 years ago
if padliography is not None:
init_page(padliography, description)
return redirect(url_for('home'))
return 'ok'
2 years ago
# Get the port and mount the app
port = os.environ.get('FLASK_RUN_PORT', '')
2 years ago
debug = os.environ.get('DEBUG', False)
app.run(port=port, debug=debug)