Refactored Logbook

pull/78/head
Konstantin Gründger 2020-11-15 18:27:54 +01:00
rodzic 2f577c8c29
commit 03cfd6136a
7 zmienionych plików z 231 dodań i 117 usunięć

Wyświetl plik

@ -1,9 +1,10 @@
from sqlalchemy import and_, or_, insert, update, exists, between from sqlalchemy import and_, or_, insert, update, exists, between
from sqlalchemy.sql import func, null from sqlalchemy.sql import func, null
from sqlalchemy.sql.expression import case, true, false from sqlalchemy.sql.expression import case, true, false
from sqlalchemy.dialects.postgresql import insert # special insert for upsert ("ON CONFLICT ...")
from flask import current_app from flask import current_app
from app.model import Airport, SenderPosition, Sender, TakeoffLanding, Logbook from app.model import Airport, Country, SenderPosition, Sender, TakeoffLanding, Logbook
from app.utils import date_to_timestamps from app.utils import date_to_timestamps
from datetime import datetime, timedelta from datetime import datetime, timedelta
@ -39,12 +40,6 @@ def update_takeoff_landings(start, end):
current_app.logger.warn(abort_message) current_app.logger.warn(abort_message)
return abort_message return abort_message
# delete existing elements
db.session.query(TakeoffLanding) \
.filter(between(TakeoffLanding.timestamp, start, end))\
.delete(synchronize_session='fetch')
db.session.commit()
# get beacons for selected time range (+ buffer for duration), one per name and timestamp # get beacons for selected time range (+ buffer for duration), one per name and timestamp
sq = ( sq = (
db.session.query(SenderPosition.name, SenderPosition.timestamp, SenderPosition.location, SenderPosition.track, db.func.coalesce(SenderPosition.ground_speed, 0.0).label("ground_speed"), SenderPosition.altitude, db.func.coalesce(SenderPosition.climb_rate, 0.0).label("climb_rate")) db.session.query(SenderPosition.name, SenderPosition.timestamp, SenderPosition.location, SenderPosition.track, db.func.coalesce(SenderPosition.ground_speed, 0.0).label("ground_speed"), SenderPosition.altitude, db.func.coalesce(SenderPosition.climb_rate, 0.0).label("climb_rate"))
@ -83,11 +78,11 @@ def update_takeoff_landings(start, end):
# consider only positions between start and end and with predecessor and successor and limit distance and duration between points # consider only positions between start and end and with predecessor and successor and limit distance and duration between points
sq3 = ( sq3 = (
db.session.query(sq2) db.session.query(sq2)
.filter(and_(sq2.c.name_prev != null(), sq2.c.name_next != null())) .filter(and_(sq2.c.name_prev != null(), sq2.c.name_next != null()))
.filter(and_(func.ST_DistanceSphere(sq2.c.location, sq2.c.location_wkt_prev) < MAX_EVENT_RADIUS, func.ST_DistanceSphere(sq2.c.location, sq2.c.location_wkt_next) < MAX_EVENT_RADIUS)) .filter(and_(func.ST_DistanceSphere(sq2.c.location, sq2.c.location_wkt_prev) < MAX_EVENT_RADIUS, func.ST_DistanceSphere(sq2.c.location, sq2.c.location_wkt_next) < MAX_EVENT_RADIUS))
.filter(sq2.c.timestamp_next - sq2.c.timestamp_prev < timedelta(seconds=MAX_EVENT_DURATION)) .filter(sq2.c.timestamp_next - sq2.c.timestamp_prev < timedelta(seconds=MAX_EVENT_DURATION))
.filter(between(sq2.c.timestamp, start, end)) .filter(between(sq2.c.timestamp, start, end))
.subquery() .subquery()
) )
# find possible takeoffs and landings # find possible takeoffs and landings
@ -129,15 +124,25 @@ def update_takeoff_landings(start, end):
) )
# ... and take the nearest airport # ... and take the nearest airport
takeoff_landing_query = ( sq6 = (
db.session.query(sq5.c.timestamp, sq5.c.track, sq5.c.is_takeoff, sq5.c.device_id, sq5.c.airport_id) db.session.query(sq5.c.timestamp, sq5.c.track, sq5.c.is_takeoff, sq5.c.device_id, sq5.c.airport_id)
.distinct(sq5.c.timestamp, sq5.c.track, sq5.c.is_takeoff, sq5.c.device_id) .distinct(sq5.c.timestamp, sq5.c.track, sq5.c.is_takeoff, sq5.c.device_id)
.order_by(sq5.c.timestamp, sq5.c.track, sq5.c.is_takeoff, sq5.c.device_id, sq5.c.airport_distance) .order_by(sq5.c.timestamp, sq5.c.track, sq5.c.is_takeoff, sq5.c.device_id, sq5.c.airport_distance)
.subquery() .subquery()
) )
# ... add the country
takeoff_landing_query = (
db.session.query(sq6.c.timestamp, sq6.c.track, sq6.c.is_takeoff, sq6.c.device_id, sq6.c.airport_id, Country.gid)
.join(Airport, sq6.c.airport_id==Airport.id)
.join(Country, Airport.country_code==Country.iso2, isouter=True)
.subquery()
)
# ... and save them # ... and save them
ins = insert(TakeoffLanding).from_select((TakeoffLanding.timestamp, TakeoffLanding.track, TakeoffLanding.is_takeoff, TakeoffLanding.sender_id, TakeoffLanding.airport_id), takeoff_landing_query) ins = insert(TakeoffLanding) \
.from_select((TakeoffLanding.timestamp, TakeoffLanding.track, TakeoffLanding.is_takeoff, TakeoffLanding.sender_id, TakeoffLanding.airport_id, TakeoffLanding.country_id), takeoff_landing_query) \
.on_conflict_do_nothing(index_elements=[TakeoffLanding.timestamp, TakeoffLanding.sender_id, TakeoffLanding.airport_id])
result = db.session.execute(ins) result = db.session.execute(ins)
db.session.commit() db.session.commit()
@ -148,48 +153,92 @@ def update_takeoff_landings(start, end):
return finish_message return finish_message
if __name__ == '__main__': def update_logbook(offset_days=None):
from app import create_app
app = create_app()
with app.app_context():
result = update_takeoff_landings(start=datetime(2020, 11, 9, 10, 0, 0), end=datetime(2020, 11, 9, 10, 10, 0))
print(result)
def update_logbook(offset_days):
"""Add/update logbook entries.""" """Add/update logbook entries."""
current_app.logger.info("Compute logbook.") current_app.logger.info("Compute logbook.")
# limit time range to given date and set window partition and window order # limit time range to given date and set window partition and window order
(start, end) = date_to_timestamps(datetime.utcnow()-timedelta(days=offset_days)) if offset_days:
(start, end) = date_to_timestamps(datetime.utcnow()-timedelta(days=offset_days))
else:
(start, end) = date_to_timestamps(datetime.utcnow().date())
pa = TakeoffLanding.sender_id pa = TakeoffLanding.sender_id
wo = and_(TakeoffLanding.sender_id, TakeoffLanding.timestamp, TakeoffLanding.airport_id) wo = and_(TakeoffLanding.sender_id, TakeoffLanding.timestamp, TakeoffLanding.airport_id)
# delete existing elements # make a query with previous, current and next "takeoff_landing" event, so we can find complete flights
db.session.query(Logbook)\
.filter(between(Logbook.reference, start, end))\
.delete(synchronize_session='fetch')
db.session.commit()
# make a query with current and next "takeoff_landing" event, so we can find complete flights
sq = ( sq = (
db.session.query( db.session.query(
TakeoffLanding.sender_id, TakeoffLanding.sender_id,
func.lag(TakeoffLanding.sender_id).over(partition_by=pa, order_by=wo).label("sender_id_prev"),
func.lead(TakeoffLanding.sender_id).over(partition_by=pa, order_by=wo).label("sender_id_next"), func.lead(TakeoffLanding.sender_id).over(partition_by=pa, order_by=wo).label("sender_id_next"),
TakeoffLanding.timestamp, TakeoffLanding.timestamp,
func.lag(TakeoffLanding.timestamp).over(partition_by=pa, order_by=wo).label("timestamp_prev"),
func.lead(TakeoffLanding.timestamp).over(partition_by=pa, order_by=wo).label("timestamp_next"), func.lead(TakeoffLanding.timestamp).over(partition_by=pa, order_by=wo).label("timestamp_next"),
TakeoffLanding.track, TakeoffLanding.track,
func.lag(TakeoffLanding.track).over(partition_by=pa, order_by=wo).label("track_prev"),
func.lead(TakeoffLanding.track).over(partition_by=pa, order_by=wo).label("track_next"), func.lead(TakeoffLanding.track).over(partition_by=pa, order_by=wo).label("track_next"),
TakeoffLanding.is_takeoff, TakeoffLanding.is_takeoff,
func.lag(TakeoffLanding.is_takeoff).over(partition_by=pa, order_by=wo).label("is_takeoff_prev"),
func.lead(TakeoffLanding.is_takeoff).over(partition_by=pa, order_by=wo).label("is_takeoff_next"), func.lead(TakeoffLanding.is_takeoff).over(partition_by=pa, order_by=wo).label("is_takeoff_next"),
TakeoffLanding.airport_id, TakeoffLanding.airport_id,
func.lag(TakeoffLanding.airport_id).over(partition_by=pa, order_by=wo).label("airport_id_prev"),
func.lead(TakeoffLanding.airport_id).over(partition_by=pa, order_by=wo).label("airport_id_next") func.lead(TakeoffLanding.airport_id).over(partition_by=pa, order_by=wo).label("airport_id_next")
) )
.filter(between(TakeoffLanding.timestamp, start, end)) #.filter(between(TakeoffLanding.timestamp, start, end))
.subquery() .subquery()
) )
# find (new) starts without landing
only_starts_query = (
db.session.query(
sq.c.sender_id.label("sender_id"),
sq.c.timestamp.label("takeoff_timestamp"),
sq.c.track.label("takeoff_track"),
sq.c.airport_id.label("takeoff_airport_id")
)
.filter(sq.c.is_takeoff == true())
.filter(or_(sq.c.is_takeoff_next == true(), sq.c.is_takeoff_next == null()))
.filter(~Logbook.query.filter(db.and_(Logbook.sender_id==sq.c.sender_id, Logbook.takeoff_timestamp==sq.c.timestamp, Logbook.takeoff_airport_id==sq.c.airport_id)).exists())
)
ins = insert(Logbook).from_select(
(
Logbook.sender_id,
Logbook.takeoff_timestamp,
Logbook.takeoff_track,
Logbook.takeoff_airport_id
),
only_starts_query,
)
result = db.session.execute(ins)
current_app.logger.debug(f"Added {result.rowcount} starts")
db.session.commit()
# find (new) landings without start
only_landings_query = (
db.session.query(
sq.c.sender_id.label("sender_id"),
sq.c.timestamp.label("landing_timestamp"),
sq.c.track.label("landing_track"),
sq.c.airport_id.label("landing_airport_id"),
)
.filter(or_(sq.c.is_takeoff_prev == false(), sq.c.is_takeoff_prev == null()))
.filter(sq.c.is_takeoff == false())
.filter(~Logbook.query.filter(db.and_(Logbook.sender_id==sq.c.sender_id, Logbook.landing_timestamp==sq.c.timestamp, Logbook.landing_airport_id==sq.c.airport_id)).exists())
)
ins = insert(Logbook).from_select(
(
Logbook.sender_id,
Logbook.landing_timestamp,
Logbook.landing_track,
Logbook.landing_airport_id
),
only_landings_query,
)
result = db.session.execute(ins)
current_app.logger.debug(f"Added {result.rowcount} landings")
db.session.commit()
# find complete flights # find complete flights
complete_flight_query = ( complete_flight_query = (
db.session.query( db.session.query(
@ -203,42 +252,15 @@ def update_logbook(offset_days):
) )
.filter(sq.c.is_takeoff == true()) .filter(sq.c.is_takeoff == true())
.filter(sq.c.is_takeoff_next == false()) .filter(sq.c.is_takeoff_next == false())
.subquery()
) )
# find landings without start # insert (new) flights
only_landings_query = ( new_flights_query = (
db.session.query( db.session.query(complete_flight_query) \
sq.c.sender_id_next.label("sender_id"), .filter(~Logbook.query.filter(db.and_(Logbook.sender_id==complete_flight_query.c.sender_id, Logbook.landing_timestamp==complete_flight_query.c.landing_timestamp, Logbook.landing_airport_id==complete_flight_query.c.landing_airport_id)).exists())
null().label("takeoff_timestamp"), .filter(~Logbook.query.filter(db.and_(Logbook.sender_id==complete_flight_query.c.sender_id, Logbook.takeoff_timestamp==complete_flight_query.c.takeoff_timestamp, Logbook.takeoff_airport_id==complete_flight_query.c.takeoff_airport_id)).exists())
null().label("takeoff_track"),
null().label("takeoff_airport_id"),
sq.c.timestamp_next.label("landing_timestamp"),
sq.c.track_next.label("landing_track"),
sq.c.airport_id_next.label("landing_airport_id"),
)
.filter(or_(sq.c.is_takeoff == false(), sq.c.is_takeoff == null()))
.filter(sq.c.is_takeoff_next == false())
) )
# find starts without landing
only_starts_query = (
db.session.query(
sq.c.sender_id.label("sender_id"),
sq.c.timestamp.label("takeoff_timestamp"),
sq.c.track.label("takeoff_track"),
sq.c.airport_id.label("takeoff_airport_id"),
null().label("landing_timestamp"),
null().label("landing_track"),
null().label("landing_airport_id"),
)
.filter(sq.c.is_takeoff == true())
.filter(or_(sq.c.is_takeoff_next == true(), sq.c.is_takeoff_next == null()))
)
# unite all computated flights
logbook_entries = complete_flight_query.union(only_landings_query, only_starts_query).subquery()
# ... insert them into logbook
ins = insert(Logbook).from_select( ins = insert(Logbook).from_select(
( (
Logbook.sender_id, Logbook.sender_id,
@ -247,47 +269,121 @@ def update_logbook(offset_days):
Logbook.takeoff_airport_id, Logbook.takeoff_airport_id,
Logbook.landing_timestamp, Logbook.landing_timestamp,
Logbook.landing_track, Logbook.landing_track,
Logbook.landing_airport_id, Logbook.landing_airport_id
), ),
logbook_entries, new_flights_query
) )
result = db.session.execute(ins) result = db.session.execute(ins)
insert_counter = result.rowcount current_app.logger.debug(f"Added {result.rowcount} complete flights")
db.session.commit() db.session.commit()
finish_message = "Logbook: {} inserted".format(insert_counter) # update existing landing with takeoff from complete flight
return finish_message upd = update(Logbook) \
.where(db.and_(
Logbook.sender_id==complete_flight_query.c.sender_id,
Logbook.takeoff_timestamp==null(),
Logbook.takeoff_airport_id==null(),
Logbook.landing_timestamp!=null(),
Logbook.landing_timestamp==complete_flight_query.c.landing_timestamp,
Logbook.landing_airport_id==complete_flight_query.c.landing_airport_id
)) \
.values(takeoff_timestamp=complete_flight_query.c.takeoff_timestamp,
takeoff_airport_id=complete_flight_query.c.takeoff_airport_id
)
result = db.session.execute(upd)
current_app.logger.debug(f"Updated {result.rowcount} takeoffs to complete flights")
db.session.commit()
# update existing takeoff with landing from complete flight
upd = update(Logbook) \
.where(db.and_(
Logbook.sender_id==complete_flight_query.c.sender_id,
Logbook.takeoff_timestamp!=null(),
Logbook.takeoff_timestamp==complete_flight_query.c.takeoff_timestamp,
Logbook.takeoff_airport_id==complete_flight_query.c.takeoff_airport_id,
Logbook.landing_timestamp==null(),
Logbook.landing_airport_id==null()
)) \
.values(landing_timestamp=complete_flight_query.c.landing_timestamp,
landing_airport_id=complete_flight_query.c.landing_airport_id
)
result = db.session.execute(upd)
current_app.logger.debug(f"Updated {result.rowcount} landings to complete flights")
db.session.commit()
return
def update_max_altitudes(date, logger=None):
def update_max_altitudes(offset_days=100):
MAX_UPDATES = 100
query = f"""
UPDATE logbooks AS l
SET max_altitude = sq2.max_altitude
FROM (
SELECT
sq.logbook_id,
MAX(sp.altitude) AS max_altitude
FROM
sender_positions AS sp,
(
SELECT
l.id AS logbook_id, s.name, l.takeoff_timestamp, l.landing_timestamp
FROM logbooks AS l
INNER JOIN senders AS s ON l.sender_id = s.id
WHERE
l.takeoff_timestamp IS NOT NULL and l.landing_timestamp IS NOT NULL
AND l.max_altitude IS NULL
ORDER BY l.takeoff_timestamp
LIMIT {MAX_UPDATES}
) AS sq
WHERE sq.name = sp.name AND sp.timestamp BETWEEN sq.takeoff_timestamp AND sq.landing_timestamp
GROUP BY sq.logbook_id
) AS sq2
WHERE l.id = sq2.logbook_id;
"""
result = db.session.execute(query)
db.session.commit()
return result.rowcount
def update_max_altitudes_orm(offset_days):
"""Add max altitudes in logbook when flight is complete (takeoff and landing).""" """Add max altitudes in logbook when flight is complete (takeoff and landing)."""
if logger is None:
logger = current_app.logger
current_app.logger.info("Update logbook max altitude.") current_app.logger.info("Update logbook max altitude.")
(start, end) = date_to_timestamps(date) (start, end) = date_to_timestamps(datetime.today() - timedelta(days=offset_days))
logbook_entries = ( logbook_entries = (
db.query(Logbook.id) db.session.query(Logbook.id, Sender.name)
.filter(and_(Logbook.takeoff_timestamp != null(), Logbook.landing_timestamp != null(), Logbook.max_altitude == null())) .filter(and_(Logbook.takeoff_timestamp != null(), Logbook.landing_timestamp != null(), Logbook.max_altitude == null()))
.filter(between(Logbook.reference, start, end)) #.filter(between(Logbook.reference, start, end))
.subquery() .filter(Logbook.sender_id == Sender.id)
.limit(10)
.subquery()
) )
max_altitudes = ( max_altitudes = (
db.query(Logbook.id, func.max(SenderPosition.altitude).label("max_altitude")) db.session.query(Logbook.id, func.max(SenderPosition.altitude).label("max_altitude"))
.filter(Logbook.id == logbook_entries.c.id) .filter(and_(SenderPosition.name == logbook_entries.c.name, SenderPosition.timestamp >= Logbook.takeoff_timestamp, SenderPosition.timestamp <= Logbook.landing_timestamp))
.filter(and_(SenderPosition.address == Logbook.address, SenderPosition.timestamp >= Logbook.takeoff_timestamp, SenderPosition.timestamp <= Logbook.landing_timestamp)) .filter(Logbook.id == logbook_entries.c.id)
.group_by(Logbook.id) .group_by(Logbook.id)
.subquery() .subquery()
) )
update_logbook = db.query(Logbook).filter(Logbook.id == max_altitudes.c.id).update({Logbook.max_altitude: max_altitudes.c.max_altitude}, synchronize_session="fetch") update_logbooks = db.session.query(Logbook).filter(Logbook.id == max_altitudes.c.id).update({Logbook.max_altitude: max_altitudes.c.max_altitude}, synchronize_session="fetch")
db.session.commit() db.session.commit()
finish_message = "Logbook (altitude): {} entries updated.".format(update_logbook) finish_message = "Logbook (altitude): {} entries updated.".format(update_logbooks)
return finish_message return finish_message
if __name__ == '__main__':
from app import create_app
app = create_app()
with app.app_context():
#result = update_takeoff_landings(start=datetime(2020, 11, 9, 10, 0, 0), end=datetime(2020, 11, 9, 15, 30, 0))
result = update_logbook(0)
print(result)

