Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Commit

Permalink
Performance improvements, reducing queries by a couple of hundred for…
Browse files Browse the repository at this point in the history
… lists of links to events
  • Loading branch information
Greg Turner committed Jun 4, 2017
1 parent f9b2232 commit 0d35f44
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 34 deletions.
19 changes: 16 additions & 3 deletions icekit_events/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@
PublishingPolymorphicQuerySet

from django.db import models
from django.db.models.query import QuerySet
from django.db.models.query import QuerySet, Prefetch
from django.db.models import Q
from icekit.publishing.middleware import is_draft_request_context
from timezone.timezone import now, localize

from icekit_events.utils.timeutils import zero_datetime, coerce_dt_awareness
from timezone import timezone as djtz # django-timezone


class EventQueryset(PublishingPolymorphicQuerySet):

def with_upcoming_occurrences(self):
"""
:return: events having upcoming occurrences, and all their children
Expand Down Expand Up @@ -62,8 +65,13 @@ def order_by_first_occurrence(self):
"""
:return: The event in order of minimum occurrence.
"""
return self.annotate(first_occurrence=models.Min('occurrences__start')).order_by(
'first_occurrence')
def _key(e):
try:
return e.occurrence_list[0].start
except IndexError: # no occurrences; put last
return localize(datetime.max - timedelta(days=365))

return sorted(list(self), key=_key)

def order_by_next_occurrence(self):
"""
Expand Down Expand Up @@ -103,6 +111,7 @@ def _sort(x):
EventManager.use_for_related_fields = True



class OccurrenceQueryset(QuerySet):
""" Custom queryset methods for ``Occurrence`` """

Expand Down Expand Up @@ -264,3 +273,7 @@ def next_occurrence(self):

OccurrenceManager = models.Manager.from_queryset(OccurrenceQueryset)
OccurrenceManager.use_for_related_fields = True
# monkeypatch in a default queryset
def _get_queryset(self):
return super(OccurrenceManager, self).get_queryset().select_related('event')
OccurrenceManager.get_queryset = _get_queryset
84 changes: 54 additions & 30 deletions icekit_events/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from django.db.models import Q
from django.template import Context
from django.template import Template
from django.utils.functional import cached_property
from django.utils.safestring import mark_safe

