Merge branch 'main' into production

This commit is contained in:
Mouse Reeve 2021-02-24 13:32:44 -08:00
commit df4def6cef
13 changed files with 79 additions and 67 deletions

View file

@ -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)

View file

@ -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>

View file

@ -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 %}

View file

@ -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']

View file

@ -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)

View file

@ -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,

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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 = {

View file

@ -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):

View file

@ -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),

View file

@ -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(