Merge branch 'main' into images-django-imagekit

This commit is contained in:
Joachim 2021-08-06 19:16:01 +02:00 committed by GitHub
commit 6a365eafb4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
119 changed files with 4113 additions and 2707 deletions

View file

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

View file

@ -4,11 +4,12 @@ from django.db.models import signals, Q
from bookwyrm import models from bookwyrm import models
from bookwyrm.redis_store import RedisStore, r from bookwyrm.redis_store import RedisStore, r
from bookwyrm.settings import STREAMS
from bookwyrm.views.helpers import privacy_filter from bookwyrm.views.helpers import privacy_filter
class ActivityStream(RedisStore): class ActivityStream(RedisStore):
"""a category of activity stream (like home, local, federated)""" """a category of activity stream (like home, local, books)"""
def stream_id(self, user): def stream_id(self, user):
"""the redis key for this user's instance of this stream""" """the redis key for this user's instance of this stream"""
@ -155,29 +156,94 @@ class LocalStream(ActivityStream):
) )
class FederatedStream(ActivityStream): class BooksStream(ActivityStream):
"""users you follow""" """books on your shelves"""
key = "federated" key = "books"
def get_audience(self, status): def get_audience(self, status):
# this stream wants no part in non-public statuses """anyone with the mentioned book on their shelves"""
if status.privacy != "public": # only show public statuses on the books feed,
# and only statuses that mention books
if status.privacy != "public" or not (
status.mention_books.exists() or hasattr(status, "book")
):
return [] return []
return super().get_audience(status)
work = (
status.book.parent_work
if hasattr(status, "book")
else status.mention_books.first().parent_work
)
audience = super().get_audience(status)
if not audience:
return []
return audience.filter(shelfbook__book__parent_work=work).distinct()
def get_statuses_for_user(self, user): def get_statuses_for_user(self, user):
"""any public status that mentions the user's books"""
books = user.shelfbook_set.values_list(
"book__parent_work__id", flat=True
).distinct()
return privacy_filter( return privacy_filter(
user, user,
models.Status.objects.select_subclasses(), models.Status.objects.select_subclasses()
.filter(
Q(comment__book__parent_work__id__in=books)
| Q(quotation__book__parent_work__id__in=books)
| Q(review__book__parent_work__id__in=books)
| Q(mention_books__parent_work__id__in=books)
)
.distinct(),
privacy_levels=["public"], privacy_levels=["public"],
) )
def add_book_statuses(self, user, book):
"""add statuses about a book to a user's feed"""
work = book.parent_work
statuses = privacy_filter(
user,
models.Status.objects.select_subclasses()
.filter(
Q(comment__book__parent_work=work)
| Q(quotation__book__parent_work=work)
| Q(review__book__parent_work=work)
| Q(mention_books__parent_work=work)
)
.distinct(),
privacy_levels=["public"],
)
self.bulk_add_objects_to_store(statuses, self.stream_id(user))
def remove_book_statuses(self, user, book):
"""add statuses about a book to a user's feed"""
work = book.parent_work
statuses = privacy_filter(
user,
models.Status.objects.select_subclasses()
.filter(
Q(comment__book__parent_work=work)
| Q(quotation__book__parent_work=work)
| Q(review__book__parent_work=work)
| Q(mention_books__parent_work=work)
)
.distinct(),
privacy_levels=["public"],
)
self.bulk_remove_objects_from_store(statuses, self.stream_id(user))
# determine which streams are enabled in settings.py
available_streams = [s["key"] for s in STREAMS]
streams = { streams = {
k: v
for (k, v) in {
"home": HomeStream(), "home": HomeStream(),
"local": LocalStream(), "local": LocalStream(),
"federated": FederatedStream(), "books": BooksStream(),
}.items()
if k in available_streams
} }
@ -197,7 +263,6 @@ def add_status_on_create(sender, instance, created, *args, **kwargs):
if not created: if not created:
return return
# iterates through Home, Local, Federated
for stream in streams.values(): for stream in streams.values():
stream.add_status(instance) stream.add_status(instance)
@ -264,7 +329,7 @@ def remove_statuses_on_block(sender, instance, *args, **kwargs):
# pylint: disable=unused-argument # pylint: disable=unused-argument
def add_statuses_on_unblock(sender, instance, *args, **kwargs): def add_statuses_on_unblock(sender, instance, *args, **kwargs):
"""remove statuses from all feeds on block""" """remove statuses from all feeds on block"""
public_streams = [LocalStream(), FederatedStream()] public_streams = [v for (k, v) in streams.items() if k != "home"]
# add statuses back to streams with statuses from anyone # add statuses back to streams with statuses from anyone
if instance.user_subject.local: if instance.user_subject.local:
for stream in public_streams: for stream in public_streams:
@ -285,3 +350,33 @@ def populate_streams_on_account_create(sender, instance, created, *args, **kwarg
for stream in streams.values(): for stream in streams.values():
stream.populate_streams(instance) stream.populate_streams(instance)
@receiver(signals.pre_save, sender=models.ShelfBook)
# pylint: disable=unused-argument
def add_statuses_on_shelve(sender, instance, *args, **kwargs):
"""update books stream when user shelves a book"""
if not instance.user.local:
return
# check if the book is already on the user's shelves
if models.ShelfBook.objects.filter(
user=instance.user, book__in=instance.book.parent_work.editions.all()
).exists():
return
BooksStream().add_book_statuses(instance.user, instance.book)
@receiver(signals.post_delete, sender=models.ShelfBook)
# pylint: disable=unused-argument
def remove_statuses_on_shelve(sender, instance, *args, **kwargs):
"""update books stream when user unshelves a book"""
if not instance.user.local:
return
# check if the book is actually unshelved, not just moved
if models.ShelfBook.objects.filter(
user=instance.user, book__in=instance.book.parent_work.editions.all()
).exists():
return
BooksStream().remove_book_statuses(instance.user, instance.book)

View file

@ -132,6 +132,7 @@ class EditUserForm(CustomForm):
"summary", "summary",
"show_goal", "show_goal",
"manually_approves_followers", "manually_approves_followers",
"default_post_privacy",
"discoverable", "discoverable",
"preferred_timezone", "preferred_timezone",
] ]

View file

@ -1,12 +1,6 @@
""" Re-create user streams """ """ Re-create user streams """
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
import redis from bookwyrm import activitystreams, models
from bookwyrm import activitystreams, models, settings
r = redis.Redis(
host=settings.REDIS_ACTIVITY_HOST, port=settings.REDIS_ACTIVITY_PORT, db=0
)
def populate_streams(): 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

@ -0,0 +1,27 @@
# Generated by Django 3.0.7 on 2021-02-14 00:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0045_auto_20210210_2114"),
]
operations = [
migrations.AddField(
model_name="user",
name="default_post_privacy",
field=models.CharField(
choices=[
("public", "Public"),
("unlisted", "Unlisted"),
("followers", "Followers"),
("direct", "Direct"),
],
default="public",
max_length=255,
),
),
]

View file

@ -0,0 +1,13 @@
# Generated by Django 3.2.4 on 2021-08-04 17:46
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0046_user_default_post_privacy"),
("bookwyrm", "0078_add_shelved_date"),
]
operations = []

View file

@ -0,0 +1,17 @@
# Generated by Django 3.2.4 on 2021-08-05 00:00
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0079_merge_20210804_1746"),
]
operations = [
migrations.AlterModelOptions(
name="shelfbook",
options={"ordering": ("-shelved_date", "-created_date", "-updated_date")},
),
]

View file

@ -0,0 +1,19 @@
# Generated by Django 3.2.4 on 2021-08-06 02:51
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0080_alter_shelfbook_options"),
]
operations = [
migrations.AlterField(
model_name="user",
name="last_active_date",
field=models.DateTimeField(default=django.utils.timezone.now),
),
]

View file

@ -30,7 +30,7 @@ class Favorite(ActivityMixin, BookWyrmModel):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
"""update user active time""" """update user active time"""
self.user.last_active_date = timezone.now() 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) super().save(*args, **kwargs)
if self.status.user.local and self.status.user != self.user: if self.status.user.local and self.status.user != self.user:

View file

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

View file

@ -30,7 +30,7 @@ class ReadThrough(BookWyrmModel):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
"""update user active time""" """update user active time"""
self.user.last_active_date = timezone.now() 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) super().save(*args, **kwargs)
def create_update(self): def create_update(self):
@ -55,5 +55,5 @@ class ProgressUpdate(BookWyrmModel):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
"""update user active time""" """update user active time"""
self.user.last_active_date = timezone.now() 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) super().save(*args, **kwargs)

View file

@ -7,7 +7,7 @@ from django.contrib.auth.models import AbstractUser, Group
from django.contrib.postgres.fields import CICharField from django.contrib.postgres.fields import CICharField
from django.core.validators import MinValueValidator from django.core.validators import MinValueValidator
from django.dispatch import receiver from django.dispatch import receiver
from django.db import models from django.db import models, transaction
from django.utils import timezone from django.utils import timezone
from model_utils import FieldTracker from model_utils import FieldTracker
import pytz import pytz
@ -105,10 +105,13 @@ class User(OrderedCollectionPageMixin, AbstractUser):
through_fields=("user", "status"), through_fields=("user", "status"),
related_name="favorite_statuses", related_name="favorite_statuses",
) )
default_post_privacy = models.CharField(
max_length=255, default="public", choices=fields.PrivacyLevels.choices
)
remote_id = fields.RemoteIdField(null=True, unique=True, activitypub_field="id") remote_id = fields.RemoteIdField(null=True, unique=True, activitypub_field="id")
created_date = models.DateTimeField(auto_now_add=True) created_date = models.DateTimeField(auto_now_add=True)
updated_date = models.DateTimeField(auto_now=True) updated_date = models.DateTimeField(auto_now=True)
last_active_date = models.DateTimeField(auto_now=True) last_active_date = models.DateTimeField(default=timezone.now)
manually_approves_followers = fields.BooleanField(default=False) manually_approves_followers = fields.BooleanField(default=False)
show_goal = models.BooleanField(default=True) show_goal = models.BooleanField(default=True)
discoverable = fields.BooleanField(default=False) discoverable = fields.BooleanField(default=False)
@ -243,7 +246,6 @@ class User(OrderedCollectionPageMixin, AbstractUser):
# generate a username that uses the domain (webfinger format) # generate a username that uses the domain (webfinger format)
actor_parts = urlparse(self.remote_id) actor_parts = urlparse(self.remote_id)
self.username = "%s@%s" % (self.username, actor_parts.netloc) self.username = "%s@%s" % (self.username, actor_parts.netloc)
super().save(*args, **kwargs)
# this user already exists, no need to populate fields # this user already exists, no need to populate fields
if not created: if not created:
@ -253,7 +255,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
# this is a new remote user, we need to set their remote server field # this is a new remote user, we need to set their remote server field
if not self.local: if not self.local:
super().save(*args, **kwargs) super().save(*args, **kwargs)
set_remote_server.delay(self.id) transaction.on_commit(lambda: set_remote_server.delay(self.id))
return return
# populate fields for local users # populate fields for local users
@ -276,7 +278,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
self.key_pair = KeyPair.objects.create( self.key_pair = KeyPair.objects.create(
remote_id="%s/#main-key" % self.remote_id remote_id="%s/#main-key" % self.remote_id
) )
self.save(broadcast=False) self.save(broadcast=False, update_fields=["key_pair"])
shelves = [ shelves = [
{ {
@ -406,7 +408,7 @@ def set_remote_server(user_id):
user = User.objects.get(id=user_id) user = User.objects.get(id=user_id)
actor_parts = urlparse(user.remote_id) actor_parts = urlparse(user.remote_id)
user.federated_server = get_or_create_remote_server(actor_parts.netloc) 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: if user.bookwyrm_user and user.outbox:
get_remote_reviews.delay(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)) save_without_broadcast = isinstance(instance, (models.Book, models.User))
if save_without_broadcast: if save_without_broadcast:
instance.save(broadcast=False) instance.save(broadcast=False, update_fields=["preview_image"])
else: else:
instance.save() instance.save(update_fields=["preview_image"])
# Clean up old file after saving # Clean up old file after saving
if old_path and default_storage.exists(old_path): if old_path and default_storage.exists(old_path):

View file

@ -50,15 +50,15 @@ class RedisStore(ABC):
pipeline.execute() pipeline.execute()
def bulk_remove_objects_from_store(self, objs, store): 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() pipeline = r.pipeline()
for obj in objs[: self.max_length]: for obj in objs[: self.max_length]:
pipeline.zrem(store, -1, obj.id) pipeline.zrem(store, -1, obj.id)
pipeline.execute() 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""" """load the values in a store"""
return r.zrevrange(store, 0, -1) return r.zrevrange(store, 0, -1, **kwargs)
def populate_store(self, store): def populate_store(self, store):
"""go from zero to a store""" """go from zero to a store"""

View file

@ -119,7 +119,11 @@ REDIS_ACTIVITY_PORT = env("REDIS_ACTIVITY_PORT", 6379)
REDIS_ACTIVITY_PASSWORD = env("REDIS_ACTIVITY_PASSWORD", None) REDIS_ACTIVITY_PASSWORD = env("REDIS_ACTIVITY_PASSWORD", None)
MAX_STREAM_LENGTH = int(env("MAX_STREAM_LENGTH", 200)) MAX_STREAM_LENGTH = int(env("MAX_STREAM_LENGTH", 200))
STREAMS = ["home", "local", "federated"]
STREAMS = [
{"key": "home", "name": _("Home Timeline"), "shortname": _("Home")},
{"key": "books", "name": _("Books Timeline"), "shortname": _("Books")},
]
# Database # Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases # https://docs.djangoproject.com/en/3.2/ref/settings/#databases

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

@ -11,7 +11,14 @@
</a> </a>
<div class="media-content"> <div class="media-content">
<a href="{{ user.local_path }}" class="is-block mb-2"> <a href="{{ user.local_path }}" class="is-block mb-2">
<span class="title is-4 is-block">{{ user.display_name }}</span> <span class="title is-4 is-block">
{{ user.display_name }}
{% if user.manually_approves_followers %}
<span class="icon icon-lock is-size-7" title="{% trans 'Locked account' %}">
<span class="is-sr-only">{% trans "Locked account" %}</span>
</span>
{% endif %}
</span>
<span class="subtitle is-7 is-block">@{{ user|username }}</span> <span class="subtitle is-7 is-block">@{{ user|username }}</span>
</a> </a>
{% include 'snippets/follow_button.html' with user=user %} {% include 'snippets/follow_button.html' with user=user %}

View file

@ -4,35 +4,25 @@
{% block panel %} {% block panel %}
<h1 class="title"> <h1 class="title">
{% if tab == 'home' %} {{ tab.name }}
{% trans "Home Timeline" %}
{% elif tab == 'local' %}
{% trans "Local Timeline" %}
{% else %}
{% trans "Federated Timeline" %}
{% endif %}
</h1> </h1>
<div class="tabs"> <div class="tabs">
<ul> <ul>
<li class="{% if tab == 'home' %}is-active{% endif %}"{% if tab == 'home' %} aria-current="page"{% endif %}> {% for stream in streams %}
<a href="/#feed">{% trans "Home" %}</a> <li class="{% if tab.key == stream.key %}is-active{% endif %}"{% if tab.key == stream.key %} aria-current="page"{% endif %}>
</li> <a href="/{{ stream.key }}#feed">{{ stream.shortname }}</a>
<li class="{% if tab == 'local' %}is-active{% endif %}"{% if tab == 'local' %} aria-current="page"{% endif %}>
<a href="/local#feed">{% trans "Local" %}</a>
</li>
<li class="{% if tab == 'federated' %}is-active{% endif %}"{% if tab == 'federated' %} aria-current="page"{% endif %}>
<a href="/federated#feed">{% trans "Federated" %}</a>
</li> </li>
{% endfor %}
</ul> </ul>
</div> </div>
{# announcements and system messages #} {# announcements and system messages #}
{% if not activities.number > 1 %} {% if not activities.number > 1 %}
<a href="{{ request.path }}" class="transition-y is-hidden notification is-primary is-block" data-poll-wrapper> <a href="{{ request.path }}" class="transition-y is-hidden notification is-primary is-block" data-poll-wrapper>
{% blocktrans %}load <span data-poll="stream/{{ tab }}">0</span> unread status(es){% endblocktrans %} {% blocktrans %}load <span data-poll="stream/{{ tab.key }}">0</span> unread status(es){% endblocktrans %}
</a> </a>
{% if request.user.show_goal and not goal and tab == 'home' %} {% if request.user.show_goal and not goal and tab.key == streams.first.key %}
{% now 'Y' as year %} {% now 'Y' as year %}
<section class="block"> <section class="block">
{% include 'snippets/goal_card.html' with year=year %} {% include 'snippets/goal_card.html' with year=year %}
@ -44,18 +34,22 @@
{# activity feed #} {# activity feed #}
{% if not activities %} {% 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 %} {% endif %}
{% for activity in activities %} {% for activity in activities %}
{% if not activities.number > 1 and forloop.counter0 == 2 and suggested_users %} {% if not activities.number > 1 and forloop.counter0 == 2 and suggested_users %}
{# suggested users on the first page, two statuses down #} {# suggested users on the first page, two statuses down #}
<section class="block"> {% include 'feed/suggested_users.html' with suggested_users=suggested_users %}
<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>
{% endif %} {% endif %}
<div class="block"> <div class="block">
{% include 'snippets/status/status.html' with status=activity %} {% include 'snippets/status/status.html' with status=activity %}

View file

@ -1,25 +1,6 @@
{% load i18n %} {% load i18n %}
{% load utilities %} <section class="block">
{% load humanize %} <h2 class="title is-5">{% trans "Who to follow" %}</h2>
<div class="columns is-mobile scroll-x mb-0"> {% include 'snippets/suggested_users.html' with suggested_users=suggested_users %}
{% for user in suggested_users %} <a class="help" href="{% url 'directory' %}">View directory <span class="icon icon-arrow-right"></a>
<div class="column is-flex is-flex-grow-0"> </section>
<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

@ -9,7 +9,7 @@
<form class="field has-addons" method="get" action="{% url 'get-started-users' %}"> <form class="field has-addons" method="get" action="{% url 'get-started-users' %}">
<div class="control"> <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' %}"> <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> <p class="help">{% blocktrans with query=request.GET.query %}No users found for "{{ query }}"{% endblocktrans %}</p>
{% endif %} {% endif %}
</div> </div>
@ -22,7 +22,7 @@
</div> </div>
</form> </form>
{% include 'feed/suggested_users.html' with suggested_users=suggested_users %} {% include 'snippets/suggested_users.html' with suggested_users=suggested_users %}
</div> </div>
{% endblock %} {% endblock %}

View file

@ -17,9 +17,9 @@
{% block panel %} {% block panel %}
<section class="block"> <section class="block">
{% if user == request.user %} {% now 'Y' as current_year %}
{% if user == request.user and year == current_year %}
<div class="block"> <div class="block">
{% now 'Y' as year %}
<section class="card {% if goal %}is-hidden{% endif %}" id="show-edit-goal"> <section class="card {% if goal %}is-hidden{% endif %}" id="show-edit-goal">
<header class="card-header"> <header class="card-header">
<h2 class="card-header-title has-background-primary has-text-white" tabindex="0" id="edit-form-header"> <h2 class="card-header-title has-background-primary has-text-white" tabindex="0" id="edit-form-header">

View file

@ -53,6 +53,14 @@
{{ form.manually_approves_followers }} {{ form.manually_approves_followers }}
</label> </label>
</div> </div>
<div class="block">
<label class="label" for="id_default_post_privacy">
{% trans "Default post privacy:" %}
</label>
<div class="select">
{{ form.default_post_privacy }}
</div>
</div>
<div class="block"> <div class="block">
<label class="checkbox label" for="id_discoverable"> <label class="checkbox label" for="id_discoverable">
{% trans "Show this account in suggested users:" %} {% trans "Show this account in suggested users:" %}

View file

@ -1,9 +1,16 @@
{% spaceless %} {% spaceless %}
{% load i18n %}
{% load humanize %}
{% comment %} {% comment %}
@todo The author property needs to be an Organization or a Person. Well be using Thing which is the more generic ancestor. @todo The author property needs to be an Organization or a Person. Well be using Thing which is the more generic ancestor.
@see https://schema.org/Author @see https://schema.org/Author
{% endcomment %} {% endcomment %}
{% for author in book.authors.all %} {% firstof limit None as limit %}
{% with subtraction_value='-'|add:limit %}
{% with remainder_count=book.authors.count|add:subtraction_value %}
{% with remainder_count_display=remainder_count|intcomma %}
{% for author in book.authors.all|slice:limit %}
<a <a
href="{{ author.local_path }}" href="{{ author.local_path }}"
class="author" class="author"
@ -12,6 +19,14 @@
itemtype="https://schema.org/Thing" itemtype="https://schema.org/Thing"
><span ><span
itemprop="name" itemprop="name"
>{{ author.name }}<span></a>{% if not forloop.last %}, {% endif %} >{{ author.name }}<span></a>{% if not forloop.last %}, {% elif remainder_count > 0 %}, {% blocktrans trimmed count counter=remainder_count %}
and {{ remainder_count_display }} other
{% plural %}
and {{ remainder_count_display }} others
{% endblocktrans %}{% endif %}
{% endfor %} {% endfor %}
{% endwith %}
{% endwith %}
{% endwith %}
{% endspaceless %} {% endspaceless %}

View file

@ -1,8 +1,15 @@
{% load i18n %} {% load i18n %}
{% load utilities %} {% load utilities %}
{% spaceless %}
{% if book.authors %} {% if book.authors %}
{% blocktrans with path=book.local_path title=book|book_title %}<a href="{{ path }}">{{ title }}</a> by {% endblocktrans %}{% include 'snippets/authors.html' with book=book %} {% blocktrans trimmed with path=book.local_path title=book|book_title %}
<a href="{{ path }}">{{ title }}</a> by
{% endblocktrans %}
{% include 'snippets/authors.html' with book=book limit=3 %}
{% else %} {% else %}
<a href="{{ book.local_path }}">{{ book|book_title }}</a> <a href="{{ book.local_path }}">{{ book|book_title }}</a>
{% endif %} {% endif %}
{% endspaceless %}

View file

@ -5,17 +5,18 @@
{% if not no_label %} {% if not no_label %}
<label class="is-sr-only" for="privacy-{{ uuid }}">{% trans "Post privacy" %}</label> <label class="is-sr-only" for="privacy-{{ uuid }}">{% trans "Post privacy" %}</label>
{% endif %} {% endif %}
{% firstof current user.default_post_privacy "public" as privacy %}
<select name="privacy" id="privacy-{{ uuid }}"> <select name="privacy" id="privacy-{{ uuid }}">
<option value="public" {% if not current or current == 'public' %}selected{% endif %}> <option value="public" {% if privacy == 'public' %}selected{% endif %}>
{% trans "Public" %} {% trans "Public" %}
</option> </option>
<option value="unlisted" {% if current == 'unlisted' %}selected{% endif %}> <option value="unlisted" {% if privacy == 'unlisted' %}selected{% endif %}>
{% trans "Unlisted" %} {% trans "Unlisted" %}
</option> </option>
<option value="followers" {% if current == 'followers' %}selected{% endif %}> <option value="followers" {% if privacy == 'followers' %}selected{% endif %}>
{% trans "Followers" %} {% trans "Followers" %}
</option> </option>
<option value="direct" {% if current == 'direct' %}selected{% endif %}> <option value="direct" {% if privacy == 'direct' %}selected{% endif %}>
{% trans "Private" %} {% trans "Private" %}
</option> </option>
</select> </select>

View file

@ -0,0 +1,38 @@
{% 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 trimmed 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 trimmed 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>
{% elif request.user in user.following.all %}
<p class="help">
{% trans "Follows you" %}
</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 %} {% blocktrans with username=user.display_name %}{{ username }} isn't following any users{% endblocktrans %}
</div> </div>
{% endblock %} {% endblock %}

View file

@ -10,7 +10,14 @@
</a> </a>
</div> </div>
<div class="media-content"> <div class="media-content">
<p>{% if user.name %}{{ user.name }}{% else %}{{ user.localname }}{% endif %}</p> <p>
{% if user.name %}{{ user.name }}{% else %}{{ user.localname }}{% endif %}
{% if user.manually_approves_followers %}
<span class="icon icon-lock is-size-7" title="{% trans 'Locked account' %}">
<span class="is-sr-only">{% trans "Locked account" %}</span>
</span>
{% endif %}
</p>
<p><a href="{{ user.remote_id }}">{{ user.username }}</a></p> <p><a href="{{ user.remote_id }}">{{ user.username }}</a></p>
<p>{% blocktrans with date=user.created_date|naturaltime %}Joined {{ date }}{% endblocktrans %}</p> <p>{% blocktrans with date=user.created_date|naturaltime %}Joined {{ date }}{% endblocktrans %}</p>
<p> <p>
@ -23,7 +30,13 @@
{% mutuals_count user as mutuals %} {% mutuals_count user as mutuals %}
<a href="{% url 'user-followers' user|username %}"> <a href="{% url 'user-followers' user|username %}">
{% if mutuals %}
{% blocktrans with mutuals_display=mutuals|intcomma count counter=mutuals %}{{ mutuals_display }} follower you follow{% plural %}{{ mutuals_display }} followers you follow{% endblocktrans %} {% blocktrans with mutuals_display=mutuals|intcomma count counter=mutuals %}{{ mutuals_display }} follower you follow{% plural %}{{ mutuals_display }} followers you follow{% endblocktrans %}
{% elif request.user in user.following.all %}
{% trans "Follows you" %}
{% else %}
{% trans "No followers you follow" %}
{% endif %}
</a> </a>
{% endif %} {% endif %}

View file

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

View file

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

View file

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

View file

@ -171,3 +171,15 @@ class Inventaire(TestCase):
} }
self.assertEqual(get_language_code(options), "there") self.assertEqual(get_language_code(options), "there")
self.assertIsNone(get_language_code({})) self.assertIsNone(get_language_code({}))
@responses.activate
def test_get_description(self):
"""extract a wikipedia excerpt"""
responses.add(
responses.GET,
"https://inventaire.io/api/data?action=wp-extract&lang=en&title=test_path",
json={"extract": "hi hi"},
)
extract = self.connector.get_description({"enwiki": "test_path"})
self.assertEqual(extract, "hi hi")

View file

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

View file

@ -16,9 +16,12 @@ from bookwyrm.settings import DOMAIN
def make_date(*args): def make_date(*args):
"""helper function to easily generate a date obj"""
return datetime.datetime(*args, tzinfo=pytz.UTC) return datetime.datetime(*args, tzinfo=pytz.UTC)
# pylint: disable=consider-using-with
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
class GoodreadsImport(TestCase): class GoodreadsImport(TestCase):
"""importing from goodreads csv""" """importing from goodreads csv"""
@ -27,6 +30,7 @@ class GoodreadsImport(TestCase):
self.importer = GoodreadsImporter() self.importer = GoodreadsImporter()
datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv") datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv")
self.csv = open(datafile, "r", encoding=self.importer.encoding) self.csv = open(datafile, "r", encoding=self.importer.encoding)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.user = models.User.objects.create_user( self.user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "password", local=True "mouse", "mouse@mouse.mouse", "password", local=True
) )
@ -49,7 +53,7 @@ class GoodreadsImport(TestCase):
parent_work=work, parent_work=work,
) )
def test_create_job(self): def test_create_job(self, _):
"""creates the import job entry and checks csv""" """creates the import job entry and checks csv"""
import_job = self.importer.create_job(self.user, self.csv, False, "public") import_job = self.importer.create_job(self.user, self.csv, False, "public")
self.assertEqual(import_job.user, self.user) 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].index, 2)
self.assertEqual(import_items[2].data["Book Id"], "28694510") 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""" """trying again with items that didn't import"""
import_job = self.importer.create_job(self.user, self.csv, False, "unlisted") import_job = self.importer.create_job(self.user, self.csv, False, "unlisted")
import_items = models.ImportItem.objects.filter(job=import_job).all()[:2] 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].index, 1)
self.assertEqual(retry_items[1].data["Book Id"], "52691223") self.assertEqual(retry_items[1].data["Book Id"], "52691223")
def test_start_import(self): def test_start_import(self, _):
"""begin loading books""" """begin loading books"""
import_job = self.importer.create_job(self.user, self.csv, False, "unlisted") import_job = self.importer.create_job(self.user, self.csv, False, "unlisted")
MockTask = namedtuple("Task", ("id")) MockTask = namedtuple("Task", ("id"))
@ -95,7 +99,7 @@ class GoodreadsImport(TestCase):
self.assertEqual(import_job.task_id, "7") self.assertEqual(import_job.task_id, "7")
@responses.activate @responses.activate
def test_import_data(self): def test_import_data(self, _):
"""resolve entry""" """resolve entry"""
import_job = self.importer.create_job(self.user, self.csv, False, "unlisted") import_job = self.importer.create_job(self.user, self.csv, False, "unlisted")
book = models.Edition.objects.create(title="Test Book") 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) import_item = models.ImportItem.objects.get(job=import_job, index=0)
self.assertEqual(import_item.book.id, book.id) 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""" """goodreads import added a book, this adds related connections"""
shelf = self.user.shelf_set.filter(identifier="read").first() shelf = self.user.shelf_set.filter(identifier="read").first()
self.assertIsNone(shelf.books.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.start_date, make_date(2020, 10, 21))
self.assertEqual(readthrough.finish_date, make_date(2020, 10, 25)) 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""" """goodreads import added a book, this adds related connections"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
shelf = self.user.shelf_set.filter(identifier="to-read").first() 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.start_date, make_date(2020, 10, 21))
self.assertEqual(readthrough.finish_date, make_date(2020, 10, 25)) 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""" """re-importing books"""
shelf = self.user.shelf_set.filter(identifier="read").first() shelf = self.user.shelf_set.filter(identifier="read").first()
import_job = models.ImportJob.objects.create(user=self.user) 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)) self.assertEqual(readthrough.finish_date, make_date(2020, 10, 25))
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.activitystreams.ActivityStream.add_status")
def test_handle_imported_book_review(self, _): def test_handle_imported_book_review(self, *_):
"""goodreads review import""" """goodreads review import"""
import_job = models.ImportJob.objects.create(user=self.user) import_job = models.ImportJob.objects.create(user=self.user)
datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv") datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv")
@ -234,7 +238,7 @@ class GoodreadsImport(TestCase):
self.assertEqual(review.privacy, "unlisted") self.assertEqual(review.privacy, "unlisted")
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.activitystreams.ActivityStream.add_status")
def test_handle_imported_book_rating(self, _): def test_handle_imported_book_rating(self, *_):
"""goodreads rating import""" """goodreads rating import"""
import_job = models.ImportJob.objects.create(user=self.user) import_job = models.ImportJob.objects.create(user=self.user)
datafile = pathlib.Path(__file__).parent.joinpath( 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.published_date, make_date(2019, 7, 8))
self.assertEqual(review.privacy, "unlisted") self.assertEqual(review.privacy, "unlisted")
def test_handle_imported_book_reviews_disabled(self): def test_handle_imported_book_reviews_disabled(self, _):
"""goodreads review import""" """goodreads review import"""
import_job = models.ImportJob.objects.create(user=self.user) import_job = models.ImportJob.objects.create(user=self.user)
datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv") 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) return datetime.datetime(*args, tzinfo=pytz.UTC)
# pylint: disable=consider-using-with
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
class LibrarythingImport(TestCase): class LibrarythingImport(TestCase):
"""importing from librarything tsv""" """importing from librarything tsv"""
@ -29,6 +31,7 @@ class LibrarythingImport(TestCase):
# Librarything generates latin encoded exports... # Librarything generates latin encoded exports...
self.csv = open(datafile, "r", encoding=self.importer.encoding) self.csv = open(datafile, "r", encoding=self.importer.encoding)
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.user = models.User.objects.create_user( self.user = models.User.objects.create_user(
"mmai", "mmai@mmai.mmai", "password", local=True "mmai", "mmai@mmai.mmai", "password", local=True
) )
@ -51,7 +54,7 @@ class LibrarythingImport(TestCase):
parent_work=work, parent_work=work,
) )
def test_create_job(self): def test_create_job(self, _):
"""creates the import job entry and checks csv""" """creates the import job entry and checks csv"""
import_job = self.importer.create_job(self.user, self.csv, False, "public") import_job = self.importer.create_job(self.user, self.csv, False, "public")
self.assertEqual(import_job.user, self.user) 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].index, 2)
self.assertEqual(import_items[2].data["Book Id"], "5015399") 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""" """trying again with items that didn't import"""
import_job = self.importer.create_job(self.user, self.csv, False, "unlisted") import_job = self.importer.create_job(self.user, self.csv, False, "unlisted")
import_items = models.ImportItem.objects.filter(job=import_job).all()[:2] 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") self.assertEqual(retry_items[1].data["Book Id"], "5015319")
@responses.activate @responses.activate
def test_import_data(self): def test_import_data(self, _):
"""resolve entry""" """resolve entry"""
import_job = self.importer.create_job(self.user, self.csv, False, "unlisted") import_job = self.importer.create_job(self.user, self.csv, False, "unlisted")
book = models.Edition.objects.create(title="Test Book") 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) import_item = models.ImportItem.objects.get(job=import_job, index=0)
self.assertEqual(import_item.book.id, book.id) 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""" """librarything import added a book, this adds related connections"""
shelf = self.user.shelf_set.filter(identifier="read").first() shelf = self.user.shelf_set.filter(identifier="read").first()
self.assertIsNone(shelf.books.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.start_date, make_date(2007, 4, 16))
self.assertEqual(readthrough.finish_date, make_date(2007, 5, 8)) 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""" """librarything import added a book, this adds related connections"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
shelf = self.user.shelf_set.filter(identifier="to-read").first() 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.start_date, make_date(2007, 4, 16))
self.assertEqual(readthrough.finish_date, make_date(2007, 5, 8)) 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""" """re-importing books"""
shelf = self.user.shelf_set.filter(identifier="read").first() shelf = self.user.shelf_set.filter(identifier="read").first()
import_job = models.ImportJob.objects.create(user=self.user) 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)) self.assertEqual(readthrough.finish_date, make_date(2007, 5, 8))
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.activitystreams.ActivityStream.add_status")
def test_handle_imported_book_review(self, _): def test_handle_imported_book_review(self, *_):
"""librarything review import""" """librarything review import"""
import_job = models.ImportJob.objects.create(user=self.user) import_job = models.ImportJob.objects.create(user=self.user)
datafile = pathlib.Path(__file__).parent.joinpath("../data/librarything.tsv") 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.published_date, make_date(2007, 5, 8))
self.assertEqual(review.privacy, "unlisted") self.assertEqual(review.privacy, "unlisted")
def test_handle_imported_book_reviews_disabled(self): def test_handle_imported_book_reviews_disabled(self, _):
"""librarything review import""" """librarything review import"""
import_job = models.ImportJob.objects.create(user=self.user) import_job = models.ImportJob.objects.create(user=self.user)
datafile = pathlib.Path(__file__).parent.joinpath("../data/librarything.tsv") datafile = pathlib.Path(__file__).parent.joinpath("../data/librarything.tsv")

View file

@ -12,6 +12,7 @@ class Activitystreams(TestCase):
def setUp(self): def setUp(self):
"""we need some stuff""" """we need some stuff"""
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "password", local=True, localname="mouse" "mouse", "mouse@mouse.mouse", "password", local=True, localname="mouse"
) )
@ -45,4 +46,4 @@ class Activitystreams(TestCase):
"bookwyrm.activitystreams.ActivityStream.populate_store" "bookwyrm.activitystreams.ActivityStream.populate_store"
) as redis_mock: ) as redis_mock:
populate_streams() populate_streams()
self.assertEqual(redis_mock.call_count, 6) # 2 users x 3 streams self.assertEqual(redis_mock.call_count, 4) # 2 users x 2 streams

View file

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

View file

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

View file

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

View file

@ -59,6 +59,7 @@ class ImportJob(TestCase):
unknown_read_data["Exclusive Shelf"] = "read" unknown_read_data["Exclusive Shelf"] = "read"
unknown_read_data["Date Read"] = "" unknown_read_data["Date Read"] = ""
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
user = models.User.objects.create_user( user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
) )

View file

@ -11,6 +11,7 @@ class List(TestCase):
def setUp(self): def setUp(self):
"""look, a list""" """look, a list"""
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
) )

View file

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

