Make scoreboard caching only cache the score table (#1586)

* Make scoreboard caching only cache the score table instead of the entire page
* Closes #1584
This commit is contained in:
Kevin Chung
2020-08-13 11:53:36 -04:00
committed by GitHub
parent 58e38c4bde
commit da4357b07b
7 changed files with 46 additions and 7 deletions

View File

@@ -1,5 +1,5 @@
from flask import request
from flask_caching import Cache
from flask_caching import Cache, make_template_fragment_key
cache = Cache()
@@ -27,6 +27,7 @@ def clear_config():
def clear_standings():
from CTFd.models import Users, Teams
from CTFd.constants.static import CacheKeys
from CTFd.utils.scores import get_standings, get_team_standings, get_user_standings
from CTFd.api.v1.scoreboard import ScoreboardDetail, ScoreboardList
from CTFd.api import api
@@ -55,11 +56,13 @@ def clear_standings():
cache.delete_memoized(get_team_place)
# Clear out HTTP request responses
cache.delete(make_cache_key(path="scoreboard.listing"))
cache.delete(make_cache_key(path=api.name + "." + ScoreboardList.endpoint))
cache.delete(make_cache_key(path=api.name + "." + ScoreboardDetail.endpoint))
cache.delete_memoized(ScoreboardList.get)
# Clear out scoreboard templates
cache.delete(make_template_fragment_key(CacheKeys.PUBLIC_SCOREBOARD_TABLE))
def clear_pages():
from CTFd.utils.config.pages import get_page, get_pages

View File

@@ -3,6 +3,7 @@ from enum import Enum
from flask import current_app
JS_ENUMS = {}
JINJA_ENUMS = {}
class RawEnum(Enum):
@@ -59,6 +60,7 @@ def JinjaEnum(cls):
"""
if cls.__name__ not in current_app.jinja_env.globals:
current_app.jinja_env.globals[cls.__name__] = cls
JINJA_ENUMS[cls.__name__] = cls
else:
raise KeyError("{} was already defined as a JinjaEnum".format(cls.__name__))
return cls

14
CTFd/constants/static.py Normal file
View File

@@ -0,0 +1,14 @@
from CTFd.constants import JinjaEnum, RawEnum
@JinjaEnum
class CacheKeys(str, RawEnum):
PUBLIC_SCOREBOARD_TABLE = "public_scoreboard_table"
# Placeholder object. Not used, just imported to force initialization of any Enums here
class _StaticsWrapper:
pass
Static = _StaticsWrapper()

View File

@@ -1,6 +1,5 @@
from flask import Blueprint, render_template
from CTFd.cache import cache, make_cache_key
from CTFd.utils import config
from CTFd.utils.config.visibility import scores_visible
from CTFd.utils.decorators.visibility import check_score_visibility
@@ -13,7 +12,6 @@ scoreboard = Blueprint("scoreboard", __name__)
@scoreboard.route("/scoreboard")
@check_score_visibility
@cache.cached(timeout=60, key_prefix=make_cache_key)
def listing():
infos = get_infos()

View File

@@ -15,6 +15,7 @@
</div>
</div>
{% cache 60, CacheKeys.PUBLIC_SCOREBOARD_TABLE %}
{% if standings %}
<div id="scoreboard" class="row">
<div class="col-md-12">
@@ -55,6 +56,7 @@
</div>
</div>
{% endif %}
{% endcache %}
</div>
{% endblock %}

View File

@@ -52,9 +52,11 @@ def init_template_filters(app):
def init_template_globals(app):
from CTFd.constants import JINJA_ENUMS
from CTFd.constants.config import Configs
from CTFd.constants.plugins import Plugins
from CTFd.constants.sessions import Session
from CTFd.constants.static import Static
from CTFd.constants.users import User
from CTFd.constants.teams import Team
from CTFd.forms import Forms
@@ -101,10 +103,20 @@ def init_template_globals(app):
app.jinja_env.globals.update(Configs=Configs)
app.jinja_env.globals.update(Plugins=Plugins)
app.jinja_env.globals.update(Session=Session)
app.jinja_env.globals.update(Static=Static)
app.jinja_env.globals.update(Forms=Forms)
app.jinja_env.globals.update(User=User)
app.jinja_env.globals.update(Team=Team)
# Add in JinjaEnums
# The reason this exists is that on double import, JinjaEnums are not reinitialized
# Thus, if you try to create two jinja envs (e.g. during testing), sometimes
# an Enum will not be available to Jinja.
# Instead we can just directly grab them from the persisted global dictionary.
for k, v in JINJA_ENUMS.items():
# .update() can't be used here because it would use the literal value k
app.jinja_env.globals[k] = v
def init_logs(app):
logger_submissions = logging.getLogger("submissions")

View File

@@ -1,6 +1,8 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from flask_caching import make_template_fragment_key
from CTFd.cache import clear_standings
from tests.helpers import (
create_ctfd,
@@ -40,13 +42,19 @@ def test_scoreboard_is_cached():
assert app.cache.get("view/api.scoreboard_scoreboard_detail")
# Check scoreboard page
assert app.cache.get("view/scoreboard.listing") is None
assert (
app.cache.get(make_template_fragment_key("public_scoreboard_table"))
is None
)
client.get("/scoreboard")
assert app.cache.get("view/scoreboard.listing")
assert app.cache.get(make_template_fragment_key("public_scoreboard_table"))
# Empty standings and check that the cached data is gone
clear_standings()
assert app.cache.get("view/api.scoreboard_scoreboard_list") is None
assert app.cache.get("view/api.scoreboard_scoreboard_detail") is None
assert app.cache.get("view/scoreboard.listing") is None
assert (
app.cache.get(make_template_fragment_key("public_scoreboard_table"))
is None
)
destroy_ctfd(app)