diff --git a/project/api/event/resources.py b/project/api/event/resources.py index 4d39715..075fb2e 100644 --- a/project/api/event/resources.py +++ b/project/api/event/resources.py @@ -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): diff --git a/project/api/event_date/resources.py b/project/api/event_date/resources.py index 05bd9bc..878d4c4 100644 --- a/project/api/event_date/resources.py +++ b/project/api/event_date/resources.py @@ -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): diff --git a/project/models.py b/project/models.py index e6692f9..2eaeb46 100644 --- a/project/models.py +++ b/project/models.py @@ -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) diff --git a/project/services/event.py b/project/services/event.py index 692f5b2..625ddb5 100644 --- a/project/services/event.py +++ b/project/services/event.py @@ -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 diff --git a/project/services/event_suggestion.py b/project/services/event_suggestion.py index f96288f..72b3c9e 100644 --- a/project/services/event_suggestion.py +++ b/project/services/event_suggestion.py @@ -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, diff --git a/project/services/reference.py b/project/services/reference.py index 846cff9..9b74b00 100644 --- a/project/services/reference.py +++ b/project/services/reference.py @@ -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, diff --git a/project/views/event.py b/project/views/event.py index 0cfa851..751293a 100644 --- a/project/views/event.py +++ b/project/views/event.py @@ -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/") 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() diff --git a/project/views/event_date.py b/project/views/event_date.py index 0329be4..822e5fc 100644 --- a/project/views/event_date.py +++ b/project/views/event_date.py @@ -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/") 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), ) diff --git a/project/views/widget.py b/project/views/widget.py index 27452a1..347bfe5 100644 --- a/project/views/widget.py +++ b/project/views/widget.py @@ -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 )