Wyświetl plik

@ -17,26 +17,26 @@ def get_countries_in_receivers():
return [{"iso2": country[0]} for country in query.all()] return [{"iso2": country[0]} for country in query.all()]
@cache.cached(key_prefix="countries_in_logbook") @cache.cached(key_prefix="countries_in_takeoff_landings")
def get_used_countries(): def get_used_countries():
query = db.session.query(Country.iso2).filter(Country.iso2 == Airport.country_code).filter(Logbook.takeoff_airport_id == Airport.id).order_by(Country.iso2).distinct(Country.iso2) query = db.session.query(Country.iso2).filter(Country.gid == TakeoffLanding.country_id).order_by(Country.iso2).distinct(Country.iso2)
return [{"iso2": country[0]} for country in query.all()] return [{"iso2": country[0]} for country in query.all()]
@cache.memoize() @cache.memoize()
def get_used_airports_by_country(sel_country): def get_used_airports_by_country(sel_country):
query = db.session.query(Airport).filter(Airport.country_code == sel_country).filter(Logbook.takeoff_airport_id == Airport.id).order_by(Airport.name).distinct(Airport.name) query = db.session.query(Airport).filter(Airport.country_code == sel_country).filter(TakeoffLanding.airport_id==Airport.id).filter(TakeoffLanding.country_id == Country.gid).order_by(Airport.name).distinct(Airport.name)
return [used_airport for used_airport in query] return [used_airport for used_airport in query]
@cache.memoize() @cache.memoize()
def get_dates_for_airport(sel_airport): def get_dates_for_airport(sel_airport):
query = ( query = (
db.session.query(db.func.date(Logbook.reference), db.func.count(Logbook.id).label("logbook_count")) db.session.query(db.func.date(Logbook.reference_timestamp), db.func.count(Logbook.id).label("logbook_count"))
.filter(Airport.id == sel_airport) .filter(Airport.id == sel_airport)
.filter(db.or_(Airport.id == Logbook.takeoff_airport_id, Airport.id == Logbook.landing_airport_id)) .filter(db.or_(Airport.id == Logbook.takeoff_airport_id, Airport.id == Logbook.landing_airport_id))
.group_by(db.func.date(Logbook.reference)) .group_by(db.func.date(Logbook.reference_timestamp))
.order_by(db.func.date(Logbook.reference).desc()) .order_by(db.func.date(Logbook.reference_timestamp).desc())
) )
return [{"date": date, "logbook_count": logbook_count} for (date, logbook_count) in query.all()] return [{"date": date, "logbook_count": logbook_count} for (date, logbook_count) in query.all()]
@ -54,7 +54,7 @@ def index():
sender_positions_today = db.session.query(db.func.sum(ReceiverStatistic.messages_count)).filter(ReceiverStatistic.date==date.today()).one()[0] sender_positions_today = db.session.query(db.func.sum(ReceiverStatistic.messages_count)).filter(ReceiverStatistic.date==date.today()).one()[0]
sender_positions_total = db.session.query(db.func.sum(ReceiverStatistic.messages_count)).one()[0] sender_positions_total = db.session.query(db.func.sum(ReceiverStatistic.messages_count)).one()[0]
last_logbook_entries = db.session.query(Logbook).order_by(Logbook.reference.desc()).limit(10) last_logbook_entries = db.session.query(Logbook).order_by(Logbook.reference_timestamp.desc()).limit(10)
return render_template("index.html", return render_template("index.html",
senders_today=senders_today, senders_today=senders_today,
receivers_today=receivers_today, receivers_today=receivers_today,
@ -150,8 +150,8 @@ def airport_detail():
return render_template("airport_detail.html", title="Airport Detail", airport=airport.one(), senders=senders) return render_template("airport_detail.html", title="Airport Detail", airport=airport.one(), senders=senders)
@bp.route("/logbook.html", methods=["GET", "POST"]) @bp.route("/logbooks.html", methods=["GET", "POST"])
def logbook(): def logbooks():
sel_country = request.args.get("country") sel_country = request.args.get("country")
sel_airport_id = request.args.get("airport_id") sel_airport_id = request.args.get("airport_id")
sel_date = request.args.get("date") sel_date = request.args.get("date")
@ -187,17 +187,17 @@ def logbook():
filters.append(db.or_(Logbook.takeoff_airport_id == sel_airport_id, Logbook.landing_airport_id == sel_airport_id)) filters.append(db.or_(Logbook.takeoff_airport_id == sel_airport_id, Logbook.landing_airport_id == sel_airport_id))
if sel_date: if sel_date:
filters.append(db.func.date(Logbook.reference) == sel_date) filters.append(db.func.date(Logbook.reference_timestamp) == sel_date)
if sel_sender_id: if sel_sender_id:
filters.append(Logbook.sender_id == sel_sender_id) filters.append(Logbook.sender_id == sel_sender_id)
if len(filters) > 0: if len(filters) > 0:
logbook = db.session.query(Logbook).filter(*filters).order_by(Logbook.reference) logbooks = db.session.query(Logbook).filter(*filters).order_by(Logbook.reference_timestamp).limit(100)
else: else:
logbook = None logbooks = None
return render_template("logbook.html", title="Logbook", sel_country=sel_country, countries=countries, sel_airport_id=sel_airport_id, airports=airports, sel_date=sel_date, dates=dates, logbook=logbook) return render_template("logbooks.html", title="Logbook", sel_country=sel_country, countries=countries, sel_airport_id=sel_airport_id, airports=airports, sel_date=sel_date, dates=dates, logbooks=logbooks)
@bp.route("/download.html") @bp.route("/download.html")

Wyświetl plik

@ -1,12 +1,12 @@
from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.sql import null, case from sqlalchemy.sql import null, case
from sqlalchemy.orm import backref from sqlalchemy.orm import backref
from app import db from app import db
from app.model import Sender
class Logbook(db.Model): class Logbook(db.Model):
__tablename__ = "logbook" __tablename__ = "logbooks"
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
@ -18,14 +18,19 @@ class Logbook(db.Model):
# Relations # Relations
sender_id = db.Column(db.Integer, db.ForeignKey("senders.id", ondelete="CASCADE"), index=True) sender_id = db.Column(db.Integer, db.ForeignKey("senders.id", ondelete="CASCADE"), index=True)
#sender = db.relationship("Sender", foreign_keys=[sender_id], backref=backref("logbook_entries", order_by=reference.desc()) # TODO: does not work...
sender = db.relationship("Sender", foreign_keys=[sender_id], backref=backref("logbook_entries", order_by=case({True: takeoff_timestamp, False: landing_timestamp}, takeoff_timestamp != null()).desc())) sender = db.relationship("Sender", foreign_keys=[sender_id], backref=backref("logbook_entries", order_by=case({True: takeoff_timestamp, False: landing_timestamp}, takeoff_timestamp != null()).desc()))
takeoff_airport_id = db.Column(db.Integer, db.ForeignKey("airports.id", ondelete="CASCADE"), index=True) takeoff_airport_id = db.Column(db.Integer, db.ForeignKey("airports.id", ondelete="CASCADE"), index=True)
takeoff_airport = db.relationship("Airport", foreign_keys=[takeoff_airport_id]) takeoff_airport = db.relationship("Airport", foreign_keys=[takeoff_airport_id], backref=backref("logbook_entries_takeoff", order_by=case({True: takeoff_timestamp, False: landing_timestamp}, takeoff_timestamp != null()).desc()))
takeoff_country_id = db.Column(db.Integer, db.ForeignKey("countries.gid", ondelete="CASCADE"), index=True)
takeoff_country = db.relationship("Country", foreign_keys=[takeoff_country_id], backref=backref("logbook_entries_takeoff", order_by=case({True: takeoff_timestamp, False: landing_timestamp}, takeoff_timestamp != null()).desc()))
landing_airport_id = db.Column(db.Integer, db.ForeignKey("airports.id", ondelete="CASCADE"), index=True) landing_airport_id = db.Column(db.Integer, db.ForeignKey("airports.id", ondelete="CASCADE"), index=True)
landing_airport = db.relationship("Airport", foreign_keys=[landing_airport_id]) landing_airport = db.relationship("Airport", foreign_keys=[landing_airport_id], backref=backref("logbook_entries_landing", order_by=case({True: takeoff_timestamp, False: landing_timestamp}, takeoff_timestamp != null()).desc()))
landing_country_id = db.Column(db.Integer, db.ForeignKey("countries.gid", ondelete="CASCADE"), index=True)
landing_country = db.relationship("Country", foreign_keys=[landing_country_id], backref=backref("logbook_entries_landing", order_by=case({True: takeoff_timestamp, False: landing_timestamp}, takeoff_timestamp != null()).desc()))
@hybrid_property @hybrid_property
def duration(self): def duration(self):
@ -36,9 +41,19 @@ class Logbook(db.Model):
return case({False: None, True: cls.landing_timestamp - cls.takeoff_timestamp}, cls.landing_timestamp != null() and cls.takeoff_timestamp != null()) return case({False: None, True: cls.landing_timestamp - cls.takeoff_timestamp}, cls.landing_timestamp != null() and cls.takeoff_timestamp != null())
@hybrid_property @hybrid_property
def reference(self): def reference_timestamp(self):
return self.takeoff_timestamp if self.takeoff_timestamp is not None else self.landing_timestamp return self.takeoff_timestamp if self.takeoff_timestamp is not None else self.landing_timestamp
@reference.expression @reference_timestamp.expression
def reference(cls): def reference_timestamp(cls):
return case({True: cls.takeoff_timestamp, False: cls.landing_timestamp}, cls.takeoff_timestamp != null()) return case({True: cls.takeoff_timestamp, False: cls.landing_timestamp}, cls.takeoff_timestamp != null())
#__table_args__ = (db.Index('idx_logbook_reference_timestamp', case({True: takeoff_timestamp, False: landing_timestamp}, takeoff_timestamp != null())),)
# FIXME: does not work...
# FIXME: this does not throw an error as the __table_args__ above, but there is no index created
#_wrapped_case = f"({case(whens={True: Logbook.takeoff_timestamp, False: Logbook.landing_timestamp}, value=Logbook.takeoff_timestamp != null())})"
#Index("idx_logbook_reference_timestamp", _wrapped_case)
# TODO:
# so execute manually: CREATE INDEX IF NOT EXISTS idx_logbook_reference_timestamp ON logbooks ((CASE takeoff_timestamp IS NULL WHEN true THEN takeoff_timestamp WHEN false THEN landing_timestamp END));

Wyświetl plik

@ -18,4 +18,7 @@ class TakeoffLanding(db.Model):
airport_id = db.Column(db.Integer, db.ForeignKey("airports.id", ondelete="SET NULL")) airport_id = db.Column(db.Integer, db.ForeignKey("airports.id", ondelete="SET NULL"))
airport = db.relationship("Airport", foreign_keys=[airport_id], backref="takeoff_landings") airport = db.relationship("Airport", foreign_keys=[airport_id], backref="takeoff_landings")
country_id = db.Column(db.Integer, db.ForeignKey("countries.gid", ondelete="CASCADE"), index=True)
country = db.relationship("Country", foreign_keys=[country_id], backref="takeoff_landings")
__table_args__ = (Index('idx_takeoff_landings_uc', 'timestamp', 'sender_id', 'airport_id', unique=True), ) __table_args__ = (Index('idx_takeoff_landings_uc', 'timestamp', 'sender_id', 'airport_id', unique=True), )

Wyświetl plik

@ -5,7 +5,7 @@
{% endblock %} {% endblock %}
{% block navbar %} {% block navbar %}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<nav class="navbar navbar-default"> <nav class="navbar navbar-default">
<div class="container"> <div class="container">
<div class="navbar-header"> <div class="navbar-header">
@ -23,7 +23,7 @@
<li><a href="{{ url_for('main.senders') }}">Senders</a></li> <li><a href="{{ url_for('main.senders') }}">Senders</a></li>
<li><a href="{{ url_for('main.receivers') }}">Receivers</a></li> <li><a href="{{ url_for('main.receivers') }}">Receivers</a></li>
<li><a href="{{ url_for('main.airports') }}">Airports</a></li> <li><a href="{{ url_for('main.airports') }}">Airports</a></li>
<li><a href="{{ url_for('main.logbook') }}">Logbook</a></li> <li><a href="{{ url_for('main.logbooks') }}">Logbook</a></li>
<li><a href="{{ url_for('main.sender_ranking') }}">Sender Ranking</a></li> <li><a href="{{ url_for('main.sender_ranking') }}">Sender Ranking</a></li>
<li><a href="{{ url_for('main.receiver_ranking') }}">Receiver Ranking</a></li> <li><a href="{{ url_for('main.receiver_ranking') }}">Receiver Ranking</a></li>
</ul> </ul>

Wyświetl plik

@ -60,8 +60,8 @@
<tr> <tr>
<td>{{ loop.index }}</td> <td>{{ loop.index }}</td>
<td>{% if ns.mydate != entry.reference.strftime('%Y-%m-%d') %}{% set ns.mydate = entry.reference.strftime('%Y-%m-%d') %}{{ ns.mydate }}{% endif %}</td> <td>{% if ns.mydate != entry.reference.strftime('%Y-%m-%d') %}{% set ns.mydate = entry.reference.strftime('%Y-%m-%d') %}{{ ns.mydate }}{% endif %}</td>
<td>{% if entry.takeoff_airport is not none %}<a href="{{ url_for('main.logbook', country=entry.takeoff_airport.country_code, airport_id=entry.takeoff_airport.id, date=entry.reference.strftime('%Y-%m-%d')) }}">{{ entry.takeoff_airport.name }}</a>{% endif %}</td> <td>{% if entry.takeoff_airport is not none %}<a href="{{ url_for('main.logbooks', country=entry.takeoff_airport.country_code, airport_id=entry.takeoff_airport.id, date=entry.reference.strftime('%Y-%m-%d')) }}">{{ entry.takeoff_airport.name }}</a>{% endif %}</td>
<td>{% if entry.landing_airport is not none %}<a href="{{ url_for('main.logbook', country=entry.landing_airport.country_code, airport_id=entry.landing_airport.id, date=entry.reference.strftime('%Y-%m-%d')) }}">{{ entry.landing_airport.name }}</a>{% endif %}</td> <td>{% if entry.landing_airport is not none %}<a href="{{ url_for('main.logbooks', country=entry.landing_airport.country_code, airport_id=entry.landing_airport.id, date=entry.reference.strftime('%Y-%m-%d')) }}">{{ entry.landing_airport.name }}</a>{% endif %}</td>
<td>{% if entry.takeoff_timestamp is not none %} {{ entry.takeoff_timestamp.strftime('%H:%M') }} {% endif %}</td> <td>{% if entry.takeoff_timestamp is not none %} {{ entry.takeoff_timestamp.strftime('%H:%M') }} {% endif %}</td>
<td>{% if entry.landing_timestamp is not none %} {{ entry.landing_timestamp.strftime('%H:%M') }} {% endif %}</td> <td>{% if entry.landing_timestamp is not none %} {{ entry.landing_timestamp.strftime('%H:%M') }} {% endif %}</td>
<td>{% if entry.duration is not none %}{{ entry.duration }}{% endif %}</td> <td>{% if entry.duration is not none %}{{ entry.duration }}{% endif %}</td>

Wyświetl plik

@ -37,7 +37,7 @@
</form> </form>
{% if logbook is not none %} {% if logbooks is not none %}
<table class="datatable table table-striped table-bordered"> <table class="datatable table table-striped table-bordered">
<tr> <tr>
<th>#</th> <th>#</th>
@ -49,7 +49,7 @@
<th>AGL</th> <th>AGL</th>
<th>Remark</th> <th>Remark</th>
</tr> </tr>
{% for entry in logbook %} {% for entry in logbooks %}
<tr> <tr>
<td>{{ loop.index }}</td>{% set sender = entry.sender %} <td>{{ loop.index }}</td>{% set sender = entry.sender %}
<td>{{ sender|to_html_link|safe }}</td> <td>{{ sender|to_html_link|safe }}</td>
@ -59,10 +59,10 @@
<td>{% if entry.landing_timestamp is not none and entry.landing_airport.id == sel_airport_id %} {{ entry.landing_timestamp.strftime('%H:%M') }} {% endif %}</td> <td>{% if entry.landing_timestamp is not none and entry.landing_airport.id == sel_airport_id %} {{ entry.landing_timestamp.strftime('%H:%M') }} {% endif %}</td>
<td>{% if entry.landing_track is not none and entry.landing_airport.id == sel_airport_id %} {{ '%02d' | format(entry.landing_track/10) }} {% endif %}</td> <td>{% if entry.landing_track is not none and entry.landing_airport.id == sel_airport_id %} {{ '%02d' | format(entry.landing_track/10) }} {% endif %}</td>
<td>{% if entry.duration is not none %}{{ entry.duration }}{% endif %}</td> <td>{% if entry.duration is not none %}{{ entry.duration }}{% endif %}</td>
<td>{% if entry.max_altitude is not none %}{{ '%0.1f'|format(entry.max_altitude - entry.takeoff_airport.altitude) }} m{% endif %}</td> <td>{% if entry.max_altitude is not none %}{{ '%d' | format(entry.max_altitude - entry.takeoff_airport.altitude) }} m{% endif %}</td>
<td> <td>
{% if entry.takeoff_airport is not none and entry.takeoff_airport.id != sel_airport_id %}Take Off: <img src="{{ url_for('static', filename='img/Transparent.gif') }}" class="flag flag-{{ entry.takeoff_airport.country_code|lower }}" alt="{{ entry.takeoff_airport.country_code }}"/> <a href="{{ url_for('main.logbook', country=entry.takeoff_airport.country_code, airport_id=entry.takeoff_airport.id, date=sel_date) }}">{{ entry.takeoff_airport.name }}</a> {% if entry.takeoff_airport is not none and entry.takeoff_airport.id != sel_airport_id %}Take Off: <img src="{{ url_for('static', filename='img/Transparent.gif') }}" class="flag flag-{{ entry.takeoff_airport.country_code|lower }}" alt="{{ entry.takeoff_airport.country_code }}"/> <a href="{{ url_for('main.logbooks', country=entry.takeoff_airport.country_code, airport_id=entry.takeoff_airport.id, date=sel_date) }}">{{ entry.takeoff_airport.name }}</a>
{% elif entry.landing_airport is not none and entry.landing_airport.id != sel_airport_id %}Landing: <img src="{{ url_for('static', filename='img/Transparent.gif') }}" class="flag flag-{{ entry.landing_airport.country_code|lower }}" alt="{{ entry.landing_airport.country_code }}"/> <a href="{{ url_for('main.logbook', country=entry.takeoff_airport.country_code, airport_id=entry.landing_airport.id, date=sel_date) }}">{{ entry.landing_airport.name }}</a> {% elif entry.landing_airport is not none and entry.landing_airport.id != sel_airport_id %}Landing: <img src="{{ url_for('static', filename='img/Transparent.gif') }}" class="flag flag-{{ entry.landing_airport.country_code|lower }}" alt="{{ entry.landing_airport.country_code }}"/> <a href="{{ url_for('main.logbooks', country=entry.takeoff_airport.country_code, airport_id=entry.landing_airport.id, date=sel_date) }}">{{ entry.landing_airport.name }}</a>
{% endif %} {% endif %}
</td> </td>
</tr> </tr>