From ed287b6ff360ff687b296497ea7a0d1840cf8dd0 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Tue, 10 Oct 2023 19:37:36 -0500 Subject: [PATCH] Add caching for user basic info --- chat_box/utils.py | 3 ++ chat_box/views.py | 6 ++- judge/admin/profile.py | 2 +- judge/caching.py | 2 +- judge/jinja2/gravatar.py | 8 ++-- judge/jinja2/reference.py | 6 +-- judge/migrations/0172_index_rating.py | 18 ++++++++ judge/models/profile.py | 57 ++++++++++++++++++++++++- judge/views/resolver.py | 7 ++- resources/comments.scss | 2 + templates/contest/contests_summary.html | 4 +- templates/contest/ranking-table.html | 8 ++-- templates/user/link.html | 2 +- templates/user/user-about.html | 8 ++-- templates/user/users-table.html | 10 ++--- 15 files changed, 110 insertions(+), 33 deletions(-) create mode 100644 judge/migrations/0172_index_rating.py diff --git a/chat_box/utils.py b/chat_box/utils.py index dd59d98..f3f27b4 100644 --- a/chat_box/utils.py +++ b/chat_box/utils.py @@ -8,6 +8,8 @@ from django.db.models.functions import Coalesce from chat_box.models import Ignore, Message, UserRoom, Room +from judge.caching import cache_wrapper + secret_key = settings.CHAT_SECRET_KEY fernet = Fernet(secret_key) @@ -37,6 +39,7 @@ def encrypt_channel(channel): ) +@cache_wrapper(prefix="gub") def get_unread_boxes(profile): ignored_rooms = Ignore.get_ignored_rooms(profile) unread_boxes = ( diff --git a/chat_box/views.py b/chat_box/views.py index 5a15f74..48fa37a 100644 --- a/chat_box/views.py +++ b/chat_box/views.py @@ -36,7 +36,7 @@ from judge.jinja2.gravatar import gravatar from judge.models import Friend from chat_box.models import Message, Profile, Room, UserRoom, Ignore -from chat_box.utils import encrypt_url, decrypt_url, encrypt_channel +from chat_box.utils import encrypt_url, decrypt_url, encrypt_channel, get_unread_boxes import json @@ -208,6 +208,7 @@ def post_message(request): ) else: Room.last_message_body.dirty(room) + for user in room.users(): event.post( encrypt_channel("chat_" + str(user.id)), @@ -223,6 +224,7 @@ def post_message(request): UserRoom.objects.filter(user=user, room=room).update( unread_count=F("unread_count") + 1 ) + get_unread_boxes.dirty(user) return JsonResponse(ret) @@ -285,6 +287,8 @@ def update_last_seen(request, **kwargs): user_room.unread_count = 0 user_room.save() + get_unread_boxes.dirty(profile) + return JsonResponse({"msg": "updated"}) diff --git a/judge/admin/profile.py b/judge/admin/profile.py index 422fc5d..68ecee1 100644 --- a/judge/admin/profile.py +++ b/judge/admin/profile.py @@ -126,7 +126,7 @@ class ProfileAdmin(VersionAdmin): admin_user_admin.short_description = _("User") def email(self, obj): - return obj.user.email + return obj.email email.admin_order_field = "user__email" email.short_description = _("Email") diff --git a/judge/caching.py b/judge/caching.py index 70b5613..b8fb810 100644 --- a/judge/caching.py +++ b/judge/caching.py @@ -40,9 +40,9 @@ def cache_wrapper(prefix, timeout=None): if result == NONE_RESULT: result = None return result + result = func(*args, **kwargs) if result is None: result = NONE_RESULT - result = func(*args, **kwargs) cache.set(cache_key, result, timeout) return result diff --git a/judge/jinja2/gravatar.py b/judge/jinja2/gravatar.py index cffa413..373f685 100644 --- a/judge/jinja2/gravatar.py +++ b/judge/jinja2/gravatar.py @@ -12,12 +12,12 @@ from . import registry def gravatar(profile, size=80, default=None, profile_image=None, email=None): if profile_image: return profile_image - if profile and profile.profile_image: - return profile.profile_image.url + if profile and profile.cached_profile_image: + return profile.cached_profile_image.url if profile: - email = email or profile.user.email + email = email or profile.email if default is None: - default = profile.mute + default = profile.is_muted gravatar_url = ( "//www.gravatar.com/avatar/" + hashlib.md5(utf8bytes(email.strip().lower())).hexdigest() diff --git a/judge/jinja2/reference.py b/judge/jinja2/reference.py index 49a6ef2..312ac4f 100644 --- a/judge/jinja2/reference.py +++ b/judge/jinja2/reference.py @@ -157,14 +157,14 @@ def item_title(item): @registry.render_with("user/link.html") def link_user(user): if isinstance(user, Profile): - user, profile = user.user, user + profile = user elif isinstance(user, AbstractUser): profile = user.profile elif type(user).__name__ == "ContestRankingProfile": - user, profile = user.user, user + profile = user else: raise ValueError("Expected profile or user, got %s" % (type(user),)) - return {"user": user, "profile": profile} + return {"profile": profile} @registry.function diff --git a/judge/migrations/0172_index_rating.py b/judge/migrations/0172_index_rating.py new file mode 100644 index 0000000..157c005 --- /dev/null +++ b/judge/migrations/0172_index_rating.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.18 on 2023-10-10 23:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("judge", "0171_update_notification"), + ] + + operations = [ + migrations.AlterField( + model_name="profile", + name="rating", + field=models.IntegerField(db_index=True, default=None, null=True), + ), + ] diff --git a/judge/models/profile.py b/judge/models/profile.py index 83bfc32..bcb6621 100644 --- a/judge/models/profile.py +++ b/judge/models/profile.py @@ -10,6 +10,9 @@ from django.urls import reverse from django.utils.functional import cached_property from django.utils.timezone import now from django.utils.translation import gettext_lazy as _ +from django.dispatch import receiver +from django.db.models.signals import post_save, pre_save + from fernet_fields import EncryptedCharField from sortedm2m.fields import SortedManyToManyField @@ -202,7 +205,7 @@ class Profile(models.Model): help_text=_("User will not be able to vote on problems' point values."), default=False, ) - rating = models.IntegerField(null=True, default=None) + rating = models.IntegerField(null=True, default=None, db_index=True) user_script = models.TextField( verbose_name=_("user script"), default="", @@ -256,6 +259,21 @@ class Profile(models.Model): max_length=300, ) + @cache_wrapper(prefix="Pgbi") + def _get_basic_info(self): + return { + "first_name": self.user.first_name, + "last_name": self.user.last_name, + "email": self.user.email, + "username": self.user.username, + "mute": self.mute, + "profile_image": self.profile_image, + } + + @cached_property + def _cached_info(self): + return self._get_basic_info() + @cached_property def organization(self): # We do this to take advantage of prefetch_related @@ -264,7 +282,27 @@ class Profile(models.Model): @cached_property def username(self): - return self.user.username + return self._cached_info["username"] + + @cached_property + def first_name(self): + return self._cached_info["first_name"] + + @cached_property + def last_name(self): + return self._cached_info["last_name"] + + @cached_property + def email(self): + return self._cached_info["email"] + + @cached_property + def is_muted(self): + return self._cached_info["mute"] + + @cached_property + def cached_profile_image(self): + return self._cached_info["profile_image"] @cached_property def count_unseen_notifications(self): @@ -499,3 +537,18 @@ class OrganizationProfile(models.Model): @classmethod def get_most_recent_organizations(self, users): return self.objects.filter(users=users).order_by("-last_visit")[:5] + + +@receiver([post_save], sender=User) +def on_user_save(sender, instance, **kwargs): + profile = instance.profile + profile._get_user.dirty(profile) + + +@receiver([pre_save], sender=Profile) +def on_profile_save(sender, instance, **kwargs): + if instance.id is None: + return + prev = sender.objects.get(id=instance.id) + if prev.mute != instance.mute or prev.profile_image != instance.profile_image: + instance._get_user.dirty(instance) diff --git a/judge/views/resolver.py b/judge/views/resolver.py index eade951..cf8a193 100644 --- a/judge/views/resolver.py +++ b/judge/views/resolver.py @@ -31,10 +31,9 @@ class Resolver(TemplateView): for participation in self.contest.users.filter(virtual=0): cnt_user += 1 users[str(cnt_user)] = { - "username": participation.user.user.username, - "name": participation.user.user.first_name - or participation.user.user.username, - "school": participation.user.user.last_name, + "username": participation.user.username, + "name": participation.user.first_name or participation.user.username, + "school": participation.user.last_name, "last_submission": participation.cumtime_final, "problems": {}, } diff --git a/resources/comments.scss b/resources/comments.scss index 42dc90b..05ef84f 100644 --- a/resources/comments.scss +++ b/resources/comments.scss @@ -40,6 +40,8 @@ a { .comment-img { display: flex; margin-right: 4px; + height: 1.5em; + width: 1.5em; } .new-comments .comment-display { diff --git a/templates/contest/contests_summary.html b/templates/contest/contests_summary.html index a7fbe12..a43fa65 100644 --- a/templates/contest/contests_summary.html +++ b/templates/contest/contests_summary.html @@ -49,9 +49,9 @@
- {{item.user.username}} + {{item.username}}
-
{{item.user.first_name}}
+
{{item.first_name}}
{% for point_contest, rank_contest in item.point_contests %} diff --git a/templates/contest/ranking-table.html b/templates/contest/ranking-table.html index e182717..18d64dd 100644 --- a/templates/contest/ranking-table.html +++ b/templates/contest/ranking-table.html @@ -13,14 +13,14 @@ {% endblock %} {% block user_footer %} - {% if user.user.first_name %} + {% if user.first_name %} {% endif %} - {% if user.user.last_name %} + {% if user.last_name %} {% endif %} {% endblock %} diff --git a/templates/user/link.html b/templates/user/link.html index f0a73bb..ea821fa 100644 --- a/templates/user/link.html +++ b/templates/user/link.html @@ -1 +1 @@ -{{ user.username }} +{{ profile.username }} diff --git a/templates/user/user-about.html b/templates/user/user-about.html index ee7f395..23c53b1 100644 --- a/templates/user/user-about.html +++ b/templates/user/user-about.html @@ -54,9 +54,9 @@ - {% if user.user.first_name %} + {% if user.first_name %}