from icekit_events.managers import EventManager, OccurrenceManager
Expand Down Expand Up @@ -161,6 +162,7 @@ class EventBase(PolymorphicModel, AbstractBaseModel, ICEkitContentsMixin,
instances that define the rules for automatically generating repeating
occurrences.
"""

objects = EventManager()

primary_type = models.ForeignKey(
Expand Down Expand Up @@ -282,7 +284,7 @@ def cancel_occurrence(self, occurrence, hide_cancelled_occurrence=False,
We don't really delete occurrences because doing so would just cause
them to be re-generated the next time occurrence generation is done.
"""
if occurrence not in self.occurrences.all():
if occurrence not in self.occurrence_list:
return # No-op
occurrence.is_protected_from_regeneration = True
occurrence.is_cancelled = True
Expand Down Expand Up @@ -404,19 +406,32 @@ def clone_event_relationships(self, dst_obj):
generator.event = dst_obj
generator.save()

def get_part_of(self):
if self.part_of:
@cached_property
def visible_part_of(self):
if self.part_of_id:
return self.part_of.get_visible()
return False

def get_occurrences(self):
@cached_property
def occurrence_list(self):
"""
:return: My occurrences, or those of my get_part_of() event
:return: A list of my occurrences, or those of my visible_part_of event
"""
if self.occurrences.count():
return self.occurrences
elif self.get_part_of():
return self.get_part_of().get_occurrences()
return self.occurrences # will be empty, but at least queryable
o = list(self.occurrences.all())
if o:
return o
if self.visible_part_of:
return self.visible_part_of.occurrence_list
return o # empty

@cached_property
def upcoming_occurrence_list(self):
o = list(self.occurrences.upcoming())
if o:
return o
if self.visible_part_of:
return self.visible_part_of.upcoming_occurrence_list
return o # empty

def get_occurrences_range(self):
"""
Expand All @@ -425,8 +440,15 @@ def get_occurrences_range(self):

# TODO: if the event has a generator that never ends, return "None"
# for the last item.
first = self.get_occurrences().order_by('start').first()
last = self.get_occurrences().order_by('-end').first()
try:
first = self.occurrence_list[0]
except IndexError:
first = None

try:
last = self.occurrence_list[-1]
except IndexError:
last = None
return (first, last)

def get_upcoming_occurrences_by_day(self):
Expand All @@ -435,7 +457,7 @@ def get_upcoming_occurrences_by_day(self):
"""
result = OrderedDict()

for occ in self.get_occurrences().upcoming().order_by('start'):
for occ in self.upcoming_occurrence_list:
result.setdefault(occ.local_start.date(), []).append(occ)

return result.items()
Expand All @@ -446,9 +468,7 @@ def start_dates_set(self):
:return: a sorted set of all the different dates that this event
happens on.
"""
occurrences = self.get_occurrences().filter(
is_cancelled=False
)
occurrences = [o for o in self.occurrence_list if not o.is_cancelled]
dates = set([o.local_start.date() for o in occurrences])
sorted_dates = sorted(dates)
return sorted_dates
Expand All @@ -458,9 +478,7 @@ def start_times_set(self):
:return: a sorted set of all the different times that this event
happens on.
"""
occurrences = self.get_occurrences().filter(
is_all_day=False, is_cancelled=False
)
occurrences = [o for o in self.occurrence_list if not o.is_cancelled and not o.is_all_day]
times = set([o.local_start.time() for o in occurrences])
sorted_times = sorted(times)
return sorted_times
Expand All @@ -469,10 +487,7 @@ def get_absolute_url(self):
return reverse('icekit_events_eventbase_detail', args=(self.slug,))

def get_children(self):
events = EventBase.objects.filter(
id__in=self.get_draft().contained_events.values_list('id', flat=True)
)
return events
return EventBase.objects.filter(part_of_id=self.get_draft().id)

def get_cta(self):
if self.cta_url and self.cta_text:
Expand All @@ -494,10 +509,21 @@ def is_members(self):
return self.get_all_types().filter(slug='members')

def is_upcoming(self):
return self.get_occurrences().upcoming()
return self.upcoming_occurrence_list

def get_next_occurrence(self):
return self.occurrences.next_occurrence()
try:
return self.upcoming_occurence_list[0]
except IndexError:
return None

def has_finished(self):
"""
:return: True if:
There are occurrences, and
There are no upcoming occurrences
"""
return self.occurrence_list and not self.upcoming_occurrence_list


class AbstractEventWithLayouts(EventBase, FluentFieldsMixin):
Expand Down Expand Up @@ -836,14 +862,12 @@ def get_occurrence_times_for_event(event):
"""
occurrences_starts = set()
occurrences_ends = set()
for start, original_start, end, original_end in \
event.occurrences.all().values_list('start', 'original_start',
'end', 'original_end'):
for o in event.occurrence_list:
occurrences_starts.add(
coerce_naive(original_start or start)
coerce_naive(o.original_start or o.start)
)
occurrences_ends.add(
coerce_naive(original_end or end)
coerce_naive(o.original_end or o.end)
)
return occurrences_starts, occurrences_ends

Expand Down
2 changes: 1 addition & 1 deletion icekit_events/templatetags/events_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def times_range(event, format=None):
return event.human_times.strip()

sts = timesf(event.start_times_set(), format=format)
all_days = event.get_occurrences().filter(is_all_day=True)
all_days = [o for o in event.occurrence_list if o.is_all_day]
if all_days:
sts = ["all day"] + sts

Expand Down

0 comments on commit 0d35f44

Please sign in to comment.