Merge branch 'main' into production

This commit is contained in:
Mouse Reeve 2021-03-13 15:48:13 -08:00
commit fd3c6d1d21
14 changed files with 131 additions and 49 deletions

View file

@ -69,7 +69,13 @@ class Undo(Verb):
model = None model = None
if self.object.type == "Follow": if self.object.type == "Follow":
model = apps.get_model("bookwyrm.UserFollows") model = apps.get_model("bookwyrm.UserFollows")
obj = self.object.to_model(model=model, save=False, allow_create=False) obj = self.object.to_model(model=model, save=False, allow_create=False)
if not obj:
# this could be a folloq request not a follow proper
model = apps.get_model("bookwyrm.UserFollowRequest")
obj = self.object.to_model(model=model, save=False, allow_create=False)
else:
obj = self.object.to_model(model=model, save=False, allow_create=False)
if not obj: if not obj:
# if we don't have the object, we can't undo it. happens a lot with boosts # if we don't have the object, we can't undo it. happens a lot with boosts
return return

View file

@ -262,9 +262,10 @@ class SearchResult:
title: str title: str
key: str key: str
author: str
year: str
connector: object connector: object
author: str = None
year: str = None
cover: str = None
confidence: int = 1 confidence: int = 1
def __repr__(self): def __repr__(self):

View file

@ -24,5 +24,4 @@ class Connector(AbstractMinimalConnector):
return data return data
def format_isbn_search_result(self, search_result): def format_isbn_search_result(self, search_result):
search_result["connector"] = self return self.format_search_result(search_result)
return SearchResult(**search_result)

View file

@ -95,10 +95,12 @@ class Connector(AbstractConnector):
url = "%s%s" % (self.base_url, author_id) url = "%s%s" % (self.base_url, author_id)
yield self.get_or_create_author(url) yield self.get_or_create_author(url)
def get_cover_url(self, cover_blob): def get_cover_url(self, cover_blob, size="L"):
""" ask openlibrary for the cover """ """ ask openlibrary for the cover """
if not cover_blob:
return None
cover_id = cover_blob[0] cover_id = cover_blob[0]
image_name = "%s-L.jpg" % cover_id image_name = "%s-%s.jpg" % (cover_id, size)
return "%s/b/id/%s" % (self.covers_url, image_name) return "%s/b/id/%s" % (self.covers_url, image_name)
def parse_search_data(self, data): def parse_search_data(self, data):
@ -108,12 +110,15 @@ class Connector(AbstractConnector):
# build the remote id from the openlibrary key # build the remote id from the openlibrary key
key = self.books_url + search_result["key"] key = self.books_url + search_result["key"]
author = search_result.get("author_name") or ["Unknown"] author = search_result.get("author_name") or ["Unknown"]
cover_blob = search_result.get("cover_i")
cover = self.get_cover_url([cover_blob], size="M") if cover_blob else None
return SearchResult( return SearchResult(
title=search_result.get("title"), title=search_result.get("title"),
key=key, key=key,
author=", ".join(author), author=", ".join(author),
connector=self, connector=self,
year=search_result.get("first_publish_year"), year=search_result.get("first_publish_year"),
cover=cover,
) )
def parse_isbn_search_data(self, data): def parse_isbn_search_data(self, data):

View file

@ -67,20 +67,12 @@ class Connector(AbstractConnector):
if search_result.published_date if search_result.published_date
else None, else None,
connector=self, connector=self,
cover="%s%s" % (self.covers_url, search_result.cover),
confidence=search_result.rank if hasattr(search_result, "rank") else 1, confidence=search_result.rank if hasattr(search_result, "rank") else 1,
) )
def format_isbn_search_result(self, search_result): def format_isbn_search_result(self, search_result):
return SearchResult( return self.format_search_result(search_result)
title=search_result.title,
key=search_result.remote_id,
author=search_result.author_text,
year=search_result.published_date.year
if search_result.published_date
else None,
connector=self,
confidence=search_result.rank if hasattr(search_result, "rank") else 1,
)
def is_work_data(self, data): def is_work_data(self, data):
pass pass