View file

@ -1,4 +1,5 @@
""" testing models """ """ testing models """
import json
from unittest.mock import patch from unittest.mock import patch
from django.test import TestCase from django.test import TestCase
@ -20,25 +21,21 @@ class Relationship(TestCase):
inbox="https://example.com/users/rat/inbox", inbox="https://example.com/users/rat/inbox",
outbox="https://example.com/users/rat/outbox", outbox="https://example.com/users/rat/outbox",
) )
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse" "mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse"
) )
self.local_user.remote_id = "http://local.com/user/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): def test_user_follows_from_request(self):
"""convert a follow request into a follow""" """convert a follow request into a follow"""
real_broadcast = models.UserFollowRequest.broadcast with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
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( request = models.UserFollowRequest.objects.create(
user_subject=self.local_user, user_object=self.remote_user 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( self.assertEqual(
request.remote_id, "http://local.com/user/mouse#follows/%d" % request.id 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.status, "follows")
self.assertEqual(rel.user_subject, self.local_user) self.assertEqual(rel.user_subject, self.local_user)
self.assertEqual(rel.user_object, self.remote_user) self.assertEqual(rel.user_object, self.remote_user)
models.UserFollowRequest.broadcast = real_broadcast
def test_user_follows_from_request_custom_remote_id(self): def test_user_follows_from_request_custom_remote_id(self):
"""store a specific remote id for a relationship provided by remote""" """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_subject, self.local_user)
self.assertEqual(rel.user_object, self.remote_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""" """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( models.UserFollowRequest.objects.create(
user_subject=self.local_user, user_subject=self.local_user,
user_object=self.remote_user, user_object=self.remote_user,
) )
models.UserFollowRequest.broadcast = real_broadcast activity = json.loads(broadcast_mock.call_args[0][1])
def test_follow_request_accept(self):
"""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["actor"], self.local_user.remote_id)
self.assertEqual(activity["object"]["id"], "https://www.hi.com/") self.assertEqual(activity["object"], self.remote_user.remote_id)
self.assertEqual(activity["type"], "Follow")
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
def test_follow_request_accept(self, broadcast_mock):
"""accept a request and make it a relationship"""
self.local_user.manually_approves_followers = True self.local_user.manually_approves_followers = True
self.local_user.save(broadcast=False) self.local_user.save(
models.UserFollowRequest.broadcast = mock_broadcast broadcast=False, update_fields=["manually_approves_followers"]
)
request = models.UserFollowRequest.objects.create( request = models.UserFollowRequest.objects.create(
user_subject=self.remote_user, user_subject=self.remote_user,
user_object=self.local_user, user_object=self.local_user,
@ -107,32 +93,34 @@ class Relationship(TestCase):
) )
request.accept() 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.assertFalse(models.UserFollowRequest.objects.exists())
self.assertTrue(models.UserFollows.objects.exists()) self.assertTrue(models.UserFollows.objects.exists())
rel = models.UserFollows.objects.get() rel = models.UserFollows.objects.get()
self.assertEqual(rel.user_subject, self.remote_user) self.assertEqual(rel.user_subject, self.remote_user)
self.assertEqual(rel.user_object, self.local_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""" """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.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( request = models.UserFollowRequest.objects.create(
user_subject=self.remote_user, user_subject=self.remote_user,
user_object=self.local_user, user_object=self.local_user,
) )
request.reject() 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.UserFollowRequest.objects.exists())
self.assertFalse(models.UserFollows.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 # pylint: disable=unused-argument
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
class Shelf(TestCase): class Shelf(TestCase):
"""some activitypub oddness ahead""" """some activitypub oddness ahead"""
def setUp(self): def setUp(self):
"""look, a shelf""" """look, a shelf"""
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
) )
work = models.Work.objects.create(title="Test Work") work = models.Work.objects.create(title="Test Work")
self.book = models.Edition.objects.create(title="test book", parent_work=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""" """shelves use custom remote ids"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
shelf = models.Shelf.objects.create( shelf = models.Shelf.objects.create(
@ -27,7 +29,7 @@ class Shelf(TestCase):
expected_id = "https://%s/user/mouse/books/test-shelf" % settings.DOMAIN expected_id = "https://%s/user/mouse/books/test-shelf" % settings.DOMAIN
self.assertEqual(shelf.get_remote_id(), expected_id) self.assertEqual(shelf.get_remote_id(), expected_id)
def test_to_activity(self): def test_to_activity(self, _):
"""jsonify it""" """jsonify it"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
shelf = models.Shelf.objects.create( shelf = models.Shelf.objects.create(
@ -41,7 +43,7 @@ class Shelf(TestCase):
self.assertEqual(activity_json["name"], "Test Shelf") self.assertEqual(activity_json["name"], "Test Shelf")
self.assertEqual(activity_json["owner"], self.local_user.remote_id) 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""" """create and broadcast shelf creation"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock: 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(activity["object"]["name"], "arthur russel")
self.assertEqual(shelf.name, "arthur russel") self.assertEqual(shelf.name, "arthur russel")
def test_shelve(self): def test_shelve(self, _):
"""create and broadcast shelf creation""" """create and broadcast shelf creation"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
shelf = models.Shelf.objects.create( shelf = models.Shelf.objects.create(

View file

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

View file

@ -11,6 +11,7 @@ from bookwyrm.settings import DOMAIN
# pylint: disable=missing-function-docstring # pylint: disable=missing-function-docstring
class User(TestCase): class User(TestCase):
def setUp(self): def setUp(self):
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.user = models.User.objects.create_user( self.user = models.User.objects.create_user(
"mouse@%s" % DOMAIN, "mouse@%s" % DOMAIN,
"mouse@mouse.mouse", "mouse@mouse.mouse",
@ -154,7 +155,8 @@ class User(TestCase):
self.assertIsNone(server.application_type) self.assertIsNone(server.application_type)
self.assertIsNone(server.application_version) 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""" """deactivate a user"""
self.assertTrue(self.user.is_active) self.assertTrue(self.user.is_active)
with patch( with patch(

View file

@ -6,11 +6,14 @@ from bookwyrm import activitystreams, models
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") @patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.activitystreams.ActivityStream.add_status")
@patch("bookwyrm.activitystreams.BooksStream.add_book_statuses")
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
class Activitystreams(TestCase): class Activitystreams(TestCase):
"""using redis to build activity streams""" """using redis to build activity streams"""
def setUp(self): def setUp(self):
"""use a test csv""" """use a test csv"""
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "password", local=True, localname="mouse" "mouse", "mouse@mouse.mouse", "password", local=True, localname="mouse"
) )
@ -31,7 +34,8 @@ class Activitystreams(TestCase):
inbox="https://example.com/users/rat/inbox", inbox="https://example.com/users/rat/inbox",
outbox="https://example.com/users/rat/outbox", outbox="https://example.com/users/rat/outbox",
) )
self.book = models.Edition.objects.create(title="test book") work = models.Work.objects.create(title="test work")
self.book = models.Edition.objects.create(title="test book", parent_work=work)
class TestStream(activitystreams.ActivityStream): class TestStream(activitystreams.ActivityStream):
"""test stream, don't have to do anything here""" """test stream, don't have to do anything here"""
@ -190,19 +194,95 @@ class Activitystreams(TestCase):
users = activitystreams.LocalStream().get_audience(status) users = activitystreams.LocalStream().get_audience(status)
self.assertEqual(users, []) self.assertEqual(users, [])
def test_federatedstream_get_audience(self, *_): def test_localstream_get_audience_books_no_book(self, *_):
"""get a list of users that should see a status""" """get a list of users that should see a status"""
status = models.Status.objects.create( status = models.Status.objects.create(
user=self.remote_user, content="hi", privacy="public" user=self.local_user, content="hi", privacy="public"
) )
users = activitystreams.FederatedStream().get_audience(status) audience = activitystreams.BooksStream().get_audience(status)
self.assertTrue(self.local_user in users) # no books, no audience
self.assertTrue(self.another_user in users) self.assertEqual(audience, [])
def test_federatedstream_get_audience_unlisted(self, *_): def test_localstream_get_audience_books_mention_books(self, *_):
"""get a list of users that should see a status""" """get a list of users that should see a status"""
status = models.Status.objects.create( status = models.Status.objects.create(
user=self.remote_user, content="hi", privacy="unlisted" user=self.local_user, content="hi", privacy="public"
) )
users = activitystreams.FederatedStream().get_audience(status) status.mention_books.add(self.book)
self.assertEqual(users, []) status.save(broadcast=False)
models.ShelfBook.objects.create(
user=self.local_user,
shelf=self.local_user.shelf_set.first(),
book=self.book,
)
# yes book, yes audience
audience = activitystreams.BooksStream().get_audience(status)
self.assertTrue(self.local_user in audience)
def test_localstream_get_audience_books_book_field(self, *_):
"""get a list of users that should see a status"""
status = models.Comment.objects.create(
user=self.local_user, content="hi", privacy="public", book=self.book
)
models.ShelfBook.objects.create(
user=self.local_user,
shelf=self.local_user.shelf_set.first(),
book=self.book,
)
# yes book, yes audience
audience = activitystreams.BooksStream().get_audience(status)
self.assertTrue(self.local_user in audience)
def test_localstream_get_audience_books_alternate_edition(self, *_):
"""get a list of users that should see a status"""
alt_book = models.Edition.objects.create(
title="hi", parent_work=self.book.parent_work
)
status = models.Comment.objects.create(
user=self.remote_user, content="hi", privacy="public", book=alt_book
)
models.ShelfBook.objects.create(
user=self.local_user,
shelf=self.local_user.shelf_set.first(),
book=self.book,
)
# yes book, yes audience
audience = activitystreams.BooksStream().get_audience(status)
self.assertTrue(self.local_user in audience)
def test_localstream_get_audience_books_non_public(self, *_):
"""get a list of users that should see a status"""
alt_book = models.Edition.objects.create(
title="hi", parent_work=self.book.parent_work
)
status = models.Comment.objects.create(
user=self.remote_user, content="hi", privacy="unlisted", book=alt_book
)
models.ShelfBook.objects.create(
user=self.local_user,
shelf=self.local_user.shelf_set.first(),
book=self.book,
)
# yes book, yes audience
audience = activitystreams.BooksStream().get_audience(status)
self.assertEqual(audience, [])
def test_get_statuses_for_user_books(self, *_):
"""create a stream for a user"""
alt_book = models.Edition.objects.create(
title="hi", parent_work=self.book.parent_work
)
status = models.Status.objects.create(
user=self.local_user, content="hi", privacy="public"
)
status = models.Comment.objects.create(
user=self.remote_user, content="hi", privacy="public", book=alt_book
)
models.ShelfBook.objects.create(
user=self.local_user,
shelf=self.local_user.shelf_set.first(),
book=self.book,
)
# yes book, yes audience
result = activitystreams.BooksStream().get_statuses_for_user(self.local_user)
self.assertEqual(list(result), [status])

View file

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

View file

@ -1,5 +1,6 @@
""" test generating preview images """ """ test generating preview images """
import pathlib import pathlib
from unittest.mock import patch
from PIL import Image from PIL import Image
from django.test import TestCase 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 django.db.models.fields.files import ImageFieldFile
from bookwyrm import models, settings from bookwyrm import models, settings
from bookwyrm.preview_images import ( from bookwyrm.preview_images import (
generate_site_preview_image_task, generate_site_preview_image_task,
generate_edition_preview_image_task, generate_edition_preview_image_task,
@ -29,6 +29,7 @@ class PreviewImages(TestCase):
avatar_file = pathlib.Path(__file__).parent.joinpath( avatar_file = pathlib.Path(__file__).parent.joinpath(
"../static/images/no_cover.jpg" "../static/images/no_cover.jpg"
) )
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"possum@local.com", "possum@local.com",
"possum@possum.possum", "possum@possum.possum",

View file

@ -37,6 +37,7 @@ class Signature(TestCase):
def setUp(self): def setUp(self):
"""create users and test data""" """create users and test data"""
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.mouse = models.User.objects.create_user( self.mouse = models.User.objects.create_user(
"mouse@%s" % DOMAIN, "mouse@%s" % DOMAIN,
"mouse@example.com", "mouse@example.com",

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) # 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,6 +22,7 @@ class TemplateTags(TestCase):
def setUp(self): def setUp(self):
"""create some filler objects""" """create some filler objects"""
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.user = models.User.objects.create_user( self.user = models.User.objects.create_user(
"mouse@example.com", "mouse@example.com",
"mouse@mouse.mouse", "mouse@mouse.mouse",

View file

@ -19,6 +19,7 @@ class Inbox(TestCase):
self.client = Client() self.client = Client()
self.factory = RequestFactory() self.factory = RequestFactory()
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
local_user = models.User.objects.create_user( local_user = models.User.objects.create_user(
"mouse@example.com", "mouse@example.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -27,7 +28,7 @@ class Inbox(TestCase):
localname="mouse", localname="mouse",
) )
local_user.remote_id = "https://example.com/user/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"): with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user( self.remote_user = models.User.objects.create_user(
"rat", "rat",
@ -144,7 +145,8 @@ class Inbox(TestCase):
) )
self.assertTrue(views.inbox.is_blocked_activity(activity)) 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""" """don't let deactivated users post"""
self.remote_user.delete(broadcast=False) self.remote_user.delete(broadcast=False)
self.assertTrue(self.remote_user.deleted) self.assertTrue(self.remote_user.deleted)

View file

@ -13,6 +13,7 @@ class InboxAdd(TestCase):
def setUp(self): def setUp(self):
"""basic user and book data""" """basic user and book data"""
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
local_user = models.User.objects.create_user( local_user = models.User.objects.create_user(
"mouse@example.com", "mouse@example.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -21,7 +22,7 @@ class InboxAdd(TestCase):
localname="mouse", localname="mouse",
) )
local_user.remote_id = "https://example.com/user/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"): with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user( self.remote_user = models.User.objects.create_user(
"rat", "rat",

View file

@ -13,6 +13,7 @@ class InboxActivities(TestCase):
def setUp(self): def setUp(self):
"""basic user and book data""" """basic user and book data"""
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@example.com", "mouse@example.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -21,7 +22,7 @@ class InboxActivities(TestCase):
localname="mouse", localname="mouse",
) )
self.local_user.remote_id = "https://example.com/user/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"): with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user( self.remote_user = models.User.objects.create_user(
"rat", "rat",

View file

@ -12,6 +12,7 @@ class InboxBlock(TestCase):
def setUp(self): def setUp(self):
"""basic user and book data""" """basic user and book data"""
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@example.com", "mouse@example.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -20,7 +21,7 @@ class InboxBlock(TestCase):
localname="mouse", localname="mouse",
) )
self.local_user.remote_id = "https://example.com/user/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"): with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user( self.remote_user = models.User.objects.create_user(
"rat", "rat",

View file

@ -16,6 +16,7 @@ class InboxCreate(TestCase):
def setUp(self): def setUp(self):
"""basic user and book data""" """basic user and book data"""
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@example.com", "mouse@example.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -24,7 +25,7 @@ class InboxCreate(TestCase):
localname="mouse", localname="mouse",
) )
self.local_user.remote_id = "https://example.com/user/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"): with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user( self.remote_user = models.User.objects.create_user(
"rat", "rat",

View file

@ -13,6 +13,7 @@ class InboxActivities(TestCase):
def setUp(self): def setUp(self):
"""basic user and book data""" """basic user and book data"""
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@example.com", "mouse@example.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -21,7 +22,7 @@ class InboxActivities(TestCase):
localname="mouse", localname="mouse",
) )
self.local_user.remote_id = "https://example.com/user/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"): with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user( self.remote_user = models.User.objects.create_user(
"rat", "rat",
@ -105,7 +106,8 @@ class InboxActivities(TestCase):
self.assertEqual(models.Notification.objects.count(), 1) self.assertEqual(models.Notification.objects.count(), 1)
self.assertEqual(models.Notification.objects.get(), notif) 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""" """delete a user"""
self.assertTrue(models.User.objects.get(username="rat@example.com").is_active) self.assertTrue(models.User.objects.get(username="rat@example.com").is_active)
activity = { activity = {

View file

@ -13,6 +13,7 @@ class InboxRelationships(TestCase):
def setUp(self): def setUp(self):
"""basic user and book data""" """basic user and book data"""
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@example.com", "mouse@example.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -21,7 +22,7 @@ class InboxRelationships(TestCase):
localname="mouse", localname="mouse",
) )
self.local_user.remote_id = "https://example.com/user/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"): with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user( self.remote_user = models.User.objects.create_user(
"rat", "rat",
@ -102,7 +103,9 @@ class InboxRelationships(TestCase):
} }
self.local_user.manually_approves_followers = True 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"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
views.inbox.activity_task(activity) views.inbox.activity_task(activity)
@ -124,7 +127,9 @@ class InboxRelationships(TestCase):
def test_undo_follow_request(self): def test_undo_follow_request(self):
"""the requester cancels a follow request""" """the requester cancels a follow request"""
self.local_user.manually_approves_followers = True 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"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
request = models.UserFollowRequest.objects.create( request = models.UserFollowRequest.objects.create(
user_subject=self.remote_user, user_object=self.local_user user_subject=self.remote_user, user_object=self.local_user

View file

@ -12,6 +12,7 @@ class InboxActivities(TestCase):
def setUp(self): def setUp(self):
"""basic user and book data""" """basic user and book data"""
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@example.com", "mouse@example.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -20,7 +21,7 @@ class InboxActivities(TestCase):
localname="mouse", localname="mouse",
) )
self.local_user.remote_id = "https://example.com/user/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"): with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user( self.remote_user = models.User.objects.create_user(
"rat", "rat",

View file

@ -12,6 +12,7 @@ class InboxRemove(TestCase):
def setUp(self): def setUp(self):
"""basic user and book data""" """basic user and book data"""
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@example.com", "mouse@example.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -20,7 +21,7 @@ class InboxRemove(TestCase):
localname="mouse", localname="mouse",
) )
self.local_user.remote_id = "https://example.com/user/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"): with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user( self.remote_user = models.User.objects.create_user(
"rat", "rat",

View file

@ -14,6 +14,7 @@ class InboxUpdate(TestCase):
def setUp(self): def setUp(self):
"""basic user and book data""" """basic user and book data"""
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@example.com", "mouse@example.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -22,7 +23,7 @@ class InboxUpdate(TestCase):
localname="mouse", localname="mouse",
) )
self.local_user.remote_id = "https://example.com/user/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"): with patch("bookwyrm.models.user.set_remote_server.delay"):
self.remote_user = models.User.objects.create_user( self.remote_user = models.User.objects.create_user(
"rat", "rat",
@ -79,7 +80,8 @@ class InboxUpdate(TestCase):
self.assertEqual(book_list.description, "summary text") self.assertEqual(book_list.description, "summary text")
self.assertEqual(book_list.remote_id, "https://example.com/list/22") 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""" """update an existing user"""
models.UserFollows.objects.create( models.UserFollows.objects.create(
user_subject=self.local_user, user_subject=self.local_user,

View file

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

View file

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

View file

@ -17,6 +17,7 @@ class AuthorViews(TestCase):
def setUp(self): def setUp(self):
"""we need basic test data and mocks""" """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
"mouse@mouse.com", "mouse@mouse.com",

View file

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

View file

@ -23,6 +23,7 @@ class BookViews(TestCase):
def setUp(self): def setUp(self):
"""we need basic test data and mocks""" """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -200,7 +201,8 @@ class BookViews(TestCase):
self.assertEqual(book.authors.first().name, "Sappho") self.assertEqual(book.authors.first().name, "Sappho")
self.assertEqual(book.authors.first(), book.parent_work.authors.first()) 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""" """updates user's relationships to a book"""
work = models.Work.objects.create(title="test work") work = models.Work.objects.create(title="test work")
edition1 = models.Edition.objects.create(title="first ed", parent_work=work) edition1 = models.Edition.objects.create(title="first ed", parent_work=work)
@ -297,3 +299,16 @@ class BookViews(TestCase):
self.book.refresh_from_db() self.book.refresh_from_db()
self.assertTrue(self.book.cover) self.assertTrue(self.book.cover)
def test_add_description(self):
"""add a book description"""
self.local_user.groups.add(self.group)
request = self.factory.post("", {"description": "new description hi"})
request.user = self.local_user
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
views.add_description(request, self.book.id)
self.book.refresh_from_db()
self.assertEqual(self.book.description, "new description hi")
self.assertEqual(self.book.last_edited_by, self.local_user)

View file

@ -1,4 +1,7 @@
""" test for app action functionality """ """ 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.template.response import TemplateResponse
from django.test import TestCase from django.test import TestCase
from django.test.client import RequestFactory from django.test.client import RequestFactory
@ -12,6 +15,7 @@ class DirectoryViews(TestCase):
def setUp(self): def setUp(self):
"""we need basic test data and mocks""" """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -20,7 +24,16 @@ class DirectoryViews(TestCase):
localname="mouse", localname="mouse",
remote_id="https://example.com/users/mouse", remote_id="https://example.com/users/mouse",
) )
self.rat = models.User.objects.create_user(
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@local.com",
"rat@rat.com", "rat@rat.com",
"ratword", "ratword",
@ -29,10 +42,16 @@ class DirectoryViews(TestCase):
remote_id="https://example.com/users/rat", remote_id="https://example.com/users/rat",
discoverable=True, 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""" """there are so many views, this just makes sure it LOADS"""
view = views.Directory.as_view() view = views.Directory.as_view()
request = self.factory.get("") request = self.factory.get("")
@ -42,3 +61,12 @@ class DirectoryViews(TestCase):
self.assertIsInstance(result, TemplateResponse) self.assertIsInstance(result, TemplateResponse)
result.render() result.render()
self.assertEqual(result.status_code, 200) 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,12 +15,14 @@ from django.test.client import RequestFactory
from bookwyrm import forms, models, views from bookwyrm import forms, models, views
@patch("bookwyrm.suggested_users.remove_user_task.delay")
class EditUserViews(TestCase): class EditUserViews(TestCase):
"""view user and edit profile""" """view user and edit profile"""
def setUp(self): def setUp(self):
"""we need basic test data and mocks""" """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
"mouse@mouse.mouse", "mouse@mouse.mouse",
@ -32,7 +34,9 @@ class EditUserViews(TestCase):
"rat@local.com", "rat@rat.rat", "password", local=True, localname="rat" "rat@local.com", "rat@rat.rat", "password", local=True, localname="rat"
) )
self.book = models.Edition.objects.create(title="test") self.book = models.Edition.objects.create(
title="test", parent_work=models.Work.objects.create(title="test work")
)
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
models.ShelfBook.objects.create( models.ShelfBook.objects.create(
book=self.book, book=self.book,
@ -44,7 +48,7 @@ class EditUserViews(TestCase):
self.anonymous_user = AnonymousUser self.anonymous_user = AnonymousUser
self.anonymous_user.is_authenticated = False 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""" """there are so many views, this just makes sure it LOADS"""
view = views.EditUser.as_view() view = views.EditUser.as_view()
request = self.factory.get("") request = self.factory.get("")
@ -54,12 +58,13 @@ class EditUserViews(TestCase):
result.render() result.render()
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_edit_user(self): def test_edit_user(self, _):
"""use a form to update a user""" """use a form to update a user"""
view = views.EditUser.as_view() view = views.EditUser.as_view()
form = forms.EditUserForm(instance=self.local_user) form = forms.EditUserForm(instance=self.local_user)
form.data["name"] = "New Name" form.data["name"] = "New Name"
form.data["email"] = "wow@email.com" form.data["email"] = "wow@email.com"
form.data["default_post_privacy"] = "public"
form.data["preferred_timezone"] = "UTC" form.data["preferred_timezone"] = "UTC"
request = self.factory.post("", form.data) request = self.factory.post("", form.data)
request.user = self.local_user request.user = self.local_user
@ -73,12 +78,13 @@ class EditUserViews(TestCase):
self.assertEqual(self.local_user.name, "New Name") self.assertEqual(self.local_user.name, "New Name")
self.assertEqual(self.local_user.email, "wow@email.com") 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""" """use a form to update a user"""
view = views.EditUser.as_view() view = views.EditUser.as_view()
form = forms.EditUserForm(instance=self.local_user) form = forms.EditUserForm(instance=self.local_user)
form.data["name"] = "New Name" form.data["name"] = "New Name"
form.data["email"] = "wow@email.com" form.data["email"] = "wow@email.com"
form.data["default_post_privacy"] = "public"
form.data["preferred_timezone"] = "UTC" form.data["preferred_timezone"] = "UTC"
image_file = pathlib.Path(__file__).parent.joinpath( image_file = pathlib.Path(__file__).parent.joinpath(
"../../static/images/no_cover.jpg" "../../static/images/no_cover.jpg"
@ -100,7 +106,7 @@ class EditUserViews(TestCase):
self.assertEqual(self.local_user.avatar.width, 120) self.assertEqual(self.local_user.avatar.width, 120)
self.assertEqual(self.local_user.avatar.height, 120) self.assertEqual(self.local_user.avatar.height, 120)
def test_crop_avatar(self): def test_crop_avatar(self, _):
"""reduce that image size""" """reduce that image size"""
image_file = pathlib.Path(__file__).parent.joinpath( image_file = pathlib.Path(__file__).parent.joinpath(
"../../static/images/no_cover.jpg" "../../static/images/no_cover.jpg"
@ -112,7 +118,7 @@ class EditUserViews(TestCase):
image_result = Image.open(result) image_result = Image.open(result)
self.assertEqual(image_result.size, (120, 120)) 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""" """there are so many views, this just makes sure it LOADS"""
view = views.DeleteUser.as_view() view = views.DeleteUser.as_view()
request = self.factory.get("") request = self.factory.get("")
@ -122,7 +128,8 @@ class EditUserViews(TestCase):
result.render() result.render()
self.assertEqual(result.status_code, 200) 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""" """use a form to update a user"""
view = views.DeleteUser.as_view() view = views.DeleteUser.as_view()
form = forms.DeleteUserForm() form = forms.DeleteUserForm()

View file

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

View file

@ -16,12 +16,15 @@ from bookwyrm.activitypub import ActivitypubResponse
@patch("bookwyrm.activitystreams.ActivityStream.get_activity_stream") @patch("bookwyrm.activitystreams.ActivityStream.get_activity_stream")
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @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): class FeedViews(TestCase):
"""activity feed, statuses, dms""" """activity feed, statuses, dms"""
def setUp(self): def setUp(self):
"""we need basic test data and mocks""" """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
"mouse@mouse.mouse", "mouse@mouse.mouse",
@ -36,12 +39,13 @@ class FeedViews(TestCase):
) )
models.SiteSettings.objects.create() models.SiteSettings.objects.create()
@patch("bookwyrm.suggested_users.SuggestedUsers.get_suggestions")
def test_feed(self, *_): def test_feed(self, *_):
"""there are so many views, this just makes sure it LOADS""" """there are so many views, this just makes sure it LOADS"""
view = views.Feed.as_view() view = views.Feed.as_view()
request = self.factory.get("") request = self.factory.get("")
request.user = self.local_user request.user = self.local_user
result = view(request, "local") result = view(request, "home")
self.assertIsInstance(result, TemplateResponse) self.assertIsInstance(result, TemplateResponse)
result.render() result.render()
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)

View file

@ -10,12 +10,13 @@ from django.test.client import RequestFactory
from bookwyrm import models, views from bookwyrm import models, views
class BookViews(TestCase): class FollowViews(TestCase):
"""books books books""" """follows"""
def setUp(self): def setUp(self):
"""we need basic test data and mocks""" """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -66,6 +67,7 @@ class BookViews(TestCase):
def test_handle_follow_local_manually_approves(self): def test_handle_follow_local_manually_approves(self):
"""send a follow request""" """send a follow request"""
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
rat = models.User.objects.create_user( rat = models.User.objects.create_user(
"rat@local.com", "rat@local.com",
"rat@rat.com", "rat@rat.com",
@ -89,6 +91,7 @@ class BookViews(TestCase):
def test_handle_follow_local(self): def test_handle_follow_local(self):
"""send a follow request""" """send a follow request"""
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
rat = models.User.objects.create_user( rat = models.User.objects.create_user(
"rat@local.com", "rat@local.com",
"rat@rat.com", "rat@rat.com",
@ -127,7 +130,9 @@ class BookViews(TestCase):
def test_handle_accept(self): def test_handle_accept(self):
"""accept a follow request""" """accept a follow request"""
self.local_user.manually_approves_followers = True 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 = self.factory.post("", {"user": self.remote_user.username})
request.user = self.local_user request.user = self.local_user
rel = models.UserFollowRequest.objects.create( rel = models.UserFollowRequest.objects.create(
@ -144,7 +149,9 @@ class BookViews(TestCase):
def test_handle_reject(self): def test_handle_reject(self):
"""reject a follow request""" """reject a follow request"""
self.local_user.manually_approves_followers = True 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 = self.factory.post("", {"user": self.remote_user.username})
request.user = self.local_user request.user = self.local_user
rel = models.UserFollowRequest.objects.create( rel = models.UserFollowRequest.objects.create(

View file

@ -13,6 +13,7 @@ class GetStartedViews(TestCase):
def setUp(self): def setUp(self):
"""we need basic test data and mocks""" """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
"mouse@mouse.mouse", "mouse@mouse.mouse",
@ -42,7 +43,9 @@ class GetStartedViews(TestCase):
result.render() result.render()
self.assertEqual(result.status_code, 200) 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""" """save basic user details"""
view = views.GetStartedProfile.as_view() view = views.GetStartedProfile.as_view()
form = forms.LimitedEditUserForm(instance=self.local_user) form = forms.LimitedEditUserForm(instance=self.local_user)
@ -84,7 +87,8 @@ class GetStartedViews(TestCase):
result.render() result.render()
self.assertEqual(result.status_code, 200) 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""" """shelve some books"""
view = views.GetStartedBooks.as_view() view = views.GetStartedBooks.as_view()
data = {self.book.id: self.local_user.shelf_set.first().id} 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.book, self.book)
self.assertEqual(shelfbook.user, self.local_user) 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""" """there are so many views, this just makes sure it LOADS"""
view = views.GetStartedUsers.as_view() view = views.GetStartedUsers.as_view()
request = self.factory.get("") request = self.factory.get("")
@ -114,7 +119,8 @@ class GetStartedViews(TestCase):
result.render() result.render()
self.assertEqual(result.status_code, 200) 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""" """there are so many views, this just makes sure it LOADS"""
view = views.GetStartedUsers.as_view() view = views.GetStartedUsers.as_view()
request = self.factory.get("?query=rat") request = self.factory.get("?query=rat")

View file

@ -16,6 +16,7 @@ class GoalViews(TestCase):
def setUp(self): def setUp(self):
"""we need basic test data and mocks""" """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -38,6 +39,7 @@ class GoalViews(TestCase):
) )
self.anonymous_user = AnonymousUser self.anonymous_user = AnonymousUser
self.anonymous_user.is_authenticated = False self.anonymous_user.is_authenticated = False
self.year = timezone.now().year
models.SiteSettings.objects.create() models.SiteSettings.objects.create()
def test_goal_page_no_goal(self): def test_goal_page_no_goal(self):
@ -46,7 +48,7 @@ class GoalViews(TestCase):
request = self.factory.get("") request = self.factory.get("")
request.user = self.rat request.user = self.rat
result = view(request, self.local_user.localname, 2020) result = view(request, self.local_user.localname, self.year)
self.assertEqual(result.status_code, 404) self.assertEqual(result.status_code, 404)
def test_goal_page_no_goal_self(self): def test_goal_page_no_goal_self(self):
@ -55,7 +57,7 @@ class GoalViews(TestCase):
request = self.factory.get("") request = self.factory.get("")
request.user = self.local_user request.user = self.local_user
result = view(request, self.local_user.localname, 2020) result = view(request, self.local_user.localname, self.year)
result.render() result.render()
self.assertIsInstance(result, TemplateResponse) self.assertIsInstance(result, TemplateResponse)
@ -65,7 +67,7 @@ class GoalViews(TestCase):
request = self.factory.get("") request = self.factory.get("")
request.user = self.anonymous_user request.user = self.anonymous_user
result = view(request, self.local_user.localname, 2020) result = view(request, self.local_user.localname, self.year)
self.assertEqual(result.status_code, 302) self.assertEqual(result.status_code, 302)
def test_goal_page_public(self): def test_goal_page_public(self):
@ -93,13 +95,13 @@ class GoalViews(TestCase):
def test_goal_page_private(self): def test_goal_page_private(self):
"""view a user's private goal""" """view a user's private goal"""
models.AnnualGoal.objects.create( models.AnnualGoal.objects.create(
user=self.local_user, year=2020, goal=15, privacy="followers" user=self.local_user, year=self.year, goal=15, privacy="followers"
) )
view = views.Goal.as_view() view = views.Goal.as_view()
request = self.factory.get("") request = self.factory.get("")
request.user = self.rat request.user = self.rat
result = view(request, self.local_user.localname, 2020) result = view(request, self.local_user.localname, self.year)
self.assertEqual(result.status_code, 404) self.assertEqual(result.status_code, 404)
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.activitystreams.ActivityStream.add_status")
@ -111,19 +113,19 @@ class GoalViews(TestCase):
{ {
"user": self.local_user.id, "user": self.local_user.id,
"goal": 10, "goal": 10,
"year": 2020, "year": self.year,
"privacy": "unlisted", "privacy": "unlisted",
"post-status": True, "post-status": True,
}, },
) )
request.user = self.local_user request.user = self.local_user
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
view(request, self.local_user.localname, 2020) view(request, self.local_user.localname, self.year)
goal = models.AnnualGoal.objects.get() goal = models.AnnualGoal.objects.get()
self.assertEqual(goal.user, self.local_user) self.assertEqual(goal.user, self.local_user)
self.assertEqual(goal.goal, 10) self.assertEqual(goal.goal, 10)
self.assertEqual(goal.year, 2020) self.assertEqual(goal.year, self.year)
self.assertEqual(goal.privacy, "unlisted") self.assertEqual(goal.privacy, "unlisted")
status = models.GeneratedNote.objects.get() status = models.GeneratedNote.objects.get()

View file

@ -2,7 +2,6 @@
import json import json
from unittest.mock import patch from unittest.mock import patch
import pathlib import pathlib
from django.db.models import Q
from django.http import Http404 from django.http import Http404
from django.test import TestCase from django.test import TestCase
from django.test.client import RequestFactory from django.test.client import RequestFactory
@ -13,12 +12,16 @@ from bookwyrm.settings import USER_AGENT
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @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): class ViewsHelpers(TestCase):
"""viewing and creating statuses""" """viewing and creating statuses"""
def setUp(self): def setUp(self):
"""we need basic test data and mocks""" """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
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( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -29,6 +32,7 @@ class ViewsHelpers(TestCase):
remote_id="https://example.com/users/mouse", remote_id="https://example.com/users/mouse",
) )
with patch("bookwyrm.models.user.set_remote_server.delay"): with patch("bookwyrm.models.user.set_remote_server.delay"):
with patch("bookwyrm.suggested_users.rerank_user_task.delay"):
self.remote_user = models.User.objects.create_user( self.remote_user = models.User.objects.create_user(
"rat", "rat",
"rat@rat.com", "rat@rat.com",
@ -53,12 +57,12 @@ class ViewsHelpers(TestCase):
name="Test Shelf", identifier="test-shelf", user=self.local_user 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""" """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.book.id), self.book)
self.assertEqual(views.helpers.get_edition(self.work.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""" """works for either localname or username"""
self.assertEqual( self.assertEqual(
views.helpers.get_user_from_username(self.local_user, "mouse"), views.helpers.get_user_from_username(self.local_user, "mouse"),
@ -71,7 +75,7 @@ class ViewsHelpers(TestCase):
with self.assertRaises(Http404): with self.assertRaises(Http404):
views.helpers.get_user_from_username(self.local_user, "mojfse@example.com") 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""" """should it return html or json"""
request = self.factory.get("/path") request = self.factory.get("/path")
request.headers = {"Accept": "application/json"} request.headers = {"Accept": "application/json"}
@ -85,12 +89,12 @@ class ViewsHelpers(TestCase):
request.headers = {"Accept": "Praise"} request.headers = {"Accept": "Praise"}
self.assertFalse(views.helpers.is_api_request(request)) 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""" """should it return html or json"""
request = self.factory.get("/path") request = self.factory.get("/path")
self.assertFalse(views.helpers.is_api_request(request)) 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""" """checks if a request came from a bookwyrm instance"""
request = self.factory.get("", {"q": "Test Book"}) request = self.factory.get("", {"q": "Test Book"})
self.assertFalse(views.helpers.is_bookwyrm_request(request)) 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) request = self.factory.get("", {"q": "Test Book"}, HTTP_USER_AGENT=USER_AGENT)
self.assertTrue(views.helpers.is_bookwyrm_request(request)) self.assertTrue(views.helpers.is_bookwyrm_request(request))
def test_existing_user(self, _): def test_existing_user(self, *_):
"""simple database lookup by username""" """simple database lookup by username"""
result = views.helpers.handle_remote_webfinger("@mouse@local.com") result = views.helpers.handle_remote_webfinger("@mouse@local.com")
self.assertEqual(result, self.local_user) self.assertEqual(result, self.local_user)
@ -117,7 +121,7 @@ class ViewsHelpers(TestCase):
self.assertEqual(result, self.local_user) self.assertEqual(result, self.local_user)
@responses.activate @responses.activate
def test_load_user(self, _): def test_load_user(self, *_):
"""find a remote user using webfinger""" """find a remote user using webfinger"""
username = "mouse@example.com" username = "mouse@example.com"
wellknown = { wellknown = {
@ -147,7 +151,7 @@ class ViewsHelpers(TestCase):
self.assertIsInstance(result, models.User) self.assertIsInstance(result, models.User)
self.assertEqual(result.username, "mouse@example.com") 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""" """find a remote user using webfinger"""
models.FederatedServer.objects.create( models.FederatedServer.objects.create(
server_name="example.com", status="blocked" server_name="example.com", status="blocked"
@ -156,7 +160,7 @@ class ViewsHelpers(TestCase):
result = views.helpers.handle_remote_webfinger("@mouse@example.com") result = views.helpers.handle_remote_webfinger("@mouse@example.com")
self.assertIsNone(result) self.assertIsNone(result)
def test_handle_reading_status_to_read(self, _): def test_handle_reading_status_to_read(self, *_):
"""posts shelve activities""" """posts shelve activities"""
shelf = self.local_user.shelf_set.get(identifier="to-read") shelf = self.local_user.shelf_set.get(identifier="to-read")
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): 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.mention_books.first(), self.book)
self.assertEqual(status.content, "wants to read") self.assertEqual(status.content, "wants to read")
def test_handle_reading_status_reading(self, _): def test_handle_reading_status_reading(self, *_):
"""posts shelve activities""" """posts shelve activities"""
shelf = self.local_user.shelf_set.get(identifier="reading") shelf = self.local_user.shelf_set.get(identifier="reading")
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): 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.mention_books.first(), self.book)
self.assertEqual(status.content, "started reading") self.assertEqual(status.content, "started reading")
def test_handle_reading_status_read(self, _): def test_handle_reading_status_read(self, *_):
"""posts shelve activities""" """posts shelve activities"""
shelf = self.local_user.shelf_set.get(identifier="read") shelf = self.local_user.shelf_set.get(identifier="read")
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): 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.mention_books.first(), self.book)
self.assertEqual(status.content, "finished reading") self.assertEqual(status.content, "finished reading")
def test_handle_reading_status_other(self, _): def test_handle_reading_status_other(self, *_):
"""posts shelve activities""" """posts shelve activities"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
views.helpers.handle_reading_status( views.helpers.handle_reading_status(
self.local_user, self.shelf, self.book, "public" self.local_user, self.shelf, self.book, "public"
) )
self.assertFalse(models.GeneratedNote.objects.exists()) 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,6 +14,7 @@ class ImportViews(TestCase):
def setUp(self): def setUp(self):
"""we need basic test data and mocks""" """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
"mouse@mouse.mouse", "mouse@mouse.mouse",

View file

@ -15,6 +15,7 @@ class InteractionViews(TestCase):
def setUp(self): def setUp(self):
"""we need basic test data and mocks""" """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
"mouse@mouse.com", "mouse@mouse.com",

View file

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

View file

@ -16,6 +16,7 @@ class IsbnViews(TestCase):
def setUp(self): def setUp(self):
"""we need basic test data and mocks""" """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
"mouse@mouse.com", "mouse@mouse.com",

View file

@ -15,6 +15,7 @@ class LandingViews(TestCase):
def setUp(self): def setUp(self):
"""we need basic test data and mocks""" """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
"mouse@mouse.mouse", "mouse@mouse.mouse",
@ -26,7 +27,8 @@ class LandingViews(TestCase):
self.anonymous_user.is_authenticated = False self.anonymous_user.is_authenticated = False
models.SiteSettings.objects.create() 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""" """there are so many views, this just makes sure it LOADS"""
view = views.Home.as_view() view = views.Home.as_view()
request = self.factory.get("") request = self.factory.get("")

View file

@ -17,6 +17,7 @@ class ListViews(TestCase):
def setUp(self): def setUp(self):
"""we need basic test data and mocks""" """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
"mouse@mouse.com", "mouse@mouse.com",

View file

@ -15,6 +15,7 @@ class ListActionViews(TestCase):
def setUp(self): def setUp(self):
"""we need basic test data and mocks""" """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
"mouse@mouse.com", "mouse@mouse.com",

View file

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

View file

@ -18,6 +18,7 @@ class OutboxView(TestCase):
def setUp(self): def setUp(self):
"""we'll need some data""" """we'll need some data"""
self.factory = RequestFactory() self.factory = RequestFactory()
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
"mouse@mouse.com", "mouse@mouse.com",

View file

@ -15,6 +15,7 @@ class PasswordViews(TestCase):
def setUp(self): def setUp(self):
"""we need basic test data and mocks""" """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
"mouse@mouse.com", "mouse@mouse.com",

View file

@ -9,12 +9,14 @@ from bookwyrm import models, views
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.activitystreams.ActivityStream.add_status")
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
class ReadingViews(TestCase): class ReadingViews(TestCase):
"""viewing and creating statuses""" """viewing and creating statuses"""
def setUp(self): def setUp(self):
"""we need basic test data and mocks""" """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -40,7 +42,7 @@ class ReadingViews(TestCase):
parent_work=self.work, parent_work=self.work,
) )
def test_start_reading(self, _): def test_start_reading(self, *_):
"""begin a book""" """begin a book"""
shelf = self.local_user.shelf_set.get(identifier=models.Shelf.READING) shelf = self.local_user.shelf_set.get(identifier=models.Shelf.READING)
self.assertFalse(shelf.books.exists()) self.assertFalse(shelf.books.exists())
@ -71,7 +73,7 @@ class ReadingViews(TestCase):
self.assertEqual(readthrough.user, self.local_user) self.assertEqual(readthrough.user, self.local_user)
self.assertEqual(readthrough.book, self.book) self.assertEqual(readthrough.book, self.book)
def test_start_reading_reshelf(self, _): def test_start_reading_reshelf(self, *_):
"""begin a book""" """begin a book"""
to_read_shelf = self.local_user.shelf_set.get(identifier=models.Shelf.TO_READ) to_read_shelf = self.local_user.shelf_set.get(identifier=models.Shelf.TO_READ)
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
@ -91,7 +93,7 @@ class ReadingViews(TestCase):
self.assertFalse(to_read_shelf.books.exists()) self.assertFalse(to_read_shelf.books.exists())
self.assertEqual(shelf.books.get(), self.book) self.assertEqual(shelf.books.get(), self.book)
def test_finish_reading(self, _): def test_finish_reading(self, *_):
"""begin a book""" """begin a book"""
shelf = self.local_user.shelf_set.get(identifier=models.Shelf.READ_FINISHED) shelf = self.local_user.shelf_set.get(identifier=models.Shelf.READ_FINISHED)
self.assertFalse(shelf.books.exists()) self.assertFalse(shelf.books.exists())
@ -127,7 +129,7 @@ class ReadingViews(TestCase):
self.assertEqual(readthrough.user, self.local_user) self.assertEqual(readthrough.user, self.local_user)
self.assertEqual(readthrough.book, self.book) self.assertEqual(readthrough.book, self.book)
def test_edit_readthrough(self, _): def test_edit_readthrough(self, *_):
"""adding dates to an ongoing readthrough""" """adding dates to an ongoing readthrough"""
start = timezone.make_aware(dateutil.parser.parse("2021-01-03")) start = timezone.make_aware(dateutil.parser.parse("2021-01-03"))
readthrough = models.ReadThrough.objects.create( readthrough = models.ReadThrough.objects.create(
@ -154,7 +156,7 @@ class ReadingViews(TestCase):
self.assertEqual(readthrough.finish_date.day, 7) self.assertEqual(readthrough.finish_date.day, 7)
self.assertEqual(readthrough.book, self.book) self.assertEqual(readthrough.book, self.book)
def test_delete_readthrough(self, _): def test_delete_readthrough(self, *_):
"""remove a readthrough""" """remove a readthrough"""
readthrough = models.ReadThrough.objects.create( readthrough = models.ReadThrough.objects.create(
book=self.book, user=self.local_user book=self.book, user=self.local_user
@ -171,7 +173,7 @@ class ReadingViews(TestCase):
views.delete_readthrough(request) views.delete_readthrough(request)
self.assertFalse(models.ReadThrough.objects.filter(id=readthrough.id).exists()) self.assertFalse(models.ReadThrough.objects.filter(id=readthrough.id).exists())
def test_create_readthrough(self, _): def test_create_readthrough(self, *_):
"""adding new read dates""" """adding new read dates"""
request = self.factory.post( request = self.factory.post(
"", "",

View file

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

View file

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

View file

@ -14,6 +14,7 @@ class RssFeedView(TestCase):
"""test data""" """test data"""
self.site = models.SiteSettings.objects.create() self.site = models.SiteSettings.objects.create()
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.user = models.User.objects.create_user( self.user = models.User.objects.create_user(
"rss_user", "rss@test.rss", "password", local=True "rss_user", "rss@test.rss", "password", local=True
) )

View file

@ -19,6 +19,7 @@ class Views(TestCase):
def setUp(self): def setUp(self):
"""we need basic test data and mocks""" """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
"mouse@mouse.com", "mouse@mouse.com",

View file

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

View file

@ -9,6 +9,7 @@ from bookwyrm.settings import DOMAIN
# pylint: disable=invalid-name # pylint: disable=invalid-name
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") @patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
class StatusViews(TestCase): class StatusViews(TestCase):
"""viewing and creating statuses""" """viewing and creating statuses"""
@ -16,6 +17,7 @@ class StatusViews(TestCase):
def setUp(self): def setUp(self):
"""we need basic test data and mocks""" """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -43,7 +45,7 @@ class StatusViews(TestCase):
) )
models.SiteSettings.objects.create() models.SiteSettings.objects.create()
def test_handle_status(self, _): def test_handle_status(self, *_):
"""create a status""" """create a status"""
view = views.CreateStatus.as_view() view = views.CreateStatus.as_view()
form = forms.CommentForm( form = forms.CommentForm(
@ -66,7 +68,7 @@ class StatusViews(TestCase):
self.assertEqual(status.user, self.local_user) self.assertEqual(status.user, self.local_user)
self.assertEqual(status.book, self.book) 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""" """create a status in reply to an existing status"""
view = views.CreateStatus.as_view() view = views.CreateStatus.as_view()
user = models.User.objects.create_user( user = models.User.objects.create_user(
@ -96,7 +98,7 @@ class StatusViews(TestCase):
self.assertEqual(status.user, user) self.assertEqual(status.user, user)
self.assertEqual(models.Notification.objects.get().user, self.local_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""" """@mention a user in a post"""
view = views.CreateStatus.as_view() view = views.CreateStatus.as_view()
user = models.User.objects.create_user( 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 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""" """reply to a post with an @mention'ed user"""
view = views.CreateStatus.as_view() view = views.CreateStatus.as_view()
user = models.User.objects.create_user( user = models.User.objects.create_user(
@ -172,7 +174,7 @@ class StatusViews(TestCase):
self.assertFalse(self.remote_user in reply.mention_users.all()) self.assertFalse(self.remote_user in reply.mention_users.all())
self.assertTrue(self.local_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""" """delete and re-draft a status"""
view = views.DeleteAndRedraft.as_view() view = views.DeleteAndRedraft.as_view()
request = self.factory.post("") request = self.factory.post("")
@ -193,7 +195,7 @@ class StatusViews(TestCase):
status.refresh_from_db() status.refresh_from_db()
self.assertTrue(status.deleted) 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""" """you can't redraft generated statuses"""
view = views.DeleteAndRedraft.as_view() view = views.DeleteAndRedraft.as_view()
request = self.factory.post("") request = self.factory.post("")
@ -213,7 +215,7 @@ class StatusViews(TestCase):
status.refresh_from_db() status.refresh_from_db()
self.assertFalse(status.deleted) 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""" """you can't redraft generated statuses"""
view = views.DeleteAndRedraft.as_view() view = views.DeleteAndRedraft.as_view()
request = self.factory.post("") request = self.factory.post("")
@ -233,7 +235,7 @@ class StatusViews(TestCase):
status.refresh_from_db() status.refresh_from_db()
self.assertFalse(status.deleted) self.assertFalse(status.deleted)
def test_find_mentions(self, _): def test_find_mentions(self, *_):
"""detect and look up @ mentions of users""" """detect and look up @ mentions of users"""
user = models.User.objects.create_user( user = models.User.objects.create_user(
"nutria@%s" % DOMAIN, "nutria@%s" % DOMAIN,
@ -279,7 +281,7 @@ class StatusViews(TestCase):
("@nutria@%s" % DOMAIN, user), ("@nutria@%s" % DOMAIN, user),
) )
def test_format_links(self, _): def test_format_links(self, *_):
"""find and format urls into a tags""" """find and format urls into a tags"""
url = "http://www.fish.com/" url = "http://www.fish.com/"
self.assertEqual( self.assertEqual(
@ -302,7 +304,7 @@ class StatusViews(TestCase):
"?q=arkady+strugatsky&mode=everything</a>" % url, "?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""" """this is mostly handled in other places, but nonetheless"""
text = "_hi_ and http://fish.com is <marquee>rad</marquee>" text = "_hi_ and http://fish.com is <marquee>rad</marquee>"
result = views.status.to_markdown(text) 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>", '<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""" """this is mostly handled in other places, but nonetheless"""
text = "http://fish.com/@hello#okay" text = "http://fish.com/@hello#okay"
result = views.status.to_markdown(text) 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>', '<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""" """this is mostly handled in other places, but nonetheless"""
text = "[hi](http://fish.com) is <marquee>rad</marquee>" text = "[hi](http://fish.com) is <marquee>rad</marquee>"
result = views.status.to_markdown(text) result = views.status.to_markdown(text)
self.assertEqual(result, '<p><a href="http://fish.com">hi</a> ' "is rad</p>") 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""" """marks a status as deleted"""
view = views.DeleteStatus.as_view() view = views.DeleteStatus.as_view()
with patch("bookwyrm.activitystreams.ActivityStream.add_status"): with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
@ -346,7 +348,7 @@ class StatusViews(TestCase):
status.refresh_from_db() status.refresh_from_db()
self.assertTrue(status.deleted) 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""" """marks a status as deleted"""
view = views.DeleteStatus.as_view() view = views.DeleteStatus.as_view()
with patch("bookwyrm.activitystreams.ActivityStream.add_status"): with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
@ -360,7 +362,7 @@ class StatusViews(TestCase):
status.refresh_from_db() status.refresh_from_db()
self.assertFalse(status.deleted) 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""" """marks a status as deleted"""
view = views.DeleteStatus.as_view() view = views.DeleteStatus.as_view()
with patch("bookwyrm.activitystreams.ActivityStream.add_status"): with patch("bookwyrm.activitystreams.ActivityStream.add_status"):

View file

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

View file

@ -17,6 +17,7 @@ class UserViews(TestCase):
def setUp(self): def setUp(self):
"""we need basic test data and mocks""" """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
"mouse@mouse.mouse", "mouse@mouse.mouse",
@ -27,8 +28,11 @@ class UserViews(TestCase):
self.rat = models.User.objects.create_user( self.rat = models.User.objects.create_user(
"rat@local.com", "rat@rat.rat", "password", local=True, localname="rat" "rat@local.com", "rat@rat.rat", "password", local=True, localname="rat"
) )
self.book = models.Edition.objects.create(title="test") self.book = models.Edition.objects.create(
title="test", parent_work=models.Work.objects.create(title="test work")
)
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
models.ShelfBook.objects.create( models.ShelfBook.objects.create(
book=self.book, book=self.book,
user=self.local_user, user=self.local_user,
@ -94,7 +98,8 @@ class UserViews(TestCase):
self.assertIsInstance(result, ActivitypubResponse) self.assertIsInstance(result, ActivitypubResponse)
self.assertEqual(result.status_code, 200) 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""" """there are so many views, this just makes sure it LOADS"""
view = views.Followers.as_view() view = views.Followers.as_view()
request = self.factory.get("") request = self.factory.get("")

View file

@ -14,6 +14,7 @@ class UserAdminViews(TestCase):
def setUp(self): def setUp(self):
"""we need basic test data and mocks""" """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
"mouse@mouse.mouse", "mouse@mouse.mouse",
@ -47,7 +48,9 @@ class UserAdminViews(TestCase):
result.render() result.render()
self.assertEqual(result.status_code, 200) 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""" """set the user's group"""
group = Group.objects.create(name="editor") group = Group.objects.create(name="editor")
self.assertEqual( self.assertEqual(

View file

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

View file

@ -23,6 +23,8 @@ STATUS_PATH = r"%s/(%s)/(?P<status_id>\d+)" % (USER_PATH, "|".join(status_types)
BOOK_PATH = r"^book/(?P<book_id>\d+)" BOOK_PATH = r"^book/(?P<book_id>\d+)"
STREAMS = "|".join(s["key"] for s in settings.STREAMS)
urlpatterns = [ urlpatterns = [
path("admin/", admin.site.urls), path("admin/", admin.site.urls),
path( path(
@ -177,7 +179,7 @@ urlpatterns = [
name="get-started-users", name="get-started-users",
), ),
# feeds # feeds
re_path(r"^(?P<tab>home|local|federated)/?$", views.Feed.as_view()), re_path(r"^(?P<tab>{:s})/?$".format(STREAMS), views.Feed.as_view()),
re_path( re_path(
r"^direct-messages/?$", views.DirectMessage.as_view(), name="direct-messages" r"^direct-messages/?$", views.DirectMessage.as_view(), name="direct-messages"
), ),

View file

@ -50,7 +50,7 @@ class Login(View):
# successful login # successful login
login(request, user) login(request, user)
user.last_active_date = timezone.now() 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", "/")) return redirect(request.GET.get("next", "/"))
# login errors # login errors

View file

@ -339,18 +339,15 @@ def set_cover_from_url(url):
@permission_required("bookwyrm.edit_book", raise_exception=True) @permission_required("bookwyrm.edit_book", raise_exception=True)
def add_description(request, book_id): def add_description(request, book_id):
"""upload a new cover""" """upload a new cover"""
if not request.method == "POST":
return redirect("/")
book = get_object_or_404(models.Edition, id=book_id) book = get_object_or_404(models.Edition, id=book_id)
description = request.POST.get("description") description = request.POST.get("description")
book.description = description book.description = description
book.last_edited_by = request.user book.last_edited_by = request.user
book.save() book.save(update_fields=["description", "last_edited_by"])
return redirect("/book/%s" % book.id) return redirect("book", book.id)
@require_POST @require_POST
@ -360,7 +357,7 @@ def resolve_book(request):
connector = connector_manager.get_or_create_connector(remote_id) connector = connector_manager.get_or_create_connector(remote_id)
book = connector.get_or_create_book(remote_id) book = connector.get_or_create_book(remote_id)
return redirect("/book/%d" % book.id) return redirect("book", book.id)
@login_required @login_required

Some files were not shown because too many files have changed in this diff Show more