Merge branch 'main' into production

This commit is contained in:
Mouse Reeve 2021-02-12 10:11:16 -08:00
commit 355b2fad35
13 changed files with 120 additions and 18 deletions

View file

@ -16,7 +16,7 @@ from .response import ActivitypubResponse
from .book import Edition, Work, Author
from .verbs import Create, Delete, Undo, Update
from .verbs import Follow, Accept, Reject, Block
from .verbs import Add, AddBook, Remove
from .verbs import Add, AddBook, AddListItem, Remove
# this creates a list of all the Activity types that we can serialize,
# so when an Activity comes in from outside, we can check if it's known

View file

@ -65,6 +65,13 @@ class ActivityObject:
def to_model(self, model, instance=None, save=True):
''' convert from an activity to a model instance '''
if self.type != model.activity_serializer.type:
raise ActivitySerializerError(
'Wrong activity type "%s" for activity of type "%s"' % \
(model.activity_serializer.type,
self.type)
)
if not isinstance(self, model.activity_serializer):
raise ActivitySerializerError(
'Wrong activity type "%s" for model "%s" (expects "%s")' % \

View file

@ -70,17 +70,26 @@ class Reject(Verb):
@dataclass(init=False)
class Add(Verb):
'''Add activity '''
target: ActivityObject
target: str
object: ActivityObject
type: str = 'Add'
@dataclass(init=False)
class AddBook(Verb):
class AddBook(Add):
'''Add activity that's aware of the book obj '''
target: Edition
object: Edition
type: str = 'Add'
@dataclass(init=False)
class AddListItem(AddBook):
'''Add activity that's aware of the book obj '''
notes: str = None
order: int = 0
approved: bool = True
@dataclass(init=False)
class Remove(Verb):
'''Remove activity '''

View file

@ -239,7 +239,8 @@ def get_image(url):
'User-Agent': settings.USER_AGENT,
},
)
except (RequestError, SSLError):
except (RequestError, SSLError) as e:
logger.exception(e)
return None
if not resp.ok:
return None

View file

@ -142,7 +142,12 @@ class Connector(AbstractConnector):
work = book.parent_work
# we can mass download edition data from OL to avoid repeatedly querying
edition_options = self.load_edition_data(work.openlibrary_key)
try:
edition_options = self.load_edition_data(work.openlibrary_key)
except ConnectorException:
# who knows, man
return
for edition_data in edition_options.get('entries'):
# does this edition have ANY interesting data?
if ignore_edition(edition_data):

View file

@ -216,9 +216,9 @@ def handle_create_list(activity):
def handle_update_list(activity):
''' update a list '''
try:
book_list = models.List.objects.get(id=activity['object']['id'])
book_list = models.List.objects.get(remote_id=activity['object']['id'])
except models.List.DoesNotExist:
return
book_list = None
activitypub.BookList(
**activity['object']).to_model(models.List, instance=book_list)
@ -319,8 +319,19 @@ def handle_add(activity):
#this is janky as heck but I haven't thought of a better solution
try:
activitypub.AddBook(**activity).to_model(models.ShelfBook)
return
except activitypub.ActivitySerializerError:
activitypub.AddBook(**activity).to_model(models.Tag)
pass
try:
activitypub.AddListItem(**activity).to_model(models.ListItem)
return
except activitypub.ActivitySerializerError:
pass
try:
activitypub.AddBook(**activity).to_model(models.UserTag)
return
except activitypub.ActivitySerializerError:
pass
@app.task

View file

@ -10,7 +10,10 @@ def set_user(app_registry, schema_editor):
shelfbook = app_registry.get_model('bookwyrm', 'ShelfBook')
for item in shelfbook.objects.using(db_alias).filter(user__isnull=True):
item.user = item.shelf.user
item.save(broadcast=False)
try:
item.save(broadcast=False)
except TypeError:
item.save()
class Migration(migrations.Migration):

View file

@ -68,7 +68,7 @@ class ListItem(CollectionItemMixin, BookWyrmModel):
order = fields.IntegerField(blank=True, null=True)
endorsement = models.ManyToManyField('User', related_name='endorsers')
activity_serializer = activitypub.AddBook
activity_serializer = activitypub.AddListItem
object_field = 'book'
collection_field = 'book_list'

View file

@ -103,7 +103,7 @@
<div class="column">
<div class="block">
<h3 class="field is-grouped">{% include 'snippets/stars.html' with rating=rating %} ({{ review_count }} review{{ reviews|length|pluralize }})</h3>
<h3 class="field is-grouped">{% include 'snippets/stars.html' with rating=rating %} ({{ review_count }} review{{ review_count|pluralize }})</h3>
{% include 'snippets/trimmed_text.html' with full=book|book_description %}

View file

@ -8,7 +8,7 @@
<a href="{{ list.local_path }}">{{ list.name }}</a> <span class="subtitle">{% include 'snippets/privacy-icons.html' with item=list %}</span>
</h4>
</header>
<div class="card-image is-flex">
<div class="card-image is-flex is-clipped">
{% for book in list.listitem_set.all|slice:5 %}
<a href="{{ book.book.local_path }}">{% include 'snippets/book_cover.html' with book=book.book size="small" %}</a>
{% endfor %}

View file

