Readme and dynamic page

master
km0 2 years ago
parent 177e0f6b41
commit 458131241b

@ -2,24 +2,104 @@
![frog](cover.jpg) ![frog](cover.jpg)
A way to create, archive and retrieve our pads. Now with a fast way to insert new pads without too much effort uiii. The Padliography is a tool to keep track of our pads. It is built to interact with the MediaWiki API, and it uses [XPUB & Lens-Based Wiki](https://pzwiki.wdka.nl/mediadesign/Main_Page)'s pages as archive.
## Features:
# Features - Multiple archives
- Custom categories - Custom categories
- Filter by categories - Filter by categories
- Table sorting - Table Sorting
- Create new pad if URL is not provided - Create new pad
- Interact with the wiki
## Quick Start
When you enter the Padliography homepage, it loads by default the XPUB2 archive. Just above the pads list you can read from where the pads are being fetched, and change the source by inserting the title of the page in which tne archive you desire is.
To create a brand new archive click on `Init a new padliography` and insert a title for the page and a short description to put in the wiki.
**Watch out:** This action will create a new blank page, so check that there is not already something with the same title in the wiki, or it will be overwritten. Thanks to the wiki's history it is possible to recover overwritten contents, so we are safe and the contents are not in total danger.
# TODO Use the **Add a new pad** form to insert a pad in the archive. The required information are a title, the URL of the document, a short overview, a date and some categories. The categories are custom, and are usefull to organize pads in themes or context.
- Setup script The **Filter Categories** section loads all the categories from the current archive and use it to filter the list of pads.
- Search
- Server side rendering?
The list of pads is sortable. Just click on the headers to order the pad, click twice to invert the order.
At the moment this Padliography instance runs on the [Soupboat](https://hub.xpub.nl/soupboat/). If you are using it a lot, consider to install a new instance on a different environment, in order not to put a strain on the small Raspberry. It should be fine anyway.
## How does it work
![lifecycle](lifecycle.jpg) ![lifecycle](lifecycle.jpg)
_wip_ & To have an overview over the lifecycle of the Padliography let's start from the wiki.
_Documentation coming soon_ The pads are stored in a page with a minimal template. They are organized in a table that has the CSS class `padliography`.
Through the [MediaWiki API](https://pzwiki.wdka.nl/mediadesign/Special:ApiSandbox) we can request the contents of this page from somewhere else. This somewhere is a small Raspberry Pi called Soupboat and installed on the 4th floor of the WdKA.
The soupboat is a small server, and it runs a [Flask](https://flask.palletsprojects.com/en/2.2.x/) application that interact with the wiki using the [mwclient](https://mwclient.readthedocs.io/en/latest/index.html) python library.
When a user enter the homepage of the padliography, the Flask application returns a [Vue3](https://vuejs.org/) app and through that request to the wiki API for the list of pads.
The response from the MediaWiki API is parsed on the server using [BeautifulSoup](https://www.crummy.com/software/BeautifulSoup/), that try to find the `padliography` table with some CSS selectors and then to build a list of pads organized by the properties of the table's headers. The list of table is sent to the Vue app as a JSON message, and used to build a fancy sortable table and filtering system.
When a user interacts with the form to add a new pad the process is the same, but in the opposite direction: first the Vue app send the data to the Flask application that in turns tries to add it in the archive wiki page.
## Development
### Local setup
To install the Padliography somewhere else start by cloning this repo.
```
git clone https://git.xpub.nl/kamo/pad-bis.git
```
Then move to the cloned repository folder and create a python virtual environment.
```
cd pad-bis
python3 -m venv venv
```
Activate the virtual environment
```
# on unix
source venv/bin/activate
# on windows
venv\Scripts\activate
```
and then install the requirements with pip. The **.** here refers to the current working directory, where the **setup.py** file can be found. There are specified all the packages the application needs.
```
pip install -e .
```
Once the installation is completed, launch the application and open it on the default port .
```
python pad-bis.py
```
If you open `localhost:5000` on your browser, it will return the padliography stuck in the loading process. This is why before interacting with the MediaWiki API we need to specify the credential for a user to log into the wiki. On your local version you can use your own credentials, but if you plan to put this padliography online it's better to use a dedicated user, aka a bot.
Remember not to commit your credentials on git, or they will be rendered public! Instead create and `.env` file, that will not be uploaded on git as specified in the `.gitignore` file.
Create an `.env` file in your working directory with the following properties:
```
MW_BOT= your username
MW_KEY= your password
```
At this step you are up and running, the padliography should be able to fetch the pad from the wiki etc.
<!-- TODO: put the padliography online -->
## License
This Padliography was distilled with the help and within the context of XPUB, 2022.
Copyleft with a difference: This is a collective work, you are invited to copy, distribute, and modify it under the terms of the [CC4r](https://constantvzw.org/wefts/cc4r.en.html).

@ -1,16 +1,15 @@
# Flask application to serve the web pages # Flask application to serve the web pages
from flask import Flask, request, redirect, url_for, jsonify, render_template # 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
# Mediawiki client to interact with the Wiki from flask import Flask, request, redirect, url_for, jsonify, render_template
import mwclient import mwclient
# BS to read the html table from the wiki
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
# os and dotenv to store the mediawiki credentials in a safe place
import os import os
from dotenv import load_dotenv from dotenv import load_dotenv
from pathlib import Path from pathlib import Path
import textwrap
# load the mediawiki credentials from the shared folder # load the mediawiki credentials from the shared folder
dotenv_path = Path("/var/www/.mw-credentials") dotenv_path = Path("/var/www/.mw-credentials")
@ -20,7 +19,6 @@ load_dotenv(dotenv_path=dotenv_path)
load_dotenv() load_dotenv()
# prefix to add /soupboat/padliography to all the routes # prefix to add /soupboat/padliography to all the routes
class PrefixMiddleware(object): class PrefixMiddleware(object):
def __init__(self, app, prefix=""): def __init__(self, app, prefix=""):
self.app = app self.app = app
@ -45,9 +43,6 @@ app = Flask(__name__)
base_url = os.environ.get('BASE_URL', '') base_url = os.environ.get('BASE_URL', '')
app.wsgi_app = PrefixMiddleware(app.wsgi_app, prefix=base_url) app.wsgi_app = PrefixMiddleware(app.wsgi_app, prefix=base_url)
# Page of the wiki with the pads
# padliography = os.environ.get('PAGE', '')
def add_pad(padliography, link, title, overview, categories, date): def add_pad(padliography, link, title, overview, categories, date):
'''Add a new pad to the wiki page''' '''Add a new pad to the wiki page'''
@ -127,20 +122,20 @@ def init_page(padliography, description):
# 4. Insert the table template and a user-provided description # 4. Insert the table template and a user-provided description
text = ''' text = f'''
{} {description}
== Padliography == == Padliography ==
{| class = "wikitable sortable padliography" {{| class = "wikitable sortable padliography"
|- |-
!link !! title !! overview !! categories !! date !link !! title !! overview !! categories !! date
|- |-
|} |}}
'''.format(description) '''
# 5. Apply the edit # 5. Apply the edit
page.edit(text, f'New padliographish page created: {padliography}') page.edit(textwrap.dedent(text), f'New padliographish page created: {padliography}')
# Routes # Routes
@ -155,8 +150,8 @@ def home():
def api(padliography): def api(padliography):
'''Manage the interaction with the MediaWiki API''' '''Manage the interaction with the MediaWiki API'''
# Add a new pad
if request.method == 'POST': if request.method == 'POST':
# Add a new pad
link = request.json.get('link', None) link = request.json.get('link', None)
title = request.json.get('title', None) title = request.json.get('title', None)
overview = request.json.get('overview', '') overview = request.json.get('overview', '')
@ -171,10 +166,16 @@ def api(padliography):
'pads': get_pads(padliography) 'pads': get_pads(padliography)
}) })
response.headers.add('Access-Control-Allow-Origin', '*') response.headers.add('Access-Control-Allow-Origin', '*')
return response return response
@app.route('/api/<padliography>/init', methods=['GET', 'POST'])
def init(padliography):
if request.method == 'POST':
description = request.json.get('description', None)
if padliography != None:
init_page(padliography, description)
return redirect(url_for('home'))
return 'ok'
# Get the port and mount the app # Get the port and mount the app
port = os.environ.get('FLASK_RUN_PORT', '') port = os.environ.get('FLASK_RUN_PORT', '')

@ -87,6 +87,7 @@ button:hover {
} }
table { table {
margin-top: 32px;
width: 100%; width: 100%;
} }
@ -235,14 +236,19 @@ td.categories {
border: none; border: none;
} }
.change { .change, .init {
opacity: 0.5; opacity: 0.5;
border: none; border: none;
display: inline; display: inline;
padding: 0; padding: 0;
} }
.change:hover{ .change {
margin-left: 6px;
}
.change:hover,
.init:hover{
background-color: transparent; background-color: transparent;
color: currentColor; color: currentColor;
opacity: 1; opacity: 1;

@ -1,7 +1,7 @@
const { ref } = Vue; const { ref } = Vue;
const padStore = ref([]); const padStore = ref([]);
const currentPage = ref("Pad-test"); const currentPage = ref("Padliography2");
export default function padliographyStore() { export default function padliographyStore() {
return { padStore, currentPage }; return { padStore, currentPage };

@ -9,7 +9,21 @@ export default {
const { padStore, currentPage } = padliographyStore(); const { padStore, currentPage } = padliographyStore();
const newPage = ref(""); const newPage = ref("");
const initPage = ref("");
const pageDescription = ref("");
const initialize = function () {
fetch(`api/${initPage.value}/init`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
description: pageDescription.value,
}),
}).then((res) => (currentPage.value = initPage.value));
};
const edit = ref(false); const edit = ref(false);
const init = ref(false);
const pads = ref([]); const pads = ref([]);
@ -99,10 +113,14 @@ export default {
toggle, toggle,
sort, sort,
formatDate, formatDate,
initialize,
padStore, padStore,
storedPads, storedPads,
edit, edit,
init,
newPage, newPage,
initPage,
pageDescription,
}; };
}, },
template: ` template: `
@ -121,17 +139,27 @@ export default {
</div> </div>
<p class="from"> <div class="from" v-if="!init">
Fetching pads from Fetching pads from
<span class="editing" v-if="edit"> <span class="editing" v-if="edit">
<input placeholder="New Page" v-model="newPage">
<input v-model="newPage">
<button @click="edit=false, currentPage=newPage"> confirm </button> <button @click="edit=false, currentPage=newPage"> confirm </button>
<button @click="edit=false"> x </button> <button @click="edit=false"> x </button>
</span> </span>
<a v-else :href="'https://pzwiki.wdka.nl/mediadesign/' + currentPage">{{currentPage}}</a> <button class="change" v-if="!edit" @click="edit = true">change</button> <a v-else :href="'https://pzwiki.wdka.nl/mediadesign/' + currentPage">{{currentPage}}</a>
</p> <button class="change" v-if="!edit" @click="edit = true">change</button> <br>
</div>
<div v-if="init" class="init-form">
<input placeholder="New Page" v-model="initPage">
<input placeholder="Description" v-model="pageDescription">
<button @click="init=false, initialize()"> confirm </button>
<button @click="init=false, initPage=''"> x </button>
</div>
<button class="init" v-else @click="init = true"> Init a new Padliography</button>

Loading…
Cancel
Save