mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-12-04 23:36:32 +00:00
Merge branch 'main' into production
This commit is contained in:
commit
df4def6cef
13 changed files with 79 additions and 67 deletions
|
@ -6,6 +6,7 @@ import operator
|
||||||
import logging
|
import logging
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
import requests
|
import requests
|
||||||
|
from requests.exceptions import HTTPError, SSLError
|
||||||
|
|
||||||
from Crypto.PublicKey import RSA
|
from Crypto.PublicKey import RSA
|
||||||
from Crypto.Signature import pkcs1_15
|
from Crypto.Signature import pkcs1_15
|
||||||
|
@ -440,7 +441,7 @@ def broadcast_task(sender_id, activity, recipients):
|
||||||
for recipient in recipients:
|
for recipient in recipients:
|
||||||
try:
|
try:
|
||||||
sign_and_send(sender, activity, recipient)
|
sign_and_send(sender, activity, recipient)
|
||||||
except requests.exceptions.HTTPError as e:
|
except (HTTPError, SSLError) as e:
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow">
|
||||||
<a href="{{ book.local_path }}">{% include 'snippets/book_cover.html' with book=book size="large" %}</a>
|
<a href="{{ book.local_path }}">{% include 'snippets/book_cover.html' with book=book size="large" %}</a>
|
||||||
{% include 'snippets/stars.html' with rating=ratings|dict_key:book.id %}
|
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<h3 class="title is-5"><a href="/book/{{ book.id }}">{{ book.title }}</a></h3>
|
<h3 class="title is-5"><a href="/book/{{ book.id }}">{{ book.title }}</a></h3>
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
{% load bookwyrm_tags %}
|
{% load bookwyrm_tags %}
|
||||||
{% if book %}
|
{% if book %}
|
||||||
<a href="{{ book.local_path }}">{% include 'snippets/book_cover.html' with book=book %}</a>
|
<a href="{{ book.local_path }}">{% include 'snippets/book_cover.html' with book=book %}</a>
|
||||||
{% if ratings %}
|
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
|
||||||
{% include 'snippets/stars.html' with rating=ratings|dict_key:book.id %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<h3 class="title is-6"><a href="/book/{{ book.id }}">{{ book.title }}</a></h3>
|
<h3 class="title is-6"><a href="/book/{{ book.id }}">{{ book.title }}</a></h3>
|
||||||
{% if book.authors %}
|
{% if book.authors %}
|
||||||
|
|
|
@ -22,11 +22,8 @@ def dict_key(d, k):
|
||||||
@register.filter(name='rating')
|
@register.filter(name='rating')
|
||||||
def get_rating(book, user):
|
def get_rating(book, user):
|
||||||
''' get the overall rating of a book '''
|
''' get the overall rating of a book '''
|
||||||
queryset = views.helpers.get_activity_feed(
|
queryset = views.helpers.privacy_filter(
|
||||||
user,
|
user, models.Review.objects.filter(book=book))
|
||||||
['public', 'followers', 'unlisted', 'direct'],
|
|
||||||
queryset=models.Review.objects.filter(book=book),
|
|
||||||
)
|
|
||||||
return queryset.aggregate(Avg('rating'))['rating__avg']
|
return queryset.aggregate(Avg('rating'))['rating__avg']
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,7 @@ class ViewsHelpers(TestCase):
|
||||||
|
|
||||||
statuses = views.helpers.get_activity_feed(
|
statuses = views.helpers.get_activity_feed(
|
||||||
self.local_user,
|
self.local_user,
|
||||||
['public', 'unlisted', 'followers'],
|
privacy=['public', 'unlisted', 'followers'],
|
||||||
following_only=True,
|
following_only=True,
|
||||||
queryset=models.Comment.objects
|
queryset=models.Comment.objects
|
||||||
)
|
)
|
||||||
|
@ -115,20 +115,21 @@ class ViewsHelpers(TestCase):
|
||||||
|
|
||||||
statuses = views.helpers.get_activity_feed(
|
statuses = views.helpers.get_activity_feed(
|
||||||
self.local_user,
|
self.local_user,
|
||||||
['public', 'followers'],
|
privacy=['public', 'followers'],
|
||||||
local_only=True
|
local_only=True
|
||||||
)
|
)
|
||||||
self.assertEqual(len(statuses), 2)
|
self.assertEqual(len(statuses), 2)
|
||||||
self.assertEqual(statuses[1], public_status)
|
self.assertEqual(statuses[1], public_status)
|
||||||
self.assertEqual(statuses[0], rat_public)
|
self.assertEqual(statuses[0], rat_public)
|
||||||
|
|
||||||
statuses = views.helpers.get_activity_feed(self.local_user, 'direct')
|
statuses = views.helpers.get_activity_feed(
|
||||||
|
self.local_user, privacy=['direct'])
|
||||||
self.assertEqual(len(statuses), 1)
|
self.assertEqual(len(statuses), 1)
|
||||||
self.assertEqual(statuses[0], direct_status)
|
self.assertEqual(statuses[0], direct_status)
|
||||||
|
|
||||||
statuses = views.helpers.get_activity_feed(
|
statuses = views.helpers.get_activity_feed(
|
||||||
self.local_user,
|
self.local_user,
|
||||||
['public', 'followers'],
|
privacy=['public', 'followers'],
|
||||||
)
|
)
|
||||||
self.assertEqual(len(statuses), 3)
|
self.assertEqual(len(statuses), 3)
|
||||||
self.assertEqual(statuses[2], public_status)
|
self.assertEqual(statuses[2], public_status)
|
||||||
|
@ -137,7 +138,7 @@ class ViewsHelpers(TestCase):
|
||||||
|
|
||||||
statuses = views.helpers.get_activity_feed(
|
statuses = views.helpers.get_activity_feed(
|
||||||
self.local_user,
|
self.local_user,
|
||||||
['public', 'unlisted', 'followers'],
|
privacy=['public', 'unlisted', 'followers'],
|
||||||
following_only=True
|
following_only=True
|
||||||
)
|
)
|
||||||
self.assertEqual(len(statuses), 2)
|
self.assertEqual(len(statuses), 2)
|
||||||
|
@ -147,7 +148,7 @@ class ViewsHelpers(TestCase):
|
||||||
rat.followers.add(self.local_user)
|
rat.followers.add(self.local_user)
|
||||||
statuses = views.helpers.get_activity_feed(
|
statuses = views.helpers.get_activity_feed(
|
||||||
self.local_user,
|
self.local_user,
|
||||||
['public', 'unlisted', 'followers'],
|
privacy=['public', 'unlisted', 'followers'],
|
||||||
following_only=True
|
following_only=True
|
||||||
)
|
)
|
||||||
self.assertEqual(len(statuses), 5)
|
self.assertEqual(len(statuses), 5)
|
||||||
|
@ -170,18 +171,18 @@ class ViewsHelpers(TestCase):
|
||||||
content='blah blah', user=rat)
|
content='blah blah', user=rat)
|
||||||
|
|
||||||
statuses = views.helpers.get_activity_feed(
|
statuses = views.helpers.get_activity_feed(
|
||||||
self.local_user, ['public'])
|
self.local_user, privacy=['public'])
|
||||||
self.assertEqual(len(statuses), 2)
|
self.assertEqual(len(statuses), 2)
|
||||||
|
|
||||||
# block relationship
|
# block relationship
|
||||||
rat.blocks.add(self.local_user)
|
rat.blocks.add(self.local_user)
|
||||||
statuses = views.helpers.get_activity_feed(
|
statuses = views.helpers.get_activity_feed(
|
||||||
self.local_user, ['public'])
|
self.local_user, privacy=['public'])
|
||||||
self.assertEqual(len(statuses), 1)
|
self.assertEqual(len(statuses), 1)
|
||||||
self.assertEqual(statuses[0], public_status)
|
self.assertEqual(statuses[0], public_status)
|
||||||
|
|
||||||
statuses = views.helpers.get_activity_feed(
|
statuses = views.helpers.get_activity_feed(
|
||||||
rat, ['public'])
|
rat, privacy=['public'])
|
||||||
self.assertEqual(len(statuses), 1)
|
self.assertEqual(len(statuses), 1)
|
||||||
self.assertEqual(statuses[0], rat_public)
|
self.assertEqual(statuses[0], rat_public)
|
||||||
|
|
||||||
|
|
|
@ -45,15 +45,9 @@ class Book(View):
|
||||||
if not work:
|
if not work:
|
||||||
return HttpResponseNotFound()
|
return HttpResponseNotFound()
|
||||||
|
|
||||||
reviews = models.Review.objects.filter(
|
|
||||||
book__in=work.editions.all(),
|
|
||||||
)
|
|
||||||
# all reviews for the book
|
# all reviews for the book
|
||||||
reviews = get_activity_feed(
|
reviews = models.Review.objects.filter(book__in=work.editions.all())
|
||||||
request.user,
|
reviews = get_activity_feed(request.user, queryset=reviews)
|
||||||
['public', 'unlisted', 'followers', 'direct'],
|
|
||||||
queryset=reviews
|
|
||||||
)
|
|
||||||
|
|
||||||
# the reviews to show
|
# the reviews to show
|
||||||
paginated = Paginator(reviews.exclude(
|
paginated = Paginator(reviews.exclude(
|
||||||
|
@ -96,9 +90,8 @@ class Book(View):
|
||||||
'rating': reviews.aggregate(Avg('rating'))['rating__avg'],
|
'rating': reviews.aggregate(Avg('rating'))['rating__avg'],
|
||||||
'tags': models.UserTag.objects.filter(book=book),
|
'tags': models.UserTag.objects.filter(book=book),
|
||||||
'lists': privacy_filter(
|
'lists': privacy_filter(
|
||||||
request.user,
|
request.user, book.list_set.all()
|
||||||
book.list_set.all(),
|
),
|
||||||
['public', 'unlisted', 'followers']),
|
|
||||||
'user_tags': user_tags,
|
'user_tags': user_tags,
|
||||||
'user_shelves': user_shelves,
|
'user_shelves': user_shelves,
|
||||||
'other_edition_shelves': other_edition_shelves,
|
'other_edition_shelves': other_edition_shelves,
|
||||||
|
|
|
@ -11,8 +11,7 @@ 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.settings import PAGE_LENGTH
|
from bookwyrm.settings import PAGE_LENGTH
|
||||||
from .helpers import get_activity_feed
|
from .helpers import get_activity_feed, get_user_from_username
|
||||||
from .helpers import get_user_from_username
|
|
||||||
from .helpers import is_api_request, is_bookwyrm_request, object_visible_to_user
|
from .helpers import is_api_request, is_bookwyrm_request, object_visible_to_user
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,14 +28,13 @@ class Feed(View):
|
||||||
|
|
||||||
if tab == 'home':
|
if tab == 'home':
|
||||||
activities = get_activity_feed(
|
activities = get_activity_feed(
|
||||||
request.user, ['public', 'unlisted', 'followers'],
|
request.user, following_only=True)
|
||||||
following_only=True)
|
|
||||||
elif tab == 'local':
|
elif tab == 'local':
|
||||||
activities = get_activity_feed(
|
activities = get_activity_feed(
|
||||||
request.user, ['public', 'followers'], local_only=True)
|
request.user, privacy=['public', 'followers'], local_only=True)
|
||||||
else:
|
else:
|
||||||
activities = get_activity_feed(
|
activities = get_activity_feed(
|
||||||
request.user, ['public', 'followers'])
|
request.user, privacy=['public', 'followers'])
|
||||||
paginated = Paginator(activities, PAGE_LENGTH)
|
paginated = Paginator(activities, PAGE_LENGTH)
|
||||||
|
|
||||||
data = {**feed_page_data(request.user), **{
|
data = {**feed_page_data(request.user), **{
|
||||||
|
@ -72,7 +70,7 @@ class DirectMessage(View):
|
||||||
queryset = queryset.filter(Q(user=user) | Q(mention_users=user))
|
queryset = queryset.filter(Q(user=user) | Q(mention_users=user))
|
||||||
|
|
||||||
activities = get_activity_feed(
|
activities = get_activity_feed(
|
||||||
request.user, 'direct', queryset=queryset)
|
request.user, privacy=['direct'], queryset=queryset)
|
||||||
|
|
||||||
paginated = Paginator(activities, PAGE_LENGTH)
|
paginated = Paginator(activities, PAGE_LENGTH)
|
||||||
activity_page = paginated.page(page)
|
activity_page = paginated.page(page)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
''' helper functions used in various views '''
|
''' helper functions used in various views '''
|
||||||
import re
|
import re
|
||||||
from requests import HTTPError
|
from requests import HTTPError
|
||||||
|
from django.core.exceptions import FieldError
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from bookwyrm import activitypub, models
|
from bookwyrm import activitypub, models
|
||||||
|
@ -59,8 +60,11 @@ def object_visible_to_user(viewer, obj):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def privacy_filter(viewer, queryset, privacy_levels, following_only=False):
|
def privacy_filter(viewer, queryset, privacy_levels=None, following_only=False):
|
||||||
''' filter objects that have "user" and "privacy" fields '''
|
''' filter objects that have "user" and "privacy" fields '''
|
||||||
|
privacy_levels = privacy_levels or \
|
||||||
|
['public', 'unlisted', 'followers', 'direct']
|
||||||
|
|
||||||
# exclude blocks from both directions
|
# exclude blocks from both directions
|
||||||
if not viewer.is_anonymous:
|
if not viewer.is_anonymous:
|
||||||
blocked = models.User.objects.filter(id__in=viewer.blocks.all()).all()
|
blocked = models.User.objects.filter(id__in=viewer.blocks.all()).all()
|
||||||
|
@ -95,30 +99,56 @@ def privacy_filter(viewer, queryset, privacy_levels, following_only=False):
|
||||||
|
|
||||||
# exclude direct messages not intended for the user
|
# exclude direct messages not intended for the user
|
||||||
if 'direct' in privacy_levels:
|
if 'direct' in privacy_levels:
|
||||||
queryset = queryset.exclude(
|
try:
|
||||||
~Q(
|
queryset = queryset.exclude(
|
||||||
Q(user=viewer) | Q(mention_users=viewer)
|
~Q(
|
||||||
), privacy='direct'
|
Q(user=viewer) | Q(mention_users=viewer)
|
||||||
)
|
), privacy='direct'
|
||||||
|
)
|
||||||
|
except FieldError:
|
||||||
|
queryset = queryset.exclude(
|
||||||
|
~Q(user=viewer), privacy='direct'
|
||||||
|
)
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
def get_activity_feed(
|
def get_activity_feed(
|
||||||
user, privacy, local_only=False, following_only=False,
|
user, privacy=None, local_only=False, following_only=False,
|
||||||
queryset=models.Status.objects):
|
queryset=None):
|
||||||
''' get a filtered queryset of statuses '''
|
''' get a filtered queryset of statuses '''
|
||||||
# if we're looking at Status, we need this. We don't if it's Comment
|
if queryset is None:
|
||||||
if hasattr(queryset, 'select_subclasses'):
|
queryset = models.Status.objects.select_subclasses()
|
||||||
queryset = queryset.select_subclasses()
|
|
||||||
|
|
||||||
# exclude deleted
|
# exclude deleted
|
||||||
queryset = queryset.exclude(deleted=True).order_by('-published_date')
|
queryset = queryset.exclude(deleted=True).order_by('-published_date')
|
||||||
|
|
||||||
# apply privacy filters
|
# apply privacy filters
|
||||||
privacy = privacy if isinstance(privacy, list) else [privacy]
|
|
||||||
queryset = privacy_filter(
|
queryset = privacy_filter(
|
||||||
user, queryset, privacy, following_only=following_only)
|
user, queryset, privacy, following_only=following_only)
|
||||||
|
|
||||||
|
# only show dms if we only want dms
|
||||||
|
if privacy == ['direct']:
|
||||||
|
# dms are direct statuses not related to books
|
||||||
|
queryset = queryset.filter(
|
||||||
|
review__isnull=True,
|
||||||
|
comment__isnull=True,
|
||||||
|
quotation__isnull=True,
|
||||||
|
generatednote__isnull=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
queryset = queryset.exclude(
|
||||||
|
review__isnull=True,
|
||||||
|
comment__isnull=True,
|
||||||
|
quotation__isnull=True,
|
||||||
|
generatednote__isnull=True,
|
||||||
|
privacy='direct'
|
||||||
|
)
|
||||||
|
except FieldError:
|
||||||
|
# if we're looking at a subtype of Status (like Review)
|
||||||
|
pass
|
||||||
|
|
||||||
# filter for only local status
|
# filter for only local status
|
||||||
if local_only:
|
if local_only:
|
||||||
queryset = queryset.filter(user__local=True)
|
queryset = queryset.filter(user__local=True)
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
''' non-interactive pages '''
|
''' non-interactive pages '''
|
||||||
from django.db.models import Avg, Max
|
from django.db.models import Max
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.views import View
|
from django.views import View
|
||||||
|
|
||||||
from bookwyrm import forms, models
|
from bookwyrm import forms, models
|
||||||
from .feed import Feed
|
from .feed import Feed
|
||||||
from .helpers import get_activity_feed
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable= no-self-use
|
# pylint: disable= no-self-use
|
||||||
|
@ -34,6 +33,7 @@ class Discover(View):
|
||||||
''' tiled book activity page '''
|
''' tiled book activity page '''
|
||||||
books = models.Edition.objects.filter(
|
books = models.Edition.objects.filter(
|
||||||
review__published_date__isnull=False,
|
review__published_date__isnull=False,
|
||||||
|
review__deleted=False,
|
||||||
review__user__local=True,
|
review__user__local=True,
|
||||||
review__privacy__in=['public', 'unlisted'],
|
review__privacy__in=['public', 'unlisted'],
|
||||||
).exclude(
|
).exclude(
|
||||||
|
@ -42,18 +42,9 @@ class Discover(View):
|
||||||
Max('review__published_date')
|
Max('review__published_date')
|
||||||
).order_by('-review__published_date__max')[:6]
|
).order_by('-review__published_date__max')[:6]
|
||||||
|
|
||||||
ratings = {}
|
|
||||||
for book in books:
|
|
||||||
reviews = models.Review.objects.filter(
|
|
||||||
book__in=book.parent_work.editions.all()
|
|
||||||
)
|
|
||||||
reviews = get_activity_feed(
|
|
||||||
request.user, ['public', 'unlisted'], queryset=reviews)
|
|
||||||
ratings[book.id] = reviews.aggregate(Avg('rating'))['rating__avg']
|
|
||||||
data = {
|
data = {
|
||||||
'title': 'Discover',
|
'title': 'Discover',
|
||||||
'register_form': forms.RegisterForm(),
|
'register_form': forms.RegisterForm(),
|
||||||
'books': list(set(books)),
|
'books': list(set(books)),
|
||||||
'ratings': ratings
|
|
||||||
}
|
}
|
||||||
return TemplateResponse(request, 'discover/discover.html', data)
|
return TemplateResponse(request, 'discover/discover.html', data)
|
||||||
|
|
|
@ -35,7 +35,8 @@ class Lists(View):
|
||||||
).filter(
|
).filter(
|
||||||
item_count__gt=0
|
item_count__gt=0
|
||||||
).distinct().all()
|
).distinct().all()
|
||||||
lists = privacy_filter(request.user, lists, ['public', 'followers'])
|
lists = privacy_filter(
|
||||||
|
request.user, lists, privacy_levels=['public', 'followers'])
|
||||||
|
|
||||||
paginated = Paginator(lists, 12)
|
paginated = Paginator(lists, 12)
|
||||||
data = {
|
data = {
|
||||||
|
@ -67,8 +68,7 @@ class UserLists(View):
|
||||||
page = 1
|
page = 1
|
||||||
user = get_user_from_username(request.user, username)
|
user = get_user_from_username(request.user, username)
|
||||||
lists = models.List.objects.filter(user=user).all()
|
lists = models.List.objects.filter(user=user).all()
|
||||||
lists = privacy_filter(
|
lists = privacy_filter(request.user, lists)
|
||||||
request.user, lists, ['public', 'followers', 'unlisted'])
|
|
||||||
paginated = Paginator(lists, 12)
|
paginated = Paginator(lists, 12)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
|
|
|
@ -27,7 +27,10 @@ class RssFeed(Feed):
|
||||||
def items(self, obj):
|
def items(self, obj):
|
||||||
''' the user's activity feed '''
|
''' the user's activity feed '''
|
||||||
return get_activity_feed(
|
return get_activity_feed(
|
||||||
obj, ['public', 'unlisted'], queryset=obj.status_set)
|
obj,
|
||||||
|
privacy=['public', 'unlisted'],
|
||||||
|
queryset=obj.status_set.select_subclasses()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def item_link(self, item):
|
def item_link(self, item):
|
||||||
|
|
|
@ -44,7 +44,8 @@ class Search(View):
|
||||||
|
|
||||||
# any relevent lists?
|
# any relevent lists?
|
||||||
list_results = privacy_filter(
|
list_results = privacy_filter(
|
||||||
request.user, models.List.objects, ['public', 'followers']
|
request.user, models.List.objects,
|
||||||
|
privacy_levels=['public', 'followers']
|
||||||
).annotate(
|
).annotate(
|
||||||
similarity=Greatest(
|
similarity=Greatest(
|
||||||
TrigramSimilarity('name', query),
|
TrigramSimilarity('name', query),
|
||||||
|
|
|
@ -71,8 +71,7 @@ class User(View):
|
||||||
# user's posts
|
# user's posts
|
||||||
activities = get_activity_feed(
|
activities = get_activity_feed(
|
||||||
request.user,
|
request.user,
|
||||||
['public', 'unlisted', 'followers'],
|
queryset=user.status_set.select_subclasses(),
|
||||||
queryset=user.status_set
|
|
||||||
)
|
)
|
||||||
paginated = Paginator(activities, PAGE_LENGTH)
|
paginated = Paginator(activities, PAGE_LENGTH)
|
||||||
goal = models.AnnualGoal.objects.filter(
|
goal = models.AnnualGoal.objects.filter(
|
||||||
|
|
Loading…
Reference in a new issue