diff --git a/.env.dev.example b/.env.dev.example index 22e12de12..b65c1b023 100644 --- a/.env.dev.example +++ b/.env.dev.example @@ -26,15 +26,15 @@ POSTGRES_HOST=db MAX_STREAM_LENGTH=200 REDIS_ACTIVITY_HOST=redis_activity REDIS_ACTIVITY_PORT=6379 -#REDIS_ACTIVITY_PASSWORD=redispassword345 +REDIS_ACTIVITY_PASSWORD=redispassword345 # Redis as celery broker REDIS_BROKER_PORT=6379 -#REDIS_BROKER_PASSWORD=redispassword123 +REDIS_BROKER_PASSWORD=redispassword123 FLOWER_PORT=8888 -#FLOWER_USER=mouse -#FLOWER_PASSWORD=changeme +FLOWER_USER=mouse +FLOWER_PASSWORD=changeme EMAIL_HOST=smtp.mailgun.org EMAIL_PORT=587 diff --git a/.github/workflows/django-tests.yml b/.github/workflows/django-tests.yml index 038751935..00e08dadb 100644 --- a/.github/workflows/django-tests.yml +++ b/.github/workflows/django-tests.yml @@ -46,6 +46,8 @@ jobs: POSTGRES_HOST: 127.0.0.1 CELERY_BROKER: "" REDIS_BROKER_PORT: 6379 + REDIS_BROKER_PASSWORD: beep + USE_DUMMY_CACHE: true FLOWER_PORT: 8888 EMAIL_HOST: "smtp.mailgun.org" EMAIL_PORT: 587 diff --git a/bookwyrm/models/author.py b/bookwyrm/models/author.py index 5cc11afd6..5edac57d5 100644 --- a/bookwyrm/models/author.py +++ b/bookwyrm/models/author.py @@ -1,6 +1,8 @@ """ database schema for info about authors """ import re from django.contrib.postgres.indexes import GinIndex +from django.core.cache import cache +from django.core.cache.utils import make_template_fragment_key from django.db import models from bookwyrm import activitypub @@ -34,6 +36,17 @@ class Author(BookDataModel): ) bio = fields.HtmlField(null=True, blank=True) + def save(self, *args, **kwargs): + """clear related template caches""" + # clear template caches + if self.id: + cache_keys = [ + make_template_fragment_key("titleby", [book]) + for book in self.book_set.values_list("id", flat=True) + ] + cache.delete_many(cache_keys) + return super().save(*args, **kwargs) + @property def isni_link(self): """generate the url from the isni id""" diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index 0a551bf28..a9dd9508d 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -3,6 +3,8 @@ import re from django.contrib.postgres.search import SearchVectorField from django.contrib.postgres.indexes import GinIndex +from django.core.cache import cache +from django.core.cache.utils import make_template_fragment_key from django.db import models, transaction from django.db.models import Prefetch from django.dispatch import receiver @@ -185,6 +187,11 @@ class Book(BookDataModel): """can't be abstract for query reasons, but you shouldn't USE it""" if not isinstance(self, Edition) and not isinstance(self, Work): raise ValueError("Books should be added as Editions or Works") + + # clear template caches + cache_key = make_template_fragment_key("titleby", [self.id]) + cache.delete(cache_key) + return super().save(*args, **kwargs) def get_remote_id(self): diff --git a/bookwyrm/models/relationship.py b/bookwyrm/models/relationship.py index fc7a9df83..034174546 100644 --- a/bookwyrm/models/relationship.py +++ b/bookwyrm/models/relationship.py @@ -1,5 +1,7 @@ """ 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 @@ -36,6 +38,20 @@ class UserRelationship(BookWyrmModel): """the remote user needs to recieve direct broadcasts""" return [u for u in [self.user_subject, self.user_object] if not u.local] + 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) + super().save(*args, **kwargs) + class Meta: """relationships should be unique""" diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index c7c0a4253..ee138d979 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -82,6 +82,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): if not self.reply_parent: self.thread_id = self.id + super().save(broadcast=False, update_fields=["thread_id"]) def delete(self, *args, **kwargs): # pylint: disable=unused-argument diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index f2068a16b..5b69e3782 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -119,6 +119,28 @@ STREAMS = [ {"key": "books", "name": _("Books Timeline"), "shortname": _("Books")}, ] +# Redis cache backend +if env("USE_DUMMY_CACHE", False): + CACHES = { + "default": { + "BACKEND": "django.core.cache.backends.dummy.DummyCache", + } + } +else: + # pylint: disable=line-too-long + CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": f"redis://:{REDIS_ACTIVITY_PASSWORD}@{REDIS_ACTIVITY_HOST}:{REDIS_ACTIVITY_PORT}/0", + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + }, + } + } + + SESSION_ENGINE = "django.contrib.sessions.backends.cache" + SESSION_CACHE_ALIAS = "default" + # Database # https://docs.djangoproject.com/en/3.2/ref/settings/#databases diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index 86c181a28..0c1d59b53 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -2,7 +2,7 @@ import math import logging from django.dispatch import receiver -from django.db.models import signals, Count, Q +from django.db.models import signals, Count, Q, Case, When, IntegerField from bookwyrm import models from bookwyrm.redis_store import RedisStore, r @@ -29,6 +29,7 @@ class SuggestedUsers(RedisStore): def get_counts_from_rank(self, rank): # pylint: disable=no-self-use """calculate mutuals count and shared books count from rank""" + # pylint: disable=c-extension-no-member return { "mutuals": math.floor(rank), # "shared_books": int(1 / (-1 * (rank % 1 - 1))) - 1, @@ -84,24 +85,17 @@ class SuggestedUsers(RedisStore): def get_suggestions(self, user, local=False): """get suggestions""" values = self.get_store(self.store_id(user), withscores=True) - results = [] + annotations = [ + When(pk=int(pk), then=self.get_counts_from_rank(score)["mutuals"]) + for (pk, score) in values + ] # annotate users with mutuals and shared book counts - for user_id, rank in values: - counts = self.get_counts_from_rank(rank) - try: - user = models.User.objects.get( - id=user_id, is_active=True, bookwyrm_user=True - ) - except models.User.DoesNotExist as err: - # if this happens, the suggestions are janked way up - logger.exception(err) - continue - user.mutuals = counts["mutuals"] - if (local and user.local) or not local: - results.append(user) - if len(results) >= 5: - break - return results + users = models.User.objects.filter( + is_active=True, bookwyrm_user=True, id__in=[pk for (pk, _) in values] + ).annotate(mutuals=Case(*annotations, output_field=IntegerField(), default=0)) + if local: + users = users.filter(local=True) + return users[:5] def get_annotated_users(viewer, *args, **kwargs): @@ -119,16 +113,17 @@ def get_annotated_users(viewer, *args, **kwargs): ), distinct=True, ), - # shared_books=Count( - # "shelfbook", - # filter=Q( - # ~Q(id=viewer.id), - # shelfbook__book__parent_work__in=[ - # s.book.parent_work for s in viewer.shelfbook_set.all() - # ], - # ), - # distinct=True, - # ), + # pylint: disable=line-too-long + # shared_books=Count( + # "shelfbook", + # filter=Q( + # ~Q(id=viewer.id), + # shelfbook__book__parent_work__in=[ + # s.book.parent_work for s in viewer.shelfbook_set.all() + # ], + # ), + # distinct=True, + # ), ) ) diff --git a/bookwyrm/templates/directory/sort_filter.html b/bookwyrm/templates/directory/sort_filter.html index c7c19f6fe..344366016 100644 --- a/bookwyrm/templates/directory/sort_filter.html +++ b/bookwyrm/templates/directory/sort_filter.html @@ -3,10 +3,12 @@ {% block filter %} -
- +
+
+ +
{% endblock %} diff --git a/bookwyrm/templates/feed/layout.html b/bookwyrm/templates/feed/layout.html index 6e7ec849f..5697f2669 100644 --- a/bookwyrm/templates/feed/layout.html +++ b/bookwyrm/templates/feed/layout.html @@ -8,82 +8,7 @@
{% if user.is_authenticated %}
-
-

