Skip to content

Commit

Permalink
Make ical feed generation of meetings not slow
Browse files Browse the repository at this point in the history
The lookup for participating was very expensive and slow. It would cause
timeouts so some users' calendars would not load.
  • Loading branch information
LudvigHz committed Apr 25, 2023
1 parent 72fad94 commit 8f6ddcc
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 7 deletions.
2 changes: 1 addition & 1 deletion lego/apps/events/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
log = get_logger()


class EventPermissionHandler(PermissionHandler):
class EventPermissionHandler(PermissionHandler["Event"]):
perms_without_object = [CREATE, "administrate"]

def event_type_keyword_permissions(self, event_type, perm):
Expand Down
3 changes: 2 additions & 1 deletion lego/apps/ical/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ def add_meeting_to_ical_feed(feed, meeting, user):
start_datetime=meeting.start_time,
end_datetime=meeting.end_time,
location=meeting.location,
transparency="OPAQUE" if user in meeting.participants else "TRANSPARENT",
# This uses an annotation on the queryset for performance reasons
transparency="OPAQUE" if meeting.user_participating else "TRANSPARENT",
)


Expand Down
12 changes: 11 additions & 1 deletion lego/apps/ical/viewsets.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from datetime import timedelta

from django.db import FilteredRelation, Q
from django.utils import timezone
from rest_framework import decorators, permissions, viewsets
from rest_framework.permissions import IsAuthenticated
Expand Down Expand Up @@ -92,7 +93,16 @@ def personal(self, request):
)

permission_handler = get_permission_handler(Meeting)
meetings = permission_handler.filter_queryset(request.user, Meeting.objects)
meetings = permission_handler.filter_queryset(
request.user,
Meeting.objects.annotate(
# Annotate the queryset with participating status for use in the feed generation.
# Otherwise lookups are very expensive
user_participating=FilteredRelation(
"participants", condition=Q(participants__in=request.user)
)
),
)

utils.add_events_to_ical_feed(feed, following_events)
utils.add_meetings_to_ical_feed(feed, meetings, request.user)
Expand Down
16 changes: 13 additions & 3 deletions lego/apps/permissions/permissions.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
from django.db.models import Q
from typing import TYPE_CHECKING, Generic, TypeVar

from django.db.models import Q, QuerySet

from lego.apps.permissions.constants import CREATE, LIST, VIEW
from lego.apps.permissions.models import ObjectPermissionsModel

if TYPE_CHECKING:
from lego.apps.users.models import User


def _check_intersection(first, second):
return len(set(first).intersection(set(second))) > 0


class PermissionHandler:
T = TypeVar("T")


class PermissionHandler(Generic[T]):
"""
The permission handler defines how permissions should be handled on a model
Expand Down Expand Up @@ -161,7 +169,9 @@ def has_perm(

return False

def filter_queryset(self, user, queryset, **kwargs):
def filter_queryset(
self, user: User, queryset: QuerySet[T], **kwargs
) -> QuerySet[T]:
"""
Filter queryset based on object-level permissions.
We currently only supports queryset filtering on ObjectPermissionsModels.
Expand Down
7 changes: 6 additions & 1 deletion lego/apps/permissions/utils.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
from typing import TypeVar

from lego.apps.permissions.permissions import PermissionHandler

# isort:skip

default_permission_handler = PermissionHandler()


def get_permission_handler(model):
T = TypeVar("T")


def get_permission_handler(model: T) -> PermissionHandler[T]:
"""
Try to get the permission handler used by the model or use the default handler.
"""
Expand Down

0 comments on commit 8f6ddcc

Please sign in to comment.