Files
courses/2024_Datenbanksysteme/app.py
T
2025-07-30 15:57:48 +02:00

232 lines
9.7 KiB
Python

from flask import Flask, render_template, request
from flask_sqlalchemy import SQLAlchemy
from tabulate import tabulate
from sqlalchemy import text
import folium
import os
from decimal import Decimal
## INIT
app = Flask(__name__, template_folder=os.path.abspath('.'))
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL', 'postgresql://huaqo:0000@localhost:5432/geowebapp')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
## QUERIES
DATASET = """
SELECT {attributes}
FROM Hochschulen h
JOIN Mieten m ON m.land = h.land
LEFT JOIN Bevoelkerung b ON b.region_name = h.ort
AND b.bundesland = h.land
AND (
b.region_type = 'kreis'
OR b.region_name = 'Berlin'
OR b.region_name = 'Hamburg'
OR b.region_name = 'Bremen'
OR b.region_name = 'Hannover' AND b.region_type = 'landeshauptstadt'
OR b.region_name = 'Saarbrücken' AND b.region_type = 'landeshauptstadt')
WHERE {search_attr} ILIKE :search
ORDER BY {sort_by} {order}
LIMIT {limit};
"""
DATASET_GROUPED = """
SELECT {group_by}, COUNT(*) AS Anzahl
FROM Hochschulen
GROUP BY {group_by}
ORDER BY Anzahl DESC;
"""
## PARAMETERS
order_by_options = [
{'value': 'land', 'label': 'Bundesland'},
{'value': 'typ', 'label': 'Hochschultyp'},
{'value': 'traeger', 'label': 'Träger'},
{'value': 'precht', 'label': 'Promotionsrecht'},
{'value': 'hrecht', 'label': 'Habilitationsrecht'}
]
shortcut_options = [
{'url': '//127.0.0.1:5000/?search_attr=typ&search=&order=ASC&sort_by=gjahr&limit=10&attributes=&attributes=h.name_h&attributes=h.gjahr&attributes=h.ort&generate_map=true', 'label': 'Älteste Hochschulen in Deutschland'},
{'url': '//127.0.0.1:5000/?search_attr=typ&search=&order=DESC&sort_by=gjahr&limit=10&attributes=&attributes=h.name_h&attributes=h.gjahr&attributes=h.ort&generate_map=true', 'label': 'Jüngste Hochschulen in Deutschland'},
{'url': '//127.0.0.1:5000/?search_attr=typ&search=&order=DESC&sort_by=studenten&limit=10&attributes=&attributes=h.name_h&attributes=h.studenten&attributes=h.ort&generate_map=true', 'label': 'Hochschulen mit den meisten Studenten'},
{'url': '//127.0.0.1:5000/?search_attr=h.land&search=Berlin&order=DESC&sort_by=studenten&limit=10&attributes=&attributes=h.name_h&attributes=h.studenten&generate_map=true', 'label': 'Berliner Hochschulen mit den meisten Studenten'},
{'url': '//127.0.0.1:5000/?search_attr=typ&search=&order=ASC&sort_by=studenten&limit=10&attributes=&attributes=h.name_h&attributes=h.studenten&attributes=h.ort&generate_map=true', 'label': 'Hochschulen mit den wenigsten Studenten'},
{'url': '//127.0.0.1:5000/?search_attr=typ&search=&order=DESC&sort_by=b.total_population&limit=10&attributes=&attributes=h.name_h&attributes=h.ort&attributes=b.total_population&generate_map=true', 'label': 'Hochschulen mit den meisten Einwohnern'},
{'url': '//127.0.0.1:5000/?search_attr=typ&search=&order=ASC&sort_by=b.total_population&limit=10&attributes=&attributes=h.name_h&attributes=h.ort&attributes=b.total_population&generate_map=true', 'label': 'Hochschulen mit den wenigsten Einwohnern'},
{'url': '//127.0.0.1:5000/?search_attr=typ&search=&order=ASC&sort_by=m.mieten_ab_2019&limit=100&attributes=&attributes=h.name_h&attributes=h.ort&attributes=m.mieten_ab_2019&generate_map=true', 'label': 'Hochschulen mit den niedrigsten Mieten'},
{'url': '//127.0.0.1:5000/?search_attr=typ&search=&order=DESC&sort_by=m.mieten_ab_2019&limit=200&attributes=&attributes=h.name_h&attributes=h.ort&attributes=m.mieten_ab_2019&generate_map=true', 'label': 'Hochschulen mit den höchsten Mieten'}
]
search_options = [
{'value': 'h.name_h', 'label': 'Hochschulname'},
{'value': 'h.typ', 'label': 'Typ'},
{'value': 'h.traeger', 'label': 'Träger'},
{'value': 'h.land', 'label': 'Land'},
{'value': 'h.precht', 'label': 'Precht'},
{'value': 'h.hrecht', 'label': 'Hrecht'},
{'value': 'h.str', 'label': 'Straße'},
{'value': 'h.ort', 'label': 'Ort'},
{'value': 'h.web', 'label': 'Web'}
]
order_options = [
{'value': 'ASC', 'label': 'Aufsteigend'},
{'value': 'DESC', 'label': 'Absteigend'}
]
sort_by_options = [
{'value': 'name_h', 'label': 'Hochschulname'},
{'value': 'gjahr', 'label': 'Gründungsjahr'},
{'value': 'typ', 'label': 'Typ'},
{'value': 'traeger', 'label': 'Träger'},
{'value': 'land', 'label': 'Land'},
{'value': 'studenten', 'label': 'Studenten'},
{'value': 'precht', 'label': 'Precht'},
{'value': 'hrecht', 'label': 'Hrecht'},
{'value': 'plz', 'label': 'PLZ'},
{'value': 'str', 'label': 'Straße'},
{'value': 'ort', 'label': 'Ort'},
{'value': 'b.total_population', 'label': 'Einwohner'},
{'value': 'm.mieten_ab_2019', 'label': 'Mieten'}
]
checkbox_options = [
{'value': 'h.name_h', 'label': 'Hochschulname'},
{'value': 'h.gjahr', 'label': 'Gründungsjahr'},
{'value': 'h.typ', 'label': 'Typ'},
{'value': 'h.traeger', 'label': 'Träger'},
{'value': 'h.land', 'label': 'Land'},
{'value': 'h.studenten', 'label': 'Studenten'},
{'value': 'h.precht', 'label': 'Precht'},
{'value': 'h.hrecht', 'label': 'Hrecht'},
{'value': 'h.plz', 'label': 'PLZ'},
{'value': 'h.str', 'label': 'Straße'},
{'value': 'h.ort', 'label': 'Ort'},
{'value': 'h.web', 'label': 'Web'},
{'value': 'b.total_population', 'label': 'Einwohner'},
{'value': 'm.mieten_ab_2019', 'label': 'Mieten'}
]
## DATA QUERY
class Dataset_Parameters:
def __init__(self, order='ASC', limit='10', sort_by='gjahr', attributes=['name_h'], search='', search_attr='name_h', group_by='typ'):
if attributes is None:
attributes = ['name_h']
self.order = order
self.limit = limit
self.sort_by = sort_by
self.attributes = attributes
self.search = search
self.search_attr = search_attr
self.group_by = group_by
def update(self, **kwargs):
for key, value in kwargs.items():
if hasattr(self, key):
setattr(self, key, value)
@classmethod
def get_parameters(cls, request):
parameters = cls()
parameters.update(
order=request.args.get('order', parameters.order),
limit=request.args.get('limit', parameters.limit),
sort_by=request.args.get('sort_by', parameters.sort_by),
attributes=request.args.getlist('attributes') or parameters.attributes,
search=request.args.get('search', parameters.search),
search_attr=request.args.get('search_attr', parameters.search_attr),
group_by=request.args.get('group_by', parameters.group_by)
)
parameters.attributes = [attr for attr in parameters.attributes if attr]
if 'lat' not in parameters.attributes:
parameters.attributes.append('lat')
if 'lon' not in parameters.attributes:
parameters.attributes.append('lon')
return parameters
def get_data(query_template, parameters):
params_dict = {k: v for k, v in parameters.__dict__.items()}
for key, value in params_dict.items():
if isinstance(value, list):
params_dict[key] = ', '.join(value)
query = query_template.format(**params_dict)
result = db.session.execute(text(query), {'search': f"%{params_dict.get('search', '')}%"})
return result.fetchall()
## TABLES
def create_table(data, parameters):
lat_index = parameters.attributes.index('lat') if 'lat' in parameters.attributes else -1
lon_index = parameters.attributes.index('lon') if 'lon' in parameters.attributes else -1
filtered_data = []
for row in data:
filtered_row = [value for i, value in enumerate(row) if i not in [lat_index, lon_index]]
filtered_data.append(filtered_row)
filtered_attributes = [attr for attr in parameters.attributes if attr not in ['lat', 'lon']]
table = tabulate(filtered_data, headers=filtered_attributes, tablefmt='html')
return table
def create_grouped_table(data, parameters):
headers = [parameters.group_by, 'Anzahl']
table = tabulate(data, headers=headers, tablefmt='html')
return table
## MAP
def generate_map(data, column_names):
map_center = [51.1657, 10.4515]
map = folium.Map(location=map_center, tiles="cartodb positron", zoom_start=6)
try:
lat_index = column_names.index('lat')
lon_index = column_names.index('lon')
except ValueError:
print("Lat or Lon column not found in the result set")
return None
bounds = []
for row in data:
lat = row[lat_index]
lon = row[lon_index]
if isinstance(lat, Decimal):
lat = float(lat)
if isinstance(lon, Decimal):
lon = float(lon)
if lat is not None and lon is not None:
folium.Marker([lat, lon], popup=f"Data: {row}").add_to(map)
bounds.append([lat, lon])
if bounds:
map.fit_bounds(bounds)
map_html = map._repr_html_()
return map_html
## VIEWS
@app.route('/', methods=['GET'])
def home():
dataset_parameters = Dataset_Parameters.get_parameters(request)
dataset = get_data(DATASET, dataset_parameters)
dataset_grouped = get_data(DATASET_GROUPED, dataset_parameters)
table = create_table(dataset, dataset_parameters)
table_grouped = create_grouped_table(dataset_grouped, dataset_parameters)
map = generate_map(dataset, dataset_parameters.attributes)
return render_template(
'index.html',
table=table,
table_grouped=table_grouped,
parameters=dataset_parameters,
map=map,
order_by_options=order_by_options,
search_options=search_options,
order_options=order_options,
sort_by_options=sort_by_options,
shortcut_options=shortcut_options,
checkbox_options=checkbox_options)
## RUN
if __name__ == '__main__':
app.run(debug=True)