{% trans "Your Books" %}

- {% if not suggested_books %} -

{% trans "There are no books here right now! Try searching for a book to get started" %}

- {% else %} - {% with active_book=request.GET.book %} -
-
-
    - {% for shelf in suggested_books %} - {% if shelf.books %} - {% with shelf_counter=forloop.counter %} -
  • -

    - {% if shelf.identifier == 'to-read' %}{% trans "To Read" %} - {% elif shelf.identifier == 'reading' %}{% trans "Currently Reading" %} - {% elif shelf.identifier == 'read' %}{% trans "Read" %} - {% else %}{{ shelf.name }}{% endif %} -

    -
    - -
    -
  • - {% endwith %} - {% endif %} - {% endfor %} -
-
- {% for shelf in suggested_books %} - {% with shelf_counter=forloop.counter %} - {% for book in shelf.books %} -
- -
-
-
-

{% include 'snippets/book_titleby.html' with book=book %}

- {% include 'snippets/shelve_button/shelve_button.html' with book=book %} -
-
-
- {% trans "Close" as button_text %} - {% include 'snippets/toggle/toggle_button.html' with label=button_text controls_text="book" controls_uid=book.id class="delete" nonbutton=True pressed=True %} -
-
-
- {% include 'snippets/create_status.html' with book=book %} -
-
- {% endfor %} - {% endwith %} - {% endfor %} -
- {% endwith %} - {% endif %} -
- + {% include "feed/suggested_books.html" %} {% if goal %}
diff --git a/bookwyrm/templates/feed/suggested_books.html b/bookwyrm/templates/feed/suggested_books.html new file mode 100644 index 000000000..b2f1b5d28 --- /dev/null +++ b/bookwyrm/templates/feed/suggested_books.html @@ -0,0 +1,83 @@ +{% load i18n %} +{% load cache %} +{% load bookwyrm_tags %} + +{# 6 month cache #} +{% cache 15552000 suggested_books request.user.id %} +{% suggested_books as suggested_books %} +
+

{% trans "Your Books" %}

+ {% if not suggested_books %} +

{% trans "There are no books here right now! Try searching for a book to get started" %}

+ {% else %} + {% with active_book=request.GET.book %} +
+
+
    + {% for shelf in suggested_books %} + {% if shelf.books %} + {% with shelf_counter=forloop.counter %} +
  • +

    + {% if shelf.identifier == 'to-read' %}{% trans "To Read" %} + {% elif shelf.identifier == 'reading' %}{% trans "Currently Reading" %} + {% elif shelf.identifier == 'read' %}{% trans "Read" %} + {% else %}{{ shelf.name }}{% endif %} +

    +
    + +
    +
  • + {% endwith %} + {% endif %} + {% endfor %} +
+
+ {% for shelf in suggested_books %} + {% with shelf_counter=forloop.counter %} + {% for book in shelf.books %} +
+ +
+
+
+

{% include 'snippets/book_titleby.html' with book=book %}

+ {% include 'snippets/shelve_button/shelve_button.html' with book=book %} +
+
+
+ {% trans "Close" as button_text %} + {% include 'snippets/toggle/toggle_button.html' with label=button_text controls_text="book" controls_uid=book.id class="delete" nonbutton=True pressed=True %} +
+
+
+ {% include 'snippets/create_status.html' with book=book %} +
+
+ {% endfor %} + {% endwith %} + {% endfor %} +
+ {% endwith %} + {% endif %} +
+{% endcache %} diff --git a/bookwyrm/templates/settings/users/server_filter.html b/bookwyrm/templates/settings/users/server_filter.html index 2a4b89fdb..5879f7488 100644 --- a/bookwyrm/templates/settings/users/server_filter.html +++ b/bookwyrm/templates/settings/users/server_filter.html @@ -3,5 +3,7 @@ {% block filter %} - +
+ +
{% endblock %} diff --git a/bookwyrm/templates/settings/users/username_filter.html b/bookwyrm/templates/settings/users/username_filter.html index d7da033a9..343e61c8f 100644 --- a/bookwyrm/templates/settings/users/username_filter.html +++ b/bookwyrm/templates/settings/users/username_filter.html @@ -3,6 +3,7 @@ {% block filter %} - +
+ +
{% endblock %} - diff --git a/bookwyrm/templates/snippets/book_titleby.html b/bookwyrm/templates/snippets/book_titleby.html index 6dbaeb265..5e35e36a7 100644 --- a/bookwyrm/templates/snippets/book_titleby.html +++ b/bookwyrm/templates/snippets/book_titleby.html @@ -1,7 +1,11 @@ {% load i18n %} {% load utilities %} +{% load cache %} {% spaceless %} +{# 6 month cache #} +{% cache 15552000 titleby book.id %} + {% if book.authors.exists %} {% blocktrans trimmed with path=book.local_path title=book|book_title %} {{ title }} by @@ -10,4 +14,6 @@ {% else %} {{ book|book_title }} {% endif %} + +{% endcache %} {% endspaceless %} diff --git a/bookwyrm/templates/snippets/follow_button.html b/bookwyrm/templates/snippets/follow_button.html index 530322b94..a5c40b24b 100644 --- a/bookwyrm/templates/snippets/follow_button.html +++ b/bookwyrm/templates/snippets/follow_button.html @@ -1,4 +1,8 @@ {% load i18n %} +{% load cache %} + +{# 6 month cache #} +{% cache 15552000 follow_button request.user.id user.id %} {% if request.user == user or not request.user.is_authenticated %} {% elif user in request.user.blocks.all %} {% include 'snippets/block_button.html' with blocks=True %} @@ -42,3 +46,4 @@ {% endif %}
{% endif %} +{% endcache %} diff --git a/bookwyrm/templates/snippets/shelve_button/shelve_button.html b/bookwyrm/templates/snippets/shelve_button/shelve_button.html index 38f6be38c..0ffc708d3 100644 --- a/bookwyrm/templates/snippets/shelve_button/shelve_button.html +++ b/bookwyrm/templates/snippets/shelve_button/shelve_button.html @@ -1,7 +1,10 @@ {% load bookwyrm_tags %} {% load utilities %} +{% load cache %} {% if request.user.is_authenticated %} +{# 6 month cache #} +{% cache 15552000 shelve_button request.user.id book.id %} {% with book.id|uuid as uuid %} {% active_shelf book as active_shelf %} @@ -32,4 +35,5 @@ {% include 'snippets/reading_modals/progress_update_modal.html' with book=active_shelf.book id=modal_id readthrough=readthrough class="" %} {% endwith %} +{% endcache %} {% endif %} diff --git a/bookwyrm/templates/snippets/status/headers/generatednote.html b/bookwyrm/templates/snippets/status/headers/generatednote.html index cc684a5f2..7fc635ab2 100644 --- a/bookwyrm/templates/snippets/status/headers/generatednote.html +++ b/bookwyrm/templates/snippets/status/headers/generatednote.html @@ -1,3 +1,7 @@ +{% load cache %} + +{# Three day cache #} +{% cache 259200 generated_note_header status.id %} {% if status.content == 'wants to read' %} {% include 'snippets/status/headers/to_read.html' with book=status.mention_books.first %} {% elif status.content == 'finished reading' %} @@ -7,3 +11,4 @@ {% else %} {{ status.content }} {% endif %} +{% endcache %} diff --git a/bookwyrm/templates/snippets/status/layout.html b/bookwyrm/templates/snippets/status/layout.html index 93620a083..5cbcef208 100644 --- a/bookwyrm/templates/snippets/status/layout.html +++ b/bookwyrm/templates/snippets/status/layout.html @@ -30,38 +30,39 @@ {# nothing here #} {% elif request.user.is_authenticated %} - - - -{% if not moderation_mode %} - -{% endif %} + + + + {% if not moderation_mode %} + + {% endif %} {% else %} - {% endif %} {% endblock %} diff --git a/bookwyrm/templates/snippets/status/status_options.html b/bookwyrm/templates/snippets/status/status_options.html index 854d4779e..fdf8ac148 100644 --- a/bookwyrm/templates/snippets/status/status_options.html +++ b/bookwyrm/templates/snippets/status/status_options.html @@ -20,17 +20,21 @@ {% if status.status_type != 'GeneratedNote' and status.status_type != 'Rating' %} {% endif %} {% else %} {# things you can do to other people's statuses #}