Skip to content
This repository has been archived by the owner on Mar 24, 2022. It is now read-only.

Commit

Permalink
✨ added support for bookings
Browse files Browse the repository at this point in the history
  • Loading branch information
nlohmann committed Apr 25, 2017
1 parent 77be81b commit 407cea5
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 4 deletions.
2 changes: 1 addition & 1 deletion app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
cache = Cache(app, config={'CACHE_TYPE': 'simple'})

# create a Flask-RESTPlus API
api = Api(app, version='0.0.5', catch_all_404s=True, prefix='/api',
api = Api(app, version='0.0.6', catch_all_404s=True, prefix='/api', validate=True,
title='Deutsche Bahn Reiseplan', description=u'API for the itineraries of the Deutsche Bahn',
terms_url='https://www.bahn.de/p/view/home/agb/nutzungsbedingungen.shtml',
contact='Niels Lohmann', contact_email='[email protected]',
Expand Down
76 changes: 76 additions & 0 deletions app/buchung.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# coding=utf-8

import requests
from bs4 import BeautifulSoup
import datetime


class Buchungseintrag(object):
def __init__(self):
self.type = None
self.travel_date = None
self.description = None
self.voucher_number = None
self.price = None


class Buchung(object):
def __init__(self, auftragsnummer, nachname):
self.auftragsnummer = auftragsnummer
self.nachname = nachname

self.reference_number = None
self.booking_date = None
self.booker = None
self.traveler = None
self.state = None
self.entries = []

self.__download()

@property
def valid(self):
return self.reference_number is not None

def __download(self):
url = 'https://fahrkarten.bahn.de/privatkunde/start/start.post'
params = {
'dbkanal_007': 'L01_S01_D001_KIN0014_qf-buchungenauftrag_LZ08'
}
payload = {
'scope': 'bahnatsuche',
'search': 1,
'country': 'DEU',
'lang': 'de',
'auftragsnr': self.auftragsnummer,
'reisenderNachname': self.nachname
}

page = requests.post(url, params=params, data=payload)
soup = BeautifulSoup(page.content, 'html.parser')

headers = soup.find_all('table', {'class': 'form brsDetailsTable brsDetailsKopfzeileTable bottommargin-big spanall'})
try:
header_entries = headers[0].find_all('td')
except IndexError:
return

self.reference_number = header_entries[0].text.strip()
self.booking_date = datetime.datetime.strptime(header_entries[1].text.strip(), '%d.%m.%Y').date()
self.booker = header_entries[2].text.strip()
self.traveler = header_entries[3].text.strip()
self.state = header_entries[5].text.strip()

details = soup.find_all('table', {'class': 'form brsDetailsTable spanall'})
for tr in details[0].find_all('tr'):
try:
detail_entries = tr.find_all('td')
entry = Buchungseintrag()
entry.type = detail_entries[0].text.strip()
entry.travel_date = datetime.datetime.strptime(detail_entries[1].text.strip(), '%d.%m.%Y').date()
entry.description = detail_entries[2].text.strip()
entry.voucher_number = int(detail_entries[3].text.strip())
entry.price = float(detail_entries[5].text.strip().replace(' EUR', '').replace(',', '.'))
self.entries.append(entry)
except IndexError:
pass
59 changes: 56 additions & 3 deletions app/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,18 @@
from flask_restplus import Resource, fields
from app import cache, api
from app.reiseplan import Reiseplan
from app.buchung import Buchung

ns = api.namespace('itineraries', description='Operations related to itineraries')
########################################################################
# namespaces
########################################################################

ns_itineraries = api.namespace('itineraries', description='Operations related to itineraries')
ns_bookings = api.namespace('bookings', description='Operations related to bookings')

########################################################################
# models
########################################################################

m_location = api.model('Location', {
'coordinates': fields.Nested(
Expand Down Expand Up @@ -39,11 +49,37 @@
m_itinerary = api.model('Itinerary', {
'travelDate': fields.Date(description="start date", required=True),
'mapUrl': fields.Url(description='map with the complete route', example='http://maps.googleapis.com/maps/api/staticmap?size=800x800&scale=2&maptype=terrain&path=enc:}{q_IufrpAov@br`@c{eEjdnR??q_OmppG{of@akrB_bn@q}X&sensor=false&language=de', required=True),
'referenceNumber': fields.String(example='TYFMQE', description='reference number', minLength=6, pattern='^[A-F0-9]{6}$', required=True),
'referenceNumber': fields.String(example='TYFMQE', description='reference number', minLength=6, pattern='^[A-Z0-9]{6}$', required=True),
'legs': fields.List(fields.Nested(m_leg), description='legs of the travel', required=True)
})

@ns.route('/<auftragsnummer>')
m_bookingrequest = api.model('BookingRequest', {
'referenceNumber': fields.String(description='reference number of the booking ("Auftragsnummer")', minLength=6, pattern='^[A-Z0-9]{6}$', required=True),
'lastName': fields.String(description='the last name of the booker', required=True)
})

m_booking_entry = api.model('BookingEntry', {
'entryType': fields.String(description='type of the booking entry; values can be "Fahrschein" or "Reservierung"', example="Fahrschein", attribute='type', required=True),
'travelDate': fields.Date(description='travel date', example='2017-05-01', attribute='travel_date', required=True),
'description': fields.String(description='descriptive text for the booking entry', example='Einfache Fahrt, Flexpreis, 2. Kl., 2 Erw., BC 50', required=True),
'voucherNumber': fields.Integer(description='voucher number of the booking entry', example=75133739, attribute='voucher_number', required=True),
'price': fields.Float(min=0.0, description='price of the booking entry in EUR', example=39.90, required=True)
})

m_booking = api.model('Booking', {
'referenceNumber': fields.String(description='reference number', example='TYFMQE', attribute='reference_number', required=True),
'bookingDate': fields.Date(description='booking date', example='2017-04-20', attribute='booking_date', required=True),
'booker': fields.String(description='name of the booker', example='Mustermann', required=True),
'traveler': fields.String(description='name of the traveler', example='Mustermann', required=True),
'state': fields.String(description='state of the booking; values can be "bearbeitet"', example="bearbeitet", required=True),
'entries': fields.List(fields.Nested(m_booking_entry), description='entries of the booking', required=True)
})

########################################################################
# routes
########################################################################

@ns_itineraries.route('/<auftragsnummer>')
@api.doc(params={'auftragsnummer': 'a 6-character reference number'})
class ReferenceNumber(Resource):
@api.response(200, 'success', m_itinerary)
Expand All @@ -60,3 +96,20 @@ def get(self, auftragsnummer):
return dict(rp)
else:
api.abort(404, error='itinerary not found', referenceNumber=auftragsnummer)

@ns_bookings.route('/')
class Bookin(Resource):
@api.response(404, 'booking not found')
@ns_bookings.marshal_with(m_booking)
@ns_bookings.expect(m_bookingrequest)
def post(self):
"""
returns a booking
[terms of use](https://www.bahn.de/p/view/home/agb/nutzungsbedingungen.shtml)
"""
booking = Buchung(api.payload.get('referenceNumber'), api.payload.get('lastName'))
if booking.valid:
return booking
else:
api.abort(404, error='booking not found', payload=api.payload)
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
aniso8601==1.2.0
appdirs==1.4.3
beautifulsoup4==4.5.3
click==6.7
Flask==0.12.1
Flask-Cache==0.13.1
Flask-Compress==1.4.0
flask-restplus==0.10.1
functools32==3.2.3.post2
gunicorn==19.7.1
itsdangerous==0.24
Jinja2==2.9.6
Expand Down

0 comments on commit 407cea5

Please sign in to comment.