- {{user.user.first_name}}{% if user.user.last_name %} ({{user.user.last_name}}){% endif %} + {{user.first_name}}{% if user.last_name %} ({{user.last_name}}){% endif %}

{% endif %} {% if perms.judge.change_profile %} @@ -70,9 +70,7 @@ {% endif%} {% if user.about %}

{{ _('About') }}

- {% cache 86400 'user_about' user.id MATH_ENGINE %} - {{ user.about|markdown(lazy_load=True)|reference|str|safe }} - {% endcache %} + {{ user.about|markdown(lazy_load=True)|reference|str|safe }} {% else %} {% if user.user == request.user %} diff --git a/templates/user/users-table.html b/templates/user/users-table.html index 0eb95e0..606e374 100644 --- a/templates/user/users-table.html +++ b/templates/user/users-table.html @@ -10,7 +10,7 @@ {% block user_footer %} {% if user.rating %}{{ rating_number(user) }}{% endif %} -
{{ user.user.first_name if user.user.first_name else ''}}
+
{{ user.first_name or ''}}
{% endblock %} {% block after_point_head %} @@ -39,11 +39,11 @@ {{ user.problem_count }}
- {% if user.about %} - {% cache 86400 'user_about' user.id MATH_ENGINE %} + {% cache 86400 'user_about' user.id MATH_ENGINE %} + {% if user.about %} {{ user.about|markdown(lazy_load=True)|reference|str|safe }} - {% endcache %} - {% endif %} + {% endif %} + {% endcache %}
{% endblock %}