View file

@ -76,7 +76,7 @@ def init_connectors():
connector_file="self_connector", connector_file="self_connector",
base_url="https://%s" % DOMAIN, base_url="https://%s" % DOMAIN,
books_url="https://%s/book" % DOMAIN, books_url="https://%s/book" % DOMAIN,
covers_url="https://%s/images/covers" % DOMAIN, covers_url="https://%s/images/" % DOMAIN,
search_url="https://%s/search?q=" % DOMAIN, search_url="https://%s/search?q=" % DOMAIN,
isbn_search_url="https://%s/isbn/" % DOMAIN, isbn_search_url="https://%s/isbn/" % DOMAIN,
priority=1, priority=1,
@ -88,7 +88,7 @@ def init_connectors():
connector_file="bookwyrm_connector", connector_file="bookwyrm_connector",
base_url="https://bookwyrm.social", base_url="https://bookwyrm.social",
books_url="https://bookwyrm.social/book", books_url="https://bookwyrm.social/book",
covers_url="https://bookwyrm.social/images/covers", covers_url="https://bookwyrm.social/images/",
search_url="https://bookwyrm.social/search?q=", search_url="https://bookwyrm.social/search?q=",
isbn_search_url="https://bookwyrm.social/isbn/", isbn_search_url="https://bookwyrm.social/isbn/",
priority=2, priority=2,

View file

@ -78,13 +78,22 @@
</dl> </dl>
<p> <p>
{% if book.physical_format and not book.pages %} {% if book.physical_format and not book.pages %}
{{ book.physical_format | title }} {{ book.physical_format | title }}
{% elif book.physical_format and book.pages %} {% elif book.physical_format and book.pages %}
{% blocktrans with format=book.physical_format|title pages=book.pages %}{{ format }}, {{ pages }} pages{% endblocktrans %} {% blocktrans with format=book.physical_format|title pages=book.pages %}{{ format }}, {{ pages }} pages{% endblocktrans %}
{% elif book.pages %} {% elif book.pages %}
{% blocktrans with pages=book.pages %}{{ pages }} pages{% endblocktrans %} {% blocktrans with pages=book.pages %}{{ pages }} pages{% endblocktrans %}
{% endif %} {% endif %}
</p>
<p>
{% if book.published_date and book.publishers %}
{% blocktrans with date=book.published_date|date:'M jS Y' publisher=book.publishers|join:', ' %}Published {{ date }} by {{ publisher }}.{% endblocktrans %}
{% elif book.published_date %}
{% blocktrans with date=book.published_date|date:'M jS Y' %}Published {{ date }}{% endblocktrans %}
{% else %}
{% blocktrans with publisher=book.publishers|join:', ' %}Published by {{ publisher }}.{% endblocktrans %}
{% endif %}
</p> </p>
{% if book.openlibrary_key %} {% if book.openlibrary_key %}

View file

@ -19,7 +19,7 @@
<ul> <ul>
{% for result in local_results.results %} {% for result in local_results.results %}
<li class="pd-4"> <li class="pd-4">
<a href="{{ result.key }}">{% include 'snippets/search_result_text.html' with result=result link=True %}</a> {% include 'snippets/search_result_text.html' with result=result %}
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
@ -50,12 +50,7 @@
<ul> <ul>
{% for result in result_set.results %} {% for result in result_set.results %}
<li class="pb-4"> <li class="pb-4">
<form action="/resolve-book" method="POST"> {% include 'snippets/search_result_text.html' with result=result remote_result=True %}
{% csrf_token %}
<input type="hidden" name="remote_id" value="{{ result.key }}">
<div>{% include 'snippets/search_result_text.html' with result=result link=False %}</div>
<button type="submit" class="button is-small is-link">{% trans "Import book" %}</button>
</form>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>

