mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-12-12 03:06:32 +00:00
Merge branch 'main' into production
This commit is contained in:
commit
355b2fad35
13 changed files with 120 additions and 18 deletions
|
@ -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
|
||||
|
|
|
@ -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")' % \
|
||||
|
|
|
@ -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 '''
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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 %}
|
||||
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in a new issue