diff --git a/.env.example b/.env.example index d2b43e17..91516422 100644 --- a/.env.example +++ b/.env.example @@ -23,16 +23,10 @@ POSTGRES_USER=fedireads POSTGRES_DB=fedireads POSTGRES_HOST=db -# Redis as activitystreams manager -REDIS_ACTIVITY_HOST=redis_activity -REDIS_ACTIVITY_PORT=6379 -REDIS_ACTIVITY_PASSWORD=redispassword345 - -# Redis as celery broker -REDIS_BROKER_PORT=6379 -REDIS_BROKER_PASSWORD=redispassword123 -CELERY_BROKER=redis://:${REDIS_BROKER_PASSWORD}@redis_broker:${REDIS_BROKER_PORT}/0 -CELERY_RESULT_BACKEND=redis://:${REDIS_BROKER_PASSWORD}@redis_broker:${REDIS_BROKER_PORT}/0 +REDIS_PORT=6379 +REDIS_PASSWORD=redispassword123 +CELERY_BROKER=redis://:${REDIS_PASSWORD}@redis:${REDIS_PORT}/0 +CELERY_RESULT_BACKEND=redis://:${REDIS_PASSWORD}@redis:${REDIS_PORT}/0 FLOWER_PORT=8888 FLOWER_USER=mouse diff --git a/README.md b/README.md index e798fedf..e6a75375 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,6 @@ Web backend - [ActivityPub](http://activitypub.rocks/) federation - [Celery](http://celeryproject.org/) task queuing - [Redis](https://redis.io/) task backend -- [Redis (again)](https://redis.io/) activity stream manager Front end - Django templates @@ -237,11 +236,6 @@ When there are changes available in the production branch, you can install and g - `docker-compose exec web python manage.py collectstatic --no-input` loads any updated static files (such as the JavaScript and CSS) - `docker-compose restart` reloads the docker containers -### Re-building activity streams - -If something goes awry with user timelines, and you want to re-create them en mass, there's a management command for that: -`docker-compose run --rm web python manage.py rebuild_feeds` - ### Port Conflicts BookWyrm has multiple services that run on their default ports. diff --git a/bookwyrm/activitystreams.py b/bookwyrm/activitystreams.py deleted file mode 100644 index 5968c5d1..00000000 --- a/bookwyrm/activitystreams.py +++ /dev/null @@ -1,281 +0,0 @@ -""" access the activity streams stored in redis """ -from abc import ABC -from django.dispatch import receiver -from django.db.models import signals, Q -import redis - -from bookwyrm import models, settings -from bookwyrm.views.helpers import privacy_filter - -r = redis.Redis( - host=settings.REDIS_ACTIVITY_HOST, - port=settings.REDIS_ACTIVITY_PORT, - password=settings.REDIS_ACTIVITY_PASSWORD, - db=0, -) - - -class ActivityStream(ABC): - """ a category of activity stream (like home, local, federated) """ - - def stream_id(self, user): - """ the redis key for this user's instance of this stream """ - return "{}-{}".format(user.id, self.key) - - def unread_id(self, user): - """ the redis key for this user's unread count for this stream """ - return "{}-unread".format(self.stream_id(user)) - - def add_status(self, status): - """ add a status to users' feeds """ - # we want to do this as a bulk operation, hence "pipeline" - pipeline = r.pipeline() - for user in self.stream_users(status): - # add the status to the feed - pipeline.lpush(self.stream_id(user), status.id) - pipeline.ltrim(self.stream_id(user), 0, settings.MAX_STREAM_LENGTH) - - # add to the unread status count - pipeline.incr(self.unread_id(user)) - # and go! - pipeline.execute() - - def remove_status(self, status): - """ remove a status from all feeds """ - pipeline = r.pipeline() - for user in self.stream_users(status): - pipeline.lrem(self.stream_id(user), -1, status.id) - pipeline.execute() - - def add_user_statuses(self, viewer, user): - """ add a user's statuses to another user's feed """ - pipeline = r.pipeline() - for status in user.status_set.all()[: settings.MAX_STREAM_LENGTH]: - pipeline.lpush(self.stream_id(viewer), status.id) - pipeline.execute() - - def remove_user_statuses(self, viewer, user): - """ remove a user's status from another user's feed """ - pipeline = r.pipeline() - for status in user.status_set.all()[: settings.MAX_STREAM_LENGTH]: - pipeline.lrem(self.stream_id(viewer), -1, status.id) - pipeline.execute() - - def get_activity_stream(self, user): - """ load the ids for statuses to be displayed """ - # clear unreads for this feed - r.set(self.unread_id(user), 0) - - statuses = r.lrange(self.stream_id(user), 0, -1) - return ( - models.Status.objects.select_subclasses() - .filter(id__in=statuses) - .order_by("-published_date") - ) - - def get_unread_count(self, user): - """ get the unread status count for this user's feed """ - return int(r.get(self.unread_id(user))) - - def populate_stream(self, user): - """ go from zero to a timeline """ - pipeline = r.pipeline() - statuses = self.stream_statuses(user) - - stream_id = self.stream_id(user) - for status in statuses.all()[: settings.MAX_STREAM_LENGTH]: - pipeline.lpush(stream_id, status.id) - pipeline.execute() - - def stream_users(self, status): # pylint: disable=no-self-use - """ given a status, what users should see it """ - # direct messages don't appeard in feeds, direct comments/reviews/etc do - if status.privacy == "direct" and status.status_type == "Note": - return None - - # everybody who could plausibly see this status - audience = models.User.objects.filter( - is_active=True, - local=True, # we only create feeds for users of this instance - ).exclude( - Q(id__in=status.user.blocks.all()) | Q(blocks=status.user) # not blocked - ) - - # only visible to the poster and mentioned users - if status.privacy == "direct": - audience = audience.filter( - Q(id=status.user.id) # if the user is the post's author - | Q(id__in=status.mention_users.all()) # if the user is mentioned - ) - # only visible to the poster's followers and tagged users - elif status.privacy == "followers": - audience = audience.filter( - Q(id=status.user.id) # if the user is the post's author - | Q(following=status.user) # if the user is following the author - ) - return audience - - def stream_statuses(self, user): # pylint: disable=no-self-use - """ given a user, what statuses should they see on this stream """ - return privacy_filter( - user, - models.Status.objects.select_subclasses(), - privacy_levels=["public", "unlisted", "followers"], - ) - - -class HomeStream(ActivityStream): - """ users you follow """ - - key = "home" - - def stream_users(self, status): - audience = super().stream_users(status) - return audience.filter( - Q(id=status.user.id) # if the user is the post's author - | Q(following=status.user) # if the user is following the author - ) - - def stream_statuses(self, user): - return privacy_filter( - user, - models.Status.objects.select_subclasses(), - privacy_levels=["public", "unlisted", "followers"], - following_only=True, - ) - - -class LocalStream(ActivityStream): - """ users you follow """ - - key = "local" - - def stream_users(self, status): - # this stream wants no part in non-public statuses - if status.privacy != "public" or not status.user.local: - return None - return super().stream_users(status) - - def stream_statuses(self, user): - # all public statuses by a local user - return privacy_filter( - user, - models.Status.objects.select_subclasses().filter(user__local=True), - privacy_levels=["public"], - ) - - -class FederatedStream(ActivityStream): - """ users you follow """ - - key = "federated" - - def stream_users(self, status): - # this stream wants no part in non-public statuses - if status.privacy != "public": - return None - return super().stream_users(status) - - def stream_statuses(self, user): - return privacy_filter( - user, - models.Status.objects.select_subclasses(), - privacy_levels=["public"], - ) - - -streams = { - "home": HomeStream(), - "local": LocalStream(), - "federated": FederatedStream(), -} - - -@receiver(signals.post_save) -# pylint: disable=unused-argument -def add_status_on_create(sender, instance, created, *args, **kwargs): - """ add newly created statuses to activity feeds """ - # we're only interested in new statuses - if not issubclass(sender, models.Status): - return - - if instance.deleted: - for stream in streams.values(): - stream.remove_status(instance) - return - - if not created: - return - - # iterates through Home, Local, Federated - for stream in streams.values(): - stream.add_status(instance) - - -@receiver(signals.post_delete, sender=models.Boost) -# pylint: disable=unused-argument -def remove_boost_on_delete(sender, instance, *args, **kwargs): - """ boosts are deleted """ - # we're only interested in new statuses - for stream in streams.values(): - stream.remove_status(instance) - - -@receiver(signals.post_save, sender=models.UserFollows) -# pylint: disable=unused-argument -def add_statuses_on_follow(sender, instance, created, *args, **kwargs): - """ add a newly followed user's statuses to feeds """ - if not created or not instance.user_subject.local: - return - HomeStream().add_user_statuses(instance.user_subject, instance.user_object) - - -@receiver(signals.post_delete, sender=models.UserFollows) -# pylint: disable=unused-argument -def remove_statuses_on_unfollow(sender, instance, *args, **kwargs): - """ remove statuses from a feed on unfollow """ - if not instance.user_subject.local: - return - HomeStream().remove_user_statuses(instance.user_subject, instance.user_object) - - -@receiver(signals.post_save, sender=models.UserBlocks) -# pylint: disable=unused-argument -def remove_statuses_on_block(sender, instance, *args, **kwargs): - """ remove statuses from all feeds on block """ - # blocks apply ot all feeds - if instance.user_subject.local: - for stream in streams.values(): - stream.remove_user_statuses(instance.user_subject, instance.user_object) - - # and in both directions - if instance.user_object.local: - for stream in streams.values(): - stream.remove_user_statuses(instance.user_object, instance.user_subject) - - -@receiver(signals.post_delete, sender=models.UserBlocks) -# pylint: disable=unused-argument -def add_statuses_on_unblock(sender, instance, *args, **kwargs): - """ remove statuses from all feeds on block """ - public_streams = [LocalStream(), FederatedStream()] - # add statuses back to streams with statuses from anyone - if instance.user_subject.local: - for stream in public_streams: - stream.add_user_statuses(instance.user_subject, instance.user_object) - - # add statuses back to streams with statuses from anyone - if instance.user_object.local: - for stream in public_streams: - stream.add_user_statuses(instance.user_object, instance.user_subject) - - -@receiver(signals.post_save, sender=models.User) -# pylint: disable=unused-argument -def populate_feed_on_account_create(sender, instance, created, *args, **kwargs): - """ build a user's feeds when they join """ - if not created or not instance.local: - return - - for stream in streams.values(): - stream.populate_stream(instance) diff --git a/bookwyrm/management/commands/rebuild_feeds.py b/bookwyrm/management/commands/rebuild_feeds.py deleted file mode 100644 index 70d4dd98..00000000 --- a/bookwyrm/management/commands/rebuild_feeds.py +++ /dev/null @@ -1,36 +0,0 @@ -""" Delete and re-create user feeds """ -from django.core.management.base import BaseCommand -import redis - -from bookwyrm import activitystreams, models, settings - -r = redis.Redis( - host=settings.REDIS_ACTIVITY_HOST, port=settings.REDIS_ACTIVITY_PORT, db=0 -) - - -def erase_feeds(): - """ throw the whole redis away """ - r.flushall() - - -def create_feeds(): - """ build all the fields for all the users """ - users = models.User.objects.filter( - local=True, - is_active=True, - ) - for user in users: - for stream in activitystreams.streams.values(): - stream.populate_stream(user) - - -class Command(BaseCommand): - """ start all over with user feeds """ - - help = "Delete and re-create all the user feeds" - # pylint: disable=no-self-use,unused-argument - def handle(self, *args, **options): - """ run feed builder """ - erase_feeds() - create_feeds() diff --git a/bookwyrm/models/base_model.py b/bookwyrm/models/base_model.py index cb2fc851..60e5da0a 100644 --- a/bookwyrm/models/base_model.py +++ b/bookwyrm/models/base_model.py @@ -34,7 +34,7 @@ class BookWyrmModel(models.Model): @receiver(models.signals.post_save) # pylint: disable=unused-argument -def set_remote_id(sender, instance, created, *args, **kwargs): +def execute_after_save(sender, instance, created, *args, **kwargs): """ set the remote_id after save (when the id is available) """ if not created or not hasattr(instance, "get_remote_id"): return diff --git a/bookwyrm/models/relationship.py b/bookwyrm/models/relationship.py index 998d7bed..df99d216 100644 --- a/bookwyrm/models/relationship.py +++ b/bookwyrm/models/relationship.py @@ -62,7 +62,7 @@ class UserFollows(ActivityMixin, UserRelationship): status = "follows" - def to_activity(self): # pylint: disable=arguments-differ + def to_activity(self): """ overrides default to manually set serializer """ return activitypub.Follow(**generate_activity(self)) diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 77ac4b58..904ce461 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -114,7 +114,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): return list(set(mentions)) @classmethod - def ignore_activity(cls, activity): # pylint: disable=too-many-return-statements + def ignore_activity(cls, activity): """ keep notes if they are replies to existing statuses """ if activity.type == "Announce": try: diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index 0f010da2..bcff5828 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -92,13 +92,6 @@ TEMPLATES = [ WSGI_APPLICATION = "bookwyrm.wsgi.application" -# redis/activity streams settings -REDIS_ACTIVITY_HOST = env("REDIS_ACTIVITY_HOST", "localhost") -REDIS_ACTIVITY_PORT = env("REDIS_ACTIVITY_PORT", 6379) -REDIS_ACTIVITY_PASSWORD = env("REDIS_ACTIVITY_PASSWORD") - -MAX_STREAM_LENGTH = env("MAX_STREAM_LENGTH", 200) -STREAMS = ["home", "local", "federated"] # Database # https://docs.djangoproject.com/en/2.0/ref/settings/#databases diff --git a/bookwyrm/static/js/shared.js b/bookwyrm/static/js/shared.js index 1f26aba6..d390f482 100644 --- a/bookwyrm/static/js/shared.js +++ b/bookwyrm/static/js/shared.js @@ -61,9 +61,9 @@ function polling(el, delay) { function updateCountElement(el, data) { const currentCount = el.innerText; - const count = data.count; + const count = data[el.getAttribute('data-poll')]; if (count != currentCount) { - addRemoveClass(el.closest('[data-poll-wrapper]'), 'hidden', count < 1); + addRemoveClass(el, 'hidden', count < 1); el.innerText = count; } } diff --git a/bookwyrm/templates/feed/feed.html b/bookwyrm/templates/feed/feed.html index 379bf927..76afea7e 100644 --- a/bookwyrm/templates/feed/feed.html +++ b/bookwyrm/templates/feed/feed.html @@ -3,15 +3,7 @@ {% load bookwyrm_tags %} {% block panel %} -

