Performance: Cache complete organizer index page, cache subevent list template fragment (#2125)

This commit is contained in:
Raphael Michel
2021-06-14 17:12:11 +02:00
committed by GitHub
parent cd88659351
commit ba895270fa
6 changed files with 108 additions and 31 deletions

View File

@@ -9,21 +9,21 @@
{% endfor %}
<div class="row">
<div class="col-sm-4 hidden-xs text-left flip">
<a href="?{% url_replace request "year" before.year "month" before.month %}"
<a href="?{% url_replace request "year" subevent_list.before.year "month" subevent_list.before.month %}"
class="btn btn-default">
<span class="fa fa-arrow-left" aria-hidden="true"></span>
{{ before|date:"F Y" }}
{{ subevent_list.before|date:"F Y" }}
</a>
</div>
<div class="col-sm-4 col-xs-12 text-center">
<select name="month" class="form-control">
{% for m in months %}
<option value="{{ m|date:"m" }}" {% if m == date %}selected{% endif %}>{{ m|date:"F" }}</option>
{% for m in subevent_list.months %}
<option value="{{ m|date:"m" }}" {% if m == subevent_list.date.month %}selected{% endif %}>{{ m|date:"F" }}</option>
{% endfor %}
</select>
<select name="year" class="form-control">
{% for y in years %}
<option value="{{ y }}" {% if y == date.year %}selected{% endif %}>{{ y }}</option>
{% for y in subevent_list.years %}
<option value="{{ y }}" {% if y == subevent_list.date.year %}selected{% endif %}>{{ y }}</option>
{% endfor %}
</select>
<button type="submit" class="js-hidden btn btn-default">
@@ -31,11 +31,11 @@
</button>
</div>
<div class="col-sm-4 hidden-xs text-right flip">
<a href="?{% url_replace request "year" after.year "month" after.month %}" class="btn btn-default">
{{ after|date:"F Y" }}
<a href="?{% url_replace request "year" subevent_list.after.year "month" subevent_list.after.month %}" class="btn btn-default">
{{ subevent_list.after|date:"F Y" }}
<span class="fa fa-arrow-right" aria-hidden="true"></span>
</a>
</div>
</div>
</form>
{% include "pretixpresale/fragment_calendar.html" with show_avail=event.settings.event_list_availability %}
{% include "pretixpresale/fragment_calendar.html" with show_avail=event.settings.event_list_availability weeks=subevent_list.weeks %}

View File

@@ -9,21 +9,21 @@
{% endfor %}
<div class="row">
<div class="col-sm-4 hidden-xs text-left flip">
<a href="?{% url_replace request "year" before.isocalendar.0 "week" before.isocalendar.1 %}"
<a href="?{% url_replace request "year" subevent_list.before.isocalendar.0 "week" subevent_list.before.isocalendar.1 %}"
class="btn btn-default">
<span class="fa fa-arrow-left" aria-hidden="true"></span>
{{ before|date:week_format }}
{{ before|date:subevent_list.week_format }}
</a>
</div>
<div class="col-sm-4 col-xs-12 text-center">
<select name="week" class="form-control select-calendar-week-short">
{% for w in weeks %}
<option value="{{ w.0.isocalendar.1 }}" {% if w.0.isocalendar.1 == date.isocalendar.1 %}selected{% endif %}>{% trans "W" %} {{ w.0.isocalendar.1 }} ({{ w.0|date:"SHORT_DATE_FORMAT" }} {{ w.1|date:"SHORT_DATE_FORMAT" }})</option>
{% for w in subevent_list.weeks %}
<option value="{{ w.0.isocalendar.1 }}" {% if w.0.isocalendar.1 == subevent_list.date.isocalendar.1 %}selected{% endif %}>{% trans "W" %} {{ w.0.isocalendar.1 }} ({{ w.0|date:"SHORT_DATE_FORMAT" }} {{ w.1|date:"SHORT_DATE_FORMAT" }})</option>
{% endfor %}
</select>
<select name="year" class="form-control">
{% for y in years %}
<option value="{{ y }}" {% if y == date.isocalendar.0 %}selected{% endif %}>{{ y }}</option>
{% for y in subevent_list.years %}
<option value="{{ y }}" {% if y == subevent_list.date.isocalendar.0 %}selected{% endif %}>{{ y }}</option>
{% endfor %}
</select>
<button type="submit" class="js-hidden btn btn-default">
@@ -31,24 +31,24 @@
</button>
</div>
<div class="col-sm-4 hidden-xs text-right flip">
<a href="?{% url_replace request "year" after.isocalendar.0 "week" after.isocalendar.1 %}"
<a href="?{% url_replace request "year" subevent_list.after.isocalendar.0 "week" subevent_list.after.isocalendar.1 %}"
class="btn btn-default">
{{ after|date:week_format }}
{{ subevent_list.after|date:subevent_list.week_format }}
<span class="fa fa-arrow-right" aria-hidden="true"></span>
</a>
</div>
</div>
</form>
{% include "pretixpresale/fragment_week_calendar.html" with show_avail=event.settings.event_list_availability %}
{% include "pretixpresale/fragment_week_calendar.html" with show_avail=event.settings.event_list_availability days=subevent_list.days %}
<div class="col-sm-4 visible-xs text-center">
<a href="?{% url_replace request "year" before.isocalendar.0 "week" before.isocalendar.1 %}"
<a href="?{% url_replace request "year" subevent_list.before.isocalendar.0 "week" subevent_list.before.isocalendar.1 %}"
class="btn btn-default">
<span class="fa fa-arrow-left" aria-hidden="true"></span>
{{ before|date:week_format }}
{{ subevent_list.before|date:subevent_list.week_format }}
</a>
<a href="?{% url_replace request "year" after.isocalendar.0 "week" after.isocalendar.1 %}"
<a href="?{% url_replace request "year" subevent_list.after.isocalendar.0 "week" subevent_list.after.isocalendar.1 %}"
class="btn btn-default">
{{ after|date:week_format }}
{{ subevent_list.after|date:subevent_list.week_format }}
<span class="fa fa-arrow-right" aria-hidden="true"></span>
</a>
</div>

View File

@@ -1,6 +1,6 @@
{% load i18n %}
{% load eventurl %}
{% for subev in subevent_list %}
{% for subev in subevent_list.subevent_list %}
<a href="{% if request.GET.voucher %}{% eventurl event "presale:event.redeem" cart_namespace=cart_namespace %}?voucher={{ request.GET.voucher|urlencode }}&subevent={{ subev.pk }}{% else %}{% eventurl event "presale:event.index" subevent=subev.id cart_namespace=cart_namespace %}{% endif %}"
class="subevent-row">
<div class="row">

View File

@@ -2,6 +2,7 @@
{% load i18n %}
{% load l10n %}
{% load eventurl %}
{% load cache %}
{% load money %}
{% load thumb %}
{% load eventsignal %}
@@ -73,13 +74,15 @@
</div>
<div class="panel-body">
<div>
{% if list_type == "calendar" %}
{% include "pretixpresale/event/fragment_subevent_calendar.html" %}
{% elif list_type == "week" %}
{% include "pretixpresale/event/fragment_subevent_calendar_week.html" %}
{% else %}
{% include "pretixpresale/event/fragment_subevent_list.html" %}
{% endif %}
{% cache 15 subevent_lits subevent_list_cache_key %}
{% if subevent_list.list_type == "calendar" %}
{% include "pretixpresale/event/fragment_subevent_calendar.html" %}
{% elif subevent_list.list_type == "week" %}
{% include "pretixpresale/event/fragment_subevent_calendar_week.html" %}
{% else %}
{% include "pretixpresale/event/fragment_subevent_list.html" %}
{% endif %}
{% endcache %}
</div>
</div>
</div>

View File

@@ -33,6 +33,7 @@
# License for the specific language governing permissions and limitations under the License.
import calendar
import hashlib
import sys
from collections import defaultdict
from datetime import date, datetime, timedelta
@@ -50,6 +51,7 @@ from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.utils.decorators import method_decorator
from django.utils.formats import get_format
from django.utils.functional import SimpleLazyObject
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django.views import View
@@ -448,7 +450,8 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
context['frontpage_text'] = str(self.request.event.settings.frontpage_text)
if self.request.event.has_subevents:
context.update(self._subevent_list_context())
context['subevent_list'] = SimpleLazyObject(self._subevent_list_context)
context['subevent_list_cache_key'] = self._subevent_list_cachekey()
context['show_cart'] = (
context['cart']['positions'] and (
@@ -465,6 +468,17 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
return context
def _subevent_list_cachekey(self):
cache_key_parts = [
self.request.host,
str(self.request.event.pk),
self.request.get_full_path(),
self.request.LANGUAGE_CODE,
self.request.sales_channel.identifier,
]
cache_key = f'pretix.presale.views.event.EventIndex.subevent_list_context:{hashlib.md5(":".join(cache_key_parts).encode()).hexdigest()}'
return cache_key
def _subevent_list_context(self):
voucher = None
if self.request.GET.get('voucher'):

View File

@@ -33,6 +33,7 @@
# License for the specific language governing permissions and limitations under the License.
import calendar
import hashlib
from collections import defaultdict
from datetime import date, datetime, time, timedelta
from urllib.parse import quote
@@ -40,6 +41,7 @@ from urllib.parse import quote
import isoweek
import pytz
from django.conf import settings
from django.core.cache import cache
from django.db.models import Exists, Max, Min, OuterRef, Q
from django.db.models.functions import Coalesce, Greatest
from django.http import Http404, HttpResponse
@@ -306,6 +308,64 @@ class OrganizerIndex(OrganizerViewMixin, EventListMixin, ListView):
template_name = 'pretixpresale/organizers/index.html'
paginate_by = 30
def dispatch(self, request, *args, **kwargs):
# In stock pretix, nothing on this page is session-dependent except for the language and the customer login part,
# so we can cache pretty aggressively if the user is anonymous. Note that we deliberately implement the caching
# on the view layer, *after* all middlewares have been ran, so we have access to the computed locale, as well
# as the login status etc.
cache_allowed = (
not getattr(request, 'customer', None) and not request.user.is_authenticated
)
cache_key_parts = [
request.method,
request.host,
str(request.organizer.pk),
request.get_full_path(),
request.LANGUAGE_CODE,
self.request.sales_channel.identifier,
]
for c, v in request.COOKIES.items():
# If the cookie is not one we know, it might be set by a plugin and we need to include it in the
# cache key to be safe. A known example includes plugins that e.g. store cookie banner state.
if c not in (settings.SESSION_COOKIE_NAME, settings.LANGUAGE_COOKIE_NAME, settings.CSRF_COOKIE_NAME):
cache_key_parts.append(f'{c}={v}')
for c, v in request.session.items():
# If the session key is not one we know, it might be set by a plugin and we need to include it in the
# cache key to be safe. A known example would be the pretix-campaigns plugin setting the campaign ID.
if (
not c.startswith('_auth') and
not c.startswith('pretix_auth_') and
not c.startswith('customer_auth_') and
not c.startswith('current_cart_') and
not c.startswith('cart_') and
not c.startswith('payment_') and
c not in ('carts', 'payment', 'pinned_user_agent')
):
cache_key_parts.append(f'{c}={repr(v)}')
cache_key = f'pretix.presale.views.organizer.OrganizerIndex:{hashlib.md5(":".join(cache_key_parts).encode()).hexdigest()}'
cache_timeout = 15
if not cache_allowed:
return super().dispatch(request, *args, **kwargs)
response = cache.get(cache_key)
if response is not None:
return response
response = super().dispatch(request, *kwargs, **kwargs)
if response.status_code >= 400:
return response
if hasattr(response, 'render') and callable(response.render):
def _store_to_cache(r):
cache.set(cache_key, r, cache_timeout)
response.add_post_render_callback(_store_to_cache)
else:
cache.set(cache_key, response, cache_timeout)
return response
def get(self, request, *args, **kwargs):
style = request.GET.get("style", request.organizer.settings.event_list_type)
if style == "calendar":