View file

@ -1,18 +1,12 @@
{% load i18n %} {% load i18n %}
{% if request.user == user or not request.user.is_authenticated %} {% if request.user == user or not request.user.is_authenticated %}
{% elif request.user in user.follower_requests.all %}
<div>
{% trans "Follow request already sent." %}
</div>
{% elif user in request.user.blocks.all %} {% elif user in request.user.blocks.all %}
{% include 'snippets/block_button.html' %} {% include 'snippets/block_button.html' %}
{% else %} {% else %}
<div class="field has-addons"> <div class="field has-addons">
<div class="control"> <div class="control">
<form action="/follow/" method="POST" class="interaction follow-{{ user.id }} {% if request.user in user.followers.all %}hidden{%endif %}" data-id="follow-{{ user.id }}"> <form action="{% url 'follow' %}" method="POST" class="interaction follow-{{ user.id }} {% if request.user in user.followers.all or request.user in user.follower_requests.all %}hidden{%endif %}" data-id="follow-{{ user.id }}">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="user" value="{{ user.username }}"> <input type="hidden" name="user" value="{{ user.username }}">
{% if user.manually_approves_followers %} {% if user.manually_approves_followers %}
@ -21,10 +15,14 @@
<button class="button is-small is-link" type="submit">{% trans "Follow" %}</button> <button class="button is-small is-link" type="submit">{% trans "Follow" %}</button>
{% endif %} {% endif %}
</form> </form>
<form action="/unfollow/" method="POST" class="interaction follow-{{ user.id }} {% if not request.user in user.followers.all %}hidden{%endif %}" data-id="follow-{{ user.id }}"> <form action="{% url 'unfollow' %}" method="POST" class="interaction follow-{{ user.id }} {% if not request.user in user.followers.all and not request.user in user.follower_requests.all %}hidden{%endif %}" data-id="follow-{{ user.id }}">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="user" value="{{ user.username }}"> <input type="hidden" name="user" value="{{ user.username }}">
{% if user.manually_approves_followers and request.user not in user.followers.all %}
<button class="button is-small is-danger is-light" type="submit">{% trans "Undo follow request" %}</button>
{% else %}
<button class="button is-small is-danger is-light" type="submit">{% trans "Unfollow" %}</button> <button class="button is-small is-danger is-light" type="submit">{% trans "Unfollow" %}</button>
{% endif %}
</form> </form>
</div> </div>
<div class="control"> <div class="control">

View file

@ -1,3 +1,34 @@
{% load i18n %} {% load i18n %}
<strong>{% if link %}<a href="{{ result.key }}">{{ result.title }}</a>{% else %}{{ result.title }}{% endif %}</strong> <div class="columns is-mobile">
{% if result.author %} {% blocktrans with author=result.author %}by {{ author }}{% endblocktrans %}{% endif %}{% if result.year %} ({{ result.year }}){% endif %} <div class="cover-container is-small column is-2">
{% if result.cover %}
<img src="{{ result.cover }}" class="book-cover" aria-hidden="true">
{% else %}
<div class="no-cover book-cover">
<img class="book-cover" src="/static/images/no_cover.jpg" aria-hidden="true">
<div>
<p>{% trans "No cover" %}</p>
</div>
</div>
{% endif %}
</div>
<div class="column">
<p>
<strong>
<a href="{{ result.key }}"{% if remote_result %} rel=”noopener” target="_blank"{% endif %}>{{ result.title }}</a>
</strong>
{% if result.author %}
{% blocktrans with author=result.author %}by {{ author }}{% endblocktrans %}{% endif %}{% if result.year %} ({{ result.year }})
{% endif %}
</p>
{% if remote_result %}
<form action="/resolve-book" method="POST">
{% csrf_token %}
<input type="hidden" name="remote_id" value="{{ result.key }}">
<button type="submit" class="button is-small is-link">{% trans "Import book" %}</button>
</form>
{% endif %}
</div>
</div>

