Rework misc config to run exactly one query

There should never be that much stuff in MiscConfig, so let's just read
it all and deal with it in python instead of trying to run one query
for each item of interest and poorly cache it.

This PR also moved MiscConfigDict into a middleware so it can be used
outside of templates.
This commit is contained in:
Quantum 2023-02-22 23:03:25 -05:00 committed by Tudor Brindus
parent 6ecec69904
commit eda67c54e2
6 changed files with 61 additions and 65 deletions

View File

@ -287,6 +287,7 @@ MIDDLEWARE = (
'judge.middleware.APIMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'judge.middleware.MiscConfigMiddleware',
'judge.middleware.DMOJLoginMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',

View File

@ -6,11 +6,15 @@ from urllib.parse import quote
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.sites.shortcuts import get_current_site
from django.core.cache import cache
from django.http import HttpResponse, HttpResponseRedirect
from django.urls import Resolver404, resolve, reverse
from django.utils.encoding import force_bytes
from requests.exceptions import HTTPError
from judge.models import MiscConfig
try:
import uwsgi
except ImportError:
@ -130,3 +134,46 @@ class APIMiddleware(object):
response.status_code = 401
return response
return self.get_response(request)
class MiscConfigDict(dict):
__slots__ = ('language', 'site', 'backing')
def __init__(self, language='', domain=None):
self.language = language
self.site = domain
self.backing = None
super().__init__()
def __missing__(self, key):
if self.backing is None:
cache_key = 'misc_config'
backing = cache.get(cache_key)
if backing is None:
backing = dict(MiscConfig.objects.values_list('key', 'value'))
cache.set(cache_key, backing, 86400)
self.backing = backing
keys = ['%s.%s' % (key, self.language), key] if self.language else [key]
if self.site is not None:
keys = ['%s:%s' % (self.site, key) for key in keys] + keys
for attempt in keys:
result = self.backing.get(attempt)
if result is not None:
break
else:
result = ''
self[key] = result
return result
class MiscConfigMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
domain = get_current_site(request).domain
request.misc_config = MiscConfigDict(language=request.LANGUAGE_CODE, domain=domain)
return self.get_response(request)

View File

@ -2,10 +2,9 @@ import errno
import os
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.cache import cache
from django.core.cache.utils import make_template_fragment_key
from django.db.models.signals import post_delete, post_save, pre_save
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
from .caching import finished_submission
@ -128,35 +127,14 @@ def organization_update(sender, instance, **kwargs):
for engine in EFFECTIVE_MATH_ENGINES])
_misc_config_i18n = [code for code, _ in settings.LANGUAGES]
_misc_config_i18n.append('')
def misc_config_cache_delete(key):
cache.delete_many(['misc_config:%s:%s:%s' % (domain, lang, key.split('.')[0])
for lang in _misc_config_i18n
for domain in Site.objects.values_list('domain', flat=True)])
@receiver(pre_save, sender=MiscConfig)
def misc_config_pre_save(sender, instance, **kwargs):
try:
old_key = MiscConfig.objects.filter(id=instance.id).values_list('key').get()[0]
except MiscConfig.DoesNotExist:
old_key = None
instance._old_key = old_key
@receiver(post_save, sender=MiscConfig)
def misc_config_update(sender, instance, **kwargs):
misc_config_cache_delete(instance.key)
if instance._old_key is not None and instance._old_key != instance.key:
misc_config_cache_delete(instance._old_key)
cache.delete('misc_config')
@receiver(post_delete, sender=MiscConfig)
def misc_config_delete(sender, instance, **kwargs):
misc_config_cache_delete(instance.key)
cache.delete('misc_config')
@receiver(post_save, sender=ContestSubmission)

View File

@ -3,11 +3,10 @@ from functools import partial
from django.conf import settings
from django.contrib.auth.context_processors import PermWrapper
from django.contrib.sites.shortcuts import get_current_site
from django.core.cache import cache
from django.utils.functional import SimpleLazyObject, new_method_proxy
from judge.utils.caniuse import CanIUse, SUPPORT
from .models import MiscConfig, NavigationBar, Profile
from .models import NavigationBar, Profile
class FixedSimpleLazyObject(SimpleLazyObject):
@ -71,37 +70,8 @@ def site(request):
return {'site': get_current_site(request)}
class MiscConfigDict(dict):
__slots__ = ('language', 'site')
def __init__(self, language='', domain=None):
self.language = language
self.site = domain
super(MiscConfigDict, self).__init__()
def __missing__(self, key):
cache_key = 'misc_config:%s:%s:%s' % (self.site, self.language, key)
value = cache.get(cache_key)
if value is None:
keys = ['%s.%s' % (key, self.language), key] if self.language else [key]
if self.site is not None:
keys = ['%s:%s' % (self.site, key) for key in keys] + keys
map = dict(MiscConfig.objects.values_list('key', 'value').filter(key__in=keys))
for item in keys:
if item in map:
value = map[item]
break
else:
value = ''
cache.set(cache_key, value, 86400)
self[key] = value
return value
def misc_config(request):
domain = get_current_site(request).domain
return {'misc_config': MiscConfigDict(domain=domain),
'i18n_config': MiscConfigDict(language=request.LANGUAGE_CODE, domain=domain)}
return {'misc_config': request.misc_config}
def site_name(request):

View File

@ -281,8 +281,8 @@
<div id="content-body">{% block body %}{% endblock %}</div>
</main>
{% if i18n_config.announcement %}
<div id="announcement">{{ i18n_config.announcement|safe }}</div>
{% if misc_config.announcement %}
<div id="announcement">{{ misc_config.announcement|safe }}</div>
{% endif %}
{% block bodyend %}{% endblock %}
@ -291,8 +291,8 @@
<span id="footer-content">
<br>
<a style="color: #808080" href="https://dmoj.ca">{{ _('proudly powered by **DMOJ**')|markdown('default', strip_paragraphs=True) }}</a> |
{% if i18n_config.footer %}
{{ i18n_config.footer|safe }} |
{% if misc_config.footer %}
{{ misc_config.footer|safe }} |
{% endif %}
<form action="{{ url('set_language') }}" method="post" style="display: inline">
{% csrf_token %}

View File

@ -1,12 +1,12 @@
{% extends "blog/list.html" %}
{% block before_posts %}
{% if i18n_config.home_page_top %}
{{ render_django(i18n_config.home_page_top, request=request, user_count=user_count, problem_count=problem_count, submission_count=submission_count, language_count=language_count, perms=perms) }}
{% if misc_config.home_page_top %}
{{ render_django(misc_config.home_page_top, request=request, user_count=user_count, problem_count=problem_count, submission_count=submission_count, language_count=language_count, perms=perms) }}
{% endif %}
{% endblock %}
{% block meta %}
{% if i18n_config.meta_description %}
<meta name="description" content="{{ i18n_config['meta_description'] }}">
{% if misc_config.meta_description %}
<meta name="description" content="{{ misc_config['meta_description'] }}">
{% endif %}
<script type="application/ld+json">
{