OpenMensa parser for STW Potsdam after canteen website redesign

This commit is contained in:
Falco Duersch
2018-09-19 17:15:34 +02:00
commit e0b88bbc87
17 changed files with 5788 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
# -*- encoding: utf-8 -*-
+4
View File
@@ -0,0 +1,4 @@
# -*- encoding: utf-8 -*-
from views import app
app.run()
+5
View File
@@ -0,0 +1,5 @@
# -*- encoding: utf-8 -*-
from collections import namedtuple
Canteen = namedtuple('Canteen', ('name', 'street', 'city', 'id', 'chash'))
+33
View File
@@ -0,0 +1,33 @@
# -*- encoding: utf-8 -*-
import json
import requests
from collections import namedtuple
MenuParams = namedtuple('MenuParams', ('canteen_id', 'chash'))
URL = 'https://www.studentenwerk-potsdam.de/essen/unsere-mensen-cafeterien/detailinfos/'
def _param_json(it):
return json.dumps(it, separators=(',', ':'))
def download_menu(menu_params):
context = {
'record': 'pages_66',
'path': 'tt_content.list.20.ddfmensa_ddfmensajson'
}
params = {
'tx_typoscriptrendering[context]': _param_json(context),
'tx_ddfmensa_ddfmensajson[mensa]': menu_params.canteen_id,
'cHash': menu_params.chash
}
body = {
'data': False
}
request = requests.post(URL, params=params, json=body)
return request.json()
+41
View File
@@ -0,0 +1,41 @@
[neues_palais]
name = Mensa Am Neuen Palais
street = Am Neuen Palais 10, Haus 12
city = 14469 Potsdam
id = 1
cHash = 677e69a0122d1ccca57a6c30dd149cc8
[golm]
name = Mensa Golm
street = Karl-Liebknecht-Straße 24/25
city = 14476 Potsdam / OT Golm
id = 4
cHash = b14a1168346e0b7db67da056660d3a0e
[griebnitzsee]
name = Mensa Griebnitzsee
street = August-Bebel-Straße 69, Haus 6
city = 14482 Potsdam
id = 6
cHash = 0c7f1095dcc78ff74b6cd32cd231c75f
[kiepenheuerallee]
name = Mensa Kiepenheuerallee
street = Kiepenheuerallee 5
city = 14469 Potsdam
id = 7
cHash = 3900afbd3d9b776e947708b7dfd2378c
[wildau]
name = Mensa Wildau
street = Hochschulring 1
city = 15745 Wildau
id = 33
cHash = 2ba7ac17766ceec4a35dac29b593378f
[brandenburg]
name = Mensa Brandenburg an der Havel
street = Magdeburger Straße 50
city = 14770 Brandenburg an der Havel
id = 38
cHash = 0843fa029901bdfc0e76d8f31eee7f56
+29
View File
@@ -0,0 +1,29 @@
# -*- encoding: utf-8 -*-
import ConfigParser
import io
import os
from functools import partial
from canteen import Canteen
def _get_config(filename):
config = ConfigParser.SafeConfigParser()
path = os.path.join('stw_potsdam', filename)
with io.open(path, encoding='utf-8') as f:
config.readfp(f)
return config
def _parse_canteen(config, canteen_name):
get = partial(config.get, canteen_name)
return Canteen(name=get('name'),
street=get('street'),
city=get('city'),
id=get('id'),
chash=get('cHash'))
def read_canteen_config():
config = _get_config('canteens.ini')
return {name: _parse_canteen(config, name) for name in config.sections()}
+65
View File
@@ -0,0 +1,65 @@
# -*- encoding: utf-8 -*-
from pyopenmensa.feed import LazyBuilder
PRICE_ROLE_MAPPING = {
'student': 'preis_s',
'other': 'preis_g',
'employee': 'preis_m'
}
def _active_days(menu):
for container in menu['wochentage']:
day = container['datum']
active = 'angebote' in day
if active:
yield day
def _notes(offer):
result = []
for label in offer['labels']:
result.append(label['name'].capitalize())
return result
def _prices(offer):
result = {}
for role, api_role in PRICE_ROLE_MAPPING.items():
if api_role not in offer:
continue
price = offer[api_role]
# When no price is set, this can be empty dict
if (isinstance(price, unicode) or isinstance(price, str)) and price.strip():
result[role] = str(price) # Convert unicode to str for PyOpenMensa -> misses type check
return result
def _process_day(builder, day):
for offer in day['angebote']:
builder.addMeal(date=day['data'],
category=offer['titel'],
name=offer['beschreibung'],
notes=_notes(offer),
prices=_prices(offer),
roles=None)
def _create_builder(canteen):
builder = LazyBuilder()
builder.name = canteen.name
builder.address = canteen.street
builder.city = canteen.city
return builder
def render(canteen, menu):
builder = _create_builder(canteen)
for day in _active_days(menu):
_process_day(builder, day)
return builder.toXMLFeed()
+56
View File
@@ -0,0 +1,56 @@
# -*- encoding: utf-8 -*-
from flask import Flask, make_response
from werkzeug.contrib.cache import SimpleCache
import feed
from config import read_canteen_config
from canteen_api import MenuParams, download_menu
CACHE_TIMEOUT = 45 * 60
app = Flask(__name__)
cache = SimpleCache()
def canteen_not_found(config, canteen_name):
app.logger.warn('Canteen %s not found', canteen_name)
configured = ', '.join("'{}'".format(c) for c in config.keys())
message = "Canteen '{canteen}' not found, available: {configured}".format(canteen=canteen_name,
configured=configured)
return make_response(message, 404)
def get_menu_cached(canteen):
params = MenuParams(canteen_id=canteen.id, chash=canteen.chash)
menu = cache.get(params)
if menu:
app.logger.info('Using cached menu for %s', canteen)
return menu or get_menu(canteen, params)
def get_menu(canteen, params):
app.logger.info('Downloading menu for %s', canteen)
menu = download_menu(params)
cache.set(params, menu, timeout=CACHE_TIMEOUT)
return menu
def canteen_feed_xml(canteen, menu):
xml = feed.render(canteen, menu)
response = make_response(xml)
response.mimetype = 'text/xml'
return response
@app.route('/canteen/<canteen_name>')
def canteen_feed(canteen_name):
config = read_canteen_config()
if canteen_name not in config:
return canteen_not_found(config, canteen_name)
canteen = config[canteen_name]
menu = get_menu_cached(canteen)
return canteen_feed_xml(canteen, menu)