mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-12-14 12:16:31 +00:00
Merge branch 'main' into production
This commit is contained in:
commit
dd8f91a044
9 changed files with 53 additions and 17 deletions
|
@ -55,7 +55,6 @@ class Work(Book):
|
||||||
@dataclass(init=False)
|
@dataclass(init=False)
|
||||||
class Author(ActivityObject):
|
class Author(ActivityObject):
|
||||||
''' author of a book '''
|
''' author of a book '''
|
||||||
url: str
|
|
||||||
name: str
|
name: str
|
||||||
born: str
|
born: str
|
||||||
died: str
|
died: str
|
||||||
|
|
|
@ -61,7 +61,7 @@ def broadcast_task(sender_id, activity, recipients):
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
|
|
||||||
def sign_and_send(sender, activity, destination):
|
def sign_and_send(sender, data, destination):
|
||||||
''' crpyto whatever and http junk '''
|
''' crpyto whatever and http junk '''
|
||||||
now = http_date()
|
now = http_date()
|
||||||
|
|
||||||
|
@ -69,7 +69,6 @@ def sign_and_send(sender, activity, destination):
|
||||||
# this shouldn't happen. it would be bad if it happened.
|
# this shouldn't happen. it would be bad if it happened.
|
||||||
raise ValueError('No private key found for sender')
|
raise ValueError('No private key found for sender')
|
||||||
|
|
||||||
data = json.dumps(activity).encode('utf-8')
|
|
||||||
digest = make_digest(data)
|
digest = make_digest(data)
|
||||||
|
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
|
|
|
@ -62,6 +62,8 @@ def shared_inbox(request):
|
||||||
'Announce': handle_boost,
|
'Announce': handle_boost,
|
||||||
'Add': {
|
'Add': {
|
||||||
'Tag': handle_tag,
|
'Tag': handle_tag,
|
||||||
|
'Edition': handle_shelve,
|
||||||
|
'Work': handle_shelve,
|
||||||
},
|
},
|
||||||
'Undo': {
|
'Undo': {
|
||||||
'Follow': handle_unfollow,
|
'Follow': handle_unfollow,
|
||||||
|
@ -318,6 +320,22 @@ def handle_tag(activity):
|
||||||
status_builder.create_tag(user, book, activity['object']['name'])
|
status_builder.create_tag(user, book, activity['object']['name'])
|
||||||
|
|
||||||
|
|
||||||
|
@app.task
|
||||||
|
def handle_shelve(activity):
|
||||||
|
''' putting a book on a shelf '''
|
||||||
|
user = get_or_create_remote_user(activity['actor'])
|
||||||
|
book = books_manager.get_or_create_book(activity['object'])
|
||||||
|
try:
|
||||||
|
shelf = models.Shelf.objects.get(remote_id=activity['target'])
|
||||||
|
except models.Shelf.DoesNotExist:
|
||||||
|
return
|
||||||
|
if shelf.user != user:
|
||||||
|
# this doesn't add up.
|
||||||
|
return
|
||||||
|
shelf.books.add(book)
|
||||||
|
shelf.save()
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def handle_update_user(activity):
|
def handle_update_user(activity):
|
||||||
''' receive an updated user Person activity object '''
|
''' receive an updated user Person activity object '''
|
||||||
|
|
|
@ -68,7 +68,10 @@ class ActivitypubMixin:
|
||||||
if not hasattr(self, mapping.model_key) or not mapping.activity_key:
|
if not hasattr(self, mapping.model_key) or not mapping.activity_key:
|
||||||
continue
|
continue
|
||||||
value = getattr(self, mapping.model_key)
|
value = getattr(self, mapping.model_key)
|
||||||
if hasattr(value, 'remote_id'):
|
print(value)
|
||||||
|
if hasattr(value, 'local_id'):
|
||||||
|
value = value.local_id
|
||||||
|
elif hasattr(value, 'remote_id'):
|
||||||
value = value.remote_id
|
value = value.remote_id
|
||||||
if isinstance(value, datetime):
|
if isinstance(value, datetime):
|
||||||
value = value.isoformat()
|
value = value.isoformat()
|
||||||
|
|
|
@ -56,7 +56,7 @@ class Book(ActivitypubMixin, BookWyrmModel):
|
||||||
@property
|
@property
|
||||||
def ap_authors(self):
|
def ap_authors(self):
|
||||||
''' the activitypub serialization should be a list of author ids '''
|
''' the activitypub serialization should be a list of author ids '''
|
||||||
return [a.remote_id for a in self.authors.all()]
|
return [a.local_id for a in self.authors.all()]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ap_cover(self):
|
def ap_cover(self):
|
||||||
|
@ -73,7 +73,7 @@ class Book(ActivitypubMixin, BookWyrmModel):
|
||||||
return self.parent_work.local_id
|
return self.parent_work.local_id
|
||||||
|
|
||||||
activity_mappings = [
|
activity_mappings = [
|
||||||
ActivityMapping('id', 'remote_id'),
|
ActivityMapping('id', 'local_id'),
|
||||||
|
|
||||||
ActivityMapping('authors', 'ap_authors'),
|
ActivityMapping('authors', 'ap_authors'),
|
||||||
ActivityMapping('first_published_date', 'first_published_date'),
|
ActivityMapping('first_published_date', 'first_published_date'),
|
||||||
|
@ -258,7 +258,7 @@ class Author(ActivitypubMixin, BookWyrmModel):
|
||||||
an instance, so it needs a local url for federation. but it still needs
|
an instance, so it needs a local url for federation. but it still needs
|
||||||
the remote_id for easier deduplication and, if appropriate, to sync with
|
the remote_id for easier deduplication and, if appropriate, to sync with
|
||||||
the remote canonical copy (ditto here for author)'''
|
the remote canonical copy (ditto here for author)'''
|
||||||
return 'https://%s/book/%d' % (DOMAIN, self.id)
|
return 'https://%s/author/%d' % (DOMAIN, self.id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def display_name(self):
|
def display_name(self):
|
||||||
|
@ -271,8 +271,7 @@ class Author(ActivitypubMixin, BookWyrmModel):
|
||||||
return self.last_name or self.first_name
|
return self.last_name or self.first_name
|
||||||
|
|
||||||
activity_mappings = [
|
activity_mappings = [
|
||||||
ActivityMapping('id', 'remote_id'),
|
ActivityMapping('id', 'local_id'),
|
||||||
ActivityMapping('url', 'remote_id'),
|
|
||||||
ActivityMapping('name', 'display_name'),
|
ActivityMapping('name', 'display_name'),
|
||||||
ActivityMapping('born', 'born'),
|
ActivityMapping('born', 'born'),
|
||||||
ActivityMapping('died', 'died'),
|
ActivityMapping('died', 'died'),
|
||||||
|
|
|
@ -49,8 +49,8 @@ class ShelfBook(BookWyrmModel):
|
||||||
return activitypub.Add(
|
return activitypub.Add(
|
||||||
id='%s#add' % self.remote_id,
|
id='%s#add' % self.remote_id,
|
||||||
actor=user.remote_id,
|
actor=user.remote_id,
|
||||||
object=self.book.to_activity(),
|
object=self.book.local_id,
|
||||||
target=self.shelf.to_activity()
|
target=self.shelf.remote_id,
|
||||||
).serialize()
|
).serialize()
|
||||||
|
|
||||||
def to_remove_activity(self, user):
|
def to_remove_activity(self, user):
|
||||||
|
|
|
@ -44,7 +44,8 @@ def make_signature(sender, destination, date, digest):
|
||||||
|
|
||||||
def make_digest(data):
|
def make_digest(data):
|
||||||
''' creates a message digest for signing '''
|
''' creates a message digest for signing '''
|
||||||
return 'SHA-256=' + b64encode(hashlib.sha256(data).digest()).decode('utf-8')
|
return 'SHA-256=' + b64encode(hashlib.sha256(data.encode('utf-8'))\
|
||||||
|
.digest()).decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
def verify_digest(request):
|
def verify_digest(request):
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>BookWyrm</title>
|
<title>{% if title %}{{ title }} | {% endif %}BookWyrm</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link type="text/css" rel="stylesheet" href="/static/css/bulma.min.css">
|
<link type="text/css" rel="stylesheet" href="/static/css/bulma.min.css">
|
||||||
<link type="text/css" rel="stylesheet" href="/static/css/format.css">
|
<link type="text/css" rel="stylesheet" href="/static/css/format.css">
|
||||||
|
|
|
@ -36,12 +36,12 @@ def is_api_request(request):
|
||||||
|
|
||||||
def server_error_page(request):
|
def server_error_page(request):
|
||||||
''' 500 errors '''
|
''' 500 errors '''
|
||||||
return TemplateResponse(request, 'error.html')
|
return TemplateResponse(request, 'error.html', {'title': 'Oops!'})
|
||||||
|
|
||||||
|
|
||||||
def not_found_page(request, _):
|
def not_found_page(request, _):
|
||||||
''' 404s '''
|
''' 404s '''
|
||||||
return TemplateResponse(request, 'notfound.html')
|
return TemplateResponse(request, 'notfound.html', {'title': 'Not found'})
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -97,6 +97,7 @@ def home_tab(request, tab):
|
||||||
next_page = '/?page=%d#feed' % (page + 1)
|
next_page = '/?page=%d#feed' % (page + 1)
|
||||||
prev_page = '/?page=%d#feed' % (page - 1)
|
prev_page = '/?page=%d#feed' % (page - 1)
|
||||||
data = {
|
data = {
|
||||||
|
'title': 'Updates Feed',
|
||||||
'user': request.user,
|
'user': request.user,
|
||||||
'suggested_books': set(suggested_books),
|
'suggested_books': set(suggested_books),
|
||||||
'activities': activities,
|
'activities': activities,
|
||||||
|
@ -181,6 +182,7 @@ def search(request):
|
||||||
|
|
||||||
book_results = books_manager.search(query)
|
book_results = books_manager.search(query)
|
||||||
data = {
|
data = {
|
||||||
|
'title': 'Search Results',
|
||||||
'book_results': book_results,
|
'book_results': book_results,
|
||||||
'user_results': user_results,
|
'user_results': user_results,
|
||||||
'query': query,
|
'query': query,
|
||||||
|
@ -192,6 +194,7 @@ def search(request):
|
||||||
def import_page(request):
|
def import_page(request):
|
||||||
''' import history from goodreads '''
|
''' import history from goodreads '''
|
||||||
return TemplateResponse(request, 'import.html', {
|
return TemplateResponse(request, 'import.html', {
|
||||||
|
'title': 'Import Books',
|
||||||
'import_form': forms.ImportForm(),
|
'import_form': forms.ImportForm(),
|
||||||
'jobs': models.ImportJob.
|
'jobs': models.ImportJob.
|
||||||
objects.filter(user=request.user).order_by('-created_date'),
|
objects.filter(user=request.user).order_by('-created_date'),
|
||||||
|
@ -207,6 +210,7 @@ def import_status(request, job_id):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
task = app.AsyncResult(job.task_id)
|
task = app.AsyncResult(job.task_id)
|
||||||
return TemplateResponse(request, 'import_status.html', {
|
return TemplateResponse(request, 'import_status.html', {
|
||||||
|
'title': 'Import Status',
|
||||||
'job': job,
|
'job': job,
|
||||||
'items': job.items.order_by('index').all(),
|
'items': job.items.order_by('index').all(),
|
||||||
'task': task
|
'task': task
|
||||||
|
@ -219,6 +223,7 @@ def login_page(request):
|
||||||
return redirect('/')
|
return redirect('/')
|
||||||
# send user to the login page
|
# send user to the login page
|
||||||
data = {
|
data = {
|
||||||
|
'title': 'Login',
|
||||||
'site_settings': models.SiteSettings.get(),
|
'site_settings': models.SiteSettings.get(),
|
||||||
'login_form': forms.LoginForm(),
|
'login_form': forms.LoginForm(),
|
||||||
'register_form': forms.RegisterForm(),
|
'register_form': forms.RegisterForm(),
|
||||||
|
@ -229,6 +234,7 @@ def login_page(request):
|
||||||
def about_page(request):
|
def about_page(request):
|
||||||
''' more information about the instance '''
|
''' more information about the instance '''
|
||||||
data = {
|
data = {
|
||||||
|
'title': 'About',
|
||||||
'site_settings': models.SiteSettings.get(),
|
'site_settings': models.SiteSettings.get(),
|
||||||
}
|
}
|
||||||
return TemplateResponse(request, 'about.html', data)
|
return TemplateResponse(request, 'about.html', data)
|
||||||
|
@ -236,7 +242,7 @@ def about_page(request):
|
||||||
|
|
||||||
def password_reset_request(request):
|
def password_reset_request(request):
|
||||||
''' invite management page '''
|
''' invite management page '''
|
||||||
return TemplateResponse(request, 'password_reset_request.html')
|
return TemplateResponse(request, 'password_reset_request.html', {'title': 'Reset Password'})
|
||||||
|
|
||||||
|
|
||||||
def password_reset(request, code):
|
def password_reset(request, code):
|
||||||
|
@ -253,7 +259,7 @@ def password_reset(request, code):
|
||||||
return TemplateResponse(
|
return TemplateResponse(
|
||||||
request,
|
request,
|
||||||
'password_reset.html',
|
'password_reset.html',
|
||||||
{'code': reset_code.code}
|
{'title': 'Reset Password', 'code': reset_code.code}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -269,6 +275,7 @@ def invite_page(request, code):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
|
'title': 'Join',
|
||||||
'site_settings': models.SiteSettings.get(),
|
'site_settings': models.SiteSettings.get(),
|
||||||
'register_form': forms.RegisterForm(),
|
'register_form': forms.RegisterForm(),
|
||||||
'invite': invite,
|
'invite': invite,
|
||||||
|
@ -280,6 +287,7 @@ def invite_page(request, code):
|
||||||
def manage_invites(request):
|
def manage_invites(request):
|
||||||
''' invite management page '''
|
''' invite management page '''
|
||||||
data = {
|
data = {
|
||||||
|
'title': 'Invitations',
|
||||||
'invites': models.SiteInvite.objects.filter(user=request.user),
|
'invites': models.SiteInvite.objects.filter(user=request.user),
|
||||||
'form': forms.CreateInviteForm(),
|
'form': forms.CreateInviteForm(),
|
||||||
}
|
}
|
||||||
|
@ -293,6 +301,7 @@ def notifications_page(request):
|
||||||
.order_by('-created_date')
|
.order_by('-created_date')
|
||||||
unread = [n.id for n in notifications.filter(read=False)]
|
unread = [n.id for n in notifications.filter(read=False)]
|
||||||
data = {
|
data = {
|
||||||
|
'title': 'Notifications',
|
||||||
'notifications': notifications,
|
'notifications': notifications,
|
||||||
'unread': unread,
|
'unread': unread,
|
||||||
}
|
}
|
||||||
|
@ -313,6 +322,7 @@ def user_page(request, username, subpage=None, shelf=None):
|
||||||
# otherwise we're at a UI view
|
# otherwise we're at a UI view
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
|
'title': user.name,
|
||||||
'user': user,
|
'user': user,
|
||||||
'is_self': request.user.id == user.id,
|
'is_self': request.user.id == user.id,
|
||||||
}
|
}
|
||||||
|
@ -416,6 +426,7 @@ def status_page(request, username, status_id):
|
||||||
return JsonResponse(status.to_activity(), encoder=ActivityEncoder)
|
return JsonResponse(status.to_activity(), encoder=ActivityEncoder)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
|
'title': status.type,
|
||||||
'status': status,
|
'status': status,
|
||||||
}
|
}
|
||||||
return TemplateResponse(request, 'status.html', data)
|
return TemplateResponse(request, 'status.html', data)
|
||||||
|
@ -460,6 +471,7 @@ def edit_profile_page(request):
|
||||||
|
|
||||||
form = forms.EditUserForm(instance=request.user)
|
form = forms.EditUserForm(instance=request.user)
|
||||||
data = {
|
data = {
|
||||||
|
'title': 'Edit profile',
|
||||||
'form': form,
|
'form': form,
|
||||||
'user': user,
|
'user': user,
|
||||||
}
|
}
|
||||||
|
@ -506,6 +518,7 @@ def book_page(request, book_id):
|
||||||
).distinct().all()
|
).distinct().all()
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
|
'title': book.title,
|
||||||
'book': book,
|
'book': book,
|
||||||
'reviews': reviews.filter(content__isnull=False),
|
'reviews': reviews.filter(content__isnull=False),
|
||||||
'ratings': reviews.filter(content__isnull=True),
|
'ratings': reviews.filter(content__isnull=True),
|
||||||
|
@ -539,6 +552,7 @@ def edit_book_page(request, book_id):
|
||||||
if not book.description:
|
if not book.description:
|
||||||
book.description = book.parent_work.description
|
book.description = book.parent_work.description
|
||||||
data = {
|
data = {
|
||||||
|
'title': 'Edit Book',
|
||||||
'book': book,
|
'book': book,
|
||||||
'form': forms.EditionForm(instance=book)
|
'form': forms.EditionForm(instance=book)
|
||||||
}
|
}
|
||||||
|
@ -550,6 +564,7 @@ def editions_page(request, work_id):
|
||||||
work = models.Work.objects.get(id=work_id)
|
work = models.Work.objects.get(id=work_id)
|
||||||
editions = models.Edition.objects.filter(parent_work=work).all()
|
editions = models.Edition.objects.filter(parent_work=work).all()
|
||||||
data = {
|
data = {
|
||||||
|
'title': 'Editions of %s' % work.title,
|
||||||
'editions': editions,
|
'editions': editions,
|
||||||
'work': work,
|
'work': work,
|
||||||
}
|
}
|
||||||
|
@ -568,6 +583,7 @@ def author_page(request, author_id):
|
||||||
|
|
||||||
books = models.Work.objects.filter(authors=author)
|
books = models.Work.objects.filter(authors=author)
|
||||||
data = {
|
data = {
|
||||||
|
'title': author.name,
|
||||||
'author': author,
|
'author': author,
|
||||||
'books': [b.default_edition for b in books],
|
'books': [b.default_edition for b in books],
|
||||||
}
|
}
|
||||||
|
@ -586,6 +602,7 @@ def tag_page(request, tag_id):
|
||||||
|
|
||||||
books = models.Edition.objects.filter(tag__identifier=tag_id).distinct()
|
books = models.Edition.objects.filter(tag__identifier=tag_id).distinct()
|
||||||
data = {
|
data = {
|
||||||
|
'title': tag_obj.name,
|
||||||
'books': books,
|
'books': books,
|
||||||
'tag': tag_obj,
|
'tag': tag_obj,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue