forked from mirrors/bookwyrm
Merge pull request #784 from mouse-reeve/redis-activity-stream
Uses redis to manage activity streams
This commit is contained in:
commit
ac0e13d6e2
42 changed files with 935 additions and 437 deletions
|
@ -22,8 +22,13 @@ POSTGRES_USER=fedireads
|
|||
POSTGRES_DB=fedireads
|
||||
POSTGRES_HOST=db
|
||||
|
||||
CELERY_BROKER=redis://redis:6379/0
|
||||
CELERY_RESULT_BACKEND=redis://redis:6379/0
|
||||
# Redis activity stream manager
|
||||
REDIS_ACTIVITY_HOST=redis_activity
|
||||
REDIS_ACTIVITY_PORT=6379
|
||||
|
||||
# Celery config with redis broker
|
||||
CELERY_BROKER=redis://redis_broker:6379/0
|
||||
CELERY_RESULT_BACKEND=redis://redis_broker:6379/0
|
||||
|
||||
EMAIL_HOST="smtp.mailgun.org"
|
||||
EMAIL_PORT=587
|
||||
|
|
|
@ -76,6 +76,7 @@ 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
|
||||
|
@ -236,6 +237,11 @@ 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.
|
||||
|
|
278
bookwyrm/activitystreams.py
Normal file
278
bookwyrm/activitystreams.py
Normal file
|
@ -0,0 +1,278 @@
|
|||
""" 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, 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)
|
36
bookwyrm/management/commands/rebuild_feeds.py
Normal file
36
bookwyrm/management/commands/rebuild_feeds.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
""" 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()
|
|
@ -34,7 +34,7 @@ class BookWyrmModel(models.Model):
|
|||
|
||||
@receiver(models.signals.post_save)
|
||||
# pylint: disable=unused-argument
|
||||
def execute_after_save(sender, instance, created, *args, **kwargs):
|
||||
def set_remote_id(sender, instance, created, *args, **kwargs):
|
||||
""" set the remote_id after save (when the id is available) """
|
||||
if not created or not hasattr(instance, "get_remote_id"):
|
||||
return
|
||||
|
|
|
@ -62,7 +62,7 @@ class UserFollows(ActivityMixin, UserRelationship):
|
|||
|
||||
status = "follows"
|
||||
|
||||
def to_activity(self):
|
||||
def to_activity(self): # pylint: disable=arguments-differ
|
||||
""" overrides default to manually set serializer """
|
||||
return activitypub.Follow(**generate_activity(self))
|
||||
|
||||
|
|
|
@ -114,7 +114,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
|
|||
return list(set(mentions))
|
||||
|
||||
@classmethod
|
||||
def ignore_activity(cls, activity):
|
||||
def ignore_activity(cls, activity): # pylint: disable=too-many-return-statements
|
||||
""" keep notes if they are replies to existing statuses """
|
||||
if activity.type == "Announce":
|
||||
try:
|
||||
|
|
|
@ -92,6 +92,12 @@ 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)
|
||||
|
||||
MAX_STREAM_LENGTH = env("MAX_STREAM_LENGTH", 200)
|
||||
STREAMS = ["home", "local", "federated"]
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases
|
||||
|
|
|
@ -61,9 +61,9 @@ function polling(el, delay) {
|
|||
|
||||
function updateCountElement(el, data) {
|
||||
const currentCount = el.innerText;
|
||||
const count = data[el.getAttribute('data-poll')];
|
||||
const count = data.count;
|
||||
if (count != currentCount) {
|
||||
addRemoveClass(el, 'hidden', count < 1);
|
||||
addRemoveClass(el.closest('[data-poll-wrapper]'), 'hidden', count < 1);
|
||||
el.innerText = count;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,15 @@
|
|||
{% load bookwyrm_tags %}
|
||||
{% block panel %}
|
||||
|
||||
<h1 class="title">{% blocktrans %}{{ tab_title }} Timeline{% endblocktrans %}</h1>
|
||||
<h1 class="title">
|
||||
{% if tab == 'home' %}
|
||||
{% trans "Home Timeline" %}
|
||||
{% elif tab == 'local' %}
|
||||
{% trans "Local Timeline" %}
|
||||
{% else %}
|
||||
{% trans "Federated Timeline" %}
|
||||
{% endif %}
|
||||
</h1>
|
||||
<div class="tabs">
|
||||
<ul>
|
||||
<li class="{% if tab == 'home' %}is-active{% endif %}"{% if tab == 'home' %} aria-current="page"{% endif %}>
|
||||
|
@ -19,7 +27,12 @@
|
|||
</div>
|
||||
|
||||
{# announcements and system messages #}
|
||||
{% if request.user.show_goal and not goal and tab == 'home' and not activities.number > 1 %}
|
||||
{% if not activities.number > 1 %}
|
||||
<a href="{{ request.path }}" class="hidden notification is-primary is-block" data-poll-wrapper>
|
||||
{% blocktrans %}load <span data-poll="stream/{{ tab }}">0</span> unread status(es){% endblocktrans %}
|
||||
</a>
|
||||
|
||||
{% if request.user.show_goal and not goal and tab == 'home' %}
|
||||
{% now 'Y' as year %}
|
||||
<section class="block">
|
||||
{% include 'snippets/goal_card.html' with year=year %}
|
||||
|
@ -27,6 +40,8 @@
|
|||
</section>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{# activity feed #}
|
||||
{% if not activities %}
|
||||
<p>{% trans "There aren't any activities right now! Try following a user to get started" %}</p>
|
||||
|
|
|
@ -139,8 +139,8 @@
|
|||
<span class="is-sr-only">{% trans "Notifications" %}</span>
|
||||
</span>
|
||||
</span>
|
||||
<span class="{% if not request.user|notification_count %}hidden {% endif %}tag is-danger is-medium" data-poll="notifications">
|
||||
{{ request.user | notification_count }}
|
||||
<span class="{% if not request.user|notification_count %}hidden {% endif %}tag is-danger is-medium" data-poll-wrapper>
|
||||
<span data-poll="notifications">{{ request.user | notification_count }}</span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -19,6 +19,7 @@ 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 """
|
||||
|
||||
|
@ -43,24 +44,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)
|
||||
|
@ -73,7 +74,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()
|
||||
|
@ -82,7 +83,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)
|
||||
|
@ -104,14 +105,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,
|
||||
|
@ -144,7 +145,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(
|
||||
|
@ -175,7 +176,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"):
|
||||
|
@ -214,7 +215,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(
|
||||
|
|
|
@ -13,6 +13,7 @@ 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 """
|
||||
|
||||
|
@ -44,7 +45,7 @@ class ActivitypubMixins(TestCase):
|
|||
}
|
||||
|
||||
# ActivitypubMixin
|
||||
def test_to_activity(self):
|
||||
def test_to_activity(self, _):
|
||||
""" model to ActivityPub json """
|
||||
|
||||
@dataclass(init=False)
|
||||
|
@ -65,7 +66,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
|
||||
|
@ -98,7 +99,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",
|
||||
|
@ -108,7 +109,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")
|
||||
|
@ -116,7 +117,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)
|
||||
|
@ -124,7 +125,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)
|
||||
|
@ -134,7 +135,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)
|
||||
|
@ -157,7 +158,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)
|
||||
|
@ -179,7 +180,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)
|
||||
|
@ -203,7 +204,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(
|
||||
|
@ -233,7 +234,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):
|
||||
|
@ -264,7 +265,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):
|
||||
|
@ -290,7 +291,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):
|
||||
|
@ -312,7 +313,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(
|
||||
|
@ -327,7 +328,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(
|
||||
|
@ -345,7 +346,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(
|
||||
|
|
|
@ -27,18 +27,18 @@ class BaseModel(TestCase):
|
|||
expected = instance.get_remote_id()
|
||||
self.assertEqual(expected, "https://%s/user/mouse/bookwyrmmodel/1" % DOMAIN)
|
||||
|
||||
def test_execute_after_save(self):
|
||||
def test_set_remote_id(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.execute_after_save(None, instance, True)
|
||||
base_model.set_remote_id(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.execute_after_save(None, instance, False)
|
||||
base_model.set_remote_id(None, instance, False)
|
||||
self.assertIsNone(instance.remote_id)
|
||||
|
|
|
@ -185,7 +185,8 @@ class ActivitypubFields(TestCase):
|
|||
self.assertEqual(model_instance.privacy_field, "unlisted")
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast")
|
||||
def test_privacy_field_set_activity_from_field(self, _):
|
||||
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||
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"
|
||||
|
|
|
@ -15,6 +15,7 @@ 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 """
|
||||
|
||||
|
@ -44,14 +45,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(
|
||||
|
@ -70,7 +71,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")
|
||||
|
@ -78,14 +79,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(
|
||||
|
@ -102,7 +103,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
|
||||
|
@ -113,20 +114,21 @@ 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 """
|
||||
status = models.Status.objects.create(
|
||||
content="test content",
|
||||
user=self.local_user,
|
||||
deleted=True,
|
||||
deleted_date=timezone.now(),
|
||||
)
|
||||
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(),
|
||||
)
|
||||
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
|
||||
|
@ -138,7 +140,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
|
||||
|
@ -152,7 +154,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
|
||||
|
@ -176,7 +178,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
|
||||
|
@ -187,7 +189,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
|
||||
|
@ -207,7 +209,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",
|
||||
|
@ -222,7 +224,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",
|
||||
|
@ -245,7 +247,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",
|
||||
|
@ -262,7 +264,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",
|
||||
|
@ -285,7 +287,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
|
||||
|
||||
|
@ -311,7 +313,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
|
||||
|
@ -323,7 +325,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"
|
||||
|
@ -335,7 +337,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
|
||||
|
@ -355,7 +357,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
|
||||
|
@ -364,7 +366,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
|
||||
|
@ -375,7 +377,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
|
||||
|
@ -388,7 +390,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",
|
||||
|
|
204
bookwyrm/tests/test_activitystreams.py
Normal file
204
bookwyrm/tests/test_activitystreams.py
Normal file
|
@ -0,0 +1,204 @@
|
|||
""" 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)
|
|
@ -203,7 +203,8 @@ class GoodreadsImport(TestCase):
|
|||
self.assertEqual(readthrough.finish_date.month, 10)
|
||||
self.assertEqual(readthrough.finish_date.day, 25)
|
||||
|
||||
def test_handle_imported_book_review(self):
|
||||
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||
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")
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
""" testing import """
|
||||
from collections import namedtuple
|
||||
import csv
|
||||
import pathlib
|
||||
from unittest.mock import patch
|
||||
|
@ -16,8 +15,8 @@ class LibrarythingImport(TestCase):
|
|||
""" importing from librarything tsv """
|
||||
|
||||
def setUp(self):
|
||||
self.importer = LibrarythingImporter()
|
||||
""" use a test tsv """
|
||||
self.importer = LibrarythingImporter()
|
||||
datafile = pathlib.Path(__file__).parent.joinpath("data/librarything.tsv")
|
||||
|
||||
# Librarything generates latin encoded exports...
|
||||
|
@ -200,7 +199,8 @@ class LibrarythingImport(TestCase):
|
|||
self.assertEqual(readthrough.finish_date.month, 5)
|
||||
self.assertEqual(readthrough.finish_date.day, 8)
|
||||
|
||||
def test_handle_imported_book_review(self):
|
||||
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||
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")
|
||||
|
|
|
@ -10,6 +10,7 @@ from bookwyrm import models
|
|||
from bookwyrm.templatetags import bookwyrm_tags
|
||||
|
||||
|
||||
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||
class TemplateTags(TestCase):
|
||||
""" lotta different things here """
|
||||
|
||||
|
@ -32,34 +33,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)
|
||||
|
||||
|
@ -72,7 +73,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(
|
||||
|
@ -84,12 +85,13 @@ class TemplateTags(TestCase):
|
|||
second_child = models.Status.objects.create(
|
||||
reply_parent=parent, user=self.user, content="hi"
|
||||
)
|
||||
third_child = models.Status.objects.create(
|
||||
reply_parent=parent,
|
||||
user=self.user,
|
||||
deleted=True,
|
||||
deleted_date=timezone.now(),
|
||||
)
|
||||
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(),
|
||||
)
|
||||
|
||||
replies = bookwyrm_tags.get_replies(parent)
|
||||
self.assertEqual(len(replies), 2)
|
||||
|
@ -97,7 +99,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(
|
||||
|
@ -111,7 +113,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)
|
||||
|
||||
|
@ -120,7 +122,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)
|
||||
|
||||
|
@ -129,7 +131,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)
|
||||
|
@ -147,7 +149,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)
|
||||
|
@ -156,7 +158,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
|
||||
|
@ -172,12 +174,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"), "")
|
||||
|
||||
|
@ -207,7 +209,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, "<p><em>hi</em></p>")
|
||||
|
@ -215,13 +217,13 @@ class TemplateTags(TestCase):
|
|||
result = bookwyrm_tags.get_markdown("<marquee>_hi_</marquee>")
|
||||
self.assertEqual(result, "<p><em>hi</em></p>")
|
||||
|
||||
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)
|
||||
|
@ -246,7 +248,7 @@ class TemplateTags(TestCase):
|
|||
result = bookwyrm_tags.get_status_preview_name(status)
|
||||
self.assertEqual(result, "quotation from <em>Test Book</em>")
|
||||
|
||||
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)
|
||||
|
|
|
@ -7,6 +7,7 @@ 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 """
|
||||
|
||||
|
@ -32,7 +33,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("")
|
||||
|
@ -42,20 +43,19 @@ 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)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
models.UserFollowRequest.objects.create(
|
||||
user_subject=self.local_user, user_object=self.remote_user
|
||||
)
|
||||
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.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.remove_user_statuses"):
|
||||
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.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_user_statuses"):
|
||||
views.block.unblock(request, self.remote_user.id)
|
||||
|
||||
self.assertFalse(models.UserBlocks.objects.exists())
|
||||
|
|
|
@ -14,6 +14,8 @@ 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 """
|
||||
|
||||
|
@ -34,7 +36,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("")
|
||||
|
@ -44,7 +46,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"):
|
||||
|
@ -64,7 +66,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()
|
||||
|
||||
|
@ -100,7 +102,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"):
|
||||
|
@ -120,7 +122,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("")
|
||||
|
@ -130,7 +132,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(
|
||||
|
|
|
@ -102,7 +102,8 @@ class GoalViews(TestCase):
|
|||
result = view(request, self.local_user.localname, 2020)
|
||||
self.assertEqual(result.status_code, 404)
|
||||
|
||||
def test_create_goal(self):
|
||||
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||
def test_create_goal(self, _):
|
||||
""" create a new goal """
|
||||
view = views.Goal.as_view()
|
||||
request = self.factory.post(
|
||||
|
|
|
@ -80,113 +80,6 @@ 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"})
|
||||
|
@ -241,7 +134,8 @@ class ViewsHelpers(TestCase):
|
|||
self.assertIsInstance(result, models.User)
|
||||
self.assertEqual(result.username, "mouse@example.com")
|
||||
|
||||
def test_handle_reading_status_to_read(self):
|
||||
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||
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"):
|
||||
|
@ -253,7 +147,8 @@ class ViewsHelpers(TestCase):
|
|||
self.assertEqual(status.mention_books.first(), self.book)
|
||||
self.assertEqual(status.content, "wants to read")
|
||||
|
||||
def test_handle_reading_status_reading(self):
|
||||
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||
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"):
|
||||
|
@ -265,7 +160,8 @@ class ViewsHelpers(TestCase):
|
|||
self.assertEqual(status.mention_books.first(), self.book)
|
||||
self.assertEqual(status.content, "started reading")
|
||||
|
||||
def test_handle_reading_status_read(self):
|
||||
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||
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"):
|
||||
|
@ -277,7 +173,8 @@ class ViewsHelpers(TestCase):
|
|||
self.assertEqual(status.mention_books.first(), self.book)
|
||||
self.assertEqual(status.content, "finished reading")
|
||||
|
||||
def test_handle_reading_status_other(self):
|
||||
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||
def test_handle_reading_status_other(self, _):
|
||||
""" posts shelve activities """
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
views.helpers.handle_reading_status(
|
||||
|
@ -285,7 +182,8 @@ class ViewsHelpers(TestCase):
|
|||
)
|
||||
self.assertFalse(models.GeneratedNote.objects.exists())
|
||||
|
||||
def test_object_visible_to_user(self):
|
||||
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||
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"
|
||||
|
@ -313,7 +211,8 @@ class ViewsHelpers(TestCase):
|
|||
obj.mention_users.add(self.local_user)
|
||||
self.assertTrue(views.helpers.object_visible_to_user(self.local_user, obj))
|
||||
|
||||
def test_object_visible_to_user_follower(self):
|
||||
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||
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(
|
||||
|
@ -332,7 +231,8 @@ class ViewsHelpers(TestCase):
|
|||
obj.mention_users.add(self.local_user)
|
||||
self.assertTrue(views.helpers.object_visible_to_user(self.local_user, obj))
|
||||
|
||||
def test_object_visible_to_user_blocked(self):
|
||||
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||
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(
|
||||
|
|
|
@ -38,11 +38,12 @@ class Inbox(TestCase):
|
|||
outbox="https://example.com/users/rat/outbox",
|
||||
)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
self.status = models.Status.objects.create(
|
||||
user=self.local_user,
|
||||
content="Test status",
|
||||
remote_id="https://example.com/status/1",
|
||||
)
|
||||
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.create_json = {
|
||||
"id": "hi",
|
||||
|
@ -139,7 +140,9 @@ class Inbox(TestCase):
|
|||
activity = self.create_json
|
||||
activity["object"] = status_data
|
||||
|
||||
views.inbox.activity_task(activity)
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock:
|
||||
views.inbox.activity_task(activity)
|
||||
self.assertTrue(redis_mock.called)
|
||||
|
||||
status = models.Quotation.objects.get()
|
||||
self.assertEqual(
|
||||
|
@ -166,7 +169,9 @@ class Inbox(TestCase):
|
|||
activity = self.create_json
|
||||
activity["object"] = status_data
|
||||
|
||||
views.inbox.activity_task(activity)
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock:
|
||||
views.inbox.activity_task(activity)
|
||||
self.assertTrue(redis_mock.called)
|
||||
status = models.Status.objects.last()
|
||||
self.assertEqual(status.content, "test content in note")
|
||||
self.assertEqual(status.mention_users.first(), self.local_user)
|
||||
|
@ -187,7 +192,9 @@ class Inbox(TestCase):
|
|||
activity = self.create_json
|
||||
activity["object"] = status_data
|
||||
|
||||
views.inbox.activity_task(activity)
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock:
|
||||
views.inbox.activity_task(activity)
|
||||
self.assertTrue(redis_mock.called)
|
||||
status = models.Status.objects.last()
|
||||
self.assertEqual(status.content, "test content in note")
|
||||
self.assertEqual(status.reply_parent, self.status)
|
||||
|
@ -218,7 +225,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_x(self):
|
||||
def test_handle_follow(self):
|
||||
""" remote user wants to follow local user """
|
||||
activity = {
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
|
@ -436,7 +443,11 @@ class Inbox(TestCase):
|
|||
"actor": self.remote_user.remote_id,
|
||||
"object": {"id": self.status.remote_id, "type": "Tombstone"},
|
||||
}
|
||||
views.inbox.activity_task(activity)
|
||||
with patch(
|
||||
"bookwyrm.activitystreams.ActivityStream.remove_status"
|
||||
) as redis_mock:
|
||||
views.inbox.activity_task(activity)
|
||||
self.assertTrue(redis_mock.called)
|
||||
# deletion doens't remove the status, it turns it into a tombstone
|
||||
status = models.Status.objects.get()
|
||||
self.assertTrue(status.deleted)
|
||||
|
@ -465,7 +476,11 @@ class Inbox(TestCase):
|
|||
"actor": self.remote_user.remote_id,
|
||||
"object": {"id": self.status.remote_id, "type": "Tombstone"},
|
||||
}
|
||||
views.inbox.activity_task(activity)
|
||||
with patch(
|
||||
"bookwyrm.activitystreams.ActivityStream.remove_status"
|
||||
) as redis_mock:
|
||||
views.inbox.activity_task(activity)
|
||||
self.assertTrue(redis_mock.called)
|
||||
# deletion doens't remove the status, it turns it into a tombstone
|
||||
status = models.Status.objects.get()
|
||||
self.assertTrue(status.deleted)
|
||||
|
@ -535,7 +550,8 @@ class Inbox(TestCase):
|
|||
views.inbox.activity_task(activity)
|
||||
self.assertEqual(models.Favorite.objects.count(), 0)
|
||||
|
||||
def test_handle_boost(self):
|
||||
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||
def test_handle_boost(self, _):
|
||||
""" boost a status """
|
||||
self.assertEqual(models.Notification.objects.count(), 0)
|
||||
activity = {
|
||||
|
@ -560,7 +576,8 @@ class Inbox(TestCase):
|
|||
content="hi",
|
||||
user=self.remote_user,
|
||||
)
|
||||
status.save(broadcast=False)
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
|
||||
status.save(broadcast=False)
|
||||
activity = {
|
||||
"type": "Announce",
|
||||
"id": "http://www.faraway.com/boost/12",
|
||||
|
@ -575,9 +592,10 @@ class Inbox(TestCase):
|
|||
|
||||
def test_handle_unboost(self):
|
||||
""" undo a boost """
|
||||
boost = models.Boost.objects.create(
|
||||
boosted_status=self.status, user=self.remote_user
|
||||
)
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
|
||||
boost = models.Boost.objects.create(
|
||||
boosted_status=self.status, user=self.remote_user
|
||||
)
|
||||
activity = {
|
||||
"type": "Undo",
|
||||
"actor": "hi",
|
||||
|
@ -591,7 +609,11 @@ class Inbox(TestCase):
|
|||
"object": self.status.remote_id,
|
||||
},
|
||||
}
|
||||
views.inbox.activity_task(activity)
|
||||
with patch(
|
||||
"bookwyrm.activitystreams.ActivityStream.remove_status"
|
||||
) as redis_mock:
|
||||
views.inbox.activity_task(activity)
|
||||
self.assertTrue(redis_mock.called)
|
||||
|
||||
def test_handle_unboost_unknown_boost(self):
|
||||
""" undo a boost """
|
||||
|
@ -863,6 +885,11 @@ 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)
|
||||
|
@ -896,5 +923,9 @@ class Inbox(TestCase):
|
|||
"object": "https://example.com/user/mouse",
|
||||
},
|
||||
}
|
||||
views.inbox.activity_task(activity)
|
||||
with patch(
|
||||
"bookwyrm.activitystreams.ActivityStream.add_user_statuses"
|
||||
) as redis_mock:
|
||||
views.inbox.activity_task(activity)
|
||||
self.assertTrue(redis_mock.called)
|
||||
self.assertFalse(models.UserBlocks.objects.exists())
|
||||
|
|
|
@ -6,6 +6,7 @@ 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 """
|
||||
|
||||
|
@ -38,12 +39,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.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
|
||||
status = models.Status.objects.create(user=self.local_user, content="hi")
|
||||
|
||||
view(request, status.id)
|
||||
|
@ -56,12 +57,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.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
|
||||
status = models.Status.objects.create(user=self.local_user, content="hi")
|
||||
views.Favorite.as_view()(request, status.id)
|
||||
|
||||
|
@ -73,12 +74,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.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
|
||||
status = models.Status.objects.create(user=self.local_user, content="hi")
|
||||
|
||||
view(request, status.id)
|
||||
|
@ -94,12 +95,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.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
|
||||
status = models.Status.objects.create(
|
||||
user=self.local_user, content="hi", privacy="unlisted"
|
||||
)
|
||||
|
@ -109,12 +110,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.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
|
||||
status = models.Status.objects.create(
|
||||
user=self.local_user, content="hi", privacy="followers"
|
||||
)
|
||||
|
@ -122,31 +123,35 @@ 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.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
|
||||
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):
|
||||
def test_handle_unboost(self, broadcast_mock):
|
||||
""" undo a boost """
|
||||
view = views.Unboost.as_view()
|
||||
request = self.factory.post("")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
|
||||
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)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
broadcast_mock.call_count = 0
|
||||
with patch(
|
||||
"bookwyrm.activitystreams.ActivityStream.remove_status"
|
||||
) as redis_mock:
|
||||
view(request, status.id)
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
self.assertEqual(broadcast_mock.call_count, 1)
|
||||
self.assertTrue(redis_mock.called)
|
||||
self.assertEqual(models.Boost.objects.count(), 0)
|
||||
self.assertEqual(models.Notification.objects.count(), 0)
|
||||
|
|
|
@ -3,12 +3,10 @@ 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
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
""" 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
|
||||
|
@ -30,7 +31,8 @@ class LandingViews(TestCase):
|
|||
view = views.Home.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
result = view(request)
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.get_activity_stream"):
|
||||
result = view(request)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
result.render()
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ 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 """
|
||||
|
||||
|
@ -32,19 +33,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")
|
||||
|
@ -52,9 +53,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.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
|
||||
models.Status.objects.create(
|
||||
content="PRIVATE!!", user=self.local_user, privacy="direct"
|
||||
)
|
||||
|
@ -75,9 +76,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.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
|
||||
models.Review.objects.create(
|
||||
content="look at this",
|
||||
name="hi",
|
||||
|
@ -101,9 +102,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.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
|
||||
models.Review.objects.create(
|
||||
name="hi",
|
||||
content="look at this",
|
||||
|
@ -119,9 +120,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.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
|
||||
models.Review.objects.create(
|
||||
name="hi",
|
||||
content="look at this",
|
||||
|
|
|
@ -8,6 +8,7 @@ from django.utils import timezone
|
|||
from bookwyrm import models, views
|
||||
|
||||
|
||||
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||
class ReadingViews(TestCase):
|
||||
""" viewing and creating statuses """
|
||||
|
||||
|
@ -39,7 +40,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())
|
||||
|
@ -70,7 +71,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"):
|
||||
|
@ -90,7 +91,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())
|
||||
|
@ -126,7 +127,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(
|
||||
|
@ -153,7 +154,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
|
||||
|
@ -170,7 +171,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(
|
||||
"",
|
||||
|
|
|
@ -26,28 +26,30 @@ class RssFeedView(TestCase):
|
|||
)
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
self.review = models.Review.objects.create(
|
||||
name="Review name",
|
||||
content="test content",
|
||||
rating=3,
|
||||
user=self.user,
|
||||
book=self.book,
|
||||
)
|
||||
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.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()
|
||||
|
||||
def test_rss_feed(self):
|
||||
@patch("bookwyrm.activitystreams.ActivityStream.get_activity_stream")
|
||||
def test_rss_feed(self, _):
|
||||
""" load an rss feed """
|
||||
view = rss_feed.RssFeed()
|
||||
request = self.factory.get("/user/rss_user/rss")
|
||||
|
|
|
@ -8,6 +8,7 @@ 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 """
|
||||
|
||||
|
@ -40,7 +41,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(
|
||||
|
@ -53,20 +54,23 @@ class StatusViews(TestCase):
|
|||
)
|
||||
request = self.factory.post("", form.data)
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock:
|
||||
view(request, "comment")
|
||||
self.assertTrue(redis_mock.called)
|
||||
|
||||
status = models.Comment.objects.get()
|
||||
self.assertEqual(status.content, "<p>hi</p>")
|
||||
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.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
|
||||
parent = models.Status.objects.create(
|
||||
content="parent status", user=self.local_user
|
||||
)
|
||||
|
@ -80,14 +84,17 @@ class StatusViews(TestCase):
|
|||
)
|
||||
request = self.factory.post("", form.data)
|
||||
request.user = user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock:
|
||||
view(request, "reply")
|
||||
self.assertTrue(redis_mock.called)
|
||||
|
||||
status = models.Status.objects.get(user=user)
|
||||
self.assertEqual(status.content, "<p>hi</p>")
|
||||
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(
|
||||
|
@ -104,8 +111,10 @@ class StatusViews(TestCase):
|
|||
request = self.factory.post("", form.data)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock:
|
||||
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)
|
||||
|
@ -113,7 +122,7 @@ class StatusViews(TestCase):
|
|||
status.content, '<p>hi <a href="%s">@rat</a></p>' % 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(
|
||||
|
@ -130,8 +139,9 @@ class StatusViews(TestCase):
|
|||
request = self.factory.post("", form.data)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock:
|
||||
view(request, "comment")
|
||||
self.assertTrue(redis_mock.called)
|
||||
status = models.Status.objects.get()
|
||||
|
||||
form = forms.ReplyForm(
|
||||
|
@ -144,8 +154,10 @@ class StatusViews(TestCase):
|
|||
)
|
||||
request = self.factory.post("", form.data)
|
||||
request.user = user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock:
|
||||
view(request, "reply")
|
||||
self.assertTrue(redis_mock.called)
|
||||
|
||||
reply = models.Status.replies(status).first()
|
||||
self.assertEqual(reply.content, "<p>right</p>")
|
||||
|
@ -154,7 +166,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,
|
||||
|
@ -200,7 +212,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(
|
||||
|
@ -223,7 +235,7 @@ class StatusViews(TestCase):
|
|||
"?q=arkady+strugatsky&mode=everything</a>" % 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 <marquee>rad</marquee>"
|
||||
result = views.status.to_markdown(text)
|
||||
|
@ -232,32 +244,36 @@ class StatusViews(TestCase):
|
|||
'<p><em>hi</em> and <a href="http://fish.com">fish.com</a> ' "is rad</p>",
|
||||
)
|
||||
|
||||
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 <marquee>rad</marquee>"
|
||||
result = views.status.to_markdown(text)
|
||||
self.assertEqual(result, '<p><a href="http://fish.com">hi</a> ' "is rad</p>")
|
||||
|
||||
def test_handle_delete_status(self):
|
||||
def test_handle_delete_status(self, mock):
|
||||
""" marks a status as deleted """
|
||||
view = views.DeleteStatus.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
|
||||
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.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
|
||||
with patch(
|
||||
"bookwyrm.activitystreams.ActivityStream.remove_status"
|
||||
) as redis_mock:
|
||||
view(request, status.id)
|
||||
activity = json.loads(mock.call_args_list[0][0][1])
|
||||
self.assertEqual(activity["type"], "Delete")
|
||||
self.assertEqual(activity["object"]["type"], "Tombstone")
|
||||
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")
|
||||
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.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
|
||||
status = models.Status.objects.create(user=self.local_user, content="hi")
|
||||
self.assertFalse(status.deleted)
|
||||
request = self.factory.post("")
|
||||
|
@ -268,20 +284,23 @@ class StatusViews(TestCase):
|
|||
status.refresh_from_db()
|
||||
self.assertFalse(status.deleted)
|
||||
|
||||
def test_handle_delete_status_moderator(self):
|
||||
def test_handle_delete_status_moderator(self, mock):
|
||||
""" marks a status as deleted """
|
||||
view = views.DeleteStatus.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
|
||||
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.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
with patch(
|
||||
"bookwyrm.activitystreams.ActivityStream.remove_status"
|
||||
) as redis_mock:
|
||||
view(request, status.id)
|
||||
activity = json.loads(mock.call_args_list[0][0][1])
|
||||
self.assertEqual(activity["type"], "Delete")
|
||||
self.assertEqual(activity["object"]["type"], "Tombstone")
|
||||
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")
|
||||
status.refresh_from_db()
|
||||
self.assertTrue(status.deleted)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
""" 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
|
||||
|
@ -22,21 +24,33 @@ class UpdateViews(TestCase):
|
|||
)
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_get_updates(self):
|
||||
def test_get_notification_count(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 = view(request)
|
||||
result = views.get_notification_count(request)
|
||||
self.assertIsInstance(result, JsonResponse)
|
||||
data = json.loads(result.getvalue())
|
||||
self.assertEqual(data["notifications"], 0)
|
||||
self.assertEqual(data["count"], 0)
|
||||
|
||||
models.Notification.objects.create(
|
||||
notification_type="BOOST", user=self.local_user
|
||||
)
|
||||
result = view(request)
|
||||
result = views.get_notification_count(request)
|
||||
self.assertIsInstance(result, JsonResponse)
|
||||
data = json.loads(result.getvalue())
|
||||
self.assertEqual(data["notifications"], 1)
|
||||
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)
|
||||
|
|
|
@ -37,7 +37,8 @@ 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.Updates.as_view()),
|
||||
re_path("^api/updates/notifications/?$", views.get_notification_count),
|
||||
re_path("^api/updates/stream/(?P<stream>[a-z]+)/?$", views.get_unread_status_count),
|
||||
# authentication
|
||||
re_path(r"^login/?$", views.Login.as_view()),
|
||||
re_path(r"^register/?$", views.Register.as_view()),
|
||||
|
|
|
@ -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 Updates
|
||||
from .updates import get_notification_count, get_unread_status_count
|
||||
from .user import User, EditUser, Followers, Following
|
||||
from .wellknown import webfinger, nodeinfo_pointer, nodeinfo, instance_info, peers
|
||||
|
|
|
@ -19,8 +19,7 @@ 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_activity_feed, get_edition
|
||||
from .helpers import privacy_filter
|
||||
from .helpers import is_api_request, get_edition, privacy_filter
|
||||
|
||||
|
||||
# pylint: disable= no-self-use
|
||||
|
@ -53,7 +52,7 @@ class Book(View):
|
|||
|
||||
# all reviews for the book
|
||||
reviews = models.Review.objects.filter(book__in=work.editions.all())
|
||||
reviews = get_activity_feed(request.user, queryset=reviews)
|
||||
reviews = privacy_filter(request.user, reviews)
|
||||
|
||||
# the reviews to show
|
||||
paginated = Paginator(
|
||||
|
|
|
@ -6,13 +6,12 @@ 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 forms, models
|
||||
from bookwyrm import activitystreams, forms, models
|
||||
from bookwyrm.activitypub import ActivitypubResponse
|
||||
from bookwyrm.settings import PAGE_LENGTH
|
||||
from .helpers import get_activity_feed, get_user_from_username
|
||||
from bookwyrm.settings import PAGE_LENGTH, STREAMS
|
||||
from .helpers import get_user_from_username, privacy_filter
|
||||
from .helpers import is_api_request, is_bookwyrm_request, object_visible_to_user
|
||||
|
||||
|
||||
|
@ -28,19 +27,11 @@ class Feed(View):
|
|||
except ValueError:
|
||||
page = 1
|
||||
|
||||
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")
|
||||
if not tab in STREAMS:
|
||||
tab = "home"
|
||||
|
||||
activities = activitystreams.streams[tab].get_activity_stream(request.user)
|
||||
|
||||
paginated = Paginator(activities, PAGE_LENGTH)
|
||||
|
||||
data = {
|
||||
|
@ -49,7 +40,6 @@ class Feed(View):
|
|||
"user": request.user,
|
||||
"activities": paginated.page(page),
|
||||
"tab": tab,
|
||||
"tab_title": tab_title,
|
||||
"goal_form": forms.GoalForm(),
|
||||
"path": "/%s" % tab,
|
||||
},
|
||||
|
@ -68,7 +58,13 @@ class DirectMessage(View):
|
|||
except ValueError:
|
||||
page = 1
|
||||
|
||||
queryset = models.Status.objects
|
||||
# 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,
|
||||
)
|
||||
|
||||
user = None
|
||||
if username:
|
||||
|
@ -79,9 +75,7 @@ class DirectMessage(View):
|
|||
if user:
|
||||
queryset = queryset.filter(Q(user=user) | Q(mention_users=user))
|
||||
|
||||
activities = get_activity_feed(
|
||||
request.user, privacy=["direct"], queryset=queryset
|
||||
)
|
||||
activities = privacy_filter(request.user, queryset, privacy_levels=["direct"])
|
||||
|
||||
paginated = Paginator(activities, PAGE_LENGTH)
|
||||
activity_page = paginated.page(page)
|
||||
|
|
|
@ -59,6 +59,11 @@ 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:
|
||||
|
@ -102,54 +107,6 @@ 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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
""" serialize user's posts in rss feed """
|
||||
|
||||
from django.contrib.syndication.views import Feed
|
||||
from .helpers import get_activity_feed, get_user_from_username
|
||||
from .helpers import get_user_from_username, privacy_filter
|
||||
|
||||
# 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 get_activity_feed(
|
||||
return privacy_filter(
|
||||
obj,
|
||||
privacy=["public", "unlisted"],
|
||||
queryset=obj.status_set.select_subclasses(),
|
||||
obj.status_set.select_subclasses(),
|
||||
privacy_levels=["public", "unlisted"],
|
||||
)
|
||||
|
||||
def item_link(self, item):
|
||||
|
|
|
@ -1,20 +1,24 @@
|
|||
""" 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
|
||||
|
||||
# pylint: disable= no-self-use
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class Updates(View):
|
||||
""" so the app can poll """
|
||||
from bookwyrm import activitystreams
|
||||
|
||||
def get(self, request):
|
||||
""" any notifications waiting? """
|
||||
return JsonResponse(
|
||||
{
|
||||
"notifications": request.user.notification_set.filter(
|
||||
read=False
|
||||
).count(),
|
||||
}
|
||||
)
|
||||
|
||||
@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)})
|
||||
|
|
|
@ -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_activity_feed, get_user_from_username, is_api_request
|
||||
from .helpers import is_blocked, object_visible_to_user
|
||||
from .helpers import get_user_from_username, is_api_request
|
||||
from .helpers import is_blocked, privacy_filter, object_visible_to_user
|
||||
|
||||
|
||||
# pylint: disable= no-self-use
|
||||
|
@ -72,9 +72,9 @@ class User(View):
|
|||
break
|
||||
|
||||
# user's posts
|
||||
activities = get_activity_feed(
|
||||
activities = privacy_filter(
|
||||
request.user,
|
||||
queryset=user.status_set.select_subclasses(),
|
||||
user.status_set.select_subclasses(),
|
||||
)
|
||||
paginated = Paginator(activities, PAGE_LENGTH)
|
||||
goal = models.AnnualGoal.objects.filter(
|
||||
|
|
|
@ -31,11 +31,20 @@ services:
|
|||
depends_on:
|
||||
- db
|
||||
- celery_worker
|
||||
- redis_activity
|
||||
networks:
|
||||
- main
|
||||
ports:
|
||||
- 8000:8000
|
||||
redis:
|
||||
redis_activity:
|
||||
image: redis
|
||||
env_file: .env
|
||||
ports:
|
||||
- 6378:6378
|
||||
networks:
|
||||
- main
|
||||
restart: on-failure
|
||||
redis_broker:
|
||||
image: redis
|
||||
env_file: .env
|
||||
ports:
|
||||
|
@ -55,7 +64,7 @@ services:
|
|||
- media_volume:/app/images
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
- redis_broker
|
||||
restart: on-failure
|
||||
flower:
|
||||
build: .
|
||||
|
@ -67,7 +76,7 @@ services:
|
|||
- main
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
- redis_broker
|
||||
restart: on-failure
|
||||
ports:
|
||||
- 8888:8888
|
||||
|
|
Loading…
Reference in a new issue