Add statuses to timelines

This commit is contained in:
Mouse Reeve 2021-03-22 14:11:23 -07:00
parent ebc01362e6
commit 459479db43
4 changed files with 95 additions and 15 deletions

View file

@ -34,7 +34,7 @@ class BookWyrmModel(models.Model):
@receiver(models.signals.post_save) @receiver(models.signals.post_save)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def execute_after_save(sender, instance, created, *args, **kwargs): def set_remote_id(sender, instance, created, *args, **kwargs):
""" set the remote_id after save (when the id is available) """ """ set the remote_id after save (when the id is available) """
if not created or not hasattr(instance, "get_remote_id"): if not created or not hasattr(instance, "get_remote_id"):
return return

View file

@ -5,11 +5,14 @@ import re
from django.apps import apps from django.apps import apps
from django.core.validators import MaxValueValidator, MinValueValidator from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models from django.db import models
from django.db.models import Q
from django.dispatch import receiver
from django.template.loader import get_template from django.template.loader import get_template
from django.utils import timezone from django.utils import timezone
from model_utils.managers import InheritanceManager from model_utils.managers import InheritanceManager
import redis
from bookwyrm import activitypub from bookwyrm import activitypub, settings
from .activitypub_mixin import ActivitypubMixin, ActivityMixin from .activitypub_mixin import ActivitypubMixin, ActivityMixin
from .activitypub_mixin import OrderedCollectionPageMixin from .activitypub_mixin import OrderedCollectionPageMixin
from .base_model import BookWyrmModel from .base_model import BookWyrmModel
@ -17,6 +20,10 @@ from .fields import image_serializer
from .readthrough import ProgressMode from .readthrough import ProgressMode
from . import fields from . import fields
r = redis.Redis(
host=settings.REDIS_ACTIVITY_HOST, port=settings.REDIS_ACTIVITY_PORT, db=0
)
class Status(OrderedCollectionPageMixin, BookWyrmModel): class Status(OrderedCollectionPageMixin, BookWyrmModel):
""" any post, like a reply to a review, etc """ """ any post, like a reply to a review, etc """
@ -383,3 +390,71 @@ class Boost(ActivityMixin, Status):
# This constraint can't work as it would cross tables. # This constraint can't work as it would cross tables.
# class Meta: # class Meta:
# unique_together = ('user', 'boosted_status') # unique_together = ('user', 'boosted_status')
@receiver(models.signals.post_save)
# pylint: disable=unused-argument
def update_feeds(sender, instance, created, *args, **kwargs):
""" add statuses to activity feeds """
# we're only interested in new statuses that aren't dms
if not created or not issubclass(sender, Status) or instance.privacy == 'direct':
return
user = instance.user
community = user.__class__.objects.filter(
local=True # we only manage timelines for local users
).exclude(
Q(id__in=user.blocks.all()) | Q(blocks=user) # not blocked
)
# ------ home timeline: users you follow and yourself
friends = community.filter(
Q(id=user.id) | Q(following=user)
)
add_status(friends, instance, 'home')
# local and federated timelines only get public statuses
if instance.privacy != 'public':
return
# ------ federated timeline: to anyone, anywhere
add_status(community, instance, 'federated')
# if the author is a remote user, it doesn't go on the local timeline
if not user.local:
return
# ------ local timeline: to anyone, anywhere
add_status(community, instance, 'local')
def add_status(users, status, feed_name):
""" add a status to users' feeds """
# we want to do this as a bulk operation
pipeline = r.pipeline()
value = {status.id: status.published_date.timestamp()}
for user in users:
feed_id = '{}-{}'.format(user.id, feed_name)
unread_feed_id = '{}-unread'.format(feed_id)
# add the status to the feed
pipeline.zadd(feed_id, value)
# add to the unread status count
pipeline.incr(unread_feed_id)
pipeline.execute()
def get_activity_stream(user, feed_name, start, end):
""" load the ids for statuses to be displayed """
feed_id = '{}-{}'.format(user.id, feed_name)
unread_feed_id = '{}-unread'.format(feed_id)
# clear unreads for this feed
r.set(unread_feed_id, 0)
statuses = r.zrange(feed_id, start, end)
return Status.objects.select_subclasses().filter(
id__in=statuses
).order_by('-published_date')

View file

@ -92,10 +92,12 @@ TEMPLATES = [
WSGI_APPLICATION = "bookwyrm.wsgi.application" WSGI_APPLICATION = "bookwyrm.wsgi.application"
# redis # redis/activity streams settings
REDIS_ACTIVITY_HOST = env("REDIS_ACTIVITY_HOST", "localhost") REDIS_ACTIVITY_HOST = env("REDIS_ACTIVITY_HOST", "localhost")
REDIS_ACTIVITY_PORT = env("REDIS_ACTIVITY_PORT", 6379) REDIS_ACTIVITY_PORT = env("REDIS_ACTIVITY_PORT", 6379)
MAX_STREAM_LENGTH = env("MAX_STREAM_LENGTH", 200)
# Database # Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases # https://docs.djangoproject.com/en/2.0/ref/settings/#databases

View file

@ -28,19 +28,22 @@ class Feed(View):
except ValueError: except ValueError:
page = 1 page = 1
if tab == "home": try:
activities = get_activity_feed(request.user, following_only=True) tab_title = {
'home': _("Home"),
"local": _("Local"),
"federated": _("Federated")
}[tab]
except KeyError:
tab = 'home'
tab_title = _("Home") tab_title = _("Home")
elif tab == "local":
activities = get_activity_feed( activities = models.status.get_activity_stream(
request.user, privacy=["public", "followers"], local_only=True request.user, tab,
(1 - page) * PAGE_LENGTH,
page * PAGE_LENGTH
) )
tab_title = _("Local")
else:
activities = get_activity_feed(
request.user, privacy=["public", "followers"]
)
tab_title = _("Federated")
paginated = Paginator(activities, PAGE_LENGTH) paginated = Paginator(activities, PAGE_LENGTH)
data = { data = {