View file

@ -1,5 +1,7 @@
""" test for app action functionality """ """ test for app action functionality """
import json
from unittest.mock import patch from unittest.mock import patch
from django.contrib.auth.models import Group, Permission from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.test import TestCase from django.test import TestCase
@ -117,6 +119,8 @@ class BookViews(TestCase):
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock: with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
views.unfollow(request) views.unfollow(request)
self.assertEqual(mock.call_count, 1) self.assertEqual(mock.call_count, 1)
activity = json.loads(mock.call_args_list[0][0][1])
self.assertEqual(activity["type"], "Undo")
self.assertEqual(self.remote_user.followers.count(), 0) self.assertEqual(self.remote_user.followers.count(), 0)

View file

@ -275,6 +275,36 @@ class Inbox(TestCase):
follow = models.UserFollows.objects.all() follow = models.UserFollows.objects.all()
self.assertEqual(list(follow), []) self.assertEqual(list(follow), [])
def test_handle_undo_follow_request(self):
""" the requester cancels a follow request """
self.local_user.manually_approves_followers = True
self.local_user.save(broadcast=False)
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
request = models.UserFollowRequest.objects.create(
user_subject=self.remote_user, user_object=self.local_user
)
self.assertTrue(self.local_user.follower_requests.exists())
activity = {
"type": "Undo",
"id": "bleh",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"cc": ["https://example.com/user/mouse/followers"],
"actor": self.remote_user.remote_id,
"@context": "https://www.w3.org/ns/activitystreams",
"object": {
"@context": "https://www.w3.org/ns/activitystreams",
"id": request.remote_id,
"type": "Follow",
"actor": "https://example.com/users/rat",
"object": "https://example.com/user/mouse",
},
}
views.inbox.activity_task(activity)
self.assertFalse(self.local_user.follower_requests.exists())
def test_handle_unfollow(self): def test_handle_unfollow(self):
""" remove a relationship """ """ remove a relationship """
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):

View file

@ -188,8 +188,8 @@ urlpatterns = [
re_path(r"^start-reading/(?P<book_id>\d+)/?$", views.start_reading), re_path(r"^start-reading/(?P<book_id>\d+)/?$", views.start_reading),
re_path(r"^finish-reading/(?P<book_id>\d+)/?$", views.finish_reading), re_path(r"^finish-reading/(?P<book_id>\d+)/?$", views.finish_reading),
# following # following
re_path(r"^follow/?$", views.follow), re_path(r"^follow/?$", views.follow, name="follow"),
re_path(r"^unfollow/?$", views.unfollow), re_path(r"^unfollow/?$", views.unfollow, name="unfollow"),
re_path(r"^accept-follow-request/?$", views.accept_follow_request), re_path(r"^accept-follow-request/?$", views.accept_follow_request),
re_path(r"^delete-follow-request/?$", views.delete_follow_request), re_path(r"^delete-follow-request/?$", views.delete_follow_request),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View file

@ -40,10 +40,22 @@ def unfollow(request):
except models.User.DoesNotExist: except models.User.DoesNotExist:
return HttpResponseBadRequest() return HttpResponseBadRequest()
models.UserFollows.objects.get( try:
user_subject=request.user, user_object=to_unfollow models.UserFollows.objects.get(
).delete() user_subject=request.user, user_object=to_unfollow
return redirect(to_unfollow.local_path) ).delete()
except models.UserFollows.DoesNotExist:
pass
try:
models.UserFollowRequest.objects.get(
user_subject=request.user, user_object=to_unfollow
).delete()
except models.UserFollowRequest.DoesNotExist:
pass
# this is handled with ajax so it shouldn't really matter
return redirect(request.headers.get("Referer", "/"))
@login_required @login_required