forked from mirrors/bookwyrm
Remove explicit broadcast calls
This commit is contained in:
parent
44996917c7
commit
42d80ce238
18 changed files with 25 additions and 685 deletions
|
@ -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
|
||||||
|
@ -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, added_by=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)
|
|
||||||
|
|
|
@ -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)
|
|
|
@ -1,14 +1,9 @@
|
||||||
''' base model with default fields '''
|
''' activitypub model functionality '''
|
||||||
from base64 import b64encode
|
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
import json
|
import json
|
||||||
import operator
|
import operator
|
||||||
from uuid import uuid4
|
|
||||||
import requests
|
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.apps import apps
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
@ -55,10 +50,6 @@ class ActivitypubMixin:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
|
||||||
''' broadcast suitable delete activities '''
|
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def find_existing_by_remote_id(cls, remote_id):
|
def find_existing_by_remote_id(cls, remote_id):
|
||||||
''' look up a remote id in the db '''
|
''' look up a remote id in the db '''
|
||||||
|
@ -158,66 +149,6 @@ class ActivitypubMixin:
|
||||||
return self.activity_serializer(**activity).serialize()
|
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):
|
def generate_activity(obj):
|
||||||
''' go through the fields on an object '''
|
''' go through the fields on an object '''
|
||||||
activity = {}
|
activity = {}
|
||||||
|
@ -297,13 +228,14 @@ def execute_after_save(sender, instance, created, *args, **kwargs):
|
||||||
''' broadcast when a model instance is created or updated '''
|
''' broadcast when a model instance is created or updated '''
|
||||||
# user content like statuses, lists, and shelves, have a "user" field
|
# user content like statuses, lists, and shelves, have a "user" field
|
||||||
user = instance.user if hasattr(instance, 'user') else None
|
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:
|
if user and not user.local:
|
||||||
# we don't want to broadcast when we save remote activities
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if created:
|
if created:
|
||||||
|
# book data and users don't need to broadcast on creation
|
||||||
if not user:
|
if not user:
|
||||||
# book data and users don't need to broadcast on creation
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# ordered collection items get "Add"ed
|
# ordered collection items get "Add"ed
|
||||||
|
@ -312,19 +244,6 @@ def execute_after_save(sender, instance, created, *args, **kwargs):
|
||||||
else:
|
else:
|
||||||
# everything else gets "Create"d
|
# everything else gets "Create"d
|
||||||
activity = instance.to_create_activity(user)
|
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:
|
if activity and user and user.local:
|
||||||
instance.broadcast(activity, user)
|
instance.broadcast(activity, user)
|
||||||
|
|
|
@ -3,12 +3,12 @@ from django.core.paginator import Paginator
|
||||||
|
|
||||||
from bookwyrm import activitypub
|
from bookwyrm import activitypub
|
||||||
from bookwyrm.settings import PAGE_LENGTH
|
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
|
''' 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
|
@property
|
||||||
def collection_remote_id(self):
|
def collection_remote_id(self):
|
||||||
''' this can be overriden if there's a special remote id, ie outbox '''
|
''' this can be overriden if there's a special remote id, ie outbox '''
|
||||||
|
|
|
@ -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 ActivitypubMixin, OrderedCollectionPageMixin
|
from .activitypub_mixin import ObjectMixin, OrderedCollectionPageMixin
|
||||||
from .base_model import BookWyrmModel
|
from .base_model import BookWyrmModel
|
||||||
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(
|
||||||
|
|
|
@ -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
|
||||||
|
@ -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,13 +211,10 @@ 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))
|
# TODO: this needs to be a delete and re-create
|
||||||
|
|
||||||
shelfbook.book = new_edition
|
shelfbook.book = new_edition
|
||||||
shelfbook.save()
|
shelfbook.save()
|
||||||
|
|
||||||
broadcast(request.user, shelfbook.to_add_activity(request.user))
|
|
||||||
|
|
||||||
readthroughs = models.ReadThrough.objects.filter(
|
readthroughs = models.ReadThrough.objects.filter(
|
||||||
book__parent_work=new_edition.parent_work,
|
book__parent_work=new_edition.parent_work,
|
||||||
user=request.user
|
user=request.user
|
||||||
|
|
|
@ -6,7 +6,6 @@ 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 +18,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 +35,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)
|
||||||
|
@ -77,16 +69,11 @@ def accept_follow_request(request):
|
||||||
|
|
||||||
def handle_accept(follow_request):
|
def handle_accept(follow_request):
|
||||||
''' send an acceptance message to a follow request '''
|
''' send an acceptance message to a follow request '''
|
||||||
user = follow_request.user_subject
|
|
||||||
to_follow = follow_request.user_object
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
relationship = models.UserFollows.from_request(follow_request)
|
relationship = models.UserFollows.from_request(follow_request)
|
||||||
follow_request.delete()
|
follow_request.delete()
|
||||||
relationship.save()
|
relationship.save()
|
||||||
|
|
||||||
activity = relationship.to_accept_activity()
|
|
||||||
broadcast(to_follow, activity, privacy='direct', direct_recipients=[user])
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@require_POST
|
@require_POST
|
||||||
|
@ -106,8 +93,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
|
||||||
|
@ -51,13 +50,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):
|
||||||
|
@ -138,13 +130,6 @@ class List(View):
|
||||||
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)
|
||||||
|
|
||||||
|
|
||||||
|
@ -178,13 +163,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)
|
||||||
|
@ -201,18 +179,11 @@ 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,
|
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':
|
elif book_list.curation == 'curated':
|
||||||
# make a pending entry
|
# make a pending entry
|
||||||
models.ListItem.objects.create(
|
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:
|
if not book_list.user == request.user and not item.added_by == 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)
|
||||||
|
|
|
@ -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, added_by=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, added_by=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
|
||||||
|
|
||||||
|
@ -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, 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
|
# 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
|
||||||
|
@ -84,15 +83,6 @@ class CreateStatus(View):
|
||||||
status.quote = to_markdown(status.quote)
|
status.quote = to_markdown(status.quote)
|
||||||
|
|
||||||
status.save()
|
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', '/'))
|
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