Fetch actors with posts when needed

Fixes #190, #205
This commit is contained in:
Andrew Godwin 2022-12-20 10:17:52 +00:00
parent 0ea08768ec
commit db3fc7c53c
7 changed files with 26 additions and 7 deletions

View file

@ -23,6 +23,7 @@ from activities.models.post_types import (
from activities.templatetags.emoji_tags import imageify_emojis from activities.templatetags.emoji_tags import imageify_emojis
from core.html import sanitize_post, strip_html from core.html import sanitize_post, strip_html
from core.ld import canonicalise, format_ld_date, get_list, parse_ld_date from core.ld import canonicalise, format_ld_date, get_list, parse_ld_date
from stator.exceptions import TryAgainLater
from stator.models import State, StateField, StateGraph, StatorModel from stator.models import State, StateField, StateGraph, StatorModel
from users.models.identity import Identity, IdentityStates from users.models.identity import Identity, IdentityStates
from users.models.system_actor import SystemActor from users.models.system_actor import SystemActor
@ -686,7 +687,7 @@ class Post(StatorModel):
### ActivityPub (inbound) ### ### ActivityPub (inbound) ###
@classmethod @classmethod
def by_ap(cls, data, create=False, update=False) -> "Post": def by_ap(cls, data, create=False, update=False, fetch_author=False) -> "Post":
""" """
Retrieves a Post instance by its ActivityPub JSON object. Retrieves a Post instance by its ActivityPub JSON object.
@ -704,8 +705,16 @@ class Post(StatorModel):
if create: if create:
# Resolve the author # Resolve the author
author = Identity.by_actor_uri(data["attributedTo"], create=create) author = Identity.by_actor_uri(data["attributedTo"], create=create)
# If the author is not fetched yet, try again later
if author.domain is None:
if fetch_author:
async_to_sync(author.fetch_actor)()
if author.domain is None:
raise TryAgainLater()
else:
raise TryAgainLater()
# If the post is from a blocked domain, stop and drop # If the post is from a blocked domain, stop and drop
if author.domain and author.domain.blocked: if author.domain.blocked:
raise cls.DoesNotExist("Post is from a blocked domain") raise cls.DoesNotExist("Post is from a blocked domain")
post = cls.objects.create( post = cls.objects.create(
object_uri=data["id"], object_uri=data["id"],
@ -800,6 +809,7 @@ class Post(StatorModel):
canonicalise(response.json(), include_security=True), canonicalise(response.json(), include_security=True),
create=True, create=True,
update=True, update=True,
fetch_author=True,
) )
# We may need to fetch the author too # We may need to fetch the author too
if post.author.state == IdentityStates.outdated: if post.author.state == IdentityStates.outdated:

View file

@ -1 +1,2 @@
from .post import PostService # noqa from .post import PostService # noqa
from .search import SearchService # noqa

View file

@ -7,7 +7,7 @@ from users.models import Domain, Identity, IdentityStates
from users.models.system_actor import SystemActor from users.models.system_actor import SystemActor
class Searcher: class SearchService:
""" """
Captures the logic needed to search - reused in the UI and API Captures the logic needed to search - reused in the UI and API
""" """

View file

@ -1,7 +1,7 @@
from django import forms from django import forms
from django.views.generic import FormView from django.views.generic import FormView
from activities.search import Searcher from activities.services import SearchService
class Search(FormView): class Search(FormView):
@ -15,7 +15,7 @@ class Search(FormView):
) )
def form_valid(self, form): def form_valid(self, form):
searcher = Searcher(form.cleaned_data["query"], self.request.identity) searcher = SearchService(form.cleaned_data["query"], self.request.identity)
# Render results # Render results
context = self.get_context_data(form=form) context = self.get_context_data(form=form)
context["results"] = searcher.search_all() context["results"] = searcher.search_all()

View file

@ -3,7 +3,7 @@ from typing import Literal
from ninja import Field from ninja import Field
from activities.models import PostInteraction from activities.models import PostInteraction
from activities.search import Searcher from activities.services.search import SearchService
from api import schemas from api import schemas
from api.decorators import identity_required from api.decorators import identity_required
from api.views.base import api_router from api.views.base import api_router
@ -32,7 +32,7 @@ def search(
if max_id or since_id or min_id or offset: if max_id or since_id or min_id or offset:
return result return result
# Run search # Run search
searcher = Searcher(q, request.identity) searcher = SearchService(q, request.identity)
search_result = searcher.search_all() search_result = searcher.search_all()
if type is None or type == "accounts": if type is None or type == "accounts":
result["accounts"] = [i.to_mastodon_json() for i in search_result["identities"]] result["accounts"] = [i.to_mastodon_json() for i in search_result["identities"]]

5
stator/exceptions.py Normal file
View file

@ -0,0 +1,5 @@
class TryAgainLater(BaseException):
"""
Special exception that Stator will catch without error,
leaving a state to have another attempt soon.
"""

View file

@ -8,6 +8,7 @@ from django.utils import timezone
from django.utils.functional import classproperty from django.utils.functional import classproperty
from core import exceptions from core import exceptions
from stator.exceptions import TryAgainLater
from stator.graph import State, StateGraph from stator.graph import State, StateGraph
@ -169,6 +170,8 @@ class StatorModel(models.Model):
return None return None
try: try:
next_state = await current_state.handler(self) # type: ignore next_state = await current_state.handler(self) # type: ignore
except TryAgainLater:
pass
except BaseException as e: except BaseException as e:
await exceptions.acapture_exception(e) await exceptions.acapture_exception(e)
traceback.print_exc() traceback.print_exc()