mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-27 12:01:14 +00:00
Merge pull request #594 from mouse-reeve/broadcast-fixes
Refactors broadcasting
This commit is contained in:
commit
486b660351
68 changed files with 1806 additions and 1340 deletions
|
@ -93,6 +93,9 @@ class ActivityObject:
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
# 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:
|
||||||
|
instance.save(broadcast=False)
|
||||||
|
except TypeError:
|
||||||
instance.save()
|
instance.save()
|
||||||
except IntegrityError as e:
|
except IntegrityError as e:
|
||||||
raise ActivitySerializerError(e)
|
raise ActivitySerializerError(e)
|
||||||
|
|
|
@ -18,7 +18,7 @@ class Note(ActivityObject):
|
||||||
''' Note activity '''
|
''' Note activity '''
|
||||||
published: str
|
published: str
|
||||||
attributedTo: str
|
attributedTo: str
|
||||||
content: str
|
content: str = ''
|
||||||
to: List[str] = field(default_factory=lambda: [])
|
to: List[str] = field(default_factory=lambda: [])
|
||||||
cc: List[str] = field(default_factory=lambda: [])
|
cc: List[str] = field(default_factory=lambda: [])
|
||||||
replies: Dict = field(default_factory=lambda: {})
|
replies: Dict = field(default_factory=lambda: {})
|
||||||
|
|
|
@ -1,87 +0,0 @@
|
||||||
''' send out activitypub messages '''
|
|
||||||
import json
|
|
||||||
from django.utils.http import http_date
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from bookwyrm import models, settings
|
|
||||||
from bookwyrm.activitypub import ActivityEncoder
|
|
||||||
from bookwyrm.tasks import app
|
|
||||||
from bookwyrm.signatures import make_signature, make_digest
|
|
||||||
|
|
||||||
|
|
||||||
def get_public_recipients(user, software=None):
|
|
||||||
''' everybody and their public inboxes '''
|
|
||||||
followers = user.followers.filter(local=False)
|
|
||||||
if software:
|
|
||||||
followers = followers.filter(bookwyrm_user=(software == 'bookwyrm'))
|
|
||||||
|
|
||||||
# we want shared inboxes when available
|
|
||||||
shared = followers.filter(
|
|
||||||
shared_inbox__isnull=False
|
|
||||||
).values_list('shared_inbox', flat=True).distinct()
|
|
||||||
|
|
||||||
# if a user doesn't have a shared inbox, we need their personal inbox
|
|
||||||
# iirc pixelfed doesn't have shared inboxes
|
|
||||||
inboxes = followers.filter(
|
|
||||||
shared_inbox__isnull=True
|
|
||||||
).values_list('inbox', flat=True)
|
|
||||||
|
|
||||||
return list(shared) + list(inboxes)
|
|
||||||
|
|
||||||
|
|
||||||
def broadcast(sender, activity, software=None, \
|
|
||||||
privacy='public', direct_recipients=None):
|
|
||||||
''' send out an event '''
|
|
||||||
# start with parsing the direct recipients
|
|
||||||
recipients = [u.inbox for u in direct_recipients or []]
|
|
||||||
# and then add any other recipients
|
|
||||||
if privacy == 'public':
|
|
||||||
recipients += get_public_recipients(sender, software=software)
|
|
||||||
broadcast_task.delay(
|
|
||||||
sender.id,
|
|
||||||
json.dumps(activity, cls=ActivityEncoder),
|
|
||||||
recipients
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
|
||||||
def broadcast_task(sender_id, activity, recipients):
|
|
||||||
''' the celery task for broadcast '''
|
|
||||||
sender = models.User.objects.get(id=sender_id)
|
|
||||||
errors = []
|
|
||||||
for recipient in recipients:
|
|
||||||
try:
|
|
||||||
sign_and_send(sender, activity, recipient)
|
|
||||||
except requests.exceptions.HTTPError as e:
|
|
||||||
errors.append({
|
|
||||||
'error': str(e),
|
|
||||||
'recipient': recipient,
|
|
||||||
'activity': activity,
|
|
||||||
})
|
|
||||||
return errors
|
|
||||||
|
|
||||||
|
|
||||||
def sign_and_send(sender, data, destination):
|
|
||||||
''' crpyto whatever and http junk '''
|
|
||||||
now = http_date()
|
|
||||||
|
|
||||||
if not sender.key_pair.private_key:
|
|
||||||
# this shouldn't happen. it would be bad if it happened.
|
|
||||||
raise ValueError('No private key found for sender')
|
|
||||||
|
|
||||||
digest = make_digest(data)
|
|
||||||
|
|
||||||
response = requests.post(
|
|
||||||
destination,
|
|
||||||
data=data,
|
|
||||||
headers={
|
|
||||||
'Date': now,
|
|
||||||
'Digest': digest,
|
|
||||||
'Signature': make_signature(sender, destination, now, digest),
|
|
||||||
'Content-Type': 'application/activity+json; charset=utf-8',
|
|
||||||
'User-Agent': settings.USER_AGENT,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if not response.ok:
|
|
||||||
response.raise_for_status()
|
|
||||||
return response
|
|
|
@ -3,7 +3,6 @@ import csv
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from bookwyrm import models
|
from bookwyrm import models
|
||||||
from bookwyrm.broadcast import broadcast
|
|
||||||
from bookwyrm.models import ImportJob, ImportItem
|
from bookwyrm.models import ImportJob, ImportItem
|
||||||
from bookwyrm.status import create_notification
|
from bookwyrm.status import create_notification
|
||||||
from bookwyrm.tasks import app
|
from bookwyrm.tasks import app
|
||||||
|
@ -82,7 +81,7 @@ def handle_imported_book(user, item, include_reviews, privacy):
|
||||||
return
|
return
|
||||||
|
|
||||||
existing_shelf = models.ShelfBook.objects.filter(
|
existing_shelf = models.ShelfBook.objects.filter(
|
||||||
book=item.book, added_by=user).exists()
|
book=item.book, user=user).exists()
|
||||||
|
|
||||||
# shelve the book if it hasn't been shelved already
|
# shelve the book if it hasn't been shelved already
|
||||||
if item.shelf and not existing_shelf:
|
if item.shelf and not existing_shelf:
|
||||||
|
@ -90,9 +89,8 @@ def handle_imported_book(user, item, include_reviews, privacy):
|
||||||
identifier=item.shelf,
|
identifier=item.shelf,
|
||||||
user=user
|
user=user
|
||||||
)
|
)
|
||||||
shelf_book = models.ShelfBook.objects.create(
|
models.ShelfBook.objects.create(
|
||||||
book=item.book, shelf=desired_shelf, added_by=user)
|
book=item.book, shelf=desired_shelf, user=user)
|
||||||
broadcast(user, shelf_book.to_add_activity(user), privacy=privacy)
|
|
||||||
|
|
||||||
for read in item.reads:
|
for read in item.reads:
|
||||||
# check for an existing readthrough with the same dates
|
# check for an existing readthrough with the same dates
|
||||||
|
@ -114,7 +112,7 @@ def handle_imported_book(user, item, include_reviews, privacy):
|
||||||
# we don't know the publication date of the review,
|
# we don't know the publication date of the review,
|
||||||
# but "now" is a bad guess
|
# but "now" is a bad guess
|
||||||
published_date_guess = item.date_read or item.date_added
|
published_date_guess = item.date_read or item.date_added
|
||||||
review = models.Review.objects.create(
|
models.Review.objects.create(
|
||||||
user=user,
|
user=user,
|
||||||
book=item.book,
|
book=item.book,
|
||||||
name=review_title,
|
name=review_title,
|
||||||
|
@ -123,6 +121,3 @@ def handle_imported_book(user, item, include_reviews, privacy):
|
||||||
published_date=published_date_guess,
|
published_date=published_date_guess,
|
||||||
privacy=privacy,
|
privacy=privacy,
|
||||||
)
|
)
|
||||||
# we don't need to send out pure activities because non-bookwyrm
|
|
||||||
# instances don't need this data
|
|
||||||
broadcast(user, review.to_create_activity(user), privacy=privacy)
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.views.decorators.http import require_POST
|
from django.views.decorators.http import require_POST
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from bookwyrm import activitypub, models, views
|
from bookwyrm import activitypub, models
|
||||||
from bookwyrm import status as status_builder
|
from bookwyrm import status as status_builder
|
||||||
from bookwyrm.tasks import app
|
from bookwyrm.tasks import app
|
||||||
from bookwyrm.signatures import Signature
|
from bookwyrm.signatures import Signature
|
||||||
|
@ -144,7 +144,7 @@ def handle_follow(activity):
|
||||||
related_user=relationship.user_subject
|
related_user=relationship.user_subject
|
||||||
)
|
)
|
||||||
if not manually_approves:
|
if not manually_approves:
|
||||||
views.handle_accept(relationship)
|
relationship.accept()
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Generated by Django 3.0.7 on 2020-11-30 18:19
|
# Generated by Django 3.0.7 on 2020-11-30 18:19
|
||||||
|
|
||||||
import bookwyrm.models.base_model
|
import bookwyrm.models.activitypub_mixin
|
||||||
import bookwyrm.models.fields
|
import bookwyrm.models.fields
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
@ -38,7 +38,7 @@ class Migration(migrations.Migration):
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
'abstract': False,
|
||||||
},
|
},
|
||||||
bases=(bookwyrm.models.base_model.ActivitypubMixin, models.Model),
|
bases=(bookwyrm.models.activitypub_mixin.ActivitypubMixin, models.Model),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='user',
|
model_name='user',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Generated by Django 3.0.7 on 2021-01-31 16:14
|
# Generated by Django 3.0.7 on 2021-01-31 16:14
|
||||||
|
|
||||||
import bookwyrm.models.base_model
|
import bookwyrm.models.activitypub_mixin
|
||||||
import bookwyrm.models.fields
|
import bookwyrm.models.fields
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
@ -29,7 +29,7 @@ class Migration(migrations.Migration):
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
'abstract': False,
|
||||||
},
|
},
|
||||||
bases=(bookwyrm.models.base_model.OrderedCollectionMixin, models.Model),
|
bases=(bookwyrm.models.activitypub_mixin.OrderedCollectionMixin, models.Model),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ListItem',
|
name='ListItem',
|
||||||
|
@ -50,7 +50,7 @@ class Migration(migrations.Migration):
|
||||||
'ordering': ('-created_date',),
|
'ordering': ('-created_date',),
|
||||||
'unique_together': {('book', 'book_list')},
|
'unique_together': {('book', 'book_list')},
|
||||||
},
|
},
|
||||||
bases=(bookwyrm.models.base_model.ActivitypubMixin, models.Model),
|
bases=(bookwyrm.models.activitypub_mixin.ActivitypubMixin, models.Model),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='list',
|
model_name='list',
|
||||||
|
|
23
bookwyrm/migrations/0043_auto_20210204_2223.py
Normal file
23
bookwyrm/migrations/0043_auto_20210204_2223.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 3.0.7 on 2021-02-04 22:23
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('bookwyrm', '0042_auto_20210201_2108'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='listitem',
|
||||||
|
old_name='added_by',
|
||||||
|
new_name='user',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='shelfbook',
|
||||||
|
old_name='added_by',
|
||||||
|
new_name='user',
|
||||||
|
),
|
||||||
|
]
|
30
bookwyrm/migrations/0044_auto_20210207_1924.py
Normal file
30
bookwyrm/migrations/0044_auto_20210207_1924.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# Generated by Django 3.0.7 on 2021-02-07 19:24
|
||||||
|
|
||||||
|
import bookwyrm.models.fields
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
def set_user(app_registry, schema_editor):
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
shelfbook = app_registry.get_model('bookwyrm', 'ShelfBook')
|
||||||
|
for item in shelfbook.objects.using(db_alias).filter(user__isnull=True):
|
||||||
|
item.user = item.shelf.user
|
||||||
|
item.save(broadcast=False)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('bookwyrm', '0043_auto_20210204_2223'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(set_user, lambda x, y: None),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='shelfbook',
|
||||||
|
name='user',
|
||||||
|
field=bookwyrm.models.fields.ForeignKey(default=2, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
497
bookwyrm/models/activitypub_mixin.py
Normal file
497
bookwyrm/models/activitypub_mixin.py
Normal file
|
@ -0,0 +1,497 @@
|
||||||
|
''' activitypub model functionality '''
|
||||||
|
from base64 import b64encode
|
||||||
|
from functools import reduce
|
||||||
|
import json
|
||||||
|
import operator
|
||||||
|
import logging
|
||||||
|
from uuid import uuid4
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from Crypto.PublicKey import RSA
|
||||||
|
from Crypto.Signature import pkcs1_15
|
||||||
|
from Crypto.Hash import SHA256
|
||||||
|
from django.apps import apps
|
||||||
|
from django.core.paginator import Paginator
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.utils.http import http_date
|
||||||
|
|
||||||
|
from bookwyrm import activitypub
|
||||||
|
from bookwyrm.settings import USER_AGENT, PAGE_LENGTH
|
||||||
|
from bookwyrm.signatures import make_signature, make_digest
|
||||||
|
from bookwyrm.tasks import app
|
||||||
|
from bookwyrm.models.fields import ImageField, ManyToManyField
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
# I tried to separate these classes into mutliple files but I kept getting
|
||||||
|
# circular import errors so I gave up. I'm sure it could be done though!
|
||||||
|
class ActivitypubMixin:
|
||||||
|
''' add this mixin for models that are AP serializable '''
|
||||||
|
activity_serializer = lambda: {}
|
||||||
|
reverse_unfurl = False
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
''' collect some info on model fields '''
|
||||||
|
self.image_fields = []
|
||||||
|
self.many_to_many_fields = []
|
||||||
|
self.simple_fields = [] # "simple"
|
||||||
|
# sort model fields by type
|
||||||
|
for field in self._meta.get_fields():
|
||||||
|
if not hasattr(field, 'field_to_activity'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(field, ImageField):
|
||||||
|
self.image_fields.append(field)
|
||||||
|
elif isinstance(field, ManyToManyField):
|
||||||
|
self.many_to_many_fields.append(field)
|
||||||
|
else:
|
||||||
|
self.simple_fields.append(field)
|
||||||
|
|
||||||
|
# a list of allll the serializable fields
|
||||||
|
self.activity_fields = self.image_fields + \
|
||||||
|
self.many_to_many_fields + self.simple_fields
|
||||||
|
|
||||||
|
# these are separate to avoid infinite recursion issues
|
||||||
|
self.deserialize_reverse_fields = self.deserialize_reverse_fields \
|
||||||
|
if hasattr(self, 'deserialize_reverse_fields') else []
|
||||||
|
self.serialize_reverse_fields = self.serialize_reverse_fields \
|
||||||
|
if hasattr(self, 'serialize_reverse_fields') else []
|
||||||
|
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def find_existing_by_remote_id(cls, remote_id):
|
||||||
|
''' look up a remote id in the db '''
|
||||||
|
return cls.find_existing({'id': remote_id})
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def find_existing(cls, data):
|
||||||
|
''' compare data to fields that can be used for deduplation.
|
||||||
|
This always includes remote_id, but can also be unique identifiers
|
||||||
|
like an isbn for an edition '''
|
||||||
|
filters = []
|
||||||
|
# grabs all the data from the model to create django queryset filters
|
||||||
|
for field in cls._meta.get_fields():
|
||||||
|
if not hasattr(field, 'deduplication_field') or \
|
||||||
|
not field.deduplication_field:
|
||||||
|
continue
|
||||||
|
|
||||||
|
value = data.get(field.get_activitypub_field())
|
||||||
|
if not value:
|
||||||
|
continue
|
||||||
|
filters.append({field.name: value})
|
||||||
|
|
||||||
|
if hasattr(cls, 'origin_id') and 'id' in data:
|
||||||
|
# kinda janky, but this handles special case for books
|
||||||
|
filters.append({'origin_id': data['id']})
|
||||||
|
|
||||||
|
if not filters:
|
||||||
|
# if there are no deduplication fields, it will match the first
|
||||||
|
# item no matter what. this shouldn't happen but just in case.
|
||||||
|
return None
|
||||||
|
|
||||||
|
objects = cls.objects
|
||||||
|
if hasattr(objects, 'select_subclasses'):
|
||||||
|
objects = objects.select_subclasses()
|
||||||
|
|
||||||
|
# an OR operation on all the match fields, sorry for the dense syntax
|
||||||
|
match = objects.filter(
|
||||||
|
reduce(operator.or_, (Q(**f) for f in filters))
|
||||||
|
)
|
||||||
|
# there OUGHT to be only one match
|
||||||
|
return match.first()
|
||||||
|
|
||||||
|
|
||||||
|
def broadcast(self, activity, sender, software=None):
|
||||||
|
''' send out an activity '''
|
||||||
|
broadcast_task.delay(
|
||||||
|
sender.id,
|
||||||
|
json.dumps(activity, cls=activitypub.ActivityEncoder),
|
||||||
|
self.get_recipients(software=software)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_recipients(self, software=None):
|
||||||
|
''' figure out which inbox urls to post to '''
|
||||||
|
# first we have to figure out who should receive this activity
|
||||||
|
privacy = self.privacy if hasattr(self, 'privacy') else 'public'
|
||||||
|
# is this activity owned by a user (statuses, lists, shelves), or is it
|
||||||
|
# general to the instance (like books)
|
||||||
|
user = self.user if hasattr(self, 'user') else None
|
||||||
|
user_model = apps.get_model('bookwyrm.User', require_ready=True)
|
||||||
|
if not user and isinstance(self, user_model):
|
||||||
|
# or maybe the thing itself is a user
|
||||||
|
user = self
|
||||||
|
# find anyone who's tagged in a status, for example
|
||||||
|
mentions = self.recipients if hasattr(self, 'recipients') else []
|
||||||
|
|
||||||
|
# we always send activities to explicitly mentioned users' inboxes
|
||||||
|
recipients = [u.inbox for u in mentions or []]
|
||||||
|
|
||||||
|
# unless it's a dm, all the followers should receive the activity
|
||||||
|
if privacy != 'direct':
|
||||||
|
# we will send this out to a subset of all remote users
|
||||||
|
queryset = user_model.objects.filter(
|
||||||
|
local=False,
|
||||||
|
)
|
||||||
|
# filter users first by whether they're using the desired software
|
||||||
|
# this lets us send book updates only to other bw servers
|
||||||
|
if software:
|
||||||
|
queryset = queryset.filter(
|
||||||
|
bookwyrm_user=(software == 'bookwyrm')
|
||||||
|
)
|
||||||
|
# if there's a user, we only want to send to the user's followers
|
||||||
|
if user:
|
||||||
|
queryset = queryset.filter(following=user)
|
||||||
|
|
||||||
|
# ideally, we will send to shared inboxes for efficiency
|
||||||
|
shared_inboxes = queryset.filter(
|
||||||
|
shared_inbox__isnull=False
|
||||||
|
).values_list('shared_inbox', flat=True).distinct()
|
||||||
|
# but not everyone has a shared inbox
|
||||||
|
inboxes = queryset.filter(
|
||||||
|
shared_inbox__isnull=True
|
||||||
|
).values_list('inbox', flat=True)
|
||||||
|
recipients += list(shared_inboxes) + list(inboxes)
|
||||||
|
return recipients
|
||||||
|
|
||||||
|
|
||||||
|
def to_activity(self):
|
||||||
|
''' convert from a model to an activity '''
|
||||||
|
activity = generate_activity(self)
|
||||||
|
return self.activity_serializer(**activity).serialize()
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectMixin(ActivitypubMixin):
|
||||||
|
''' add this mixin for object models that are AP serializable '''
|
||||||
|
def save(self, *args, created=None, **kwargs):
|
||||||
|
''' broadcast created/updated/deleted objects as appropriate '''
|
||||||
|
broadcast = kwargs.get('broadcast', True)
|
||||||
|
# this bonus kwarg woul cause an error in the base save method
|
||||||
|
if 'broadcast' in kwargs:
|
||||||
|
del kwargs['broadcast']
|
||||||
|
|
||||||
|
created = created or not bool(self.id)
|
||||||
|
# first off, we want to save normally no matter what
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
if not broadcast:
|
||||||
|
return
|
||||||
|
|
||||||
|
# this will work for objects owned by a user (lists, shelves)
|
||||||
|
user = self.user if hasattr(self, 'user') else None
|
||||||
|
|
||||||
|
if created:
|
||||||
|
# broadcast Create activities for objects owned by a local user
|
||||||
|
if not user or not user.local:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
software = None
|
||||||
|
# do we have a "pure" activitypub version of this for mastodon?
|
||||||
|
if hasattr(self, 'pure_content'):
|
||||||
|
pure_activity = self.to_create_activity(user, pure=True)
|
||||||
|
self.broadcast(pure_activity, user, software='other')
|
||||||
|
software = 'bookwyrm'
|
||||||
|
# sends to BW only if we just did a pure version for masto
|
||||||
|
activity = self.to_create_activity(user)
|
||||||
|
self.broadcast(activity, user, software=software)
|
||||||
|
except KeyError:
|
||||||
|
# janky as heck, this catches the mutliple inheritence chain
|
||||||
|
# for boosts and ignores this auxilliary broadcast
|
||||||
|
return
|
||||||
|
return
|
||||||
|
|
||||||
|
# --- updating an existing object
|
||||||
|
if not user:
|
||||||
|
# users don't have associated users, they ARE users
|
||||||
|
user_model = apps.get_model('bookwyrm.User', require_ready=True)
|
||||||
|
if isinstance(self, user_model):
|
||||||
|
user = self
|
||||||
|
# book data tracks last editor
|
||||||
|
elif hasattr(self, 'last_edited_by'):
|
||||||
|
user = self.last_edited_by
|
||||||
|
# again, if we don't know the user or they're remote, don't bother
|
||||||
|
if not user or not user.local:
|
||||||
|
return
|
||||||
|
|
||||||
|
# is this a deletion?
|
||||||
|
if hasattr(self, 'deleted') and self.deleted:
|
||||||
|
activity = self.to_delete_activity(user)
|
||||||
|
else:
|
||||||
|
activity = self.to_update_activity(user)
|
||||||
|
self.broadcast(activity, user)
|
||||||
|
|
||||||
|
|
||||||
|
def to_create_activity(self, user, **kwargs):
|
||||||
|
''' returns the object wrapped in a Create activity '''
|
||||||
|
activity_object = self.to_activity(**kwargs)
|
||||||
|
|
||||||
|
signature = None
|
||||||
|
create_id = self.remote_id + '/activity'
|
||||||
|
if 'content' in activity_object and activity_object['content']:
|
||||||
|
signer = pkcs1_15.new(RSA.import_key(user.key_pair.private_key))
|
||||||
|
content = activity_object['content']
|
||||||
|
signed_message = signer.sign(SHA256.new(content.encode('utf8')))
|
||||||
|
|
||||||
|
signature = activitypub.Signature(
|
||||||
|
creator='%s#main-key' % user.remote_id,
|
||||||
|
created=activity_object['published'],
|
||||||
|
signatureValue=b64encode(signed_message).decode('utf8')
|
||||||
|
)
|
||||||
|
|
||||||
|
return activitypub.Create(
|
||||||
|
id=create_id,
|
||||||
|
actor=user.remote_id,
|
||||||
|
to=activity_object['to'],
|
||||||
|
cc=activity_object['cc'],
|
||||||
|
object=activity_object,
|
||||||
|
signature=signature,
|
||||||
|
).serialize()
|
||||||
|
|
||||||
|
|
||||||
|
def to_delete_activity(self, user):
|
||||||
|
''' notice of deletion '''
|
||||||
|
return activitypub.Delete(
|
||||||
|
id=self.remote_id + '/activity',
|
||||||
|
actor=user.remote_id,
|
||||||
|
to=['%s/followers' % user.remote_id],
|
||||||
|
cc=['https://www.w3.org/ns/activitystreams#Public'],
|
||||||
|
object=self.to_activity(),
|
||||||
|
).serialize()
|
||||||
|
|
||||||
|
|
||||||
|
def to_update_activity(self, user):
|
||||||
|
''' wrapper for Updates to an activity '''
|
||||||
|
activity_id = '%s#update/%s' % (self.remote_id, uuid4())
|
||||||
|
return activitypub.Update(
|
||||||
|
id=activity_id,
|
||||||
|
actor=user.remote_id,
|
||||||
|
to=['https://www.w3.org/ns/activitystreams#Public'],
|
||||||
|
object=self.to_activity()
|
||||||
|
).serialize()
|
||||||
|
|
||||||
|
|
||||||
|
class OrderedCollectionPageMixin(ObjectMixin):
|
||||||
|
''' just the paginator utilities, so you don't HAVE to
|
||||||
|
override ActivitypubMixin's to_activity (ie, for outbox) '''
|
||||||
|
@property
|
||||||
|
def collection_remote_id(self):
|
||||||
|
''' this can be overriden if there's a special remote id, ie outbox '''
|
||||||
|
return self.remote_id
|
||||||
|
|
||||||
|
|
||||||
|
def to_ordered_collection(self, queryset, \
|
||||||
|
remote_id=None, page=False, collection_only=False, **kwargs):
|
||||||
|
''' an ordered collection of whatevers '''
|
||||||
|
if not queryset.ordered:
|
||||||
|
raise RuntimeError('queryset must be ordered')
|
||||||
|
|
||||||
|
remote_id = remote_id or self.remote_id
|
||||||
|
if page:
|
||||||
|
return to_ordered_collection_page(
|
||||||
|
queryset, remote_id, **kwargs)
|
||||||
|
|
||||||
|
if collection_only or not hasattr(self, 'activity_serializer'):
|
||||||
|
serializer = activitypub.OrderedCollection
|
||||||
|
activity = {}
|
||||||
|
else:
|
||||||
|
serializer = self.activity_serializer
|
||||||
|
# a dict from the model fields
|
||||||
|
activity = generate_activity(self)
|
||||||
|
|
||||||
|
if remote_id:
|
||||||
|
activity['id'] = remote_id
|
||||||
|
|
||||||
|
paginated = Paginator(queryset, PAGE_LENGTH)
|
||||||
|
# add computed fields specific to orderd collections
|
||||||
|
activity['totalItems'] = paginated.count
|
||||||
|
activity['first'] = '%s?page=1' % remote_id
|
||||||
|
activity['last'] = '%s?page=%d' % (remote_id, paginated.num_pages)
|
||||||
|
|
||||||
|
return serializer(**activity).serialize()
|
||||||
|
|
||||||
|
|
||||||
|
class OrderedCollectionMixin(OrderedCollectionPageMixin):
|
||||||
|
''' extends activitypub models to work as ordered collections '''
|
||||||
|
@property
|
||||||
|
def collection_queryset(self):
|
||||||
|
''' usually an ordered collection model aggregates a different model '''
|
||||||
|
raise NotImplementedError('Model must define collection_queryset')
|
||||||
|
|
||||||
|
activity_serializer = activitypub.OrderedCollection
|
||||||
|
|
||||||
|
def to_activity(self, **kwargs):
|
||||||
|
''' an ordered collection of the specified model queryset '''
|
||||||
|
return self.to_ordered_collection(self.collection_queryset, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class CollectionItemMixin(ActivitypubMixin):
|
||||||
|
''' for items that are part of an (Ordered)Collection '''
|
||||||
|
activity_serializer = activitypub.Add
|
||||||
|
object_field = collection_field = None
|
||||||
|
|
||||||
|
def save(self, *args, broadcast=True, **kwargs):
|
||||||
|
''' broadcast updated '''
|
||||||
|
created = not bool(self.id)
|
||||||
|
# first off, we want to save normally no matter what
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
# these shouldn't be edited, only created and deleted
|
||||||
|
if not broadcast or not created or not self.user.local:
|
||||||
|
return
|
||||||
|
|
||||||
|
# adding an obj to the collection
|
||||||
|
activity = self.to_add_activity()
|
||||||
|
self.broadcast(activity, self.user)
|
||||||
|
|
||||||
|
|
||||||
|
def delete(self, *args, **kwargs):
|
||||||
|
''' broadcast a remove activity '''
|
||||||
|
activity = self.to_remove_activity()
|
||||||
|
super().delete(*args, **kwargs)
|
||||||
|
self.broadcast(activity, self.user)
|
||||||
|
|
||||||
|
|
||||||
|
def to_add_activity(self):
|
||||||
|
''' AP for shelving a book'''
|
||||||
|
object_field = getattr(self, self.object_field)
|
||||||
|
collection_field = getattr(self, self.collection_field)
|
||||||
|
return activitypub.Add(
|
||||||
|
id='%s#add' % self.remote_id,
|
||||||
|
actor=self.user.remote_id,
|
||||||
|
object=object_field.to_activity(),
|
||||||
|
target=collection_field.remote_id
|
||||||
|
).serialize()
|
||||||
|
|
||||||
|
def to_remove_activity(self):
|
||||||
|
''' AP for un-shelving a book'''
|
||||||
|
object_field = getattr(self, self.object_field)
|
||||||
|
collection_field = getattr(self, self.collection_field)
|
||||||
|
return activitypub.Remove(
|
||||||
|
id='%s#remove' % self.remote_id,
|
||||||
|
actor=self.user.remote_id,
|
||||||
|
object=object_field.to_activity(),
|
||||||
|
target=collection_field.remote_id
|
||||||
|
).serialize()
|
||||||
|
|
||||||
|
|
||||||
|
class ActivityMixin(ActivitypubMixin):
|
||||||
|
''' add this mixin for models that are AP serializable '''
|
||||||
|
def save(self, *args, broadcast=True, **kwargs):
|
||||||
|
''' broadcast activity '''
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
user = self.user if hasattr(self, 'user') else self.user_subject
|
||||||
|
if broadcast and user.local:
|
||||||
|
self.broadcast(self.to_activity(), user)
|
||||||
|
|
||||||
|
|
||||||
|
def delete(self, *args, broadcast=True, **kwargs):
|
||||||
|
''' nevermind, undo that activity '''
|
||||||
|
user = self.user if hasattr(self, 'user') else self.user_subject
|
||||||
|
if broadcast and user.local:
|
||||||
|
self.broadcast(self.to_undo_activity(), user)
|
||||||
|
super().delete(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def to_undo_activity(self):
|
||||||
|
''' undo an action '''
|
||||||
|
user = self.user if hasattr(self, 'user') else self.user_subject
|
||||||
|
return activitypub.Undo(
|
||||||
|
id='%s#undo' % self.remote_id,
|
||||||
|
actor=user.remote_id,
|
||||||
|
object=self.to_activity()
|
||||||
|
).serialize()
|
||||||
|
|
||||||
|
|
||||||
|
def generate_activity(obj):
|
||||||
|
''' go through the fields on an object '''
|
||||||
|
activity = {}
|
||||||
|
for field in obj.activity_fields:
|
||||||
|
field.set_activity_from_field(activity, obj)
|
||||||
|
|
||||||
|
if hasattr(obj, 'serialize_reverse_fields'):
|
||||||
|
# for example, editions of a work
|
||||||
|
for model_field_name, activity_field_name, sort_field in \
|
||||||
|
obj.serialize_reverse_fields:
|
||||||
|
related_field = getattr(obj, model_field_name)
|
||||||
|
activity[activity_field_name] = \
|
||||||
|
unfurl_related_field(related_field, sort_field)
|
||||||
|
|
||||||
|
if not activity.get('id'):
|
||||||
|
activity['id'] = obj.get_remote_id()
|
||||||
|
return activity
|
||||||
|
|
||||||
|
|
||||||
|
def unfurl_related_field(related_field, sort_field=None):
|
||||||
|
''' load reverse lookups (like public key owner or Status attachment '''
|
||||||
|
if hasattr(related_field, 'all'):
|
||||||
|
return [unfurl_related_field(i) for i in related_field.order_by(
|
||||||
|
sort_field).all()]
|
||||||
|
if related_field.reverse_unfurl:
|
||||||
|
return related_field.field_to_activity()
|
||||||
|
return related_field.remote_id
|
||||||
|
|
||||||
|
|
||||||
|
@app.task
|
||||||
|
def broadcast_task(sender_id, activity, recipients):
|
||||||
|
''' the celery task for broadcast '''
|
||||||
|
user_model = apps.get_model('bookwyrm.User', require_ready=True)
|
||||||
|
sender = user_model.objects.get(id=sender_id)
|
||||||
|
for recipient in recipients:
|
||||||
|
try:
|
||||||
|
sign_and_send(sender, activity, recipient)
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
logger.exception(e)
|
||||||
|
|
||||||
|
|
||||||
|
def sign_and_send(sender, data, destination):
|
||||||
|
''' crpyto whatever and http junk '''
|
||||||
|
now = http_date()
|
||||||
|
|
||||||
|
if not sender.key_pair.private_key:
|
||||||
|
# this shouldn't happen. it would be bad if it happened.
|
||||||
|
raise ValueError('No private key found for sender')
|
||||||
|
|
||||||
|
digest = make_digest(data)
|
||||||
|
|
||||||
|
response = requests.post(
|
||||||
|
destination,
|
||||||
|
data=data,
|
||||||
|
headers={
|
||||||
|
'Date': now,
|
||||||
|
'Digest': digest,
|
||||||
|
'Signature': make_signature(sender, destination, now, digest),
|
||||||
|
'Content-Type': 'application/activity+json; charset=utf-8',
|
||||||
|
'User-Agent': USER_AGENT,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if not response.ok:
|
||||||
|
response.raise_for_status()
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def to_ordered_collection_page(
|
||||||
|
queryset, remote_id, id_only=False, page=1, **kwargs):
|
||||||
|
''' serialize and pagiante a queryset '''
|
||||||
|
paginated = Paginator(queryset, PAGE_LENGTH)
|
||||||
|
|
||||||
|
activity_page = paginated.page(page)
|
||||||
|
if id_only:
|
||||||
|
items = [s.remote_id for s in activity_page.object_list]
|
||||||
|
else:
|
||||||
|
items = [s.to_activity() for s in activity_page.object_list]
|
||||||
|
|
||||||
|
prev_page = next_page = None
|
||||||
|
if activity_page.has_next():
|
||||||
|
next_page = '%s?page=%d' % (remote_id, activity_page.next_page_number())
|
||||||
|
if activity_page.has_previous():
|
||||||
|
prev_page = '%s?page=%d' % \
|
||||||
|
(remote_id, activity_page.previous_page_number())
|
||||||
|
return activitypub.OrderedCollectionPage(
|
||||||
|
id='%s?page=%s' % (remote_id, page),
|
||||||
|
partOf=remote_id,
|
||||||
|
orderedItems=items,
|
||||||
|
next=next_page,
|
||||||
|
prev=prev_page
|
||||||
|
).serialize()
|
|
@ -2,7 +2,7 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from bookwyrm import activitypub
|
from bookwyrm import activitypub
|
||||||
from .base_model import ActivitypubMixin
|
from .activitypub_mixin import ActivitypubMixin
|
||||||
from .base_model import BookWyrmModel
|
from .base_model import BookWyrmModel
|
||||||
from . import fields
|
from . import fields
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,9 @@
|
||||||
''' base model with default fields '''
|
''' base model with default fields '''
|
||||||
from base64 import b64encode
|
|
||||||
from functools import reduce
|
|
||||||
import operator
|
|
||||||
from uuid import uuid4
|
|
||||||
|
|
||||||
from Crypto.PublicKey import RSA
|
|
||||||
from Crypto.Signature import pkcs1_15
|
|
||||||
from Crypto.Hash import SHA256
|
|
||||||
from django.core.paginator import Paginator
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q
|
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
|
||||||
from bookwyrm import activitypub
|
from bookwyrm.settings import DOMAIN
|
||||||
from bookwyrm.settings import DOMAIN, PAGE_LENGTH
|
from .fields import RemoteIdField
|
||||||
from .fields import ImageField, ManyToManyField, RemoteIdField
|
|
||||||
|
|
||||||
|
|
||||||
class BookWyrmModel(models.Model):
|
class BookWyrmModel(models.Model):
|
||||||
|
@ -49,254 +38,7 @@ def execute_after_save(sender, instance, created, *args, **kwargs):
|
||||||
return
|
return
|
||||||
if not instance.remote_id:
|
if not instance.remote_id:
|
||||||
instance.remote_id = instance.get_remote_id()
|
instance.remote_id = instance.get_remote_id()
|
||||||
|
try:
|
||||||
|
instance.save(broadcast=False)
|
||||||
|
except TypeError:
|
||||||
instance.save()
|
instance.save()
|
||||||
|
|
||||||
|
|
||||||
def unfurl_related_field(related_field, sort_field=None):
|
|
||||||
''' load reverse lookups (like public key owner or Status attachment '''
|
|
||||||
if hasattr(related_field, 'all'):
|
|
||||||
return [unfurl_related_field(i) for i in related_field.order_by(
|
|
||||||
sort_field).all()]
|
|
||||||
if related_field.reverse_unfurl:
|
|
||||||
return related_field.field_to_activity()
|
|
||||||
return related_field.remote_id
|
|
||||||
|
|
||||||
|
|
||||||
class ActivitypubMixin:
|
|
||||||
''' add this mixin for models that are AP serializable '''
|
|
||||||
activity_serializer = lambda: {}
|
|
||||||
reverse_unfurl = False
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
''' collect some info on model fields '''
|
|
||||||
self.image_fields = []
|
|
||||||
self.many_to_many_fields = []
|
|
||||||
self.simple_fields = [] # "simple"
|
|
||||||
for field in self._meta.get_fields():
|
|
||||||
if not hasattr(field, 'field_to_activity'):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if isinstance(field, ImageField):
|
|
||||||
self.image_fields.append(field)
|
|
||||||
elif isinstance(field, ManyToManyField):
|
|
||||||
self.many_to_many_fields.append(field)
|
|
||||||
else:
|
|
||||||
self.simple_fields.append(field)
|
|
||||||
|
|
||||||
self.activity_fields = self.image_fields + \
|
|
||||||
self.many_to_many_fields + self.simple_fields
|
|
||||||
|
|
||||||
self.deserialize_reverse_fields = self.deserialize_reverse_fields \
|
|
||||||
if hasattr(self, 'deserialize_reverse_fields') else []
|
|
||||||
self.serialize_reverse_fields = self.serialize_reverse_fields \
|
|
||||||
if hasattr(self, 'serialize_reverse_fields') else []
|
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def find_existing_by_remote_id(cls, remote_id):
|
|
||||||
''' look up a remote id in the db '''
|
|
||||||
return cls.find_existing({'id': remote_id})
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def find_existing(cls, data):
|
|
||||||
''' compare data to fields that can be used for deduplation.
|
|
||||||
This always includes remote_id, but can also be unique identifiers
|
|
||||||
like an isbn for an edition '''
|
|
||||||
filters = []
|
|
||||||
for field in cls._meta.get_fields():
|
|
||||||
if not hasattr(field, 'deduplication_field') or \
|
|
||||||
not field.deduplication_field:
|
|
||||||
continue
|
|
||||||
|
|
||||||
value = data.get(field.get_activitypub_field())
|
|
||||||
if not value:
|
|
||||||
continue
|
|
||||||
filters.append({field.name: value})
|
|
||||||
|
|
||||||
if hasattr(cls, 'origin_id') and 'id' in data:
|
|
||||||
# kinda janky, but this handles special case for books
|
|
||||||
filters.append({'origin_id': data['id']})
|
|
||||||
|
|
||||||
if not filters:
|
|
||||||
# if there are no deduplication fields, it will match the first
|
|
||||||
# item no matter what. this shouldn't happen but just in case.
|
|
||||||
return None
|
|
||||||
|
|
||||||
objects = cls.objects
|
|
||||||
if hasattr(objects, 'select_subclasses'):
|
|
||||||
objects = objects.select_subclasses()
|
|
||||||
|
|
||||||
# an OR operation on all the match fields
|
|
||||||
match = objects.filter(
|
|
||||||
reduce(
|
|
||||||
operator.or_, (Q(**f) for f in filters)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
# there OUGHT to be only one match
|
|
||||||
return match.first()
|
|
||||||
|
|
||||||
|
|
||||||
def to_activity(self):
|
|
||||||
''' convert from a model to an activity '''
|
|
||||||
activity = generate_activity(self)
|
|
||||||
return self.activity_serializer(**activity).serialize()
|
|
||||||
|
|
||||||
|
|
||||||
def to_create_activity(self, user, **kwargs):
|
|
||||||
''' returns the object wrapped in a Create activity '''
|
|
||||||
activity_object = self.to_activity(**kwargs)
|
|
||||||
|
|
||||||
signature = None
|
|
||||||
create_id = self.remote_id + '/activity'
|
|
||||||
if 'content' in activity_object:
|
|
||||||
signer = pkcs1_15.new(RSA.import_key(user.key_pair.private_key))
|
|
||||||
content = activity_object['content']
|
|
||||||
signed_message = signer.sign(SHA256.new(content.encode('utf8')))
|
|
||||||
|
|
||||||
signature = activitypub.Signature(
|
|
||||||
creator='%s#main-key' % user.remote_id,
|
|
||||||
created=activity_object['published'],
|
|
||||||
signatureValue=b64encode(signed_message).decode('utf8')
|
|
||||||
)
|
|
||||||
|
|
||||||
return activitypub.Create(
|
|
||||||
id=create_id,
|
|
||||||
actor=user.remote_id,
|
|
||||||
to=activity_object['to'],
|
|
||||||
cc=activity_object['cc'],
|
|
||||||
object=activity_object,
|
|
||||||
signature=signature,
|
|
||||||
).serialize()
|
|
||||||
|
|
||||||
|
|
||||||
def to_delete_activity(self, user):
|
|
||||||
''' notice of deletion '''
|
|
||||||
return activitypub.Delete(
|
|
||||||
id=self.remote_id + '/activity',
|
|
||||||
actor=user.remote_id,
|
|
||||||
to=['%s/followers' % user.remote_id],
|
|
||||||
cc=['https://www.w3.org/ns/activitystreams#Public'],
|
|
||||||
object=self.to_activity(),
|
|
||||||
).serialize()
|
|
||||||
|
|
||||||
|
|
||||||
def to_update_activity(self, user):
|
|
||||||
''' wrapper for Updates to an activity '''
|
|
||||||
activity_id = '%s#update/%s' % (self.remote_id, uuid4())
|
|
||||||
return activitypub.Update(
|
|
||||||
id=activity_id,
|
|
||||||
actor=user.remote_id,
|
|
||||||
to=['https://www.w3.org/ns/activitystreams#Public'],
|
|
||||||
object=self.to_activity()
|
|
||||||
).serialize()
|
|
||||||
|
|
||||||
|
|
||||||
def to_undo_activity(self, user):
|
|
||||||
''' undo an action '''
|
|
||||||
return activitypub.Undo(
|
|
||||||
id='%s#undo' % self.remote_id,
|
|
||||||
actor=user.remote_id,
|
|
||||||
object=self.to_activity()
|
|
||||||
).serialize()
|
|
||||||
|
|
||||||
|
|
||||||
class OrderedCollectionPageMixin(ActivitypubMixin):
|
|
||||||
''' just the paginator utilities, so you don't HAVE to
|
|
||||||
override ActivitypubMixin's to_activity (ie, for outbox '''
|
|
||||||
@property
|
|
||||||
def collection_remote_id(self):
|
|
||||||
''' this can be overriden if there's a special remote id, ie outbox '''
|
|
||||||
return self.remote_id
|
|
||||||
|
|
||||||
|
|
||||||
def to_ordered_collection(self, queryset, \
|
|
||||||
remote_id=None, page=False, collection_only=False, **kwargs):
|
|
||||||
''' an ordered collection of whatevers '''
|
|
||||||
if not queryset.ordered:
|
|
||||||
raise RuntimeError('queryset must be ordered')
|
|
||||||
|
|
||||||
remote_id = remote_id or self.remote_id
|
|
||||||
if page:
|
|
||||||
return to_ordered_collection_page(
|
|
||||||
queryset, remote_id, **kwargs)
|
|
||||||
|
|
||||||
if collection_only or not hasattr(self, 'activity_serializer'):
|
|
||||||
serializer = activitypub.OrderedCollection
|
|
||||||
activity = {}
|
|
||||||
else:
|
|
||||||
serializer = self.activity_serializer
|
|
||||||
# a dict from the model fields
|
|
||||||
activity = generate_activity(self)
|
|
||||||
|
|
||||||
if remote_id:
|
|
||||||
activity['id'] = remote_id
|
|
||||||
|
|
||||||
paginated = Paginator(queryset, PAGE_LENGTH)
|
|
||||||
# add computed fields specific to orderd collections
|
|
||||||
activity['totalItems'] = paginated.count
|
|
||||||
activity['first'] = '%s?page=1' % remote_id
|
|
||||||
activity['last'] = '%s?page=%d' % (remote_id, paginated.num_pages)
|
|
||||||
|
|
||||||
return serializer(**activity).serialize()
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def to_ordered_collection_page(
|
|
||||||
queryset, remote_id, id_only=False, page=1, **kwargs):
|
|
||||||
''' serialize and pagiante a queryset '''
|
|
||||||
paginated = Paginator(queryset, PAGE_LENGTH)
|
|
||||||
|
|
||||||
activity_page = paginated.page(page)
|
|
||||||
if id_only:
|
|
||||||
items = [s.remote_id for s in activity_page.object_list]
|
|
||||||
else:
|
|
||||||
items = [s.to_activity() for s in activity_page.object_list]
|
|
||||||
|
|
||||||
prev_page = next_page = None
|
|
||||||
if activity_page.has_next():
|
|
||||||
next_page = '%s?page=%d' % (remote_id, activity_page.next_page_number())
|
|
||||||
if activity_page.has_previous():
|
|
||||||
prev_page = '%s?page=%d' % \
|
|
||||||
(remote_id, activity_page.previous_page_number())
|
|
||||||
return activitypub.OrderedCollectionPage(
|
|
||||||
id='%s?page=%s' % (remote_id, page),
|
|
||||||
partOf=remote_id,
|
|
||||||
orderedItems=items,
|
|
||||||
next=next_page,
|
|
||||||
prev=prev_page
|
|
||||||
).serialize()
|
|
||||||
|
|
||||||
|
|
||||||
class OrderedCollectionMixin(OrderedCollectionPageMixin):
|
|
||||||
''' extends activitypub models to work as ordered collections '''
|
|
||||||
@property
|
|
||||||
def collection_queryset(self):
|
|
||||||
''' usually an ordered collection model aggregates a different model '''
|
|
||||||
raise NotImplementedError('Model must define collection_queryset')
|
|
||||||
|
|
||||||
activity_serializer = activitypub.OrderedCollection
|
|
||||||
|
|
||||||
def to_activity(self, **kwargs):
|
|
||||||
''' an ordered collection of the specified model queryset '''
|
|
||||||
return self.to_ordered_collection(self.collection_queryset, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def generate_activity(obj):
|
|
||||||
''' go through the fields on an object '''
|
|
||||||
activity = {}
|
|
||||||
for field in obj.activity_fields:
|
|
||||||
field.set_activity_from_field(activity, obj)
|
|
||||||
|
|
||||||
if hasattr(obj, 'serialize_reverse_fields'):
|
|
||||||
# for example, editions of a work
|
|
||||||
for model_field_name, activity_field_name, sort_field in \
|
|
||||||
obj.serialize_reverse_fields:
|
|
||||||
related_field = getattr(obj, model_field_name)
|
|
||||||
activity[activity_field_name] = \
|
|
||||||
unfurl_related_field(related_field, sort_field)
|
|
||||||
|
|
||||||
if not activity.get('id'):
|
|
||||||
activity['id'] = obj.get_remote_id()
|
|
||||||
return activity
|
|
||||||
|
|
|
@ -7,11 +7,11 @@ from model_utils.managers import InheritanceManager
|
||||||
from bookwyrm import activitypub
|
from bookwyrm import activitypub
|
||||||
from bookwyrm.settings import DOMAIN
|
from bookwyrm.settings import DOMAIN
|
||||||
|
|
||||||
|
from .activitypub_mixin import OrderedCollectionPageMixin, ObjectMixin
|
||||||
from .base_model import BookWyrmModel
|
from .base_model import BookWyrmModel
|
||||||
from .base_model import ActivitypubMixin, OrderedCollectionPageMixin
|
|
||||||
from . import fields
|
from . import fields
|
||||||
|
|
||||||
class BookDataModel(ActivitypubMixin, BookWyrmModel):
|
class BookDataModel(ObjectMixin, BookWyrmModel):
|
||||||
''' fields shared between editable book data (books, works, authors) '''
|
''' fields shared between editable book data (books, works, authors) '''
|
||||||
origin_id = models.CharField(max_length=255, null=True, blank=True)
|
origin_id = models.CharField(max_length=255, null=True, blank=True)
|
||||||
openlibrary_key = fields.CharField(
|
openlibrary_key = fields.CharField(
|
||||||
|
@ -74,6 +74,7 @@ class Book(BookDataModel):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def latest_readthrough(self):
|
def latest_readthrough(self):
|
||||||
|
''' most recent readthrough activity '''
|
||||||
return self.readthrough_set.order_by('-updated_date').first()
|
return self.readthrough_set.order_by('-updated_date').first()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -3,10 +3,11 @@ from django.db import models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from bookwyrm import activitypub
|
from bookwyrm import activitypub
|
||||||
from .base_model import ActivitypubMixin, BookWyrmModel
|
from .activitypub_mixin import ActivityMixin
|
||||||
|
from .base_model import BookWyrmModel
|
||||||
from . import fields
|
from . import fields
|
||||||
|
|
||||||
class Favorite(ActivitypubMixin, BookWyrmModel):
|
class Favorite(ActivityMixin, BookWyrmModel):
|
||||||
''' fav'ing a post '''
|
''' fav'ing a post '''
|
||||||
user = fields.ForeignKey(
|
user = fields.ForeignKey(
|
||||||
'User', on_delete=models.PROTECT, activitypub_field='actor')
|
'User', on_delete=models.PROTECT, activitypub_field='actor')
|
||||||
|
@ -18,7 +19,7 @@ class Favorite(ActivitypubMixin, 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()
|
self.user.save(broadcast=False)
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -3,8 +3,8 @@ from django.db import models
|
||||||
|
|
||||||
from bookwyrm import activitypub
|
from bookwyrm import activitypub
|
||||||
from bookwyrm.settings import DOMAIN
|
from bookwyrm.settings import DOMAIN
|
||||||
from .base_model import ActivitypubMixin, BookWyrmModel
|
from .activitypub_mixin import CollectionItemMixin, OrderedCollectionMixin
|
||||||
from .base_model import OrderedCollectionMixin
|
from .base_model import BookWyrmModel
|
||||||
from . import fields
|
from . import fields
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,13 +51,13 @@ class List(OrderedCollectionMixin, BookWyrmModel):
|
||||||
ordering = ('-updated_date',)
|
ordering = ('-updated_date',)
|
||||||
|
|
||||||
|
|
||||||
class ListItem(ActivitypubMixin, BookWyrmModel):
|
class ListItem(CollectionItemMixin, BookWyrmModel):
|
||||||
''' ok '''
|
''' ok '''
|
||||||
book = fields.ForeignKey(
|
book = fields.ForeignKey(
|
||||||
'Edition', on_delete=models.PROTECT, activitypub_field='object')
|
'Edition', on_delete=models.PROTECT, activitypub_field='object')
|
||||||
book_list = fields.ForeignKey(
|
book_list = fields.ForeignKey(
|
||||||
'List', on_delete=models.CASCADE, activitypub_field='target')
|
'List', on_delete=models.CASCADE, activitypub_field='target')
|
||||||
added_by = fields.ForeignKey(
|
user = fields.ForeignKey(
|
||||||
'User',
|
'User',
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
activitypub_field='actor'
|
activitypub_field='actor'
|
||||||
|
@ -68,24 +68,8 @@ class ListItem(ActivitypubMixin, BookWyrmModel):
|
||||||
endorsement = models.ManyToManyField('User', related_name='endorsers')
|
endorsement = models.ManyToManyField('User', related_name='endorsers')
|
||||||
|
|
||||||
activity_serializer = activitypub.AddBook
|
activity_serializer = activitypub.AddBook
|
||||||
|
object_field = 'book'
|
||||||
def to_add_activity(self, user):
|
collection_field = 'book_list'
|
||||||
''' AP for shelving a book'''
|
|
||||||
return activitypub.Add(
|
|
||||||
id='%s#add' % self.remote_id,
|
|
||||||
actor=user.remote_id,
|
|
||||||
object=self.book.to_activity(),
|
|
||||||
target=self.book_list.remote_id,
|
|
||||||
).serialize()
|
|
||||||
|
|
||||||
def to_remove_activity(self, user):
|
|
||||||
''' AP for un-shelving a book'''
|
|
||||||
return activitypub.Remove(
|
|
||||||
id='%s#remove' % self.remote_id,
|
|
||||||
actor=user.remote_id,
|
|
||||||
object=self.book.to_activity(),
|
|
||||||
target=self.book_list.remote_id
|
|
||||||
).serialize()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
''' an opinionated constraint! you can't put a book on a list twice '''
|
''' an opinionated constraint! you can't put a book on a list twice '''
|
||||||
|
|
|
@ -31,7 +31,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()
|
self.user.save(broadcast=False)
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
def create_update(self):
|
def create_update(self):
|
||||||
|
@ -54,5 +54,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()
|
self.user.save(broadcast=False)
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
''' defines relationships between users '''
|
''' defines relationships between users '''
|
||||||
from django.db import models
|
from django.db import models, transaction
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
|
||||||
from bookwyrm import activitypub
|
from bookwyrm import activitypub
|
||||||
from .base_model import ActivitypubMixin, BookWyrmModel
|
from .activitypub_mixin import ActivitypubMixin, ActivityMixin
|
||||||
|
from .base_model import BookWyrmModel
|
||||||
from . import fields
|
from . import fields
|
||||||
|
|
||||||
|
|
||||||
class UserRelationship(ActivitypubMixin, BookWyrmModel):
|
class UserRelationship(BookWyrmModel):
|
||||||
''' many-to-many through table for followers '''
|
''' many-to-many through table for followers '''
|
||||||
user_subject = fields.ForeignKey(
|
user_subject = fields.ForeignKey(
|
||||||
'User',
|
'User',
|
||||||
|
@ -23,6 +24,16 @@ class UserRelationship(ActivitypubMixin, BookWyrmModel):
|
||||||
activitypub_field='object',
|
activitypub_field='object',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def privacy(self):
|
||||||
|
''' all relationships are handled directly with the participants '''
|
||||||
|
return 'direct'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def recipients(self):
|
||||||
|
''' the remote user needs to recieve direct broadcasts '''
|
||||||
|
return [u for u in [self.user_subject, self.user_object] if not u.local]
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
''' relationships should be unique '''
|
''' relationships should be unique '''
|
||||||
abstract = True
|
abstract = True
|
||||||
|
@ -37,8 +48,6 @@ class UserRelationship(ActivitypubMixin, BookWyrmModel):
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
activity_serializer = activitypub.Follow
|
|
||||||
|
|
||||||
def get_remote_id(self, status=None):# pylint: disable=arguments-differ
|
def get_remote_id(self, status=None):# pylint: disable=arguments-differ
|
||||||
''' use shelf identifier in remote_id '''
|
''' use shelf identifier in remote_id '''
|
||||||
status = status or 'follows'
|
status = status or 'follows'
|
||||||
|
@ -46,55 +55,73 @@ class UserRelationship(ActivitypubMixin, BookWyrmModel):
|
||||||
return '%s#%s/%d' % (base_path, status, self.id)
|
return '%s#%s/%d' % (base_path, status, self.id)
|
||||||
|
|
||||||
|
|
||||||
def to_accept_activity(self):
|
class UserFollows(ActivitypubMixin, UserRelationship):
|
||||||
''' generate an Accept for this follow request '''
|
|
||||||
return activitypub.Accept(
|
|
||||||
id=self.get_remote_id(status='accepts'),
|
|
||||||
actor=self.user_object.remote_id,
|
|
||||||
object=self.to_activity()
|
|
||||||
).serialize()
|
|
||||||
|
|
||||||
|
|
||||||
def to_reject_activity(self):
|
|
||||||
''' generate a Reject for this follow request '''
|
|
||||||
return activitypub.Reject(
|
|
||||||
id=self.get_remote_id(status='rejects'),
|
|
||||||
actor=self.user_object.remote_id,
|
|
||||||
object=self.to_activity()
|
|
||||||
).serialize()
|
|
||||||
|
|
||||||
|
|
||||||
class UserFollows(UserRelationship):
|
|
||||||
''' Following a user '''
|
''' Following a user '''
|
||||||
status = 'follows'
|
status = 'follows'
|
||||||
|
activity_serializer = activitypub.Follow
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_request(cls, follow_request):
|
def from_request(cls, follow_request):
|
||||||
''' converts a follow request into a follow relationship '''
|
''' converts a follow request into a follow relationship '''
|
||||||
return cls(
|
return cls.objects.create(
|
||||||
user_subject=follow_request.user_subject,
|
user_subject=follow_request.user_subject,
|
||||||
user_object=follow_request.user_object,
|
user_object=follow_request.user_object,
|
||||||
remote_id=follow_request.remote_id,
|
remote_id=follow_request.remote_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class UserFollowRequest(UserRelationship):
|
class UserFollowRequest(ActivitypubMixin, UserRelationship):
|
||||||
''' following a user requires manual or automatic confirmation '''
|
''' following a user requires manual or automatic confirmation '''
|
||||||
status = 'follow_request'
|
status = 'follow_request'
|
||||||
|
activity_serializer = activitypub.Follow
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, broadcast=True, **kwargs):
|
||||||
''' make sure the follow relationship doesn't already exist '''
|
''' make sure the follow or block relationship doesn't already exist '''
|
||||||
try:
|
try:
|
||||||
UserFollows.objects.get(
|
UserFollows.objects.get(
|
||||||
user_subject=self.user_subject,
|
user_subject=self.user_subject,
|
||||||
user_object=self.user_object
|
user_object=self.user_object
|
||||||
)
|
)
|
||||||
|
UserBlocks.objects.get(
|
||||||
|
user_subject=self.user_subject,
|
||||||
|
user_object=self.user_object
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
except UserFollows.DoesNotExist:
|
except (UserFollows.DoesNotExist, UserBlocks.DoesNotExist):
|
||||||
return super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
if broadcast and self.user_subject.local and not self.user_object.local:
|
||||||
|
self.broadcast(self.to_activity(), self.user_subject)
|
||||||
|
|
||||||
|
|
||||||
class UserBlocks(UserRelationship):
|
def accept(self):
|
||||||
|
''' turn this request into the real deal'''
|
||||||
|
user = self.user_object
|
||||||
|
activity = activitypub.Accept(
|
||||||
|
id=self.get_remote_id(status='accepts'),
|
||||||
|
actor=self.user_object.remote_id,
|
||||||
|
object=self.to_activity()
|
||||||
|
).serialize()
|
||||||
|
with transaction.atomic():
|
||||||
|
UserFollows.from_request(self)
|
||||||
|
self.delete()
|
||||||
|
|
||||||
|
self.broadcast(activity, user)
|
||||||
|
|
||||||
|
|
||||||
|
def reject(self):
|
||||||
|
''' generate a Reject for this follow request '''
|
||||||
|
user = self.user_object
|
||||||
|
activity = activitypub.Reject(
|
||||||
|
id=self.get_remote_id(status='rejects'),
|
||||||
|
actor=self.user_object.remote_id,
|
||||||
|
object=self.to_activity()
|
||||||
|
).serialize()
|
||||||
|
self.delete()
|
||||||
|
self.broadcast(activity, user)
|
||||||
|
|
||||||
|
|
||||||
|
class UserBlocks(ActivityMixin, UserRelationship):
|
||||||
''' prevent another user from following you and seeing your posts '''
|
''' prevent another user from following you and seeing your posts '''
|
||||||
status = 'blocks'
|
status = 'blocks'
|
||||||
activity_serializer = activitypub.Block
|
activity_serializer = activitypub.Block
|
||||||
|
|
|
@ -3,8 +3,8 @@ import re
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from bookwyrm import activitypub
|
from bookwyrm import activitypub
|
||||||
from .base_model import ActivitypubMixin, BookWyrmModel
|
from .activitypub_mixin import CollectionItemMixin, OrderedCollectionMixin
|
||||||
from .base_model import OrderedCollectionMixin
|
from .base_model import BookWyrmModel
|
||||||
from . import fields
|
from . import fields
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,12 +27,11 @@ class Shelf(OrderedCollectionMixin, BookWyrmModel):
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
''' set the identifier '''
|
''' set the identifier '''
|
||||||
saved = super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
if not self.identifier:
|
if not self.identifier:
|
||||||
slug = re.sub(r'[^\w]', '', self.name).lower()
|
slug = re.sub(r'[^\w]', '', self.name).lower()
|
||||||
self.identifier = '%s-%d' % (slug, self.id)
|
self.identifier = '%s-%d' % (slug, self.id)
|
||||||
return super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
return saved
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def collection_queryset(self):
|
def collection_queryset(self):
|
||||||
|
@ -49,39 +48,18 @@ class Shelf(OrderedCollectionMixin, BookWyrmModel):
|
||||||
unique_together = ('user', 'identifier')
|
unique_together = ('user', 'identifier')
|
||||||
|
|
||||||
|
|
||||||
class ShelfBook(ActivitypubMixin, BookWyrmModel):
|
class ShelfBook(CollectionItemMixin, BookWyrmModel):
|
||||||
''' many to many join table for books and shelves '''
|
''' many to many join table for books and shelves '''
|
||||||
book = fields.ForeignKey(
|
book = fields.ForeignKey(
|
||||||
'Edition', on_delete=models.PROTECT, activitypub_field='object')
|
'Edition', on_delete=models.PROTECT, activitypub_field='object')
|
||||||
shelf = fields.ForeignKey(
|
shelf = fields.ForeignKey(
|
||||||
'Shelf', on_delete=models.PROTECT, activitypub_field='target')
|
'Shelf', on_delete=models.PROTECT, activitypub_field='target')
|
||||||
added_by = fields.ForeignKey(
|
user = fields.ForeignKey(
|
||||||
'User',
|
'User', on_delete=models.PROTECT, activitypub_field='actor')
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
on_delete=models.PROTECT,
|
|
||||||
activitypub_field='actor'
|
|
||||||
)
|
|
||||||
|
|
||||||
activity_serializer = activitypub.AddBook
|
activity_serializer = activitypub.AddBook
|
||||||
|
object_field = 'book'
|
||||||
def to_add_activity(self, user):
|
collection_field = 'shelf'
|
||||||
''' AP for shelving a book'''
|
|
||||||
return activitypub.Add(
|
|
||||||
id='%s#add' % self.remote_id,
|
|
||||||
actor=user.remote_id,
|
|
||||||
object=self.book.to_activity(),
|
|
||||||
target=self.shelf.remote_id,
|
|
||||||
).serialize()
|
|
||||||
|
|
||||||
def to_remove_activity(self, user):
|
|
||||||
''' AP for un-shelving a book'''
|
|
||||||
return activitypub.Remove(
|
|
||||||
id='%s#remove' % self.remote_id,
|
|
||||||
actor=user.remote_id,
|
|
||||||
object=self.book.to_activity(),
|
|
||||||
target=self.shelf.to_activity()
|
|
||||||
).serialize()
|
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -9,10 +9,12 @@ from django.utils import timezone
|
||||||
from model_utils.managers import InheritanceManager
|
from model_utils.managers import InheritanceManager
|
||||||
|
|
||||||
from bookwyrm import activitypub
|
from bookwyrm import activitypub
|
||||||
from .base_model import ActivitypubMixin, OrderedCollectionPageMixin
|
from .activitypub_mixin import ActivitypubMixin, ActivityMixin
|
||||||
|
from .activitypub_mixin import OrderedCollectionPageMixin
|
||||||
from .base_model import BookWyrmModel
|
from .base_model import BookWyrmModel
|
||||||
from . import fields
|
|
||||||
from .fields import image_serializer
|
from .fields import image_serializer
|
||||||
|
from . import fields
|
||||||
|
|
||||||
|
|
||||||
class Status(OrderedCollectionPageMixin, BookWyrmModel):
|
class Status(OrderedCollectionPageMixin, BookWyrmModel):
|
||||||
''' any post, like a reply to a review, etc '''
|
''' any post, like a reply to a review, etc '''
|
||||||
|
@ -50,6 +52,15 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
|
||||||
serialize_reverse_fields = [('attachments', 'attachment', 'id')]
|
serialize_reverse_fields = [('attachments', 'attachment', 'id')]
|
||||||
deserialize_reverse_fields = [('attachments', 'attachment')]
|
deserialize_reverse_fields = [('attachments', 'attachment')]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def recipients(self):
|
||||||
|
''' tagged users who definitely need to get this status in broadcast '''
|
||||||
|
mentions = [u for u in self.mention_users.all() if not u.local]
|
||||||
|
if hasattr(self, 'reply_parent') and self.reply_parent \
|
||||||
|
and not self.reply_parent.user.local:
|
||||||
|
mentions.append(self.reply_parent.user)
|
||||||
|
return list(set(mentions))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def ignore_activity(cls, activity):
|
def ignore_activity(cls, activity):
|
||||||
''' keep notes if they are replies to existing statuses '''
|
''' keep notes if they are replies to existing statuses '''
|
||||||
|
@ -126,14 +137,6 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
|
||||||
return activity
|
return activity
|
||||||
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
''' update user active time '''
|
|
||||||
if self.user.local:
|
|
||||||
self.user.last_active_date = timezone.now()
|
|
||||||
self.user.save()
|
|
||||||
return super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class GeneratedNote(Status):
|
class GeneratedNote(Status):
|
||||||
''' these are app-generated messages about user activity '''
|
''' these are app-generated messages about user activity '''
|
||||||
@property
|
@property
|
||||||
|
@ -223,7 +226,7 @@ class Review(Status):
|
||||||
pure_type = 'Article'
|
pure_type = 'Article'
|
||||||
|
|
||||||
|
|
||||||
class Boost(Status):
|
class Boost(ActivityMixin, Status):
|
||||||
''' boost'ing a post '''
|
''' boost'ing a post '''
|
||||||
boosted_status = fields.ForeignKey(
|
boosted_status = fields.ForeignKey(
|
||||||
'Status',
|
'Status',
|
||||||
|
@ -231,6 +234,8 @@ class Boost(Status):
|
||||||
related_name='boosters',
|
related_name='boosters',
|
||||||
activitypub_field='object',
|
activitypub_field='object',
|
||||||
)
|
)
|
||||||
|
activity_serializer = activitypub.Boost
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
''' the user field is "actor" here instead of "attributedTo" '''
|
''' the user field is "actor" here instead of "attributedTo" '''
|
||||||
|
@ -244,8 +249,6 @@ class Boost(Status):
|
||||||
self.image_fields = []
|
self.image_fields = []
|
||||||
self.deserialize_reverse_fields = []
|
self.deserialize_reverse_fields = []
|
||||||
|
|
||||||
activity_serializer = activitypub.Boost
|
|
||||||
|
|
||||||
# This constraint can't work as it would cross tables.
|
# This constraint can't work as it would cross tables.
|
||||||
# class Meta:
|
# class Meta:
|
||||||
# unique_together = ('user', 'boosted_status')
|
# unique_together = ('user', 'boosted_status')
|
||||||
|
|
|
@ -5,7 +5,8 @@ from django.db import models
|
||||||
|
|
||||||
from bookwyrm import activitypub
|
from bookwyrm import activitypub
|
||||||
from bookwyrm.settings import DOMAIN
|
from bookwyrm.settings import DOMAIN
|
||||||
from .base_model import OrderedCollectionMixin, BookWyrmModel
|
from .activitypub_mixin import CollectionItemMixin, OrderedCollectionMixin
|
||||||
|
from .base_model import BookWyrmModel
|
||||||
from . import fields
|
from . import fields
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,7 +41,7 @@ class Tag(OrderedCollectionMixin, BookWyrmModel):
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class UserTag(BookWyrmModel):
|
class UserTag(CollectionItemMixin, BookWyrmModel):
|
||||||
''' an instance of a tag on a book by a user '''
|
''' an instance of a tag on a book by a user '''
|
||||||
user = fields.ForeignKey(
|
user = fields.ForeignKey(
|
||||||
'User', on_delete=models.PROTECT, activitypub_field='actor')
|
'User', on_delete=models.PROTECT, activitypub_field='actor')
|
||||||
|
@ -50,25 +51,8 @@ class UserTag(BookWyrmModel):
|
||||||
'Tag', on_delete=models.PROTECT, activitypub_field='target')
|
'Tag', on_delete=models.PROTECT, activitypub_field='target')
|
||||||
|
|
||||||
activity_serializer = activitypub.AddBook
|
activity_serializer = activitypub.AddBook
|
||||||
|
object_field = 'book'
|
||||||
def to_add_activity(self, user):
|
collection_field = 'tag'
|
||||||
''' AP for shelving a book'''
|
|
||||||
return activitypub.Add(
|
|
||||||
id='%s#add' % self.remote_id,
|
|
||||||
actor=user.remote_id,
|
|
||||||
object=self.book.to_activity(),
|
|
||||||
target=self.remote_id,
|
|
||||||
).serialize()
|
|
||||||
|
|
||||||
def to_remove_activity(self, user):
|
|
||||||
''' AP for un-shelving a book'''
|
|
||||||
return activitypub.Remove(
|
|
||||||
id='%s#remove' % self.remote_id,
|
|
||||||
actor=user.remote_id,
|
|
||||||
object=self.book.to_activity(),
|
|
||||||
target=self.remote_id,
|
|
||||||
).serialize()
|
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
''' unqiueness constraint '''
|
''' unqiueness constraint '''
|
||||||
|
|
|
@ -17,8 +17,8 @@ from bookwyrm.settings import DOMAIN
|
||||||
from bookwyrm.signatures import create_key_pair
|
from bookwyrm.signatures import create_key_pair
|
||||||
from bookwyrm.tasks import app
|
from bookwyrm.tasks import app
|
||||||
from bookwyrm.utils import regex
|
from bookwyrm.utils import regex
|
||||||
from .base_model import OrderedCollectionPageMixin
|
from .activitypub_mixin import OrderedCollectionPageMixin, ActivitypubMixin
|
||||||
from .base_model import ActivitypubMixin, BookWyrmModel
|
from .base_model import BookWyrmModel
|
||||||
from .federated_server import FederatedServer
|
from .federated_server import FederatedServer
|
||||||
from . import fields, Review
|
from . import fields, Review
|
||||||
|
|
||||||
|
@ -211,6 +211,9 @@ class KeyPair(ActivitypubMixin, BookWyrmModel):
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
''' create a key pair '''
|
''' create a key pair '''
|
||||||
|
# no broadcasting happening here
|
||||||
|
if 'broadcast' in kwargs:
|
||||||
|
del kwargs['broadcast']
|
||||||
if not self.public_key:
|
if not self.public_key:
|
||||||
self.private_key, self.public_key = create_key_pair()
|
self.private_key, self.public_key = create_key_pair()
|
||||||
return super().save(*args, **kwargs)
|
return super().save(*args, **kwargs)
|
||||||
|
@ -291,7 +294,7 @@ def execute_after_save(sender, instance, created, *args, **kwargs):
|
||||||
|
|
||||||
instance.key_pair = KeyPair.objects.create(
|
instance.key_pair = KeyPair.objects.create(
|
||||||
remote_id='%s/#main-key' % instance.remote_id)
|
remote_id='%s/#main-key' % instance.remote_id)
|
||||||
instance.save()
|
instance.save(broadcast=False)
|
||||||
|
|
||||||
shelves = [{
|
shelves = [{
|
||||||
'name': 'To Read',
|
'name': 'To Read',
|
||||||
|
@ -310,7 +313,7 @@ def execute_after_save(sender, instance, created, *args, **kwargs):
|
||||||
identifier=shelf['identifier'],
|
identifier=shelf['identifier'],
|
||||||
user=instance,
|
user=instance,
|
||||||
editable=False
|
editable=False
|
||||||
).save()
|
).save(broadcast=False)
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
{% include 'snippets/book_titleby.html' with book=item.book %}
|
{% include 'snippets/book_titleby.html' with book=item.book %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% include 'snippets/username.html' with user=item.added_by %}
|
{% include 'snippets/username.html' with user=item.user %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="field has-addons">
|
<div class="field has-addons">
|
||||||
|
|
|
@ -31,9 +31,9 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer has-background-white-bis">
|
<div class="card-footer has-background-white-bis">
|
||||||
<div class="card-footer-item">
|
<div class="card-footer-item">
|
||||||
<p>Added by {% include 'snippets/username.html' with user=item.added_by %}</p>
|
<p>Added by {% include 'snippets/username.html' with user=item.user %}</p>
|
||||||
</div>
|
</div>
|
||||||
{% if list.user == request.user or list.curation == 'open' and item.added_by == request.user %}
|
{% if list.user == request.user or list.curation == 'open' and item.user == request.user %}
|
||||||
<form name="add-book" method="post" action="{% url 'list-remove-book' list.id %}" class="card-footer-item">
|
<form name="add-book" method="post" action="{% url 'list-remove-book' list.id %}" class="card-footer-item">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="item" value="{{ item.id }}">
|
<input type="hidden" name="item" value="{{ item.id }}">
|
||||||
|
|
|
@ -23,7 +23,7 @@ class BaseActivity(TestCase):
|
||||||
'mouse', 'mouse@mouse.mouse', 'mouseword',
|
'mouse', 'mouse@mouse.mouse', 'mouseword',
|
||||||
local=True, localname='mouse')
|
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()
|
self.user.save(broadcast=False)
|
||||||
|
|
||||||
datafile = pathlib.Path(__file__).parent.joinpath(
|
datafile = pathlib.Path(__file__).parent.joinpath(
|
||||||
'../data/ap_user.json'
|
'../data/ap_user.json'
|
||||||
|
@ -167,12 +167,15 @@ class BaseActivity(TestCase):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
self.user.avatar.file #pylint: disable=pointless-statement
|
self.user.avatar.file #pylint: disable=pointless-statement
|
||||||
|
|
||||||
|
# this would trigger a broadcast because it's a local user
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
activity.to_model(models.User, self.user)
|
activity.to_model(models.User, self.user)
|
||||||
self.assertIsNotNone(self.user.avatar.name)
|
self.assertIsNotNone(self.user.avatar.name)
|
||||||
self.assertIsNotNone(self.user.avatar.file)
|
self.assertIsNotNone(self.user.avatar.file)
|
||||||
|
|
||||||
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'):
|
||||||
status = models.Status.objects.create(
|
status = models.Status.objects.create(
|
||||||
content='test status',
|
content='test status',
|
||||||
user=self.user,
|
user=self.user,
|
||||||
|
@ -208,6 +211,7 @@ class BaseActivity(TestCase):
|
||||||
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'):
|
||||||
status = models.Status.objects.create(
|
status = models.Status.objects.create(
|
||||||
content='test status',
|
content='test status',
|
||||||
user=self.user,
|
user=self.user,
|
||||||
|
@ -242,6 +246,7 @@ class BaseActivity(TestCase):
|
||||||
@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'):
|
||||||
status = models.Status.objects.create(
|
status = models.Status.objects.create(
|
||||||
content='test status',
|
content='test status',
|
||||||
user=self.user,
|
user=self.user,
|
||||||
|
|
383
bookwyrm/tests/models/test_activitypub_mixin.py
Normal file
383
bookwyrm/tests/models/test_activitypub_mixin.py
Normal file
|
@ -0,0 +1,383 @@
|
||||||
|
''' testing model activitypub utilities '''
|
||||||
|
from unittest.mock import patch
|
||||||
|
from collections import namedtuple
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import re
|
||||||
|
from django import db
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from bookwyrm.activitypub.base_activity import ActivityObject
|
||||||
|
from bookwyrm import models
|
||||||
|
from bookwyrm.models import base_model
|
||||||
|
from bookwyrm.models.activitypub_mixin import ActivitypubMixin
|
||||||
|
from bookwyrm.models.activitypub_mixin import ActivityMixin, ObjectMixin
|
||||||
|
|
||||||
|
class ActivitypubMixins(TestCase):
|
||||||
|
''' functionality shared across models '''
|
||||||
|
def setUp(self):
|
||||||
|
''' shared data '''
|
||||||
|
self.local_user = models.User.objects.create_user(
|
||||||
|
'mouse', 'mouse@mouse.com', 'mouseword',
|
||||||
|
local=True, localname='mouse')
|
||||||
|
self.local_user.remote_id = 'http://example.com/a/b'
|
||||||
|
self.local_user.save(broadcast=False)
|
||||||
|
with patch('bookwyrm.models.user.set_remote_server.delay'):
|
||||||
|
self.remote_user = models.User.objects.create_user(
|
||||||
|
'rat', 'rat@rat.com', 'ratword',
|
||||||
|
local=False,
|
||||||
|
remote_id='https://example.com/users/rat',
|
||||||
|
inbox='https://example.com/users/rat/inbox',
|
||||||
|
outbox='https://example.com/users/rat/outbox',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ActivitypubMixin
|
||||||
|
def test_to_activity(self):
|
||||||
|
''' model to ActivityPub json '''
|
||||||
|
@dataclass(init=False)
|
||||||
|
class TestActivity(ActivityObject):
|
||||||
|
''' real simple mock '''
|
||||||
|
type: str = 'Test'
|
||||||
|
|
||||||
|
class TestModel(ActivitypubMixin, base_model.BookWyrmModel):
|
||||||
|
''' real simple mock model because BookWyrmModel is abstract '''
|
||||||
|
|
||||||
|
instance = TestModel()
|
||||||
|
instance.remote_id = 'https://www.example.com/test'
|
||||||
|
instance.activity_serializer = TestActivity
|
||||||
|
|
||||||
|
activity = instance.to_activity()
|
||||||
|
self.assertIsInstance(activity, dict)
|
||||||
|
self.assertEqual(activity['id'], 'https://www.example.com/test')
|
||||||
|
self.assertEqual(activity['type'], 'Test')
|
||||||
|
|
||||||
|
|
||||||
|
def test_find_existing_by_remote_id(self):
|
||||||
|
''' attempt to match a remote id to an object in the db '''
|
||||||
|
# uses a different remote id scheme
|
||||||
|
# this isn't really part of this test directly but it's helpful to state
|
||||||
|
book = models.Edition.objects.create(
|
||||||
|
title='Test Edition', remote_id='http://book.com/book')
|
||||||
|
|
||||||
|
self.assertEqual(book.origin_id, 'http://book.com/book')
|
||||||
|
self.assertNotEqual(book.remote_id, 'http://book.com/book')
|
||||||
|
|
||||||
|
# uses subclasses
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
|
models.Comment.objects.create(
|
||||||
|
user=self.local_user, content='test status', book=book, \
|
||||||
|
remote_id='https://comment.net')
|
||||||
|
|
||||||
|
result = models.User.find_existing_by_remote_id('hi')
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
result = models.User.find_existing_by_remote_id(
|
||||||
|
'http://example.com/a/b')
|
||||||
|
self.assertEqual(result, self.local_user)
|
||||||
|
|
||||||
|
# test using origin id
|
||||||
|
result = models.Edition.find_existing_by_remote_id(
|
||||||
|
'http://book.com/book')
|
||||||
|
self.assertEqual(result, book)
|
||||||
|
|
||||||
|
# test subclass match
|
||||||
|
result = models.Status.find_existing_by_remote_id(
|
||||||
|
'https://comment.net')
|
||||||
|
|
||||||
|
|
||||||
|
def test_find_existing(self):
|
||||||
|
''' match a blob of data to a model '''
|
||||||
|
book = models.Edition.objects.create(
|
||||||
|
title='Test edition',
|
||||||
|
openlibrary_key='OL1234',
|
||||||
|
)
|
||||||
|
|
||||||
|
result = models.Edition.find_existing(
|
||||||
|
{'openlibraryKey': 'OL1234'})
|
||||||
|
self.assertEqual(result, book)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_recipients_public_object(self):
|
||||||
|
''' determines the recipients for an object's broadcast '''
|
||||||
|
MockSelf = namedtuple('Self', ('privacy'))
|
||||||
|
mock_self = MockSelf('public')
|
||||||
|
recipients = ActivitypubMixin.get_recipients(mock_self)
|
||||||
|
self.assertEqual(len(recipients), 1)
|
||||||
|
self.assertEqual(recipients[0], self.remote_user.inbox)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_recipients_public_user_object_no_followers(self):
|
||||||
|
''' determines the recipients for a user's object broadcast '''
|
||||||
|
MockSelf = namedtuple('Self', ('privacy', 'user'))
|
||||||
|
mock_self = MockSelf('public', self.local_user)
|
||||||
|
|
||||||
|
recipients = ActivitypubMixin.get_recipients(mock_self)
|
||||||
|
self.assertEqual(len(recipients), 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_recipients_public_user_object(self):
|
||||||
|
''' determines the recipients for a user's object broadcast '''
|
||||||
|
MockSelf = namedtuple('Self', ('privacy', 'user'))
|
||||||
|
mock_self = MockSelf('public', self.local_user)
|
||||||
|
self.local_user.followers.add(self.remote_user)
|
||||||
|
|
||||||
|
recipients = ActivitypubMixin.get_recipients(mock_self)
|
||||||
|
self.assertEqual(len(recipients), 1)
|
||||||
|
self.assertEqual(recipients[0], self.remote_user.inbox)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_recipients_public_user_object_with_mention(self):
|
||||||
|
''' determines the recipients for a user's object broadcast '''
|
||||||
|
MockSelf = namedtuple('Self', ('privacy', 'user'))
|
||||||
|
mock_self = MockSelf('public', self.local_user)
|
||||||
|
self.local_user.followers.add(self.remote_user)
|
||||||
|
with patch('bookwyrm.models.user.set_remote_server.delay'):
|
||||||
|
another_remote_user = models.User.objects.create_user(
|
||||||
|
'nutria', 'nutria@nutria.com', 'nutriaword',
|
||||||
|
local=False,
|
||||||
|
remote_id='https://example.com/users/nutria',
|
||||||
|
inbox='https://example.com/users/nutria/inbox',
|
||||||
|
outbox='https://example.com/users/nutria/outbox',
|
||||||
|
)
|
||||||
|
MockSelf = namedtuple('Self', ('privacy', 'user', 'recipients'))
|
||||||
|
mock_self = MockSelf('public', self.local_user, [another_remote_user])
|
||||||
|
|
||||||
|
recipients = ActivitypubMixin.get_recipients(mock_self)
|
||||||
|
self.assertEqual(len(recipients), 2)
|
||||||
|
self.assertEqual(recipients[0], another_remote_user.inbox)
|
||||||
|
self.assertEqual(recipients[1], self.remote_user.inbox)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_recipients_direct(self):
|
||||||
|
''' determines the recipients for a user's object broadcast '''
|
||||||
|
MockSelf = namedtuple('Self', ('privacy', 'user'))
|
||||||
|
mock_self = MockSelf('public', self.local_user)
|
||||||
|
self.local_user.followers.add(self.remote_user)
|
||||||
|
with patch('bookwyrm.models.user.set_remote_server.delay'):
|
||||||
|
another_remote_user = models.User.objects.create_user(
|
||||||
|
'nutria', 'nutria@nutria.com', 'nutriaword',
|
||||||
|
local=False,
|
||||||
|
remote_id='https://example.com/users/nutria',
|
||||||
|
inbox='https://example.com/users/nutria/inbox',
|
||||||
|
outbox='https://example.com/users/nutria/outbox',
|
||||||
|
)
|
||||||
|
MockSelf = namedtuple('Self', ('privacy', 'user', 'recipients'))
|
||||||
|
mock_self = MockSelf('direct', self.local_user, [another_remote_user])
|
||||||
|
|
||||||
|
recipients = ActivitypubMixin.get_recipients(mock_self)
|
||||||
|
self.assertEqual(len(recipients), 1)
|
||||||
|
self.assertEqual(recipients[0], another_remote_user.inbox)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_recipients_combine_inboxes(self):
|
||||||
|
''' should combine users with the same shared_inbox '''
|
||||||
|
self.remote_user.shared_inbox = 'http://example.com/inbox'
|
||||||
|
self.remote_user.save(broadcast=False)
|
||||||
|
with patch('bookwyrm.models.user.set_remote_server.delay'):
|
||||||
|
another_remote_user = models.User.objects.create_user(
|
||||||
|
'nutria', 'nutria@nutria.com', 'nutriaword',
|
||||||
|
local=False,
|
||||||
|
remote_id='https://example.com/users/nutria',
|
||||||
|
inbox='https://example.com/users/nutria/inbox',
|
||||||
|
shared_inbox='http://example.com/inbox',
|
||||||
|
outbox='https://example.com/users/nutria/outbox',
|
||||||
|
)
|
||||||
|
MockSelf = namedtuple('Self', ('privacy', 'user'))
|
||||||
|
mock_self = MockSelf('public', self.local_user)
|
||||||
|
self.local_user.followers.add(self.remote_user)
|
||||||
|
self.local_user.followers.add(another_remote_user)
|
||||||
|
|
||||||
|
recipients = ActivitypubMixin.get_recipients(mock_self)
|
||||||
|
self.assertEqual(len(recipients), 1)
|
||||||
|
self.assertEqual(recipients[0], 'http://example.com/inbox')
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_recipients_software(self):
|
||||||
|
''' should differentiate between bookwyrm and other remote users '''
|
||||||
|
with patch('bookwyrm.models.user.set_remote_server.delay'):
|
||||||
|
another_remote_user = models.User.objects.create_user(
|
||||||
|
'nutria', 'nutria@nutria.com', 'nutriaword',
|
||||||
|
local=False,
|
||||||
|
remote_id='https://example.com/users/nutria',
|
||||||
|
inbox='https://example.com/users/nutria/inbox',
|
||||||
|
outbox='https://example.com/users/nutria/outbox',
|
||||||
|
bookwyrm_user=False,
|
||||||
|
)
|
||||||
|
MockSelf = namedtuple('Self', ('privacy', 'user'))
|
||||||
|
mock_self = MockSelf('public', self.local_user)
|
||||||
|
self.local_user.followers.add(self.remote_user)
|
||||||
|
self.local_user.followers.add(another_remote_user)
|
||||||
|
|
||||||
|
recipients = ActivitypubMixin.get_recipients(mock_self)
|
||||||
|
self.assertEqual(len(recipients), 2)
|
||||||
|
|
||||||
|
recipients = ActivitypubMixin.get_recipients(
|
||||||
|
mock_self, software='bookwyrm')
|
||||||
|
self.assertEqual(len(recipients), 1)
|
||||||
|
self.assertEqual(recipients[0], self.remote_user.inbox)
|
||||||
|
|
||||||
|
recipients = ActivitypubMixin.get_recipients(
|
||||||
|
mock_self, software='other')
|
||||||
|
self.assertEqual(len(recipients), 1)
|
||||||
|
self.assertEqual(recipients[0], another_remote_user.inbox)
|
||||||
|
|
||||||
|
|
||||||
|
# ObjectMixin
|
||||||
|
def test_object_save_create(self):
|
||||||
|
''' should save uneventufully when broadcast is disabled '''
|
||||||
|
class Success(Exception):
|
||||||
|
''' this means we got to the right method '''
|
||||||
|
|
||||||
|
class ObjectModel(ObjectMixin, base_model.BookWyrmModel):
|
||||||
|
''' real simple mock model because BookWyrmModel is abstract '''
|
||||||
|
user = models.fields.ForeignKey('User', on_delete=db.models.CASCADE)
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
with patch('django.db.models.Model.save'):
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
def broadcast(self, activity, sender, **kwargs):#pylint: disable=arguments-differ
|
||||||
|
''' do something '''
|
||||||
|
raise Success()
|
||||||
|
def to_create_activity(self, user):#pylint: disable=arguments-differ
|
||||||
|
return {}
|
||||||
|
|
||||||
|
with self.assertRaises(Success):
|
||||||
|
ObjectModel(user=self.local_user).save()
|
||||||
|
|
||||||
|
ObjectModel(user=self.remote_user).save()
|
||||||
|
ObjectModel(user=self.local_user).save(broadcast=False)
|
||||||
|
ObjectModel(user=None).save()
|
||||||
|
|
||||||
|
|
||||||
|
def test_object_save_update(self):
|
||||||
|
''' should save uneventufully when broadcast is disabled '''
|
||||||
|
class Success(Exception):
|
||||||
|
''' this means we got to the right method '''
|
||||||
|
|
||||||
|
class UpdateObjectModel(ObjectMixin, base_model.BookWyrmModel):
|
||||||
|
''' real simple mock model because BookWyrmModel is abstract '''
|
||||||
|
user = models.fields.ForeignKey('User', on_delete=db.models.CASCADE)
|
||||||
|
last_edited_by = models.fields.ForeignKey(
|
||||||
|
'User', on_delete=db.models.CASCADE)
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
with patch('django.db.models.Model.save'):
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
def to_update_activity(self, user):
|
||||||
|
raise Success()
|
||||||
|
|
||||||
|
with self.assertRaises(Success):
|
||||||
|
UpdateObjectModel(id=1, user=self.local_user).save()
|
||||||
|
with self.assertRaises(Success):
|
||||||
|
UpdateObjectModel(id=1, last_edited_by=self.local_user).save()
|
||||||
|
|
||||||
|
|
||||||
|
def test_object_save_delete(self):
|
||||||
|
''' should create delete activities when objects are deleted by flag '''
|
||||||
|
class ActivitySuccess(Exception):
|
||||||
|
''' this means we got to the right method '''
|
||||||
|
|
||||||
|
class DeletableObjectModel(ObjectMixin, base_model.BookWyrmModel):
|
||||||
|
''' real simple mock model because BookWyrmModel is abstract '''
|
||||||
|
user = models.fields.ForeignKey('User', on_delete=db.models.CASCADE)
|
||||||
|
deleted = models.fields.BooleanField()
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
with patch('django.db.models.Model.save'):
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
def to_delete_activity(self, user):
|
||||||
|
raise ActivitySuccess()
|
||||||
|
|
||||||
|
with self.assertRaises(ActivitySuccess):
|
||||||
|
DeletableObjectModel(
|
||||||
|
id=1, user=self.local_user, deleted=True).save()
|
||||||
|
|
||||||
|
|
||||||
|
def test_to_create_activity(self):
|
||||||
|
''' wrapper for ActivityPub "create" action '''
|
||||||
|
object_activity = {
|
||||||
|
'to': 'to field', 'cc': 'cc field',
|
||||||
|
'content': 'hi',
|
||||||
|
'published': '2020-12-04T17:52:22.623807+00:00',
|
||||||
|
}
|
||||||
|
MockSelf = namedtuple('Self', ('remote_id', 'to_activity'))
|
||||||
|
mock_self = MockSelf(
|
||||||
|
'https://example.com/status/1',
|
||||||
|
lambda *args: object_activity
|
||||||
|
)
|
||||||
|
activity = ObjectMixin.to_create_activity(
|
||||||
|
mock_self, self.local_user)
|
||||||
|
self.assertEqual(
|
||||||
|
activity['id'],
|
||||||
|
'https://example.com/status/1/activity'
|
||||||
|
)
|
||||||
|
self.assertEqual(activity['actor'], self.local_user.remote_id)
|
||||||
|
self.assertEqual(activity['type'], 'Create')
|
||||||
|
self.assertEqual(activity['to'], 'to field')
|
||||||
|
self.assertEqual(activity['cc'], 'cc field')
|
||||||
|
self.assertEqual(activity['object'], object_activity)
|
||||||
|
self.assertEqual(
|
||||||
|
activity['signature'].creator,
|
||||||
|
'%s#main-key' % self.local_user.remote_id
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_to_delete_activity(self):
|
||||||
|
''' wrapper for Delete activity '''
|
||||||
|
MockSelf = namedtuple('Self', ('remote_id', 'to_activity'))
|
||||||
|
mock_self = MockSelf(
|
||||||
|
'https://example.com/status/1',
|
||||||
|
lambda *args: {}
|
||||||
|
)
|
||||||
|
activity = ObjectMixin.to_delete_activity(
|
||||||
|
mock_self, self.local_user)
|
||||||
|
self.assertEqual(
|
||||||
|
activity['id'],
|
||||||
|
'https://example.com/status/1/activity'
|
||||||
|
)
|
||||||
|
self.assertEqual(activity['actor'], self.local_user.remote_id)
|
||||||
|
self.assertEqual(activity['type'], 'Delete')
|
||||||
|
self.assertEqual(
|
||||||
|
activity['to'],
|
||||||
|
['%s/followers' % self.local_user.remote_id])
|
||||||
|
self.assertEqual(
|
||||||
|
activity['cc'],
|
||||||
|
['https://www.w3.org/ns/activitystreams#Public'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_to_update_activity(self):
|
||||||
|
''' ditto above but for Update '''
|
||||||
|
MockSelf = namedtuple('Self', ('remote_id', 'to_activity'))
|
||||||
|
mock_self = MockSelf(
|
||||||
|
'https://example.com/status/1',
|
||||||
|
lambda *args: {}
|
||||||
|
)
|
||||||
|
activity = ObjectMixin.to_update_activity(
|
||||||
|
mock_self, self.local_user)
|
||||||
|
self.assertIsNotNone(
|
||||||
|
re.match(
|
||||||
|
r'^https:\/\/example\.com\/status\/1#update\/.*',
|
||||||
|
activity['id']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertEqual(activity['actor'], self.local_user.remote_id)
|
||||||
|
self.assertEqual(activity['type'], 'Update')
|
||||||
|
self.assertEqual(
|
||||||
|
activity['to'],
|
||||||
|
['https://www.w3.org/ns/activitystreams#Public'])
|
||||||
|
self.assertEqual(activity['object'], {})
|
||||||
|
|
||||||
|
|
||||||
|
# Activity mixin
|
||||||
|
def test_to_undo_activity(self):
|
||||||
|
''' and again, for Undo '''
|
||||||
|
MockSelf = namedtuple('Self', ('remote_id', 'to_activity', 'user'))
|
||||||
|
mock_self = MockSelf(
|
||||||
|
'https://example.com/status/1',
|
||||||
|
lambda *args: {},
|
||||||
|
self.local_user,
|
||||||
|
)
|
||||||
|
activity = ActivityMixin.to_undo_activity(mock_self)
|
||||||
|
self.assertEqual(
|
||||||
|
activity['id'],
|
||||||
|
'https://example.com/status/1#undo'
|
||||||
|
)
|
||||||
|
self.assertEqual(activity['actor'], self.local_user.remote_id)
|
||||||
|
self.assertEqual(activity['type'], 'Undo')
|
||||||
|
self.assertEqual(activity['object'], {})
|
|
@ -1,13 +1,8 @@
|
||||||
''' testing models '''
|
''' testing models '''
|
||||||
from collections import namedtuple
|
|
||||||
from dataclasses import dataclass
|
|
||||||
import re
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from bookwyrm.activitypub.base_activity import ActivityObject
|
|
||||||
from bookwyrm import models
|
from bookwyrm import models
|
||||||
from bookwyrm.models import base_model
|
from bookwyrm.models import base_model
|
||||||
from bookwyrm.models.base_model import ActivitypubMixin
|
|
||||||
from bookwyrm.settings import DOMAIN
|
from bookwyrm.settings import DOMAIN
|
||||||
|
|
||||||
class BaseModel(TestCase):
|
class BaseModel(TestCase):
|
||||||
|
@ -48,173 +43,3 @@ class BaseModel(TestCase):
|
||||||
instance.remote_id = None
|
instance.remote_id = None
|
||||||
base_model.execute_after_save(None, instance, False)
|
base_model.execute_after_save(None, instance, False)
|
||||||
self.assertIsNone(instance.remote_id)
|
self.assertIsNone(instance.remote_id)
|
||||||
|
|
||||||
def test_to_create_activity(self):
|
|
||||||
''' wrapper for ActivityPub "create" action '''
|
|
||||||
user = models.User.objects.create_user(
|
|
||||||
'mouse', 'mouse@mouse.com', 'mouseword',
|
|
||||||
local=True, localname='mouse')
|
|
||||||
|
|
||||||
object_activity = {
|
|
||||||
'to': 'to field', 'cc': 'cc field',
|
|
||||||
'content': 'hi',
|
|
||||||
'published': '2020-12-04T17:52:22.623807+00:00',
|
|
||||||
}
|
|
||||||
MockSelf = namedtuple('Self', ('remote_id', 'to_activity'))
|
|
||||||
mock_self = MockSelf(
|
|
||||||
'https://example.com/status/1',
|
|
||||||
lambda *args: object_activity
|
|
||||||
)
|
|
||||||
activity = ActivitypubMixin.to_create_activity(mock_self, user)
|
|
||||||
self.assertEqual(
|
|
||||||
activity['id'],
|
|
||||||
'https://example.com/status/1/activity'
|
|
||||||
)
|
|
||||||
self.assertEqual(activity['actor'], user.remote_id)
|
|
||||||
self.assertEqual(activity['type'], 'Create')
|
|
||||||
self.assertEqual(activity['to'], 'to field')
|
|
||||||
self.assertEqual(activity['cc'], 'cc field')
|
|
||||||
self.assertEqual(activity['object'], object_activity)
|
|
||||||
self.assertEqual(
|
|
||||||
activity['signature'].creator,
|
|
||||||
'%s#main-key' % user.remote_id
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_to_delete_activity(self):
|
|
||||||
''' wrapper for Delete activity '''
|
|
||||||
user = models.User.objects.create_user(
|
|
||||||
'mouse', 'mouse@mouse.com', 'mouseword',
|
|
||||||
local=True, localname='mouse')
|
|
||||||
|
|
||||||
MockSelf = namedtuple('Self', ('remote_id', 'to_activity'))
|
|
||||||
mock_self = MockSelf(
|
|
||||||
'https://example.com/status/1',
|
|
||||||
lambda *args: {}
|
|
||||||
)
|
|
||||||
activity = ActivitypubMixin.to_delete_activity(mock_self, user)
|
|
||||||
self.assertEqual(
|
|
||||||
activity['id'],
|
|
||||||
'https://example.com/status/1/activity'
|
|
||||||
)
|
|
||||||
self.assertEqual(activity['actor'], user.remote_id)
|
|
||||||
self.assertEqual(activity['type'], 'Delete')
|
|
||||||
self.assertEqual(
|
|
||||||
activity['to'],
|
|
||||||
['%s/followers' % user.remote_id])
|
|
||||||
self.assertEqual(
|
|
||||||
activity['cc'],
|
|
||||||
['https://www.w3.org/ns/activitystreams#Public'])
|
|
||||||
|
|
||||||
def test_to_update_activity(self):
|
|
||||||
''' ditto above but for Update '''
|
|
||||||
user = models.User.objects.create_user(
|
|
||||||
'mouse', 'mouse@mouse.com', 'mouseword',
|
|
||||||
local=True, localname='mouse')
|
|
||||||
|
|
||||||
MockSelf = namedtuple('Self', ('remote_id', 'to_activity'))
|
|
||||||
mock_self = MockSelf(
|
|
||||||
'https://example.com/status/1',
|
|
||||||
lambda *args: {}
|
|
||||||
)
|
|
||||||
activity = ActivitypubMixin.to_update_activity(mock_self, user)
|
|
||||||
self.assertIsNotNone(
|
|
||||||
re.match(
|
|
||||||
r'^https:\/\/example\.com\/status\/1#update\/.*',
|
|
||||||
activity['id']
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.assertEqual(activity['actor'], user.remote_id)
|
|
||||||
self.assertEqual(activity['type'], 'Update')
|
|
||||||
self.assertEqual(
|
|
||||||
activity['to'],
|
|
||||||
['https://www.w3.org/ns/activitystreams#Public'])
|
|
||||||
self.assertEqual(activity['object'], {})
|
|
||||||
|
|
||||||
def test_to_undo_activity(self):
|
|
||||||
''' and again, for Undo '''
|
|
||||||
user = models.User.objects.create_user(
|
|
||||||
'mouse', 'mouse@mouse.com', 'mouseword',
|
|
||||||
local=True, localname='mouse')
|
|
||||||
|
|
||||||
MockSelf = namedtuple('Self', ('remote_id', 'to_activity'))
|
|
||||||
mock_self = MockSelf(
|
|
||||||
'https://example.com/status/1',
|
|
||||||
lambda *args: {}
|
|
||||||
)
|
|
||||||
activity = ActivitypubMixin.to_undo_activity(mock_self, user)
|
|
||||||
self.assertEqual(
|
|
||||||
activity['id'],
|
|
||||||
'https://example.com/status/1#undo'
|
|
||||||
)
|
|
||||||
self.assertEqual(activity['actor'], user.remote_id)
|
|
||||||
self.assertEqual(activity['type'], 'Undo')
|
|
||||||
self.assertEqual(activity['object'], {})
|
|
||||||
|
|
||||||
|
|
||||||
def test_to_activity(self):
|
|
||||||
''' model to ActivityPub json '''
|
|
||||||
@dataclass(init=False)
|
|
||||||
class TestActivity(ActivityObject):
|
|
||||||
''' real simple mock '''
|
|
||||||
type: str = 'Test'
|
|
||||||
|
|
||||||
class TestModel(ActivitypubMixin, base_model.BookWyrmModel):
|
|
||||||
''' real simple mock model because BookWyrmModel is abstract '''
|
|
||||||
|
|
||||||
instance = TestModel()
|
|
||||||
instance.remote_id = 'https://www.example.com/test'
|
|
||||||
instance.activity_serializer = TestActivity
|
|
||||||
|
|
||||||
activity = instance.to_activity()
|
|
||||||
self.assertIsInstance(activity, dict)
|
|
||||||
self.assertEqual(activity['id'], 'https://www.example.com/test')
|
|
||||||
self.assertEqual(activity['type'], 'Test')
|
|
||||||
|
|
||||||
|
|
||||||
def test_find_existing_by_remote_id(self):
|
|
||||||
''' attempt to match a remote id to an object in the db '''
|
|
||||||
# uses a different remote id scheme
|
|
||||||
# this isn't really part of this test directly but it's helpful to state
|
|
||||||
book = models.Edition.objects.create(
|
|
||||||
title='Test Edition', remote_id='http://book.com/book')
|
|
||||||
user = models.User.objects.create_user(
|
|
||||||
'mouse', 'mouse@mouse.mouse', 'mouseword',
|
|
||||||
local=True, localname='mouse')
|
|
||||||
user.remote_id = 'http://example.com/a/b'
|
|
||||||
user.save()
|
|
||||||
|
|
||||||
self.assertEqual(book.origin_id, 'http://book.com/book')
|
|
||||||
self.assertNotEqual(book.remote_id, 'http://book.com/book')
|
|
||||||
|
|
||||||
# uses subclasses
|
|
||||||
models.Comment.objects.create(
|
|
||||||
user=user, content='test status', book=book, \
|
|
||||||
remote_id='https://comment.net')
|
|
||||||
|
|
||||||
result = models.User.find_existing_by_remote_id('hi')
|
|
||||||
self.assertIsNone(result)
|
|
||||||
|
|
||||||
result = models.User.find_existing_by_remote_id(
|
|
||||||
'http://example.com/a/b')
|
|
||||||
self.assertEqual(result, user)
|
|
||||||
|
|
||||||
# test using origin id
|
|
||||||
result = models.Edition.find_existing_by_remote_id(
|
|
||||||
'http://book.com/book')
|
|
||||||
self.assertEqual(result, book)
|
|
||||||
|
|
||||||
# test subclass match
|
|
||||||
result = models.Status.find_existing_by_remote_id(
|
|
||||||
'https://comment.net')
|
|
||||||
|
|
||||||
|
|
||||||
def test_find_existing(self):
|
|
||||||
''' match a blob of data to a model '''
|
|
||||||
book = models.Edition.objects.create(
|
|
||||||
title='Test edition',
|
|
||||||
openlibrary_key='OL1234',
|
|
||||||
)
|
|
||||||
|
|
||||||
result = models.Edition.find_existing(
|
|
||||||
{'openlibraryKey': 'OL1234'})
|
|
||||||
self.assertEqual(result, book)
|
|
||||||
|
|
|
@ -19,7 +19,8 @@ from django.utils import timezone
|
||||||
|
|
||||||
from bookwyrm.activitypub.base_activity import ActivityObject
|
from bookwyrm.activitypub.base_activity import ActivityObject
|
||||||
from bookwyrm.models import fields, User, Status
|
from bookwyrm.models import fields, User, Status
|
||||||
from bookwyrm.models.base_model import ActivitypubMixin, BookWyrmModel
|
from bookwyrm.models.base_model import BookWyrmModel
|
||||||
|
from bookwyrm.models.activitypub_mixin import ActivitypubMixin
|
||||||
|
|
||||||
#pylint: disable=too-many-public-methods
|
#pylint: disable=too-many-public-methods
|
||||||
class ActivitypubFields(TestCase):
|
class ActivitypubFields(TestCase):
|
||||||
|
@ -177,7 +178,8 @@ class ActivitypubFields(TestCase):
|
||||||
self.assertEqual(model_instance.privacy_field, 'unlisted')
|
self.assertEqual(model_instance.privacy_field, 'unlisted')
|
||||||
|
|
||||||
|
|
||||||
def test_privacy_field_set_activity_from_field(self):
|
@patch('bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast')
|
||||||
|
def test_privacy_field_set_activity_from_field(self, _):
|
||||||
''' translate between to/cc fields and privacy '''
|
''' translate between to/cc fields and privacy '''
|
||||||
user = User.objects.create_user(
|
user = User.objects.create_user(
|
||||||
'rat', 'rat@rat.rat', 'ratword',
|
'rat', 'rat@rat.rat', 'ratword',
|
||||||
|
@ -194,13 +196,15 @@ class ActivitypubFields(TestCase):
|
||||||
self.assertEqual(activity['to'], [public])
|
self.assertEqual(activity['to'], [public])
|
||||||
self.assertEqual(activity['cc'], [followers])
|
self.assertEqual(activity['cc'], [followers])
|
||||||
|
|
||||||
model_instance = Status.objects.create(user=user, privacy='unlisted')
|
model_instance = Status.objects.create(
|
||||||
|
user=user, content='hi', privacy='unlisted')
|
||||||
activity = {}
|
activity = {}
|
||||||
instance.set_activity_from_field(activity, model_instance)
|
instance.set_activity_from_field(activity, model_instance)
|
||||||
self.assertEqual(activity['to'], [followers])
|
self.assertEqual(activity['to'], [followers])
|
||||||
self.assertEqual(activity['cc'], [public])
|
self.assertEqual(activity['cc'], [public])
|
||||||
|
|
||||||
model_instance = Status.objects.create(user=user, privacy='followers')
|
model_instance = Status.objects.create(
|
||||||
|
user=user, content='hi', privacy='followers')
|
||||||
activity = {}
|
activity = {}
|
||||||
instance.set_activity_from_field(activity, model_instance)
|
instance.set_activity_from_field(activity, model_instance)
|
||||||
self.assertEqual(activity['to'], [followers])
|
self.assertEqual(activity['to'], [followers])
|
||||||
|
@ -208,6 +212,7 @@ class ActivitypubFields(TestCase):
|
||||||
|
|
||||||
model_instance = Status.objects.create(
|
model_instance = Status.objects.create(
|
||||||
user=user,
|
user=user,
|
||||||
|
content='hi',
|
||||||
privacy='direct',
|
privacy='direct',
|
||||||
)
|
)
|
||||||
model_instance.mention_users.set([user])
|
model_instance.mention_users.set([user])
|
||||||
|
@ -289,11 +294,12 @@ class ActivitypubFields(TestCase):
|
||||||
'mouse', 'mouse@mouse.mouse', 'mouseword',
|
'mouse', 'mouse@mouse.mouse', 'mouseword',
|
||||||
local=True, localname='mouse')
|
local=True, localname='mouse')
|
||||||
user.remote_id = 'https://example.com/user/mouse'
|
user.remote_id = 'https://example.com/user/mouse'
|
||||||
user.save()
|
user.save(broadcast=False)
|
||||||
User.objects.create_user(
|
User.objects.create_user(
|
||||||
'rat', 'rat@rat.rat', 'ratword',
|
'rat', 'rat@rat.rat', 'ratword',
|
||||||
local=True, localname='rat')
|
local=True, localname='rat')
|
||||||
|
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast'):
|
||||||
value = instance.field_from_activity(userdata)
|
value = instance.field_from_activity(userdata)
|
||||||
self.assertEqual(value, user)
|
self.assertEqual(value, user)
|
||||||
|
|
||||||
|
@ -393,7 +399,8 @@ class ActivitypubFields(TestCase):
|
||||||
|
|
||||||
|
|
||||||
@responses.activate
|
@responses.activate
|
||||||
def test_image_field(self):
|
@patch('bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast')
|
||||||
|
def test_image_field(self, _):
|
||||||
''' storing images '''
|
''' storing images '''
|
||||||
user = User.objects.create_user(
|
user = User.objects.create_user(
|
||||||
'mouse', 'mouse@mouse.mouse', 'mouseword',
|
'mouse', 'mouse@mouse.mouse', 'mouseword',
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
''' testing models '''
|
''' testing models '''
|
||||||
|
from unittest.mock import patch
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from bookwyrm import models, settings
|
from bookwyrm import models, settings
|
||||||
|
|
||||||
|
|
||||||
|
@patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay')
|
||||||
class List(TestCase):
|
class List(TestCase):
|
||||||
''' some activitypub oddness ahead '''
|
''' some activitypub oddness ahead '''
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -11,17 +13,18 @@ class List(TestCase):
|
||||||
self.user = models.User.objects.create_user(
|
self.user = models.User.objects.create_user(
|
||||||
'mouse', 'mouse@mouse.mouse', 'mouseword',
|
'mouse', 'mouse@mouse.mouse', 'mouseword',
|
||||||
local=True, localname='mouse')
|
local=True, localname='mouse')
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
self.list = models.List.objects.create(
|
self.list = models.List.objects.create(
|
||||||
name='Test List', user=self.user)
|
name='Test List', user=self.user)
|
||||||
|
|
||||||
def test_remote_id(self):
|
def test_remote_id(self, _):
|
||||||
''' shelves use custom remote ids '''
|
''' shelves use custom remote ids '''
|
||||||
expected_id = 'https://%s/list/%d' % \
|
expected_id = 'https://%s/list/%d' % \
|
||||||
(settings.DOMAIN, self.list.id)
|
(settings.DOMAIN, self.list.id)
|
||||||
self.assertEqual(self.list.get_remote_id(), expected_id)
|
self.assertEqual(self.list.get_remote_id(), expected_id)
|
||||||
|
|
||||||
|
|
||||||
def test_to_activity(self):
|
def test_to_activity(self, _):
|
||||||
''' jsonify it '''
|
''' jsonify it '''
|
||||||
activity_json = self.list.to_activity()
|
activity_json = self.list.to_activity()
|
||||||
self.assertIsInstance(activity_json, dict)
|
self.assertIsInstance(activity_json, dict)
|
||||||
|
@ -31,24 +34,24 @@ class List(TestCase):
|
||||||
self.assertEqual(activity_json['name'], 'Test List')
|
self.assertEqual(activity_json['name'], 'Test List')
|
||||||
self.assertEqual(activity_json['owner'], self.user.remote_id)
|
self.assertEqual(activity_json['owner'], self.user.remote_id)
|
||||||
|
|
||||||
def test_list_item(self):
|
def test_list_item(self, _):
|
||||||
''' a list entry '''
|
''' a list entry '''
|
||||||
work = models.Work.objects.create(title='hello')
|
work = models.Work.objects.create(title='hello')
|
||||||
book = models.Edition.objects.create(title='hi', parent_work=work)
|
book = models.Edition.objects.create(title='hi', parent_work=work)
|
||||||
item = models.ListItem.objects.create(
|
item = models.ListItem.objects.create(
|
||||||
book_list=self.list,
|
book_list=self.list,
|
||||||
book=book,
|
book=book,
|
||||||
added_by=self.user,
|
user=self.user,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertTrue(item.approved)
|
self.assertTrue(item.approved)
|
||||||
|
|
||||||
add_activity = item.to_add_activity(self.user)
|
add_activity = item.to_add_activity()
|
||||||
self.assertEqual(add_activity['actor'], self.user.remote_id)
|
self.assertEqual(add_activity['actor'], self.user.remote_id)
|
||||||
self.assertEqual(add_activity['object']['id'], book.remote_id)
|
self.assertEqual(add_activity['object']['id'], book.remote_id)
|
||||||
self.assertEqual(add_activity['target'], self.list.remote_id)
|
self.assertEqual(add_activity['target'], self.list.remote_id)
|
||||||
|
|
||||||
remove_activity = item.to_remove_activity(self.user)
|
remove_activity = item.to_remove_activity()
|
||||||
self.assertEqual(remove_activity['actor'], self.user.remote_id)
|
self.assertEqual(remove_activity['actor'], self.user.remote_id)
|
||||||
self.assertEqual(remove_activity['object']['id'], book.remote_id)
|
self.assertEqual(remove_activity['object']['id'], book.remote_id)
|
||||||
self.assertEqual(remove_activity['target'], self.list.remote_id)
|
self.assertEqual(remove_activity['target'], self.list.remote_id)
|
||||||
|
|
|
@ -6,7 +6,9 @@ from bookwyrm import models
|
||||||
|
|
||||||
|
|
||||||
class Relationship(TestCase):
|
class Relationship(TestCase):
|
||||||
|
''' following, blocking, stuff like that '''
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
''' we need some users for this '''
|
||||||
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@rat.com', 'ratword',
|
'rat', 'rat@rat.com', 'ratword',
|
||||||
|
@ -19,68 +21,31 @@ class Relationship(TestCase):
|
||||||
'mouse', 'mouse@mouse.com', 'mouseword',
|
'mouse', 'mouse@mouse.com', 'mouseword',
|
||||||
local=True, localname='mouse')
|
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()
|
self.local_user.save(broadcast=False)
|
||||||
|
|
||||||
def test_user_follows(self):
|
def test_user_follows(self):
|
||||||
|
''' create a follow relationship '''
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.ActivityMixin.broadcast'):
|
||||||
rel = models.UserFollows.objects.create(
|
rel = models.UserFollows.objects.create(
|
||||||
user_subject=self.local_user,
|
user_subject=self.local_user,
|
||||||
user_object=self.remote_user
|
user_object=self.remote_user
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
rel.remote_id,
|
|
||||||
'http://local.com/user/mouse#follows/%d' % rel.id
|
|
||||||
)
|
|
||||||
|
|
||||||
activity = rel.to_activity()
|
activity = rel.to_activity()
|
||||||
self.assertEqual(activity['id'], rel.remote_id)
|
self.assertEqual(activity['id'], rel.remote_id)
|
||||||
self.assertEqual(activity['actor'], 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['object'], self.remote_user.remote_id)
|
||||||
|
|
||||||
def test_user_follow_accept_serialization(self):
|
|
||||||
rel = models.UserFollows.objects.create(
|
|
||||||
user_subject=self.local_user,
|
|
||||||
user_object=self.remote_user
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
rel.remote_id,
|
|
||||||
'http://local.com/user/mouse#follows/%d' % rel.id
|
|
||||||
)
|
|
||||||
accept = rel.to_accept_activity()
|
|
||||||
self.assertEqual(accept['type'], 'Accept')
|
|
||||||
self.assertEqual(
|
|
||||||
accept['id'],
|
|
||||||
'http://local.com/user/mouse#accepts/%d' % rel.id
|
|
||||||
)
|
|
||||||
self.assertEqual(accept['actor'], self.remote_user.remote_id)
|
|
||||||
self.assertEqual(accept['object']['id'], rel.remote_id)
|
|
||||||
self.assertEqual(accept['object']['actor'], self.local_user.remote_id)
|
|
||||||
self.assertEqual(accept['object']['object'], self.remote_user.remote_id)
|
|
||||||
|
|
||||||
def test_user_follow_reject_serialization(self):
|
|
||||||
rel = models.UserFollows.objects.create(
|
|
||||||
user_subject=self.local_user,
|
|
||||||
user_object=self.remote_user
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
rel.remote_id,
|
|
||||||
'http://local.com/user/mouse#follows/%d' % rel.id
|
|
||||||
)
|
|
||||||
reject = rel.to_reject_activity()
|
|
||||||
self.assertEqual(reject['type'], 'Reject')
|
|
||||||
self.assertEqual(
|
|
||||||
reject['id'],
|
|
||||||
'http://local.com/user/mouse#rejects/%d' % rel.id
|
|
||||||
)
|
|
||||||
self.assertEqual(reject['actor'], self.remote_user.remote_id)
|
|
||||||
self.assertEqual(reject['object']['id'], rel.remote_id)
|
|
||||||
self.assertEqual(reject['object']['actor'], self.local_user.remote_id)
|
|
||||||
self.assertEqual(reject['object']['object'], self.remote_user.remote_id)
|
|
||||||
|
|
||||||
|
|
||||||
def test_user_follows_from_request(self):
|
def test_user_follows_from_request(self):
|
||||||
|
''' convert a follow request into a follow '''
|
||||||
|
real_broadcast = models.UserFollowRequest.broadcast
|
||||||
|
def mock_broadcast(_, activity, user):
|
||||||
|
''' introspect what's being sent out '''
|
||||||
|
self.assertEqual(user.remote_id, self.local_user.remote_id)
|
||||||
|
self.assertEqual(activity['type'], 'Follow')
|
||||||
|
|
||||||
|
models.UserFollowRequest.broadcast = mock_broadcast
|
||||||
request = models.UserFollowRequest.objects.create(
|
request = models.UserFollowRequest.objects.create(
|
||||||
user_subject=self.local_user,
|
user_subject=self.local_user,
|
||||||
user_object=self.remote_user
|
user_object=self.remote_user
|
||||||
|
@ -99,9 +64,13 @@ 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 '''
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
request = models.UserFollowRequest.objects.create(
|
request = models.UserFollowRequest.objects.create(
|
||||||
user_subject=self.local_user,
|
user_subject=self.local_user,
|
||||||
user_object=self.remote_user,
|
user_object=self.remote_user,
|
||||||
|
@ -121,3 +90,67 @@ 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)
|
||||||
|
|
||||||
|
|
||||||
|
def test_follow_request_activity(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['actor'], self.local_user.remote_id)
|
||||||
|
self.assertEqual(activity['object'], self.remote_user.remote_id)
|
||||||
|
self.assertEqual(activity['type'], 'Follow')
|
||||||
|
|
||||||
|
models.UserFollowRequest.broadcast = mock_broadcast
|
||||||
|
models.UserFollowRequest.objects.create(
|
||||||
|
user_subject=self.local_user,
|
||||||
|
user_object=self.remote_user,
|
||||||
|
)
|
||||||
|
models.UserFollowRequest.broadcast = real_broadcast
|
||||||
|
|
||||||
|
|
||||||
|
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['object']['id'], request.remote_id)
|
||||||
|
|
||||||
|
models.UserFollowRequest.broadcast = mock_broadcast
|
||||||
|
request = models.UserFollowRequest.objects.create(
|
||||||
|
user_subject=self.remote_user,
|
||||||
|
user_object=self.local_user,
|
||||||
|
)
|
||||||
|
request.accept()
|
||||||
|
|
||||||
|
self.assertFalse(models.UserFollowRequest.objects.exists())
|
||||||
|
self.assertTrue(models.UserFollows.objects.exists())
|
||||||
|
rel = models.UserFollows.objects.get()
|
||||||
|
self.assertEqual(rel.user_subject, self.remote_user)
|
||||||
|
self.assertEqual(rel.user_object, self.local_user)
|
||||||
|
models.UserFollowRequest.broadcast = real_broadcast
|
||||||
|
|
||||||
|
|
||||||
|
def test_follow_request_reject(self):
|
||||||
|
''' 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
|
||||||
|
request = models.UserFollowRequest.objects.create(
|
||||||
|
user_subject=self.remote_user,
|
||||||
|
user_object=self.local_user,
|
||||||
|
)
|
||||||
|
request.reject()
|
||||||
|
|
||||||
|
self.assertFalse(models.UserFollowRequest.objects.exists())
|
||||||
|
self.assertFalse(models.UserFollows.objects.exists())
|
||||||
|
models.UserFollowRequest.broadcast = real_broadcast
|
||||||
|
|
|
@ -8,24 +8,113 @@ class Shelf(TestCase):
|
||||||
''' some activitypub oddness ahead '''
|
''' some activitypub oddness ahead '''
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
''' look, a shelf '''
|
''' look, a shelf '''
|
||||||
self.user = models.User.objects.create_user(
|
self.local_user = models.User.objects.create_user(
|
||||||
'mouse', 'mouse@mouse.mouse', 'mouseword',
|
'mouse', 'mouse@mouse.mouse', 'mouseword',
|
||||||
local=True, localname='mouse')
|
local=True, localname='mouse')
|
||||||
self.shelf = models.Shelf.objects.create(
|
work = models.Work.objects.create(title='Test Work')
|
||||||
name='Test Shelf', identifier='test-shelf', user=self.user)
|
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 '''
|
||||||
|
real_broadcast = models.Shelf.broadcast
|
||||||
|
def broadcast_mock(_, activity, user, **kwargs):
|
||||||
|
''' nah '''
|
||||||
|
models.Shelf.broadcast = broadcast_mock
|
||||||
|
shelf = models.Shelf.objects.create(
|
||||||
|
name='Test Shelf', identifier='test-shelf',
|
||||||
|
user=self.local_user)
|
||||||
expected_id = 'https://%s/user/mouse/shelf/test-shelf' % settings.DOMAIN
|
expected_id = 'https://%s/user/mouse/shelf/test-shelf' % settings.DOMAIN
|
||||||
self.assertEqual(self.shelf.get_remote_id(), expected_id)
|
self.assertEqual(shelf.get_remote_id(), expected_id)
|
||||||
|
models.Shelf.broadcast = real_broadcast
|
||||||
|
|
||||||
|
|
||||||
def test_to_activity(self):
|
def test_to_activity(self):
|
||||||
''' jsonify it '''
|
''' jsonify it '''
|
||||||
activity_json = self.shelf.to_activity()
|
real_broadcast = models.Shelf.broadcast
|
||||||
|
def empty_mock(_, activity, user, **kwargs):
|
||||||
|
''' nah '''
|
||||||
|
models.Shelf.broadcast = empty_mock
|
||||||
|
shelf = models.Shelf.objects.create(
|
||||||
|
name='Test Shelf', identifier='test-shelf',
|
||||||
|
user=self.local_user)
|
||||||
|
activity_json = shelf.to_activity()
|
||||||
self.assertIsInstance(activity_json, dict)
|
self.assertIsInstance(activity_json, dict)
|
||||||
self.assertEqual(activity_json['id'], self.shelf.remote_id)
|
self.assertEqual(activity_json['id'], shelf.remote_id)
|
||||||
self.assertEqual(activity_json['totalItems'], 0)
|
self.assertEqual(activity_json['totalItems'], 0)
|
||||||
self.assertEqual(activity_json['type'], 'Shelf')
|
self.assertEqual(activity_json['type'], 'Shelf')
|
||||||
self.assertEqual(activity_json['name'], 'Test Shelf')
|
self.assertEqual(activity_json['name'], 'Test Shelf')
|
||||||
self.assertEqual(activity_json['owner'], self.user.remote_id)
|
self.assertEqual(activity_json['owner'], self.local_user.remote_id)
|
||||||
|
models.Shelf.broadcast = real_broadcast
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_update_shelf(self):
|
||||||
|
''' create and broadcast shelf creation '''
|
||||||
|
real_broadcast = models.Shelf.broadcast
|
||||||
|
def create_mock(_, activity, user, **kwargs):
|
||||||
|
''' ok '''
|
||||||
|
self.assertEqual(user.remote_id, self.local_user.remote_id)
|
||||||
|
self.assertEqual(activity['type'], 'Create')
|
||||||
|
self.assertEqual(activity['actor'], self.local_user.remote_id)
|
||||||
|
self.assertEqual(activity['object']['name'], 'Test Shelf')
|
||||||
|
models.Shelf.broadcast = create_mock
|
||||||
|
|
||||||
|
shelf = models.Shelf.objects.create(
|
||||||
|
name='Test Shelf', identifier='test-shelf', user=self.local_user)
|
||||||
|
|
||||||
|
def update_mock(_, activity, user, **kwargs):
|
||||||
|
''' ok '''
|
||||||
|
self.assertEqual(user.remote_id, self.local_user.remote_id)
|
||||||
|
self.assertEqual(activity['type'], 'Update')
|
||||||
|
self.assertEqual(activity['actor'], self.local_user.remote_id)
|
||||||
|
self.assertEqual(activity['object']['name'], 'arthur russel')
|
||||||
|
models.Shelf.broadcast = update_mock
|
||||||
|
|
||||||
|
shelf.name = 'arthur russel'
|
||||||
|
shelf.save()
|
||||||
|
self.assertEqual(shelf.name, 'arthur russel')
|
||||||
|
models.Shelf.broadcast = real_broadcast
|
||||||
|
|
||||||
|
|
||||||
|
def test_shelve(self):
|
||||||
|
''' create and broadcast shelf creation '''
|
||||||
|
real_broadcast = models.Shelf.broadcast
|
||||||
|
real_shelfbook_broadcast = models.ShelfBook.broadcast
|
||||||
|
|
||||||
|
def add_mock(_, activity, user, **kwargs):
|
||||||
|
''' ok '''
|
||||||
|
self.assertEqual(user.remote_id, self.local_user.remote_id)
|
||||||
|
self.assertEqual(activity['type'], 'Add')
|
||||||
|
self.assertEqual(activity['actor'], self.local_user.remote_id)
|
||||||
|
self.assertEqual(activity['object']['id'], self.book.remote_id)
|
||||||
|
self.assertEqual(activity['target'], shelf.remote_id)
|
||||||
|
|
||||||
|
def remove_mock(_, activity, user, **kwargs):
|
||||||
|
''' ok '''
|
||||||
|
self.assertEqual(user.remote_id, self.local_user.remote_id)
|
||||||
|
self.assertEqual(activity['type'], 'Remove')
|
||||||
|
self.assertEqual(activity['actor'], self.local_user.remote_id)
|
||||||
|
self.assertEqual(activity['object']['id'], self.book.remote_id)
|
||||||
|
self.assertEqual(activity['target'], shelf.remote_id)
|
||||||
|
|
||||||
|
def empty_mock(_, activity, user, **kwargs):
|
||||||
|
''' nah '''
|
||||||
|
|
||||||
|
models.Shelf.broadcast = empty_mock
|
||||||
|
shelf = models.Shelf.objects.create(
|
||||||
|
name='Test Shelf', identifier='test-shelf', user=self.local_user)
|
||||||
|
|
||||||
|
models.ShelfBook.broadcast = add_mock
|
||||||
|
shelf_book = models.ShelfBook.objects.create(
|
||||||
|
shelf=shelf,
|
||||||
|
user=self.local_user,
|
||||||
|
book=self.book)
|
||||||
|
self.assertEqual(shelf.books.first(), self.book)
|
||||||
|
|
||||||
|
models.ShelfBook.broadcast = remove_mock
|
||||||
|
shelf_book.delete()
|
||||||
|
self.assertFalse(shelf.books.exists())
|
||||||
|
|
||||||
|
models.ShelfBook.broadcast = real_shelfbook_broadcast
|
||||||
|
models.Shelf.broadcast = real_broadcast
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
''' testing models '''
|
''' testing models '''
|
||||||
|
from unittest.mock import patch
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
|
@ -11,6 +12,7 @@ from django.utils import timezone
|
||||||
from bookwyrm import models, settings
|
from bookwyrm import models, settings
|
||||||
|
|
||||||
|
|
||||||
|
@patch('bookwyrm.models.Status.broadcast')
|
||||||
class Status(TestCase):
|
class Status(TestCase):
|
||||||
''' lotta types of statuses '''
|
''' lotta types of statuses '''
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -24,13 +26,14 @@ class Status(TestCase):
|
||||||
'../../static/images/default_avi.jpg')
|
'../../static/images/default_avi.jpg')
|
||||||
image = Image.open(image_file)
|
image = Image.open(image_file)
|
||||||
output = BytesIO()
|
output = BytesIO()
|
||||||
|
with patch('bookwyrm.models.Status.broadcast'):
|
||||||
image.save(output, format=image.format)
|
image.save(output, format=image.format)
|
||||||
self.book.cover.save(
|
self.book.cover.save(
|
||||||
'test.jpg',
|
'test.jpg',
|
||||||
ContentFile(output.getvalue())
|
ContentFile(output.getvalue())
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_status_generated_fields(self):
|
def test_status_generated_fields(self, _):
|
||||||
''' setting remote id '''
|
''' setting remote id '''
|
||||||
status = models.Status.objects.create(content='bleh', user=self.user)
|
status = models.Status.objects.create(content='bleh', user=self.user)
|
||||||
expected_id = 'https://%s/user/mouse/status/%d' % \
|
expected_id = 'https://%s/user/mouse/status/%d' % \
|
||||||
|
@ -38,7 +41,7 @@ class Status(TestCase):
|
||||||
self.assertEqual(status.remote_id, expected_id)
|
self.assertEqual(status.remote_id, expected_id)
|
||||||
self.assertEqual(status.privacy, 'public')
|
self.assertEqual(status.privacy, 'public')
|
||||||
|
|
||||||
def test_replies(self):
|
def test_replies(self, _):
|
||||||
''' get a list of replies '''
|
''' get a list of replies '''
|
||||||
parent = models.Status.objects.create(content='hi', user=self.user)
|
parent = models.Status.objects.create(content='hi', user=self.user)
|
||||||
child = models.Status.objects.create(
|
child = models.Status.objects.create(
|
||||||
|
@ -54,7 +57,7 @@ class Status(TestCase):
|
||||||
# should select subclasses
|
# should select subclasses
|
||||||
self.assertIsInstance(replies.last(), models.Review)
|
self.assertIsInstance(replies.last(), models.Review)
|
||||||
|
|
||||||
def test_status_type(self):
|
def test_status_type(self, _):
|
||||||
''' class name '''
|
''' class name '''
|
||||||
self.assertEqual(models.Status().status_type, 'Note')
|
self.assertEqual(models.Status().status_type, 'Note')
|
||||||
self.assertEqual(models.Review().status_type, 'Review')
|
self.assertEqual(models.Review().status_type, 'Review')
|
||||||
|
@ -62,14 +65,14 @@ class Status(TestCase):
|
||||||
self.assertEqual(models.Comment().status_type, 'Comment')
|
self.assertEqual(models.Comment().status_type, 'Comment')
|
||||||
self.assertEqual(models.Boost().status_type, 'Boost')
|
self.assertEqual(models.Boost().status_type, 'Boost')
|
||||||
|
|
||||||
def test_boostable(self):
|
def test_boostable(self, _):
|
||||||
''' can a status be boosted, based on privacy '''
|
''' can a status be boosted, based on privacy '''
|
||||||
self.assertTrue(models.Status(privacy='public').boostable)
|
self.assertTrue(models.Status(privacy='public').boostable)
|
||||||
self.assertTrue(models.Status(privacy='unlisted').boostable)
|
self.assertTrue(models.Status(privacy='unlisted').boostable)
|
||||||
self.assertFalse(models.Status(privacy='followers').boostable)
|
self.assertFalse(models.Status(privacy='followers').boostable)
|
||||||
self.assertFalse(models.Status(privacy='direct').boostable)
|
self.assertFalse(models.Status(privacy='direct').boostable)
|
||||||
|
|
||||||
def test_to_replies(self):
|
def test_to_replies(self, _):
|
||||||
''' activitypub replies collection '''
|
''' activitypub replies collection '''
|
||||||
parent = models.Status.objects.create(content='hi', user=self.user)
|
parent = models.Status.objects.create(content='hi', user=self.user)
|
||||||
child = models.Status.objects.create(
|
child = models.Status.objects.create(
|
||||||
|
@ -83,7 +86,7 @@ class Status(TestCase):
|
||||||
self.assertEqual(replies['id'], '%s/replies' % parent.remote_id)
|
self.assertEqual(replies['id'], '%s/replies' % parent.remote_id)
|
||||||
self.assertEqual(replies['totalItems'], 2)
|
self.assertEqual(replies['totalItems'], 2)
|
||||||
|
|
||||||
def test_status_to_activity(self):
|
def test_status_to_activity(self, _):
|
||||||
''' subclass of the base model version with a "pure" serializer '''
|
''' subclass of the base model version with a "pure" serializer '''
|
||||||
status = models.Status.objects.create(
|
status = models.Status.objects.create(
|
||||||
content='test content', user=self.user)
|
content='test content', user=self.user)
|
||||||
|
@ -93,7 +96,7 @@ class Status(TestCase):
|
||||||
self.assertEqual(activity['content'], 'test content')
|
self.assertEqual(activity['content'], 'test content')
|
||||||
self.assertEqual(activity['sensitive'], False)
|
self.assertEqual(activity['sensitive'], False)
|
||||||
|
|
||||||
def test_status_to_activity_tombstone(self):
|
def test_status_to_activity_tombstone(self, _):
|
||||||
''' subclass of the base model version with a "pure" serializer '''
|
''' subclass of the base model version with a "pure" serializer '''
|
||||||
status = models.Status.objects.create(
|
status = models.Status.objects.create(
|
||||||
content='test content', user=self.user,
|
content='test content', user=self.user,
|
||||||
|
@ -103,7 +106,7 @@ class Status(TestCase):
|
||||||
self.assertEqual(activity['type'], 'Tombstone')
|
self.assertEqual(activity['type'], 'Tombstone')
|
||||||
self.assertFalse(hasattr(activity, 'content'))
|
self.assertFalse(hasattr(activity, 'content'))
|
||||||
|
|
||||||
def test_status_to_pure_activity(self):
|
def test_status_to_pure_activity(self, _):
|
||||||
''' subclass of the base model version with a "pure" serializer '''
|
''' subclass of the base model version with a "pure" serializer '''
|
||||||
status = models.Status.objects.create(
|
status = models.Status.objects.create(
|
||||||
content='test content', user=self.user)
|
content='test content', user=self.user)
|
||||||
|
@ -114,7 +117,7 @@ class Status(TestCase):
|
||||||
self.assertEqual(activity['sensitive'], False)
|
self.assertEqual(activity['sensitive'], False)
|
||||||
self.assertEqual(activity['attachment'], [])
|
self.assertEqual(activity['attachment'], [])
|
||||||
|
|
||||||
def test_generated_note_to_activity(self):
|
def test_generated_note_to_activity(self, _):
|
||||||
''' subclass of the base model version with a "pure" serializer '''
|
''' subclass of the base model version with a "pure" serializer '''
|
||||||
status = models.GeneratedNote.objects.create(
|
status = models.GeneratedNote.objects.create(
|
||||||
content='test content', user=self.user)
|
content='test content', user=self.user)
|
||||||
|
@ -127,7 +130,7 @@ class Status(TestCase):
|
||||||
self.assertEqual(activity['sensitive'], False)
|
self.assertEqual(activity['sensitive'], False)
|
||||||
self.assertEqual(len(activity['tag']), 2)
|
self.assertEqual(len(activity['tag']), 2)
|
||||||
|
|
||||||
def test_generated_note_to_pure_activity(self):
|
def test_generated_note_to_pure_activity(self, _):
|
||||||
''' subclass of the base model version with a "pure" serializer '''
|
''' subclass of the base model version with a "pure" serializer '''
|
||||||
status = models.GeneratedNote.objects.create(
|
status = models.GeneratedNote.objects.create(
|
||||||
content='test content', user=self.user)
|
content='test content', user=self.user)
|
||||||
|
@ -149,7 +152,7 @@ class Status(TestCase):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
activity['attachment'][0].name, 'Test Edition cover')
|
activity['attachment'][0].name, 'Test Edition cover')
|
||||||
|
|
||||||
def test_comment_to_activity(self):
|
def test_comment_to_activity(self, _):
|
||||||
''' subclass of the base model version with a "pure" serializer '''
|
''' subclass of the base model version with a "pure" serializer '''
|
||||||
status = models.Comment.objects.create(
|
status = models.Comment.objects.create(
|
||||||
content='test content', user=self.user, book=self.book)
|
content='test content', user=self.user, book=self.book)
|
||||||
|
@ -159,7 +162,7 @@ class Status(TestCase):
|
||||||
self.assertEqual(activity['content'], 'test content')
|
self.assertEqual(activity['content'], 'test content')
|
||||||
self.assertEqual(activity['inReplyToBook'], self.book.remote_id)
|
self.assertEqual(activity['inReplyToBook'], self.book.remote_id)
|
||||||
|
|
||||||
def test_comment_to_pure_activity(self):
|
def test_comment_to_pure_activity(self, _):
|
||||||
''' subclass of the base model version with a "pure" serializer '''
|
''' subclass of the base model version with a "pure" serializer '''
|
||||||
status = models.Comment.objects.create(
|
status = models.Comment.objects.create(
|
||||||
content='test content', user=self.user, book=self.book)
|
content='test content', user=self.user, book=self.book)
|
||||||
|
@ -176,7 +179,7 @@ class Status(TestCase):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
activity['attachment'][0].name, 'Test Edition cover')
|
activity['attachment'][0].name, 'Test Edition cover')
|
||||||
|
|
||||||
def test_quotation_to_activity(self):
|
def test_quotation_to_activity(self, _):
|
||||||
''' subclass of the base model version with a "pure" serializer '''
|
''' subclass of the base model version with a "pure" serializer '''
|
||||||
status = models.Quotation.objects.create(
|
status = models.Quotation.objects.create(
|
||||||
quote='a sickening sense', content='test content',
|
quote='a sickening sense', content='test content',
|
||||||
|
@ -188,7 +191,7 @@ class Status(TestCase):
|
||||||
self.assertEqual(activity['content'], 'test content')
|
self.assertEqual(activity['content'], 'test content')
|
||||||
self.assertEqual(activity['inReplyToBook'], self.book.remote_id)
|
self.assertEqual(activity['inReplyToBook'], self.book.remote_id)
|
||||||
|
|
||||||
def test_quotation_to_pure_activity(self):
|
def test_quotation_to_pure_activity(self, _):
|
||||||
''' subclass of the base model version with a "pure" serializer '''
|
''' subclass of the base model version with a "pure" serializer '''
|
||||||
status = models.Quotation.objects.create(
|
status = models.Quotation.objects.create(
|
||||||
quote='a sickening sense', content='test content',
|
quote='a sickening sense', content='test content',
|
||||||
|
@ -206,7 +209,7 @@ class Status(TestCase):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
activity['attachment'][0].name, 'Test Edition cover')
|
activity['attachment'][0].name, 'Test Edition cover')
|
||||||
|
|
||||||
def test_review_to_activity(self):
|
def test_review_to_activity(self, _):
|
||||||
''' subclass of the base model version with a "pure" serializer '''
|
''' subclass of the base model version with a "pure" serializer '''
|
||||||
status = models.Review.objects.create(
|
status = models.Review.objects.create(
|
||||||
name='Review name', content='test content', rating=3,
|
name='Review name', content='test content', rating=3,
|
||||||
|
@ -219,7 +222,7 @@ class Status(TestCase):
|
||||||
self.assertEqual(activity['content'], 'test content')
|
self.assertEqual(activity['content'], 'test content')
|
||||||
self.assertEqual(activity['inReplyToBook'], self.book.remote_id)
|
self.assertEqual(activity['inReplyToBook'], self.book.remote_id)
|
||||||
|
|
||||||
def test_review_to_pure_activity(self):
|
def test_review_to_pure_activity(self, _):
|
||||||
''' subclass of the base model version with a "pure" serializer '''
|
''' subclass of the base model version with a "pure" serializer '''
|
||||||
status = models.Review.objects.create(
|
status = models.Review.objects.create(
|
||||||
name='Review name', content='test content', rating=3,
|
name='Review name', content='test content', rating=3,
|
||||||
|
@ -237,8 +240,15 @@ class Status(TestCase):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
activity['attachment'][0].name, 'Test Edition cover')
|
activity['attachment'][0].name, 'Test Edition cover')
|
||||||
|
|
||||||
def test_favorite(self):
|
def test_favorite(self, _):
|
||||||
''' fav a status '''
|
''' fav a status '''
|
||||||
|
real_broadcast = models.Favorite.broadcast
|
||||||
|
def fav_broadcast_mock(_, activity, user):
|
||||||
|
''' ok '''
|
||||||
|
self.assertEqual(user.remote_id, self.user.remote_id)
|
||||||
|
self.assertEqual(activity['type'], 'Like')
|
||||||
|
models.Favorite.broadcast = fav_broadcast_mock
|
||||||
|
|
||||||
status = models.Status.objects.create(
|
status = models.Status.objects.create(
|
||||||
content='test content', user=self.user)
|
content='test content', user=self.user)
|
||||||
fav = models.Favorite.objects.create(status=status, user=self.user)
|
fav = models.Favorite.objects.create(status=status, user=self.user)
|
||||||
|
@ -251,8 +261,9 @@ class Status(TestCase):
|
||||||
self.assertEqual(activity['type'], 'Like')
|
self.assertEqual(activity['type'], 'Like')
|
||||||
self.assertEqual(activity['actor'], self.user.remote_id)
|
self.assertEqual(activity['actor'], self.user.remote_id)
|
||||||
self.assertEqual(activity['object'], status.remote_id)
|
self.assertEqual(activity['object'], status.remote_id)
|
||||||
|
models.Favorite.broadcast = real_broadcast
|
||||||
|
|
||||||
def test_boost(self):
|
def test_boost(self, _):
|
||||||
''' boosting, this one's a bit fussy '''
|
''' boosting, this one's a bit fussy '''
|
||||||
status = models.Status.objects.create(
|
status = models.Status.objects.create(
|
||||||
content='test content', user=self.user)
|
content='test content', user=self.user)
|
||||||
|
@ -264,7 +275,7 @@ class Status(TestCase):
|
||||||
self.assertEqual(activity['type'], 'Announce')
|
self.assertEqual(activity['type'], 'Announce')
|
||||||
self.assertEqual(activity, boost.to_activity(pure=True))
|
self.assertEqual(activity, boost.to_activity(pure=True))
|
||||||
|
|
||||||
def test_notification(self):
|
def test_notification(self, _):
|
||||||
''' a simple model '''
|
''' a simple model '''
|
||||||
notification = models.Notification.objects.create(
|
notification = models.Notification.objects.create(
|
||||||
user=self.user, notification_type='FAVORITE')
|
user=self.user, notification_type='FAVORITE')
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
from unittest.mock import patch
|
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
from bookwyrm import models, broadcast
|
|
||||||
|
|
||||||
|
|
||||||
class Book(TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.user = models.User.objects.create_user(
|
|
||||||
'mouse', 'mouse@mouse.mouse', 'mouseword',
|
|
||||||
local=True, localname='mouse')
|
|
||||||
|
|
||||||
local_follower = models.User.objects.create_user(
|
|
||||||
'joe', 'joe@mouse.mouse', 'jeoword',
|
|
||||||
local=True, localname='joe')
|
|
||||||
self.user.followers.add(local_follower)
|
|
||||||
|
|
||||||
with patch('bookwyrm.models.user.set_remote_server.delay'):
|
|
||||||
follower = models.User.objects.create_user(
|
|
||||||
'rat', 'rat@mouse.mouse', 'ratword', local=False,
|
|
||||||
remote_id='http://example.com/u/1',
|
|
||||||
outbox='http://example.com/u/1/o',
|
|
||||||
shared_inbox='http://example.com/inbox',
|
|
||||||
inbox='http://example.com/u/1/inbox')
|
|
||||||
self.user.followers.add(follower)
|
|
||||||
|
|
||||||
no_inbox_follower = models.User.objects.create_user(
|
|
||||||
'hamster', 'hamster@mouse.mouse', 'hamword',
|
|
||||||
shared_inbox=None, local=False,
|
|
||||||
remote_id='http://example.com/u/2',
|
|
||||||
outbox='http://example.com/u/2/o',
|
|
||||||
inbox='http://example.com/u/2/inbox')
|
|
||||||
self.user.followers.add(no_inbox_follower)
|
|
||||||
|
|
||||||
non_bw_follower = models.User.objects.create_user(
|
|
||||||
'gerbil', 'gerb@mouse.mouse', 'gerbword',
|
|
||||||
remote_id='http://example.com/u/3',
|
|
||||||
outbox='http://example2.com/u/3/o',
|
|
||||||
inbox='http://example2.com/u/3/inbox',
|
|
||||||
shared_inbox='http://example2.com/inbox',
|
|
||||||
bookwyrm_user=False, local=False)
|
|
||||||
self.user.followers.add(non_bw_follower)
|
|
||||||
|
|
||||||
models.User.objects.create_user(
|
|
||||||
'nutria', 'nutria@mouse.mouse', 'nuword',
|
|
||||||
remote_id='http://example.com/u/4',
|
|
||||||
outbox='http://example.com/u/4/o',
|
|
||||||
shared_inbox='http://example.com/inbox',
|
|
||||||
inbox='http://example.com/u/4/inbox',
|
|
||||||
local=False)
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_public_recipients(self):
|
|
||||||
expected = [
|
|
||||||
'http://example2.com/inbox',
|
|
||||||
'http://example.com/inbox',
|
|
||||||
'http://example.com/u/2/inbox',
|
|
||||||
]
|
|
||||||
|
|
||||||
recipients = broadcast.get_public_recipients(self.user)
|
|
||||||
self.assertEqual(recipients, expected)
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_public_recipients_software(self):
|
|
||||||
expected = [
|
|
||||||
'http://example.com/inbox',
|
|
||||||
'http://example.com/u/2/inbox',
|
|
||||||
]
|
|
||||||
|
|
||||||
recipients = broadcast.get_public_recipients(self.user, software='bookwyrm')
|
|
||||||
self.assertEqual(recipients, expected)
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_public_recipients_software_other(self):
|
|
||||||
expected = [
|
|
||||||
'http://example2.com/inbox',
|
|
||||||
]
|
|
||||||
|
|
||||||
recipients = broadcast.get_public_recipients(self.user, software='mastodon')
|
|
||||||
self.assertEqual(recipients, expected)
|
|
|
@ -124,7 +124,7 @@ class GoodreadsImport(TestCase):
|
||||||
job_id=import_job.id, index=index, data=entry, book=self.book)
|
job_id=import_job.id, index=index, data=entry, book=self.book)
|
||||||
break
|
break
|
||||||
|
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
goodreads_import.handle_imported_book(
|
goodreads_import.handle_imported_book(
|
||||||
self.user, import_item, False, 'public')
|
self.user, import_item, False, 'public')
|
||||||
|
|
||||||
|
@ -144,9 +144,10 @@ class GoodreadsImport(TestCase):
|
||||||
|
|
||||||
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'):
|
||||||
shelf = self.user.shelf_set.filter(identifier='to-read').first()
|
shelf = self.user.shelf_set.filter(identifier='to-read').first()
|
||||||
models.ShelfBook.objects.create(
|
models.ShelfBook.objects.create(
|
||||||
shelf=shelf, added_by=self.user, book=self.book)
|
shelf=shelf, user=self.user, book=self.book)
|
||||||
|
|
||||||
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')
|
||||||
|
@ -156,7 +157,7 @@ class GoodreadsImport(TestCase):
|
||||||
job_id=import_job.id, index=index, data=entry, book=self.book)
|
job_id=import_job.id, index=index, data=entry, book=self.book)
|
||||||
break
|
break
|
||||||
|
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
goodreads_import.handle_imported_book(
|
goodreads_import.handle_imported_book(
|
||||||
self.user, import_item, False, 'public')
|
self.user, import_item, False, 'public')
|
||||||
|
|
||||||
|
@ -185,7 +186,7 @@ class GoodreadsImport(TestCase):
|
||||||
job_id=import_job.id, index=index, data=entry, book=self.book)
|
job_id=import_job.id, index=index, data=entry, book=self.book)
|
||||||
break
|
break
|
||||||
|
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
goodreads_import.handle_imported_book(
|
goodreads_import.handle_imported_book(
|
||||||
self.user, import_item, False, 'public')
|
self.user, import_item, False, 'public')
|
||||||
goodreads_import.handle_imported_book(
|
goodreads_import.handle_imported_book(
|
||||||
|
@ -214,7 +215,7 @@ class GoodreadsImport(TestCase):
|
||||||
import_item = models.ImportItem.objects.create(
|
import_item = models.ImportItem.objects.create(
|
||||||
job_id=import_job.id, index=0, data=entry, book=self.book)
|
job_id=import_job.id, index=0, data=entry, book=self.book)
|
||||||
|
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
goodreads_import.handle_imported_book(
|
goodreads_import.handle_imported_book(
|
||||||
self.user, import_item, True, 'unlisted')
|
self.user, import_item, True, 'unlisted')
|
||||||
review = models.Review.objects.get(book=self.book, user=self.user)
|
review = models.Review.objects.get(book=self.book, user=self.user)
|
||||||
|
@ -235,7 +236,7 @@ class GoodreadsImport(TestCase):
|
||||||
import_item = models.ImportItem.objects.create(
|
import_item = models.ImportItem.objects.create(
|
||||||
job_id=import_job.id, index=0, data=entry, book=self.book)
|
job_id=import_job.id, index=0, data=entry, book=self.book)
|
||||||
|
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
goodreads_import.handle_imported_book(
|
goodreads_import.handle_imported_book(
|
||||||
self.user, import_item, False, 'unlisted')
|
self.user, import_item, False, 'unlisted')
|
||||||
self.assertFalse(models.Review.objects.filter(
|
self.assertFalse(models.Review.objects.filter(
|
||||||
|
|
|
@ -22,7 +22,7 @@ class Incoming(TestCase):
|
||||||
'mouse@example.com', 'mouse@mouse.com', 'mouseword',
|
'mouse@example.com', 'mouse@mouse.com', 'mouseword',
|
||||||
local=True, localname='mouse')
|
local=True, 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()
|
self.local_user.save(broadcast=False)
|
||||||
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@rat.com', 'ratword',
|
'rat', 'rat@rat.com', 'ratword',
|
||||||
|
@ -31,6 +31,7 @@ class Incoming(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.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
self.status = models.Status.objects.create(
|
self.status = models.Status.objects.create(
|
||||||
user=self.local_user,
|
user=self.local_user,
|
||||||
content='Test status',
|
content='Test status',
|
||||||
|
@ -117,7 +118,7 @@ class Incoming(TestCase):
|
||||||
"object": "https://example.com/user/mouse"
|
"object": "https://example.com/user/mouse"
|
||||||
}
|
}
|
||||||
|
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
incoming.handle_follow(activity)
|
incoming.handle_follow(activity)
|
||||||
|
|
||||||
# notification created
|
# notification created
|
||||||
|
@ -145,9 +146,9 @@ class Incoming(TestCase):
|
||||||
}
|
}
|
||||||
|
|
||||||
self.local_user.manually_approves_followers = True
|
self.local_user.manually_approves_followers = True
|
||||||
self.local_user.save()
|
self.local_user.save(broadcast=False)
|
||||||
|
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
incoming.handle_follow(activity)
|
incoming.handle_follow(activity)
|
||||||
|
|
||||||
# notification created
|
# notification created
|
||||||
|
@ -177,6 +178,7 @@ class Incoming(TestCase):
|
||||||
"object": "https://example.com/user/mouse"
|
"object": "https://example.com/user/mouse"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
models.UserFollows.objects.create(
|
models.UserFollows.objects.create(
|
||||||
user_subject=self.remote_user, user_object=self.local_user)
|
user_subject=self.remote_user, user_object=self.local_user)
|
||||||
self.assertEqual(self.remote_user, self.local_user.followers.first())
|
self.assertEqual(self.remote_user, self.local_user.followers.first())
|
||||||
|
@ -200,6 +202,7 @@ class Incoming(TestCase):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
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
|
||||||
|
@ -232,6 +235,7 @@ class Incoming(TestCase):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
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
|
||||||
|
@ -280,6 +284,7 @@ class Incoming(TestCase):
|
||||||
|
|
||||||
def test_handle_update_list(self):
|
def test_handle_update_list(self):
|
||||||
''' a new list '''
|
''' a new list '''
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
book_list = models.List.objects.create(
|
book_list = models.List.objects.create(
|
||||||
name='hi', remote_id='https://example.com/list/22',
|
name='hi', remote_id='https://example.com/list/22',
|
||||||
user=self.local_user)
|
user=self.local_user)
|
||||||
|
@ -387,11 +392,14 @@ class Incoming(TestCase):
|
||||||
|
|
||||||
def test_handle_delete_status(self):
|
def test_handle_delete_status(self):
|
||||||
''' remove a status '''
|
''' remove a status '''
|
||||||
|
self.status.user = self.remote_user
|
||||||
|
self.status.save(broadcast=False)
|
||||||
|
|
||||||
self.assertFalse(self.status.deleted)
|
self.assertFalse(self.status.deleted)
|
||||||
activity = {
|
activity = {
|
||||||
'type': 'Delete',
|
'type': 'Delete',
|
||||||
'id': '%s/activity' % self.status.remote_id,
|
'id': '%s/activity' % self.status.remote_id,
|
||||||
'actor': self.local_user.remote_id,
|
'actor': self.remote_user.remote_id,
|
||||||
'object': {'id': self.status.remote_id},
|
'object': {'id': self.status.remote_id},
|
||||||
}
|
}
|
||||||
incoming.handle_delete_status(activity)
|
incoming.handle_delete_status(activity)
|
||||||
|
@ -403,6 +411,8 @@ class Incoming(TestCase):
|
||||||
|
|
||||||
def test_handle_delete_status_notifications(self):
|
def test_handle_delete_status_notifications(self):
|
||||||
''' remove a status with related notifications '''
|
''' remove a status with related notifications '''
|
||||||
|
self.status.user = self.remote_user
|
||||||
|
self.status.save(broadcast=False)
|
||||||
models.Notification.objects.create(
|
models.Notification.objects.create(
|
||||||
related_status=self.status,
|
related_status=self.status,
|
||||||
user=self.local_user,
|
user=self.local_user,
|
||||||
|
@ -418,7 +428,7 @@ class Incoming(TestCase):
|
||||||
activity = {
|
activity = {
|
||||||
'type': 'Delete',
|
'type': 'Delete',
|
||||||
'id': '%s/activity' % self.status.remote_id,
|
'id': '%s/activity' % self.status.remote_id,
|
||||||
'actor': self.local_user.remote_id,
|
'actor': self.remote_user.remote_id,
|
||||||
'object': {'id': self.status.remote_id},
|
'object': {'id': self.status.remote_id},
|
||||||
}
|
}
|
||||||
incoming.handle_delete_status(activity)
|
incoming.handle_delete_status(activity)
|
||||||
|
@ -510,7 +520,6 @@ class Incoming(TestCase):
|
||||||
self.assertEqual(models.Boost.objects.count(), 0)
|
self.assertEqual(models.Boost.objects.count(), 0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def test_handle_unboost(self):
|
def test_handle_unboost(self):
|
||||||
''' undo a boost '''
|
''' undo a boost '''
|
||||||
activity = {
|
activity = {
|
||||||
|
@ -539,7 +548,7 @@ class Incoming(TestCase):
|
||||||
activity = {
|
activity = {
|
||||||
"id": "https://bookwyrm.social/shelfbook/6189#add",
|
"id": "https://bookwyrm.social/shelfbook/6189#add",
|
||||||
"type": "Add",
|
"type": "Add",
|
||||||
"actor": "hhttps://example.com/users/rat",
|
"actor": "https://example.com/users/rat",
|
||||||
"object": "https://bookwyrm.social/book/37292",
|
"object": "https://bookwyrm.social/book/37292",
|
||||||
"target": "https://bookwyrm.social/user/mouse/shelf/to-read",
|
"target": "https://bookwyrm.social/user/mouse/shelf/to-read",
|
||||||
"@context": "https://www.w3.org/ns/activitystreams"
|
"@context": "https://www.w3.org/ns/activitystreams"
|
||||||
|
@ -608,6 +617,7 @@ class Incoming(TestCase):
|
||||||
def test_handle_blocks(self):
|
def test_handle_blocks(self):
|
||||||
''' create a "block" database entry from an activity '''
|
''' create a "block" database entry from an activity '''
|
||||||
self.local_user.followers.add(self.remote_user)
|
self.local_user.followers.add(self.remote_user)
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
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)
|
||||||
|
|
|
@ -35,6 +35,7 @@ class TemplateTags(TestCase):
|
||||||
|
|
||||||
def test_get_user_rating(self):
|
def test_get_user_rating(self):
|
||||||
''' get a user's most recent rating of a book '''
|
''' get a user's most recent rating of a book '''
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
models.Review.objects.create(
|
models.Review.objects.create(
|
||||||
user=self.user, book=self.book, rating=3)
|
user=self.user, book=self.book, rating=3)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
@ -76,14 +77,16 @@ class TemplateTags(TestCase):
|
||||||
|
|
||||||
def test_get_replies(self):
|
def test_get_replies(self):
|
||||||
''' direct replies to a status '''
|
''' direct replies to a status '''
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
parent = models.Review.objects.create(
|
parent = models.Review.objects.create(
|
||||||
user=self.user, book=self.book)
|
user=self.user, book=self.book, content='hi')
|
||||||
first_child = models.Status.objects.create(
|
first_child = models.Status.objects.create(
|
||||||
reply_parent=parent, user=self.user)
|
reply_parent=parent, user=self.user, content='hi')
|
||||||
second_child = models.Status.objects.create(
|
second_child = models.Status.objects.create(
|
||||||
reply_parent=parent, user=self.user)
|
reply_parent=parent, user=self.user, content='hi')
|
||||||
third_child = models.Status.objects.create(
|
third_child = models.Status.objects.create(
|
||||||
reply_parent=parent, user=self.user, deleted=True)
|
reply_parent=parent, user=self.user,
|
||||||
|
deleted=True, deleted_date=timezone.now())
|
||||||
|
|
||||||
replies = bookwyrm_tags.get_replies(parent)
|
replies = bookwyrm_tags.get_replies(parent)
|
||||||
self.assertEqual(len(replies), 2)
|
self.assertEqual(len(replies), 2)
|
||||||
|
@ -94,10 +97,11 @@ class TemplateTags(TestCase):
|
||||||
|
|
||||||
def test_get_parent(self):
|
def test_get_parent(self):
|
||||||
''' get the reply parent of a status '''
|
''' get the reply parent of a status '''
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
parent = models.Review.objects.create(
|
parent = models.Review.objects.create(
|
||||||
user=self.user, book=self.book)
|
user=self.user, book=self.book, content='hi')
|
||||||
child = models.Status.objects.create(
|
child = models.Status.objects.create(
|
||||||
reply_parent=parent, user=self.user)
|
reply_parent=parent, user=self.user, content='hi')
|
||||||
|
|
||||||
result = bookwyrm_tags.get_parent(child)
|
result = bookwyrm_tags.get_parent(child)
|
||||||
self.assertEqual(result, parent)
|
self.assertEqual(result, parent)
|
||||||
|
@ -110,6 +114,7 @@ class TemplateTags(TestCase):
|
||||||
user=self.remote_user, book=self.book)
|
user=self.remote_user, book=self.book)
|
||||||
|
|
||||||
self.assertFalse(bookwyrm_tags.get_user_liked(self.user, status))
|
self.assertFalse(bookwyrm_tags.get_user_liked(self.user, status))
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
models.Favorite.objects.create(
|
models.Favorite.objects.create(
|
||||||
user=self.user,
|
user=self.user,
|
||||||
status=status
|
status=status
|
||||||
|
@ -123,6 +128,7 @@ class TemplateTags(TestCase):
|
||||||
user=self.remote_user, book=self.book)
|
user=self.remote_user, book=self.book)
|
||||||
|
|
||||||
self.assertFalse(bookwyrm_tags.get_user_boosted(self.user, status))
|
self.assertFalse(bookwyrm_tags.get_user_boosted(self.user, status))
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
models.Boost.objects.create(
|
models.Boost.objects.create(
|
||||||
user=self.user,
|
user=self.user,
|
||||||
boosted_status=status
|
boosted_status=status
|
||||||
|
@ -135,6 +141,7 @@ class TemplateTags(TestCase):
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
bookwyrm_tags.follow_request_exists(self.user, self.remote_user))
|
bookwyrm_tags.follow_request_exists(self.user, self.remote_user))
|
||||||
|
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
models.UserFollowRequest.objects.create(
|
models.UserFollowRequest.objects.create(
|
||||||
user_subject=self.user,
|
user_subject=self.user,
|
||||||
user_object=self.remote_user)
|
user_object=self.remote_user)
|
||||||
|
@ -147,6 +154,7 @@ class TemplateTags(TestCase):
|
||||||
|
|
||||||
def test_get_boosted(self):
|
def test_get_boosted(self):
|
||||||
''' load a boosted status '''
|
''' load a boosted status '''
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
status = models.Review.objects.create(
|
status = models.Review.objects.create(
|
||||||
user=self.remote_user, book=self.book)
|
user=self.remote_user, book=self.book)
|
||||||
boost = models.Boost.objects.create(
|
boost = models.Boost.objects.create(
|
||||||
|
@ -233,6 +241,7 @@ class TemplateTags(TestCase):
|
||||||
|
|
||||||
def test_get_status_preview_name(self):
|
def test_get_status_preview_name(self):
|
||||||
''' status context string '''
|
''' status context string '''
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
status = models.Status.objects.create(content='hi', user=self.user)
|
status = models.Status.objects.create(content='hi', user=self.user)
|
||||||
result = bookwyrm_tags.get_status_preview_name(status)
|
result = bookwyrm_tags.get_status_preview_name(status)
|
||||||
self.assertEqual(result, 'status')
|
self.assertEqual(result, 'status')
|
||||||
|
@ -255,6 +264,7 @@ class TemplateTags(TestCase):
|
||||||
|
|
||||||
def test_related_status(self):
|
def test_related_status(self):
|
||||||
''' gets the subclass model for a notification status '''
|
''' gets the subclass model for a notification status '''
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
status = models.Status.objects.create(content='hi', user=self.user)
|
status = models.Status.objects.create(content='hi', user=self.user)
|
||||||
notification = models.Notification.objects.create(
|
notification = models.Notification.objects.create(
|
||||||
user=self.user, notification_type='MENTION',
|
user=self.user, notification_type='MENTION',
|
||||||
|
|
|
@ -84,7 +84,7 @@ class AuthorViews(TestCase):
|
||||||
request = self.factory.post('', form.data)
|
request = self.factory.post('', form.data)
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
|
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
view(request, author.id)
|
view(request, author.id)
|
||||||
author.refresh_from_db()
|
author.refresh_from_db()
|
||||||
self.assertEqual(author.name, 'New Name')
|
self.assertEqual(author.name, 'New Name')
|
||||||
|
|
|
@ -40,6 +40,7 @@ class BlockViews(TestCase):
|
||||||
''' create a "block" database entry from an activity '''
|
''' create a "block" database entry from an activity '''
|
||||||
view = views.Block.as_view()
|
view = views.Block.as_view()
|
||||||
self.local_user.followers.add(self.remote_user)
|
self.local_user.followers.add(self.remote_user)
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
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)
|
||||||
|
@ -48,7 +49,7 @@ class BlockViews(TestCase):
|
||||||
|
|
||||||
request = self.factory.post('')
|
request = self.factory.post('')
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
view(request, self.remote_user.id)
|
view(request, self.remote_user.id)
|
||||||
block = models.UserBlocks.objects.get()
|
block = models.UserBlocks.objects.get()
|
||||||
self.assertEqual(block.user_subject, self.local_user)
|
self.assertEqual(block.user_subject, self.local_user)
|
||||||
|
@ -63,7 +64,7 @@ class BlockViews(TestCase):
|
||||||
request = self.factory.post('')
|
request = self.factory.post('')
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
|
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
views.block.unblock(request, self.remote_user.id)
|
views.block.unblock(request, self.remote_user.id)
|
||||||
|
|
||||||
self.assertFalse(models.UserBlocks.objects.exists())
|
self.assertFalse(models.UserBlocks.objects.exists())
|
||||||
|
|
|
@ -77,7 +77,7 @@ class BookViews(TestCase):
|
||||||
form.data['last_edited_by'] = self.local_user.id
|
form.data['last_edited_by'] = self.local_user.id
|
||||||
request = self.factory.post('', form.data)
|
request = self.factory.post('', form.data)
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
view(request, self.book.id)
|
view(request, self.book.id)
|
||||||
self.book.refresh_from_db()
|
self.book.refresh_from_db()
|
||||||
self.assertEqual(self.book.title, 'New Title')
|
self.assertEqual(self.book.title, 'New Title')
|
||||||
|
@ -90,9 +90,14 @@ class BookViews(TestCase):
|
||||||
title='first ed', parent_work=work)
|
title='first ed', parent_work=work)
|
||||||
edition2 = models.Edition.objects.create(
|
edition2 = models.Edition.objects.create(
|
||||||
title='second ed', parent_work=work)
|
title='second ed', parent_work=work)
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
shelf = models.Shelf.objects.create(
|
shelf = models.Shelf.objects.create(
|
||||||
name='Test Shelf', user=self.local_user)
|
name='Test Shelf', user=self.local_user)
|
||||||
shelf.books.add(edition1)
|
models.ShelfBook.objects.create(
|
||||||
|
book=edition1,
|
||||||
|
user=self.local_user,
|
||||||
|
shelf=shelf,
|
||||||
|
)
|
||||||
models.ReadThrough.objects.create(
|
models.ReadThrough.objects.create(
|
||||||
user=self.local_user, book=edition1)
|
user=self.local_user, book=edition1)
|
||||||
|
|
||||||
|
@ -102,7 +107,7 @@ class BookViews(TestCase):
|
||||||
'edition': edition2.id
|
'edition': edition2.id
|
||||||
})
|
})
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
views.switch_edition(request)
|
views.switch_edition(request)
|
||||||
|
|
||||||
self.assertEqual(models.ShelfBook.objects.get().book, edition2)
|
self.assertEqual(models.ShelfBook.objects.get().book, edition2)
|
||||||
|
|
|
@ -18,6 +18,7 @@ class FeedMessageViews(TestCase):
|
||||||
'mouse@local.com', 'mouse@mouse.mouse', 'password',
|
'mouse@local.com', 'mouse@mouse.mouse', 'password',
|
||||||
local=True, localname='mouse')
|
local=True, localname='mouse')
|
||||||
self.book = models.Edition.objects.create(
|
self.book = models.Edition.objects.create(
|
||||||
|
parent_work=models.Work.objects.create(title='hi'),
|
||||||
title='Example Edition',
|
title='Example Edition',
|
||||||
remote_id='https://example.com/book/1',
|
remote_id='https://example.com/book/1',
|
||||||
)
|
)
|
||||||
|
@ -38,6 +39,7 @@ class FeedMessageViews(TestCase):
|
||||||
def test_status_page(self):
|
def test_status_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.Status.as_view()
|
view = views.Status.as_view()
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
status = models.Status.objects.create(
|
status = models.Status.objects.create(
|
||||||
content='hi', user=self.local_user)
|
content='hi', user=self.local_user)
|
||||||
request = self.factory.get('')
|
request = self.factory.get('')
|
||||||
|
@ -59,6 +61,7 @@ class FeedMessageViews(TestCase):
|
||||||
def test_replies_page(self):
|
def test_replies_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.Replies.as_view()
|
view = views.Replies.as_view()
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
status = models.Status.objects.create(
|
status = models.Status.objects.create(
|
||||||
content='hi', user=self.local_user)
|
content='hi', user=self.local_user)
|
||||||
request = self.factory.get('')
|
request = self.factory.get('')
|
||||||
|
@ -90,9 +93,10 @@ class FeedMessageViews(TestCase):
|
||||||
|
|
||||||
def test_get_suggested_book(self):
|
def test_get_suggested_book(self):
|
||||||
''' gets books the ~*~ algorithm ~*~ thinks you want to post about '''
|
''' gets books the ~*~ algorithm ~*~ thinks you want to post about '''
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
models.ShelfBook.objects.create(
|
models.ShelfBook.objects.create(
|
||||||
book=self.book,
|
book=self.book,
|
||||||
added_by=self.local_user,
|
user=self.local_user,
|
||||||
shelf=self.local_user.shelf_set.get(identifier='reading')
|
shelf=self.local_user.shelf_set.get(identifier='reading')
|
||||||
)
|
)
|
||||||
suggestions = views.feed.get_suggested_books(self.local_user)
|
suggestions = views.feed.get_suggested_books(self.local_user)
|
||||||
|
|
|
@ -46,7 +46,7 @@ class BookViews(TestCase):
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
self.assertEqual(models.UserFollowRequest.objects.count(), 0)
|
self.assertEqual(models.UserFollowRequest.objects.count(), 0)
|
||||||
|
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
views.follow(request)
|
views.follow(request)
|
||||||
|
|
||||||
rel = models.UserFollowRequest.objects.get()
|
rel = models.UserFollowRequest.objects.get()
|
||||||
|
@ -62,7 +62,7 @@ class BookViews(TestCase):
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
self.remote_user.followers.add(self.local_user)
|
self.remote_user.followers.add(self.local_user)
|
||||||
self.assertEqual(self.remote_user.followers.count(), 1)
|
self.assertEqual(self.remote_user.followers.count(), 1)
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
views.unfollow(request)
|
views.unfollow(request)
|
||||||
|
|
||||||
self.assertEqual(self.remote_user.followers.count(), 0)
|
self.assertEqual(self.remote_user.followers.count(), 0)
|
||||||
|
@ -77,7 +77,7 @@ class BookViews(TestCase):
|
||||||
user_object=self.local_user
|
user_object=self.local_user
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
views.accept_follow_request(request)
|
views.accept_follow_request(request)
|
||||||
# request should be deleted
|
# request should be deleted
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
@ -96,7 +96,7 @@ class BookViews(TestCase):
|
||||||
user_object=self.local_user
|
user_object=self.local_user
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
views.delete_follow_request(request)
|
views.delete_follow_request(request)
|
||||||
# request should be deleted
|
# request should be deleted
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
|
|
@ -100,7 +100,7 @@ class GoalViews(TestCase):
|
||||||
'post-status': True
|
'post-status': True
|
||||||
})
|
})
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
with patch('bookwyrm.broadcast.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, 2020)
|
||||||
|
|
||||||
goal = models.AnnualGoal.objects.get()
|
goal = models.AnnualGoal.objects.get()
|
||||||
|
|
|
@ -38,6 +38,7 @@ class ViewsHelpers(TestCase):
|
||||||
)
|
)
|
||||||
self.userdata = json.loads(datafile.read_bytes())
|
self.userdata = json.loads(datafile.read_bytes())
|
||||||
del self.userdata['icon']
|
del self.userdata['icon']
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
self.shelf = models.Shelf.objects.create(
|
self.shelf = models.Shelf.objects.create(
|
||||||
name='Test Shelf',
|
name='Test Shelf',
|
||||||
identifier='test-shelf',
|
identifier='test-shelf',
|
||||||
|
@ -83,6 +84,7 @@ class ViewsHelpers(TestCase):
|
||||||
rat = models.User.objects.create_user(
|
rat = models.User.objects.create_user(
|
||||||
'rat', 'rat@rat.rat', 'password', local=True)
|
'rat', 'rat@rat.rat', 'password', local=True)
|
||||||
|
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
public_status = models.Comment.objects.create(
|
public_status = models.Comment.objects.create(
|
||||||
content='public status', book=self.book, user=self.local_user)
|
content='public status', book=self.book, user=self.local_user)
|
||||||
direct_status = models.Status.objects.create(
|
direct_status = models.Status.objects.create(
|
||||||
|
@ -159,6 +161,7 @@ class ViewsHelpers(TestCase):
|
||||||
rat = models.User.objects.create_user(
|
rat = models.User.objects.create_user(
|
||||||
'rat', 'rat@rat.rat', 'password', local=True)
|
'rat', 'rat@rat.rat', 'password', local=True)
|
||||||
|
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
public_status = models.Comment.objects.create(
|
public_status = models.Comment.objects.create(
|
||||||
content='public status', book=self.book, user=self.local_user)
|
content='public status', book=self.book, user=self.local_user)
|
||||||
rat_public = models.Status.objects.create(
|
rat_public = models.Status.objects.create(
|
||||||
|
@ -240,7 +243,7 @@ class ViewsHelpers(TestCase):
|
||||||
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.broadcast.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, shelf, self.book, 'public')
|
self.local_user, shelf, self.book, 'public')
|
||||||
status = models.GeneratedNote.objects.get()
|
status = models.GeneratedNote.objects.get()
|
||||||
|
@ -251,7 +254,7 @@ class ViewsHelpers(TestCase):
|
||||||
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.broadcast.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, shelf, self.book, 'public')
|
self.local_user, shelf, self.book, 'public')
|
||||||
status = models.GeneratedNote.objects.get()
|
status = models.GeneratedNote.objects.get()
|
||||||
|
@ -262,7 +265,7 @@ class ViewsHelpers(TestCase):
|
||||||
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.broadcast.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, shelf, self.book, 'public')
|
self.local_user, shelf, self.book, 'public')
|
||||||
status = models.GeneratedNote.objects.get()
|
status = models.GeneratedNote.objects.get()
|
||||||
|
@ -272,7 +275,7 @@ class ViewsHelpers(TestCase):
|
||||||
|
|
||||||
def test_handle_reading_status_other(self):
|
def test_handle_reading_status_other(self):
|
||||||
''' posts shelve activities '''
|
''' posts shelve activities '''
|
||||||
with patch('bookwyrm.broadcast.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())
|
||||||
|
|
|
@ -38,10 +38,10 @@ class InteractionViews(TestCase):
|
||||||
view = views.Favorite.as_view()
|
view = views.Favorite.as_view()
|
||||||
request = self.factory.post('')
|
request = self.factory.post('')
|
||||||
request.user = self.remote_user
|
request.user = self.remote_user
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
status = models.Status.objects.create(
|
status = models.Status.objects.create(
|
||||||
user=self.local_user, content='hi')
|
user=self.local_user, content='hi')
|
||||||
|
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
|
||||||
view(request, status.id)
|
view(request, status.id)
|
||||||
fav = models.Favorite.objects.get()
|
fav = models.Favorite.objects.get()
|
||||||
self.assertEqual(fav.status, status)
|
self.assertEqual(fav.status, status)
|
||||||
|
@ -58,15 +58,15 @@ class InteractionViews(TestCase):
|
||||||
view = views.Unfavorite.as_view()
|
view = views.Unfavorite.as_view()
|
||||||
request = self.factory.post('')
|
request = self.factory.post('')
|
||||||
request.user = self.remote_user
|
request.user = self.remote_user
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
status = models.Status.objects.create(
|
status = models.Status.objects.create(
|
||||||
user=self.local_user, content='hi')
|
user=self.local_user, content='hi')
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
|
||||||
views.Favorite.as_view()(request, status.id)
|
views.Favorite.as_view()(request, status.id)
|
||||||
|
|
||||||
self.assertEqual(models.Favorite.objects.count(), 1)
|
self.assertEqual(models.Favorite.objects.count(), 1)
|
||||||
self.assertEqual(models.Notification.objects.count(), 1)
|
self.assertEqual(models.Notification.objects.count(), 1)
|
||||||
|
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
view(request, status.id)
|
view(request, status.id)
|
||||||
self.assertEqual(models.Favorite.objects.count(), 0)
|
self.assertEqual(models.Favorite.objects.count(), 0)
|
||||||
self.assertEqual(models.Notification.objects.count(), 0)
|
self.assertEqual(models.Notification.objects.count(), 0)
|
||||||
|
@ -77,10 +77,10 @@ class InteractionViews(TestCase):
|
||||||
view = views.Boost.as_view()
|
view = views.Boost.as_view()
|
||||||
request = self.factory.post('')
|
request = self.factory.post('')
|
||||||
request.user = self.remote_user
|
request.user = self.remote_user
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
status = models.Status.objects.create(
|
status = models.Status.objects.create(
|
||||||
user=self.local_user, content='hi')
|
user=self.local_user, content='hi')
|
||||||
|
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
|
||||||
view(request, status.id)
|
view(request, status.id)
|
||||||
|
|
||||||
boost = models.Boost.objects.get()
|
boost = models.Boost.objects.get()
|
||||||
|
@ -99,10 +99,10 @@ class InteractionViews(TestCase):
|
||||||
view = views.Boost.as_view()
|
view = views.Boost.as_view()
|
||||||
request = self.factory.post('')
|
request = self.factory.post('')
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
status = models.Status.objects.create(
|
status = models.Status.objects.create(
|
||||||
user=self.local_user, content='hi', privacy='unlisted')
|
user=self.local_user, content='hi', privacy='unlisted')
|
||||||
|
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
|
||||||
view(request, status.id)
|
view(request, status.id)
|
||||||
|
|
||||||
boost = models.Boost.objects.get()
|
boost = models.Boost.objects.get()
|
||||||
|
@ -113,10 +113,10 @@ class InteractionViews(TestCase):
|
||||||
view = views.Boost.as_view()
|
view = views.Boost.as_view()
|
||||||
request = self.factory.post('')
|
request = self.factory.post('')
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
status = models.Status.objects.create(
|
status = models.Status.objects.create(
|
||||||
user=self.local_user, content='hi', privacy='followers')
|
user=self.local_user, content='hi', privacy='followers')
|
||||||
|
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
|
||||||
view(request, status.id)
|
view(request, status.id)
|
||||||
self.assertFalse(models.Boost.objects.exists())
|
self.assertFalse(models.Boost.objects.exists())
|
||||||
|
|
||||||
|
@ -125,10 +125,10 @@ class InteractionViews(TestCase):
|
||||||
view = views.Boost.as_view()
|
view = views.Boost.as_view()
|
||||||
request = self.factory.post('')
|
request = self.factory.post('')
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
status = models.Status.objects.create(
|
status = models.Status.objects.create(
|
||||||
user=self.local_user, content='hi')
|
user=self.local_user, content='hi')
|
||||||
|
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
|
||||||
view(request, status.id)
|
view(request, status.id)
|
||||||
view(request, status.id)
|
view(request, status.id)
|
||||||
self.assertEqual(models.Boost.objects.count(), 1)
|
self.assertEqual(models.Boost.objects.count(), 1)
|
||||||
|
@ -139,14 +139,14 @@ class InteractionViews(TestCase):
|
||||||
view = views.Unboost.as_view()
|
view = views.Unboost.as_view()
|
||||||
request = self.factory.post('')
|
request = self.factory.post('')
|
||||||
request.user = self.remote_user
|
request.user = self.remote_user
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
status = models.Status.objects.create(
|
status = models.Status.objects.create(
|
||||||
user=self.local_user, content='hi')
|
user=self.local_user, content='hi')
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
|
||||||
views.Boost.as_view()(request, status.id)
|
views.Boost.as_view()(request, status.id)
|
||||||
|
|
||||||
self.assertEqual(models.Boost.objects.count(), 1)
|
self.assertEqual(models.Boost.objects.count(), 1)
|
||||||
self.assertEqual(models.Notification.objects.count(), 1)
|
self.assertEqual(models.Notification.objects.count(), 1)
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
view(request, status.id)
|
view(request, status.id)
|
||||||
self.assertEqual(models.Boost.objects.count(), 0)
|
self.assertEqual(models.Boost.objects.count(), 0)
|
||||||
self.assertEqual(models.Notification.objects.count(), 0)
|
self.assertEqual(models.Notification.objects.count(), 0)
|
||||||
|
|
|
@ -10,7 +10,6 @@ from bookwyrm import models, views
|
||||||
from bookwyrm.activitypub import ActivitypubResponse
|
from bookwyrm.activitypub import ActivitypubResponse
|
||||||
|
|
||||||
|
|
||||||
@patch('bookwyrm.broadcast.broadcast_task.delay')
|
|
||||||
class ListViews(TestCase):
|
class ListViews(TestCase):
|
||||||
''' tag views'''
|
''' tag views'''
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -32,6 +31,7 @@ class ListViews(TestCase):
|
||||||
remote_id='https://example.com/book/1',
|
remote_id='https://example.com/book/1',
|
||||||
parent_work=work,
|
parent_work=work,
|
||||||
)
|
)
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
self.list = models.List.objects.create(
|
self.list = models.List.objects.create(
|
||||||
name='Test List', user=self.local_user)
|
name='Test List', user=self.local_user)
|
||||||
self.anonymous_user = AnonymousUser
|
self.anonymous_user = AnonymousUser
|
||||||
|
@ -39,9 +39,10 @@ class ListViews(TestCase):
|
||||||
models.SiteSettings.objects.create()
|
models.SiteSettings.objects.create()
|
||||||
|
|
||||||
|
|
||||||
def test_lists_page(self, _):
|
def test_lists_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.Lists.as_view()
|
view = views.Lists.as_view()
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
models.List.objects.create(name='Public list', user=self.local_user)
|
models.List.objects.create(name='Public list', user=self.local_user)
|
||||||
models.List.objects.create(
|
models.List.objects.create(
|
||||||
name='Private list', privacy='private', user=self.local_user)
|
name='Private list', privacy='private', user=self.local_user)
|
||||||
|
@ -61,8 +62,16 @@ class ListViews(TestCase):
|
||||||
self.assertEqual(result.status_code, 200)
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
def test_lists_create(self, _):
|
def test_lists_create(self):
|
||||||
''' create list view '''
|
''' create list view '''
|
||||||
|
real_broadcast = models.List.broadcast
|
||||||
|
def mock_broadcast(_, activity, user, **kwargs):
|
||||||
|
''' ok '''
|
||||||
|
self.assertEqual(user.remote_id, self.local_user.remote_id)
|
||||||
|
self.assertEqual(activity['type'], 'Create')
|
||||||
|
self.assertEqual(activity['actor'], self.local_user.remote_id)
|
||||||
|
models.List.broadcast = mock_broadcast
|
||||||
|
|
||||||
view = views.Lists.as_view()
|
view = views.Lists.as_view()
|
||||||
request = self.factory.post('', {
|
request = self.factory.post('', {
|
||||||
'name': 'A list',
|
'name': 'A list',
|
||||||
|
@ -78,9 +87,10 @@ class ListViews(TestCase):
|
||||||
self.assertEqual(new_list.description, 'wow')
|
self.assertEqual(new_list.description, 'wow')
|
||||||
self.assertEqual(new_list.privacy, 'unlisted')
|
self.assertEqual(new_list.privacy, 'unlisted')
|
||||||
self.assertEqual(new_list.curation, 'open')
|
self.assertEqual(new_list.curation, 'open')
|
||||||
|
models.List.broadcast = real_broadcast
|
||||||
|
|
||||||
|
|
||||||
def test_list_page(self, _):
|
def test_list_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.List.as_view()
|
view = views.List.as_view()
|
||||||
request = self.factory.get('')
|
request = self.factory.get('')
|
||||||
|
@ -116,8 +126,17 @@ class ListViews(TestCase):
|
||||||
self.assertEqual(result.status_code, 200)
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
def test_list_edit(self, _):
|
def test_list_edit(self):
|
||||||
''' edit a list '''
|
''' edit a list '''
|
||||||
|
real_broadcast = models.List.broadcast
|
||||||
|
def mock_broadcast(_, activity, user, **kwargs):
|
||||||
|
''' ok '''
|
||||||
|
self.assertEqual(user.remote_id, self.local_user.remote_id)
|
||||||
|
self.assertEqual(activity['type'], 'Update')
|
||||||
|
self.assertEqual(activity['actor'], self.local_user.remote_id)
|
||||||
|
self.assertEqual(activity['object']['id'], self.list.remote_id)
|
||||||
|
models.List.broadcast = mock_broadcast
|
||||||
|
|
||||||
view = views.List.as_view()
|
view = views.List.as_view()
|
||||||
request = self.factory.post('', {
|
request = self.factory.post('', {
|
||||||
'name': 'New Name',
|
'name': 'New Name',
|
||||||
|
@ -136,11 +155,13 @@ class ListViews(TestCase):
|
||||||
self.assertEqual(self.list.description, 'wow')
|
self.assertEqual(self.list.description, 'wow')
|
||||||
self.assertEqual(self.list.privacy, 'direct')
|
self.assertEqual(self.list.privacy, 'direct')
|
||||||
self.assertEqual(self.list.curation, 'curated')
|
self.assertEqual(self.list.curation, 'curated')
|
||||||
|
models.List.broadcast = real_broadcast
|
||||||
|
|
||||||
|
|
||||||
def test_curate_page(self, _):
|
def test_curate_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.Curate.as_view()
|
view = views.Curate.as_view()
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
models.List.objects.create(name='Public list', user=self.local_user)
|
models.List.objects.create(name='Public list', user=self.local_user)
|
||||||
models.List.objects.create(
|
models.List.objects.create(
|
||||||
name='Private list', privacy='private', user=self.local_user)
|
name='Private list', privacy='private', user=self.local_user)
|
||||||
|
@ -157,12 +178,22 @@ class ListViews(TestCase):
|
||||||
self.assertEqual(result.status_code, 302)
|
self.assertEqual(result.status_code, 302)
|
||||||
|
|
||||||
|
|
||||||
def test_curate_approve(self, _):
|
def test_curate_approve(self):
|
||||||
''' approve a pending item '''
|
''' approve a pending item '''
|
||||||
|
real_broadcast = models.List.broadcast
|
||||||
|
def mock_broadcast(_, activity, user, **kwargs):
|
||||||
|
''' ok '''
|
||||||
|
self.assertEqual(user.remote_id, self.local_user.remote_id)
|
||||||
|
self.assertEqual(activity['type'], 'Add')
|
||||||
|
self.assertEqual(activity['actor'], self.local_user.remote_id)
|
||||||
|
self.assertEqual(activity['target'], self.list.remote_id)
|
||||||
|
models.ListItem.broadcast = mock_broadcast
|
||||||
|
|
||||||
view = views.Curate.as_view()
|
view = views.Curate.as_view()
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
pending = models.ListItem.objects.create(
|
pending = models.ListItem.objects.create(
|
||||||
book_list=self.list,
|
book_list=self.list,
|
||||||
added_by=self.local_user,
|
user=self.local_user,
|
||||||
book=self.book,
|
book=self.book,
|
||||||
approved=False
|
approved=False
|
||||||
)
|
)
|
||||||
|
@ -178,14 +209,16 @@ class ListViews(TestCase):
|
||||||
self.assertEqual(self.list.books.count(), 1)
|
self.assertEqual(self.list.books.count(), 1)
|
||||||
self.assertEqual(self.list.listitem_set.first(), pending)
|
self.assertEqual(self.list.listitem_set.first(), pending)
|
||||||
self.assertTrue(pending.approved)
|
self.assertTrue(pending.approved)
|
||||||
|
models.ListItem.broadcast = real_broadcast
|
||||||
|
|
||||||
|
|
||||||
def test_curate_reject(self, _):
|
def test_curate_reject(self):
|
||||||
''' approve a pending item '''
|
''' approve a pending item '''
|
||||||
view = views.Curate.as_view()
|
view = views.Curate.as_view()
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
pending = models.ListItem.objects.create(
|
pending = models.ListItem.objects.create(
|
||||||
book_list=self.list,
|
book_list=self.list,
|
||||||
added_by=self.local_user,
|
user=self.local_user,
|
||||||
book=self.book,
|
book=self.book,
|
||||||
approved=False
|
approved=False
|
||||||
)
|
)
|
||||||
|
@ -196,13 +229,22 @@ class ListViews(TestCase):
|
||||||
})
|
})
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
|
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
view(request, self.list.id)
|
view(request, self.list.id)
|
||||||
self.assertFalse(self.list.books.exists())
|
self.assertFalse(self.list.books.exists())
|
||||||
self.assertFalse(models.ListItem.objects.exists())
|
self.assertFalse(models.ListItem.objects.exists())
|
||||||
|
|
||||||
|
|
||||||
def test_add_book(self, _):
|
def test_add_book(self):
|
||||||
''' put a book on a list '''
|
''' put a book on a list '''
|
||||||
|
real_broadcast = models.List.broadcast
|
||||||
|
def mock_broadcast(_, activity, user):
|
||||||
|
''' ok '''
|
||||||
|
self.assertEqual(user.remote_id, self.local_user.remote_id)
|
||||||
|
self.assertEqual(activity['type'], 'Add')
|
||||||
|
self.assertEqual(activity['actor'], self.local_user.remote_id)
|
||||||
|
self.assertEqual(activity['target'], self.list.remote_id)
|
||||||
|
models.ListItem.broadcast = mock_broadcast
|
||||||
request = self.factory.post('', {
|
request = self.factory.post('', {
|
||||||
'book': self.book.id,
|
'book': self.book.id,
|
||||||
})
|
})
|
||||||
|
@ -211,14 +253,23 @@ class ListViews(TestCase):
|
||||||
views.list.add_book(request, self.list.id)
|
views.list.add_book(request, self.list.id)
|
||||||
item = self.list.listitem_set.get()
|
item = self.list.listitem_set.get()
|
||||||
self.assertEqual(item.book, self.book)
|
self.assertEqual(item.book, self.book)
|
||||||
self.assertEqual(item.added_by, self.local_user)
|
self.assertEqual(item.user, self.local_user)
|
||||||
self.assertTrue(item.approved)
|
self.assertTrue(item.approved)
|
||||||
|
models.ListItem.broadcast = real_broadcast
|
||||||
|
|
||||||
|
|
||||||
def test_add_book_outsider(self, _):
|
def test_add_book_outsider(self):
|
||||||
''' put a book on a list '''
|
''' put a book on a list '''
|
||||||
|
real_broadcast = models.List.broadcast
|
||||||
|
def mock_broadcast(_, activity, user):
|
||||||
|
''' ok '''
|
||||||
|
self.assertEqual(user.remote_id, self.rat.remote_id)
|
||||||
|
self.assertEqual(activity['type'], 'Add')
|
||||||
|
self.assertEqual(activity['actor'], self.rat.remote_id)
|
||||||
|
self.assertEqual(activity['target'], self.list.remote_id)
|
||||||
|
models.ListItem.broadcast = mock_broadcast
|
||||||
self.list.curation = 'open'
|
self.list.curation = 'open'
|
||||||
self.list.save()
|
self.list.save(broadcast=False)
|
||||||
request = self.factory.post('', {
|
request = self.factory.post('', {
|
||||||
'book': self.book.id,
|
'book': self.book.id,
|
||||||
})
|
})
|
||||||
|
@ -227,14 +278,24 @@ class ListViews(TestCase):
|
||||||
views.list.add_book(request, self.list.id)
|
views.list.add_book(request, self.list.id)
|
||||||
item = self.list.listitem_set.get()
|
item = self.list.listitem_set.get()
|
||||||
self.assertEqual(item.book, self.book)
|
self.assertEqual(item.book, self.book)
|
||||||
self.assertEqual(item.added_by, self.rat)
|
self.assertEqual(item.user, self.rat)
|
||||||
self.assertTrue(item.approved)
|
self.assertTrue(item.approved)
|
||||||
|
models.ListItem.broadcast = real_broadcast
|
||||||
|
|
||||||
|
|
||||||
def test_add_book_pending(self, _):
|
def test_add_book_pending(self):
|
||||||
''' put a book on a list '''
|
''' put a book on a list awaiting approval '''
|
||||||
|
real_broadcast = models.List.broadcast
|
||||||
|
def mock_broadcast(_, activity, user):
|
||||||
|
''' ok '''
|
||||||
|
self.assertEqual(user.remote_id, self.rat.remote_id)
|
||||||
|
self.assertEqual(activity['type'], 'Add')
|
||||||
|
self.assertEqual(activity['actor'], self.rat.remote_id)
|
||||||
|
self.assertEqual(activity['target'], self.list.remote_id)
|
||||||
|
self.assertEqual(activity['object']['id'], self.book.remote_id)
|
||||||
|
models.ListItem.broadcast = mock_broadcast
|
||||||
self.list.curation = 'curated'
|
self.list.curation = 'curated'
|
||||||
self.list.save()
|
self.list.save(broadcast=False)
|
||||||
request = self.factory.post('', {
|
request = self.factory.post('', {
|
||||||
'book': self.book.id,
|
'book': self.book.id,
|
||||||
})
|
})
|
||||||
|
@ -243,14 +304,24 @@ class ListViews(TestCase):
|
||||||
views.list.add_book(request, self.list.id)
|
views.list.add_book(request, self.list.id)
|
||||||
item = self.list.listitem_set.get()
|
item = self.list.listitem_set.get()
|
||||||
self.assertEqual(item.book, self.book)
|
self.assertEqual(item.book, self.book)
|
||||||
self.assertEqual(item.added_by, self.rat)
|
self.assertEqual(item.user, self.rat)
|
||||||
self.assertFalse(item.approved)
|
self.assertFalse(item.approved)
|
||||||
|
models.ListItem.broadcast = real_broadcast
|
||||||
|
|
||||||
|
|
||||||
def test_add_book_self_curated(self, _):
|
def test_add_book_self_curated(self):
|
||||||
''' put a book on a list '''
|
''' put a book on a list automatically approved '''
|
||||||
|
real_broadcast = models.ListItem.broadcast
|
||||||
|
def mock_broadcast(_, activity, user):
|
||||||
|
''' ok '''
|
||||||
|
self.assertEqual(user.remote_id, self.local_user.remote_id)
|
||||||
|
self.assertEqual(activity['type'], 'Add')
|
||||||
|
self.assertEqual(activity['actor'], self.local_user.remote_id)
|
||||||
|
self.assertEqual(activity['target'], self.list.remote_id)
|
||||||
|
models.ListItem.broadcast = mock_broadcast
|
||||||
|
|
||||||
self.list.curation = 'curated'
|
self.list.curation = 'curated'
|
||||||
self.list.save()
|
self.list.save(broadcast=False)
|
||||||
request = self.factory.post('', {
|
request = self.factory.post('', {
|
||||||
'book': self.book.id,
|
'book': self.book.id,
|
||||||
})
|
})
|
||||||
|
@ -259,18 +330,30 @@ class ListViews(TestCase):
|
||||||
views.list.add_book(request, self.list.id)
|
views.list.add_book(request, self.list.id)
|
||||||
item = self.list.listitem_set.get()
|
item = self.list.listitem_set.get()
|
||||||
self.assertEqual(item.book, self.book)
|
self.assertEqual(item.book, self.book)
|
||||||
self.assertEqual(item.added_by, self.local_user)
|
self.assertEqual(item.user, self.local_user)
|
||||||
self.assertTrue(item.approved)
|
self.assertTrue(item.approved)
|
||||||
|
models.ListItem.broadcast = real_broadcast
|
||||||
|
|
||||||
|
|
||||||
def test_remove_book(self, _):
|
def test_remove_book(self):
|
||||||
''' take an item off a list '''
|
''' take an item off a list '''
|
||||||
|
real_broadcast = models.ListItem.broadcast
|
||||||
|
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
item = models.ListItem.objects.create(
|
item = models.ListItem.objects.create(
|
||||||
book_list=self.list,
|
book_list=self.list,
|
||||||
added_by=self.local_user,
|
user=self.local_user,
|
||||||
book=self.book,
|
book=self.book,
|
||||||
)
|
)
|
||||||
self.assertTrue(self.list.listitem_set.exists())
|
self.assertTrue(self.list.listitem_set.exists())
|
||||||
|
|
||||||
|
def mock_broadcast(_, activity, user):
|
||||||
|
''' ok '''
|
||||||
|
self.assertEqual(user.remote_id, self.local_user.remote_id)
|
||||||
|
self.assertEqual(activity['type'], 'Remove')
|
||||||
|
self.assertEqual(activity['actor'], self.local_user.remote_id)
|
||||||
|
self.assertEqual(activity['target'], self.list.remote_id)
|
||||||
|
models.ListItem.broadcast = mock_broadcast
|
||||||
request = self.factory.post('', {
|
request = self.factory.post('', {
|
||||||
'item': item.id,
|
'item': item.id,
|
||||||
})
|
})
|
||||||
|
@ -279,13 +362,15 @@ class ListViews(TestCase):
|
||||||
views.list.remove_book(request, self.list.id)
|
views.list.remove_book(request, self.list.id)
|
||||||
|
|
||||||
self.assertFalse(self.list.listitem_set.exists())
|
self.assertFalse(self.list.listitem_set.exists())
|
||||||
|
models.ListItem.broadcast = real_broadcast
|
||||||
|
|
||||||
|
|
||||||
def test_remove_book_unauthorized(self, _):
|
def test_remove_book_unauthorized(self):
|
||||||
''' take an item off a list '''
|
''' take an item off a list '''
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
item = models.ListItem.objects.create(
|
item = models.ListItem.objects.create(
|
||||||
book_list=self.list,
|
book_list=self.list,
|
||||||
added_by=self.local_user,
|
user=self.local_user,
|
||||||
book=self.book,
|
book=self.book,
|
||||||
)
|
)
|
||||||
self.assertTrue(self.list.listitem_set.exists())
|
self.assertTrue(self.list.listitem_set.exists())
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
''' sending out activities '''
|
''' sending out activities '''
|
||||||
|
from unittest.mock import patch
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
|
@ -49,12 +50,14 @@ class OutboxView(TestCase):
|
||||||
|
|
||||||
def test_outbox_privacy(self):
|
def test_outbox_privacy(self):
|
||||||
''' don't show dms et cetera in outbox '''
|
''' don't show dms et cetera in outbox '''
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
models.Status.objects.create(
|
models.Status.objects.create(
|
||||||
content='PRIVATE!!', user=self.local_user, privacy='direct')
|
content='PRIVATE!!', user=self.local_user, privacy='direct')
|
||||||
models.Status.objects.create(
|
models.Status.objects.create(
|
||||||
content='bffs ONLY', user=self.local_user, privacy='followers')
|
content='bffs ONLY', user=self.local_user, privacy='followers')
|
||||||
models.Status.objects.create(
|
models.Status.objects.create(
|
||||||
content='unlisted status', user=self.local_user, privacy='unlisted')
|
content='unlisted status', user=self.local_user,
|
||||||
|
privacy='unlisted')
|
||||||
models.Status.objects.create(
|
models.Status.objects.create(
|
||||||
content='look at this', user=self.local_user, privacy='public')
|
content='look at this', user=self.local_user, privacy='public')
|
||||||
|
|
||||||
|
@ -67,6 +70,7 @@ class OutboxView(TestCase):
|
||||||
|
|
||||||
def test_outbox_filter(self):
|
def test_outbox_filter(self):
|
||||||
''' if we only care about reviews, only get reviews '''
|
''' if we only care about reviews, only get reviews '''
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
models.Review.objects.create(
|
models.Review.objects.create(
|
||||||
content='look at this', name='hi', rating=1,
|
content='look at this', name='hi', rating=1,
|
||||||
book=self.book, user=self.local_user)
|
book=self.book, user=self.local_user)
|
||||||
|
|
|
@ -45,7 +45,7 @@ class ReadingViews(TestCase):
|
||||||
'start_date': '2020-01-05',
|
'start_date': '2020-01-05',
|
||||||
})
|
})
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
views.start_reading(request, self.book.id)
|
views.start_reading(request, self.book.id)
|
||||||
|
|
||||||
self.assertEqual(shelf.books.get(), self.book)
|
self.assertEqual(shelf.books.get(), self.book)
|
||||||
|
@ -65,8 +65,9 @@ class ReadingViews(TestCase):
|
||||||
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='to-read')
|
to_read_shelf = self.local_user.shelf_set.get(identifier='to-read')
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
models.ShelfBook.objects.create(
|
models.ShelfBook.objects.create(
|
||||||
shelf=to_read_shelf, book=self.book, added_by=self.local_user)
|
shelf=to_read_shelf, book=self.book, user=self.local_user)
|
||||||
shelf = self.local_user.shelf_set.get(identifier='reading')
|
shelf = self.local_user.shelf_set.get(identifier='reading')
|
||||||
self.assertEqual(to_read_shelf.books.get(), self.book)
|
self.assertEqual(to_read_shelf.books.get(), self.book)
|
||||||
self.assertFalse(shelf.books.exists())
|
self.assertFalse(shelf.books.exists())
|
||||||
|
@ -74,7 +75,7 @@ class ReadingViews(TestCase):
|
||||||
|
|
||||||
request = self.factory.post('')
|
request = self.factory.post('')
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
views.start_reading(request, self.book.id)
|
views.start_reading(request, self.book.id)
|
||||||
|
|
||||||
self.assertFalse(to_read_shelf.books.exists())
|
self.assertFalse(to_read_shelf.books.exists())
|
||||||
|
@ -98,7 +99,7 @@ class ReadingViews(TestCase):
|
||||||
})
|
})
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
|
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
views.finish_reading(request, self.book.id)
|
views.finish_reading(request, self.book.id)
|
||||||
|
|
||||||
self.assertEqual(shelf.books.get(), self.book)
|
self.assertEqual(shelf.books.get(), self.book)
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
|
''' tests updating reading progress '''
|
||||||
|
from datetime import datetime
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
from django.test import TestCase, Client
|
from django.test import TestCase, Client
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from bookwyrm import models
|
from bookwyrm import models
|
||||||
|
|
||||||
@patch('bookwyrm.broadcast.broadcast_task.delay')
|
@patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay')
|
||||||
class ReadThrough(TestCase):
|
class ReadThrough(TestCase):
|
||||||
|
''' readthrough tests '''
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
''' basic user and book data '''
|
||||||
self.client = Client()
|
self.client = Client()
|
||||||
|
|
||||||
self.work = models.Work.objects.create(
|
self.work = models.Work.objects.create(
|
||||||
|
@ -25,6 +28,7 @@ class ReadThrough(TestCase):
|
||||||
'cinco', 'cinco@example.com', 'seissiete',
|
'cinco', 'cinco@example.com', 'seissiete',
|
||||||
local=True, localname='cinco')
|
local=True, localname='cinco')
|
||||||
|
|
||||||
|
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):
|
||||||
|
@ -38,13 +42,15 @@ class ReadThrough(TestCase):
|
||||||
readthroughs = self.edition.readthrough_set.all()
|
readthroughs = self.edition.readthrough_set.all()
|
||||||
self.assertEqual(len(readthroughs), 1)
|
self.assertEqual(len(readthroughs), 1)
|
||||||
self.assertEqual(readthroughs[0].progressupdate_set.count(), 0)
|
self.assertEqual(readthroughs[0].progressupdate_set.count(), 0)
|
||||||
self.assertEqual(readthroughs[0].start_date,
|
self.assertEqual(
|
||||||
|
readthroughs[0].start_date,
|
||||||
datetime(2020, 11, 27, tzinfo=timezone.utc))
|
datetime(2020, 11, 27, tzinfo=timezone.utc))
|
||||||
self.assertEqual(readthroughs[0].progress, None)
|
self.assertEqual(readthroughs[0].progress, None)
|
||||||
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 '''
|
||||||
self.assertEqual(self.edition.readthrough_set.count(), 0)
|
self.assertEqual(self.edition.readthrough_set.count(), 0)
|
||||||
|
|
||||||
self.client.post('/start-reading/{}'.format(self.edition.id), {
|
self.client.post('/start-reading/{}'.format(self.edition.id), {
|
||||||
|
@ -54,7 +60,8 @@ class ReadThrough(TestCase):
|
||||||
|
|
||||||
readthroughs = self.edition.readthrough_set.all()
|
readthroughs = self.edition.readthrough_set.all()
|
||||||
self.assertEqual(len(readthroughs), 1)
|
self.assertEqual(len(readthroughs), 1)
|
||||||
self.assertEqual(readthroughs[0].start_date,
|
self.assertEqual(
|
||||||
|
readthroughs[0].start_date,
|
||||||
datetime(2020, 11, 27, tzinfo=timezone.utc))
|
datetime(2020, 11, 27, tzinfo=timezone.utc))
|
||||||
self.assertEqual(readthroughs[0].progress, 50)
|
self.assertEqual(readthroughs[0].progress, 50)
|
||||||
self.assertEqual(readthroughs[0].finish_date, None)
|
self.assertEqual(readthroughs[0].finish_date, None)
|
||||||
|
@ -76,7 +83,9 @@ class ReadThrough(TestCase):
|
||||||
self.assertEqual(len(progress_updates), 2)
|
self.assertEqual(len(progress_updates), 2)
|
||||||
self.assertEqual(progress_updates[1].mode, models.ProgressMode.PAGE)
|
self.assertEqual(progress_updates[1].mode, models.ProgressMode.PAGE)
|
||||||
self.assertEqual(progress_updates[1].progress, 100)
|
self.assertEqual(progress_updates[1].progress, 100)
|
||||||
self.assertEqual(delay_mock.call_count, 1) # Edit doesn't publish anything
|
|
||||||
|
# Edit doesn't publish anything
|
||||||
|
self.assertEqual(delay_mock.call_count, 1)
|
||||||
|
|
||||||
self.client.post('/delete-readthrough', {
|
self.client.post('/delete-readthrough', {
|
||||||
'id': readthroughs[0].id,
|
'id': readthroughs[0].id,
|
|
@ -2,16 +2,14 @@
|
||||||
|
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
from django.test import RequestFactory, TestCase
|
from django.test import RequestFactory, TestCase
|
||||||
import responses
|
|
||||||
|
|
||||||
from bookwyrm import models
|
from bookwyrm import models
|
||||||
from bookwyrm.views import rss_feed
|
from bookwyrm.views import rss_feed
|
||||||
from bookwyrm.settings import DOMAIN
|
|
||||||
|
|
||||||
class RssFeedView(TestCase):
|
class RssFeedView(TestCase):
|
||||||
''' rss feed behaves as expected '''
|
''' rss feed behaves as expected '''
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
''' test data '''
|
||||||
self.site = models.SiteSettings.objects.create()
|
self.site = models.SiteSettings.objects.create()
|
||||||
|
|
||||||
self.user = models.User.objects.create_user(
|
self.user = models.User.objects.create_user(
|
||||||
|
@ -24,6 +22,7 @@ class RssFeedView(TestCase):
|
||||||
parent_work=work
|
parent_work=work
|
||||||
)
|
)
|
||||||
|
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
self.review = models.Review.objects.create(
|
self.review = models.Review.objects.create(
|
||||||
name='Review name', content='test content', rating=3,
|
name='Review name', content='test content', rating=3,
|
||||||
user=self.user, book=self.book)
|
user=self.user, book=self.book)
|
||||||
|
@ -39,6 +38,7 @@ class RssFeedView(TestCase):
|
||||||
|
|
||||||
|
|
||||||
def test_rss_feed(self):
|
def test_rss_feed(self):
|
||||||
|
''' load an rss feed '''
|
||||||
view = rss_feed.RssFeed()
|
view = rss_feed.RssFeed()
|
||||||
request = self.factory.get('/user/rss_user/rss')
|
request = self.factory.get('/user/rss_user/rss')
|
||||||
with patch("bookwyrm.models.SiteSettings.objects.get") as site:
|
with patch("bookwyrm.models.SiteSettings.objects.get") as site:
|
||||||
|
@ -47,6 +47,5 @@ class RssFeedView(TestCase):
|
||||||
self.assertEqual(result.status_code, 200)
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
self.assertIn(b"Status updates from rss_user", result.content)
|
self.assertIn(b"Status updates from rss_user", result.content)
|
||||||
self.assertIn( b"a sickening sense", result.content)
|
self.assertIn(b"a sickening sense", result.content)
|
||||||
self.assertIn(b"Example Edition", result.content)
|
self.assertIn(b"Example Edition", result.content)
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ from bookwyrm import models, views
|
||||||
from bookwyrm.activitypub import ActivitypubResponse
|
from bookwyrm.activitypub import ActivitypubResponse
|
||||||
|
|
||||||
|
|
||||||
@patch('bookwyrm.broadcast.broadcast_task.delay')
|
@patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay')
|
||||||
class ShelfViews(TestCase):
|
class ShelfViews(TestCase):
|
||||||
''' tag views'''
|
''' tag views'''
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -25,6 +25,7 @@ class ShelfViews(TestCase):
|
||||||
remote_id='https://example.com/book/1',
|
remote_id='https://example.com/book/1',
|
||||||
parent_work=self.work
|
parent_work=self.work
|
||||||
)
|
)
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
self.shelf = models.Shelf.objects.create(
|
self.shelf = models.Shelf.objects.create(
|
||||||
name='Test Shelf',
|
name='Test Shelf',
|
||||||
identifier='test-shelf',
|
identifier='test-shelf',
|
||||||
|
@ -96,6 +97,7 @@ class ShelfViews(TestCase):
|
||||||
'name': 'cool name'
|
'name': 'cool name'
|
||||||
})
|
})
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
view(request, request.user.username, shelf.identifier)
|
view(request, request.user.username, shelf.identifier)
|
||||||
shelf.refresh_from_db()
|
shelf.refresh_from_db()
|
||||||
|
|
||||||
|
@ -116,6 +118,7 @@ class ShelfViews(TestCase):
|
||||||
'name': 'cool name'
|
'name': 'cool name'
|
||||||
})
|
})
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
view(request, request.user.username, shelf.identifier)
|
view(request, request.user.username, shelf.identifier)
|
||||||
|
|
||||||
self.assertEqual(shelf.name, 'To Read')
|
self.assertEqual(shelf.name, 'To Read')
|
||||||
|
@ -128,6 +131,7 @@ class ShelfViews(TestCase):
|
||||||
'shelf': self.shelf.identifier
|
'shelf': self.shelf.identifier
|
||||||
})
|
})
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
views.shelve(request)
|
views.shelve(request)
|
||||||
# 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)
|
||||||
|
@ -142,6 +146,7 @@ class ShelfViews(TestCase):
|
||||||
})
|
})
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
|
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
views.shelve(request)
|
views.shelve(request)
|
||||||
# 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)
|
||||||
|
@ -156,6 +161,7 @@ class ShelfViews(TestCase):
|
||||||
})
|
})
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
|
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
views.shelve(request)
|
views.shelve(request)
|
||||||
# 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)
|
||||||
|
@ -170,7 +176,7 @@ class ShelfViews(TestCase):
|
||||||
})
|
})
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
|
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
views.shelve(request)
|
views.shelve(request)
|
||||||
# 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)
|
||||||
|
@ -178,7 +184,12 @@ class ShelfViews(TestCase):
|
||||||
|
|
||||||
def test_handle_unshelve(self, _):
|
def test_handle_unshelve(self, _):
|
||||||
''' remove a book from a shelf '''
|
''' remove a book from a shelf '''
|
||||||
self.shelf.books.add(self.book)
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
|
models.ShelfBook.objects.create(
|
||||||
|
book=self.book,
|
||||||
|
user=self.local_user,
|
||||||
|
shelf=self.shelf
|
||||||
|
)
|
||||||
self.shelf.save()
|
self.shelf.save()
|
||||||
self.assertEqual(self.shelf.books.count(), 1)
|
self.assertEqual(self.shelf.books.count(), 1)
|
||||||
request = self.factory.post('', {
|
request = self.factory.post('', {
|
||||||
|
@ -186,6 +197,6 @@ class ShelfViews(TestCase):
|
||||||
'shelf': self.shelf.id
|
'shelf': self.shelf.id
|
||||||
})
|
})
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
views.unshelve(request)
|
views.unshelve(request)
|
||||||
self.assertEqual(self.shelf.books.count(), 0)
|
self.assertEqual(self.shelf.books.count(), 0)
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
''' test for app action functionality '''
|
''' test for app action functionality '''
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
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
|
||||||
|
|
||||||
from bookwyrm import forms, models, views
|
from bookwyrm import forms, models, views
|
||||||
from bookwyrm.activitypub import ActivitypubResponse
|
|
||||||
from bookwyrm.settings import DOMAIN
|
from bookwyrm.settings import DOMAIN
|
||||||
|
|
||||||
|
|
||||||
|
@ -47,7 +45,7 @@ class StatusViews(TestCase):
|
||||||
})
|
})
|
||||||
request = self.factory.post('', form.data)
|
request = self.factory.post('', form.data)
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
view(request, 'comment')
|
view(request, 'comment')
|
||||||
status = models.Comment.objects.get()
|
status = models.Comment.objects.get()
|
||||||
self.assertEqual(status.content, '<p>hi</p>')
|
self.assertEqual(status.content, '<p>hi</p>')
|
||||||
|
@ -59,6 +57,7 @@ class StatusViews(TestCase):
|
||||||
view = views.CreateStatus.as_view()
|
view = views.CreateStatus.as_view()
|
||||||
user = models.User.objects.create_user(
|
user = models.User.objects.create_user(
|
||||||
'rat', 'rat@rat.com', 'password', local=True)
|
'rat', 'rat@rat.com', 'password', local=True)
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
parent = models.Status.objects.create(
|
parent = models.Status.objects.create(
|
||||||
content='parent status', user=self.local_user)
|
content='parent status', user=self.local_user)
|
||||||
form = forms.ReplyForm({
|
form = forms.ReplyForm({
|
||||||
|
@ -69,7 +68,7 @@ class StatusViews(TestCase):
|
||||||
})
|
})
|
||||||
request = self.factory.post('', form.data)
|
request = self.factory.post('', form.data)
|
||||||
request.user = user
|
request.user = user
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
view(request, 'reply')
|
view(request, 'reply')
|
||||||
status = models.Status.objects.get(user=user)
|
status = models.Status.objects.get(user=user)
|
||||||
self.assertEqual(status.content, '<p>hi</p>')
|
self.assertEqual(status.content, '<p>hi</p>')
|
||||||
|
@ -92,7 +91,7 @@ class StatusViews(TestCase):
|
||||||
request = self.factory.post('', form.data)
|
request = self.factory.post('', form.data)
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
|
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
view(request, 'comment')
|
view(request, 'comment')
|
||||||
status = models.Status.objects.get()
|
status = models.Status.objects.get()
|
||||||
self.assertEqual(list(status.mention_users.all()), [user])
|
self.assertEqual(list(status.mention_users.all()), [user])
|
||||||
|
@ -116,7 +115,7 @@ class StatusViews(TestCase):
|
||||||
request = self.factory.post('', form.data)
|
request = self.factory.post('', form.data)
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
|
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
view(request, 'comment')
|
view(request, 'comment')
|
||||||
status = models.Status.objects.get()
|
status = models.Status.objects.get()
|
||||||
|
|
||||||
|
@ -128,7 +127,7 @@ class StatusViews(TestCase):
|
||||||
})
|
})
|
||||||
request = self.factory.post('', form.data)
|
request = self.factory.post('', form.data)
|
||||||
request.user = user
|
request.user = user
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
view(request, 'reply')
|
view(request, 'reply')
|
||||||
|
|
||||||
reply = models.Status.replies(status).first()
|
reply = models.Status.replies(status).first()
|
||||||
|
@ -221,12 +220,13 @@ class StatusViews(TestCase):
|
||||||
def test_handle_delete_status(self):
|
def test_handle_delete_status(self):
|
||||||
''' marks a status as deleted '''
|
''' marks a status as deleted '''
|
||||||
view = views.DeleteStatus.as_view()
|
view = views.DeleteStatus.as_view()
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
status = models.Status.objects.create(
|
status = models.Status.objects.create(
|
||||||
user=self.local_user, content='hi')
|
user=self.local_user, content='hi')
|
||||||
self.assertFalse(status.deleted)
|
self.assertFalse(status.deleted)
|
||||||
request = self.factory.post('')
|
request = self.factory.post('')
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
view(request, status.id)
|
view(request, status.id)
|
||||||
status.refresh_from_db()
|
status.refresh_from_db()
|
||||||
self.assertTrue(status.deleted)
|
self.assertTrue(status.deleted)
|
||||||
|
|
|
@ -39,6 +39,7 @@ class TagViews(TestCase):
|
||||||
def test_tag_page(self):
|
def test_tag_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.Tag.as_view()
|
view = views.Tag.as_view()
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
tag = models.Tag.objects.create(name='hi there')
|
tag = models.Tag.objects.create(name='hi there')
|
||||||
models.UserTag.objects.create(
|
models.UserTag.objects.create(
|
||||||
tag=tag, user=self.local_user, book=self.book)
|
tag=tag, user=self.local_user, book=self.book)
|
||||||
|
@ -68,7 +69,7 @@ class TagViews(TestCase):
|
||||||
})
|
})
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
|
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
view(request)
|
view(request)
|
||||||
|
|
||||||
tag = models.Tag.objects.get()
|
tag = models.Tag.objects.get()
|
||||||
|
@ -82,6 +83,7 @@ class TagViews(TestCase):
|
||||||
def test_untag(self):
|
def test_untag(self):
|
||||||
''' remove a tag from a book '''
|
''' remove a tag from a book '''
|
||||||
view = views.RemoveTag.as_view()
|
view = views.RemoveTag.as_view()
|
||||||
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
tag = models.Tag.objects.create(name='A Tag!?')
|
tag = models.Tag.objects.create(name='A Tag!?')
|
||||||
models.UserTag.objects.create(
|
models.UserTag.objects.create(
|
||||||
user=self.local_user, book=self.book, tag=tag)
|
user=self.local_user, book=self.book, tag=tag)
|
||||||
|
@ -93,7 +95,7 @@ class TagViews(TestCase):
|
||||||
})
|
})
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
|
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
view(request)
|
view(request)
|
||||||
|
|
||||||
self.assertTrue(models.Tag.objects.filter(name='A Tag!?').exists())
|
self.assertTrue(models.Tag.objects.filter(name='A Tag!?').exists())
|
||||||
|
|
|
@ -138,7 +138,7 @@ class UserViews(TestCase):
|
||||||
request = self.factory.post('', form.data)
|
request = self.factory.post('', form.data)
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
|
|
||||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
|
||||||
view(request)
|
view(request)
|
||||||
self.assertEqual(self.local_user.name, 'New Name')
|
self.assertEqual(self.local_user.name, 'New Name')
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ from .error import not_found_page, server_error_page
|
||||||
from .federation import Federation
|
from .federation import Federation
|
||||||
from .feed import DirectMessage, Feed, Replies, Status
|
from .feed import DirectMessage, Feed, Replies, Status
|
||||||
from .follow import follow, unfollow
|
from .follow import follow, unfollow
|
||||||
from .follow import accept_follow_request, delete_follow_request, handle_accept
|
from .follow import accept_follow_request, delete_follow_request
|
||||||
from .goal import Goal
|
from .goal import Goal
|
||||||
from .import_data import Import, ImportStatus
|
from .import_data import Import, ImportStatus
|
||||||
from .interaction import Favorite, Unfavorite, Boost, Unboost
|
from .interaction import Favorite, Unfavorite, Boost, Unboost
|
||||||
|
|
|
@ -46,6 +46,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)
|
||||||
return redirect(request.GET.get('next', '/'))
|
return redirect(request.GET.get('next', '/'))
|
||||||
|
|
||||||
# login errors
|
# login errors
|
||||||
|
|
|
@ -8,7 +8,6 @@ from django.views import View
|
||||||
|
|
||||||
from bookwyrm import forms, models
|
from bookwyrm import forms, models
|
||||||
from bookwyrm.activitypub import ActivitypubResponse
|
from bookwyrm.activitypub import ActivitypubResponse
|
||||||
from bookwyrm.broadcast import broadcast
|
|
||||||
from .helpers import is_api_request
|
from .helpers import is_api_request
|
||||||
|
|
||||||
|
|
||||||
|
@ -62,5 +61,4 @@ class EditAuthor(View):
|
||||||
return TemplateResponse(request, 'edit_author.html', data)
|
return TemplateResponse(request, 'edit_author.html', data)
|
||||||
author = form.save()
|
author = form.save()
|
||||||
|
|
||||||
broadcast(request.user, author.to_update_activity(request.user))
|
|
||||||
return redirect('/author/%s' % author.id)
|
return redirect('/author/%s' % author.id)
|
||||||
|
|
|
@ -8,7 +8,6 @@ from django.views import View
|
||||||
from django.views.decorators.http import require_POST
|
from django.views.decorators.http import require_POST
|
||||||
|
|
||||||
from bookwyrm import models
|
from bookwyrm import models
|
||||||
from bookwyrm.broadcast import broadcast
|
|
||||||
|
|
||||||
# pylint: disable= no-self-use
|
# pylint: disable= no-self-use
|
||||||
@method_decorator(login_required, name='dispatch')
|
@method_decorator(login_required, name='dispatch')
|
||||||
|
@ -22,15 +21,8 @@ class Block(View):
|
||||||
def post(self, request, user_id):
|
def post(self, request, user_id):
|
||||||
''' block a user '''
|
''' block a user '''
|
||||||
to_block = get_object_or_404(models.User, id=user_id)
|
to_block = get_object_or_404(models.User, id=user_id)
|
||||||
block = models.UserBlocks.objects.create(
|
models.UserBlocks.objects.create(
|
||||||
user_subject=request.user, user_object=to_block)
|
user_subject=request.user, user_object=to_block)
|
||||||
if not to_block.local:
|
|
||||||
broadcast(
|
|
||||||
request.user,
|
|
||||||
block.to_activity(),
|
|
||||||
privacy='direct',
|
|
||||||
direct_recipients=[to_block]
|
|
||||||
)
|
|
||||||
return redirect('/preferences/block')
|
return redirect('/preferences/block')
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,13 +38,5 @@ def unblock(request, user_id):
|
||||||
)
|
)
|
||||||
except models.UserBlocks.DoesNotExist:
|
except models.UserBlocks.DoesNotExist:
|
||||||
return HttpResponseNotFound()
|
return HttpResponseNotFound()
|
||||||
|
|
||||||
if not to_unblock.local:
|
|
||||||
broadcast(
|
|
||||||
request.user,
|
|
||||||
block.to_undo_activity(request.user),
|
|
||||||
privacy='direct',
|
|
||||||
direct_recipients=[to_unblock]
|
|
||||||
)
|
|
||||||
block.delete()
|
block.delete()
|
||||||
return redirect('/preferences/block')
|
return redirect('/preferences/block')
|
||||||
|
|
|
@ -12,7 +12,6 @@ from django.views.decorators.http import require_POST
|
||||||
|
|
||||||
from bookwyrm import forms, models
|
from bookwyrm import forms, models
|
||||||
from bookwyrm.activitypub import ActivitypubResponse
|
from bookwyrm.activitypub import ActivitypubResponse
|
||||||
from bookwyrm.broadcast import broadcast
|
|
||||||
from bookwyrm.connectors import connector_manager
|
from bookwyrm.connectors import connector_manager
|
||||||
from bookwyrm.settings import PAGE_LENGTH
|
from bookwyrm.settings import PAGE_LENGTH
|
||||||
from .helpers import is_api_request, get_activity_feed, get_edition
|
from .helpers import is_api_request, get_activity_feed, get_edition
|
||||||
|
@ -78,12 +77,12 @@ class Book(View):
|
||||||
.order_by('-updated_date')
|
.order_by('-updated_date')
|
||||||
|
|
||||||
user_shelves = models.ShelfBook.objects.filter(
|
user_shelves = models.ShelfBook.objects.filter(
|
||||||
added_by=request.user, book=book
|
user=request.user, book=book
|
||||||
)
|
)
|
||||||
|
|
||||||
other_edition_shelves = models.ShelfBook.objects.filter(
|
other_edition_shelves = models.ShelfBook.objects.filter(
|
||||||
~Q(book=book),
|
~Q(book=book),
|
||||||
added_by=request.user,
|
user=request.user,
|
||||||
book__parent_work=book.parent_work,
|
book__parent_work=book.parent_work,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -136,7 +135,6 @@ class EditBook(View):
|
||||||
return TemplateResponse(request, 'edit_book.html', data)
|
return TemplateResponse(request, 'edit_book.html', data)
|
||||||
book = form.save()
|
book = form.save()
|
||||||
|
|
||||||
broadcast(request.user, book.to_update_activity(request.user))
|
|
||||||
return redirect('/book/%s' % book.id)
|
return redirect('/book/%s' % book.id)
|
||||||
|
|
||||||
|
|
||||||
|
@ -170,7 +168,6 @@ def upload_cover(request, book_id):
|
||||||
book.cover = form.files['cover']
|
book.cover = form.files['cover']
|
||||||
book.save()
|
book.save()
|
||||||
|
|
||||||
broadcast(request.user, book.to_update_activity(request.user))
|
|
||||||
return redirect('/book/%s' % book.id)
|
return redirect('/book/%s' % book.id)
|
||||||
|
|
||||||
|
|
||||||
|
@ -189,7 +186,6 @@ def add_description(request, book_id):
|
||||||
book.description = description
|
book.description = description
|
||||||
book.save()
|
book.save()
|
||||||
|
|
||||||
broadcast(request.user, book.to_update_activity(request.user))
|
|
||||||
return redirect('/book/%s' % book.id)
|
return redirect('/book/%s' % book.id)
|
||||||
|
|
||||||
|
|
||||||
|
@ -215,12 +211,14 @@ def switch_edition(request):
|
||||||
shelf__user=request.user
|
shelf__user=request.user
|
||||||
)
|
)
|
||||||
for shelfbook in shelfbooks.all():
|
for shelfbook in shelfbooks.all():
|
||||||
broadcast(request.user, shelfbook.to_remove_activity(request.user))
|
with transaction.atomic():
|
||||||
|
models.ShelfBook.objects.create(
|
||||||
shelfbook.book = new_edition
|
created_date=shelfbook.created_date,
|
||||||
shelfbook.save()
|
user=shelfbook.user,
|
||||||
|
shelf=shelfbook.shelf,
|
||||||
broadcast(request.user, shelfbook.to_add_activity(request.user))
|
book=new_edition
|
||||||
|
)
|
||||||
|
shelfbook.delete()
|
||||||
|
|
||||||
readthroughs = models.ReadThrough.objects.filter(
|
readthroughs = models.ReadThrough.objects.filter(
|
||||||
book__parent_work=new_edition.parent_work,
|
book__parent_work=new_edition.parent_work,
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
''' views for actions you can take in the application '''
|
''' views for actions you can take in the application '''
|
||||||
from django.db import transaction
|
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.http import HttpResponseBadRequest
|
from django.http import HttpResponseBadRequest
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.views.decorators.http import require_POST
|
from django.views.decorators.http import require_POST
|
||||||
|
|
||||||
from bookwyrm import models
|
from bookwyrm import models
|
||||||
from bookwyrm.broadcast import broadcast
|
|
||||||
from .helpers import get_user_from_username
|
from .helpers import get_user_from_username
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -19,13 +17,10 @@ def follow(request):
|
||||||
except models.User.DoesNotExist:
|
except models.User.DoesNotExist:
|
||||||
return HttpResponseBadRequest()
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
relationship, _ = models.UserFollowRequest.objects.get_or_create(
|
models.UserFollowRequest.objects.get_or_create(
|
||||||
user_subject=request.user,
|
user_subject=request.user,
|
||||||
user_object=to_follow,
|
user_object=to_follow,
|
||||||
)
|
)
|
||||||
activity = relationship.to_activity()
|
|
||||||
broadcast(
|
|
||||||
request.user, activity, privacy='direct', direct_recipients=[to_follow])
|
|
||||||
return redirect(to_follow.local_path)
|
return redirect(to_follow.local_path)
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,14 +34,10 @@ def unfollow(request):
|
||||||
except models.User.DoesNotExist:
|
except models.User.DoesNotExist:
|
||||||
return HttpResponseBadRequest()
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
relationship = models.UserFollows.objects.get(
|
models.UserFollows.objects.get(
|
||||||
user_subject=request.user,
|
user_subject=request.user,
|
||||||
user_object=to_unfollow
|
user_object=to_unfollow
|
||||||
)
|
)
|
||||||
activity = relationship.to_undo_activity(request.user)
|
|
||||||
broadcast(
|
|
||||||
request.user, activity,
|
|
||||||
privacy='direct', direct_recipients=[to_unfollow])
|
|
||||||
|
|
||||||
to_unfollow.followers.remove(request.user)
|
to_unfollow.followers.remove(request.user)
|
||||||
return redirect(to_unfollow.local_path)
|
return redirect(to_unfollow.local_path)
|
||||||
|
@ -70,24 +61,11 @@ def accept_follow_request(request):
|
||||||
except models.UserFollowRequest.DoesNotExist:
|
except models.UserFollowRequest.DoesNotExist:
|
||||||
# Request already dealt with.
|
# Request already dealt with.
|
||||||
return redirect(request.user.local_path)
|
return redirect(request.user.local_path)
|
||||||
handle_accept(follow_request)
|
follow_request.accept()
|
||||||
|
|
||||||
return redirect(request.user.local_path)
|
return redirect(request.user.local_path)
|
||||||
|
|
||||||
|
|
||||||
def handle_accept(follow_request):
|
|
||||||
''' send an acceptance message to a follow request '''
|
|
||||||
user = follow_request.user_subject
|
|
||||||
to_follow = follow_request.user_object
|
|
||||||
with transaction.atomic():
|
|
||||||
relationship = models.UserFollows.from_request(follow_request)
|
|
||||||
follow_request.delete()
|
|
||||||
relationship.save()
|
|
||||||
|
|
||||||
activity = relationship.to_accept_activity()
|
|
||||||
broadcast(to_follow, activity, privacy='direct', direct_recipients=[user])
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@require_POST
|
@require_POST
|
||||||
def delete_follow_request(request):
|
def delete_follow_request(request):
|
||||||
|
@ -106,8 +84,5 @@ def delete_follow_request(request):
|
||||||
except models.UserFollowRequest.DoesNotExist:
|
except models.UserFollowRequest.DoesNotExist:
|
||||||
return HttpResponseBadRequest()
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
activity = follow_request.to_reject_activity()
|
|
||||||
follow_request.delete()
|
follow_request.delete()
|
||||||
broadcast(
|
|
||||||
request.user, activity, privacy='direct', direct_recipients=[requester])
|
|
||||||
return redirect('/user/%s' % request.user.localname)
|
return redirect('/user/%s' % request.user.localname)
|
||||||
|
|
|
@ -7,7 +7,6 @@ from django.utils.decorators import method_decorator
|
||||||
from django.views import View
|
from django.views import View
|
||||||
|
|
||||||
from bookwyrm import forms, models
|
from bookwyrm import forms, models
|
||||||
from bookwyrm.broadcast import broadcast
|
|
||||||
from bookwyrm.status import create_generated_note
|
from bookwyrm.status import create_generated_note
|
||||||
from .helpers import get_user_from_username, object_visible_to_user
|
from .helpers import get_user_from_username, object_visible_to_user
|
||||||
|
|
||||||
|
@ -63,23 +62,10 @@ class Goal(View):
|
||||||
|
|
||||||
if request.POST.get('post-status'):
|
if request.POST.get('post-status'):
|
||||||
# create status, if appropraite
|
# create status, if appropraite
|
||||||
status = create_generated_note(
|
create_generated_note(
|
||||||
request.user,
|
request.user,
|
||||||
'set a goal to read %d books in %d' % (goal.goal, goal.year),
|
'set a goal to read %d books in %d' % (goal.goal, goal.year),
|
||||||
privacy=goal.privacy
|
privacy=goal.privacy
|
||||||
)
|
)
|
||||||
broadcast(
|
|
||||||
request.user,
|
|
||||||
status.to_create_activity(request.user),
|
|
||||||
privacy=status.privacy,
|
|
||||||
software='bookwyrm')
|
|
||||||
|
|
||||||
# re-format the activity for non-bookwyrm servers
|
|
||||||
remote_activity = status.to_create_activity(request.user, pure=True)
|
|
||||||
broadcast(
|
|
||||||
request.user,
|
|
||||||
remote_activity,
|
|
||||||
privacy=status.privacy,
|
|
||||||
software='other')
|
|
||||||
|
|
||||||
return redirect(request.headers.get('Referer', '/'))
|
return redirect(request.headers.get('Referer', '/'))
|
||||||
|
|
|
@ -4,7 +4,6 @@ from requests import HTTPError
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from bookwyrm import activitypub, models
|
from bookwyrm import activitypub, models
|
||||||
from bookwyrm.broadcast import broadcast
|
|
||||||
from bookwyrm.connectors import ConnectorException, get_data
|
from bookwyrm.connectors import ConnectorException, get_data
|
||||||
from bookwyrm.status import create_generated_note
|
from bookwyrm.status import create_generated_note
|
||||||
from bookwyrm.utils import regex
|
from bookwyrm.utils import regex
|
||||||
|
@ -199,7 +198,6 @@ def handle_reading_status(user, shelf, book, privacy):
|
||||||
)
|
)
|
||||||
status.save()
|
status.save()
|
||||||
|
|
||||||
broadcast(user, status.to_create_activity(user))
|
|
||||||
|
|
||||||
def is_blocked(viewer, user):
|
def is_blocked(viewer, user):
|
||||||
''' is this viewer blocked by the user? '''
|
''' is this viewer blocked by the user? '''
|
||||||
|
|
|
@ -7,7 +7,6 @@ from django.utils.decorators import method_decorator
|
||||||
from django.views import View
|
from django.views import View
|
||||||
|
|
||||||
from bookwyrm import models
|
from bookwyrm import models
|
||||||
from bookwyrm.broadcast import broadcast
|
|
||||||
from bookwyrm.status import create_notification
|
from bookwyrm.status import create_notification
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,7 +18,7 @@ class Favorite(View):
|
||||||
''' create a like '''
|
''' create a like '''
|
||||||
status = models.Status.objects.get(id=status_id)
|
status = models.Status.objects.get(id=status_id)
|
||||||
try:
|
try:
|
||||||
favorite = models.Favorite.objects.create(
|
models.Favorite.objects.create(
|
||||||
status=status,
|
status=status,
|
||||||
user=request.user
|
user=request.user
|
||||||
)
|
)
|
||||||
|
@ -27,10 +26,6 @@ class Favorite(View):
|
||||||
# you already fav'ed that
|
# you already fav'ed that
|
||||||
return HttpResponseBadRequest()
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
fav_activity = favorite.to_activity()
|
|
||||||
broadcast(
|
|
||||||
request.user, fav_activity, privacy='direct',
|
|
||||||
direct_recipients=[status.user])
|
|
||||||
if status.user.local:
|
if status.user.local:
|
||||||
create_notification(
|
create_notification(
|
||||||
status.user,
|
status.user,
|
||||||
|
@ -56,9 +51,7 @@ class Unfavorite(View):
|
||||||
# can't find that status, idk
|
# can't find that status, idk
|
||||||
return HttpResponseNotFound()
|
return HttpResponseNotFound()
|
||||||
|
|
||||||
fav_activity = favorite.to_undo_activity(request.user)
|
|
||||||
favorite.delete()
|
favorite.delete()
|
||||||
broadcast(request.user, fav_activity, direct_recipients=[status.user])
|
|
||||||
|
|
||||||
# check for notification
|
# check for notification
|
||||||
if status.user.local:
|
if status.user.local:
|
||||||
|
@ -86,15 +79,12 @@ class Boost(View):
|
||||||
# you already boosted that.
|
# you already boosted that.
|
||||||
return redirect(request.headers.get('Referer', '/'))
|
return redirect(request.headers.get('Referer', '/'))
|
||||||
|
|
||||||
boost = models.Boost.objects.create(
|
models.Boost.objects.create(
|
||||||
boosted_status=status,
|
boosted_status=status,
|
||||||
privacy=status.privacy,
|
privacy=status.privacy,
|
||||||
user=request.user,
|
user=request.user,
|
||||||
)
|
)
|
||||||
|
|
||||||
boost_activity = boost.to_activity()
|
|
||||||
broadcast(request.user, boost_activity)
|
|
||||||
|
|
||||||
if status.user.local:
|
if status.user.local:
|
||||||
create_notification(
|
create_notification(
|
||||||
status.user,
|
status.user,
|
||||||
|
@ -114,10 +104,8 @@ class Unboost(View):
|
||||||
boost = models.Boost.objects.filter(
|
boost = models.Boost.objects.filter(
|
||||||
boosted_status=status, user=request.user
|
boosted_status=status, user=request.user
|
||||||
).first()
|
).first()
|
||||||
activity = boost.to_undo_activity(request.user)
|
|
||||||
|
|
||||||
boost.delete()
|
boost.delete()
|
||||||
broadcast(request.user, activity)
|
|
||||||
|
|
||||||
# delete related notification
|
# delete related notification
|
||||||
if status.user.local:
|
if status.user.local:
|
||||||
|
|
|
@ -11,7 +11,6 @@ from django.views.decorators.http import require_POST
|
||||||
|
|
||||||
from bookwyrm import forms, models
|
from bookwyrm import forms, models
|
||||||
from bookwyrm.activitypub import ActivitypubResponse
|
from bookwyrm.activitypub import ActivitypubResponse
|
||||||
from bookwyrm.broadcast import broadcast
|
|
||||||
from bookwyrm.connectors import connector_manager
|
from bookwyrm.connectors import connector_manager
|
||||||
from .helpers import is_api_request, object_visible_to_user, privacy_filter
|
from .helpers import is_api_request, object_visible_to_user, privacy_filter
|
||||||
from .helpers import get_user_from_username
|
from .helpers import get_user_from_username
|
||||||
|
@ -55,13 +54,6 @@ class Lists(View):
|
||||||
return redirect('lists')
|
return redirect('lists')
|
||||||
book_list = form.save()
|
book_list = form.save()
|
||||||
|
|
||||||
# let the world know
|
|
||||||
broadcast(
|
|
||||||
request.user,
|
|
||||||
book_list.to_create_activity(request.user),
|
|
||||||
privacy=book_list.privacy,
|
|
||||||
software='bookwyrm'
|
|
||||||
)
|
|
||||||
return redirect(book_list.local_path)
|
return redirect(book_list.local_path)
|
||||||
|
|
||||||
class UserLists(View):
|
class UserLists(View):
|
||||||
|
@ -136,19 +128,12 @@ class List(View):
|
||||||
@method_decorator(login_required, name='dispatch')
|
@method_decorator(login_required, name='dispatch')
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def post(self, request, list_id):
|
def post(self, request, list_id):
|
||||||
''' edit a book_list '''
|
''' edit a list '''
|
||||||
book_list = get_object_or_404(models.List, id=list_id)
|
book_list = get_object_or_404(models.List, id=list_id)
|
||||||
form = forms.ListForm(request.POST, instance=book_list)
|
form = forms.ListForm(request.POST, instance=book_list)
|
||||||
if not form.is_valid():
|
if not form.is_valid():
|
||||||
return redirect('list', book_list.id)
|
return redirect('list', book_list.id)
|
||||||
book_list = form.save()
|
book_list = form.save()
|
||||||
# let the world know
|
|
||||||
broadcast(
|
|
||||||
request.user,
|
|
||||||
book_list.to_update_activity(request.user),
|
|
||||||
privacy=book_list.privacy,
|
|
||||||
software='bookwyrm'
|
|
||||||
)
|
|
||||||
return redirect(book_list.local_path)
|
return redirect(book_list.local_path)
|
||||||
|
|
||||||
|
|
||||||
|
@ -182,13 +167,6 @@ class Curate(View):
|
||||||
if approved:
|
if approved:
|
||||||
suggestion.approved = True
|
suggestion.approved = True
|
||||||
suggestion.save()
|
suggestion.save()
|
||||||
# let the world know
|
|
||||||
broadcast(
|
|
||||||
request.user,
|
|
||||||
suggestion.to_add_activity(request.user),
|
|
||||||
privacy=book_list.privacy,
|
|
||||||
software='bookwyrm'
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
suggestion.delete()
|
suggestion.delete()
|
||||||
return redirect('list-curate', book_list.id)
|
return redirect('list-curate', book_list.id)
|
||||||
|
@ -205,17 +183,10 @@ def add_book(request, list_id):
|
||||||
# do you have permission to add to the list?
|
# do you have permission to add to the list?
|
||||||
if request.user == book_list.user or book_list.curation == 'open':
|
if request.user == book_list.user or book_list.curation == 'open':
|
||||||
# go ahead and add it
|
# go ahead and add it
|
||||||
item = models.ListItem.objects.create(
|
models.ListItem.objects.create(
|
||||||
book=book,
|
book=book,
|
||||||
book_list=book_list,
|
book_list=book_list,
|
||||||
added_by=request.user,
|
user=request.user,
|
||||||
)
|
|
||||||
# let the world know
|
|
||||||
broadcast(
|
|
||||||
request.user,
|
|
||||||
item.to_add_activity(request.user),
|
|
||||||
privacy=book_list.privacy,
|
|
||||||
software='bookwyrm'
|
|
||||||
)
|
)
|
||||||
elif book_list.curation == 'curated':
|
elif book_list.curation == 'curated':
|
||||||
# make a pending entry
|
# make a pending entry
|
||||||
|
@ -223,7 +194,7 @@ def add_book(request, list_id):
|
||||||
approved=False,
|
approved=False,
|
||||||
book=book,
|
book=book,
|
||||||
book_list=book_list,
|
book_list=book_list,
|
||||||
added_by=request.user,
|
user=request.user,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# you can't add to this list, what were you THINKING
|
# you can't add to this list, what were you THINKING
|
||||||
|
@ -238,16 +209,8 @@ def remove_book(request, list_id):
|
||||||
book_list = get_object_or_404(models.List, id=list_id)
|
book_list = get_object_or_404(models.List, id=list_id)
|
||||||
item = get_object_or_404(models.ListItem, id=request.POST.get('item'))
|
item = get_object_or_404(models.ListItem, id=request.POST.get('item'))
|
||||||
|
|
||||||
if not book_list.user == request.user and not item.added_by == request.user:
|
if not book_list.user == request.user and not item.user == request.user:
|
||||||
return HttpResponseNotFound()
|
return HttpResponseNotFound()
|
||||||
|
|
||||||
activity = item.to_remove_activity(request.user)
|
|
||||||
item.delete()
|
item.delete()
|
||||||
# let the world know
|
|
||||||
broadcast(
|
|
||||||
request.user,
|
|
||||||
activity,
|
|
||||||
privacy=book_list.privacy,
|
|
||||||
software='bookwyrm'
|
|
||||||
)
|
|
||||||
return redirect('list', list_id)
|
return redirect('list', list_id)
|
||||||
|
|
|
@ -79,7 +79,7 @@ class PasswordReset(View):
|
||||||
return TemplateResponse(request, 'password_reset.html', data)
|
return TemplateResponse(request, 'password_reset.html', data)
|
||||||
|
|
||||||
user.set_password(new_password)
|
user.set_password(new_password)
|
||||||
user.save()
|
user.save(broadcast=False)
|
||||||
login(request, user)
|
login(request, user)
|
||||||
reset_code.delete()
|
reset_code.delete()
|
||||||
return redirect('/')
|
return redirect('/')
|
||||||
|
@ -106,6 +106,6 @@ class ChangePassword(View):
|
||||||
return redirect('preferences/password')
|
return redirect('preferences/password')
|
||||||
|
|
||||||
request.user.set_password(new_password)
|
request.user.set_password(new_password)
|
||||||
request.user.save()
|
request.user.save(broadcast=False)
|
||||||
login(request, request.user)
|
login(request, request.user)
|
||||||
return redirect(request.user.local_path)
|
return redirect(request.user.local_path)
|
||||||
|
|
|
@ -9,7 +9,6 @@ from django.utils import timezone
|
||||||
from django.views.decorators.http import require_POST
|
from django.views.decorators.http import require_POST
|
||||||
|
|
||||||
from bookwyrm import models
|
from bookwyrm import models
|
||||||
from bookwyrm.broadcast import broadcast
|
|
||||||
from .helpers import get_edition, handle_reading_status
|
from .helpers import get_edition, handle_reading_status
|
||||||
from .shelf import handle_unshelve
|
from .shelf import handle_unshelve
|
||||||
|
|
||||||
|
@ -44,9 +43,8 @@ def start_reading(request, book_id):
|
||||||
except models.Shelf.DoesNotExist:
|
except models.Shelf.DoesNotExist:
|
||||||
# this just means it isn't currently on the user's shelves
|
# this just means it isn't currently on the user's shelves
|
||||||
pass
|
pass
|
||||||
shelfbook = models.ShelfBook.objects.create(
|
models.ShelfBook.objects.create(
|
||||||
book=book, shelf=shelf, added_by=request.user)
|
book=book, shelf=shelf, user=request.user)
|
||||||
broadcast(request.user, shelfbook.to_add_activity(request.user))
|
|
||||||
|
|
||||||
# post about it (if you want)
|
# post about it (if you want)
|
||||||
if request.POST.get('post-status'):
|
if request.POST.get('post-status'):
|
||||||
|
@ -82,9 +80,8 @@ def finish_reading(request, book_id):
|
||||||
except models.Shelf.DoesNotExist:
|
except models.Shelf.DoesNotExist:
|
||||||
# this just means it isn't currently on the user's shelves
|
# this just means it isn't currently on the user's shelves
|
||||||
pass
|
pass
|
||||||
shelfbook = models.ShelfBook.objects.create(
|
models.ShelfBook.objects.create(
|
||||||
book=book, shelf=shelf, added_by=request.user)
|
book=book, shelf=shelf, user=request.user)
|
||||||
broadcast(request.user, shelfbook.to_add_activity(request.user))
|
|
||||||
|
|
||||||
# post about it (if you want)
|
# post about it (if you want)
|
||||||
if request.POST.get('post-status'):
|
if request.POST.get('post-status'):
|
||||||
|
|
|
@ -9,7 +9,6 @@ from django.views.decorators.http import require_POST
|
||||||
|
|
||||||
from bookwyrm import forms, models
|
from bookwyrm import forms, models
|
||||||
from bookwyrm.activitypub import ActivitypubResponse
|
from bookwyrm.activitypub import ActivitypubResponse
|
||||||
from bookwyrm.broadcast import broadcast
|
|
||||||
from .helpers import is_api_request, get_edition, get_user_from_username
|
from .helpers import is_api_request, get_edition, get_user_from_username
|
||||||
from .helpers import handle_reading_status
|
from .helpers import handle_reading_status
|
||||||
|
|
||||||
|
@ -50,7 +49,7 @@ class Shelf(View):
|
||||||
return ActivitypubResponse(shelf.to_activity(**request.GET))
|
return ActivitypubResponse(shelf.to_activity(**request.GET))
|
||||||
|
|
||||||
books = models.ShelfBook.objects.filter(
|
books = models.ShelfBook.objects.filter(
|
||||||
added_by=user, shelf=shelf
|
user=user, shelf=shelf
|
||||||
).order_by('-updated_date').all()
|
).order_by('-updated_date').all()
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
|
@ -136,14 +135,8 @@ def shelve(request):
|
||||||
except models.Shelf.DoesNotExist:
|
except models.Shelf.DoesNotExist:
|
||||||
# this just means it isn't currently on the user's shelves
|
# this just means it isn't currently on the user's shelves
|
||||||
pass
|
pass
|
||||||
shelfbook = models.ShelfBook.objects.create(
|
models.ShelfBook.objects.create(
|
||||||
book=book, shelf=desired_shelf, added_by=request.user)
|
book=book, shelf=desired_shelf, user=request.user)
|
||||||
broadcast(
|
|
||||||
request.user,
|
|
||||||
shelfbook.to_add_activity(request.user),
|
|
||||||
privacy=shelfbook.shelf.privacy,
|
|
||||||
software='bookwyrm'
|
|
||||||
)
|
|
||||||
|
|
||||||
# post about "want to read" shelves
|
# post about "want to read" shelves
|
||||||
if desired_shelf.identifier == 'to-read':
|
if desired_shelf.identifier == 'to-read':
|
||||||
|
@ -168,10 +161,8 @@ def unshelve(request):
|
||||||
return redirect(request.headers.get('Referer', '/'))
|
return redirect(request.headers.get('Referer', '/'))
|
||||||
|
|
||||||
|
|
||||||
|
#pylint: disable=unused-argument
|
||||||
def handle_unshelve(user, book, shelf):
|
def handle_unshelve(user, book, shelf):
|
||||||
''' unshelve a book '''
|
''' unshelve a book '''
|
||||||
row = models.ShelfBook.objects.get(book=book, shelf=shelf)
|
row = models.ShelfBook.objects.get(book=book, shelf=shelf)
|
||||||
activity = row.to_remove_activity(user)
|
|
||||||
row.delete()
|
row.delete()
|
||||||
|
|
||||||
broadcast(user, activity, privacy=shelf.privacy, software='bookwyrm')
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ from django.views import View
|
||||||
from markdown import markdown
|
from markdown import markdown
|
||||||
|
|
||||||
from bookwyrm import forms, models
|
from bookwyrm import forms, models
|
||||||
from bookwyrm.broadcast import broadcast
|
|
||||||
from bookwyrm.sanitize_html import InputHtmlParser
|
from bookwyrm.sanitize_html import InputHtmlParser
|
||||||
from bookwyrm.settings import DOMAIN
|
from bookwyrm.settings import DOMAIN
|
||||||
from bookwyrm.status import create_notification, delete_status
|
from bookwyrm.status import create_notification, delete_status
|
||||||
|
@ -35,7 +34,7 @@ class CreateStatus(View):
|
||||||
if not status.sensitive and status.content_warning:
|
if not status.sensitive and status.content_warning:
|
||||||
# the cw text field remains populated when you click "remove"
|
# the cw text field remains populated when you click "remove"
|
||||||
status.content_warning = None
|
status.content_warning = None
|
||||||
status.save()
|
status.save(broadcast=False)
|
||||||
|
|
||||||
# inspect the text for user tags
|
# inspect the text for user tags
|
||||||
content = status.content
|
content = status.content
|
||||||
|
@ -83,16 +82,7 @@ class CreateStatus(View):
|
||||||
if hasattr(status, 'quote'):
|
if hasattr(status, 'quote'):
|
||||||
status.quote = to_markdown(status.quote)
|
status.quote = to_markdown(status.quote)
|
||||||
|
|
||||||
status.save()
|
status.save(created=True)
|
||||||
|
|
||||||
broadcast(
|
|
||||||
request.user,
|
|
||||||
status.to_create_activity(request.user),
|
|
||||||
software='bookwyrm')
|
|
||||||
|
|
||||||
# re-format the activity for non-bookwyrm servers
|
|
||||||
remote_activity = status.to_create_activity(request.user, pure=True)
|
|
||||||
broadcast(request.user, remote_activity, software='other')
|
|
||||||
return redirect(request.headers.get('Referer', '/'))
|
return redirect(request.headers.get('Referer', '/'))
|
||||||
|
|
||||||
|
|
||||||
|
@ -108,7 +98,6 @@ class DeleteStatus(View):
|
||||||
|
|
||||||
# perform deletion
|
# perform deletion
|
||||||
delete_status(status)
|
delete_status(status)
|
||||||
broadcast(request.user, status.to_delete_activity(request.user))
|
|
||||||
return redirect(request.headers.get('Referer', '/'))
|
return redirect(request.headers.get('Referer', '/'))
|
||||||
|
|
||||||
def find_mentions(content):
|
def find_mentions(content):
|
||||||
|
|
|
@ -8,7 +8,6 @@ from django.views import View
|
||||||
|
|
||||||
from bookwyrm import models
|
from bookwyrm import models
|
||||||
from bookwyrm.activitypub import ActivitypubResponse
|
from bookwyrm.activitypub import ActivitypubResponse
|
||||||
from bookwyrm.broadcast import broadcast
|
|
||||||
from .helpers import is_api_request
|
from .helpers import is_api_request
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,17 +44,15 @@ class AddTag(View):
|
||||||
name = request.POST.get('name')
|
name = request.POST.get('name')
|
||||||
book_id = request.POST.get('book')
|
book_id = request.POST.get('book')
|
||||||
book = get_object_or_404(models.Edition, id=book_id)
|
book = get_object_or_404(models.Edition, id=book_id)
|
||||||
tag_obj, created = models.Tag.objects.get_or_create(
|
tag_obj, _ = models.Tag.objects.get_or_create(
|
||||||
name=name,
|
name=name,
|
||||||
)
|
)
|
||||||
user_tag, _ = models.UserTag.objects.get_or_create(
|
models.UserTag.objects.get_or_create(
|
||||||
user=request.user,
|
user=request.user,
|
||||||
book=book,
|
book=book,
|
||||||
tag=tag_obj,
|
tag=tag_obj,
|
||||||
)
|
)
|
||||||
|
|
||||||
if created:
|
|
||||||
broadcast(request.user, user_tag.to_add_activity(request.user))
|
|
||||||
return redirect('/book/%s' % book_id)
|
return redirect('/book/%s' % book_id)
|
||||||
|
|
||||||
|
|
||||||
|
@ -71,8 +68,6 @@ class RemoveTag(View):
|
||||||
|
|
||||||
user_tag = get_object_or_404(
|
user_tag = get_object_or_404(
|
||||||
models.UserTag, tag=tag_obj, book=book, user=request.user)
|
models.UserTag, tag=tag_obj, book=book, user=request.user)
|
||||||
tag_activity = user_tag.to_remove_activity(request.user)
|
|
||||||
user_tag.delete()
|
user_tag.delete()
|
||||||
|
|
||||||
broadcast(request.user, tag_activity)
|
|
||||||
return redirect('/book/%s' % book_id)
|
return redirect('/book/%s' % book_id)
|
||||||
|
|
|
@ -15,7 +15,6 @@ from django.views import View
|
||||||
|
|
||||||
from bookwyrm import forms, models
|
from bookwyrm import forms, models
|
||||||
from bookwyrm.activitypub import ActivitypubResponse
|
from bookwyrm.activitypub import ActivitypubResponse
|
||||||
from bookwyrm.broadcast import broadcast
|
|
||||||
from bookwyrm.settings import PAGE_LENGTH
|
from bookwyrm.settings import PAGE_LENGTH
|
||||||
from .helpers import get_activity_feed, get_user_from_username, is_api_request
|
from .helpers import get_activity_feed, get_user_from_username, is_api_request
|
||||||
from .helpers import is_blocked, object_visible_to_user
|
from .helpers import is_blocked, object_visible_to_user
|
||||||
|
@ -176,7 +175,6 @@ class EditUser(View):
|
||||||
user.avatar.save(filename, image)
|
user.avatar.save(filename, image)
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
broadcast(user, user.to_update_activity(user))
|
|
||||||
return redirect(user.local_path)
|
return redirect(user.local_path)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue