mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-26 11:31:08 +00:00
Merge branch 'main' into images-django-imagekit
This commit is contained in:
commit
6a365eafb4
119 changed files with 4113 additions and 2707 deletions
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
|
@ -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():
|
||||||
|
|
25
bookwyrm/management/commands/populate_suggestions.py
Normal file
25
bookwyrm/management/commands/populate_suggestions.py
Normal 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()
|
27
bookwyrm/migrations/0046_user_default_post_privacy.py
Normal file
27
bookwyrm/migrations/0046_user_default_post_privacy.py
Normal 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
13
bookwyrm/migrations/0079_merge_20210804_1746.py
Normal file
13
bookwyrm/migrations/0079_merge_20210804_1746.py
Normal 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 = []
|
17
bookwyrm/migrations/0080_alter_shelfbook_options.py
Normal file
17
bookwyrm/migrations/0080_alter_shelfbook_options.py
Normal 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")},
|
||||||
|
),
|
||||||
|
]
|
19
bookwyrm/migrations/0081_alter_user_last_active_date.py
Normal file
19
bookwyrm/migrations/0081_alter_user_last_active_date.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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"""
|
||||||
|
|
|
@ -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
221
bookwyrm/suggested_users.py
Normal 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)
|
|
@ -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 %}
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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>
|
|
||||||
|
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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:" %}
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
{% spaceless %}
|
{% spaceless %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load humanize %}
|
||||||
{% comment %}
|
{% comment %}
|
||||||
@todo The author property needs to be an Organization or a Person. We’ll be using Thing which is the more generic ancestor.
|
@todo The author property needs to be an Organization or a Person. We’ll 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 %}
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
38
bookwyrm/templates/snippets/suggested_users.html
Normal file
38
bookwyrm/templates/snippets/suggested_users.html
Normal 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>
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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])
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
173
bookwyrm/tests/test_suggested_users.py
Normal file
173
bookwyrm/tests/test_suggested_users.py
Normal 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)
|
|
@ -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",
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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("")
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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(
|
||||||
"",
|
"",
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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("")
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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"):
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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("")
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
),
|
),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
Loading…
Reference in a new issue