diff --git a/bookwyrm/goodreads_import.py b/bookwyrm/goodreads_import.py index b19994eda..606705c8a 100644 --- a/bookwyrm/goodreads_import.py +++ b/bookwyrm/goodreads_import.py @@ -3,7 +3,6 @@ import csv import logging from bookwyrm import models -from bookwyrm.broadcast import broadcast from bookwyrm.models import ImportJob, ImportItem from bookwyrm.status import create_notification from bookwyrm.tasks import app @@ -90,9 +89,8 @@ def handle_imported_book(user, item, include_reviews, privacy): identifier=item.shelf, user=user ) - shelf_book = models.ShelfBook.objects.create( + models.ShelfBook.objects.create( book=item.book, shelf=desired_shelf, added_by=user) - broadcast(user, shelf_book.to_add_activity(user), privacy=privacy) for read in item.reads: # 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, # but "now" is a bad guess published_date_guess = item.date_read or item.date_added - review = models.Review.objects.create( + models.Review.objects.create( user=user, book=item.book, name=review_title, @@ -123,6 +121,3 @@ def handle_imported_book(user, item, include_reviews, privacy): published_date=published_date_guess, 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) diff --git a/bookwyrm/models/activitypub_mixin.py b/bookwyrm/models/activitypub_mixin.py deleted file mode 100644 index 135cf89e6..000000000 --- a/bookwyrm/models/activitypub_mixin.py +++ /dev/null @@ -1,438 +0,0 @@ -''' base model with default fields ''' -from base64 import b64encode -from functools import reduce -import json -import operator -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 import models -from django.db.models import Q -from django.dispatch import receiver -from django.utils.http import http_date - - -from bookwyrm import activitypub -from bookwyrm.settings import PAGE_LENGTH, USER_AGENT -from bookwyrm.signatures import make_signature, make_digest -from bookwyrm.tasks import app -from .fields import ImageField, ManyToManyField - - -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) - - - def delete(self, *args, **kwargs): - ''' broadcast suitable delete activities ''' - - - @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 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 - if not user and self.__model__ == 'user': - # or maybe the thing itself is a user - user = self - # find anyone who's tagged in a status, for example - mentions = self.mention_users if hasattr(self, 'mention_users') 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': - user_model = apps.get_model('bookwyrm.User', require_ready=True) - # filter users first by whether they're using the desired software - # this lets us send book updates only to other bw servers - queryset = user_model.objects.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_inboxes__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() - - - 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) - - -class CollectionItemMixin(ActivitypubMixin): - ''' for items that are part of an (Ordered)Collection ''' - activity_serializer = activitypub.Add - object_field = collection_field = None - - 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() - - -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) - 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': USER_AGENT, - }, - ) - if not response.ok: - response.raise_for_status() - return response - - -@receiver(models.signals.post_save) -#pylint: disable=unused-argument -def execute_after_save(sender, instance, created, *args, **kwargs): - ''' broadcast when a model instance is created or updated ''' - # user content like statuses, lists, and shelves, have a "user" field - user = instance.user if hasattr(instance, 'user') else None - if user and not user.local: - # we don't want to broadcast when we save remote activities - return - - if created: - if not user: - # book data and users don't need to broadcast on creation - return - - # ordered collection items get "Add"ed - if hasattr(instance, 'to_add_activity'): - activity = instance.to_add_activity() - else: - # everything else gets "Create"d - activity = instance.to_create_activity(user) - else: - # now, handle updates - if not user: - # users don't have associated users, they ARE users - if sender.__class__ == 'User': - user = instance - # book data trakcs last editor - elif hasattr(instance, 'last_edited_by'): - user = instance.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 - activity = instance.to_update_activity(user) - - if activity and user and user.local: - instance.broadcast(activity, user) diff --git a/bookwyrm/models/activitypub_mixin/activitypub_mixin.py b/bookwyrm/models/activitypub_mixin/activitypub_mixin.py index 61f042ee4..eef5ce2b8 100644 --- a/bookwyrm/models/activitypub_mixin/activitypub_mixin.py +++ b/bookwyrm/models/activitypub_mixin/activitypub_mixin.py @@ -1,14 +1,9 @@ -''' base model with default fields ''' -from base64 import b64encode +''' activitypub model functionality ''' from functools import reduce import json import operator -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.db import models from django.db.models import Q @@ -55,10 +50,6 @@ class ActivitypubMixin: super().__init__(*args, **kwargs) - def delete(self, *args, **kwargs): - ''' broadcast suitable delete activities ''' - - @classmethod def find_existing_by_remote_id(cls, remote_id): ''' look up a remote id in the db ''' @@ -158,66 +149,6 @@ class ActivitypubMixin: 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() - - - - def generate_activity(obj): ''' go through the fields on an object ''' activity = {} @@ -297,13 +228,14 @@ def execute_after_save(sender, instance, created, *args, **kwargs): ''' broadcast when a model instance is created or updated ''' # user content like statuses, lists, and shelves, have a "user" field user = instance.user if hasattr(instance, 'user') else None + + # we don't want to broadcast when we save remote activities if user and not user.local: - # we don't want to broadcast when we save remote activities return if created: + # book data and users don't need to broadcast on creation if not user: - # book data and users don't need to broadcast on creation return # ordered collection items get "Add"ed @@ -312,19 +244,6 @@ def execute_after_save(sender, instance, created, *args, **kwargs): else: # everything else gets "Create"d activity = instance.to_create_activity(user) - else: - # now, handle updates - if not user: - # users don't have associated users, they ARE users - if sender.__class__ == 'User': - user = instance - # book data trakcs last editor - elif hasattr(instance, 'last_edited_by'): - user = instance.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 - activity = instance.to_update_activity(user) if activity and user and user.local: instance.broadcast(activity, user) diff --git a/bookwyrm/models/activitypub_mixin/ordered_collection.py b/bookwyrm/models/activitypub_mixin/ordered_collection.py index e755e5227..954e03645 100644 --- a/bookwyrm/models/activitypub_mixin/ordered_collection.py +++ b/bookwyrm/models/activitypub_mixin/ordered_collection.py @@ -3,12 +3,12 @@ from django.core.paginator import Paginator from bookwyrm import activitypub from bookwyrm.settings import PAGE_LENGTH -from . import ActivitypubMixin, generate_activity +from . import ActivitypubMixin, ObjectMixin, generate_activity -class OrderedCollectionPageMixin(ActivitypubMixin): +class OrderedCollectionPageMixin(ObjectMixin): ''' just the paginator utilities, so you don't HAVE to - override ActivitypubMixin's to_activity (ie, for outbox ''' + 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 ''' diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index 383668e08..ef1dd96dc 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -7,11 +7,11 @@ from model_utils.managers import InheritanceManager from bookwyrm import activitypub from bookwyrm.settings import DOMAIN -from .activitypub_mixin import ActivitypubMixin, OrderedCollectionPageMixin +from .activitypub_mixin import ObjectMixin, OrderedCollectionPageMixin from .base_model import BookWyrmModel from . import fields -class BookDataModel(ActivitypubMixin, BookWyrmModel): +class BookDataModel(ObjectMixin, BookWyrmModel): ''' fields shared between editable book data (books, works, authors) ''' origin_id = models.CharField(max_length=255, null=True, blank=True) openlibrary_key = fields.CharField( diff --git a/bookwyrm/views/author.py b/bookwyrm/views/author.py index ad9d18731..a1a37cc53 100644 --- a/bookwyrm/views/author.py +++ b/bookwyrm/views/author.py @@ -8,7 +8,6 @@ from django.views import View from bookwyrm import forms, models from bookwyrm.activitypub import ActivitypubResponse -from bookwyrm.broadcast import broadcast from .helpers import is_api_request @@ -62,5 +61,4 @@ class EditAuthor(View): return TemplateResponse(request, 'edit_author.html', data) author = form.save() - broadcast(request.user, author.to_update_activity(request.user)) return redirect('/author/%s' % author.id) diff --git a/bookwyrm/views/block.py b/bookwyrm/views/block.py index ebcced2a0..cb14aae38 100644 --- a/bookwyrm/views/block.py +++ b/bookwyrm/views/block.py @@ -8,7 +8,6 @@ from django.views import View from django.views.decorators.http import require_POST from bookwyrm import models -from bookwyrm.broadcast import broadcast # pylint: disable= no-self-use @method_decorator(login_required, name='dispatch') @@ -22,15 +21,8 @@ class Block(View): def post(self, request, user_id): ''' block a user ''' 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) - if not to_block.local: - broadcast( - request.user, - block.to_activity(), - privacy='direct', - direct_recipients=[to_block] - ) return redirect('/preferences/block') @@ -46,13 +38,5 @@ def unblock(request, user_id): ) except models.UserBlocks.DoesNotExist: return HttpResponseNotFound() - - if not to_unblock.local: - broadcast( - request.user, - block.to_undo_activity(request.user), - privacy='direct', - direct_recipients=[to_unblock] - ) block.delete() return redirect('/preferences/block') diff --git a/bookwyrm/views/books.py b/bookwyrm/views/books.py index 527045606..02c7f7b4b 100644 --- a/bookwyrm/views/books.py +++ b/bookwyrm/views/books.py @@ -12,7 +12,6 @@ from django.views.decorators.http import require_POST from bookwyrm import forms, models from bookwyrm.activitypub import ActivitypubResponse -from bookwyrm.broadcast import broadcast from bookwyrm.connectors import connector_manager from bookwyrm.settings import PAGE_LENGTH from .helpers import is_api_request, get_activity_feed, get_edition @@ -136,7 +135,6 @@ class EditBook(View): return TemplateResponse(request, 'edit_book.html', data) book = form.save() - broadcast(request.user, book.to_update_activity(request.user)) return redirect('/book/%s' % book.id) @@ -170,7 +168,6 @@ def upload_cover(request, book_id): book.cover = form.files['cover'] book.save() - broadcast(request.user, book.to_update_activity(request.user)) return redirect('/book/%s' % book.id) @@ -189,7 +186,6 @@ def add_description(request, book_id): book.description = description book.save() - broadcast(request.user, book.to_update_activity(request.user)) return redirect('/book/%s' % book.id) @@ -215,13 +211,10 @@ def switch_edition(request): shelf__user=request.user ) for shelfbook in shelfbooks.all(): - broadcast(request.user, shelfbook.to_remove_activity(request.user)) - + # TODO: this needs to be a delete and re-create shelfbook.book = new_edition shelfbook.save() - broadcast(request.user, shelfbook.to_add_activity(request.user)) - readthroughs = models.ReadThrough.objects.filter( book__parent_work=new_edition.parent_work, user=request.user diff --git a/bookwyrm/views/follow.py b/bookwyrm/views/follow.py index 08b6cca3f..992b0dc10 100644 --- a/bookwyrm/views/follow.py +++ b/bookwyrm/views/follow.py @@ -6,7 +6,6 @@ from django.shortcuts import redirect from django.views.decorators.http import require_POST from bookwyrm import models -from bookwyrm.broadcast import broadcast from .helpers import get_user_from_username @login_required @@ -19,13 +18,10 @@ def follow(request): except models.User.DoesNotExist: return HttpResponseBadRequest() - relationship, _ = models.UserFollowRequest.objects.get_or_create( + models.UserFollowRequest.objects.get_or_create( user_subject=request.user, user_object=to_follow, ) - activity = relationship.to_activity() - broadcast( - request.user, activity, privacy='direct', direct_recipients=[to_follow]) return redirect(to_follow.local_path) @@ -39,14 +35,10 @@ def unfollow(request): except models.User.DoesNotExist: return HttpResponseBadRequest() - relationship = models.UserFollows.objects.get( + models.UserFollows.objects.get( user_subject=request.user, 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) return redirect(to_unfollow.local_path) @@ -77,16 +69,11 @@ def accept_follow_request(request): 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 @require_POST @@ -106,8 +93,5 @@ def delete_follow_request(request): except models.UserFollowRequest.DoesNotExist: return HttpResponseBadRequest() - activity = follow_request.to_reject_activity() follow_request.delete() - broadcast( - request.user, activity, privacy='direct', direct_recipients=[requester]) return redirect('/user/%s' % request.user.localname) diff --git a/bookwyrm/views/goal.py b/bookwyrm/views/goal.py index 0496848ff..87404b100 100644 --- a/bookwyrm/views/goal.py +++ b/bookwyrm/views/goal.py @@ -7,7 +7,6 @@ from django.utils.decorators import method_decorator from django.views import View from bookwyrm import forms, models -from bookwyrm.broadcast import broadcast from bookwyrm.status import create_generated_note from .helpers import get_user_from_username, object_visible_to_user @@ -63,23 +62,10 @@ class Goal(View): if request.POST.get('post-status'): # create status, if appropraite - status = create_generated_note( + create_generated_note( request.user, 'set a goal to read %d books in %d' % (goal.goal, goal.year), 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', '/')) diff --git a/bookwyrm/views/helpers.py b/bookwyrm/views/helpers.py index 8742884f1..842b8d1c4 100644 --- a/bookwyrm/views/helpers.py +++ b/bookwyrm/views/helpers.py @@ -4,7 +4,6 @@ from requests import HTTPError from django.db.models import Q from bookwyrm import activitypub, models -from bookwyrm.broadcast import broadcast from bookwyrm.connectors import ConnectorException, get_data from bookwyrm.status import create_generated_note from bookwyrm.utils import regex @@ -199,7 +198,6 @@ def handle_reading_status(user, shelf, book, privacy): ) status.save() - broadcast(user, status.to_create_activity(user)) def is_blocked(viewer, user): ''' is this viewer blocked by the user? ''' diff --git a/bookwyrm/views/interaction.py b/bookwyrm/views/interaction.py index a6732c520..ebee4719d 100644 --- a/bookwyrm/views/interaction.py +++ b/bookwyrm/views/interaction.py @@ -7,7 +7,6 @@ from django.utils.decorators import method_decorator from django.views import View from bookwyrm import models -from bookwyrm.broadcast import broadcast from bookwyrm.status import create_notification @@ -19,7 +18,7 @@ class Favorite(View): ''' create a like ''' status = models.Status.objects.get(id=status_id) try: - favorite = models.Favorite.objects.create( + models.Favorite.objects.create( status=status, user=request.user ) @@ -27,10 +26,6 @@ class Favorite(View): # you already fav'ed that return HttpResponseBadRequest() - fav_activity = favorite.to_activity() - broadcast( - request.user, fav_activity, privacy='direct', - direct_recipients=[status.user]) if status.user.local: create_notification( status.user, @@ -56,9 +51,7 @@ class Unfavorite(View): # can't find that status, idk return HttpResponseNotFound() - fav_activity = favorite.to_undo_activity(request.user) favorite.delete() - broadcast(request.user, fav_activity, direct_recipients=[status.user]) # check for notification if status.user.local: @@ -86,15 +79,12 @@ class Boost(View): # you already boosted that. return redirect(request.headers.get('Referer', '/')) - boost = models.Boost.objects.create( + models.Boost.objects.create( boosted_status=status, privacy=status.privacy, user=request.user, ) - boost_activity = boost.to_activity() - broadcast(request.user, boost_activity) - if status.user.local: create_notification( status.user, @@ -114,10 +104,8 @@ class Unboost(View): boost = models.Boost.objects.filter( boosted_status=status, user=request.user ).first() - activity = boost.to_undo_activity(request.user) boost.delete() - broadcast(request.user, activity) # delete related notification if status.user.local: diff --git a/bookwyrm/views/list.py b/bookwyrm/views/list.py index 5128c2b04..17c0e2f37 100644 --- a/bookwyrm/views/list.py +++ b/bookwyrm/views/list.py @@ -11,7 +11,6 @@ from django.views.decorators.http import require_POST from bookwyrm import forms, models from bookwyrm.activitypub import ActivitypubResponse -from bookwyrm.broadcast import broadcast from bookwyrm.connectors import connector_manager from .helpers import is_api_request, object_visible_to_user, privacy_filter from .helpers import get_user_from_username @@ -51,13 +50,6 @@ class Lists(View): return redirect('lists') 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) class UserLists(View): @@ -138,13 +130,6 @@ class List(View): if not form.is_valid(): return redirect('list', book_list.id) 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) @@ -178,13 +163,6 @@ class Curate(View): if approved: suggestion.approved = True suggestion.save() - # let the world know - broadcast( - request.user, - suggestion.to_add_activity(request.user), - privacy=book_list.privacy, - software='bookwyrm' - ) else: suggestion.delete() return redirect('list-curate', book_list.id) @@ -201,18 +179,11 @@ def add_book(request, list_id): # do you have permission to add to the list? if request.user == book_list.user or book_list.curation == 'open': # go ahead and add it - item = models.ListItem.objects.create( + models.ListItem.objects.create( book=book, book_list=book_list, added_by=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': # make a pending entry models.ListItem.objects.create( @@ -237,13 +208,5 @@ def remove_book(request, list_id): if not book_list.user == request.user and not item.added_by == request.user: return HttpResponseNotFound() - activity = item.to_remove_activity(request.user) item.delete() - # let the world know - broadcast( - request.user, - activity, - privacy=book_list.privacy, - software='bookwyrm' - ) return redirect('list', list_id) diff --git a/bookwyrm/views/reading.py b/bookwyrm/views/reading.py index c2c725095..ba6c8a4a1 100644 --- a/bookwyrm/views/reading.py +++ b/bookwyrm/views/reading.py @@ -9,7 +9,6 @@ from django.utils import timezone from django.views.decorators.http import require_POST from bookwyrm import models -from bookwyrm.broadcast import broadcast from .helpers import get_edition, handle_reading_status from .shelf import handle_unshelve @@ -44,9 +43,8 @@ def start_reading(request, book_id): except models.Shelf.DoesNotExist: # this just means it isn't currently on the user's shelves pass - shelfbook = models.ShelfBook.objects.create( + models.ShelfBook.objects.create( book=book, shelf=shelf, added_by=request.user) - broadcast(request.user, shelfbook.to_add_activity(request.user)) # post about it (if you want) if request.POST.get('post-status'): @@ -82,9 +80,8 @@ def finish_reading(request, book_id): except models.Shelf.DoesNotExist: # this just means it isn't currently on the user's shelves pass - shelfbook = models.ShelfBook.objects.create( + models.ShelfBook.objects.create( book=book, shelf=shelf, added_by=request.user) - broadcast(request.user, shelfbook.to_add_activity(request.user)) # post about it (if you want) if request.POST.get('post-status'): diff --git a/bookwyrm/views/shelf.py b/bookwyrm/views/shelf.py index 508d5ea5b..dca53aced 100644 --- a/bookwyrm/views/shelf.py +++ b/bookwyrm/views/shelf.py @@ -9,7 +9,6 @@ from django.views.decorators.http import require_POST from bookwyrm import forms, models 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 handle_reading_status @@ -136,14 +135,8 @@ def shelve(request): except models.Shelf.DoesNotExist: # this just means it isn't currently on the user's shelves pass - shelfbook = models.ShelfBook.objects.create( + models.ShelfBook.objects.create( book=book, shelf=desired_shelf, added_by=request.user) - broadcast( - request.user, - shelfbook.to_add_activity(request.user), - privacy=shelfbook.shelf.privacy, - software='bookwyrm' - ) # post about "want to read" shelves if desired_shelf.identifier == 'to-read': @@ -168,10 +161,8 @@ def unshelve(request): return redirect(request.headers.get('Referer', '/')) +#pylint: disable=unused-argument def handle_unshelve(user, book, shelf): ''' unshelve a book ''' row = models.ShelfBook.objects.get(book=book, shelf=shelf) - activity = row.to_remove_activity(user) row.delete() - - broadcast(user, activity, privacy=shelf.privacy, software='bookwyrm') diff --git a/bookwyrm/views/status.py b/bookwyrm/views/status.py index 4d342bfbc..4b2b4519b 100644 --- a/bookwyrm/views/status.py +++ b/bookwyrm/views/status.py @@ -8,7 +8,6 @@ from django.views import View from markdown import markdown from bookwyrm import forms, models -from bookwyrm.broadcast import broadcast from bookwyrm.sanitize_html import InputHtmlParser from bookwyrm.settings import DOMAIN from bookwyrm.status import create_notification, delete_status @@ -84,15 +83,6 @@ class CreateStatus(View): status.quote = to_markdown(status.quote) status.save() - - 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', '/')) @@ -108,7 +98,6 @@ class DeleteStatus(View): # perform deletion delete_status(status) - broadcast(request.user, status.to_delete_activity(request.user)) return redirect(request.headers.get('Referer', '/')) def find_mentions(content): diff --git a/bookwyrm/views/tag.py b/bookwyrm/views/tag.py index e95ffe817..b50bc0eff 100644 --- a/bookwyrm/views/tag.py +++ b/bookwyrm/views/tag.py @@ -8,7 +8,6 @@ from django.views import View from bookwyrm import models from bookwyrm.activitypub import ActivitypubResponse -from bookwyrm.broadcast import broadcast from .helpers import is_api_request @@ -45,17 +44,15 @@ class AddTag(View): name = request.POST.get('name') book_id = request.POST.get('book') 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, ) - user_tag, _ = models.UserTag.objects.get_or_create( + models.UserTag.objects.get_or_create( user=request.user, book=book, tag=tag_obj, ) - if created: - broadcast(request.user, user_tag.to_add_activity(request.user)) return redirect('/book/%s' % book_id) @@ -71,8 +68,6 @@ class RemoveTag(View): user_tag = get_object_or_404( models.UserTag, tag=tag_obj, book=book, user=request.user) - tag_activity = user_tag.to_remove_activity(request.user) user_tag.delete() - broadcast(request.user, tag_activity) return redirect('/book/%s' % book_id) diff --git a/bookwyrm/views/user.py b/bookwyrm/views/user.py index a06face2b..4da0fdac1 100644 --- a/bookwyrm/views/user.py +++ b/bookwyrm/views/user.py @@ -15,7 +15,6 @@ from django.views import View from bookwyrm import forms, models from bookwyrm.activitypub import ActivitypubResponse -from bookwyrm.broadcast import broadcast from bookwyrm.settings import PAGE_LENGTH from .helpers import get_activity_feed, get_user_from_username, is_api_request from .helpers import is_blocked, object_visible_to_user @@ -176,7 +175,6 @@ class EditUser(View): user.avatar.save(filename, image) user.save() - broadcast(user, user.to_update_activity(user)) return redirect(user.local_path)