Merge pull request #96 from DanielGrams/issue/95-optimize-loading

Optimize database loading #95
This commit is contained in:
Daniel Grams 2021-01-26 10:04:39 +01:00 committed by GitHub
commit d220867392
9 changed files with 170 additions and 50 deletions

View File

@ -13,8 +13,9 @@ from project.api.event_date.schemas import (
EventDateListResponseSchema,
)
from project.models import Event, EventDate
from project.services.event import get_events_query
from project.services.event import get_events_query, get_event_with_details_or_404
from project.services.event_search import EventSearchParams
from sqlalchemy.orm import lazyload, load_only
class EventListResource(MethodResource):
@ -30,7 +31,7 @@ class EventResource(MethodResource):
@doc(summary="Get event", tags=["Events"])
@marshal_with(EventSchema)
def get(self, id):
return Event.query.get_or_404(id)
return get_event_with_details_or_404(id)
class EventDatesResource(MethodResource):
@ -38,8 +39,13 @@ class EventDatesResource(MethodResource):
@use_kwargs(EventDateListRequestSchema, location=("query"))
@marshal_with(EventDateListResponseSchema)
def get(self, id):
event = Event.query.get_or_404(id)
return EventDate.query.with_parent(event).paginate()
event = Event.query.options(load_only(Event.id)).get_or_404(id)
return (
EventDate.query.options(lazyload(EventDate.event))
.filter(EventDate.event_id == event.id)
.order_by(EventDate.start)
.paginate()
)
class EventSearchResource(MethodResource):

View File

@ -8,9 +8,10 @@ from project.api.event_date.schemas import (
EventDateSearchRequestSchema,
EventDateSearchResponseSchema,
)
from project.models import EventDate
from project.models import EventDate, Event
from project.services.event import get_event_dates_query
from project.services.event_search import EventSearchParams
from sqlalchemy.orm import defaultload, lazyload
class EventDateListResource(MethodResource):
@ -18,7 +19,7 @@ class EventDateListResource(MethodResource):
@use_kwargs(EventDateListRequestSchema, location=("query"))
@marshal_with(EventDateListResponseSchema)
def get(self, **kwargs):
pagination = EventDate.query.paginate()
pagination = EventDate.query.options(lazyload(EventDate.event)).paginate()
return pagination
@ -26,7 +27,9 @@ class EventDateResource(MethodResource):
@doc(summary="Get event date", tags=["Event Dates"])
@marshal_with(EventDateSchema)
def get(self, id):
return EventDate.query.get_or_404(id)
return EventDate.query.options(
defaultload(EventDate.event).load_only(Event.id, Event.name)
).get_or_404(id)
class EventDateSearchResource(MethodResource):

View File

@ -36,15 +36,32 @@ def _current_user_id_or_none():
class TrackableMixin(object):
created_at = Column(DateTime, default=datetime.datetime.utcnow)
updated_at = Column(
DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow
)
@declared_attr
def created_at(cls):
return deferred(
Column(DateTime, default=datetime.datetime.utcnow), group="trackable"
)
@declared_attr
def updated_at(cls):
return deferred(
Column(
DateTime,
default=datetime.datetime.utcnow,
onupdate=datetime.datetime.utcnow,
),
group="trackable",
)
@declared_attr
def created_by_id(cls):
return Column(
"created_by_id", ForeignKey("user.id"), default=_current_user_id_or_none
return deferred(
Column(
"created_by_id",
ForeignKey("user.id"),
default=_current_user_id_or_none,
),
group="trackable",
)
@declared_attr
@ -57,11 +74,14 @@ class TrackableMixin(object):
@declared_attr
def updated_by_id(cls):
return Column(
"updated_by_id",
ForeignKey("user.id"),
default=_current_user_id_or_none,
onupdate=_current_user_id_or_none,
return deferred(
Column(
"updated_by_id",
ForeignKey("user.id"),
default=_current_user_id_or_none,
onupdate=_current_user_id_or_none,
),
group="trackable",
)
@declared_attr
@ -198,19 +218,19 @@ class AdminUnit(db.Model, TrackableMixin):
"EventOrganizer", backref=backref("adminunit", lazy=True)
)
event_places = relationship("EventPlace", backref=backref("adminunit", lazy=True))
location_id = db.Column(db.Integer, db.ForeignKey("location.id"))
location_id = deferred(db.Column(db.Integer, db.ForeignKey("location.id")))
location = db.relationship("Location")
logo_id = db.Column(db.Integer, db.ForeignKey("image.id"))
logo_id = deferred(db.Column(db.Integer, db.ForeignKey("image.id")))
logo = db.relationship("Image", uselist=False)
url = Column(String(255))
email = Column(Unicode(255))
phone = Column(Unicode(255))
fax = Column(Unicode(255))
widget_font = Column(Unicode(255))
widget_background_color = Column(ColorType)
widget_primary_color = Column(ColorType)
widget_link_color = Column(ColorType)
incoming_reference_requests_allowed = Column(Boolean())
url = deferred(Column(String(255)), group="detail")
email = deferred(Column(Unicode(255)), group="detail")
phone = deferred(Column(Unicode(255)), group="detail")
fax = deferred(Column(Unicode(255)), group="detail")
widget_font = deferred(Column(Unicode(255)), group="widget")
widget_background_color = deferred(Column(ColorType), group="widget")
widget_primary_color = deferred(Column(ColorType), group="widget")
widget_link_color = deferred(Column(ColorType), group="widget")
incoming_reference_requests_allowed = deferred(Column(Boolean()))
@listens_for(AdminUnit, "before_insert")
@ -354,13 +374,13 @@ class EventOrganizer(db.Model, TrackableMixin):
__table_args__ = (UniqueConstraint("name", "admin_unit_id"),)
id = Column(Integer(), primary_key=True)
name = Column(Unicode(255), nullable=False)
url = Column(String(255))
email = Column(Unicode(255))
phone = Column(Unicode(255))
fax = Column(Unicode(255))
location_id = db.Column(db.Integer, db.ForeignKey("location.id"))
url = deferred(Column(String(255)), group="detail")
email = deferred(Column(Unicode(255)), group="detail")
phone = deferred(Column(Unicode(255)), group="detail")
fax = deferred(Column(Unicode(255)), group="detail")
location_id = deferred(db.Column(db.Integer, db.ForeignKey("location.id")))
location = db.relationship("Location")
logo_id = db.Column(db.Integer, db.ForeignKey("image.id"))
logo_id = deferred(db.Column(db.Integer, db.ForeignKey("image.id")))
logo = db.relationship("Image", uselist=False)
admin_unit_id = db.Column(db.Integer, db.ForeignKey("adminunit.id"), nullable=True)

View File

@ -17,7 +17,7 @@ from project.dateutils import (
)
from sqlalchemy import and_, or_, func
from sqlalchemy.sql import extract
from sqlalchemy.orm import joinedload, contains_eager
from sqlalchemy.orm import joinedload, contains_eager, defaultload
from dateutil.relativedelta import relativedelta
@ -117,6 +117,83 @@ def get_event_dates_query(params):
)
def get_event_date_with_details_or_404(event_id):
return (
EventDate.query.join(EventDate.event)
.join(Event.event_place, isouter=True)
.join(EventPlace.location, isouter=True)
.options(
contains_eager(EventDate.event)
.contains_eager(Event.event_place)
.contains_eager(EventPlace.location),
joinedload(EventDate.event).undefer_group("trackable"),
# Place
defaultload(EventDate.event)
.defaultload(Event.event_place)
.joinedload(EventPlace.photo),
# Category
joinedload(EventDate.event)
.joinedload(Event.categories)
.load_only(EventCategory.id, EventCategory.name),
# Organizer
joinedload(EventDate.event)
.joinedload(Event.organizer)
.undefer_group("detail")
.undefer("logo_id")
.joinedload(EventOrganizer.logo),
# Photo
joinedload(EventDate.event).joinedload(Event.photo),
# Admin unit
joinedload(EventDate.event)
.joinedload(Event.admin_unit)
.undefer("logo_id")
.undefer_group("detail")
.undefer_group("widget")
.joinedload(AdminUnit.location),
# Admin unit logo
defaultload(EventDate.event)
.defaultload(Event.admin_unit)
.joinedload(AdminUnit.logo),
)
.filter(EventDate.id == event_id)
.first_or_404()
)
def get_event_with_details_or_404(event_id):
return (
Event.query.join(EventPlace, isouter=True)
.join(Location, isouter=True)
.options(
contains_eager(Event.event_place).contains_eager(EventPlace.location),
defaultload(Event).undefer_group("trackable"),
# Place
joinedload(Event.event_place).joinedload(EventPlace.photo),
# Category
joinedload(Event.categories).load_only(
EventCategory.id, EventCategory.name
),
# Organizer
joinedload(Event.organizer)
.undefer_group("detail")
.undefer("logo_id")
.joinedload(EventOrganizer.logo),
# Photo
joinedload(Event.photo),
# Admin unit with location
joinedload(Event.admin_unit)
.undefer("logo_id")
.undefer_group("detail")
.undefer_group("widget")
.joinedload(AdminUnit.location),
# Admin unit logo
defaultload(Event.admin_unit).joinedload(AdminUnit.logo),
)
.filter(Event.id == event_id)
.first_or_404()
)
def get_events_query(params):
event_filter = 1 == 1
date_filter = EventDate.start >= today

