Merge pull request #880 from bookwyrm-social/suggestions-redis

Uses redis for storing suggested users
This commit is contained in:
Mouse Reeve 2021-08-04 11:23:33 -06:00 committed by GitHub
commit 0a9fe4631c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
90 changed files with 1363 additions and 915 deletions

View file

@ -106,6 +106,7 @@ class ActivityObject:
value = field.default
setattr(self, field.name, value)
# pylint: disable=too-many-locals,too-many-branches
def to_model(self, model=None, instance=None, allow_create=True, save=True):
"""convert from an activity to a model instance"""
model = model or get_model_from_type(self.type)
@ -126,27 +127,36 @@ class ActivityObject:
return None
instance = instance or model()
# keep track of what we've changed
update_fields = []
for field in instance.simple_fields:
try:
field.set_field_from_activity(instance, self)
changed = field.set_field_from_activity(instance, self)
if changed:
update_fields.append(field.name)
except AttributeError as e:
raise ActivitySerializerError(e)
# image fields have to be set after other fields because they can save
# too early and jank up users
for field in instance.image_fields:
field.set_field_from_activity(instance, self, save=save)
changed = field.set_field_from_activity(instance, self, save=save)
if changed:
update_fields.append(field.name)
if not save:
return instance
with transaction.atomic():
# can't force an update on fields unless the object already exists in the db
if not instance.id:
update_fields = None
# we can't set many to many and reverse fields on an unsaved object
try:
try:
instance.save(broadcast=False)
instance.save(broadcast=False, update_fields=update_fields)
except TypeError:
instance.save()
instance.save(update_fields=update_fields)
except IntegrityError as e:
raise ActivitySerializerError(e)

View file

@ -1,12 +1,6 @@
""" Re-create user streams """
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
)
from bookwyrm import activitystreams, models
def populate_streams():

View file

@ -0,0 +1,25 @@
""" Populate suggested users """
from django.core.management.base import BaseCommand
from bookwyrm import models
from bookwyrm.suggested_users import rerank_suggestions_task
def populate_suggestions():
"""build all the streams for all the users"""
users = models.User.objects.filter(
local=True,
is_active=True,
).values_list("id", flat=True)
for user in users:
rerank_suggestions_task.delay(user)
class Command(BaseCommand):
"""start all over with user suggestions"""
help = "Populate suggested users for all users"
# pylint: disable=no-self-use,unused-argument
def handle(self, *args, **options):
"""run builder"""
populate_suggestions()

View file

@ -30,7 +30,7 @@ class Favorite(ActivityMixin, BookWyrmModel):
def save(self, *args, **kwargs):
"""update user active time"""
self.user.last_active_date = timezone.now()
self.user.save(broadcast=False)
self.user.save(broadcast=False, update_fields=["last_active_date"])
super().save(*args, **kwargs)
if self.status.user.local and self.status.user != self.user:

View file

@ -67,7 +67,7 @@ class ActivitypubFieldMixin:
super().__init__(*args, **kwargs)
def set_field_from_activity(self, instance, data):
"""helper function for assinging a value to the field"""
"""helper function for assinging a value to the field. Returns if changed"""
try:
value = getattr(data, self.get_activitypub_field())
except AttributeError:
@ -77,8 +77,14 @@ class ActivitypubFieldMixin:
value = getattr(data, "actor")
formatted = self.field_from_activity(value)
if formatted is None or formatted is MISSING or formatted == {}:
return
return False
# the field is unchanged
if hasattr(instance, self.name) and getattr(instance, self.name) == formatted:
return False
setattr(instance, self.name, formatted)
return True
def set_activity_from_field(self, activity, instance):
"""update the json object"""
@ -205,6 +211,7 @@ class PrivacyField(ActivitypubFieldMixin, models.CharField):
# pylint: disable=invalid-name
def set_field_from_activity(self, instance, data):
original = getattr(instance, self.name)
to = data.to
cc = data.cc
if to == [self.public]:
@ -215,6 +222,7 @@ class PrivacyField(ActivitypubFieldMixin, models.CharField):
setattr(instance, self.name, "unlisted")
else:
setattr(instance, self.name, "followers")
return original == getattr(instance, self.name)
def set_activity_from_field(self, activity, instance):
# explicitly to anyone mentioned (statuses only)
@ -270,9 +278,10 @@ class ManyToManyField(ActivitypubFieldMixin, models.ManyToManyField):
value = getattr(data, self.get_activitypub_field())
formatted = self.field_from_activity(value)
if formatted is None or formatted is MISSING:
return
return False
getattr(instance, self.name).set(formatted)
instance.save(broadcast=False)
return True
def field_to_activity(self, value):
if self.link_only:
@ -373,8 +382,10 @@ class ImageField(ActivitypubFieldMixin, models.ImageField):
value = getattr(data, self.get_activitypub_field())
formatted = self.field_from_activity(value)
if formatted is None or formatted is MISSING:
return
return False
getattr(instance, self.name).save(*formatted, save=save)
return True
def set_activity_from_field(self, activity, instance):
value = getattr(instance, self.name)

View file

@ -30,7 +30,7 @@ class ReadThrough(BookWyrmModel):
def save(self, *args, **kwargs):
"""update user active time"""
self.user.last_active_date = timezone.now()
self.user.save(broadcast=False)
self.user.save(broadcast=False, update_fields=["last_active_date"])
super().save(*args, **kwargs)
def create_update(self):
@ -55,5 +55,5 @@ class ProgressUpdate(BookWyrmModel):
def save(self, *args, **kwargs):
"""update user active time"""
self.user.last_active_date = timezone.now()
self.user.save(broadcast=False)
self.user.save(broadcast=False, update_fields=["last_active_date"])
super().save(*args, **kwargs)

View file

@ -243,7 +243,6 @@ class User(OrderedCollectionPageMixin, AbstractUser):
# generate a username that uses the domain (webfinger format)
actor_parts = urlparse(self.remote_id)
self.username = "%s@%s" % (self.username, actor_parts.netloc)
super().save(*args, **kwargs)
# this user already exists, no need to populate fields
if not created:
@ -276,7 +275,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
self.key_pair = KeyPair.objects.create(
remote_id="%s/#main-key" % self.remote_id
)
self.save(broadcast=False)
self.save(broadcast=False, update_fields=["key_pair"])
shelves = [
{
@ -406,7 +405,7 @@ def set_remote_server(user_id):
user = User.objects.get(id=user_id)
actor_parts = urlparse(user.remote_id)
user.federated_server = get_or_create_remote_server(actor_parts.netloc)
user.save(broadcast=False)
user.save(broadcast=False, update_fields=["federated_server"])
if user.bookwyrm_user and user.outbox:
get_remote_reviews.delay(user.outbox)

View file

@ -338,9 +338,9 @@ def save_and_cleanup(image, instance=None):
save_without_broadcast = isinstance(instance, (models.Book, models.User))
if save_without_broadcast:
instance.save(broadcast=False)
instance.save(broadcast=False, update_fields=["preview_image"])
else:
instance.save()
instance.save(update_fields=["preview_image"])
# Clean up old file after saving
if old_path and default_storage.exists(old_path):

View file

@ -50,15 +50,15 @@ class RedisStore(ABC):
pipeline.execute()
def bulk_remove_objects_from_store(self, objs, store):
"""remoev a list of objects from a given store"""
"""remove a list of objects from a given store"""
pipeline = r.pipeline()
for obj in objs[: self.max_length]:
pipeline.zrem(store, -1, obj.id)
pipeline.execute()
def get_store(self, store): # pylint: disable=no-self-use
def get_store(self, store, **kwargs): # pylint: disable=no-self-use
"""load the values in a store"""
return r.zrevrange(store, 0, -1)
return r.zrevrange(store, 0, -1, **kwargs)
def populate_store(self, store):
"""go from zero to a store"""

221
bookwyrm/suggested_users.py Normal file
View file

@ -0,0 +1,221 @@
""" store recommended follows in redis """
import math
import logging
from django.dispatch import receiver
from django.db.models import signals, Count, Q
from bookwyrm import models
from bookwyrm.redis_store import RedisStore, r
from bookwyrm.tasks import app
logger = logging.getLogger(__name__)
class SuggestedUsers(RedisStore):
"""suggested users for a user"""
max_length = 30
def get_rank(self, obj):
"""get computed rank"""
return obj.mutuals + (1.0 - (1.0 / (obj.shared_books + 1)))
def store_id(self, user): # pylint: disable=no-self-use
"""the key used to store this user's recs"""
if isinstance(user, int):
return "{:d}-suggestions".format(user)
return "{:d}-suggestions".format(user.id)
def get_counts_from_rank(self, rank): # pylint: disable=no-self-use
"""calculate mutuals count and shared books count from rank"""
return {
"mutuals": math.floor(rank),
"shared_books": int(1 / (-1 * (rank % 1 - 1))) - 1,
}
def get_objects_for_store(self, store):
"""a list of potential follows for a user"""
user = models.User.objects.get(id=store.split("-")[0])
return get_annotated_users(
user,
~Q(id=user.id),
~Q(followers=user),
~Q(follower_requests=user),
bookwyrm_user=True,
)
def get_stores_for_object(self, obj):
return [self.store_id(u) for u in self.get_users_for_object(obj)]
def get_users_for_object(self, obj): # pylint: disable=no-self-use
"""given a user, who might want to follow them"""
return models.User.objects.filter(local=True,).exclude(
Q(id=obj.id) | Q(followers=obj) | Q(id__in=obj.blocks.all()) | Q(blocks=obj)
)
def rerank_obj(self, obj, update_only=True):
"""update all the instances of this user with new ranks"""
pipeline = r.pipeline()
for store_user in self.get_users_for_object(obj):
annotated_user = get_annotated_users(
store_user,
id=obj.id,
).first()
if not annotated_user:
continue
pipeline.zadd(
self.store_id(store_user),
self.get_value(annotated_user),
xx=update_only,
)
pipeline.execute()
def rerank_user_suggestions(self, user):
"""update the ranks of the follows suggested to a user"""
self.populate_store(self.store_id(user))
def remove_suggestion(self, user, suggested_user):
"""take a user out of someone's suggestions"""
self.bulk_remove_objects_from_store([suggested_user], self.store_id(user))
def get_suggestions(self, user):
"""get suggestions"""
values = self.get_store(self.store_id(user), withscores=True)
results = []
# annotate users with mutuals and shared book counts
for user_id, rank in values[:5]:
counts = self.get_counts_from_rank(rank)
try:
user = models.User.objects.get(id=user_id)
except models.User.DoesNotExist as err:
# if this happens, the suggestions are janked way up
logger.exception(err)
continue
user.mutuals = counts["mutuals"]
user.shared_books = counts["shared_books"]
results.append(user)
return results
def get_annotated_users(viewer, *args, **kwargs):
"""Users, annotated with things they have in common"""
return (
models.User.objects.filter(discoverable=True, is_active=True, *args, **kwargs)
.exclude(Q(id__in=viewer.blocks.all()) | Q(blocks=viewer))
.annotate(
mutuals=Count(
"followers",
filter=Q(
~Q(id=viewer.id),
~Q(id__in=viewer.following.all()),
followers__in=viewer.following.all(),
),
distinct=True,
),
shared_books=Count(
"shelfbook",
filter=Q(
~Q(id=viewer.id),
shelfbook__book__parent_work__in=[
s.book.parent_work for s in viewer.shelfbook_set.all()
],
),
distinct=True,
),
)
)
suggested_users = SuggestedUsers()
@receiver(signals.post_save, sender=models.UserFollows)
# pylint: disable=unused-argument
def update_suggestions_on_follow(sender, instance, created, *args, **kwargs):
"""remove a follow from the recs and update the ranks"""
if not created or not instance.user_object.discoverable:
return
if instance.user_subject.local:
remove_suggestion_task.delay(instance.user_subject.id, instance.user_object.id)
rerank_user_task.delay(instance.user_object.id, update_only=False)
@receiver(signals.post_save, sender=models.UserBlocks)
# pylint: disable=unused-argument
def update_suggestions_on_block(sender, instance, *args, **kwargs):
"""remove blocked users from recs"""
if instance.user_subject.local and instance.user_object.discoverable:
remove_suggestion_task.delay(instance.user_subject.id, instance.user_object.id)
if instance.user_object.local and instance.user_subject.discoverable:
remove_suggestion_task.delay(instance.user_object.id, instance.user_subject.id)
@receiver(signals.post_delete, sender=models.UserFollows)
# pylint: disable=unused-argument
def update_suggestions_on_unfollow(sender, instance, **kwargs):
"""update rankings, but don't re-suggest because it was probably intentional"""
if instance.user_object.discoverable:
rerank_user_task.delay(instance.user_object.id, update_only=False)
@receiver(signals.post_save, sender=models.ShelfBook)
@receiver(signals.post_delete, sender=models.ShelfBook)
# pylint: disable=unused-argument
def update_rank_on_shelving(sender, instance, *args, **kwargs):
"""when a user shelves or unshelves a book, re-compute their rank"""
# if it's a local user, re-calculate who is rec'ed to them
if instance.user.local:
rerank_suggestions_task.delay(instance.user.id)
# if the user is discoverable, update their rankings
if instance.user.discoverable:
rerank_user_task.delay(instance.user.id)
@receiver(signals.post_save, sender=models.User)
# pylint: disable=unused-argument, too-many-arguments
def add_new_user(sender, instance, created, update_fields=None, **kwargs):
"""a new user, wow how cool"""
# a new user is found, create suggestions for them
if created and instance.local:
rerank_suggestions_task.delay(instance.id)
if update_fields and not "discoverable" in update_fields:
return
# this happens on every save, not just when discoverability changes, annoyingly
if instance.discoverable:
rerank_user_task.delay(instance.id, update_only=False)
elif not created:
remove_user_task.delay(instance.id)
@app.task
def rerank_suggestions_task(user_id):
"""do the hard work in celery"""
suggested_users.rerank_user_suggestions(user_id)
@app.task
def rerank_user_task(user_id, update_only=False):
"""do the hard work in celery"""
user = models.User.objects.get(id=user_id)
suggested_users.rerank_obj(user, update_only=update_only)
@app.task
def remove_user_task(user_id):
"""do the hard work in celery"""
user = models.User.objects.get(id=user_id)
suggested_users.remove_object_from_related_stores(user)
@app.task
def remove_suggestion_task(user_id, suggested_user_id):
"""remove a specific user from a specific user's suggestions"""
suggested_user = models.User.objects.get(id=suggested_user_id)
suggested_users.remove_suggestion(user_id, suggested_user)

View file

@ -44,18 +44,22 @@
{# activity feed #}
{% if not activities %}
<p>{% trans "There aren't any activities right now! Try following a user to get started" %}</p>
<div class="block content">
<p>{% trans "There aren't any activities right now! Try following a user to get started" %}</p>
{% if suggested_users %}
{# suggested users for when things are very lonely #}
{% include 'feed/suggested_users.html' with suggested_users=suggested_users %}
</div>
{% endif %}
{% endif %}
{% for activity in activities %}
{% if not activities.number > 1 and forloop.counter0 == 2 and suggested_users %}
{# suggested users on the first page, two statuses down #}
<section class="block">
<h2 class="title is-5">{% trans "Who to follow" %}</h2>
{% include 'feed/suggested_users.html' with suggested_users=suggested_users %}
<a class="help" href="{% url 'directory' %}">View directory <span class="icon icon-arrow-right"></a>
</section>
{% include 'feed/suggested_users.html' with suggested_users=suggested_users %}
{% endif %}
<div class="block">
{% include 'snippets/status/status.html' with status=activity %}

View file

@ -1,25 +1,6 @@
{% load i18n %}
{% load utilities %}
{% load humanize %}
<div class="columns is-mobile scroll-x mb-0">
{% for user in suggested_users %}
<div class="column is-flex is-flex-grow-0">
<div class="box has-text-centered is-shadowless has-background-white-bis m-0">
<a href="{{ user.local_path }}" class="has-text-black">
{% include 'snippets/avatar.html' with user=user large=True %}
<span title="{{ user.display_name }}" class="is-block is-6 has-text-weight-bold">{{ user.display_name|truncatechars:10 }}</span>
<span title="@{{ user|username }}" class="is-block pb-3">@{{ user|username|truncatechars:8 }}</span>
</a>
{% include 'snippets/follow_button.html' with user=user minimal=True %}
{% if user.mutuals %}
<p class="help">
{% blocktrans with mutuals=user.mutuals|intcomma count counter=user.mutuals %}{{ mutuals }} follower you follow{% plural %}{{ mutuals }} followers you follow{% endblocktrans %}
</p>
{% elif user.shared_books %}
<p class="help">{% blocktrans with shared_books=user.shared_books|intcomma count counter=user.shared_books %}{{ shared_books }} book on your shelves{% plural %}{{ shared_books }} books on your shelves{% endblocktrans %}</p>
{% endif %}
</div>
</div>
{% endfor %}
</div>
<section class="block">
<h2 class="title is-5">{% trans "Who to follow" %}</h2>
{% include 'snippets/suggested_users.html' with suggested_users=suggested_users %}
<a class="help" href="{% url 'directory' %}">View directory <span class="icon icon-arrow-right"></a>
</section>

View file

@ -9,7 +9,7 @@
<form class="field has-addons" method="get" action="{% url 'get-started-users' %}">
<div class="control">
<input type="text" name="query" value="{{ request.GET.query }}" class="input" placeholder="{% trans 'Search for a user' %}" aria-label="{% trans 'Search for a user' %}">
{% if request.GET.query and not user_results %}
{% if request.GET.query and no_results %}
<p class="help">{% blocktrans with query=request.GET.query %}No users found for "{{ query }}"{% endblocktrans %}</p>
{% endif %}
</div>
@ -22,7 +22,7 @@
</div>
</form>
{% include 'feed/suggested_users.html' with suggested_users=suggested_users %}
{% include 'snippets/suggested_users.html' with suggested_users=suggested_users %}
</div>
{% endblock %}

View file

@ -0,0 +1,25 @@
{% load i18n %}
{% load utilities %}
{% load humanize %}
<div class="columns is-mobile scroll-x mb-0">
{% for user in suggested_users %}
<div class="column is-flex is-flex-grow-0">
<div class="box has-text-centered is-shadowless has-background-white-bis m-0">
<a href="{{ user.local_path }}" class="has-text-black">
{% include 'snippets/avatar.html' with user=user large=True %}
<span title="{{ user.display_name }}" class="is-block is-6 has-text-weight-bold">{{ user.display_name|truncatechars:10 }}</span>
<span title="@{{ user|username }}" class="is-block pb-3">@{{ user|username|truncatechars:8 }}</span>
</a>
{% include 'snippets/follow_button.html' with user=user minimal=True %}
{% if user.mutuals %}
<p class="help">
{% blocktrans with mutuals=user.mutuals|intcomma count counter=user.mutuals %}{{ mutuals }} follower you follow{% plural %}{{ mutuals }} followers you follow{% endblocktrans %}
</p>
{% elif user.shared_books %}
<p class="help">{% blocktrans with shared_books=user.shared_books|intcomma count counter=user.shared_books %}{{ shared_books }} book on your shelves{% plural %}{{ shared_books }} books on your shelves{% endblocktrans %}</p>
{% endif %}
</div>
</div>
{% endfor %}
</div>

View file

@ -12,3 +12,4 @@
{% blocktrans with username=user.display_name %}{{ username }} isn't following any users{% endblocktrans %}
</div>
{% endblock %}

View file

@ -1,6 +1,4 @@
import datetime
from unittest.mock import patch
"""test author serializer"""
from django.test import TestCase
from bookwyrm import models

View file

@ -20,16 +20,20 @@ from bookwyrm import models
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
@patch("bookwyrm.suggested_users.rerank_user_task.delay")
@patch("bookwyrm.suggested_users.remove_user_task.delay")
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
class BaseActivity(TestCase):
"""the super class for model-linked activitypub dataclasses"""
def setUp(self):
"""we're probably going to re-use this so why copy/paste"""
self.user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
)
self.user.remote_id = "http://example.com/a/b"
self.user.save(broadcast=False)
self.user.save(broadcast=False, update_fields=["remote_id"])
datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json")
self.userdata = json.loads(datafile.read_bytes())
@ -44,24 +48,24 @@ class BaseActivity(TestCase):
image.save(output, format=image.format)
self.image_data = output.getvalue()
def test_init(self, _):
def test_init(self, *_):
"""simple successfuly init"""
instance = ActivityObject(id="a", type="b")
self.assertTrue(hasattr(instance, "id"))
self.assertTrue(hasattr(instance, "type"))
def test_init_missing(self, _):
def test_init_missing(self, *_):
"""init with missing required params"""
with self.assertRaises(ActivitySerializerError):
ActivityObject()
def test_init_extra_fields(self, _):
def test_init_extra_fields(self, *_):
"""init ignoring additional fields"""
instance = ActivityObject(id="a", type="b", fish="c")
self.assertTrue(hasattr(instance, "id"))
self.assertTrue(hasattr(instance, "type"))
def test_init_default_field(self, _):
def test_init_default_field(self, *_):
"""replace an existing required field with a default field"""
@dataclass(init=False)
@ -74,7 +78,7 @@ class BaseActivity(TestCase):
self.assertEqual(instance.id, "a")
self.assertEqual(instance.type, "TestObject")
def test_serialize(self, _):
def test_serialize(self, *_):
"""simple function for converting dataclass to dict"""
instance = ActivityObject(id="a", type="b")
serialized = instance.serialize()
@ -83,7 +87,7 @@ class BaseActivity(TestCase):
self.assertEqual(serialized["type"], "b")
@responses.activate
def test_resolve_remote_id(self, _):
def test_resolve_remote_id(self, *_):
"""look up or load remote data"""
# existing item
result = resolve_remote_id("http://example.com/a/b", model=models.User)
@ -105,14 +109,14 @@ class BaseActivity(TestCase):
self.assertEqual(result.remote_id, "https://example.com/user/mouse")
self.assertEqual(result.name, "MOUSE?? MOUSE!!")
def test_to_model_invalid_model(self, _):
def test_to_model_invalid_model(self, *_):
"""catch mismatch between activity type and model type"""
instance = ActivityObject(id="a", type="b")
with self.assertRaises(ActivitySerializerError):
instance.to_model(model=models.User)
@responses.activate
def test_to_model_image(self, _):
def test_to_model_image(self, *_):
"""update an image field"""
activity = activitypub.Person(
id=self.user.remote_id,
@ -145,7 +149,7 @@ class BaseActivity(TestCase):
self.assertEqual(self.user.name, "New Name")
self.assertEqual(self.user.key_pair.public_key, "hi")
def test_to_model_many_to_many(self, _):
def test_to_model_many_to_many(self, *_):
"""annoying that these all need special handling"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
status = models.Status.objects.create(
@ -176,7 +180,7 @@ class BaseActivity(TestCase):
self.assertEqual(status.mention_books.first(), book)
@responses.activate
def test_to_model_one_to_many(self, _):
def test_to_model_one_to_many(self, *_):
"""these are reversed relationships, where the secondary object
keys the primary object but not vice versa"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
@ -215,7 +219,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(

View file

@ -119,10 +119,12 @@ class AbstractConnector(TestCase):
@responses.activate
def test_get_or_create_author(self):
"""load an author"""
self.connector.author_mappings = [
Mapping("id"),
Mapping("name"),
]
self.connector.author_mappings = (
[ # pylint: disable=attribute-defined-outside-init
Mapping("id"),
Mapping("name"),
]
)
responses.add(
responses.GET,

View file

@ -28,7 +28,7 @@
},
"bookwyrmUser": true,
"manuallyApprovesFollowers": false,
"discoverable": true,
"discoverable": false,
"devices": "https://friend.camp/users/tripofmice/collections/devices",
"tag": [],
"icon": {

View file

@ -16,9 +16,12 @@ from bookwyrm.settings import DOMAIN
def make_date(*args):
"""helper function to easily generate a date obj"""
return datetime.datetime(*args, tzinfo=pytz.UTC)
# pylint: disable=consider-using-with
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
class GoodreadsImport(TestCase):
"""importing from goodreads csv"""
@ -27,9 +30,10 @@ class GoodreadsImport(TestCase):
self.importer = GoodreadsImporter()
datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv")
self.csv = open(datafile, "r", encoding=self.importer.encoding)
self.user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "password", local=True
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "password", local=True
)
models.Connector.objects.create(
identifier=DOMAIN,
@ -49,7 +53,7 @@ class GoodreadsImport(TestCase):
parent_work=work,
)
def test_create_job(self):
def test_create_job(self, _):
"""creates the import job entry and checks csv"""
import_job = self.importer.create_job(self.user, self.csv, False, "public")
self.assertEqual(import_job.user, self.user)
@ -65,7 +69,7 @@ class GoodreadsImport(TestCase):
self.assertEqual(import_items[2].index, 2)
self.assertEqual(import_items[2].data["Book Id"], "28694510")
def test_create_retry_job(self):
def test_create_retry_job(self, _):
"""trying again with items that didn't import"""
import_job = self.importer.create_job(self.user, self.csv, False, "unlisted")
import_items = models.ImportItem.objects.filter(job=import_job).all()[:2]
@ -83,7 +87,7 @@ class GoodreadsImport(TestCase):
self.assertEqual(retry_items[1].index, 1)
self.assertEqual(retry_items[1].data["Book Id"], "52691223")
def test_start_import(self):
def test_start_import(self, _):
"""begin loading books"""
import_job = self.importer.create_job(self.user, self.csv, False, "unlisted")
MockTask = namedtuple("Task", ("id"))
@ -95,7 +99,7 @@ class GoodreadsImport(TestCase):
self.assertEqual(import_job.task_id, "7")
@responses.activate
def test_import_data(self):
def test_import_data(self, _):
"""resolve entry"""
import_job = self.importer.create_job(self.user, self.csv, False, "unlisted")
book = models.Edition.objects.create(title="Test Book")
@ -110,7 +114,7 @@ class GoodreadsImport(TestCase):
import_item = models.ImportItem.objects.get(job=import_job, index=0)
self.assertEqual(import_item.book.id, book.id)
def test_handle_imported_book(self):
def test_handle_imported_book(self, _):
"""goodreads import added a book, this adds related connections"""
shelf = self.user.shelf_set.filter(identifier="read").first()
self.assertIsNone(shelf.books.first())
@ -141,7 +145,7 @@ class GoodreadsImport(TestCase):
self.assertEqual(readthrough.start_date, make_date(2020, 10, 21))
self.assertEqual(readthrough.finish_date, make_date(2020, 10, 25))
def test_handle_imported_book_already_shelved(self):
def test_handle_imported_book_already_shelved(self, _):
"""goodreads import added a book, this adds related connections"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
shelf = self.user.shelf_set.filter(identifier="to-read").first()
@ -179,7 +183,7 @@ class GoodreadsImport(TestCase):
self.assertEqual(readthrough.start_date, make_date(2020, 10, 21))
self.assertEqual(readthrough.finish_date, make_date(2020, 10, 25))
def test_handle_import_twice(self):
def test_handle_import_twice(self, _):
"""re-importing books"""
shelf = self.user.shelf_set.filter(identifier="read").first()
import_job = models.ImportJob.objects.create(user=self.user)
@ -212,7 +216,7 @@ class GoodreadsImport(TestCase):
self.assertEqual(readthrough.finish_date, make_date(2020, 10, 25))
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
def test_handle_imported_book_review(self, _):
def test_handle_imported_book_review(self, *_):
"""goodreads review import"""
import_job = models.ImportJob.objects.create(user=self.user)
datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv")
@ -234,7 +238,7 @@ class GoodreadsImport(TestCase):
self.assertEqual(review.privacy, "unlisted")
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
def test_handle_imported_book_rating(self, _):
def test_handle_imported_book_rating(self, *_):
"""goodreads rating import"""
import_job = models.ImportJob.objects.create(user=self.user)
datafile = pathlib.Path(__file__).parent.joinpath(
@ -257,7 +261,7 @@ class GoodreadsImport(TestCase):
self.assertEqual(review.published_date, make_date(2019, 7, 8))
self.assertEqual(review.privacy, "unlisted")
def test_handle_imported_book_reviews_disabled(self):
def test_handle_imported_book_reviews_disabled(self, _):
"""goodreads review import"""
import_job = models.ImportJob.objects.create(user=self.user)
datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv")

View file

@ -19,6 +19,8 @@ def make_date(*args):
return datetime.datetime(*args, tzinfo=pytz.UTC)
# pylint: disable=consider-using-with
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
class LibrarythingImport(TestCase):
"""importing from librarything tsv"""
@ -29,9 +31,10 @@ class LibrarythingImport(TestCase):
# Librarything generates latin encoded exports...
self.csv = open(datafile, "r", encoding=self.importer.encoding)
self.user = models.User.objects.create_user(
"mmai", "mmai@mmai.mmai", "password", local=True
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.user = models.User.objects.create_user(
"mmai", "mmai@mmai.mmai", "password", local=True
)
models.Connector.objects.create(
identifier=DOMAIN,
@ -51,7 +54,7 @@ class LibrarythingImport(TestCase):
parent_work=work,
)
def test_create_job(self):
def test_create_job(self, _):
"""creates the import job entry and checks csv"""
import_job = self.importer.create_job(self.user, self.csv, False, "public")
self.assertEqual(import_job.user, self.user)
@ -67,7 +70,7 @@ class LibrarythingImport(TestCase):
self.assertEqual(import_items[2].index, 2)
self.assertEqual(import_items[2].data["Book Id"], "5015399")
def test_create_retry_job(self):
def test_create_retry_job(self, _):
"""trying again with items that didn't import"""
import_job = self.importer.create_job(self.user, self.csv, False, "unlisted")
import_items = models.ImportItem.objects.filter(job=import_job).all()[:2]
@ -86,7 +89,7 @@ class LibrarythingImport(TestCase):
self.assertEqual(retry_items[1].data["Book Id"], "5015319")
@responses.activate
def test_import_data(self):
def test_import_data(self, _):
"""resolve entry"""
import_job = self.importer.create_job(self.user, self.csv, False, "unlisted")
book = models.Edition.objects.create(title="Test Book")
@ -101,7 +104,7 @@ class LibrarythingImport(TestCase):
import_item = models.ImportItem.objects.get(job=import_job, index=0)
self.assertEqual(import_item.book.id, book.id)
def test_handle_imported_book(self):
def test_handle_imported_book(self, _):
"""librarything import added a book, this adds related connections"""
shelf = self.user.shelf_set.filter(identifier="read").first()
self.assertIsNone(shelf.books.first())
@ -131,7 +134,7 @@ class LibrarythingImport(TestCase):
self.assertEqual(readthrough.start_date, make_date(2007, 4, 16))
self.assertEqual(readthrough.finish_date, make_date(2007, 5, 8))
def test_handle_imported_book_already_shelved(self):
def test_handle_imported_book_already_shelved(self, _):
"""librarything import added a book, this adds related connections"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
shelf = self.user.shelf_set.filter(identifier="to-read").first()
@ -163,7 +166,7 @@ class LibrarythingImport(TestCase):
self.assertEqual(readthrough.start_date, make_date(2007, 4, 16))
self.assertEqual(readthrough.finish_date, make_date(2007, 5, 8))
def test_handle_import_twice(self):
def test_handle_import_twice(self, _):
"""re-importing books"""
shelf = self.user.shelf_set.filter(identifier="read").first()
import_job = models.ImportJob.objects.create(user=self.user)
@ -195,7 +198,7 @@ class LibrarythingImport(TestCase):
self.assertEqual(readthrough.finish_date, make_date(2007, 5, 8))
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
def test_handle_imported_book_review(self, _):
def test_handle_imported_book_review(self, *_):
"""librarything review import"""
import_job = models.ImportJob.objects.create(user=self.user)
datafile = pathlib.Path(__file__).parent.joinpath("../data/librarything.tsv")
@ -216,7 +219,7 @@ class LibrarythingImport(TestCase):
self.assertEqual(review.published_date, make_date(2007, 5, 8))
self.assertEqual(review.privacy, "unlisted")
def test_handle_imported_book_reviews_disabled(self):
def test_handle_imported_book_reviews_disabled(self, _):
"""librarything review import"""
import_job = models.ImportJob.objects.create(user=self.user)
datafile = pathlib.Path(__file__).parent.joinpath("../data/librarything.tsv")

View file

@ -12,16 +12,17 @@ class Activitystreams(TestCase):
def setUp(self):
"""we need some stuff"""
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.suggested_users.rerank_suggestions_task.delay"):
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",

View file

@ -27,11 +27,12 @@ class ActivitypubMixins(TestCase):
def setUp(self):
"""shared data"""
self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse"
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse"
)
self.local_user.remote_id = "http://example.com/a/b"
self.local_user.save(broadcast=False)
self.local_user.save(broadcast=False, update_fields=["remote_id"])
with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user(
"rat",
@ -189,7 +190,7 @@ class ActivitypubMixins(TestCase):
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)
self.remote_user.save(broadcast=False, update_fields=["shared_inbox"])
with patch("bookwyrm.models.user.set_remote_server.delay"):
another_remote_user = models.User.objects.create_user(
"nutria",

View file

@ -13,9 +13,10 @@ class BaseModel(TestCase):
def setUp(self):
"""shared data"""
self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse"
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse"
)
with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user(
"rat",

View file

@ -24,10 +24,11 @@ from bookwyrm.models.base_model import BookWyrmModel
from bookwyrm.models.activitypub_mixin import ActivitypubMixin
# pylint: disable=too-many-public-methods
class ActivitypubFields(TestCase):
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
class ModelFields(TestCase):
"""overwrites standard model feilds to work with activitypub"""
def test_validate_remote_id(self):
def test_validate_remote_id(self, _):
"""should look like a url"""
self.assertIsNone(fields.validate_remote_id("http://www.example.com"))
self.assertIsNone(fields.validate_remote_id("https://www.example.com"))
@ -44,7 +45,7 @@ class ActivitypubFields(TestCase):
"http://www.example.com/dlfjg 23/x",
)
def test_activitypub_field_mixin(self):
def test_activitypub_field_mixin(self, _):
"""generic mixin with super basic to and from functionality"""
instance = fields.ActivitypubFieldMixin()
self.assertEqual(instance.field_to_activity("fish"), "fish")
@ -62,7 +63,7 @@ class ActivitypubFields(TestCase):
instance.name = "snake_case_name"
self.assertEqual(instance.get_activitypub_field(), "snakeCaseName")
def test_set_field_from_activity(self):
def test_set_field_from_activity(self, _):
"""setter from entire json blob"""
@dataclass
@ -81,7 +82,7 @@ class ActivitypubFields(TestCase):
instance.set_field_from_activity(mock_model, data)
self.assertEqual(mock_model.field_name, "hi")
def test_set_activity_from_field(self):
def test_set_activity_from_field(self, _):
"""set json field given entire model"""
@dataclass
@ -99,7 +100,7 @@ class ActivitypubFields(TestCase):
instance.set_activity_from_field(data, mock_model)
self.assertEqual(data["fieldName"], "bip")
def test_remote_id_field(self):
def test_remote_id_field(self, _):
"""just sets some defaults on charfield"""
instance = fields.RemoteIdField()
self.assertEqual(instance.max_length, 255)
@ -108,7 +109,7 @@ class ActivitypubFields(TestCase):
with self.assertRaises(ValidationError):
instance.run_validators("http://www.example.com/dlfjg 23/x")
def test_username_field(self):
def test_username_field(self, _):
"""again, just setting defaults on username field"""
instance = fields.UsernameField()
self.assertEqual(instance.activitypub_field, "preferredUsername")
@ -129,7 +130,7 @@ class ActivitypubFields(TestCase):
self.assertEqual(instance.field_to_activity("test@example.com"), "test")
def test_privacy_field_defaults(self):
def test_privacy_field_defaults(self, _):
"""post privacy field's many default values"""
instance = fields.PrivacyField()
self.assertEqual(instance.max_length, 255)
@ -142,7 +143,7 @@ class ActivitypubFields(TestCase):
instance.public, "https://www.w3.org/ns/activitystreams#Public"
)
def test_privacy_field_set_field_from_activity(self):
def test_privacy_field_set_field_from_activity(self, _):
"""translate between to/cc fields and privacy"""
@dataclass(init=False)
@ -230,7 +231,7 @@ class ActivitypubFields(TestCase):
self.assertEqual(activity["to"], [user.remote_id])
self.assertEqual(activity["cc"], [])
def test_foreign_key(self):
def test_foreign_key(self, _):
"""should be able to format a related model"""
instance = fields.ForeignKey("User", on_delete=models.CASCADE)
Serializable = namedtuple("Serializable", ("to_activity", "remote_id"))
@ -239,7 +240,7 @@ class ActivitypubFields(TestCase):
self.assertEqual(instance.field_to_activity(item), "https://e.b/c")
@responses.activate
def test_foreign_key_from_activity_str(self):
def test_foreign_key_from_activity_str(self, _):
"""create a new object from a foreign key"""
instance = fields.ForeignKey(User, on_delete=models.CASCADE)
datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json")
@ -266,7 +267,7 @@ class ActivitypubFields(TestCase):
self.assertEqual(value.remote_id, "https://example.com/user/mouse")
self.assertEqual(value.name, "MOUSE?? MOUSE!!")
def test_foreign_key_from_activity_dict(self):
def test_foreign_key_from_activity_dict(self, *_):
"""test recieving activity json"""
instance = fields.ForeignKey(User, on_delete=models.CASCADE)
datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json")
@ -286,7 +287,7 @@ class ActivitypubFields(TestCase):
self.assertEqual(value.name, "MOUSE?? MOUSE!!")
# et cetera but we're not testing serializing user json
def test_foreign_key_from_activity_dict_existing(self):
def test_foreign_key_from_activity_dict_existing(self, _):
"""test receiving a dict of an existing object in the db"""
instance = fields.ForeignKey(User, on_delete=models.CASCADE)
datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json")
@ -295,7 +296,7 @@ class ActivitypubFields(TestCase):
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
)
user.remote_id = "https://example.com/user/mouse"
user.save(broadcast=False)
user.save(broadcast=False, update_fields=["remote_id"])
User.objects.create_user(
"rat", "rat@rat.rat", "ratword", local=True, localname="rat"
@ -305,7 +306,7 @@ class ActivitypubFields(TestCase):
value = instance.field_from_activity(activitypub.Person(**userdata))
self.assertEqual(value, user)
def test_foreign_key_from_activity_str_existing(self):
def test_foreign_key_from_activity_str_existing(self, _):
"""test receiving a remote id of an existing object in the db"""
instance = fields.ForeignKey(User, on_delete=models.CASCADE)
user = User.objects.create_user(
@ -318,14 +319,14 @@ class ActivitypubFields(TestCase):
value = instance.field_from_activity(user.remote_id)
self.assertEqual(value, user)
def test_one_to_one_field(self):
def test_one_to_one_field(self, _):
"""a gussied up foreign key"""
instance = fields.OneToOneField("User", on_delete=models.CASCADE)
Serializable = namedtuple("Serializable", ("to_activity", "remote_id"))
item = Serializable(lambda: {"a": "b"}, "https://e.b/c")
self.assertEqual(instance.field_to_activity(item), {"a": "b"})
def test_many_to_many_field(self):
def test_many_to_many_field(self, _):
"""lists!"""
instance = fields.ManyToManyField("User")
@ -343,7 +344,7 @@ class ActivitypubFields(TestCase):
self.assertEqual(instance.field_to_activity(items), "example.com/snake_case")
@responses.activate
def test_many_to_many_field_from_activity(self):
def test_many_to_many_field_from_activity(self, _):
"""resolve related fields for a list, takes a list of remote ids"""
instance = fields.ManyToManyField(User)
datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json")
@ -363,7 +364,7 @@ class ActivitypubFields(TestCase):
self.assertEqual(len(value), 1)
self.assertIsInstance(value[0], User)
def test_tag_field(self):
def test_tag_field(self, _):
"""a special type of many to many field"""
instance = fields.TagField("User")
@ -382,13 +383,14 @@ class ActivitypubFields(TestCase):
self.assertEqual(result[0].name, "Name")
self.assertEqual(result[0].type, "Serializable")
def test_tag_field_from_activity(self):
def test_tag_field_from_activity(self, _):
"""loadin' a list of items from Links"""
# TODO
@responses.activate
@patch("bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast")
def test_image_field(self, _):
@patch("bookwyrm.suggested_users.remove_user_task.delay")
def test_image_field(self, *_):
"""storing images"""
user = User.objects.create_user(
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
@ -426,7 +428,7 @@ class ActivitypubFields(TestCase):
self.assertIsInstance(loaded_image, list)
self.assertIsInstance(loaded_image[1], ContentFile)
def test_image_serialize(self):
def test_image_serialize(self, _):
"""make sure we're creating sensible image paths"""
ValueMock = namedtuple("ValueMock", ("url"))
value_mock = ValueMock("/images/fish.jpg")
@ -435,7 +437,7 @@ class ActivitypubFields(TestCase):
self.assertEqual(result.url, "https://your.domain.here/images/fish.jpg")
self.assertEqual(result.name, "hello")
def test_datetime_field(self):
def test_datetime_field(self, _):
"""this one is pretty simple, it just has to use isoformat"""
instance = fields.DateTimeField()
now = timezone.now()
@ -443,12 +445,12 @@ class ActivitypubFields(TestCase):
self.assertEqual(instance.field_from_activity(now.isoformat()), now)
self.assertEqual(instance.field_from_activity("bip"), None)
def test_array_field(self):
def test_array_field(self, _):
"""idk why it makes them strings but probably for a good reason"""
instance = fields.ArrayField(fields.IntegerField)
self.assertEqual(instance.field_to_activity([0, 1]), ["0", "1"])
def test_html_field(self):
def test_html_field(self, _):
"""sanitizes html, the sanitizer has its own tests"""
instance = fields.HtmlField()
self.assertEqual(

View file

@ -59,9 +59,10 @@ class ImportJob(TestCase):
unknown_read_data["Exclusive Shelf"] = "read"
unknown_read_data["Date Read"] = ""
user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
)
job = models.ImportJob.objects.create(user=user)
self.item_1 = models.ImportItem.objects.create(
job=job, index=1, data=currently_reading_data

View file

@ -11,9 +11,10 @@ class List(TestCase):
def setUp(self):
"""look, a list"""
self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
)
work = models.Work.objects.create(title="hello")
self.book = models.Edition.objects.create(title="hi", parent_work=work)

View file

@ -1,4 +1,5 @@
""" testing models """
from unittest.mock import patch
from django.test import TestCase
from django.core.exceptions import ValidationError
@ -10,9 +11,10 @@ class ReadThrough(TestCase):
def setUp(self):
"""look, a shelf"""
self.user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
)
self.work = models.Work.objects.create(title="Example Work")
self.edition = models.Edition.objects.create(

View file

@ -1,4 +1,5 @@
""" testing models """
import json
from unittest.mock import patch
from django.test import TestCase
@ -20,25 +21,21 @@ class Relationship(TestCase):
inbox="https://example.com/users/rat/inbox",
outbox="https://example.com/users/rat/outbox",
)
self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse"
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse"
)
self.local_user.remote_id = "http://local.com/user/mouse"
self.local_user.save(broadcast=False)
self.local_user.save(broadcast=False, update_fields=["remote_id"])
def test_user_follows_from_request(self):
"""convert a follow request into a follow"""
real_broadcast = models.UserFollowRequest.broadcast
def mock_broadcast(_, activity, user):
"""introspect what's being sent out"""
self.assertEqual(user.remote_id, self.local_user.remote_id)
self.assertEqual(activity["type"], "Follow")
models.UserFollowRequest.broadcast = mock_broadcast
request = models.UserFollowRequest.objects.create(
user_subject=self.local_user, user_object=self.remote_user
)
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
request = models.UserFollowRequest.objects.create(
user_subject=self.local_user, user_object=self.remote_user
)
activity = json.loads(mock.call_args[0][1])
self.assertEqual(activity["type"], "Follow")
self.assertEqual(
request.remote_id, "http://local.com/user/mouse#follows/%d" % request.id
)
@ -51,7 +48,6 @@ class Relationship(TestCase):
self.assertEqual(rel.status, "follows")
self.assertEqual(rel.user_subject, self.local_user)
self.assertEqual(rel.user_object, self.remote_user)
models.UserFollowRequest.broadcast = real_broadcast
def test_user_follows_from_request_custom_remote_id(self):
"""store a specific remote id for a relationship provided by remote"""
@ -70,36 +66,26 @@ class Relationship(TestCase):
self.assertEqual(rel.user_subject, self.local_user)
self.assertEqual(rel.user_object, self.remote_user)
def test_follow_request_activity(self):
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
def test_follow_request_activity(self, broadcast_mock):
"""accept a request and make it a relationship"""
real_broadcast = models.UserFollowRequest.broadcast
def mock_broadcast(_, activity, user):
self.assertEqual(user.remote_id, self.local_user.remote_id)
self.assertEqual(activity["actor"], self.local_user.remote_id)
self.assertEqual(activity["object"], self.remote_user.remote_id)
self.assertEqual(activity["type"], "Follow")
models.UserFollowRequest.broadcast = mock_broadcast
models.UserFollowRequest.objects.create(
user_subject=self.local_user,
user_object=self.remote_user,
)
models.UserFollowRequest.broadcast = real_broadcast
activity = json.loads(broadcast_mock.call_args[0][1])
self.assertEqual(activity["actor"], self.local_user.remote_id)
self.assertEqual(activity["object"], self.remote_user.remote_id)
self.assertEqual(activity["type"], "Follow")
def test_follow_request_accept(self):
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
def test_follow_request_accept(self, broadcast_mock):
"""accept a request and make it a relationship"""
real_broadcast = models.UserFollowRequest.broadcast
def mock_broadcast(_, activity, user):
self.assertEqual(user.remote_id, self.local_user.remote_id)
self.assertEqual(activity["type"], "Accept")
self.assertEqual(activity["actor"], self.local_user.remote_id)
self.assertEqual(activity["object"]["id"], "https://www.hi.com/")
self.local_user.manually_approves_followers = True
self.local_user.save(broadcast=False)
models.UserFollowRequest.broadcast = mock_broadcast
self.local_user.save(
broadcast=False, update_fields=["manually_approves_followers"]
)
request = models.UserFollowRequest.objects.create(
user_subject=self.remote_user,
user_object=self.local_user,
@ -107,32 +93,34 @@ class Relationship(TestCase):
)
request.accept()
activity = json.loads(broadcast_mock.call_args[0][1])
self.assertEqual(activity["type"], "Accept")
self.assertEqual(activity["actor"], self.local_user.remote_id)
self.assertEqual(activity["object"]["id"], "https://www.hi.com/")
self.assertFalse(models.UserFollowRequest.objects.exists())
self.assertTrue(models.UserFollows.objects.exists())
rel = models.UserFollows.objects.get()
self.assertEqual(rel.user_subject, self.remote_user)
self.assertEqual(rel.user_object, self.local_user)
models.UserFollowRequest.broadcast = real_broadcast
def test_follow_request_reject(self):
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
def test_follow_request_reject(self, broadcast_mock):
"""accept a request and make it a relationship"""
real_broadcast = models.UserFollowRequest.broadcast
def mock_reject(_, activity, user):
self.assertEqual(user.remote_id, self.local_user.remote_id)
self.assertEqual(activity["type"], "Reject")
self.assertEqual(activity["actor"], self.local_user.remote_id)
self.assertEqual(activity["object"]["id"], request.remote_id)
models.UserFollowRequest.broadcast = mock_reject
self.local_user.manually_approves_followers = True
self.local_user.save(broadcast=False)
self.local_user.save(
broadcast=False, update_fields=["manually_approves_followers"]
)
request = models.UserFollowRequest.objects.create(
user_subject=self.remote_user,
user_object=self.local_user,
)
request.reject()
activity = json.loads(broadcast_mock.call_args[0][1])
self.assertEqual(activity["type"], "Reject")
self.assertEqual(activity["actor"], self.local_user.remote_id)
self.assertEqual(activity["object"]["id"], request.remote_id)
self.assertFalse(models.UserFollowRequest.objects.exists())
self.assertFalse(models.UserFollows.objects.exists())
models.UserFollowRequest.broadcast = real_broadcast

View file

@ -7,18 +7,20 @@ from bookwyrm import models, settings
# pylint: disable=unused-argument
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
class Shelf(TestCase):
"""some activitypub oddness ahead"""
def setUp(self):
"""look, a shelf"""
self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
)
work = models.Work.objects.create(title="Test Work")
self.book = models.Edition.objects.create(title="test book", parent_work=work)
def test_remote_id(self):
def test_remote_id(self, _):
"""shelves use custom remote ids"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
shelf = models.Shelf.objects.create(
@ -27,7 +29,7 @@ class Shelf(TestCase):
expected_id = "https://%s/user/mouse/books/test-shelf" % settings.DOMAIN
self.assertEqual(shelf.get_remote_id(), expected_id)
def test_to_activity(self):
def test_to_activity(self, _):
"""jsonify it"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
shelf = models.Shelf.objects.create(
@ -41,7 +43,7 @@ class Shelf(TestCase):
self.assertEqual(activity_json["name"], "Test Shelf")
self.assertEqual(activity_json["owner"], self.local_user.remote_id)
def test_create_update_shelf(self):
def test_create_update_shelf(self, _):
"""create and broadcast shelf creation"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
@ -62,7 +64,7 @@ class Shelf(TestCase):
self.assertEqual(activity["object"]["name"], "arthur russel")
self.assertEqual(shelf.name, "arthur russel")
def test_shelve(self):
def test_shelve(self, _):
"""create and broadcast shelf creation"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
shelf = models.Shelf.objects.create(

View file

@ -22,9 +22,10 @@ class Status(TestCase):
def setUp(self):
"""useful things for creating a status"""
self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
)
with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user(
"rat",

View file

@ -11,15 +11,16 @@ from bookwyrm.settings import DOMAIN
# pylint: disable=missing-function-docstring
class User(TestCase):
def setUp(self):
self.user = models.User.objects.create_user(
"mouse@%s" % DOMAIN,
"mouse@mouse.mouse",
"mouseword",
local=True,
localname="mouse",
name="hi",
bookwyrm_user=False,
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.user = models.User.objects.create_user(
"mouse@%s" % DOMAIN,
"mouse@mouse.mouse",
"mouseword",
local=True,
localname="mouse",
name="hi",
bookwyrm_user=False,
)
def test_computed_fields(self):
"""username instead of id here"""
@ -154,7 +155,8 @@ class User(TestCase):
self.assertIsNone(server.application_type)
self.assertIsNone(server.application_version)
def test_delete_user(self):
@patch("bookwyrm.suggested_users.remove_user_task.delay")
def test_delete_user(self, _):
"""deactivate a user"""
self.assertTrue(self.user.is_active)
with patch(

View file

@ -11,16 +11,17 @@ class Activitystreams(TestCase):
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.suggested_users.rerank_suggestions_task.delay"):
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",

View file

@ -14,13 +14,14 @@ class Emailing(TestCase):
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
models.SiteSettings.objects.create()
def test_invite_email(self, email_mock):

View file

@ -1,5 +1,6 @@
""" test generating preview images """
import pathlib
from unittest.mock import patch
from PIL import Image
from django.test import TestCase
@ -8,7 +9,6 @@ from django.core.files.uploadedfile import SimpleUploadedFile
from django.db.models.fields.files import ImageFieldFile
from bookwyrm import models, settings
from bookwyrm.preview_images import (
generate_site_preview_image_task,
generate_edition_preview_image_task,
@ -29,18 +29,19 @@ class PreviewImages(TestCase):
avatar_file = pathlib.Path(__file__).parent.joinpath(
"../static/images/no_cover.jpg"
)
self.local_user = models.User.objects.create_user(
"possum@local.com",
"possum@possum.possum",
"password",
local=True,
localname="possum",
avatar=SimpleUploadedFile(
avatar_file,
open(avatar_file, "rb").read(),
content_type="image/jpeg",
),
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"possum@local.com",
"possum@possum.possum",
"password",
local=True,
localname="possum",
avatar=SimpleUploadedFile(
avatar_file,
open(avatar_file, "rb").read(),
content_type="image/jpeg",
),
)
self.work = models.Work.objects.create(title="Test Work")
self.edition = models.Edition.objects.create(

View file

@ -37,19 +37,20 @@ class Signature(TestCase):
def setUp(self):
"""create users and test data"""
self.mouse = models.User.objects.create_user(
"mouse@%s" % DOMAIN,
"mouse@example.com",
"",
local=True,
localname="mouse",
)
self.rat = models.User.objects.create_user(
"rat@%s" % DOMAIN, "rat@example.com", "", local=True, localname="rat"
)
self.cat = models.User.objects.create_user(
"cat@%s" % DOMAIN, "cat@example.com", "", local=True, localname="cat"
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.mouse = models.User.objects.create_user(
"mouse@%s" % DOMAIN,
"mouse@example.com",
"",
local=True,
localname="mouse",
)
self.rat = models.User.objects.create_user(
"rat@%s" % DOMAIN, "rat@example.com", "", local=True, localname="rat"
)
self.cat = models.User.objects.create_user(
"cat@%s" % DOMAIN, "cat@example.com", "", local=True, localname="cat"
)
private_key, public_key = create_key_pair()

View file

@ -0,0 +1,173 @@
""" testing user follow suggestions """
from collections import namedtuple
from unittest.mock import patch
from django.db.models import Q
from django.test import TestCase
from bookwyrm import models
from bookwyrm.suggested_users import suggested_users, get_annotated_users
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
@patch("bookwyrm.suggested_users.rerank_user_task.delay")
@patch("bookwyrm.suggested_users.remove_user_task.delay")
class SuggestedUsers(TestCase):
"""using redis to build activity streams"""
def setUp(self):
"""use a test csv"""
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "password", local=True, localname="mouse"
)
def test_get_rank(self, *_):
"""a float that reflects both the mutuals count and shared books"""
Mock = namedtuple("AnnotatedUserMock", ("mutuals", "shared_books"))
annotated_user_mock = Mock(3, 27)
rank = suggested_users.get_rank(annotated_user_mock)
self.assertEqual(rank, 3.9642857142857144)
def test_store_id(self, *_):
"""redis key generation"""
self.assertEqual(
suggested_users.store_id(self.local_user),
"{:d}-suggestions".format(self.local_user.id),
)
def test_get_counts_from_rank(self, *_):
"""reverse the rank computation to get the mutuals and shared books counts"""
counts = suggested_users.get_counts_from_rank(3.9642857142857144)
self.assertEqual(counts["mutuals"], 3)
self.assertEqual(counts["shared_books"], 27)
def test_get_objects_for_store(self, *_):
"""list of people to follow for a given user"""
mutual_user = models.User.objects.create_user(
"rat", "rat@local.rat", "password", local=True, localname="rat"
)
suggestable_user = models.User.objects.create_user(
"nutria",
"nutria@nutria.nutria",
"password",
local=True,
localname="nutria",
discoverable=True,
)
# you follow rat
mutual_user.followers.add(self.local_user)
# rat follows the suggested user
suggestable_user.followers.add(mutual_user)
results = suggested_users.get_objects_for_store(
"{:d}-suggestions".format(self.local_user.id)
)
self.assertEqual(results.count(), 1)
match = results.first()
self.assertEqual(match.id, suggestable_user.id)
self.assertEqual(match.mutuals, 1)
def test_create_user_signal(self, *_):
"""build suggestions for new users"""
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") as mock:
models.User.objects.create_user(
"nutria", "nutria@nu.tria", "password", local=True, localname="nutria"
)
self.assertEqual(mock.call_count, 1)
def test_get_annotated_users(self, *_):
"""list of people you might know"""
user_1 = models.User.objects.create_user(
"nutria@local.com",
"nutria@nutria.com",
"nutriaword",
local=True,
localname="nutria",
discoverable=True,
)
user_2 = models.User.objects.create_user(
"fish@local.com",
"fish@fish.com",
"fishword",
local=True,
localname="fish",
)
work = models.Work.objects.create(title="Test Work")
book = models.Edition.objects.create(
title="Test Book",
remote_id="https://example.com/book/1",
parent_work=work,
)
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
# 1 shared follow
self.local_user.following.add(user_2)
user_1.followers.add(user_2)
# 1 shared book
models.ShelfBook.objects.create(
user=self.local_user,
book=book,
shelf=self.local_user.shelf_set.first(),
)
models.ShelfBook.objects.create(
user=user_1, book=book, shelf=user_1.shelf_set.first()
)
result = get_annotated_users(self.local_user)
self.assertEqual(result.count(), 1)
self.assertTrue(user_1 in result)
self.assertFalse(user_2 in result)
user_1_annotated = result.get(id=user_1.id)
self.assertEqual(user_1_annotated.mutuals, 1)
self.assertEqual(user_1_annotated.shared_books, 1)
def test_get_annotated_users_counts(self, *_):
"""correct counting for multiple shared attributed"""
user_1 = models.User.objects.create_user(
"nutria@local.com",
"nutria@nutria.com",
"nutriaword",
local=True,
localname="nutria",
discoverable=True,
)
for i in range(3):
user = models.User.objects.create_user(
"{:d}@local.com".format(i),
"{:d}@nutria.com".format(i),
"password",
local=True,
localname=i,
)
user.following.add(user_1)
user.followers.add(self.local_user)
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
for i in range(3):
book = models.Edition.objects.create(
title=i,
parent_work=models.Work.objects.create(title=i),
)
models.ShelfBook.objects.create(
user=self.local_user,
book=book,
shelf=self.local_user.shelf_set.first(),
)
models.ShelfBook.objects.create(
user=user_1, book=book, shelf=user_1.shelf_set.first()
)
result = get_annotated_users(
self.local_user,
~Q(id=self.local_user.id),
~Q(followers=self.local_user),
)
user_1_annotated = result.get(id=user_1.id)
self.assertEqual(user_1_annotated.mutuals, 3)

View file

@ -22,13 +22,14 @@ class TemplateTags(TestCase):
def setUp(self):
"""create some filler objects"""
self.user = models.User.objects.create_user(
"mouse@example.com",
"mouse@mouse.mouse",
"mouseword",
local=True,
localname="mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.user = models.User.objects.create_user(
"mouse@example.com",
"mouse@mouse.mouse",
"mouseword",
local=True,
localname="mouse",
)
with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user(
"rat",

View file

@ -19,15 +19,16 @@ class Inbox(TestCase):
self.client = Client()
self.factory = RequestFactory()
local_user = models.User.objects.create_user(
"mouse@example.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
local_user = models.User.objects.create_user(
"mouse@example.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
)
local_user.remote_id = "https://example.com/user/mouse"
local_user.save(broadcast=False)
local_user.save(broadcast=False, update_fields=["remote_id"])
with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user(
"rat",
@ -144,7 +145,8 @@ class Inbox(TestCase):
)
self.assertTrue(views.inbox.is_blocked_activity(activity))
def test_create_by_deactivated_user(self):
@patch("bookwyrm.suggested_users.remove_user_task.delay")
def test_create_by_deactivated_user(self, _):
"""don't let deactivated users post"""
self.remote_user.delete(broadcast=False)
self.assertTrue(self.remote_user.deleted)

View file

@ -13,15 +13,16 @@ class InboxAdd(TestCase):
def setUp(self):
"""basic user and book data"""
local_user = models.User.objects.create_user(
"mouse@example.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
local_user = models.User.objects.create_user(
"mouse@example.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
)
local_user.remote_id = "https://example.com/user/mouse"
local_user.save(broadcast=False)
local_user.save(broadcast=False, update_fields=["remote_id"])
with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user(
"rat",

View file

@ -13,15 +13,16 @@ class InboxActivities(TestCase):
def setUp(self):
"""basic user and book data"""
self.local_user = models.User.objects.create_user(
"mouse@example.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@example.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
)
self.local_user.remote_id = "https://example.com/user/mouse"
self.local_user.save(broadcast=False)
self.local_user.save(broadcast=False, update_fields=["remote_id"])
with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user(
"rat",

View file

@ -12,15 +12,16 @@ class InboxBlock(TestCase):
def setUp(self):
"""basic user and book data"""
self.local_user = models.User.objects.create_user(
"mouse@example.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@example.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
)
self.local_user.remote_id = "https://example.com/user/mouse"
self.local_user.save(broadcast=False)
self.local_user.save(broadcast=False, update_fields=["remote_id"])
with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user(
"rat",

View file

@ -16,15 +16,16 @@ class InboxCreate(TestCase):
def setUp(self):
"""basic user and book data"""
self.local_user = models.User.objects.create_user(
"mouse@example.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@example.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
)
self.local_user.remote_id = "https://example.com/user/mouse"
self.local_user.save(broadcast=False)
self.local_user.save(broadcast=False, update_fields=["remote_id"])
with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user(
"rat",

View file

@ -13,15 +13,16 @@ class InboxActivities(TestCase):
def setUp(self):
"""basic user and book data"""
self.local_user = models.User.objects.create_user(
"mouse@example.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@example.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
)
self.local_user.remote_id = "https://example.com/user/mouse"
self.local_user.save(broadcast=False)
self.local_user.save(broadcast=False, update_fields=["remote_id"])
with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user(
"rat",
@ -105,7 +106,8 @@ class InboxActivities(TestCase):
self.assertEqual(models.Notification.objects.count(), 1)
self.assertEqual(models.Notification.objects.get(), notif)
def test_delete_user(self):
@patch("bookwyrm.suggested_users.remove_user_task.delay")
def test_delete_user(self, _):
"""delete a user"""
self.assertTrue(models.User.objects.get(username="rat@example.com").is_active)
activity = {

View file

@ -13,15 +13,16 @@ class InboxRelationships(TestCase):
def setUp(self):
"""basic user and book data"""
self.local_user = models.User.objects.create_user(
"mouse@example.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@example.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
)
self.local_user.remote_id = "https://example.com/user/mouse"
self.local_user.save(broadcast=False)
self.local_user.save(broadcast=False, update_fields=["remote_id"])
with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user(
"rat",
@ -102,7 +103,9 @@ class InboxRelationships(TestCase):
}
self.local_user.manually_approves_followers = True
self.local_user.save(broadcast=False)
self.local_user.save(
broadcast=False, update_fields=["manually_approves_followers"]
)
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
views.inbox.activity_task(activity)
@ -124,7 +127,9 @@ class InboxRelationships(TestCase):
def test_undo_follow_request(self):
"""the requester cancels a follow request"""
self.local_user.manually_approves_followers = True
self.local_user.save(broadcast=False)
self.local_user.save(
broadcast=False, update_fields=["manually_approves_followers"]
)
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
request = models.UserFollowRequest.objects.create(
user_subject=self.remote_user, user_object=self.local_user

View file

@ -12,15 +12,16 @@ class InboxActivities(TestCase):
def setUp(self):
"""basic user and book data"""
self.local_user = models.User.objects.create_user(
"mouse@example.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@example.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
)
self.local_user.remote_id = "https://example.com/user/mouse"
self.local_user.save(broadcast=False)
self.local_user.save(broadcast=False, update_fields=["remote_id"])
with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user(
"rat",

View file

@ -12,15 +12,16 @@ class InboxRemove(TestCase):
def setUp(self):
"""basic user and book data"""
self.local_user = models.User.objects.create_user(
"mouse@example.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@example.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
)
self.local_user.remote_id = "https://example.com/user/mouse"
self.local_user.save(broadcast=False)
self.local_user.save(broadcast=False, update_fields=["remote_id"])
with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user(
"rat",

View file

@ -14,15 +14,16 @@ class InboxUpdate(TestCase):
def setUp(self):
"""basic user and book data"""
self.local_user = models.User.objects.create_user(
"mouse@example.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@example.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
)
self.local_user.remote_id = "https://example.com/user/mouse"
self.local_user.save(broadcast=False)
self.local_user.save(broadcast=False, update_fields=["remote_id"])
with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user(
"rat",
@ -79,7 +80,8 @@ class InboxUpdate(TestCase):
self.assertEqual(book_list.description, "summary text")
self.assertEqual(book_list.remote_id, "https://example.com/list/22")
def test_update_user(self):
@patch("bookwyrm.suggested_users.rerank_user_task.delay")
def test_update_user(self, _):
"""update an existing user"""
models.UserFollows.objects.create(
user_subject=self.local_user,

View file

@ -1,4 +1,5 @@
""" test for app action functionality """
from unittest.mock import patch
from django.template.response import TemplateResponse
from django.test import TestCase
from django.test.client import RequestFactory
@ -12,13 +13,14 @@ class AnnouncementViews(TestCase):
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
models.SiteSettings.objects.create()

View file

@ -13,25 +13,27 @@ from bookwyrm.settings import DOMAIN
# pylint: disable=too-many-public-methods
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
class AuthenticationViews(TestCase):
"""login and password management"""
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"password",
local=True,
localname="mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"password",
local=True,
localname="mouse",
)
self.anonymous_user = AnonymousUser
self.anonymous_user.is_authenticated = False
self.settings = models.SiteSettings.objects.create(id=1)
def test_login_get(self):
def test_login_get(self, _):
"""there are so many views, this just makes sure it LOADS"""
login = views.Login.as_view()
request = self.factory.get("")
@ -47,7 +49,7 @@ class AuthenticationViews(TestCase):
self.assertEqual(result.url, "/")
self.assertEqual(result.status_code, 302)
def test_register(self):
def test_register(self, _):
"""create a user"""
view = views.Register.as_view()
self.assertEqual(models.User.objects.count(), 1)
@ -68,7 +70,7 @@ class AuthenticationViews(TestCase):
self.assertEqual(nutria.localname, "nutria-user.user_nutria")
self.assertEqual(nutria.local, True)
def test_register_trailing_space(self):
def test_register_trailing_space(self, _):
"""django handles this so weirdly"""
view = views.Register.as_view()
request = self.factory.post(
@ -84,7 +86,7 @@ class AuthenticationViews(TestCase):
self.assertEqual(nutria.localname, "nutria")
self.assertEqual(nutria.local, True)
def test_register_invalid_email(self):
def test_register_invalid_email(self, _):
"""gotta have an email"""
view = views.Register.as_view()
self.assertEqual(models.User.objects.count(), 1)
@ -95,7 +97,7 @@ class AuthenticationViews(TestCase):
self.assertEqual(models.User.objects.count(), 1)
response.render()
def test_register_invalid_username(self):
def test_register_invalid_username(self, _):
"""gotta have an email"""
view = views.Register.as_view()
self.assertEqual(models.User.objects.count(), 1)
@ -123,7 +125,7 @@ class AuthenticationViews(TestCase):
self.assertEqual(models.User.objects.count(), 1)
response.render()
def test_register_closed_instance(self):
def test_register_closed_instance(self, _):
"""you can't just register"""
view = views.Register.as_view()
self.settings.allow_registration = False
@ -135,7 +137,7 @@ class AuthenticationViews(TestCase):
with self.assertRaises(PermissionDenied):
view(request)
def test_register_invite(self):
def test_register_invite(self, _):
"""you can't just register"""
view = views.Register.as_view()
self.settings.allow_registration = False

View file

@ -17,14 +17,15 @@ class AuthorViews(TestCase):
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
self.group = Group.objects.create(name="editor")
self.group.permissions.add(
Permission.objects.create(

View file

@ -14,13 +14,14 @@ class BlockViews(TestCase):
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user(
"rat",

View file

@ -23,14 +23,15 @@ class BookViews(TestCase):
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
self.group = Group.objects.create(name="editor")
self.group.permissions.add(
Permission.objects.create(
@ -200,7 +201,8 @@ class BookViews(TestCase):
self.assertEqual(book.authors.first().name, "Sappho")
self.assertEqual(book.authors.first(), book.parent_work.authors.first())
def test_switch_edition(self):
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
def test_switch_edition(self, _):
"""updates user's relationships to a book"""
work = models.Work.objects.create(title="test work")
edition1 = models.Edition.objects.create(title="first ed", parent_work=work)

View file

@ -1,4 +1,7 @@
""" 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
from django.test.client import RequestFactory
@ -12,15 +15,25 @@ class DirectoryViews(TestCase):
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
self.rat = models.User.objects.create_user(
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
models.SiteSettings.objects.create()
self.anonymous_user = AnonymousUser
self.anonymous_user.is_authenticated = False
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
@patch("bookwyrm.suggested_users.rerank_user_task.delay")
def test_directory_page(self, *_):
"""there are so many views, this just makes sure it LOADS"""
models.User.objects.create_user(
"rat@local.com",
"rat@rat.com",
"ratword",
@ -29,10 +42,16 @@ class DirectoryViews(TestCase):
remote_id="https://example.com/users/rat",
discoverable=True,
)
view = views.Directory.as_view()
request = self.factory.get("")
request.user = self.local_user
models.SiteSettings.objects.create()
result = view(request)
self.assertIsInstance(result, TemplateResponse)
result.render()
self.assertEqual(result.status_code, 200)
def test_directory_page(self):
def test_directory_page_empty(self):
"""there are so many views, this just makes sure it LOADS"""
view = views.Directory.as_view()
request = self.factory.get("")
@ -42,3 +61,12 @@ class DirectoryViews(TestCase):
self.assertIsInstance(result, TemplateResponse)
result.render()
self.assertEqual(result.status_code, 200)
def test_directory_page_logged_out(self):
"""there are so many views, this just makes sure it LOADS"""
view = views.Directory.as_view()
request = self.factory.get("")
request.user = self.anonymous_user
result = view(request)
self.assertEqual(result.status_code, 302)

View file

@ -15,36 +15,38 @@ from django.test.client import RequestFactory
from bookwyrm import forms, models, views
@patch("bookwyrm.suggested_users.remove_user_task.delay")
class EditUserViews(TestCase):
"""view user and edit profile"""
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
self.rat = models.User.objects.create_user(
"rat@local.com", "rat@rat.rat", "password", local=True, localname="rat"
)
self.book = models.Edition.objects.create(title="test")
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
models.ShelfBook.objects.create(
book=self.book,
user=self.local_user,
shelf=self.local_user.shelf_set.first(),
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
self.rat = models.User.objects.create_user(
"rat@local.com", "rat@rat.rat", "password", local=True, localname="rat"
)
self.book = models.Edition.objects.create(title="test")
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
models.ShelfBook.objects.create(
book=self.book,
user=self.local_user,
shelf=self.local_user.shelf_set.first(),
)
models.SiteSettings.objects.create()
self.anonymous_user = AnonymousUser
self.anonymous_user.is_authenticated = False
def test_edit_user_page(self):
def test_edit_user_page(self, _):
"""there are so many views, this just makes sure it LOADS"""
view = views.EditUser.as_view()
request = self.factory.get("")
@ -54,7 +56,7 @@ class EditUserViews(TestCase):
result.render()
self.assertEqual(result.status_code, 200)
def test_edit_user(self):
def test_edit_user(self, _):
"""use a form to update a user"""
view = views.EditUser.as_view()
form = forms.EditUserForm(instance=self.local_user)
@ -73,7 +75,7 @@ class EditUserViews(TestCase):
self.assertEqual(self.local_user.name, "New Name")
self.assertEqual(self.local_user.email, "wow@email.com")
def test_edit_user_avatar(self):
def test_edit_user_avatar(self, _):
"""use a form to update a user"""
view = views.EditUser.as_view()
form = forms.EditUserForm(instance=self.local_user)
@ -100,7 +102,7 @@ class EditUserViews(TestCase):
self.assertEqual(self.local_user.avatar.width, 120)
self.assertEqual(self.local_user.avatar.height, 120)
def test_crop_avatar(self):
def test_crop_avatar(self, _):
"""reduce that image size"""
image_file = pathlib.Path(__file__).parent.joinpath(
"../../static/images/no_cover.jpg"
@ -112,7 +114,7 @@ class EditUserViews(TestCase):
image_result = Image.open(result)
self.assertEqual(image_result.size, (120, 120))
def test_delete_user_page(self):
def test_delete_user_page(self, _):
"""there are so many views, this just makes sure it LOADS"""
view = views.DeleteUser.as_view()
request = self.factory.get("")
@ -122,7 +124,8 @@ class EditUserViews(TestCase):
result.render()
self.assertEqual(result.status_code, 200)
def test_delete_user(self):
@patch("bookwyrm.suggested_users.rerank_suggestions_task")
def test_delete_user(self, *_):
"""use a form to update a user"""
view = views.DeleteUser.as_view()
form = forms.DeleteUserForm()

View file

@ -15,13 +15,14 @@ class FederationViews(TestCase):
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user(
"rat",
@ -68,7 +69,7 @@ class FederationViews(TestCase):
identifier="hi.there.com",
)
self.remote_user.federated_server = server
self.remote_user.save()
self.remote_user.save(update_fields=["federated_server"])
self.assertEqual(server.status, "federated")
@ -107,7 +108,9 @@ class FederationViews(TestCase):
self.remote_user.federated_server = server
self.remote_user.is_active = False
self.remote_user.deactivation_reason = "domain_block"
self.remote_user.save()
self.remote_user.save(
update_fields=["federated_server", "is_active", "deactivation_reason"]
)
request = self.factory.post("")
request.user = self.local_user
@ -162,7 +165,7 @@ class FederationViews(TestCase):
"""load a json file with a list of servers to block"""
server = models.FederatedServer.objects.create(server_name="hi.there.com")
self.remote_user.federated_server = server
self.remote_user.save()
self.remote_user.save(update_fields=["federated_server"])
data = [
{"instance": "server.name", "url": "https://explanation.url"}, # new server

View file

@ -16,19 +16,22 @@ from bookwyrm.activitypub import ActivitypubResponse
@patch("bookwyrm.activitystreams.ActivityStream.get_activity_stream")
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
@patch("bookwyrm.suggested_users.remove_user_task.delay")
class FeedViews(TestCase):
"""activity feed, statuses, dms"""
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
self.book = models.Edition.objects.create(
parent_work=models.Work.objects.create(title="hi"),
title="Example Edition",
@ -36,6 +39,7 @@ class FeedViews(TestCase):
)
models.SiteSettings.objects.create()
@patch("bookwyrm.suggested_users.SuggestedUsers.get_suggestions")
def test_feed(self, *_):
"""there are so many views, this just makes sure it LOADS"""
view = views.Feed.as_view()

View file

@ -10,20 +10,21 @@ from django.test.client import RequestFactory
from bookwyrm import models, views
class BookViews(TestCase):
"""books books books"""
class FollowViews(TestCase):
"""follows"""
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
with patch("bookwyrm.models.user.set_remote_server"):
self.remote_user = models.User.objects.create_user(
"rat",
@ -66,15 +67,16 @@ class BookViews(TestCase):
def test_handle_follow_local_manually_approves(self):
"""send a follow request"""
rat = models.User.objects.create_user(
"rat@local.com",
"rat@rat.com",
"ratword",
local=True,
localname="rat",
remote_id="https://example.com/users/rat",
manually_approves_followers=True,
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
rat = models.User.objects.create_user(
"rat@local.com",
"rat@rat.com",
"ratword",
local=True,
localname="rat",
remote_id="https://example.com/users/rat",
manually_approves_followers=True,
)
request = self.factory.post("", {"user": rat})
request.user = self.local_user
self.assertEqual(models.UserFollowRequest.objects.count(), 0)
@ -89,14 +91,15 @@ class BookViews(TestCase):
def test_handle_follow_local(self):
"""send a follow request"""
rat = models.User.objects.create_user(
"rat@local.com",
"rat@rat.com",
"ratword",
local=True,
localname="rat",
remote_id="https://example.com/users/rat",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
rat = models.User.objects.create_user(
"rat@local.com",
"rat@rat.com",
"ratword",
local=True,
localname="rat",
remote_id="https://example.com/users/rat",
)
request = self.factory.post("", {"user": rat})
request.user = self.local_user
self.assertEqual(models.UserFollowRequest.objects.count(), 0)
@ -127,7 +130,9 @@ class BookViews(TestCase):
def test_handle_accept(self):
"""accept a follow request"""
self.local_user.manually_approves_followers = True
self.local_user.save(broadcast=False)
self.local_user.save(
broadcast=False, update_fields=["manually_approves_followers"]
)
request = self.factory.post("", {"user": self.remote_user.username})
request.user = self.local_user
rel = models.UserFollowRequest.objects.create(
@ -144,7 +149,9 @@ class BookViews(TestCase):
def test_handle_reject(self):
"""reject a follow request"""
self.local_user.manually_approves_followers = True
self.local_user.save(broadcast=False)
self.local_user.save(
broadcast=False, update_fields=["manually_approves_followers"]
)
request = self.factory.post("", {"user": self.remote_user.username})
request.user = self.local_user
rel = models.UserFollowRequest.objects.create(

View file

@ -13,13 +13,14 @@ class GetStartedViews(TestCase):
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
self.book = models.Edition.objects.create(
parent_work=models.Work.objects.create(title="hi"),
title="Example Edition",
@ -42,7 +43,9 @@ class GetStartedViews(TestCase):
result.render()
self.assertEqual(result.status_code, 200)
def test_profile_view_post(self):
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
@patch("bookwyrm.suggested_users.rerank_user_task.delay")
def test_profile_view_post(self, *_):
"""save basic user details"""
view = views.GetStartedProfile.as_view()
form = forms.LimitedEditUserForm(instance=self.local_user)
@ -84,7 +87,8 @@ class GetStartedViews(TestCase):
result.render()
self.assertEqual(result.status_code, 200)
def test_books_view_post(self):
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
def test_books_view_post(self, _):
"""shelve some books"""
view = views.GetStartedBooks.as_view()
data = {self.book.id: self.local_user.shelf_set.first().id}
@ -102,7 +106,8 @@ class GetStartedViews(TestCase):
self.assertEqual(shelfbook.book, self.book)
self.assertEqual(shelfbook.user, self.local_user)
def test_users_view(self):
@patch("bookwyrm.suggested_users.SuggestedUsers.get_suggestions")
def test_users_view(self, _):
"""there are so many views, this just makes sure it LOADS"""
view = views.GetStartedUsers.as_view()
request = self.factory.get("")
@ -114,7 +119,8 @@ class GetStartedViews(TestCase):
result.render()
self.assertEqual(result.status_code, 200)
def test_users_view_with_query(self):
@patch("bookwyrm.suggested_users.SuggestedUsers.get_suggestions")
def test_users_view_with_query(self, _):
"""there are so many views, this just makes sure it LOADS"""
view = views.GetStartedUsers.as_view()
request = self.factory.get("?query=rat")

View file

@ -16,22 +16,23 @@ class GoalViews(TestCase):
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
self.rat = models.User.objects.create_user(
"rat@local.com",
"rat@rat.com",
"ratword",
local=True,
localname="rat",
remote_id="https://example.com/users/rat",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
self.rat = models.User.objects.create_user(
"rat@local.com",
"rat@rat.com",
"ratword",
local=True,
localname="rat",
remote_id="https://example.com/users/rat",
)
self.book = models.Edition.objects.create(
title="Example Edition",
remote_id="https://example.com/book/1",

View file

@ -2,7 +2,6 @@
import json
from unittest.mock import patch
import pathlib
from django.db.models import Q
from django.http import Http404
from django.test import TestCase
from django.test.client import RequestFactory
@ -13,32 +12,37 @@ from bookwyrm.settings import USER_AGENT
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
@patch("bookwyrm.suggested_users.rerank_user_task.delay")
class ViewsHelpers(TestCase):
"""viewing and creating statuses"""
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
discoverable=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
with patch("bookwyrm.suggested_users.rerank_user_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
discoverable=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
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",
discoverable=True,
inbox="https://example.com/users/rat/inbox",
outbox="https://example.com/users/rat/outbox",
)
with patch("bookwyrm.suggested_users.rerank_user_task.delay"):
self.remote_user = models.User.objects.create_user(
"rat",
"rat@rat.com",
"ratword",
local=False,
remote_id="https://example.com/users/rat",
discoverable=True,
inbox="https://example.com/users/rat/inbox",
outbox="https://example.com/users/rat/outbox",
)
self.work = models.Work.objects.create(title="Test Work")
self.book = models.Edition.objects.create(
title="Test Book",
@ -53,12 +57,12 @@ class ViewsHelpers(TestCase):
name="Test Shelf", identifier="test-shelf", user=self.local_user
)
def test_get_edition(self, _):
def test_get_edition(self, *_):
"""given an edition or a work, returns an edition"""
self.assertEqual(views.helpers.get_edition(self.book.id), self.book)
self.assertEqual(views.helpers.get_edition(self.work.id), self.book)
def test_get_user_from_username(self, _):
def test_get_user_from_username(self, *_):
"""works for either localname or username"""
self.assertEqual(
views.helpers.get_user_from_username(self.local_user, "mouse"),
@ -71,7 +75,7 @@ class ViewsHelpers(TestCase):
with self.assertRaises(Http404):
views.helpers.get_user_from_username(self.local_user, "mojfse@example.com")
def test_is_api_request(self, _):
def test_is_api_request(self, *_):
"""should it return html or json"""
request = self.factory.get("/path")
request.headers = {"Accept": "application/json"}
@ -85,12 +89,12 @@ class ViewsHelpers(TestCase):
request.headers = {"Accept": "Praise"}
self.assertFalse(views.helpers.is_api_request(request))
def test_is_api_request_no_headers(self, _):
def test_is_api_request_no_headers(self, *_):
"""should it return html or json"""
request = self.factory.get("/path")
self.assertFalse(views.helpers.is_api_request(request))
def test_is_bookwyrm_request(self, _):
def test_is_bookwyrm_request(self, *_):
"""checks if a request came from a bookwyrm instance"""
request = self.factory.get("", {"q": "Test Book"})
self.assertFalse(views.helpers.is_bookwyrm_request(request))
@ -105,7 +109,7 @@ class ViewsHelpers(TestCase):
request = self.factory.get("", {"q": "Test Book"}, HTTP_USER_AGENT=USER_AGENT)
self.assertTrue(views.helpers.is_bookwyrm_request(request))
def test_existing_user(self, _):
def test_existing_user(self, *_):
"""simple database lookup by username"""
result = views.helpers.handle_remote_webfinger("@mouse@local.com")
self.assertEqual(result, self.local_user)
@ -117,7 +121,7 @@ class ViewsHelpers(TestCase):
self.assertEqual(result, self.local_user)
@responses.activate
def test_load_user(self, _):
def test_load_user(self, *_):
"""find a remote user using webfinger"""
username = "mouse@example.com"
wellknown = {
@ -147,7 +151,7 @@ class ViewsHelpers(TestCase):
self.assertIsInstance(result, models.User)
self.assertEqual(result.username, "mouse@example.com")
def test_user_on_blocked_server(self, _):
def test_user_on_blocked_server(self, *_):
"""find a remote user using webfinger"""
models.FederatedServer.objects.create(
server_name="example.com", status="blocked"
@ -156,7 +160,7 @@ class ViewsHelpers(TestCase):
result = views.helpers.handle_remote_webfinger("@mouse@example.com")
self.assertIsNone(result)
def test_handle_reading_status_to_read(self, _):
def test_handle_reading_status_to_read(self, *_):
"""posts shelve activities"""
shelf = self.local_user.shelf_set.get(identifier="to-read")
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
@ -168,7 +172,7 @@ 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, _):
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"):
@ -180,7 +184,7 @@ class ViewsHelpers(TestCase):
self.assertEqual(status.mention_books.first(), self.book)
self.assertEqual(status.content, "started reading")
def test_handle_reading_status_read(self, _):
def test_handle_reading_status_read(self, *_):
"""posts shelve activities"""
shelf = self.local_user.shelf_set.get(identifier="read")
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
@ -192,102 +196,10 @@ class ViewsHelpers(TestCase):
self.assertEqual(status.mention_books.first(), self.book)
self.assertEqual(status.content, "finished reading")
def test_handle_reading_status_other(self, _):
def test_handle_reading_status_other(self, *_):
"""posts shelve activities"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
views.helpers.handle_reading_status(
self.local_user, self.shelf, self.book, "public"
)
self.assertFalse(models.GeneratedNote.objects.exists())
def test_get_annotated_users(self, _):
"""list of people you might know"""
user_1 = models.User.objects.create_user(
"nutria@local.com",
"nutria@nutria.com",
"nutriaword",
local=True,
localname="nutria",
discoverable=True,
)
user_2 = models.User.objects.create_user(
"fish@local.com",
"fish@fish.com",
"fishword",
local=True,
localname="fish",
)
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
# 1 shared follow
self.local_user.following.add(user_2)
user_1.followers.add(user_2)
# 1 shared book
models.ShelfBook.objects.create(
user=self.local_user,
book=self.book,
shelf=self.local_user.shelf_set.first(),
)
models.ShelfBook.objects.create(
user=user_1, book=self.book, shelf=user_1.shelf_set.first()
)
result = views.helpers.get_annotated_users(self.local_user)
self.assertEqual(result.count(), 3)
self.assertTrue(user_1 in result)
self.assertFalse(user_2 in result)
self.assertTrue(self.local_user in result)
self.assertTrue(self.remote_user in result)
user_1_annotated = result.get(id=user_1.id)
self.assertEqual(user_1_annotated.mutuals, 1)
self.assertEqual(user_1_annotated.shared_books, 1)
remote_user_annotated = result.get(id=self.remote_user.id)
self.assertEqual(remote_user_annotated.mutuals, 0)
self.assertEqual(remote_user_annotated.shared_books, 0)
def test_get_annotated_users_counts(self, _):
"""correct counting for multiple shared attributed"""
user_1 = models.User.objects.create_user(
"nutria@local.com",
"nutria@nutria.com",
"nutriaword",
local=True,
localname="nutria",
discoverable=True,
)
for i in range(3):
user = models.User.objects.create_user(
"{:d}@local.com".format(i),
"{:d}@nutria.com".format(i),
"password",
local=True,
localname=i,
)
user.following.add(user_1)
user.followers.add(self.local_user)
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
for i in range(3):
book = models.Edition.objects.create(
title=i,
parent_work=models.Work.objects.create(title=i),
)
models.ShelfBook.objects.create(
user=self.local_user,
book=book,
shelf=self.local_user.shelf_set.first(),
)
models.ShelfBook.objects.create(
user=user_1, book=book, shelf=user_1.shelf_set.first()
)
result = views.helpers.get_annotated_users(
self.local_user,
~Q(id=self.local_user.id),
~Q(followers=self.local_user),
)
self.assertEqual(result.count(), 2)
user_1_annotated = result.get(id=user_1.id)
self.assertEqual(user_1_annotated.mutuals, 3)

View file

@ -14,13 +14,14 @@ class ImportViews(TestCase):
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
models.SiteSettings.objects.create()
def test_import_page(self):

View file

@ -15,14 +15,15 @@ class InteractionViews(TestCase):
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
with patch("bookwyrm.models.user.set_remote_server"):
self.remote_user = models.User.objects.create_user(
"rat",

View file

@ -16,13 +16,14 @@ class InviteViews(TestCase):
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
models.SiteSettings.objects.create()
def test_invite_page(self):

View file

@ -16,14 +16,15 @@ class IsbnViews(TestCase):
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
self.work = models.Work.objects.create(title="Test Work")
self.book = models.Edition.objects.create(
title="Test Book",

View file

@ -15,18 +15,20 @@ class LandingViews(TestCase):
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
self.anonymous_user = AnonymousUser
self.anonymous_user.is_authenticated = False
models.SiteSettings.objects.create()
def test_home_page(self):
@patch("bookwyrm.suggested_users.SuggestedUsers.get_suggestions")
def test_home_page(self, _):
"""there are so many views, this just makes sure it LOADS"""
view = views.Home.as_view()
request = self.factory.get("")

View file

@ -17,22 +17,23 @@ class ListViews(TestCase):
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
self.rat = models.User.objects.create_user(
"rat@local.com",
"rat@rat.com",
"ratword",
local=True,
localname="rat",
remote_id="https://example.com/users/rat",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
self.rat = models.User.objects.create_user(
"rat@local.com",
"rat@rat.com",
"ratword",
local=True,
localname="rat",
remote_id="https://example.com/users/rat",
)
work = models.Work.objects.create(title="Work")
self.book = models.Edition.objects.create(
title="Example Edition",

View file

@ -15,22 +15,23 @@ class ListActionViews(TestCase):
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
self.rat = models.User.objects.create_user(
"rat@local.com",
"rat@rat.com",
"ratword",
local=True,
localname="rat",
remote_id="https://example.com/users/rat",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
self.rat = models.User.objects.create_user(
"rat@local.com",
"rat@rat.com",
"ratword",
local=True,
localname="rat",
remote_id="https://example.com/users/rat",
)
work = models.Work.objects.create(title="Work")
self.book = models.Edition.objects.create(

View file

@ -1,4 +1,5 @@
""" test for app action functionality """
from unittest.mock import patch
from django.template.response import TemplateResponse
from django.test import TestCase
from django.test.client import RequestFactory
@ -13,13 +14,14 @@ class NotificationViews(TestCase):
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
models.SiteSettings.objects.create()
def test_notifications_page(self):

View file

@ -18,14 +18,15 @@ class OutboxView(TestCase):
def setUp(self):
"""we'll need some data"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
work = models.Work.objects.create(title="Test Work")
self.book = models.Edition.objects.create(
title="Example Edition",

View file

@ -15,13 +15,14 @@ class PasswordViews(TestCase):
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"password",
local=True,
localname="mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"password",
local=True,
localname="mouse",
)
self.anonymous_user = AnonymousUser
self.anonymous_user.is_authenticated = False
models.SiteSettings.objects.create(id=1)

View file

@ -9,20 +9,22 @@ from bookwyrm import models, views
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
class ReadingViews(TestCase):
"""viewing and creating statuses"""
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user(
"rat",
@ -40,7 +42,7 @@ class ReadingViews(TestCase):
parent_work=self.work,
)
def test_start_reading(self, _):
def test_start_reading(self, *_):
"""begin a book"""
shelf = self.local_user.shelf_set.get(identifier=models.Shelf.READING)
self.assertFalse(shelf.books.exists())
@ -71,7 +73,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=models.Shelf.TO_READ)
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
@ -91,7 +93,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=models.Shelf.READ_FINISHED)
self.assertFalse(shelf.books.exists())
@ -127,7 +129,7 @@ class ReadingViews(TestCase):
self.assertEqual(readthrough.user, self.local_user)
self.assertEqual(readthrough.book, self.book)
def test_edit_readthrough(self, _):
def test_edit_readthrough(self, *_):
"""adding dates to an ongoing readthrough"""
start = timezone.make_aware(dateutil.parser.parse("2021-01-03"))
readthrough = models.ReadThrough.objects.create(
@ -154,7 +156,7 @@ class ReadingViews(TestCase):
self.assertEqual(readthrough.finish_date.day, 7)
self.assertEqual(readthrough.book, self.book)
def test_delete_readthrough(self, _):
def test_delete_readthrough(self, *_):
"""remove a readthrough"""
readthrough = models.ReadThrough.objects.create(
book=self.book, user=self.local_user
@ -171,7 +173,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(
"",

View file

@ -7,6 +7,7 @@ from django.utils import timezone
from bookwyrm import models
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
class ReadThrough(TestCase):
"""readthrough tests"""
@ -21,14 +22,15 @@ class ReadThrough(TestCase):
title="Example Edition", parent_work=self.work
)
self.user = models.User.objects.create_user(
"cinco", "cinco@example.com", "seissiete", local=True, localname="cinco"
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.user = models.User.objects.create_user(
"cinco", "cinco@example.com", "seissiete", local=True, localname="cinco"
)
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
self.client.force_login(self.user)
def test_create_basic_readthrough(self, delay_mock):
def test_create_basic_readthrough(self, delay_mock, _):
"""A basic readthrough doesn't create a progress update"""
self.assertEqual(self.edition.readthrough_set.count(), 0)
@ -49,7 +51,7 @@ class ReadThrough(TestCase):
self.assertEqual(readthroughs[0].finish_date, None)
self.assertEqual(delay_mock.call_count, 1)
def test_create_progress_readthrough(self, delay_mock):
def test_create_progress_readthrough(self, delay_mock, _):
"""a readthrough with progress"""
self.assertEqual(self.edition.readthrough_set.count(), 0)

View file

@ -1,4 +1,5 @@
""" test for app action functionality """
from unittest.mock import patch
from django.template.response import TemplateResponse
from django.test import TestCase
from django.test.client import RequestFactory
@ -12,20 +13,21 @@ class ReportViews(TestCase):
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
self.rat = models.User.objects.create_user(
"rat@local.com",
"rat@mouse.mouse",
"password",
local=True,
localname="rat",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
self.rat = models.User.objects.create_user(
"rat@local.com",
"rat@mouse.mouse",
"password",
local=True,
localname="rat",
)
models.SiteSettings.objects.create()
def test_reports_page(self):
@ -114,7 +116,9 @@ class ReportViews(TestCase):
report.refresh_from_db()
self.assertFalse(report.resolved)
def test_suspend_user(self):
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
@patch("bookwyrm.suggested_users.remove_user_task.delay")
def test_suspend_user(self, *_):
"""toggle whether a user is able to log in"""
self.assertTrue(self.rat.is_active)
request = self.factory.post("")

View file

@ -14,9 +14,10 @@ class RssFeedView(TestCase):
"""test data"""
self.site = models.SiteSettings.objects.create()
self.user = models.User.objects.create_user(
"rss_user", "rss@test.rss", "password", local=True
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.user = models.User.objects.create_user(
"rss_user", "rss@test.rss", "password", local=True
)
work = models.Work.objects.create(title="Test Work")
self.book = models.Edition.objects.create(

View file

@ -19,14 +19,15 @@ class Views(TestCase):
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
self.work = models.Work.objects.create(title="Test Work")
self.book = models.Edition.objects.create(
title="Test Book",

View file

@ -10,20 +10,22 @@ from bookwyrm.activitypub import ActivitypubResponse
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
class ShelfViews(TestCase):
"""tag views"""
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
self.work = models.Work.objects.create(title="Test Work")
self.book = models.Edition.objects.create(
title="Example Edition",
@ -36,7 +38,7 @@ class ShelfViews(TestCase):
)
models.SiteSettings.objects.create()
def test_shelf_page(self, _):
def test_shelf_page(self, *_):
"""there are so many views, this just makes sure it LOADS"""
view = views.Shelf.as_view()
shelf = self.local_user.shelf_set.first()
@ -63,7 +65,7 @@ class ShelfViews(TestCase):
self.assertIsInstance(result, ActivitypubResponse)
self.assertEqual(result.status_code, 200)
def test_edit_shelf_privacy(self, _):
def test_edit_shelf_privacy(self, *_):
"""set name or privacy on shelf"""
view = views.Shelf.as_view()
shelf = self.local_user.shelf_set.get(identifier="to-read")
@ -83,7 +85,7 @@ class ShelfViews(TestCase):
self.assertEqual(shelf.privacy, "unlisted")
def test_edit_shelf_name(self, _):
def test_edit_shelf_name(self, *_):
"""change the name of an editable shelf"""
view = views.Shelf.as_view()
shelf = models.Shelf.objects.create(name="Test Shelf", user=self.local_user)
@ -100,7 +102,7 @@ class ShelfViews(TestCase):
self.assertEqual(shelf.name, "cool name")
self.assertEqual(shelf.identifier, "testshelf-%d" % shelf.id)
def test_edit_shelf_name_not_editable(self, _):
def test_edit_shelf_name_not_editable(self, *_):
"""can't change the name of an non-editable shelf"""
view = views.Shelf.as_view()
shelf = self.local_user.shelf_set.get(identifier="to-read")
@ -115,7 +117,7 @@ class ShelfViews(TestCase):
self.assertEqual(shelf.name, "To Read")
def test_handle_shelve(self, _):
def test_handle_shelve(self, *_):
"""shelve a book"""
request = self.factory.post(
"", {"book": self.book.id, "shelf": self.shelf.identifier}
@ -133,7 +135,7 @@ class ShelfViews(TestCase):
# make sure the book is on the shelf
self.assertEqual(self.shelf.books.get(), self.book)
def test_handle_shelve_to_read(self, _):
def test_handle_shelve_to_read(self, *_):
"""special behavior for the to-read shelf"""
shelf = models.Shelf.objects.get(identifier="to-read")
request = self.factory.post(
@ -146,7 +148,7 @@ class ShelfViews(TestCase):
# make sure the book is on the shelf
self.assertEqual(shelf.books.get(), self.book)
def test_handle_shelve_reading(self, _):
def test_handle_shelve_reading(self, *_):
"""special behavior for the reading shelf"""
shelf = models.Shelf.objects.get(identifier="reading")
request = self.factory.post(
@ -159,7 +161,7 @@ class ShelfViews(TestCase):
# make sure the book is on the shelf
self.assertEqual(shelf.books.get(), self.book)
def test_handle_shelve_read(self, _):
def test_handle_shelve_read(self, *_):
"""special behavior for the read shelf"""
shelf = models.Shelf.objects.get(identifier="read")
request = self.factory.post(
@ -172,7 +174,7 @@ class ShelfViews(TestCase):
# make sure the book is on the shelf
self.assertEqual(shelf.books.get(), self.book)
def test_handle_unshelve(self, _):
def test_handle_unshelve(self, *_):
"""remove a book from a shelf"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
models.ShelfBook.objects.create(

View file

@ -9,6 +9,7 @@ from bookwyrm.settings import DOMAIN
# pylint: disable=invalid-name
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
class StatusViews(TestCase):
"""viewing and creating statuses"""
@ -16,14 +17,15 @@ class StatusViews(TestCase):
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
with patch("bookwyrm.models.user.set_remote_server"):
self.remote_user = models.User.objects.create_user(
"rat",
@ -43,7 +45,7 @@ class StatusViews(TestCase):
)
models.SiteSettings.objects.create()
def test_handle_status(self, _):
def test_handle_status(self, *_):
"""create a status"""
view = views.CreateStatus.as_view()
form = forms.CommentForm(
@ -66,7 +68,7 @@ class StatusViews(TestCase):
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(
@ -96,7 +98,7 @@ class StatusViews(TestCase):
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(
@ -128,7 +130,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(
@ -172,7 +174,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_delete_and_redraft(self, _):
def test_delete_and_redraft(self, *_):
"""delete and re-draft a status"""
view = views.DeleteAndRedraft.as_view()
request = self.factory.post("")
@ -193,7 +195,7 @@ class StatusViews(TestCase):
status.refresh_from_db()
self.assertTrue(status.deleted)
def test_delete_and_redraft_invalid_status_type_rating(self, _):
def test_delete_and_redraft_invalid_status_type_rating(self, *_):
"""you can't redraft generated statuses"""
view = views.DeleteAndRedraft.as_view()
request = self.factory.post("")
@ -213,7 +215,7 @@ class StatusViews(TestCase):
status.refresh_from_db()
self.assertFalse(status.deleted)
def test_delete_and_redraft_invalid_status_type_generated_note(self, _):
def test_delete_and_redraft_invalid_status_type_generated_note(self, *_):
"""you can't redraft generated statuses"""
view = views.DeleteAndRedraft.as_view()
request = self.factory.post("")
@ -233,7 +235,7 @@ class StatusViews(TestCase):
status.refresh_from_db()
self.assertFalse(status.deleted)
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,
@ -279,7 +281,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(
@ -302,7 +304,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)
@ -311,7 +313,7 @@ class StatusViews(TestCase):
'<p><em>hi</em> and <a href="http://fish.com">fish.com</a> ' "is rad</p>",
)
def test_to_markdown_detect_url(self, _):
def test_to_markdown_detect_url(self, *_):
"""this is mostly handled in other places, but nonetheless"""
text = "http://fish.com/@hello#okay"
result = views.status.to_markdown(text)
@ -320,13 +322,13 @@ class StatusViews(TestCase):
'<p><a href="http://fish.com/@hello#okay">fish.com/@hello#okay</a></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, mock):
def test_handle_delete_status(self, mock, *_):
"""marks a status as deleted"""
view = views.DeleteStatus.as_view()
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
@ -346,7 +348,7 @@ class StatusViews(TestCase):
status.refresh_from_db()
self.assertTrue(status.deleted)
def test_handle_delete_status_permission_denied(self, _):
def test_handle_delete_status_permission_denied(self, *_):
"""marks a status as deleted"""
view = views.DeleteStatus.as_view()
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
@ -360,7 +362,7 @@ class StatusViews(TestCase):
status.refresh_from_db()
self.assertFalse(status.deleted)
def test_handle_delete_status_moderator(self, mock):
def test_handle_delete_status_moderator(self, mock, _):
"""marks a status as deleted"""
view = views.DeleteStatus.as_view()
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):

View file

@ -15,13 +15,14 @@ class UpdateViews(TestCase):
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
models.SiteSettings.objects.create()
def test_get_notification_count(self):

View file

@ -17,23 +17,25 @@ class UserViews(TestCase):
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
self.rat = models.User.objects.create_user(
"rat@local.com", "rat@rat.rat", "password", local=True, localname="rat"
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
self.rat = models.User.objects.create_user(
"rat@local.com", "rat@rat.rat", "password", local=True, localname="rat"
)
self.book = models.Edition.objects.create(title="test")
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
models.ShelfBook.objects.create(
book=self.book,
user=self.local_user,
shelf=self.local_user.shelf_set.first(),
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
models.ShelfBook.objects.create(
book=self.book,
user=self.local_user,
shelf=self.local_user.shelf_set.first(),
)
models.SiteSettings.objects.create()
self.anonymous_user = AnonymousUser
@ -94,7 +96,8 @@ class UserViews(TestCase):
self.assertIsInstance(result, ActivitypubResponse)
self.assertEqual(result.status_code, 200)
def test_followers_page_blocked(self):
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
def test_followers_page_blocked(self, _):
"""there are so many views, this just makes sure it LOADS"""
view = views.Followers.as_view()
request = self.factory.get("")

View file

@ -14,13 +14,14 @@ class UserAdminViews(TestCase):
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
models.SiteSettings.objects.create()
def test_user_admin_list_page(self):
@ -47,7 +48,9 @@ class UserAdminViews(TestCase):
result.render()
self.assertEqual(result.status_code, 200)
def test_user_admin_page_post(self):
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
@patch("bookwyrm.suggested_users.remove_user_task.delay")
def test_user_admin_page_post(self, *_):
"""set the user's group"""
group = Group.objects.create(name="editor")
self.assertEqual(

View file

@ -16,16 +16,17 @@ class UserViews(TestCase):
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
models.User.objects.create_user(
"rat@local.com", "rat@rat.rat", "password", local=True, localname="rat"
)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
models.User.objects.create_user(
"rat@local.com", "rat@rat.rat", "password", local=True, localname="rat"
)
with patch("bookwyrm.models.user.set_remote_server.delay"):
models.User.objects.create_user(
"rat",

View file

@ -50,7 +50,7 @@ class Login(View):
# successful login
login(request, user)
user.last_active_date = timezone.now()
user.save(broadcast=False)
user.save(broadcast=False, update_fields=["last_active_date"])
return redirect(request.GET.get("next", "/"))
# login errors

View file

@ -6,7 +6,7 @@ from django.template.response import TemplateResponse
from django.views import View
from django.utils.decorators import method_decorator
from .helpers import get_annotated_users
from bookwyrm import suggested_users
# pylint: disable=no-self-use
@method_decorator(login_required, name="dispatch")
@ -23,7 +23,7 @@ class Directory(View):
if scope == "local":
filters["local"] = True
users = get_annotated_users(request.user, **filters)
users = suggested_users.get_annotated_users(request.user, **filters)
sort = request.GET.get("sort")
if sort == "recent":
users = users.order_by("-last_active_date")

View file

@ -11,7 +11,8 @@ from django.views import View
from bookwyrm import activitystreams, forms, models
from bookwyrm.activitypub import ActivitypubResponse
from bookwyrm.settings import PAGE_LENGTH, STREAMS
from .helpers import get_user_from_username, privacy_filter, get_suggested_users
from bookwyrm.suggested_users import suggested_users
from .helpers import get_user_from_username, privacy_filter
from .helpers import is_api_request, is_bookwyrm_request
@ -28,14 +29,14 @@ class Feed(View):
activities = activitystreams.streams[tab].get_activity_stream(request.user)
paginated = Paginator(activities, PAGE_LENGTH)
suggested_users = get_suggested_users(request.user)
suggestions = suggested_users.get_suggestions(request.user)
data = {
**feed_page_data(request.user),
**{
"user": request.user,
"activities": paginated.get_page(request.GET.get("page")),
"suggested_users": suggested_users,
"suggested_users": suggestions,
"tab": tab,
"goal_form": forms.GoalForm(),
"path": "/%s" % tab,

View file

@ -13,7 +13,7 @@ from django.views import View
from bookwyrm import forms, models
from bookwyrm.connectors import connector_manager
from .helpers import get_suggested_users
from bookwyrm.suggested_users import suggested_users
from .edit_user import save_user_form
@ -118,11 +118,12 @@ class GetStartedUsers(View):
)
.order_by("-similarity")[:5]
)
data = {"no_results": not user_results}
if user_results.count() < 5:
user_results = list(user_results) + list(get_suggested_users(request.user))
user_results = list(user_results) + suggested_users.get_suggestions(
request.user
)
data = {
"suggested_users": user_results,
}
data["suggested_users"] = user_results
return TemplateResponse(request, "get_started/users.html", data)

View file

@ -2,7 +2,7 @@
import re
from requests import HTTPError
from django.core.exceptions import FieldError
from django.db.models import Count, Max, Q
from django.db.models import Max, Q
from django.http import Http404
from bookwyrm import activitypub, models
@ -177,47 +177,3 @@ def get_discover_books():
.order_by("-review__published_date__max")[:6]
)
)
def get_suggested_users(user):
"""bookwyrm users you don't already know"""
return (
get_annotated_users(
user,
~Q(id=user.id),
~Q(followers=user),
~Q(follower_requests=user),
bookwyrm_user=True,
)
.order_by("-mutuals", "-last_active_date")
.all()[:5]
)
def get_annotated_users(user, *args, **kwargs):
"""Users, annotated with things they have in common"""
return (
models.User.objects.filter(discoverable=True, is_active=True, *args, **kwargs)
.exclude(Q(id__in=user.blocks.all()) | Q(blocks=user))
.annotate(
mutuals=Count(
"followers",
filter=Q(
~Q(id=user.id),
~Q(id__in=user.following.all()),
followers__in=user.following.all(),
),
distinct=True,
),
shared_books=Count(
"shelfbook",
filter=Q(
~Q(id=user.id),
shelfbook__book__parent_work__in=[
s.book.parent_work for s in user.shelfbook_set.all()
],
),
distinct=True,
),
)
)

View file

@ -76,7 +76,7 @@ class PasswordReset(View):
return TemplateResponse(request, "password_reset.html", data)
user.set_password(new_password)
user.save(broadcast=False)
user.save(broadcast=False, update_fields=["password"])
login(request, user)
reset_code.delete()
return redirect("/")
@ -100,6 +100,6 @@ class ChangePassword(View):
return redirect("preferences/password")
request.user.set_password(new_password)
request.user.save(broadcast=False)
request.user.save(broadcast=False, update_fields=["password"])
login(request, request.user)
return redirect(request.user.local_path)

4
bw-dev
View file

@ -123,6 +123,9 @@ case "$CMD" in
populate_streams)
runweb python manage.py populate_streams
;;
populate_suggestions)
runweb python manage.py populate_suggestions
;;
generate_preview_images)
runweb python manage.py generate_preview_images $@
;;
@ -167,6 +170,7 @@ case "$CMD" in
echo " clean"
echo " black"
echo " populate_streams"
echo " populate_suggestions"
echo " generate_preview_images [--all]"
echo " copy_media_to_s3"
echo " set_cors_to_s3 [cors file]"

View file

@ -26,4 +26,5 @@ app.autodiscover_tasks(["bookwyrm"], related_name="emailing")
app.autodiscover_tasks(["bookwyrm"], related_name="goodreads_import")
app.autodiscover_tasks(["bookwyrm"], related_name="preview_images")
app.autodiscover_tasks(["bookwyrm"], related_name="models.user")
app.autodiscover_tasks(["bookwyrm"], related_name="suggested_users")
app.autodiscover_tasks(["bookwyrm"], related_name="views.inbox")