initial commit
This commit is contained in:
commit
8e00cbb079
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# created by virtualenv automatically
|
||||||
|
.venv
|
||||||
|
.idea
|
||||||
|
__pycache__
|
||||||
|
app-2.2.0.js
|
48
app.py
Normal file
48
app.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import locale
|
||||||
|
|
||||||
|
from flask import Flask, render_template, request
|
||||||
|
|
||||||
|
from forms.SearchForm import SearchForm
|
||||||
|
from arztapi.APIHandler import APIHandler
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.secret_key = "nosecretkey"
|
||||||
|
locale.setlocale(locale.LC_ALL, 'de_DE.UTF-8')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def main_page():
|
||||||
|
search_form = SearchForm()
|
||||||
|
return render_template("index.html", form=search_form)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/search", methods=["POST"])
|
||||||
|
def search_for_doctors_with_params():
|
||||||
|
search_form = SearchForm(request.form)
|
||||||
|
if search_form.validate():
|
||||||
|
location = search_form.location.data
|
||||||
|
therapy_types = search_form.therapy_type.data
|
||||||
|
therapy_age_range = search_form.therapy_age_range.data
|
||||||
|
therapy_setting = search_form.therapy_setting.data
|
||||||
|
|
||||||
|
print(f"Location: {location}")
|
||||||
|
print(f"Therapy Types: {therapy_types}")
|
||||||
|
print(f"Age Range: {therapy_age_range}")
|
||||||
|
print(f"Therapy Setting: {therapy_setting}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(search_form.errors)
|
||||||
|
|
||||||
|
api_handler = APIHandler()
|
||||||
|
locations = api_handler.get_lat_lon_location_list(location)[0]
|
||||||
|
base64_value = api_handler.calculate_req_value_base64(float(locations["lat"]), float(locations["lon"]))
|
||||||
|
api_handler.get_list_of_doctors(float(locations['lat']), float(locations['lon']), base64_value, therapy_types, therapy_age_range, therapy_setting)
|
||||||
|
api_handler.get_general_doctor_information()
|
||||||
|
sorted_doctor_phone_times = api_handler.get_doctor_phone_times_sorted()
|
||||||
|
|
||||||
|
return render_template("result.html", doctors=sorted_doctor_phone_times)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run()
|
199
arztapi/APIHandler.py
Normal file
199
arztapi/APIHandler.py
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from requests import JSONDecodeError
|
||||||
|
import base64
|
||||||
|
from datetime import datetime
|
||||||
|
from arztapi.ArztPraxisDatas import ArztPraxisDatas
|
||||||
|
from arztapi.DoctorInformation import DoctorInformation, PhoneTime
|
||||||
|
from arztapi.DoctorPhoneTime import DoctorPhoneTime
|
||||||
|
|
||||||
|
|
||||||
|
class APIHandler:
|
||||||
|
def __init__(self):
|
||||||
|
self.base_api_url = "https://arztsuche.116117.de/api/"
|
||||||
|
self.phone_times = []
|
||||||
|
self.general_information = []
|
||||||
|
|
||||||
|
def get_lat_lon_location_list(self, location):
|
||||||
|
api_path = self.base_api_url + "location"
|
||||||
|
headers = {
|
||||||
|
"Accept": "application/json, text/plain, */*",
|
||||||
|
"Accept-Language": "de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7",
|
||||||
|
"Authorization": "Basic YmRwczpma3I0OTNtdmdfZg==",
|
||||||
|
"Connection": "keep-alive",
|
||||||
|
}
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"loc": location,
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.get(api_path, params=params, headers=headers)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
except JSONDecodeError:
|
||||||
|
print("foo")
|
||||||
|
print(response.text)
|
||||||
|
|
||||||
|
def get_list_of_doctors(self, lat, lon, req_val_base64, therapy_types, therapy_age, therapy_setting) -> ArztPraxisDatas:
|
||||||
|
api_path = self.base_api_url + "data"
|
||||||
|
headers = {
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Accept-Language": "de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7",
|
||||||
|
"Authorization": "Basic YmRwczpma3I0OTNtdmdfZg==",
|
||||||
|
"Connection": "keep-alive",
|
||||||
|
"req-val": req_val_base64,
|
||||||
|
}
|
||||||
|
|
||||||
|
json_data = {
|
||||||
|
# TODO: Find out what r means
|
||||||
|
"r": 900,
|
||||||
|
"lat": lat,
|
||||||
|
"lon": lon,
|
||||||
|
# TODO: mapping for filters
|
||||||
|
"filterSelections": [
|
||||||
|
{
|
||||||
|
"title": "Fachgebiet Kategorie",
|
||||||
|
"fieldName": "fgg",
|
||||||
|
"selectedCodes": [
|
||||||
|
"12",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Psychotherapie: Verfahren",
|
||||||
|
"fieldName": "ptv",
|
||||||
|
# TODO: filter for therapy types
|
||||||
|
"selectedCodes": therapy_types,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Psychotherapie: Altersgruppe",
|
||||||
|
"fieldName": "pta",
|
||||||
|
# TODO: filter for age
|
||||||
|
"selectedCodes": [
|
||||||
|
therapy_age
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Psychotherapie: Setting",
|
||||||
|
"fieldName": "pts",
|
||||||
|
"selectedCodes": [
|
||||||
|
therapy_setting
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"locOrigin": "USER_INPUT",
|
||||||
|
"initialSearch": False,
|
||||||
|
"viaDeeplink": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post(api_path, headers=headers, json=json_data)
|
||||||
|
response.raise_for_status()
|
||||||
|
self.phone_times = ArztPraxisDatas(**response.json())
|
||||||
|
return self.phone_times
|
||||||
|
|
||||||
|
def get_general_doctor_information(self) -> List[DoctorInformation]:
|
||||||
|
general_doctor_information = []
|
||||||
|
for data in self.phone_times.arztPraxisDatas:
|
||||||
|
doctor_day_times = data.tsz
|
||||||
|
phone_times = []
|
||||||
|
for day in doctor_day_times:
|
||||||
|
if day.tszDesTyps:
|
||||||
|
for contact_times in day.tszDesTyps:
|
||||||
|
if contact_times.typ == "Telefonische Erreichbarkeit":
|
||||||
|
phone_times_day = contact_times.sprechzeiten
|
||||||
|
for phone_time_day in phone_times_day:
|
||||||
|
start_time_str, end_time_str = phone_time_day.zeit.split("-")
|
||||||
|
start_date_time = self.parse_date_string(f"{day.d} {start_time_str}")
|
||||||
|
end_date_time = self.parse_date_string(f"{day.d} {end_time_str}")
|
||||||
|
current_phone_time_dict = {
|
||||||
|
"start": start_date_time,
|
||||||
|
"end": end_date_time
|
||||||
|
}
|
||||||
|
current_phone_time = PhoneTime(**current_phone_time_dict)
|
||||||
|
phone_times.append(current_phone_time)
|
||||||
|
|
||||||
|
doctor_information_dict = {
|
||||||
|
"name": data.name,
|
||||||
|
"tel": data.tel,
|
||||||
|
"fax": data.fax,
|
||||||
|
"anrede": data.anrede,
|
||||||
|
"email": data.email,
|
||||||
|
"distance": data.distance,
|
||||||
|
"strasse": data.strasse,
|
||||||
|
"hausnummer": data.hausnummer,
|
||||||
|
"plz": data.plz,
|
||||||
|
"ort": data.ort,
|
||||||
|
"telefonzeiten": phone_times
|
||||||
|
}
|
||||||
|
doctor_information = DoctorInformation(**doctor_information_dict)
|
||||||
|
general_doctor_information.append(doctor_information)
|
||||||
|
|
||||||
|
self.general_information = general_doctor_information
|
||||||
|
return self.general_information
|
||||||
|
|
||||||
|
def get_doctor_phone_times_sorted(self):
|
||||||
|
doctor_phone_times = []
|
||||||
|
for doctor_information in self.general_information:
|
||||||
|
for phone_time in doctor_information.telefonzeiten:
|
||||||
|
doctor_phone_time_dict = {
|
||||||
|
"phone_time": phone_time,
|
||||||
|
"doctor_name": doctor_information.name,
|
||||||
|
"doctor_address": f"{doctor_information.plz} {doctor_information.ort} "
|
||||||
|
f"{doctor_information.strasse} {doctor_information.hausnummer}",
|
||||||
|
"doctor_phone_number": doctor_information.tel
|
||||||
|
}
|
||||||
|
doctor_phone_time = DoctorPhoneTime(**doctor_phone_time_dict)
|
||||||
|
doctor_phone_times.append(doctor_phone_time)
|
||||||
|
return sorted(doctor_phone_times, key=lambda dpt: dpt.phone_time.start)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def calculate_req_value_base64(lat, lon):
|
||||||
|
"""
|
||||||
|
This function is based on the initial Javascript code found in app.js.
|
||||||
|
It is rewritten in Python to calculate the HTTP header req_val for proper requests with the correct location.
|
||||||
|
:param lat:
|
||||||
|
:param lon:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
# Adjust lat and lon values slightly
|
||||||
|
adjusted_lat = lat + 1.1
|
||||||
|
adjusted_lon = lon + 2.3
|
||||||
|
|
||||||
|
# Get the current time in milliseconds since epoch
|
||||||
|
current_time = datetime.now()
|
||||||
|
timestamp_str = str(int(current_time.timestamp() * 1000)) # Convert to milliseconds
|
||||||
|
|
||||||
|
# Extract digits from latitude
|
||||||
|
lat_integer_part = str(adjusted_lat).split(".")[0]
|
||||||
|
lat_last_digit = lat_integer_part[-1]
|
||||||
|
lat_first_fraction_digit = str(adjusted_lat).split(".")[1][0] if len(str(adjusted_lat).split(".")) > 1 else "0"
|
||||||
|
|
||||||
|
# Extract digits from longitude
|
||||||
|
lon_integer_part = str(adjusted_lon).split(".")[0]
|
||||||
|
lon_last_digit = lon_integer_part[-1]
|
||||||
|
lon_first_fraction_digit = str(adjusted_lon).split(".")[1][0] if len(str(adjusted_lon).split(".")) > 1 else "0"
|
||||||
|
|
||||||
|
# Create the final string by combining digits
|
||||||
|
combined_string = (
|
||||||
|
lat_last_digit +
|
||||||
|
timestamp_str[-1] +
|
||||||
|
lon_last_digit +
|
||||||
|
timestamp_str[-2] +
|
||||||
|
lat_first_fraction_digit +
|
||||||
|
timestamp_str[-3] +
|
||||||
|
lon_first_fraction_digit
|
||||||
|
)
|
||||||
|
|
||||||
|
# Encode the combined string in Base64
|
||||||
|
encoded_value = base64.b64encode(combined_string.encode()).decode()
|
||||||
|
|
||||||
|
return encoded_value
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_date_string(date_string):
|
||||||
|
format_string = "%d.%m. %H:%M"
|
||||||
|
# TODO: handle turn of year
|
||||||
|
parsed_date = datetime.strptime(date_string, format_string).replace(year=datetime.now().year)
|
||||||
|
return parsed_date
|
51
arztapi/ArztPraxisDatas.py
Normal file
51
arztapi/ArztPraxisDatas.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
from typing import List, Optional
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class Sprechzeit(BaseModel):
|
||||||
|
zeit: str
|
||||||
|
freitext: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class TszDesTyps(BaseModel):
|
||||||
|
typ: str
|
||||||
|
sprechzeiten: List[Sprechzeit]
|
||||||
|
|
||||||
|
|
||||||
|
class Tsz(BaseModel):
|
||||||
|
d: str
|
||||||
|
t: str
|
||||||
|
tszDesTyps: Optional[List[TszDesTyps]] = None
|
||||||
|
|
||||||
|
|
||||||
|
class Ag(BaseModel):
|
||||||
|
key: str
|
||||||
|
value: str
|
||||||
|
|
||||||
|
|
||||||
|
class ArztPraxisData(BaseModel):
|
||||||
|
arzt: bool
|
||||||
|
id: str
|
||||||
|
web: Optional[str]
|
||||||
|
kv: str
|
||||||
|
name: str
|
||||||
|
tel: str
|
||||||
|
fax: Optional[str]
|
||||||
|
anrede: str
|
||||||
|
geschlecht: str
|
||||||
|
handy: Optional[str]
|
||||||
|
email: Optional[str] = None
|
||||||
|
|
||||||
|
distance: int
|
||||||
|
strasse: str
|
||||||
|
hausnummer: str
|
||||||
|
plz: str
|
||||||
|
ort: str
|
||||||
|
geoeffnet: str
|
||||||
|
keineSprechzeiten: bool
|
||||||
|
ag: List[Ag]
|
||||||
|
tsz: List[Tsz]
|
||||||
|
|
||||||
|
|
||||||
|
class ArztPraxisDatas(BaseModel):
|
||||||
|
arztPraxisDatas: List[ArztPraxisData]
|
23
arztapi/DoctorInformation.py
Normal file
23
arztapi/DoctorInformation.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import datetime
|
||||||
|
from typing import Optional, List
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class PhoneTime(BaseModel):
|
||||||
|
start: datetime.datetime
|
||||||
|
end: datetime.datetime
|
||||||
|
|
||||||
|
|
||||||
|
class DoctorInformation(BaseModel):
|
||||||
|
name: str
|
||||||
|
tel: str
|
||||||
|
fax: Optional[str] = None
|
||||||
|
anrede: str
|
||||||
|
email: Optional[str] = None
|
||||||
|
distance: int
|
||||||
|
strasse: str
|
||||||
|
hausnummer: str
|
||||||
|
plz: str
|
||||||
|
ort: str
|
||||||
|
telefonzeiten: List[PhoneTime]
|
9
arztapi/DoctorPhoneTime.py
Normal file
9
arztapi/DoctorPhoneTime.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
from arztapi.DoctorInformation import PhoneTime
|
||||||
|
|
||||||
|
|
||||||
|
class DoctorPhoneTime(BaseModel):
|
||||||
|
phone_time: PhoneTime
|
||||||
|
doctor_name: str
|
||||||
|
doctor_address: str
|
||||||
|
doctor_phone_number: str
|
28
forms/SearchForm.py
Normal file
28
forms/SearchForm.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
from wtforms import Form, StringField, validators
|
||||||
|
from wtforms.fields.choices import SelectMultipleField, RadioField
|
||||||
|
from wtforms.widgets.core import CheckboxInput, ListWidget
|
||||||
|
|
||||||
|
|
||||||
|
def validate_therapy_types(form, field):
|
||||||
|
valid_choices = {"A", "S", "T", "V"}
|
||||||
|
if not all(choice in valid_choices for choice in field.data):
|
||||||
|
raise validators.ValidationError("Invalid value in therapy types selection.")
|
||||||
|
|
||||||
|
|
||||||
|
class SearchForm(Form):
|
||||||
|
location = StringField("Standort", validators=[validators.InputRequired(), validators.Length(min=2, max=50),
|
||||||
|
validators.Regexp("^[a-zA-ZäöüßÄÖÜẞ]+$")])
|
||||||
|
therapy_type = SelectMultipleField("Therapieverfahren", choices=[
|
||||||
|
("A", "Analytische Psychotherapie"),
|
||||||
|
("S", "Systemische Therapie"),
|
||||||
|
("T", "Tiefenpsychologisch fundierte Psychotherapie"),
|
||||||
|
("V", "Verhaltenstherapie")], option_widget=CheckboxInput(), widget=ListWidget(prefix_label=False),
|
||||||
|
validators=[validate_therapy_types])
|
||||||
|
therapy_age_range = RadioField("Altersgruppe", choices=[
|
||||||
|
("E", "Erwachsene"),
|
||||||
|
("K", "Kinder und Jugendliche")], validators=[validators.InputRequired(), validators.any_of(["E", "K"])])
|
||||||
|
therapy_setting = RadioField("Therapiesetting", choices=[
|
||||||
|
("E", "Einzeltherapie"),
|
||||||
|
("G", "Gruppentherapie")], validators=[validators.InputRequired(), validators.any_of(["E", "G"])])
|
||||||
|
|
||||||
|
|
12
static/css/bootstrap.min.css
vendored
Normal file
12
static/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
38
templates/base.html
Normal file
38
templates/base.html
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<title>{% block title %}{% endblock %} - TheraPy jetzt</title>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<nav class="navbar navbar-expand-lg bg-primary" data-bs-theme="dark">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="#">TheraPy</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarColor01" aria-controls="navbarColor01" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarColor01">
|
||||||
|
<ul class="navbar-nav me-auto">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" href="#">Home
|
||||||
|
<span class="visually-hidden">(current)</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="#">About</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<section class="content">
|
||||||
|
<header>
|
||||||
|
{% block header %}{% endblock %}
|
||||||
|
</header>
|
||||||
|
{% for message in get_flashed_messages() %}
|
||||||
|
<div class="flash">{{ message }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</body>
|
85
templates/index.html
Normal file
85
templates/index.html
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-8 col-md-7 col-sm-6">
|
||||||
|
<form method="POST" action="/search">
|
||||||
|
{{ form.csrf_token }}
|
||||||
|
<fieldset>
|
||||||
|
<legend>Suche</legend>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="locationInput" class="form-label mt-4">{{ form.location.label }}</label>
|
||||||
|
{{ form.location(class="form-control", id="locationInput", placeholder="Standort eingeben") }}
|
||||||
|
{% if form.location.errors %}
|
||||||
|
<div class="text-danger">
|
||||||
|
{{ form.location.errors[0] }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<fieldset>
|
||||||
|
<legend class="mt-4">{{ form.therapy_type.label }}</legend>
|
||||||
|
{% for subfield in form.therapy_type %}
|
||||||
|
<div class="form-check">
|
||||||
|
{{ subfield(class="form-check-input") }}
|
||||||
|
<label class="form-check-label" for="{{ subfield.id }}">
|
||||||
|
{{ subfield.label.text }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% if form.therapy_type.errors %}
|
||||||
|
<div class="text-danger">
|
||||||
|
{{ form.therapy_type.errors[0] }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<fieldset>
|
||||||
|
<legend class="mt-4">{{ form.therapy_age_range.label }}</legend>
|
||||||
|
{% for subfield in form.therapy_age_range %}
|
||||||
|
<div class="form-check">
|
||||||
|
{{ subfield(class="form-check-input") }}
|
||||||
|
<label class="form-check-label" for="{{ subfield.id }}">
|
||||||
|
{{ subfield.label.text }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% if form.therapy_age_range.errors %}
|
||||||
|
<div class="text-danger">
|
||||||
|
{{ form.therapy_age_range.errors[0] }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<fieldset>
|
||||||
|
<legend class="mt-4">{{ form.therapy_setting.label }}</legend>
|
||||||
|
{% for subfield in form.therapy_setting %}
|
||||||
|
<div class="form-check">
|
||||||
|
{{ subfield(class="form-check-input") }}
|
||||||
|
<label class="form-check-label" for="{{ subfield.id }}">
|
||||||
|
{{ subfield.label.text }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% if form.therapy_setting.errors %}
|
||||||
|
<div class="text-danger">
|
||||||
|
{{ form.therapy_setting.errors[0] }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary">Suche</button>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
34
templates/result.html
Normal file
34
templates/result.html
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-8 col-md-7 col-sm-6">
|
||||||
|
<h2>Suchergebnisse</h2>
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Name</th>
|
||||||
|
<th scope="col">Nächster Telefontag</th>
|
||||||
|
<th scope="col">Nächste Telefonzeit</th>
|
||||||
|
<th scope="col">Telefonnummer</th>
|
||||||
|
<th scope="col">Adresse</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for doctor in doctors %}
|
||||||
|
<tr class="table-secondary">
|
||||||
|
<th scope="row">{{ doctor.doctor_name }}</th>
|
||||||
|
<td>{{ doctor.phone_time.start.strftime("%A %d.%m") }}</td>
|
||||||
|
<td>{{ doctor.phone_time.start.strftime("%H:%M") }} bis {{ doctor.phone_time.end.strftime("%H:%M") }}</td>
|
||||||
|
<td>{{ doctor.doctor_phone_number }}</td>
|
||||||
|
<td>{{ doctor.doctor_address }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
Loading…
Reference in New Issue
Block a user