View File

@ -1,6 +1,7 @@
from project import db
from project.models import EventReviewStatus, EventSuggestion
from sqlalchemy import and_
from sqlalchemy.orm import load_only
def insert_event_suggestion(event_suggestion):
@ -9,7 +10,7 @@ def insert_event_suggestion(event_suggestion):
def get_event_reviews_badge_query(admin_unit):
return EventSuggestion.query.filter(
return EventSuggestion.query.options(load_only(EventSuggestion.id)).filter(
and_(
EventSuggestion.admin_unit_id == admin_unit.id,
EventSuggestion.review_status == EventReviewStatus.inbox,

View File

@ -6,6 +6,7 @@ from project.models import (
EventReferenceRequestReviewStatus,
)
from sqlalchemy import and_
from sqlalchemy.orm import load_only
def create_event_reference_for_request(request):
@ -44,7 +45,9 @@ def get_reference_requests_incoming_query(admin_unit):
def get_reference_requests_incoming_badge_query(admin_unit):
return EventReferenceRequest.query.filter(
return EventReferenceRequest.query.options(
load_only(EventReferenceRequest.id)
).filter(
and_(
EventReferenceRequest.review_status
== EventReferenceRequestReviewStatus.inbox,

View File

@ -35,20 +35,22 @@ from project.services.event import (
upsert_event_category,
insert_event,
update_event,
get_event_with_details_or_404,
)
from project.services.place import get_event_places
from sqlalchemy.sql import func
from sqlalchemy.sql import func, and_
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import lazyload
from project.views.event_suggestion import send_event_suggestion_review_status_mail
@app.route("/event/<int:event_id>")
def event(event_id):
event = Event.query.get_or_404(event_id)
user_rights = get_user_rights(event)
event = get_event_with_details_or_404(event_id)
user_rights = get_menu_user_rights(event)
dates = (
EventDate.query.with_parent(event)
.filter(EventDate.start >= today)
EventDate.query.options(lazyload(EventDate.event))
.filter(and_(EventDate.event_id == event.id, EventDate.start >= today))
.order_by(EventDate.start)
.all()
)
@ -306,6 +308,12 @@ def get_user_rights(event):
}
def get_menu_user_rights(event):
return {
"can_update_event": has_access(event.admin_unit, "event:update"),
}
def send_referenced_event_changed_mails(event):
# Alle Referenzen
references = EventReference.query.filter(EventReference.event_id == event.id).all()

View File

@ -1,12 +1,12 @@
from project import app
from project.models import EventDate
from flask import render_template, url_for, redirect, request
from project.views.utils import flash_errors, track_analytics
import json
from project.jsonld import get_sd_for_event_date, DateTimeEncoder
from project.services.event import get_event_date_with_details_or_404
from project.services.event_search import EventSearchParams
from project.forms.event_date import FindEventDateForm
from project.views.event import get_event_category_choices, get_user_rights
from project.views.event import get_event_category_choices, get_menu_user_rights
def prepare_event_date_form(form):
@ -32,7 +32,7 @@ def event_dates():
@app.route("/eventdate/<int:id>")
def event_date(id):
event_date = EventDate.query.get_or_404(id)
event_date = get_event_date_with_details_or_404(id)
if "src" in request.args:
track_analytics("event_date", str(id), request.args["src"])
@ -45,5 +45,5 @@ def event_date(id):
"event_date/read.html",
event_date=event_date,
structured_data=structured_data,
user_rights=get_user_rights(event_date.event),
user_rights=get_menu_user_rights(event_date.event),
)

View File

@ -1,7 +1,6 @@
from project import app, db
from project.models import (
User,
EventDate,
AdminUnit,
EventOrganizer,
EventSuggestion,
@ -14,7 +13,10 @@ from flask_security import current_user
from sqlalchemy.sql import func
from sqlalchemy.exc import SQLAlchemyError
from project.services.event_suggestion import insert_event_suggestion
from project.services.event import get_event_dates_query
from project.services.event import (
get_event_dates_query,
get_event_date_with_details_or_404,
)
from project.services.event_search import EventSearchParams
from project.services.place import get_event_places
from project.views.utils import (
@ -67,7 +69,7 @@ def widget_event_date(au_short_name, id):
admin_unit = AdminUnit.query.filter(
AdminUnit.short_name == au_short_name
).first_or_404()
event_date = EventDate.query.get_or_404(id)
event_date = get_event_date_with_details_or_404(id)
structured_data = json.dumps(
get_sd_for_event_date(event_date), indent=2, cls=DateTimeEncoder
)