From e2371a3cd71204a987d3e9c9083bf687ac02510c Mon Sep 17 00:00:00 2001 From: Andrew Godwin Date: Wed, 21 Dec 2022 19:47:48 +0000 Subject: [PATCH] Move timelines to a service class --- activities/services/__init__.py | 1 + activities/services/timeline.py | 89 +++++++++++++++++++++++++++++++++ activities/views/timelines.py | 74 +++------------------------ api/views/notifications.py | 10 ++-- api/views/timelines.py | 43 +++------------- 5 files changed, 108 insertions(+), 109 deletions(-) create mode 100644 activities/services/timeline.py diff --git a/activities/services/__init__.py b/activities/services/__init__.py index f5afa1f..b1f1430 100644 --- a/activities/services/__init__.py +++ b/activities/services/__init__.py @@ -1,2 +1,3 @@ from .post import PostService # noqa from .search import SearchService # noqa +from .timeline import TimelineService # noqa diff --git a/activities/services/timeline.py b/activities/services/timeline.py new file mode 100644 index 0000000..b013228 --- /dev/null +++ b/activities/services/timeline.py @@ -0,0 +1,89 @@ +from django.db import models + +from activities.models import Hashtag, Post, TimelineEvent +from users.models import Identity + + +class TimelineService: + """ + Timelines and stuff! + """ + + def __init__(self, identity: Identity): + self.identity = identity + + def home(self) -> models.QuerySet[TimelineEvent]: + return ( + TimelineEvent.objects.filter( + identity=self.identity, + type__in=[TimelineEvent.Types.post, TimelineEvent.Types.boost], + ) + .select_related( + "subject_post", + "subject_post__author", + "subject_post__author__domain", + "subject_identity", + "subject_identity__domain", + "subject_post_interaction", + "subject_post_interaction__identity", + "subject_post_interaction__identity__domain", + ) + .prefetch_related( + "subject_post__attachments", + "subject_post__mentions", + "subject_post__emojis", + ) + .order_by("-published") + ) + + def local(self) -> models.QuerySet[Post]: + return ( + Post.objects.local_public() + .not_hidden() + .filter(author__restriction=Identity.Restriction.none) + .select_related("author", "author__domain") + .prefetch_related("attachments", "mentions", "emojis") + .order_by("-published") + ) + + def federated(self) -> models.QuerySet[Post]: + return ( + Post.objects.public() + .not_hidden() + .filter(author__restriction=Identity.Restriction.none) + .select_related("author", "author__domain") + .prefetch_related("attachments", "mentions", "emojis") + .order_by("-published") + ) + + def hashtag(self, hashtag: str | Hashtag) -> models.QuerySet[Post]: + return ( + Post.objects.public() + .not_hidden() + .filter(author__restriction=Identity.Restriction.none) + .tagged_with(hashtag) + .select_related("author", "author__domain") + .prefetch_related("attachments", "mentions") + .order_by("-published") + ) + + def notifications(self, types: list[str]) -> models.QuerySet[TimelineEvent]: + return ( + TimelineEvent.objects.filter(identity=self.identity, type__in=types) + .order_by("-published") + .select_related( + "subject_post", + "subject_post__author", + "subject_post__author__domain", + "subject_identity", + "subject_identity__domain", + "subject_post_interaction", + "subject_post_interaction__identity", + "subject_post_interaction__identity__domain", + ) + .prefetch_related( + "subject_post__emojis", + "subject_post__mentions", + "subject_post__attachments", + ) + ) diff --git a/activities/views/timelines.py b/activities/views/timelines.py index ae66a17..ff77826 100644 --- a/activities/views/timelines.py +++ b/activities/views/timelines.py @@ -3,10 +3,10 @@ from django.shortcuts import get_object_or_404, redirect from django.utils.decorators import method_decorator from django.views.generic import ListView, TemplateView -from activities.models import Hashtag, Post, PostInteraction, TimelineEvent +from activities.models import Hashtag, PostInteraction, TimelineEvent +from activities.services import TimelineService from core.decorators import cache_page from users.decorators import identity_required -from users.models import Identity from .compose import Compose @@ -22,28 +22,7 @@ class Home(TemplateView): return self.form_class(request=self.request, **self.get_form_kwargs()) def get_context_data(self): - events = ( - TimelineEvent.objects.filter( - identity=self.request.identity, - type__in=[TimelineEvent.Types.post, TimelineEvent.Types.boost], - ) - .select_related( - "subject_post", - "subject_post__author", - "subject_post__author__domain", - "subject_identity", - "subject_identity__domain", - "subject_post_interaction", - "subject_post_interaction__identity", - "subject_post_interaction__identity__domain", - ) - .prefetch_related( - "subject_post__attachments", - "subject_post__mentions", - "subject_post__emojis", - ) - .order_by("-published") - ) + events = TimelineService(self.request.identity).home() paginator = Paginator(events, 50) page_number = self.request.GET.get("page") context = { @@ -80,14 +59,7 @@ class Tag(ListView): return super().get(request, *args, **kwargs) def get_queryset(self): - return ( - Post.objects.public() - .filter(author__restriction=Identity.Restriction.none) - .tagged_with(self.hashtag) - .select_related("author", "author__domain") - .prefetch_related("attachments", "mentions") - .order_by("-published") - ) + return TimelineService(self.request.identity).hashtag(self.hashtag) def get_context_data(self): context = super().get_context_data() @@ -111,13 +83,7 @@ class Local(ListView): paginate_by = 50 def get_queryset(self): - return ( - Post.objects.local_public() - .filter(author__restriction=Identity.Restriction.none) - .select_related("author", "author__domain") - .prefetch_related("attachments", "mentions", "emojis") - .order_by("-published") - ) + return TimelineService(self.request.identity).local() def get_context_data(self): context = super().get_context_data() @@ -138,15 +104,7 @@ class Federated(ListView): paginate_by = 50 def get_queryset(self): - return ( - Post.objects.filter( - visibility=Post.Visibilities.public, in_reply_to__isnull=True - ) - .filter(author__restriction=Identity.Restriction.none) - .select_related("author", "author__domain") - .prefetch_related("attachments", "mentions", "emojis") - .order_by("-published") - ) + return TimelineService(self.request.identity).federated() def get_context_data(self): context = super().get_context_data() @@ -187,25 +145,7 @@ class Notifications(ListView): for type_name, type in self.notification_types.items(): if notification_options.get(type_name, True): types.append(type) - return ( - TimelineEvent.objects.filter(identity=self.request.identity, type__in=types) - .order_by("-published") - .select_related( - "subject_post", - "subject_post__author", - "subject_post__author__domain", - "subject_identity", - "subject_identity__domain", - "subject_post_interaction", - "subject_post_interaction__identity", - "subject_post_interaction__identity__domain", - ) - .prefetch_related( - "subject_post__emojis", - "subject_post__mentions", - "subject_post__attachments", - ) - ) + return TimelineService(self.request.identity).notifications(types) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) diff --git a/api/views/notifications.py b/api/views/notifications.py index ccc8a15..ac70be6 100644 --- a/api/views/notifications.py +++ b/api/views/notifications.py @@ -1,4 +1,5 @@ from activities.models import PostInteraction, TimelineEvent +from activities.services import TimelineService from api import schemas from api.decorators import identity_required from api.pagination import MastodonPaginator @@ -28,13 +29,8 @@ def notifications( requested_types = set(base_types.keys()) requested_types.difference_update(excluded_types) # Use that to pull relevant events - queryset = ( - TimelineEvent.objects.filter( - identity=request.identity, - type__in=[base_types[r] for r in requested_types], - ) - .order_by("-published") - .select_related("subject_post", "subject_post__author", "subject_identity") + queryset = TimelineService(request.identity).notifications( + [base_types[r] for r in requested_types] ) paginator = MastodonPaginator(TimelineEvent) events = paginator.paginate( diff --git a/api/views/timelines.py b/api/views/timelines.py index 1e3a683..090fadb 100644 --- a/api/views/timelines.py +++ b/api/views/timelines.py @@ -1,9 +1,9 @@ -from activities.models import Post, PostInteraction, TimelineEvent +from activities.models import Post, PostInteraction +from activities.services import TimelineService from api import schemas from api.decorators import identity_required from api.pagination import MastodonPaginator from api.views.base import api_router -from users.models import Identity @api_router.get("/v1/timelines/home", response=list[schemas.Status]) @@ -16,22 +16,7 @@ def home( limit: int = 20, ): paginator = MastodonPaginator(Post) - queryset = ( - TimelineEvent.objects.filter( - identity=request.identity, - type__in=[TimelineEvent.Types.post], - ) - .select_related( - "subject_post", - "subject_post__author", - "subject_post__author__domain", - ) - .prefetch_related( - "subject_post__attachments", - "subject_post__mentions", - ) - .order_by("-published") - ) + queryset = TimelineService(request.identity).home() events = paginator.paginate( queryset, min_id=min_id, @@ -58,16 +43,11 @@ def public( min_id: str | None = None, limit: int = 20, ): - queryset = ( - Post.objects.public() - .filter(author__restriction=Identity.Restriction.none) - .select_related("author") - .prefetch_related("attachments") - .order_by("-published") - ) if local: - queryset = queryset.filter(local=True) - elif remote: + queryset = TimelineService(request.identity).local() + else: + queryset = TimelineService(request.identity).federated() + if remote: queryset = queryset.filter(local=False) if only_media: queryset = queryset.filter(attachments__id__isnull=True) @@ -97,14 +77,7 @@ def hashtag( ): if limit > 40: limit = 40 - queryset = ( - Post.objects.public() - .filter(author__restriction=Identity.Restriction.none) - .tagged_with(hashtag) - .select_related("author") - .prefetch_related("attachments") - .order_by("-published") - ) + queryset = TimelineService(request.identity).hashtag(hashtag) if local: queryset = queryset.filter(local=True) if only_media: