From 2cca9fab2d880b6f16ae13d2923de89abe239270 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 8 Jan 2022 12:05:42 -0800 Subject: [PATCH 1/8] Cache user relationship for follow buttons --- .../templates/snippets/follow_button.html | 16 +++++++++---- bookwyrm/templatetags/interaction.py | 24 +++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/bookwyrm/templates/snippets/follow_button.html b/bookwyrm/templates/snippets/follow_button.html index f7025bba..0482bde0 100644 --- a/bookwyrm/templates/snippets/follow_button.html +++ b/bookwyrm/templates/snippets/follow_button.html @@ -1,13 +1,18 @@ {% load i18n %} +{% load interaction %} {% if request.user == user or not request.user.is_authenticated %} -{% elif user in request.user.blocks.all %} +{# nothing to see here -- either it's yourself or your logged out #} +{% else %} + +{% get_relationship user as relationship %} +{% if relationship.is_blocked %} {% include 'snippets/block_button.html' with blocks=True %} {% else %}
- -
{% endif %}
+ +{% endif %} + {% endif %} diff --git a/bookwyrm/templatetags/interaction.py b/bookwyrm/templatetags/interaction.py index 90309aaf..7aaaaca7 100644 --- a/bookwyrm/templatetags/interaction.py +++ b/bookwyrm/templatetags/interaction.py @@ -32,3 +32,27 @@ def get_user_boosted(user, status): def get_user_saved_lists(user, book_list): """did the user save a list""" return user.saved_lists.filter(id=book_list.id).exists() + +@register.simple_tag(takes_context=True) +def get_relationship(context, user_object): + """caches the relationship between the logged in user and another user""" + user = context["request"].user + return cache.get(f"relationship-{user.id}-{user_object.id}") or cache.set( + get_relationship_name(user, user_object), + timeout=259200, + ) + +def get_relationship_name(user, user_object): + """returns the relationship type""" + types = { + "is_following": False, + "is_follow_pending": False, + "is_blocked": False, + } + if user_object in user.blocks.all(): + types["is_blocked"] = True + elif user_object in user.following.all(): + types["is_following"] = True + elif user_object in user.follower_requests.all(): + types["is_follow_pending"] = True + return types From f2f40cf3b9a53b3c2a7f4e506880daef8cbd1a84 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 8 Jan 2022 12:59:56 -0800 Subject: [PATCH 2/8] Creates custom get_or_set function --- bookwyrm/templatetags/interaction.py | 26 +++++++++++++++++--------- bookwyrm/utils/cache.py | 10 ++++++++++ 2 files changed, 27 insertions(+), 9 deletions(-) create mode 100644 bookwyrm/utils/cache.py diff --git a/bookwyrm/templatetags/interaction.py b/bookwyrm/templatetags/interaction.py index 7aaaaca7..c9f08fb3 100644 --- a/bookwyrm/templatetags/interaction.py +++ b/bookwyrm/templatetags/interaction.py @@ -1,8 +1,8 @@ """ template filters for status interaction buttons """ from django import template -from django.core.cache import cache from bookwyrm import models +from bookwyrm.utils.cache import get_or_set register = template.Library() @@ -11,20 +11,23 @@ register = template.Library() @register.filter(name="liked") def get_user_liked(user, status): """did the given user fav a status?""" - return cache.get_or_set( + return get_or_set( f"fav-{user.id}-{status.id}", - models.Favorite.objects.filter(user=user, status=status).exists(), - 259200, + lambda u, s: models.Favorite.objects.filter(user=u, status=s).exists(), + user, + status, + timeout=259200 ) @register.filter(name="boosted") def get_user_boosted(user, status): """did the given user fav a status?""" - return cache.get_or_set( + return get_or_set( f"boost-{user.id}-{status.id}", - status.boosters.filter(user=user).exists(), - 259200, + lambda u: status.boosters.filter(user=u).exists(), + user, + timeout=259200, ) @@ -33,15 +36,20 @@ def get_user_saved_lists(user, book_list): """did the user save a list""" return user.saved_lists.filter(id=book_list.id).exists() + @register.simple_tag(takes_context=True) def get_relationship(context, user_object): """caches the relationship between the logged in user and another user""" user = context["request"].user - return cache.get(f"relationship-{user.id}-{user_object.id}") or cache.set( - get_relationship_name(user, user_object), + return get_or_set( + f"relationship-{user.id}-{user_object.id}", + get_relationship_name, + user, + user_object, timeout=259200, ) + def get_relationship_name(user, user_object): """returns the relationship type""" types = { diff --git a/bookwyrm/utils/cache.py b/bookwyrm/utils/cache.py new file mode 100644 index 00000000..2fca1264 --- /dev/null +++ b/bookwyrm/utils/cache.py @@ -0,0 +1,10 @@ +""" Custom handler for caching """ +from django.core.cache import cache + + +def get_or_set(cache_key, function, *args, timeout=None): + """Django's built-in get_or_set isn't cutting it""" + value = cache.get(cache_key) + if value is None: + cache.set(cache_key, function(*args), timeout=timeout) + return cache.get(cache_key) From c8220485092f8305e9825a84c6d866361a65aa9d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 8 Jan 2022 13:04:01 -0800 Subject: [PATCH 3/8] Invalidate template cache on relationship change --- bookwyrm/models/relationship.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/bookwyrm/models/relationship.py b/bookwyrm/models/relationship.py index 03417454..2b8f240d 100644 --- a/bookwyrm/models/relationship.py +++ b/bookwyrm/models/relationship.py @@ -1,7 +1,6 @@ """ defines relationships between users """ from django.apps import apps from django.core.cache import cache -from django.core.cache.utils import make_template_fragment_key from django.db import models, transaction, IntegrityError from django.db.models import Q @@ -41,15 +40,10 @@ class UserRelationship(BookWyrmModel): def save(self, *args, **kwargs): """clear the template cache""" # invalidate the template cache - cache_keys = [ - make_template_fragment_key( - "follow_button", [self.user_subject.id, self.user_object.id] - ), - make_template_fragment_key( - "follow_button", [self.user_object.id, self.user_subject.id] - ), - ] - cache.delete_many(cache_keys) + cache.delete_many([ + f"relationship-{self.user_subject.id}-{self.user_object.id}", + f"relationship-{self.user_object.id}-{self.user_subject.id}", + ]) super().save(*args, **kwargs) class Meta: From 82294909a8403e05e1d7b60407230f4ab956a588 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 8 Jan 2022 16:38:52 -0800 Subject: [PATCH 4/8] Python formatting --- bookwyrm/models/readthrough.py | 2 ++ bookwyrm/models/relationship.py | 10 +++--- bookwyrm/templatetags/bookwyrm_tags.py | 45 +++++++++++++------------- bookwyrm/templatetags/interaction.py | 2 +- bookwyrm/views/reading.py | 14 ++++---- 5 files changed, 39 insertions(+), 34 deletions(-) diff --git a/bookwyrm/models/readthrough.py b/bookwyrm/models/readthrough.py index f75918ac..ceb8e0b6 100644 --- a/bookwyrm/models/readthrough.py +++ b/bookwyrm/models/readthrough.py @@ -1,5 +1,6 @@ """ progress in a book """ from django.core import validators +from django.core.cache import cache from django.db import models from django.db.models import F, Q @@ -30,6 +31,7 @@ class ReadThrough(BookWyrmModel): def save(self, *args, **kwargs): """update user active time""" + cache.delete(f"latest_read_through-{self.user.id}-{self.book.id}") self.user.update_active_date() # an active readthrough must have an unset finish date if self.finish_date: diff --git a/bookwyrm/models/relationship.py b/bookwyrm/models/relationship.py index 2b8f240d..e95c38fa 100644 --- a/bookwyrm/models/relationship.py +++ b/bookwyrm/models/relationship.py @@ -40,10 +40,12 @@ class UserRelationship(BookWyrmModel): def save(self, *args, **kwargs): """clear the template cache""" # invalidate the template cache - cache.delete_many([ - f"relationship-{self.user_subject.id}-{self.user_object.id}", - f"relationship-{self.user_object.id}-{self.user_subject.id}", - ]) + cache.delete_many( + [ + f"relationship-{self.user_subject.id}-{self.user_object.id}", + f"relationship-{self.user_object.id}-{self.user_subject.id}", + ] + ) super().save(*args, **kwargs) class Meta: diff --git a/bookwyrm/templatetags/bookwyrm_tags.py b/bookwyrm/templatetags/bookwyrm_tags.py index f173e052..22f4225b 100644 --- a/bookwyrm/templatetags/bookwyrm_tags.py +++ b/bookwyrm/templatetags/bookwyrm_tags.py @@ -3,6 +3,7 @@ from django import template from django.db.models import Avg from bookwyrm import models +from bookwyrm.utils import cache from bookwyrm.views.feed import get_suggested_books @@ -79,35 +80,35 @@ def related_status(notification): @register.simple_tag(takes_context=True) def active_shelf(context, book): """check what shelf a user has a book on, if any""" - if hasattr(book, "current_shelves"): - read_shelves = [ - s - for s in book.current_shelves - if s.shelf.identifier in models.Shelf.READ_STATUS_IDENTIFIERS - ] - return read_shelves[0] if len(read_shelves) else {"book": book} - - shelf = ( - models.ShelfBook.objects.filter( - shelf__user=context["request"].user, - book__parent_work__editions=book, + user = context["request"].user + return cache.get_or_set( + f"active_shelf-{user.id}-{book.id}", + lambda u, b: ( + models.ShelfBook.objects.filter( + shelf__user=u, + book__parent_work__editions=b, + ).first() ) - .select_related("book", "shelf") - .first() + or {"book": book}, + user, + book, + timeout=15552000, ) - return shelf if shelf else {"book": book} @register.simple_tag(takes_context=False) def latest_read_through(book, user): """the most recent read activity""" - if hasattr(book, "active_readthroughs"): - return book.active_readthroughs[0] if len(book.active_readthroughs) else None - - return ( - models.ReadThrough.objects.filter(user=user, book=book, is_active=True) - .order_by("-start_date") - .first() + return cache.get_or_set( + f"latest_read_through-{user.id}-{book.id}", + lambda u, b: ( + models.ReadThrough.objects.filter(user=u, book=b, is_active=True) + .order_by("-start_date") + .first() + ), + user, + book, + timeout=15552000, ) diff --git a/bookwyrm/templatetags/interaction.py b/bookwyrm/templatetags/interaction.py index c9f08fb3..89a25420 100644 --- a/bookwyrm/templatetags/interaction.py +++ b/bookwyrm/templatetags/interaction.py @@ -16,7 +16,7 @@ def get_user_liked(user, status): lambda u, s: models.Favorite.objects.filter(user=u, status=s).exists(), user, status, - timeout=259200 + timeout=259200, ) diff --git a/bookwyrm/views/reading.py b/bookwyrm/views/reading.py index c7eda10e..77e527f3 100644 --- a/bookwyrm/views/reading.py +++ b/bookwyrm/views/reading.py @@ -1,7 +1,6 @@ """ the good stuff! the books! """ from django.contrib.auth.decorators import login_required from django.core.cache import cache -from django.core.cache.utils import make_template_fragment_key from django.db import transaction from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotFound from django.shortcuts import get_object_or_404, redirect @@ -46,12 +45,13 @@ class ReadingStatus(View): if not identifier: return HttpResponseBadRequest() - # invalidate the template cache - cache_keys = [ - make_template_fragment_key("shelve_button", [request.user.id, book_id]), - make_template_fragment_key("suggested_books", [request.user.id]), - ] - cache.delete_many(cache_keys) + # invalidate related caches + cache.delete_many( + [ + f"suggested_books-{request.user.id}", + f"active_shelf-{request.user.id}-{book_id}", + ] + ) desired_shelf = get_object_or_404( models.Shelf, identifier=identifier, user=request.user From 593d1638f969f0d872676a2ce3f294444896bc87 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 9 Jan 2022 12:04:45 -0800 Subject: [PATCH 5/8] Updates tests env --- pytest.ini | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pytest.ini b/pytest.ini index 9ef72449..c5cdc35d 100644 --- a/pytest.ini +++ b/pytest.ini @@ -6,13 +6,18 @@ markers = integration: marks tests as requiring external resources (deselect with '-m "not integration"') env = + SECRET_KEY = beepbeep DEBUG = false - USE_HTTPS=true + USE_HTTPS = true DOMAIN = your.domain.here BOOKWYRM_DATABASE_BACKEND = postgres MEDIA_ROOT = images/ CELERY_BROKER = "" REDIS_BROKER_PORT = 6379 + REDIS_BROKER_PASSWORD = beep + REDIS_ACTIVITY_PORT = 6379 + REDIS_ACTIVITY_PASSWORD = beep + USE_DUMMY_CACHE = true FLOWER_PORT = 8888 EMAIL_HOST = "smtp.mailgun.org" EMAIL_PORT = 587 @@ -20,4 +25,3 @@ env = EMAIL_HOST_PASSWORD = "" EMAIL_USE_TLS = true ENABLE_PREVIEW_IMAGES = false - USE_S3 = false From 556c9ea98fc8d7fd8e3c1afeab68cc3ee164a846 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 9 Jan 2022 12:16:01 -0800 Subject: [PATCH 6/8] Adjusts cache get_or_set to work with tests --- bookwyrm/utils/cache.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bookwyrm/utils/cache.py b/bookwyrm/utils/cache.py index 2fca1264..aebb8e75 100644 --- a/bookwyrm/utils/cache.py +++ b/bookwyrm/utils/cache.py @@ -6,5 +6,6 @@ def get_or_set(cache_key, function, *args, timeout=None): """Django's built-in get_or_set isn't cutting it""" value = cache.get(cache_key) if value is None: - cache.set(cache_key, function(*args), timeout=timeout) - return cache.get(cache_key) + value = function(*args) + cache.set(cache_key, value, timeout=timeout) + return value From e8c830750a92c70f1bfca02caeceebc889d990d3 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 9 Jan 2022 13:00:02 -0800 Subject: [PATCH 7/8] No cache for suggested books --- bookwyrm/templates/landing/landing.html | 3 ++- bookwyrm/views/reading.py | 7 ++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/bookwyrm/templates/landing/landing.html b/bookwyrm/templates/landing/landing.html index 759e8c61..985a7c6d 100644 --- a/bookwyrm/templates/landing/landing.html +++ b/bookwyrm/templates/landing/landing.html @@ -7,7 +7,8 @@

{% trans "Recent Books" %}

-{% cache 60 * 60 %} +{% get_current_language as LANGUAGE_CODE %} +{% cache 60 * 60 LANGUAGE_CODE %}
diff --git a/bookwyrm/views/reading.py b/bookwyrm/views/reading.py index 77e527f3..fd12dc0f 100644 --- a/bookwyrm/views/reading.py +++ b/bookwyrm/views/reading.py @@ -46,11 +46,8 @@ class ReadingStatus(View): return HttpResponseBadRequest() # invalidate related caches - cache.delete_many( - [ - f"suggested_books-{request.user.id}", - f"active_shelf-{request.user.id}-{book_id}", - ] + cache.delete( + f"active_shelf-{request.user.id}-{book_id}", ) desired_shelf = get_object_or_404( From 0a182e81509694910d0d9aa3dde1cf3717f6a130 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 9 Jan 2022 13:04:41 -0800 Subject: [PATCH 8/8] Caches query for landing page books --- bookwyrm/templates/landing/landing.html | 3 +++ bookwyrm/templatetags/bookwyrm_tags.py | 18 ++++++++++++++++++ bookwyrm/views/admin/invite.py | 2 -- bookwyrm/views/helpers.py | 18 ------------------ bookwyrm/views/landing/landing.py | 2 -- 5 files changed, 21 insertions(+), 22 deletions(-) diff --git a/bookwyrm/templates/landing/landing.html b/bookwyrm/templates/landing/landing.html index 985a7c6d..c3771759 100644 --- a/bookwyrm/templates/landing/landing.html +++ b/bookwyrm/templates/landing/landing.html @@ -1,6 +1,8 @@ {% extends 'landing/layout.html' %} {% load i18n %} {% load cache %} +{% load bookwyrm_tags %} + {% block panel %}
@@ -9,6 +11,7 @@ {% get_current_language as LANGUAGE_CODE %} {% cache 60 * 60 LANGUAGE_CODE %} +{% get_landing_books as books %}
diff --git a/bookwyrm/templatetags/bookwyrm_tags.py b/bookwyrm/templatetags/bookwyrm_tags.py index 22f4225b..68ba747d 100644 --- a/bookwyrm/templatetags/bookwyrm_tags.py +++ b/bookwyrm/templatetags/bookwyrm_tags.py @@ -112,6 +112,24 @@ def latest_read_through(book, user): ) +@register.simple_tag(takes_context=False) +def get_landing_books(): + """list of books for the landing page""" + return list( + set( + models.Edition.objects.filter( + review__published_date__isnull=False, + review__deleted=False, + review__user__local=True, + review__privacy__in=["public", "unlisted"], + ) + .exclude(cover__exact="") + .distinct() + .order_by("-review__published_date")[:6] + ) + ) + + @register.simple_tag(takes_context=True) def mutuals_count(context, user): """how many users that you follow, follow them""" diff --git a/bookwyrm/views/admin/invite.py b/bookwyrm/views/admin/invite.py index 8fd68b70..322c5fcb 100644 --- a/bookwyrm/views/admin/invite.py +++ b/bookwyrm/views/admin/invite.py @@ -16,7 +16,6 @@ from django.views.decorators.http import require_POST from bookwyrm import emailing, forms, models from bookwyrm.settings import PAGE_LENGTH -from bookwyrm.views import helpers # pylint: disable= no-self-use @@ -174,7 +173,6 @@ class InviteRequest(View): data = { "request_form": form, "request_received": received, - "books": helpers.get_landing_books(), } return TemplateResponse(request, "landing/landing.html", data) diff --git a/bookwyrm/views/helpers.py b/bookwyrm/views/helpers.py index 8cc0aea8..74d867b6 100644 --- a/bookwyrm/views/helpers.py +++ b/bookwyrm/views/helpers.py @@ -153,24 +153,6 @@ def is_blocked(viewer, user): return False -def get_landing_books(): - """list of books for the landing page""" - - return list( - set( - models.Edition.objects.filter( - review__published_date__isnull=False, - review__deleted=False, - review__user__local=True, - review__privacy__in=["public", "unlisted"], - ) - .exclude(cover__exact="") - .distinct() - .order_by("-review__published_date")[:6] - ) - ) - - def load_date_in_user_tz_as_utc(date_str: str, user: models.User) -> datetime: """ensures that data is stored consistently in the UTC timezone""" if not date_str: diff --git a/bookwyrm/views/landing/landing.py b/bookwyrm/views/landing/landing.py index c8bba066..4e9aba58 100644 --- a/bookwyrm/views/landing/landing.py +++ b/bookwyrm/views/landing/landing.py @@ -3,7 +3,6 @@ from django.template.response import TemplateResponse from django.views import View from bookwyrm import forms -from bookwyrm.views import helpers from bookwyrm.views.feed import Feed @@ -36,6 +35,5 @@ class Landing(View): data = { "register_form": forms.RegisterForm(), "request_form": forms.InviteRequestForm(), - "books": helpers.get_landing_books(), } return TemplateResponse(request, "landing/landing.html", data)