@ -1,10 +1,11 @@
{% load humanize %}
<p>
{% if goal.progress_percent >= 100 %}
Success!
{% elif goal.progress_percent %}
{{ goal.progress_percent }}% complete!
{% endif %}
{% if goal.user == request.user %}You've{% else %}{{ goal.user.display_name }} has{% endif %} read {% if request.path != goal.local_path %}<a href="{{ goal.local_path }}">{% endif %}{{ goal.book_count }} of {{ goal.goal }} books{% if request.path != goal.local_path %}</a>{% endif %}.
{% if goal.user == request.user %}You've{% else %}{{ goal.user.display_name }} has{% endif %} read {% if request.path != goal.local_path %}<a href="{{ goal.local_path }}">{% endif %}{{ goal.book_count }} of {{ goal.goal | intcomma }} books{% if request.path != goal.local_path %}</a>{% endif %}.
</p>
<progress class="progress is-large" value="{{ goal.book_count }}" max="{{ goal.goal }}" aria-hidden="true">{{ goal.progress_percent }}%</progress>

View file

@ -308,7 +308,7 @@ class Incoming(TestCase):
"@context": "https://www.w3.org/ns/activitystreams"
}
}
incoming.handle_create_list(activity)
incoming.handle_update_list(activity)
book_list.refresh_from_db()
self.assertEqual(book_list.name, 'Test List')
self.assertEqual(book_list.curation, 'curated')
@ -626,7 +626,7 @@ class Incoming(TestCase):
activity = {
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/9e1f41ac-9ddd-4159-aede-9f43c6b9314f",
"id": "https://example.com/9e1f41ac-9ddd-4159",
"type": "Block",
"actor": "https://example.com/users/rat",
"object": "https://example.com/user/mouse"
@ -636,6 +636,29 @@ class Incoming(TestCase):
block = models.UserBlocks.objects.get()
self.assertEqual(block.user_subject, self.remote_user)
self.assertEqual(block.user_object, self.local_user)
self.assertEqual(
block.remote_id, 'https://example.com/9e1f41ac-9ddd-4159')
self.assertFalse(models.UserFollows.objects.exists())
self.assertFalse(models.UserFollowRequest.objects.exists())
def test_handle_unblock(self):
''' unblock a user '''
self.remote_user.blocks.add(self.local_user)
block = models.UserBlocks.objects.get()
block.remote_id = 'https://example.com/9e1f41ac-9ddd-4159'
block.save()
self.assertEqual(block.user_subject, self.remote_user)
self.assertEqual(block.user_object, self.local_user)
activity = {'type': 'Undo', 'object': {
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/9e1f41ac-9ddd-4159",
"type": "Block",
"actor": "https://example.com/users/rat",
"object": "https://example.com/user/mouse"
}}
incoming.handle_unblock(activity)
self.assertFalse(models.UserBlocks.objects.exists())

View file

@ -3,7 +3,9 @@ import pathlib
from unittest.mock import patch
from PIL import Image
from django.contrib.auth.models import AnonymousUser
from django.core.files.base import ContentFile
from django.core.files.uploadedfile import SimpleUploadedFile
from django.template.response import TemplateResponse
from django.test import TestCase
from django.test.client import RequestFactory
@ -24,6 +26,8 @@ class UserViews(TestCase):
'rat@local.com', 'rat@rat.rat', 'password',
local=True, localname='rat')
models.SiteSettings.objects.create()
self.anonymous_user = AnonymousUser
self.anonymous_user.is_authenticated = False
def test_user_page(self):
@ -38,6 +42,14 @@ class UserViews(TestCase):
result.render()
self.assertEqual(result.status_code, 200)
request.user = self.anonymous_user
with patch('bookwyrm.views.user.is_api_request') as is_api:
is_api.return_value = False
result = view(request, 'mouse')
self.assertIsInstance(result, TemplateResponse)
result.render()
self.assertEqual(result.status_code, 200)
with patch('bookwyrm.views.user.is_api_request') as is_api:
is_api.return_value = True
result = view(request, 'mouse')
@ -119,7 +131,7 @@ class UserViews(TestCase):
self.assertEqual(result.status_code, 404)
def test_edit_profile_page(self):
def test_edit_user_page(self):
''' there are so many views, this just makes sure it LOADS '''
view = views.EditUser.as_view()
request = self.factory.get('')
@ -135,12 +147,42 @@ class UserViews(TestCase):
view = views.EditUser.as_view()
form = forms.EditUserForm(instance=self.local_user)
form.data['name'] = 'New Name'
form.data['email'] = 'wow@email.com'
request = self.factory.post('', form.data)
request.user = self.local_user
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
self.assertIsNone(self.local_user.name)
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay') \
as delay_mock:
view(request)
self.assertEqual(delay_mock.call_count, 1)
self.assertEqual(self.local_user.name, 'New Name')
self.assertEqual(self.local_user.email, 'wow@email.com')
# idk how to mock the upload form, got tired of triyng to make it work
# def test_edit_user_avatar(self):
# ''' use a form to update a user '''
# view = views.EditUser.as_view()
# form = forms.EditUserForm(instance=self.local_user)
# form.data['name'] = 'New Name'
# form.data['email'] = 'wow@email.com'
# image_file = pathlib.Path(__file__).parent.joinpath(
# '../../static/images/no_cover.jpg')
# image = Image.open(image_file)
# form.files['avatar'] = SimpleUploadedFile(
# image_file, open(image_file), content_type='image/jpeg')
# request = self.factory.post('', form.data, form.files)
# request.user = self.local_user
# with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay') \
# as delay_mock:
# view(request)
# self.assertEqual(delay_mock.call_count, 1)
# self.assertEqual(self.local_user.name, 'New Name')
# self.assertEqual(self.local_user.email, 'wow@email.com')
# self.assertIsNotNone(self.local_user.avatar)
# self.assertEqual(self.local_user.avatar.size, (120, 120))
def test_crop_avatar(self):