OpenMensa parser for STW Potsdam after canteen website redesign
This commit is contained in:
@@ -0,0 +1 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
from views import app
|
||||
|
||||
app.run()
|
||||
@@ -0,0 +1,5 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
Canteen = namedtuple('Canteen', ('name', 'street', 'city', 'id', 'chash'))
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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()}
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user