- {% if tab == 'home' %} - {% trans "Home Timeline" %} - {% elif tab == 'local' %} - {% trans "Local Timeline" %} - {% else %} - {% trans "Federated Timeline" %} - {% endif %} -

+

{% blocktrans %}{{ tab_title }} Timeline{% endblocktrans %}

{# announcements and system messages #} -{% if not activities.number > 1 %} - - -{% if request.user.show_goal and not goal and tab == 'home' %} +{% if request.user.show_goal and not goal and tab == 'home' and not activities.number > 1 %} {% now 'Y' as year %}
{% include 'snippets/goal_card.html' with year=year %} @@ -40,8 +27,6 @@
{% endif %} -{% endif %} - {# activity feed #} {% if not activities %}

{% trans "There aren't any activities right now! Try following a user to get started" %}

diff --git a/bookwyrm/templates/layout.html b/bookwyrm/templates/layout.html index d08b1820..e57f6152 100644 --- a/bookwyrm/templates/layout.html +++ b/bookwyrm/templates/layout.html @@ -139,8 +139,8 @@ {% trans "Notifications" %} - - {{ request.user | notification_count }} + + {{ request.user | notification_count }} diff --git a/bookwyrm/tests/activitypub/test_base_activity.py b/bookwyrm/tests/activitypub/test_base_activity.py index 8ec0b703..f3d3decd 100644 --- a/bookwyrm/tests/activitypub/test_base_activity.py +++ b/bookwyrm/tests/activitypub/test_base_activity.py @@ -19,7 +19,6 @@ from bookwyrm.activitypub import ActivitySerializerError from bookwyrm import models -@patch("bookwyrm.activitystreams.ActivityStream.add_status") class BaseActivity(TestCase): """ the super class for model-linked activitypub dataclasses """ @@ -44,24 +43,24 @@ class BaseActivity(TestCase): image.save(output, format=image.format) self.image_data = output.getvalue() - def test_init(self, _): + def test_init(self): """ simple successfuly init """ instance = ActivityObject(id="a", type="b") self.assertTrue(hasattr(instance, "id")) self.assertTrue(hasattr(instance, "type")) - def test_init_missing(self, _): + def test_init_missing(self): """ init with missing required params """ with self.assertRaises(ActivitySerializerError): ActivityObject() - def test_init_extra_fields(self, _): + def test_init_extra_fields(self): """ init ignoring additional fields """ instance = ActivityObject(id="a", type="b", fish="c") self.assertTrue(hasattr(instance, "id")) self.assertTrue(hasattr(instance, "type")) - def test_init_default_field(self, _): + def test_init_default_field(self): """ replace an existing required field with a default field """ @dataclass(init=False) @@ -74,7 +73,7 @@ class BaseActivity(TestCase): self.assertEqual(instance.id, "a") self.assertEqual(instance.type, "TestObject") - def test_serialize(self, _): + def test_serialize(self): """ simple function for converting dataclass to dict """ instance = ActivityObject(id="a", type="b") serialized = instance.serialize() @@ -83,7 +82,7 @@ class BaseActivity(TestCase): self.assertEqual(serialized["type"], "b") @responses.activate - def test_resolve_remote_id(self, _): + def test_resolve_remote_id(self): """ look up or load remote data """ # existing item result = resolve_remote_id("http://example.com/a/b", model=models.User) @@ -105,14 +104,14 @@ class BaseActivity(TestCase): self.assertEqual(result.remote_id, "https://example.com/user/mouse") self.assertEqual(result.name, "MOUSE?? MOUSE!!") - def test_to_model_invalid_model(self, _): + def test_to_model_invalid_model(self): """ catch mismatch between activity type and model type """ instance = ActivityObject(id="a", type="b") with self.assertRaises(ActivitySerializerError): instance.to_model(model=models.User) @responses.activate - def test_to_model_image(self, _): + def test_to_model_image(self): """ update an image field """ activity = activitypub.Person( id=self.user.remote_id, @@ -145,7 +144,7 @@ class BaseActivity(TestCase): self.assertEqual(self.user.name, "New Name") self.assertEqual(self.user.key_pair.public_key, "hi") - def test_to_model_many_to_many(self, _): + def test_to_model_many_to_many(self): """ annoying that these all need special handling """ with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create( @@ -176,7 +175,7 @@ class BaseActivity(TestCase): self.assertEqual(status.mention_books.first(), book) @responses.activate - def test_to_model_one_to_many(self, _): + def test_to_model_one_to_many(self): """these are reversed relationships, where the secondary object keys the primary object but not vice versa""" with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): @@ -215,7 +214,7 @@ class BaseActivity(TestCase): self.assertIsNone(status.attachments.first()) @responses.activate - def test_set_related_field(self, _): + def test_set_related_field(self): """ celery task to add back-references to created objects """ with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create( diff --git a/bookwyrm/tests/models/test_activitypub_mixin.py b/bookwyrm/tests/models/test_activitypub_mixin.py index 0d1acd97..930f3a53 100644 --- a/bookwyrm/tests/models/test_activitypub_mixin.py +++ b/bookwyrm/tests/models/test_activitypub_mixin.py @@ -13,7 +13,6 @@ from bookwyrm.models.activitypub_mixin import ActivitypubMixin from bookwyrm.models.activitypub_mixin import ActivityMixin, ObjectMixin -@patch("bookwyrm.activitystreams.ActivityStream.add_status") class ActivitypubMixins(TestCase): """ functionality shared across models """ @@ -45,7 +44,7 @@ class ActivitypubMixins(TestCase): } # ActivitypubMixin - def test_to_activity(self, _): + def test_to_activity(self): """ model to ActivityPub json """ @dataclass(init=False) @@ -66,7 +65,7 @@ class ActivitypubMixins(TestCase): self.assertEqual(activity["id"], "https://www.example.com/test") self.assertEqual(activity["type"], "Test") - def test_find_existing_by_remote_id(self, _): + def test_find_existing_by_remote_id(self): """ attempt to match a remote id to an object in the db """ # uses a different remote id scheme # this isn't really part of this test directly but it's helpful to state @@ -99,7 +98,7 @@ class ActivitypubMixins(TestCase): # test subclass match result = models.Status.find_existing_by_remote_id("https://comment.net") - def test_find_existing(self, _): + def test_find_existing(self): """ match a blob of data to a model """ book = models.Edition.objects.create( title="Test edition", @@ -109,7 +108,7 @@ class ActivitypubMixins(TestCase): result = models.Edition.find_existing({"openlibraryKey": "OL1234"}) self.assertEqual(result, book) - def test_get_recipients_public_object(self, _): + def test_get_recipients_public_object(self): """ determines the recipients for an object's broadcast """ MockSelf = namedtuple("Self", ("privacy")) mock_self = MockSelf("public") @@ -117,7 +116,7 @@ class ActivitypubMixins(TestCase): self.assertEqual(len(recipients), 1) self.assertEqual(recipients[0], self.remote_user.inbox) - def test_get_recipients_public_user_object_no_followers(self, _): + def test_get_recipients_public_user_object_no_followers(self): """ determines the recipients for a user's object broadcast """ MockSelf = namedtuple("Self", ("privacy", "user")) mock_self = MockSelf("public", self.local_user) @@ -125,7 +124,7 @@ class ActivitypubMixins(TestCase): recipients = ActivitypubMixin.get_recipients(mock_self) self.assertEqual(len(recipients), 0) - def test_get_recipients_public_user_object(self, _): + def test_get_recipients_public_user_object(self): """ determines the recipients for a user's object broadcast """ MockSelf = namedtuple("Self", ("privacy", "user")) mock_self = MockSelf("public", self.local_user) @@ -135,7 +134,7 @@ class ActivitypubMixins(TestCase): self.assertEqual(len(recipients), 1) self.assertEqual(recipients[0], self.remote_user.inbox) - def test_get_recipients_public_user_object_with_mention(self, _): + def test_get_recipients_public_user_object_with_mention(self): """ determines the recipients for a user's object broadcast """ MockSelf = namedtuple("Self", ("privacy", "user")) mock_self = MockSelf("public", self.local_user) @@ -158,7 +157,7 @@ class ActivitypubMixins(TestCase): self.assertEqual(recipients[0], another_remote_user.inbox) self.assertEqual(recipients[1], self.remote_user.inbox) - def test_get_recipients_direct(self, _): + def test_get_recipients_direct(self): """ determines the recipients for a user's object broadcast """ MockSelf = namedtuple("Self", ("privacy", "user")) mock_self = MockSelf("public", self.local_user) @@ -180,7 +179,7 @@ class ActivitypubMixins(TestCase): self.assertEqual(len(recipients), 1) self.assertEqual(recipients[0], another_remote_user.inbox) - def test_get_recipients_combine_inboxes(self, _): + def test_get_recipients_combine_inboxes(self): """ should combine users with the same shared_inbox """ self.remote_user.shared_inbox = "http://example.com/inbox" self.remote_user.save(broadcast=False) @@ -204,7 +203,7 @@ class ActivitypubMixins(TestCase): self.assertEqual(len(recipients), 1) self.assertEqual(recipients[0], "http://example.com/inbox") - def test_get_recipients_software(self, _): + def test_get_recipients_software(self): """ should differentiate between bookwyrm and other remote users """ with patch("bookwyrm.models.user.set_remote_server.delay"): another_remote_user = models.User.objects.create_user( @@ -234,7 +233,7 @@ class ActivitypubMixins(TestCase): self.assertEqual(recipients[0], another_remote_user.inbox) # ObjectMixin - def test_object_save_create(self, _): + def test_object_save_create(self): """ should save uneventufully when broadcast is disabled """ class Success(Exception): @@ -265,7 +264,7 @@ class ActivitypubMixins(TestCase): ObjectModel(user=self.local_user).save(broadcast=False) ObjectModel(user=None).save() - def test_object_save_update(self, _): + def test_object_save_update(self): """ should save uneventufully when broadcast is disabled """ class Success(Exception): @@ -291,7 +290,7 @@ class ActivitypubMixins(TestCase): with self.assertRaises(Success): UpdateObjectModel(id=1, last_edited_by=self.local_user).save() - def test_object_save_delete(self, _): + def test_object_save_delete(self): """ should create delete activities when objects are deleted by flag """ class ActivitySuccess(Exception): @@ -313,7 +312,7 @@ class ActivitypubMixins(TestCase): with self.assertRaises(ActivitySuccess): DeletableObjectModel(id=1, user=self.local_user, deleted=True).save() - def test_to_delete_activity(self, _): + def test_to_delete_activity(self): """ wrapper for Delete activity """ MockSelf = namedtuple("Self", ("remote_id", "to_activity")) mock_self = MockSelf( @@ -328,7 +327,7 @@ class ActivitypubMixins(TestCase): activity["cc"], ["https://www.w3.org/ns/activitystreams#Public"] ) - def test_to_update_activity(self, _): + def test_to_update_activity(self): """ ditto above but for Update """ MockSelf = namedtuple("Self", ("remote_id", "to_activity")) mock_self = MockSelf( @@ -346,7 +345,7 @@ class ActivitypubMixins(TestCase): self.assertIsInstance(activity["object"], dict) # Activity mixin - def test_to_undo_activity(self, _): + def test_to_undo_activity(self): """ and again, for Undo """ MockSelf = namedtuple("Self", ("remote_id", "to_activity", "user")) mock_self = MockSelf( diff --git a/bookwyrm/tests/models/test_base_model.py b/bookwyrm/tests/models/test_base_model.py index 25a2e7ee..4479d156 100644 --- a/bookwyrm/tests/models/test_base_model.py +++ b/bookwyrm/tests/models/test_base_model.py @@ -27,18 +27,18 @@ class BaseModel(TestCase): expected = instance.get_remote_id() self.assertEqual(expected, "https://%s/user/mouse/bookwyrmmodel/1" % DOMAIN) - def test_set_remote_id(self): + def test_execute_after_save(self): """ this function sets remote ids after creation """ # using Work because it BookWrymModel is abstract and this requires save # Work is a relatively not-fancy model. instance = models.Work.objects.create(title="work title") instance.remote_id = None - base_model.set_remote_id(None, instance, True) + base_model.execute_after_save(None, instance, True) self.assertEqual( instance.remote_id, "https://%s/book/%d" % (DOMAIN, instance.id) ) # shouldn't set remote_id if it's not created instance.remote_id = None - base_model.set_remote_id(None, instance, False) + base_model.execute_after_save(None, instance, False) self.assertIsNone(instance.remote_id) diff --git a/bookwyrm/tests/models/test_fields.py b/bookwyrm/tests/models/test_fields.py index 18bb028f..28faf52c 100644 --- a/bookwyrm/tests/models/test_fields.py +++ b/bookwyrm/tests/models/test_fields.py @@ -185,8 +185,7 @@ class ActivitypubFields(TestCase): self.assertEqual(model_instance.privacy_field, "unlisted") @patch("bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast") - @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_privacy_field_set_activity_from_field(self, *_): + def test_privacy_field_set_activity_from_field(self, _): """ translate between to/cc fields and privacy """ user = User.objects.create_user( "rat", "rat@rat.rat", "ratword", local=True, localname="rat" diff --git a/bookwyrm/tests/models/test_status_model.py b/bookwyrm/tests/models/test_status_model.py index 54fe7fee..1dcf5633 100644 --- a/bookwyrm/tests/models/test_status_model.py +++ b/bookwyrm/tests/models/test_status_model.py @@ -15,7 +15,6 @@ from bookwyrm import activitypub, models, settings # pylint: disable=too-many-public-methods @patch("bookwyrm.models.Status.broadcast") -@patch("bookwyrm.activitystreams.ActivityStream.add_status") class Status(TestCase): """ lotta types of statuses """ @@ -45,14 +44,14 @@ class Status(TestCase): image.save(output, format=image.format) self.book.cover.save("test.jpg", ContentFile(output.getvalue())) - def test_status_generated_fields(self, *_): + def test_status_generated_fields(self, _): """ setting remote id """ status = models.Status.objects.create(content="bleh", user=self.local_user) expected_id = "https://%s/user/mouse/status/%d" % (settings.DOMAIN, status.id) self.assertEqual(status.remote_id, expected_id) self.assertEqual(status.privacy, "public") - def test_replies(self, *_): + def test_replies(self, _): """ get a list of replies """ parent = models.Status.objects.create(content="hi", user=self.local_user) child = models.Status.objects.create( @@ -71,7 +70,7 @@ class Status(TestCase): # should select subclasses self.assertIsInstance(replies.last(), models.Review) - def test_status_type(self, *_): + def test_status_type(self, _): """ class name """ self.assertEqual(models.Status().status_type, "Note") self.assertEqual(models.Review().status_type, "Review") @@ -79,14 +78,14 @@ class Status(TestCase): self.assertEqual(models.Comment().status_type, "Comment") self.assertEqual(models.Boost().status_type, "Announce") - def test_boostable(self, *_): + def test_boostable(self, _): """ can a status be boosted, based on privacy """ self.assertTrue(models.Status(privacy="public").boostable) self.assertTrue(models.Status(privacy="unlisted").boostable) self.assertFalse(models.Status(privacy="followers").boostable) self.assertFalse(models.Status(privacy="direct").boostable) - def test_to_replies(self, *_): + def test_to_replies(self, _): """ activitypub replies collection """ parent = models.Status.objects.create(content="hi", user=self.local_user) child = models.Status.objects.create( @@ -103,7 +102,7 @@ class Status(TestCase): self.assertEqual(replies["id"], "%s/replies" % parent.remote_id) self.assertEqual(replies["totalItems"], 2) - def test_status_to_activity(self, *_): + def test_status_to_activity(self, _): """ subclass of the base model version with a "pure" serializer """ status = models.Status.objects.create( content="test content", user=self.local_user @@ -114,21 +113,20 @@ class Status(TestCase): self.assertEqual(activity["content"], "test content") self.assertEqual(activity["sensitive"], False) - def test_status_to_activity_tombstone(self, *_): + def test_status_to_activity_tombstone(self, _): """ subclass of the base model version with a "pure" serializer """ - with patch("bookwyrm.activitystreams.ActivityStream.remove_status"): - status = models.Status.objects.create( - content="test content", - user=self.local_user, - deleted=True, - deleted_date=timezone.now(), - ) + status = models.Status.objects.create( + content="test content", + user=self.local_user, + deleted=True, + deleted_date=timezone.now(), + ) activity = status.to_activity() self.assertEqual(activity["id"], status.remote_id) self.assertEqual(activity["type"], "Tombstone") self.assertFalse(hasattr(activity, "content")) - def test_status_to_pure_activity(self, *_): + def test_status_to_pure_activity(self, _): """ subclass of the base model version with a "pure" serializer """ status = models.Status.objects.create( content="test content", user=self.local_user @@ -140,7 +138,7 @@ class Status(TestCase): self.assertEqual(activity["sensitive"], False) self.assertEqual(activity["attachment"], []) - def test_generated_note_to_activity(self, *_): + def test_generated_note_to_activity(self, _): """ subclass of the base model version with a "pure" serializer """ status = models.GeneratedNote.objects.create( content="test content", user=self.local_user @@ -154,7 +152,7 @@ class Status(TestCase): self.assertEqual(activity["sensitive"], False) self.assertEqual(len(activity["tag"]), 2) - def test_generated_note_to_pure_activity(self, *_): + def test_generated_note_to_pure_activity(self, _): """ subclass of the base model version with a "pure" serializer """ status = models.GeneratedNote.objects.create( content="test content", user=self.local_user @@ -178,7 +176,7 @@ class Status(TestCase): ) self.assertEqual(activity["attachment"][0].name, "Test Edition") - def test_comment_to_activity(self, *_): + def test_comment_to_activity(self, _): """ subclass of the base model version with a "pure" serializer """ status = models.Comment.objects.create( content="test content", user=self.local_user, book=self.book @@ -189,7 +187,7 @@ class Status(TestCase): self.assertEqual(activity["content"], "test content") self.assertEqual(activity["inReplyToBook"], self.book.remote_id) - def test_comment_to_pure_activity(self, *_): + def test_comment_to_pure_activity(self, _): """ subclass of the base model version with a "pure" serializer """ status = models.Comment.objects.create( content="test content", user=self.local_user, book=self.book @@ -209,7 +207,7 @@ class Status(TestCase): ) self.assertEqual(activity["attachment"][0].name, "Test Edition") - def test_quotation_to_activity(self, *_): + def test_quotation_to_activity(self, _): """ subclass of the base model version with a "pure" serializer """ status = models.Quotation.objects.create( quote="a sickening sense", @@ -224,7 +222,7 @@ class Status(TestCase): self.assertEqual(activity["content"], "test content") self.assertEqual(activity["inReplyToBook"], self.book.remote_id) - def test_quotation_to_pure_activity(self, *_): + def test_quotation_to_pure_activity(self, _): """ subclass of the base model version with a "pure" serializer """ status = models.Quotation.objects.create( quote="a sickening sense", @@ -247,7 +245,7 @@ class Status(TestCase): ) self.assertEqual(activity["attachment"][0].name, "Test Edition") - def test_review_to_activity(self, *_): + def test_review_to_activity(self, _): """ subclass of the base model version with a "pure" serializer """ status = models.Review.objects.create( name="Review name", @@ -264,7 +262,7 @@ class Status(TestCase): self.assertEqual(activity["content"], "test content") self.assertEqual(activity["inReplyToBook"], self.book.remote_id) - def test_review_to_pure_activity(self, *_): + def test_review_to_pure_activity(self, _): """ subclass of the base model version with a "pure" serializer """ status = models.Review.objects.create( name="Review name", @@ -287,7 +285,7 @@ class Status(TestCase): ) self.assertEqual(activity["attachment"][0].name, "Test Edition") - def test_favorite(self, *_): + def test_favorite(self, _): """ fav a status """ real_broadcast = models.Favorite.broadcast @@ -313,7 +311,7 @@ class Status(TestCase): self.assertEqual(activity["object"], status.remote_id) models.Favorite.broadcast = real_broadcast - def test_boost(self, *_): + def test_boost(self, _): """ boosting, this one's a bit fussy """ status = models.Status.objects.create( content="test content", user=self.local_user @@ -325,7 +323,7 @@ class Status(TestCase): self.assertEqual(activity["type"], "Announce") self.assertEqual(activity, boost.to_activity(pure=True)) - def test_notification(self, *_): + def test_notification(self, _): """ a simple model """ notification = models.Notification.objects.create( user=self.local_user, notification_type="FAVORITE" @@ -337,7 +335,7 @@ class Status(TestCase): user=self.local_user, notification_type="GLORB" ) - def test_create_broadcast(self, _, broadcast_mock): + def test_create_broadcast(self, broadcast_mock): """ should send out two verions of a status on create """ models.Comment.objects.create( content="hi", user=self.local_user, book=self.book @@ -357,7 +355,7 @@ class Status(TestCase): self.assertEqual(args["type"], "Create") self.assertEqual(args["object"]["type"], "Comment") - def test_recipients_with_mentions(self, *_): + def test_recipients_with_mentions(self, _): """ get recipients to broadcast a status """ status = models.GeneratedNote.objects.create( content="test content", user=self.local_user @@ -366,7 +364,7 @@ class Status(TestCase): self.assertEqual(status.recipients, [self.remote_user]) - def test_recipients_with_reply_parent(self, *_): + def test_recipients_with_reply_parent(self, _): """ get recipients to broadcast a status """ parent_status = models.GeneratedNote.objects.create( content="test content", user=self.remote_user @@ -377,7 +375,7 @@ class Status(TestCase): self.assertEqual(status.recipients, [self.remote_user]) - def test_recipients_with_reply_parent_and_mentions(self, *_): + def test_recipients_with_reply_parent_and_mentions(self, _): """ get recipients to broadcast a status """ parent_status = models.GeneratedNote.objects.create( content="test content", user=self.remote_user @@ -390,7 +388,7 @@ class Status(TestCase): self.assertEqual(status.recipients, [self.remote_user]) @responses.activate - def test_ignore_activity_boost(self, *_): + def test_ignore_activity_boost(self, _): """ don't bother with most remote statuses """ activity = activitypub.Announce( id="http://www.faraway.com/boost/12", diff --git a/bookwyrm/tests/test_activitystreams.py b/bookwyrm/tests/test_activitystreams.py deleted file mode 100644 index d7a3d4eb..00000000 --- a/bookwyrm/tests/test_activitystreams.py +++ /dev/null @@ -1,204 +0,0 @@ -""" testing activitystreams """ -from unittest.mock import patch -from django.test import TestCase -from bookwyrm import activitystreams, models - - -@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") -@patch("bookwyrm.activitystreams.ActivityStream.add_status") -class Activitystreams(TestCase): - """ using redis to build activity streams """ - - def setUp(self): - """ use a test csv """ - self.local_user = models.User.objects.create_user( - "mouse", "mouse@mouse.mouse", "password", local=True, localname="mouse" - ) - self.another_user = models.User.objects.create_user( - "nutria", "nutria@nutria.nutria", "password", local=True, localname="nutria" - ) - with patch("bookwyrm.models.user.set_remote_server.delay"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@rat.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", - ) - self.book = models.Edition.objects.create(title="test book") - - class TestStream(activitystreams.ActivityStream): - """ test stream, don't have to do anything here """ - - key = "test" - - self.test_stream = TestStream() - - def test_activitystream_class_ids(self, *_): - """ the abstract base class for stream objects """ - self.assertEqual( - self.test_stream.stream_id(self.local_user), - "{}-test".format(self.local_user.id), - ) - self.assertEqual( - self.test_stream.unread_id(self.local_user), - "{}-test-unread".format(self.local_user.id), - ) - - def test_abstractstream_stream_users(self, *_): - """ get a list of users that should see a status """ - status = models.Status.objects.create( - user=self.remote_user, content="hi", privacy="public" - ) - users = self.test_stream.stream_users(status) - # remote users don't have feeds - self.assertFalse(self.remote_user in users) - self.assertTrue(self.local_user in users) - self.assertTrue(self.another_user in users) - - def test_abstractstream_stream_users_direct(self, *_): - """ get a list of users that should see a status """ - status = models.Status.objects.create( - user=self.remote_user, - content="hi", - privacy="direct", - ) - status.mention_users.add(self.local_user) - users = self.test_stream.stream_users(status) - self.assertIsNone(users) - - status = models.Comment.objects.create( - user=self.remote_user, - content="hi", - privacy="direct", - book=self.book, - ) - status.mention_users.add(self.local_user) - users = self.test_stream.stream_users(status) - self.assertTrue(self.local_user in users) - self.assertFalse(self.another_user in users) - self.assertFalse(self.remote_user in users) - - def test_abstractstream_stream_users_followers_remote_user(self, *_): - """ get a list of users that should see a status """ - status = models.Status.objects.create( - user=self.remote_user, - content="hi", - privacy="followers", - ) - users = self.test_stream.stream_users(status) - self.assertFalse(users.exists()) - - def test_abstractstream_stream_users_followers_self(self, *_): - """ get a list of users that should see a status """ - status = models.Comment.objects.create( - user=self.local_user, - content="hi", - privacy="direct", - book=self.book, - ) - users = self.test_stream.stream_users(status) - self.assertTrue(self.local_user in users) - self.assertFalse(self.another_user in users) - self.assertFalse(self.remote_user in users) - - def test_abstractstream_stream_users_followers_with_mention(self, *_): - """ get a list of users that should see a status """ - status = models.Comment.objects.create( - user=self.remote_user, - content="hi", - privacy="direct", - book=self.book, - ) - status.mention_users.add(self.local_user) - - users = self.test_stream.stream_users(status) - self.assertTrue(self.local_user in users) - self.assertFalse(self.another_user in users) - self.assertFalse(self.remote_user in users) - - def test_abstractstream_stream_users_followers_with_relationship(self, *_): - """ get a list of users that should see a status """ - self.remote_user.followers.add(self.local_user) - status = models.Comment.objects.create( - user=self.remote_user, - content="hi", - privacy="direct", - book=self.book, - ) - users = self.test_stream.stream_users(status) - self.assertFalse(self.local_user in users) - self.assertFalse(self.another_user in users) - self.assertFalse(self.remote_user in users) - - def test_homestream_stream_users(self, *_): - """ get a list of users that should see a status """ - status = models.Status.objects.create( - user=self.remote_user, content="hi", privacy="public" - ) - users = activitystreams.HomeStream().stream_users(status) - self.assertFalse(users.exists()) - - def test_homestream_stream_users_with_mentions(self, *_): - """ get a list of users that should see a status """ - status = models.Status.objects.create( - user=self.remote_user, content="hi", privacy="public" - ) - status.mention_users.add(self.local_user) - users = activitystreams.HomeStream().stream_users(status) - self.assertFalse(self.local_user in users) - self.assertFalse(self.another_user in users) - - def test_homestream_stream_users_with_relationship(self, *_): - """ get a list of users that should see a status """ - self.remote_user.followers.add(self.local_user) - status = models.Status.objects.create( - user=self.remote_user, content="hi", privacy="public" - ) - users = activitystreams.HomeStream().stream_users(status) - self.assertTrue(self.local_user in users) - self.assertFalse(self.another_user in users) - - def test_localstream_stream_users_remote_status(self, *_): - """ get a list of users that should see a status """ - status = models.Status.objects.create( - user=self.remote_user, content="hi", privacy="public" - ) - users = activitystreams.LocalStream().stream_users(status) - self.assertIsNone(users) - - def test_localstream_stream_users_local_status(self, *_): - """ get a list of users that should see a status """ - status = models.Status.objects.create( - user=self.local_user, content="hi", privacy="public" - ) - users = activitystreams.LocalStream().stream_users(status) - self.assertTrue(self.local_user in users) - self.assertTrue(self.another_user in users) - - def test_localstream_stream_users_unlisted(self, *_): - """ get a list of users that should see a status """ - status = models.Status.objects.create( - user=self.local_user, content="hi", privacy="unlisted" - ) - users = activitystreams.LocalStream().stream_users(status) - self.assertIsNone(users) - - def test_federatedstream_stream_users(self, *_): - """ get a list of users that should see a status """ - status = models.Status.objects.create( - user=self.remote_user, content="hi", privacy="public" - ) - users = activitystreams.FederatedStream().stream_users(status) - self.assertTrue(self.local_user in users) - self.assertTrue(self.another_user in users) - - def test_federatedstream_stream_users_unlisted(self, *_): - """ get a list of users that should see a status """ - status = models.Status.objects.create( - user=self.remote_user, content="hi", privacy="unlisted" - ) - users = activitystreams.FederatedStream().stream_users(status) - self.assertIsNone(users) diff --git a/bookwyrm/tests/test_goodreads_import.py b/bookwyrm/tests/test_goodreads_import.py index c06b49fc..a62cfdd2 100644 --- a/bookwyrm/tests/test_goodreads_import.py +++ b/bookwyrm/tests/test_goodreads_import.py @@ -203,8 +203,7 @@ class GoodreadsImport(TestCase): self.assertEqual(readthrough.finish_date.month, 10) self.assertEqual(readthrough.finish_date.day, 25) - @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_handle_imported_book_review(self, _): + def test_handle_imported_book_review(self): """ goodreads review import """ import_job = models.ImportJob.objects.create(user=self.user) datafile = pathlib.Path(__file__).parent.joinpath("data/goodreads.csv") diff --git a/bookwyrm/tests/test_librarything_import.py b/bookwyrm/tests/test_librarything_import.py index 54b8b422..a8e4cfe4 100644 --- a/bookwyrm/tests/test_librarything_import.py +++ b/bookwyrm/tests/test_librarything_import.py @@ -1,4 +1,5 @@ """ testing import """ +from collections import namedtuple import csv import pathlib from unittest.mock import patch @@ -15,8 +16,8 @@ class LibrarythingImport(TestCase): """ importing from librarything tsv """ def setUp(self): - """ use a test tsv """ self.importer = LibrarythingImporter() + """ use a test tsv """ datafile = pathlib.Path(__file__).parent.joinpath("data/librarything.tsv") # Librarything generates latin encoded exports... @@ -199,8 +200,7 @@ class LibrarythingImport(TestCase): self.assertEqual(readthrough.finish_date.month, 5) self.assertEqual(readthrough.finish_date.day, 8) - @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_handle_imported_book_review(self, _): + def test_handle_imported_book_review(self): """ librarything review import """ import_job = models.ImportJob.objects.create(user=self.user) datafile = pathlib.Path(__file__).parent.joinpath("data/librarything.tsv") diff --git a/bookwyrm/tests/test_templatetags.py b/bookwyrm/tests/test_templatetags.py index 61136c2e..6bdbd04c 100644 --- a/bookwyrm/tests/test_templatetags.py +++ b/bookwyrm/tests/test_templatetags.py @@ -10,7 +10,6 @@ from bookwyrm import models from bookwyrm.templatetags import bookwyrm_tags -@patch("bookwyrm.activitystreams.ActivityStream.add_status") class TemplateTags(TestCase): """ lotta different things here """ @@ -33,34 +32,34 @@ class TemplateTags(TestCase): ) self.book = models.Edition.objects.create(title="Test Book") - def test_dict_key(self, _): + def test_dict_key(self): """ just getting a value out of a dict """ test_dict = {"a": 1, "b": 3} self.assertEqual(bookwyrm_tags.dict_key(test_dict, "a"), 1) self.assertEqual(bookwyrm_tags.dict_key(test_dict, "c"), 0) - def test_get_user_rating(self, _): + def test_get_user_rating(self): """ get a user's most recent rating of a book """ with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.Review.objects.create(user=self.user, book=self.book, rating=3) self.assertEqual(bookwyrm_tags.get_user_rating(self.book, self.user), 3) - def test_get_user_rating_doesnt_exist(self, _): + def test_get_user_rating_doesnt_exist(self): """ there is no rating available """ self.assertEqual(bookwyrm_tags.get_user_rating(self.book, self.user), 0) - def test_get_user_identifer_local(self, _): + def test_get_user_identifer_local(self): """ fall back to the simplest uid available """ self.assertNotEqual(self.user.username, self.user.localname) self.assertEqual(bookwyrm_tags.get_user_identifier(self.user), "mouse") - def test_get_user_identifer_remote(self, _): + def test_get_user_identifer_remote(self): """ for a remote user, should be their full username """ self.assertEqual( bookwyrm_tags.get_user_identifier(self.remote_user), "rat@example.com" ) - def test_get_notification_count(self, _): + def test_get_notification_count(self): """ just countin' """ self.assertEqual(bookwyrm_tags.get_notification_count(self.user), 0) @@ -73,7 +72,7 @@ class TemplateTags(TestCase): self.assertEqual(bookwyrm_tags.get_notification_count(self.user), 2) - def test_get_replies(self, _): + def test_get_replies(self): """ direct replies to a status """ with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): parent = models.Review.objects.create( @@ -85,13 +84,12 @@ class TemplateTags(TestCase): second_child = models.Status.objects.create( reply_parent=parent, user=self.user, content="hi" ) - with patch("bookwyrm.activitystreams.ActivityStream.remove_status"): - third_child = models.Status.objects.create( - reply_parent=parent, - user=self.user, - deleted=True, - deleted_date=timezone.now(), - ) + third_child = models.Status.objects.create( + reply_parent=parent, + user=self.user, + deleted=True, + deleted_date=timezone.now(), + ) replies = bookwyrm_tags.get_replies(parent) self.assertEqual(len(replies), 2) @@ -99,7 +97,7 @@ class TemplateTags(TestCase): self.assertTrue(second_child in replies) self.assertFalse(third_child in replies) - def test_get_parent(self, _): + def test_get_parent(self): """ get the reply parent of a status """ with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): parent = models.Review.objects.create( @@ -113,7 +111,7 @@ class TemplateTags(TestCase): self.assertEqual(result, parent) self.assertIsInstance(result, models.Review) - def test_get_user_liked(self, _): + def test_get_user_liked(self): """ did a user like a status """ status = models.Review.objects.create(user=self.remote_user, book=self.book) @@ -122,7 +120,7 @@ class TemplateTags(TestCase): models.Favorite.objects.create(user=self.user, status=status) self.assertTrue(bookwyrm_tags.get_user_liked(self.user, status)) - def test_get_user_boosted(self, _): + def test_get_user_boosted(self): """ did a user boost a status """ status = models.Review.objects.create(user=self.remote_user, book=self.book) @@ -131,7 +129,7 @@ class TemplateTags(TestCase): models.Boost.objects.create(user=self.user, boosted_status=status) self.assertTrue(bookwyrm_tags.get_user_boosted(self.user, status)) - def test_follow_request_exists(self, _): + def test_follow_request_exists(self): """ does a user want to follow """ self.assertFalse( bookwyrm_tags.follow_request_exists(self.user, self.remote_user) @@ -149,7 +147,7 @@ class TemplateTags(TestCase): bookwyrm_tags.follow_request_exists(self.remote_user, self.user) ) - def test_get_boosted(self, _): + def test_get_boosted(self): """ load a boosted status """ with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Review.objects.create(user=self.remote_user, book=self.book) @@ -158,7 +156,7 @@ class TemplateTags(TestCase): self.assertIsInstance(boosted, models.Review) self.assertEqual(boosted, status) - def test_get_book_description(self, _): + def test_get_book_description(self): """ grab it from the edition or the parent """ work = models.Work.objects.create(title="Test Work") self.book.parent_work = work @@ -174,12 +172,12 @@ class TemplateTags(TestCase): self.book.save() self.assertEqual(bookwyrm_tags.get_book_description(self.book), "hello") - def test_get_uuid(self, _): + def test_get_uuid(self): """ uuid functionality """ uuid = bookwyrm_tags.get_uuid("hi") self.assertTrue(re.match(r"hi[A-Za-z0-9\-]", uuid)) - def test_time_since(self, _): + def test_time_since(self): """ ultraconcise timestamps """ self.assertEqual(bookwyrm_tags.time_since("bleh"), "") @@ -209,7 +207,7 @@ class TemplateTags(TestCase): re.match(r"[A-Z][a-z]{2} \d?\d \d{4}", bookwyrm_tags.time_since(years_ago)) ) - def test_get_markdown(self, _): + def test_get_markdown(self): """ mardown format data """ result = bookwyrm_tags.get_markdown("_hi_") self.assertEqual(result, "

hi

") @@ -217,13 +215,13 @@ class TemplateTags(TestCase): result = bookwyrm_tags.get_markdown("_hi_") self.assertEqual(result, "

hi

") - def test_get_mentions(self, _): + def test_get_mentions(self): """ list of people mentioned """ status = models.Status.objects.create(content="hi", user=self.remote_user) result = bookwyrm_tags.get_mentions(status, self.user) self.assertEqual(result, "@rat@example.com ") - def test_get_status_preview_name(self, _): + def test_get_status_preview_name(self): """ status context string """ with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create(content="hi", user=self.user) @@ -248,7 +246,7 @@ class TemplateTags(TestCase): result = bookwyrm_tags.get_status_preview_name(status) self.assertEqual(result, "quotation from Test Book") - def test_related_status(self, _): + def test_related_status(self): """ gets the subclass model for a notification status """ with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create(content="hi", user=self.user) diff --git a/bookwyrm/tests/views/test_block.py b/bookwyrm/tests/views/test_block.py index 71583d70..60920e38 100644 --- a/bookwyrm/tests/views/test_block.py +++ b/bookwyrm/tests/views/test_block.py @@ -7,7 +7,6 @@ from django.test.client import RequestFactory from bookwyrm import models, views -@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") class BlockViews(TestCase): """ view user and edit profile """ @@ -33,7 +32,7 @@ class BlockViews(TestCase): ) models.SiteSettings.objects.create() - def test_block_get(self, _): + def test_block_get(self): """ there are so many views, this just makes sure it LOADS """ view = views.Block.as_view() request = self.factory.get("") @@ -43,19 +42,20 @@ class BlockViews(TestCase): result.render() self.assertEqual(result.status_code, 200) - def test_block_post(self, _): + def test_block_post(self): """ create a "block" database entry from an activity """ view = views.Block.as_view() self.local_user.followers.add(self.remote_user) - models.UserFollowRequest.objects.create( - user_subject=self.local_user, user_object=self.remote_user - ) + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + models.UserFollowRequest.objects.create( + user_subject=self.local_user, user_object=self.remote_user + ) self.assertTrue(models.UserFollows.objects.exists()) self.assertTrue(models.UserFollowRequest.objects.exists()) request = self.factory.post("") request.user = self.local_user - with patch("bookwyrm.activitystreams.ActivityStream.remove_user_statuses"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): view(request, self.remote_user.id) block = models.UserBlocks.objects.get() self.assertEqual(block.user_subject, self.local_user) @@ -64,13 +64,13 @@ class BlockViews(TestCase): self.assertFalse(models.UserFollows.objects.exists()) self.assertFalse(models.UserFollowRequest.objects.exists()) - def test_unblock(self, _): + def test_unblock(self): """ undo a block """ self.local_user.blocks.add(self.remote_user) request = self.factory.post("") request.user = self.local_user - with patch("bookwyrm.activitystreams.ActivityStream.add_user_statuses"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.block.unblock(request, self.remote_user.id) self.assertFalse(models.UserBlocks.objects.exists()) diff --git a/bookwyrm/tests/views/test_feed.py b/bookwyrm/tests/views/test_feed.py index 1ff99573..42668467 100644 --- a/bookwyrm/tests/views/test_feed.py +++ b/bookwyrm/tests/views/test_feed.py @@ -14,8 +14,6 @@ from bookwyrm import views from bookwyrm.activitypub import ActivitypubResponse -@patch("bookwyrm.activitystreams.ActivityStream.get_activity_stream") -@patch("bookwyrm.activitystreams.ActivityStream.add_status") class FeedViews(TestCase): """ activity feed, statuses, dms """ @@ -36,7 +34,7 @@ class FeedViews(TestCase): ) models.SiteSettings.objects.create() - def test_feed(self, *_): + def test_feed(self): """ there are so many views, this just makes sure it LOADS """ view = views.Feed.as_view() request = self.factory.get("") @@ -46,7 +44,7 @@ class FeedViews(TestCase): result.render() self.assertEqual(result.status_code, 200) - def test_status_page(self, *_): + def test_status_page(self): """ there are so many views, this just makes sure it LOADS """ view = views.Status.as_view() with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): @@ -66,7 +64,7 @@ class FeedViews(TestCase): self.assertIsInstance(result, ActivitypubResponse) self.assertEqual(result.status_code, 200) - def test_status_page_with_image(self, *_): + def test_status_page_with_image(self): """ there are so many views, this just makes sure it LOADS """ view = views.Status.as_view() @@ -102,7 +100,7 @@ class FeedViews(TestCase): self.assertIsInstance(result, ActivitypubResponse) self.assertEqual(result.status_code, 200) - def test_replies_page(self, *_): + def test_replies_page(self): """ there are so many views, this just makes sure it LOADS """ view = views.Replies.as_view() with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): @@ -122,7 +120,7 @@ class FeedViews(TestCase): self.assertIsInstance(result, ActivitypubResponse) self.assertEqual(result.status_code, 200) - def test_direct_messages_page(self, *_): + def test_direct_messages_page(self): """ there are so many views, this just makes sure it LOADS """ view = views.DirectMessage.as_view() request = self.factory.get("") @@ -132,7 +130,7 @@ class FeedViews(TestCase): result.render() self.assertEqual(result.status_code, 200) - def test_get_suggested_book(self, *_): + def test_get_suggested_book(self): """ gets books the ~*~ algorithm ~*~ thinks you want to post about """ with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.ShelfBook.objects.create( diff --git a/bookwyrm/tests/views/test_goal.py b/bookwyrm/tests/views/test_goal.py index cbe4fe01..990bb5c2 100644 --- a/bookwyrm/tests/views/test_goal.py +++ b/bookwyrm/tests/views/test_goal.py @@ -102,8 +102,7 @@ class GoalViews(TestCase): result = view(request, self.local_user.localname, 2020) self.assertEqual(result.status_code, 404) - @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_create_goal(self, _): + def test_create_goal(self): """ create a new goal """ view = views.Goal.as_view() request = self.factory.post( diff --git a/bookwyrm/tests/views/test_helpers.py b/bookwyrm/tests/views/test_helpers.py index 4fd7ce66..bb4cf69c 100644 --- a/bookwyrm/tests/views/test_helpers.py +++ b/bookwyrm/tests/views/test_helpers.py @@ -80,6 +80,113 @@ class ViewsHelpers(TestCase): request.headers = {"Accept": "Praise"} self.assertFalse(views.helpers.is_api_request(request)) + def test_get_activity_feed(self): + """ loads statuses """ + rat = models.User.objects.create_user( + "rat", "rat@rat.rat", "password", local=True + ) + + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + public_status = models.Comment.objects.create( + content="public status", book=self.book, user=self.local_user + ) + direct_status = models.Status.objects.create( + content="direct", user=self.local_user, privacy="direct" + ) + + rat_public = models.Status.objects.create(content="blah blah", user=rat) + rat_unlisted = models.Status.objects.create( + content="blah blah", user=rat, privacy="unlisted" + ) + remote_status = models.Status.objects.create( + content="blah blah", user=self.remote_user + ) + followers_status = models.Status.objects.create( + content="blah", user=rat, privacy="followers" + ) + rat_mention = models.Status.objects.create( + content="blah blah blah", user=rat, privacy="followers" + ) + rat_mention.mention_users.set([self.local_user]) + + statuses = views.helpers.get_activity_feed( + self.local_user, + privacy=["public", "unlisted", "followers"], + following_only=True, + queryset=models.Comment.objects, + ) + self.assertEqual(len(statuses), 1) + self.assertEqual(statuses[0], public_status) + + statuses = views.helpers.get_activity_feed( + self.local_user, privacy=["public", "followers"], local_only=True + ) + self.assertEqual(len(statuses), 2) + self.assertEqual(statuses[1], public_status) + self.assertEqual(statuses[0], rat_public) + + statuses = views.helpers.get_activity_feed(self.local_user, privacy=["direct"]) + self.assertEqual(len(statuses), 1) + self.assertEqual(statuses[0], direct_status) + + statuses = views.helpers.get_activity_feed( + self.local_user, + privacy=["public", "followers"], + ) + self.assertEqual(len(statuses), 3) + self.assertEqual(statuses[2], public_status) + self.assertEqual(statuses[1], rat_public) + self.assertEqual(statuses[0], remote_status) + + statuses = views.helpers.get_activity_feed( + self.local_user, + privacy=["public", "unlisted", "followers"], + following_only=True, + ) + self.assertEqual(len(statuses), 2) + self.assertEqual(statuses[1], public_status) + self.assertEqual(statuses[0], rat_mention) + + rat.followers.add(self.local_user) + statuses = views.helpers.get_activity_feed( + self.local_user, + privacy=["public", "unlisted", "followers"], + following_only=True, + ) + self.assertEqual(len(statuses), 5) + self.assertEqual(statuses[4], public_status) + self.assertEqual(statuses[3], rat_public) + self.assertEqual(statuses[2], rat_unlisted) + self.assertEqual(statuses[1], followers_status) + self.assertEqual(statuses[0], rat_mention) + + def test_get_activity_feed_blocks(self): + """ feed generation with blocked users """ + rat = models.User.objects.create_user( + "rat", "rat@rat.rat", "password", local=True + ) + + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + public_status = models.Comment.objects.create( + content="public status", book=self.book, user=self.local_user + ) + rat_public = models.Status.objects.create(content="blah blah", user=rat) + + statuses = views.helpers.get_activity_feed( + self.local_user, privacy=["public"] + ) + self.assertEqual(len(statuses), 2) + + # block relationship + rat.blocks.add(self.local_user) + statuses = views.helpers.get_activity_feed(self.local_user, privacy=["public"]) + self.assertEqual(len(statuses), 1) + self.assertEqual(statuses[0], public_status) + + statuses = views.helpers.get_activity_feed(rat, privacy=["public"]) + self.assertEqual(len(statuses), 1) + self.assertEqual(statuses[0], rat_public) + def test_is_bookwyrm_request(self): """ checks if a request came from a bookwyrm instance """ request = self.factory.get("", {"q": "Test Book"}) @@ -134,8 +241,7 @@ class ViewsHelpers(TestCase): self.assertIsInstance(result, models.User) self.assertEqual(result.username, "mouse@example.com") - @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_handle_reading_status_to_read(self, _): + def test_handle_reading_status_to_read(self): """ posts shelve activities """ shelf = self.local_user.shelf_set.get(identifier="to-read") with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): @@ -147,8 +253,7 @@ class ViewsHelpers(TestCase): self.assertEqual(status.mention_books.first(), self.book) self.assertEqual(status.content, "wants to read") - @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_handle_reading_status_reading(self, _): + def test_handle_reading_status_reading(self): """ posts shelve activities """ shelf = self.local_user.shelf_set.get(identifier="reading") with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): @@ -160,8 +265,7 @@ class ViewsHelpers(TestCase): self.assertEqual(status.mention_books.first(), self.book) self.assertEqual(status.content, "started reading") - @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_handle_reading_status_read(self, _): + def test_handle_reading_status_read(self): """ posts shelve activities """ shelf = self.local_user.shelf_set.get(identifier="read") with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): @@ -173,8 +277,7 @@ class ViewsHelpers(TestCase): self.assertEqual(status.mention_books.first(), self.book) self.assertEqual(status.content, "finished reading") - @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_handle_reading_status_other(self, _): + def test_handle_reading_status_other(self): """ posts shelve activities """ with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.helpers.handle_reading_status( @@ -182,8 +285,7 @@ class ViewsHelpers(TestCase): ) self.assertFalse(models.GeneratedNote.objects.exists()) - @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_object_visible_to_user(self, _): + def test_object_visible_to_user(self): """ does a user have permission to view an object """ obj = models.Status.objects.create( content="hi", user=self.remote_user, privacy="public" @@ -211,8 +313,7 @@ class ViewsHelpers(TestCase): obj.mention_users.add(self.local_user) self.assertTrue(views.helpers.object_visible_to_user(self.local_user, obj)) - @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_object_visible_to_user_follower(self, _): + def test_object_visible_to_user_follower(self): """ what you can see if you follow a user """ self.remote_user.followers.add(self.local_user) obj = models.Status.objects.create( @@ -231,8 +332,7 @@ class ViewsHelpers(TestCase): obj.mention_users.add(self.local_user) self.assertTrue(views.helpers.object_visible_to_user(self.local_user, obj)) - @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_object_visible_to_user_blocked(self, _): + def test_object_visible_to_user_blocked(self): """ you can't see it if they block you """ self.remote_user.blocks.add(self.local_user) obj = models.Status.objects.create( diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py index 7d08a1e4..10f55f89 100644 --- a/bookwyrm/tests/views/test_inbox.py +++ b/bookwyrm/tests/views/test_inbox.py @@ -38,12 +38,11 @@ class Inbox(TestCase): outbox="https://example.com/users/rat/outbox", ) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): - self.status = models.Status.objects.create( - user=self.local_user, - content="Test status", - remote_id="https://example.com/status/1", - ) + self.status = models.Status.objects.create( + user=self.local_user, + content="Test status", + remote_id="https://example.com/status/1", + ) self.create_json = { "id": "hi", @@ -140,9 +139,7 @@ class Inbox(TestCase): activity = self.create_json activity["object"] = status_data - with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock: - views.inbox.activity_task(activity) - self.assertTrue(redis_mock.called) + views.inbox.activity_task(activity) status = models.Quotation.objects.get() self.assertEqual( @@ -169,9 +166,7 @@ class Inbox(TestCase): activity = self.create_json activity["object"] = status_data - with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock: - views.inbox.activity_task(activity) - self.assertTrue(redis_mock.called) + views.inbox.activity_task(activity) status = models.Status.objects.last() self.assertEqual(status.content, "test content in note") self.assertEqual(status.mention_users.first(), self.local_user) @@ -192,9 +187,7 @@ class Inbox(TestCase): activity = self.create_json activity["object"] = status_data - with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock: - views.inbox.activity_task(activity) - self.assertTrue(redis_mock.called) + views.inbox.activity_task(activity) status = models.Status.objects.last() self.assertEqual(status.content, "test content in note") self.assertEqual(status.reply_parent, self.status) @@ -225,7 +218,7 @@ class Inbox(TestCase): self.assertEqual(book_list.description, "summary text") self.assertEqual(book_list.remote_id, "https://example.com/list/22") - def test_handle_follow(self): + def test_handle_follow_x(self): """ remote user wants to follow local user """ activity = { "@context": "https://www.w3.org/ns/activitystreams", @@ -443,11 +436,7 @@ class Inbox(TestCase): "actor": self.remote_user.remote_id, "object": {"id": self.status.remote_id, "type": "Tombstone"}, } - with patch( - "bookwyrm.activitystreams.ActivityStream.remove_status" - ) as redis_mock: - views.inbox.activity_task(activity) - self.assertTrue(redis_mock.called) + views.inbox.activity_task(activity) # deletion doens't remove the status, it turns it into a tombstone status = models.Status.objects.get() self.assertTrue(status.deleted) @@ -476,11 +465,7 @@ class Inbox(TestCase): "actor": self.remote_user.remote_id, "object": {"id": self.status.remote_id, "type": "Tombstone"}, } - with patch( - "bookwyrm.activitystreams.ActivityStream.remove_status" - ) as redis_mock: - views.inbox.activity_task(activity) - self.assertTrue(redis_mock.called) + views.inbox.activity_task(activity) # deletion doens't remove the status, it turns it into a tombstone status = models.Status.objects.get() self.assertTrue(status.deleted) @@ -550,8 +535,7 @@ class Inbox(TestCase): views.inbox.activity_task(activity) self.assertEqual(models.Favorite.objects.count(), 0) - @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_handle_boost(self, _): + def test_handle_boost(self): """ boost a status """ self.assertEqual(models.Notification.objects.count(), 0) activity = { @@ -576,8 +560,7 @@ class Inbox(TestCase): content="hi", user=self.remote_user, ) - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): - status.save(broadcast=False) + status.save(broadcast=False) activity = { "type": "Announce", "id": "http://www.faraway.com/boost/12", @@ -592,10 +575,9 @@ class Inbox(TestCase): def test_handle_unboost(self): """ undo a boost """ - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): - boost = models.Boost.objects.create( - boosted_status=self.status, user=self.remote_user - ) + boost = models.Boost.objects.create( + boosted_status=self.status, user=self.remote_user + ) activity = { "type": "Undo", "actor": "hi", @@ -609,11 +591,7 @@ class Inbox(TestCase): "object": self.status.remote_id, }, } - with patch( - "bookwyrm.activitystreams.ActivityStream.remove_status" - ) as redis_mock: - views.inbox.activity_task(activity) - self.assertTrue(redis_mock.called) + views.inbox.activity_task(activity) def test_handle_unboost_unknown_boost(self): """ undo a boost """ @@ -885,11 +863,6 @@ class Inbox(TestCase): "object": "https://example.com/user/mouse", } - with patch( - "bookwyrm.activitystreams.ActivityStream.remove_user_statuses" - ) as redis_mock: - views.inbox.activity_task(activity) - self.assertTrue(redis_mock.called) views.inbox.activity_task(activity) block = models.UserBlocks.objects.get() self.assertEqual(block.user_subject, self.remote_user) @@ -923,9 +896,5 @@ class Inbox(TestCase): "object": "https://example.com/user/mouse", }, } - with patch( - "bookwyrm.activitystreams.ActivityStream.add_user_statuses" - ) as redis_mock: - views.inbox.activity_task(activity) - self.assertTrue(redis_mock.called) + views.inbox.activity_task(activity) self.assertFalse(models.UserBlocks.objects.exists()) diff --git a/bookwyrm/tests/views/test_interaction.py b/bookwyrm/tests/views/test_interaction.py index 40152c0f..857f7061 100644 --- a/bookwyrm/tests/views/test_interaction.py +++ b/bookwyrm/tests/views/test_interaction.py @@ -6,7 +6,6 @@ from django.test.client import RequestFactory from bookwyrm import models, views -@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") class InteractionViews(TestCase): """ viewing and creating statuses """ @@ -39,12 +38,12 @@ class InteractionViews(TestCase): parent_work=work, ) - def test_handle_favorite(self, _): + def test_handle_favorite(self): """ create and broadcast faving a status """ view = views.Favorite.as_view() request = self.factory.post("") request.user = self.remote_user - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create(user=self.local_user, content="hi") view(request, status.id) @@ -57,12 +56,12 @@ class InteractionViews(TestCase): self.assertEqual(notification.user, self.local_user) self.assertEqual(notification.related_user, self.remote_user) - def test_handle_unfavorite(self, _): + def test_handle_unfavorite(self): """ unfav a status """ view = views.Unfavorite.as_view() request = self.factory.post("") request.user = self.remote_user - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create(user=self.local_user, content="hi") views.Favorite.as_view()(request, status.id) @@ -74,12 +73,12 @@ class InteractionViews(TestCase): self.assertEqual(models.Favorite.objects.count(), 0) self.assertEqual(models.Notification.objects.count(), 0) - def test_handle_boost(self, _): + def test_handle_boost(self): """ boost a status """ view = views.Boost.as_view() request = self.factory.post("") request.user = self.remote_user - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create(user=self.local_user, content="hi") view(request, status.id) @@ -95,12 +94,12 @@ class InteractionViews(TestCase): self.assertEqual(notification.related_user, self.remote_user) self.assertEqual(notification.related_status, status) - def test_handle_boost_unlisted(self, _): + def test_handle_boost_unlisted(self): """ boost a status """ view = views.Boost.as_view() request = self.factory.post("") request.user = self.local_user - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create( user=self.local_user, content="hi", privacy="unlisted" ) @@ -110,12 +109,12 @@ class InteractionViews(TestCase): boost = models.Boost.objects.get() self.assertEqual(boost.privacy, "unlisted") - def test_handle_boost_private(self, _): + def test_handle_boost_private(self): """ boost a status """ view = views.Boost.as_view() request = self.factory.post("") request.user = self.local_user - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create( user=self.local_user, content="hi", privacy="followers" ) @@ -123,35 +122,31 @@ class InteractionViews(TestCase): view(request, status.id) self.assertFalse(models.Boost.objects.exists()) - def test_handle_boost_twice(self, _): + def test_handle_boost_twice(self): """ boost a status """ view = views.Boost.as_view() request = self.factory.post("") request.user = self.local_user - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create(user=self.local_user, content="hi") view(request, status.id) view(request, status.id) self.assertEqual(models.Boost.objects.count(), 1) - def test_handle_unboost(self, broadcast_mock): + def test_handle_unboost(self): """ undo a boost """ view = views.Unboost.as_view() request = self.factory.post("") request.user = self.local_user - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create(user=self.local_user, content="hi") views.Boost.as_view()(request, status.id) self.assertEqual(models.Boost.objects.count(), 1) self.assertEqual(models.Notification.objects.count(), 1) - broadcast_mock.call_count = 0 - with patch( - "bookwyrm.activitystreams.ActivityStream.remove_status" - ) as redis_mock: + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock: view(request, status.id) - self.assertEqual(broadcast_mock.call_count, 1) - self.assertTrue(redis_mock.called) + self.assertEqual(mock.call_count, 1) self.assertEqual(models.Boost.objects.count(), 0) self.assertEqual(models.Notification.objects.count(), 0) diff --git a/bookwyrm/tests/views/test_isbn.py b/bookwyrm/tests/views/test_isbn.py index 7f03a610..c7ae1f39 100644 --- a/bookwyrm/tests/views/test_isbn.py +++ b/bookwyrm/tests/views/test_isbn.py @@ -3,10 +3,12 @@ import json from unittest.mock import patch from django.http import JsonResponse +from django.template.response import TemplateResponse from django.test import TestCase from django.test.client import RequestFactory from bookwyrm import models, views +from bookwyrm.connectors import abstract_connector from bookwyrm.settings import DOMAIN diff --git a/bookwyrm/tests/views/test_landing.py b/bookwyrm/tests/views/test_landing.py index 2513fecc..910e4a85 100644 --- a/bookwyrm/tests/views/test_landing.py +++ b/bookwyrm/tests/views/test_landing.py @@ -1,5 +1,4 @@ """ test for app action functionality """ -from unittest.mock import patch from django.contrib.auth.models import AnonymousUser from django.template.response import TemplateResponse from django.test import TestCase @@ -31,8 +30,7 @@ class LandingViews(TestCase): view = views.Home.as_view() request = self.factory.get("") request.user = self.local_user - with patch("bookwyrm.activitystreams.ActivityStream.get_activity_stream"): - result = view(request) + result = view(request) self.assertEqual(result.status_code, 200) result.render() diff --git a/bookwyrm/tests/views/test_outbox.py b/bookwyrm/tests/views/test_outbox.py index 0bcfde69..5934eb7c 100644 --- a/bookwyrm/tests/views/test_outbox.py +++ b/bookwyrm/tests/views/test_outbox.py @@ -11,7 +11,6 @@ from bookwyrm.settings import USER_AGENT # pylint: disable=too-many-public-methods -@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") class OutboxView(TestCase): """ sends out activities """ @@ -33,19 +32,19 @@ class OutboxView(TestCase): parent_work=work, ) - def test_outbox(self, _): + def test_outbox(self): """ returns user's statuses """ request = self.factory.get("") result = views.Outbox.as_view()(request, "mouse") self.assertIsInstance(result, JsonResponse) - def test_outbox_bad_method(self, _): + def test_outbox_bad_method(self): """ can't POST to outbox """ request = self.factory.post("") result = views.Outbox.as_view()(request, "mouse") self.assertEqual(result.status_code, 405) - def test_outbox_unknown_user(self, _): + def test_outbox_unknown_user(self): """ should 404 for unknown and remote users """ request = self.factory.post("") result = views.Outbox.as_view()(request, "beepboop") @@ -53,9 +52,9 @@ class OutboxView(TestCase): result = views.Outbox.as_view()(request, "rat") self.assertEqual(result.status_code, 405) - def test_outbox_privacy(self, _): + def test_outbox_privacy(self): """ don't show dms et cetera in outbox """ - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.Status.objects.create( content="PRIVATE!!", user=self.local_user, privacy="direct" ) @@ -76,9 +75,9 @@ class OutboxView(TestCase): self.assertEqual(data["type"], "OrderedCollection") self.assertEqual(data["totalItems"], 2) - def test_outbox_filter(self, _): + def test_outbox_filter(self): """ if we only care about reviews, only get reviews """ - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.Review.objects.create( content="look at this", name="hi", @@ -102,9 +101,9 @@ class OutboxView(TestCase): self.assertEqual(data["type"], "OrderedCollection") self.assertEqual(data["totalItems"], 1) - def test_outbox_bookwyrm_request_true(self, _): + def test_outbox_bookwyrm_request_true(self): """ should differentiate between bookwyrm and outside requests """ - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.Review.objects.create( name="hi", content="look at this", @@ -120,9 +119,9 @@ class OutboxView(TestCase): self.assertEqual(len(data["orderedItems"]), 1) self.assertEqual(data["orderedItems"][0]["type"], "Review") - def test_outbox_bookwyrm_request_false(self, _): + def test_outbox_bookwyrm_request_false(self): """ should differentiate between bookwyrm and outside requests """ - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.Review.objects.create( name="hi", content="look at this", diff --git a/bookwyrm/tests/views/test_reading.py b/bookwyrm/tests/views/test_reading.py index 4661e487..96d1f1f4 100644 --- a/bookwyrm/tests/views/test_reading.py +++ b/bookwyrm/tests/views/test_reading.py @@ -8,7 +8,6 @@ from django.utils import timezone from bookwyrm import models, views -@patch("bookwyrm.activitystreams.ActivityStream.add_status") class ReadingViews(TestCase): """ viewing and creating statuses """ @@ -40,7 +39,7 @@ class ReadingViews(TestCase): outbox="https://example.com/users/rat/outbox", ) - def test_start_reading(self, _): + def test_start_reading(self): """ begin a book """ shelf = self.local_user.shelf_set.get(identifier="reading") self.assertFalse(shelf.books.exists()) @@ -71,7 +70,7 @@ class ReadingViews(TestCase): self.assertEqual(readthrough.user, self.local_user) self.assertEqual(readthrough.book, self.book) - def test_start_reading_reshelf(self, _): + def test_start_reading_reshelf(self): """ begin a book """ to_read_shelf = self.local_user.shelf_set.get(identifier="to-read") with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): @@ -91,7 +90,7 @@ class ReadingViews(TestCase): self.assertFalse(to_read_shelf.books.exists()) self.assertEqual(shelf.books.get(), self.book) - def test_finish_reading(self, _): + def test_finish_reading(self): """ begin a book """ shelf = self.local_user.shelf_set.get(identifier="read") self.assertFalse(shelf.books.exists()) @@ -127,7 +126,7 @@ class ReadingViews(TestCase): self.assertEqual(readthrough.user, self.local_user) self.assertEqual(readthrough.book, self.book) - def test_edit_readthrough(self, _): + def test_edit_readthrough(self): """ adding dates to an ongoing readthrough """ start = timezone.make_aware(dateutil.parser.parse("2021-01-03")) readthrough = models.ReadThrough.objects.create( @@ -154,7 +153,7 @@ class ReadingViews(TestCase): self.assertEqual(readthrough.finish_date.day, 7) self.assertEqual(readthrough.book, self.book) - def test_delete_readthrough(self, _): + def test_delete_readthrough(self): """ remove a readthrough """ readthrough = models.ReadThrough.objects.create( book=self.book, user=self.local_user @@ -171,7 +170,7 @@ class ReadingViews(TestCase): views.delete_readthrough(request) self.assertFalse(models.ReadThrough.objects.filter(id=readthrough.id).exists()) - def test_create_readthrough(self, _): + def test_create_readthrough(self): """ adding new read dates """ request = self.factory.post( "", diff --git a/bookwyrm/tests/views/test_rss_feed.py b/bookwyrm/tests/views/test_rss_feed.py index 0230b4a9..c7fef2a9 100644 --- a/bookwyrm/tests/views/test_rss_feed.py +++ b/bookwyrm/tests/views/test_rss_feed.py @@ -26,30 +26,28 @@ class RssFeedView(TestCase): ) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): - self.review = models.Review.objects.create( - name="Review name", - content="test content", - rating=3, - user=self.user, - book=self.book, - ) + self.review = models.Review.objects.create( + name="Review name", + content="test content", + rating=3, + user=self.user, + book=self.book, + ) - self.quote = models.Quotation.objects.create( - quote="a sickening sense", - content="test content", - user=self.user, - book=self.book, - ) + self.quote = models.Quotation.objects.create( + quote="a sickening sense", + content="test content", + user=self.user, + book=self.book, + ) - self.generatednote = models.GeneratedNote.objects.create( - content="test content", user=self.user - ) + self.generatednote = models.GeneratedNote.objects.create( + content="test content", user=self.user + ) self.factory = RequestFactory() - @patch("bookwyrm.activitystreams.ActivityStream.get_activity_stream") - def test_rss_feed(self, _): + def test_rss_feed(self): """ load an rss feed """ view = rss_feed.RssFeed() request = self.factory.get("/user/rss_user/rss") diff --git a/bookwyrm/tests/views/test_status.py b/bookwyrm/tests/views/test_status.py index 5dc0e49a..32080a5a 100644 --- a/bookwyrm/tests/views/test_status.py +++ b/bookwyrm/tests/views/test_status.py @@ -8,7 +8,6 @@ from bookwyrm import forms, models, views from bookwyrm.settings import DOMAIN -@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") class StatusViews(TestCase): """ viewing and creating statuses """ @@ -41,7 +40,7 @@ class StatusViews(TestCase): parent_work=work, ) - def test_handle_status(self, _): + def test_handle_status(self): """ create a status """ view = views.CreateStatus.as_view() form = forms.CommentForm( @@ -54,23 +53,20 @@ class StatusViews(TestCase): ) request = self.factory.post("", form.data) request.user = self.local_user - - with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock: + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): view(request, "comment") - self.assertTrue(redis_mock.called) - status = models.Comment.objects.get() self.assertEqual(status.content, "

hi

") self.assertEqual(status.user, self.local_user) self.assertEqual(status.book, self.book) - def test_handle_status_reply(self, _): + def test_handle_status_reply(self): """ create a status in reply to an existing status """ view = views.CreateStatus.as_view() user = models.User.objects.create_user( "rat", "rat@rat.com", "password", local=True ) - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): parent = models.Status.objects.create( content="parent status", user=self.local_user ) @@ -84,17 +80,14 @@ class StatusViews(TestCase): ) request = self.factory.post("", form.data) request.user = user - - with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock: + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): view(request, "reply") - self.assertTrue(redis_mock.called) - status = models.Status.objects.get(user=user) self.assertEqual(status.content, "

hi

") self.assertEqual(status.user, user) self.assertEqual(models.Notification.objects.get().user, self.local_user) - def test_handle_status_mentions(self, _): + def test_handle_status_mentions(self): """ @mention a user in a post """ view = views.CreateStatus.as_view() user = models.User.objects.create_user( @@ -111,10 +104,8 @@ class StatusViews(TestCase): request = self.factory.post("", form.data) request.user = self.local_user - with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock: + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): view(request, "comment") - self.assertTrue(redis_mock.called) - status = models.Status.objects.get() self.assertEqual(list(status.mention_users.all()), [user]) self.assertEqual(models.Notification.objects.get().user, user) @@ -122,7 +113,7 @@ class StatusViews(TestCase): status.content, '

hi @rat

' % user.remote_id ) - def test_handle_status_reply_with_mentions(self, _): + def test_handle_status_reply_with_mentions(self): """ reply to a post with an @mention'ed user """ view = views.CreateStatus.as_view() user = models.User.objects.create_user( @@ -139,9 +130,8 @@ class StatusViews(TestCase): request = self.factory.post("", form.data) request.user = self.local_user - with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock: + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): view(request, "comment") - self.assertTrue(redis_mock.called) status = models.Status.objects.get() form = forms.ReplyForm( @@ -154,10 +144,8 @@ class StatusViews(TestCase): ) request = self.factory.post("", form.data) request.user = user - - with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock: + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): view(request, "reply") - self.assertTrue(redis_mock.called) reply = models.Status.replies(status).first() self.assertEqual(reply.content, "

right

") @@ -166,7 +154,7 @@ class StatusViews(TestCase): self.assertFalse(self.remote_user in reply.mention_users.all()) self.assertTrue(self.local_user in reply.mention_users.all()) - def test_find_mentions(self, _): + def test_find_mentions(self): """ detect and look up @ mentions of users """ user = models.User.objects.create_user( "nutria@%s" % DOMAIN, @@ -212,7 +200,7 @@ class StatusViews(TestCase): ("@nutria@%s" % DOMAIN, user), ) - def test_format_links(self, _): + def test_format_links(self): """ find and format urls into a tags """ url = "http://www.fish.com/" self.assertEqual( @@ -235,7 +223,7 @@ class StatusViews(TestCase): "?q=arkady+strugatsky&mode=everything" % url, ) - def test_to_markdown(self, _): + def test_to_markdown(self): """ this is mostly handled in other places, but nonetheless """ text = "_hi_ and http://fish.com is rad" result = views.status.to_markdown(text) @@ -244,36 +232,32 @@ class StatusViews(TestCase): '

hi and fish.com ' "is rad

", ) - def test_to_markdown_link(self, _): + def test_to_markdown_link(self): """ this is mostly handled in other places, but nonetheless """ text = "[hi](http://fish.com) is rad" result = views.status.to_markdown(text) self.assertEqual(result, '

hi ' "is rad

") - def test_handle_delete_status(self, mock): + def test_handle_delete_status(self): """ marks a status as deleted """ view = views.DeleteStatus.as_view() - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create(user=self.local_user, content="hi") self.assertFalse(status.deleted) request = self.factory.post("") request.user = self.local_user - - with patch( - "bookwyrm.activitystreams.ActivityStream.remove_status" - ) as redis_mock: + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock: view(request, status.id) - self.assertTrue(redis_mock.called) - activity = json.loads(mock.call_args_list[1][0][1]) - self.assertEqual(activity["type"], "Delete") - self.assertEqual(activity["object"]["type"], "Tombstone") + activity = json.loads(mock.call_args_list[0][0][1]) + self.assertEqual(activity["type"], "Delete") + self.assertEqual(activity["object"]["type"], "Tombstone") status.refresh_from_db() self.assertTrue(status.deleted) - def test_handle_delete_status_permission_denied(self, _): + def test_handle_delete_status_permission_denied(self): """ marks a status as deleted """ view = views.DeleteStatus.as_view() - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create(user=self.local_user, content="hi") self.assertFalse(status.deleted) request = self.factory.post("") @@ -284,23 +268,20 @@ class StatusViews(TestCase): status.refresh_from_db() self.assertFalse(status.deleted) - def test_handle_delete_status_moderator(self, mock): + def test_handle_delete_status_moderator(self): """ marks a status as deleted """ view = views.DeleteStatus.as_view() - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create(user=self.local_user, content="hi") self.assertFalse(status.deleted) request = self.factory.post("") request.user = self.remote_user request.user.is_superuser = True - with patch( - "bookwyrm.activitystreams.ActivityStream.remove_status" - ) as redis_mock: + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock: view(request, status.id) - self.assertTrue(redis_mock.called) - activity = json.loads(mock.call_args_list[1][0][1]) - self.assertEqual(activity["type"], "Delete") - self.assertEqual(activity["object"]["type"], "Tombstone") + activity = json.loads(mock.call_args_list[0][0][1]) + self.assertEqual(activity["type"], "Delete") + self.assertEqual(activity["object"]["type"], "Tombstone") status.refresh_from_db() self.assertTrue(status.deleted) diff --git a/bookwyrm/tests/views/test_updates.py b/bookwyrm/tests/views/test_updates.py index dff730e6..b41b2014 100644 --- a/bookwyrm/tests/views/test_updates.py +++ b/bookwyrm/tests/views/test_updates.py @@ -1,7 +1,5 @@ """ test for app action functionality """ import json -from unittest.mock import patch - from django.http import JsonResponse from django.test import TestCase from django.test.client import RequestFactory @@ -24,33 +22,21 @@ class UpdateViews(TestCase): ) models.SiteSettings.objects.create() - def test_get_notification_count(self): + def test_get_updates(self): """ there are so many views, this just makes sure it LOADS """ + view = views.Updates.as_view() request = self.factory.get("") request.user = self.local_user - result = views.get_notification_count(request) + result = view(request) self.assertIsInstance(result, JsonResponse) data = json.loads(result.getvalue()) - self.assertEqual(data["count"], 0) + self.assertEqual(data["notifications"], 0) models.Notification.objects.create( notification_type="BOOST", user=self.local_user ) - result = views.get_notification_count(request) + result = view(request) self.assertIsInstance(result, JsonResponse) data = json.loads(result.getvalue()) - self.assertEqual(data["count"], 1) - - def test_get_unread_status_count(self): - """ there are so many views, this just makes sure it LOADS """ - request = self.factory.get("") - request.user = self.local_user - - with patch("bookwyrm.activitystreams.ActivityStream.get_unread_count") as mock: - mock.return_value = 3 - result = views.get_unread_status_count(request, "home") - - self.assertIsInstance(result, JsonResponse) - data = json.loads(result.getvalue()) - self.assertEqual(data["count"], 3) + self.assertEqual(data["notifications"], 1) diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 44db6892..76622799 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -37,8 +37,7 @@ urlpatterns = [ re_path(r"^api/v1/instance/?$", views.instance_info), re_path(r"^api/v1/instance/peers/?$", views.peers), # polling updates - re_path("^api/updates/notifications/?$", views.get_notification_count), - re_path("^api/updates/stream/(?P[a-z]+)/?$", views.get_unread_status_count), + re_path("^api/updates/notifications/?$", views.Updates.as_view()), # authentication re_path(r"^login/?$", views.Login.as_view()), re_path(r"^register/?$", views.Register.as_view()), diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index 3439304f..f1ebfc4c 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -33,6 +33,6 @@ from .shelf import shelve, unshelve from .site import Site from .status import CreateStatus, DeleteStatus from .tag import Tag, AddTag, RemoveTag -from .updates import get_notification_count, get_unread_status_count +from .updates import Updates from .user import User, EditUser, Followers, Following from .wellknown import webfinger, nodeinfo_pointer, nodeinfo, instance_info, peers diff --git a/bookwyrm/views/books.py b/bookwyrm/views/books.py index cabc3223..f2aa76d7 100644 --- a/bookwyrm/views/books.py +++ b/bookwyrm/views/books.py @@ -19,7 +19,8 @@ from bookwyrm.activitypub import ActivitypubResponse from bookwyrm.connectors import connector_manager from bookwyrm.connectors.abstract_connector import get_image from bookwyrm.settings import PAGE_LENGTH -from .helpers import is_api_request, get_edition, privacy_filter +from .helpers import is_api_request, get_activity_feed, get_edition +from .helpers import privacy_filter # pylint: disable= no-self-use @@ -52,7 +53,7 @@ class Book(View): # all reviews for the book reviews = models.Review.objects.filter(book__in=work.editions.all()) - reviews = privacy_filter(request.user, reviews) + reviews = get_activity_feed(request.user, queryset=reviews) # the reviews to show paginated = Paginator( diff --git a/bookwyrm/views/feed.py b/bookwyrm/views/feed.py index e0de932d..d08c9a42 100644 --- a/bookwyrm/views/feed.py +++ b/bookwyrm/views/feed.py @@ -6,12 +6,13 @@ from django.http import HttpResponseNotFound from django.template.response import TemplateResponse from django.utils import timezone from django.utils.decorators import method_decorator +from django.utils.translation import gettext as _ from django.views import View -from bookwyrm import activitystreams, forms, models +from bookwyrm import forms, models from bookwyrm.activitypub import ActivitypubResponse -from bookwyrm.settings import PAGE_LENGTH, STREAMS -from .helpers import get_user_from_username, privacy_filter +from bookwyrm.settings import PAGE_LENGTH +from .helpers import get_activity_feed, get_user_from_username from .helpers import is_api_request, is_bookwyrm_request, object_visible_to_user @@ -27,11 +28,19 @@ class Feed(View): except ValueError: page = 1 - if not tab in STREAMS: - tab = "home" - - activities = activitystreams.streams[tab].get_activity_stream(request.user) - + if tab == "home": + activities = get_activity_feed(request.user, following_only=True) + tab_title = _("Home") + elif tab == "local": + activities = get_activity_feed( + request.user, privacy=["public", "followers"], local_only=True + ) + tab_title = _("Local") + else: + activities = get_activity_feed( + request.user, privacy=["public", "followers"] + ) + tab_title = _("Federated") paginated = Paginator(activities, PAGE_LENGTH) data = { @@ -40,6 +49,7 @@ class Feed(View): "user": request.user, "activities": paginated.page(page), "tab": tab, + "tab_title": tab_title, "goal_form": forms.GoalForm(), "path": "/%s" % tab, }, @@ -58,13 +68,7 @@ class DirectMessage(View): except ValueError: page = 1 - # remove fancy subclasses of status, keep just good ol' notes - queryset = models.Status.objects.filter( - review__isnull=True, - comment__isnull=True, - quotation__isnull=True, - generatednote__isnull=True, - ) + queryset = models.Status.objects user = None if username: @@ -75,7 +79,9 @@ class DirectMessage(View): if user: queryset = queryset.filter(Q(user=user) | Q(mention_users=user)) - activities = privacy_filter(request.user, queryset, privacy_levels=["direct"]) + activities = get_activity_feed( + request.user, privacy=["direct"], queryset=queryset + ) paginated = Paginator(activities, PAGE_LENGTH) activity_page = paginated.page(page) diff --git a/bookwyrm/views/helpers.py b/bookwyrm/views/helpers.py index 41822da2..c62856ba 100644 --- a/bookwyrm/views/helpers.py +++ b/bookwyrm/views/helpers.py @@ -59,11 +59,6 @@ def object_visible_to_user(viewer, obj): def privacy_filter(viewer, queryset, privacy_levels=None, following_only=False): """ filter objects that have "user" and "privacy" fields """ privacy_levels = privacy_levels or ["public", "unlisted", "followers", "direct"] - # if there'd a deleted field, exclude deleted items - try: - queryset = queryset.filter(deleted=False) - except FieldError: - pass # exclude blocks from both directions if not viewer.is_anonymous: @@ -107,6 +102,54 @@ def privacy_filter(viewer, queryset, privacy_levels=None, following_only=False): return queryset +def get_activity_feed( + user, privacy=None, local_only=False, following_only=False, queryset=None +): + """ get a filtered queryset of statuses """ + if queryset is None: + queryset = models.Status.objects.select_subclasses() + + # exclude deleted + queryset = queryset.exclude(deleted=True).order_by("-published_date") + + # apply privacy filters + queryset = privacy_filter(user, queryset, privacy, following_only=following_only) + + # only show dms if we only want dms + if privacy == ["direct"]: + # dms are direct statuses not related to books + queryset = queryset.filter( + review__isnull=True, + comment__isnull=True, + quotation__isnull=True, + generatednote__isnull=True, + ) + else: + try: + queryset = queryset.exclude( + review__isnull=True, + comment__isnull=True, + quotation__isnull=True, + generatednote__isnull=True, + privacy="direct", + ) + except FieldError: + # if we're looking at a subtype of Status (like Review) + pass + + # filter for only local status + if local_only: + queryset = queryset.filter(user__local=True) + + # remove statuses that have boosts in the same queryset + try: + queryset = queryset.filter(~Q(boosters__in=queryset)) + except ValueError: + pass + + return queryset + + def handle_remote_webfinger(query): """ webfingerin' other servers """ user = None diff --git a/bookwyrm/views/rss_feed.py b/bookwyrm/views/rss_feed.py index ed3e84f4..57821af4 100644 --- a/bookwyrm/views/rss_feed.py +++ b/bookwyrm/views/rss_feed.py @@ -1,7 +1,7 @@ """ serialize user's posts in rss feed """ from django.contrib.syndication.views import Feed -from .helpers import get_user_from_username, privacy_filter +from .helpers import get_activity_feed, get_user_from_username # pylint: disable=no-self-use, unused-argument class RssFeed(Feed): @@ -24,10 +24,10 @@ class RssFeed(Feed): def items(self, obj): """ the user's activity feed """ - return privacy_filter( + return get_activity_feed( obj, - obj.status_set.select_subclasses(), - privacy_levels=["public", "unlisted"], + privacy=["public", "unlisted"], + queryset=obj.status_set.select_subclasses(), ) def item_link(self, item): diff --git a/bookwyrm/views/updates.py b/bookwyrm/views/updates.py index cc5fc419..83b680c0 100644 --- a/bookwyrm/views/updates.py +++ b/bookwyrm/views/updates.py @@ -1,24 +1,20 @@ """ endpoints for getting updates about activity """ from django.contrib.auth.decorators import login_required from django.http import JsonResponse +from django.utils.decorators import method_decorator +from django.views import View -from bookwyrm import activitystreams +# pylint: disable= no-self-use +@method_decorator(login_required, name="dispatch") +class Updates(View): + """ so the app can poll """ - -@login_required -def get_notification_count(request): - """ any notifications waiting? """ - return JsonResponse( - { - "count": request.user.notification_set.filter(read=False).count(), - } - ) - - -@login_required -def get_unread_status_count(request, stream="home"): - """ any unread statuses for this feed? """ - stream = activitystreams.streams.get(stream) - if not stream: - return JsonResponse({}) - return JsonResponse({"count": stream.get_unread_count(request.user)}) + def get(self, request): + """ any notifications waiting? """ + return JsonResponse( + { + "notifications": request.user.notification_set.filter( + read=False + ).count(), + } + ) diff --git a/bookwyrm/views/user.py b/bookwyrm/views/user.py index b8e3a86d..690bf158 100644 --- a/bookwyrm/views/user.py +++ b/bookwyrm/views/user.py @@ -16,8 +16,8 @@ from django.views import View from bookwyrm import forms, models from bookwyrm.activitypub import ActivitypubResponse from bookwyrm.settings import PAGE_LENGTH -from .helpers import get_user_from_username, is_api_request -from .helpers import is_blocked, privacy_filter, object_visible_to_user +from .helpers import get_activity_feed, get_user_from_username, is_api_request +from .helpers import is_blocked, object_visible_to_user # pylint: disable= no-self-use @@ -72,9 +72,9 @@ class User(View): break # user's posts - activities = privacy_filter( + activities = get_activity_feed( request.user, - user.status_set.select_subclasses(), + queryset=user.status_set.select_subclasses(), ) paginated = Paginator(activities, PAGE_LENGTH) goal = models.AnnualGoal.objects.filter( diff --git a/bw-dev b/bw-dev index b663a0e6..f9e9dda5 100755 --- a/bw-dev +++ b/bw-dev @@ -28,6 +28,10 @@ function initdb { execweb python manage.py initdb } +function makeitblack { + docker-compose run --rm web black celerywyrm bookwyrm +} + CMD=$1 shift diff --git a/docker-compose.yml b/docker-compose.yml index 1eab77f5..3691c96c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -45,25 +45,13 @@ services: depends_on: - db - celery_worker - - redis_activity networks: - main ports: - 8000:8000 - redis_activity: + redis: image: redis - command: redis-server --requirepass ${REDIS_ACTIVITY_PASSWORD} - volumes: - - ./redis.conf:/etc/redis/redis.conf - env_file: .env - ports: - - 6378:6378 - networks: - - main - restart: on-failure - redis_broker: - image: redis - command: redis-server --requirepass ${REDIS_BROKER_PASSWORD} + command: redis-server --requirepass ${REDIS_PASSWORD} volumes: - ./redis.conf:/etc/redis/redis.conf env_file: .env @@ -84,7 +72,7 @@ services: - media_volume:/app/images depends_on: - db - - redis_broker + - redis restart: on-failure flower: build: . @@ -96,7 +84,7 @@ services: - main depends_on: - db - - redis_broker + - redis restart: on-failure ports: - 8888:8888